diff --git a/.githooks/commit-msg b/.githooks/commit-msg
index 3aa02e9cad751f83b8b00c68c420b8a6c52751c8..da2c1a045998a53ebc71afcd46ed2b1c055182b3 100755
--- a/.githooks/commit-msg
+++ b/.githooks/commit-msg
@@ -106,8 +106,8 @@ msg_gerrit() {
 	die 'The Change-Id line must appear in a footer at the bottom.'
 }
 
-# First check that a ticket is referenced
-msg_trac
+# First check that a ticket is referenced - disable for now
+#msg_trac
 # Pipe commit message into the state machine.
 state=first
 cat "$commit_msg" |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cfd352c88f44017558cecbad027539cb667a7d15..fe419689dc47026419b2fece92e86a4dc5c9df33 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -415,7 +415,7 @@ if ( ENABLE_CPACK )
                                            "python-six,"
                                            "python-matplotlib,"
                                            "python-scipy,"
-                                           "python-skimage"
+                                           "python-skimage,"
                                            "python-pycifrw (>= 4.2.1),"
                                            "python-yaml,"
                                            "python-qtawesome,"
diff --git a/Framework/API/CMakeLists.txt b/Framework/API/CMakeLists.txt
index 5cf25c750d09a7725241e29a9765dc93c935e112..25d900ed64550c354568ac11c5bcdcd27f017c61 100644
--- a/Framework/API/CMakeLists.txt
+++ b/Framework/API/CMakeLists.txt
@@ -9,6 +9,7 @@ set ( SRC_FILES
 	src/AlgorithmProperty.cpp
 	src/AlgorithmProxy.cpp
 	src/AnalysisDataService.cpp
+	src/AnalysisDataServiceObserver.cpp
 	src/ArchiveSearchFactory.cpp
 	src/Axis.cpp
 	src/BinEdgeAxis.cpp
@@ -175,6 +176,7 @@ set ( INC_FILES
 	inc/MantidAPI/AlgorithmProperty.h
 	inc/MantidAPI/AlgorithmProxy.h
 	inc/MantidAPI/AnalysisDataService.h
+	inc/MantidAPI/AnalysisDataServiceObserver.h
 	inc/MantidAPI/ArchiveSearchFactory.h
 	inc/MantidAPI/Axis.h
 	inc/MantidAPI/BinEdgeAxis.h
@@ -374,6 +376,7 @@ set ( TEST_FILES
 	AlgorithmProxyTest.h
 	AlgorithmTest.h
 	AnalysisDataServiceTest.h
+	AnalysisDataServiceObserverTest.h
 	AsynchronousTest.h
 	BinEdgeAxisTest.h
 	BoxControllerTest.h
@@ -526,4 +529,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS API ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS API INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/API/inc/MantidAPI/Algorithm.h b/Framework/API/inc/MantidAPI/Algorithm.h
index 22c0052248afc5cbd5ed379f1b10cba6354d6ac6..ccc6592a715bacc95c4f3a35eeb2908292ee73c2 100644
--- a/Framework/API/inc/MantidAPI/Algorithm.h
+++ b/Framework/API/inc/MantidAPI/Algorithm.h
@@ -54,6 +54,7 @@ namespace API {
 //----------------------------------------------------------------------
 class AlgorithmProxy;
 class AlgorithmHistory;
+class WorkspaceHistory;
 
 /**
 Base class from which all concrete algorithm classes should be derived.
@@ -292,8 +293,8 @@ public:
 
   using WorkspaceVector = std::vector<boost::shared_ptr<Workspace>>;
 
-  void findWorkspaceProperties(WorkspaceVector &inputWorkspaces,
-                               WorkspaceVector &outputWorkspaces) const;
+  void findWorkspaces(WorkspaceVector &workspaces, unsigned int direction,
+                      bool checkADS = false) const;
 
   // ------------------ For WorkspaceGroups ------------------------------------
   virtual bool checkGroups();
@@ -323,6 +324,7 @@ protected:
   virtual const std::string workspaceMethodOnTypes() const { return ""; }
 
   void cacheWorkspaceProperties();
+  void cacheInputWorkspaceHistories();
 
   friend class AlgorithmProxy;
   void initializeFromProxy(const AlgorithmProxy &);
@@ -388,8 +390,9 @@ protected:
   /// Pointer to the parent history object (if set)
   boost::shared_ptr<AlgorithmHistory> m_parentHistory;
 
-  /// One vector of workspaces for each input workspace property
-  std::vector<WorkspaceVector> m_groups;
+  /// One vector of workspaces for each input workspace property. A group is
+  /// unrolled to its constituent members
+  std::vector<WorkspaceVector> m_unrolledInputWorkspaces;
   /// Size of the group(s) being processed
   size_t m_groupSize;
   /// distinguish between base processGroups() and overriden/algorithm specific
@@ -419,6 +422,8 @@ private:
 
   bool doCallProcessGroups(Mantid::Types::Core::DateAndTime &start_time);
 
+  void fillHistory(const std::vector<Workspace_sptr> &outputWorkspaces);
+
   // Report that the algorithm has completed.
   void reportCompleted(const double &duration,
                        const bool groupProcessing = false);
@@ -432,13 +437,6 @@ private:
 
   bool isCompoundProperty(const std::string &name) const;
 
-  bool hasAnADSValidator(const Mantid::Kernel::IValidator_sptr propProp) const;
-
-  void constructWorkspaceVectorForHistoryHelper(
-      std::vector<Workspace_sptr> &inputWorkspaces,
-      std::vector<Workspace_sptr> &outputWorkspaces,
-      const unsigned int direction, std::string &currentWS) const;
-
   // --------------------- Private Members -----------------------------------
   /// Poco::ActiveMethod used to implement asynchronous execution.
   std::unique_ptr<Poco::ActiveMethod<bool, Poco::Void, Algorithm,
@@ -492,7 +490,11 @@ private:
   int m_singleGroup;
   /// All the groups have similar names (group_1, group_2 etc.)
   bool m_groupsHaveSimilarNames;
+  /// Store a pointer to the input workspace histories so they can be copied to
+  /// the outputs to avoid anything being overwritten
+  std::vector<Workspace_sptr> m_inputWorkspaceHistories;
 
+  /// Reserved property names
   std::vector<std::string> m_reservedList;
 
   /// (MPI) communicator used when executing the algorithm.
diff --git a/Framework/API/inc/MantidAPI/AnalysisDataServiceObserver.h b/Framework/API/inc/MantidAPI/AnalysisDataServiceObserver.h
new file mode 100644
index 0000000000000000000000000000000000000000..3445102cf040df86b47bd80f70a6ef12da118b8d
--- /dev/null
+++ b/Framework/API/inc/MantidAPI/AnalysisDataServiceObserver.h
@@ -0,0 +1,140 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_KERNEL_ANALYSISDATASERVICEOBSERVER_H_
+#define MANTID_KERNEL_ANALYSISDATASERVICEOBSERVER_H_
+
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/DllConfig.h"
+#include "MantidKernel/DataService.h"
+#include <Poco/NObserver.h>
+
+using namespace Mantid::Kernel;
+using namespace Mantid::API;
+
+namespace Mantid {
+namespace API {
+
+/*
+ * To use the AnalysisDataServiceObserver you will need to do a few things:
+ *
+ * 1. Inherit from this class in the class you wish to take effect on
+ *
+ * 2. Make sure that the effect you are attempting to observe has been added
+ * to the AnalysisDataService itself by using the public method in this
+ * class, e.g. observeAll, observeAdd, observeReplace etc.
+ *
+ * 3. The last thing to actually have something take effect is by overriding
+ * the relevant handle function e.g. when observing all override
+ * anyChangeHandle and anything done in that overriden method will happen
+ * every time something changes in the AnalysisDataService.
+ *
+ * This works in both C++ and Python, some functionality is limited in
+ * python, but the handlers will all be called.
+ */
+
+class MANTID_API_DLL AnalysisDataServiceObserver {
+public:
+  AnalysisDataServiceObserver();
+  virtual ~AnalysisDataServiceObserver();
+
+  void observeAll(bool turnOn = true);
+  void observeAdd(bool turnOn = true);
+  void observeReplace(bool turnOn = true);
+  void observeDelete(bool turnOn = true);
+  void observeClear(bool turnOn = true);
+  void observeRename(bool turnOn = true);
+  void observeGroup(bool turnOn = true);
+  void observeUnGroup(bool turnOn = true);
+  void observeGroupUpdate(bool turnOn = true);
+
+  virtual void anyChangeHandle();
+  virtual void addHandle(const std::string &wsName, const Workspace_sptr &ws);
+  virtual void replaceHandle(const std::string &wsName,
+                             const Workspace_sptr &ws);
+  virtual void deleteHandle(const std::string &wsName,
+                            const Workspace_sptr &ws);
+  virtual void clearHandle();
+  virtual void renameHandle(const std::string &wsName,
+                            const std::string &newName);
+  virtual void groupHandle(const std::string &wsName, const Workspace_sptr &ws);
+  virtual void unGroupHandle(const std::string &wsName,
+                             const Workspace_sptr &ws);
+  virtual void groupUpdateHandle(const std::string &wsName,
+                                 const Workspace_sptr &ws);
+
+private:
+  bool m_observingAdd{false}, m_observingReplace{false},
+      m_observingDelete{false}, m_observingClear{false},
+      m_observingRename{false}, m_observingGroup{false},
+      m_observingUnGroup{false}, m_observingGroupUpdate{false};
+
+  void _addHandle(
+      const Poco::AutoPtr<AnalysisDataServiceImpl::AddNotification> &pNf);
+  void _replaceHandle(
+      const Poco::AutoPtr<AnalysisDataServiceImpl::AfterReplaceNotification>
+          &pNf);
+  void _deleteHandle(
+      const Poco::AutoPtr<AnalysisDataServiceImpl::PreDeleteNotification> &pNf);
+  void _clearHandle(
+      const Poco::AutoPtr<AnalysisDataServiceImpl::ClearNotification> &pNf);
+  void _renameHandle(
+      const Poco::AutoPtr<AnalysisDataServiceImpl::RenameNotification> &pNf);
+  void _groupHandle(
+      const Poco::AutoPtr<AnalysisDataServiceImpl::GroupWorkspacesNotification>
+          &pNf);
+  void _unGroupHandle(
+      const Poco::AutoPtr<
+          AnalysisDataServiceImpl::UnGroupingWorkspaceNotification> &pNf);
+  void _groupUpdateHandle(
+      const Poco::AutoPtr<AnalysisDataServiceImpl::GroupUpdatedNotification>
+          &pNf);
+
+  /// Poco::NObserver for AddNotification.
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::AddNotification>
+      m_addObserver;
+
+  /// Poco::NObserver for ReplaceNotification.
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::AfterReplaceNotification>
+      m_replaceObserver;
+
+  /// Poco::NObserver for DeleteNotification.
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::PreDeleteNotification>
+      m_deleteObserver;
+
+  /// Poco::NObserver for ClearNotification
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::ClearNotification>
+      m_clearObserver;
+
+  /// Poco::NObserver for RenameNotification
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::RenameNotification>
+      m_renameObserver;
+
+  /// Poco::NObserver for GroupNotification
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::GroupWorkspacesNotification>
+      m_groupObserver;
+
+  /// Poco::NObserver for UnGroupNotification
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::UnGroupingWorkspaceNotification>
+      m_unGroupObserver;
+
+  /// Poco::NObserver for GroupUpdateNotification
+  Poco::NObserver<AnalysisDataServiceObserver,
+                  AnalysisDataServiceImpl::GroupUpdatedNotification>
+      m_groupUpdatedObserver;
+};
+
+} // namespace API
+} // namespace Mantid
+
+#endif /*MANTID_KERNEL_ANALYSISDATASERVICEOBSERVER_H_*/
\ No newline at end of file
diff --git a/Framework/API/inc/MantidAPI/FileFinder.h b/Framework/API/inc/MantidAPI/FileFinder.h
index 7e12fc77af914edb22b09ff4796a79811a49f742..5ce8f4be1c95f4e7bc55a2ff6c5ae3e59ad3a3d2 100644
--- a/Framework/API/inc/MantidAPI/FileFinder.h
+++ b/Framework/API/inc/MantidAPI/FileFinder.h
@@ -49,16 +49,18 @@ public:
   std::vector<IArchiveSearch_sptr>
   getArchiveSearch(const Kernel::FacilityInfo &facility) const;
   std::string findRun(const std::string &hintstr,
-                      const std::set<std::string> &exts) const;
-  std::string findRun(
-      const std::string &hintstr,
-      const std::vector<std::string> &exts = std::vector<std::string>()) const;
-  std::vector<std::string> findRuns(const std::string &hintstr) const;
+                      const std::vector<std::string> &exts = {},
+                      const bool useExtsOnly = false) const;
+  std::vector<std::string> findRuns(const std::string &hintstr,
+                                    const std::vector<std::string> &exts = {},
+                                    const bool useExtsOnly = false) const;
   /// DO NOT USE! MADE PUBLIC FOR TESTING ONLY.
   const Kernel::InstrumentInfo getInstrument(const std::string &hint) const;
   /// DO NOT USE! MADE PUBLIC FOR TESTING ONLY.
   std::string getExtension(const std::string &filename,
                            const std::vector<std::string> &exts) const;
+  void getUniqueExtensions(const std::vector<std::string> &extensionsToAdd,
+                           std::vector<std::string> &uniqueExts) const;
 
 private:
   friend struct Mantid::Kernel::CreateUsingNew<FileFinderImpl>;
diff --git a/Framework/API/inc/MantidAPI/IEventList.h b/Framework/API/inc/MantidAPI/IEventList.h
index 5e699f6b5109af5ab99adf11a6a0bd7c93f68a65..f58454a32197ea77cb6a9da449fc683024a2db45 100644
--- a/Framework/API/inc/MantidAPI/IEventList.h
+++ b/Framework/API/inc/MantidAPI/IEventList.h
@@ -77,6 +77,8 @@ public:
   virtual void addPulsetime(const double seconds) = 0;
   /// Mask a given TOF range
   virtual void maskTof(const double tofMin, const double tofMax) = 0;
+  /// Mask the events by the condition vector
+  virtual void maskCondition(const std::vector<bool> &mask) = 0;
   /// Return the list of TOF values
   virtual std::vector<double> getTofs() const = 0;
   /// Return the list of TOF values
diff --git a/Framework/API/inc/MantidAPI/Sample.h b/Framework/API/inc/MantidAPI/Sample.h
index 9e7f7ba256f26c351cb7b0b535a2a0cea4fd1713..51f90f8ce8e1f74b1854aad2fff2a0f31e7490ce 100644
--- a/Framework/API/inc/MantidAPI/Sample.h
+++ b/Framework/API/inc/MantidAPI/Sample.h
@@ -13,7 +13,6 @@
 #include "MantidAPI/DllConfig.h"
 #include "MantidGeometry/Objects/CSGObject.h"
 #include "MantidKernel/V3D.h"
-#include <vector>
 
 namespace Mantid {
 //-----------------------------------------------------------------------------
@@ -69,7 +68,7 @@ public:
   /// Get a reference to the sample's environment
   const Geometry::SampleEnvironment &getEnvironment() const;
   /// Set the environment used to contain the sample
-  void setEnvironment(Geometry::SampleEnvironment *env);
+  void setEnvironment(std::unique_ptr<Geometry::SampleEnvironment> env);
   //@}
 
   /** @name Access the sample's lattice structure and orientation */
diff --git a/Framework/API/src/Algorithm.cpp b/Framework/API/src/Algorithm.cpp
index bca85de4090054db07337f38aac8286666a32455..297c8c3bcf105b8beb58e3215639af29817c06b6 100644
--- a/Framework/API/src/Algorithm.cpp
+++ b/Framework/API/src/Algorithm.cpp
@@ -44,6 +44,7 @@
 #include "MantidAPI/Algorithm.tcc"
 
 using namespace Mantid::Kernel;
+using VectorStringProperty = PropertyWithValue<std::vector<std::string>>;
 
 namespace Mantid {
 namespace API {
@@ -54,7 +55,7 @@ const std::string WORKSPACE_TYPES_SEPARATOR = ";";
 class WorkspacePropertyValueIs {
 public:
   explicit WorkspacePropertyValueIs(const std::string &value)
-      : m_value(value){};
+      : m_value(value) {}
   bool operator()(IWorkspaceProperty *property) {
     Property *prop = dynamic_cast<Property *>(property);
     if (!prop)
@@ -103,7 +104,7 @@ Algorithm::Algorithm()
       m_runningAsync(false), m_running(false), m_rethrow(false),
       m_isAlgStartupLoggingEnabled(true), m_startChildProgress(0.),
       m_endChildProgress(0.), m_algorithmID(this), m_singleGroup(-1),
-      m_groupsHaveSimilarNames(false),
+      m_groupsHaveSimilarNames(false), m_inputWorkspaceHistories(),
       m_communicator(Kernel::make_unique<Parallel::Communicator>()) {}
 
 /// Virtual destructor
@@ -252,11 +253,6 @@ const std::vector<std::string> Algorithm::workspaceMethodOn() const {
  */
 const std::string Algorithm::workspaceMethodInputProperty() const { return ""; }
 
-//=============================================================================================
-//================================== Initialization
-//===========================================
-//=============================================================================================
-
 //---------------------------------------------------------------------------------------------
 /** Initialization method invoked by the framework. This method is responsible
  *  for any bookkeeping of initialization required by the framework itself.
@@ -312,43 +308,104 @@ std::map<std::string, std::string> Algorithm::validateInputs() {
 }
 
 //---------------------------------------------------------------------------------------------
-/** Go through the properties and cache the input/output
+/**
+ * Go through the properties and cache the input/output
  * workspace properties for later use.
  */
 void Algorithm::cacheWorkspaceProperties() {
-  // Cache the list of the in/out workspace properties
   m_inputWorkspaceProps.clear();
   m_outputWorkspaceProps.clear();
   m_pureOutputWorkspaceProps.clear();
-  const std::vector<Property *> &props = this->getProperties();
-  for (auto prop : props) {
-    IWorkspaceProperty *wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
-    if (wsProp) {
-      switch (prop->direction()) {
-      case Kernel::Direction::Input:
-        m_inputWorkspaceProps.push_back(wsProp);
-        break;
-      case Kernel::Direction::InOut:
-        m_inputWorkspaceProps.push_back(wsProp);
-        m_outputWorkspaceProps.push_back(wsProp);
-        break;
-      case Kernel::Direction::Output:
-        m_outputWorkspaceProps.push_back(wsProp);
-        m_pureOutputWorkspaceProps.push_back(wsProp);
-        break;
-      default:
-        throw std::logic_error(
-            "Unexpected property direction found for property " + prop->name() +
-            " of algorithm " + this->name());
-      }
-    } // is a ws property
-  }   // each property
+  const auto &props = this->getProperties();
+  for (const auto &prop : props) {
+    auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
+    if (!wsProp)
+      continue;
+    switch (prop->direction()) {
+    case Kernel::Direction::Input:
+      m_inputWorkspaceProps.push_back(wsProp);
+      break;
+    case Kernel::Direction::InOut:
+      m_inputWorkspaceProps.push_back(wsProp);
+      m_outputWorkspaceProps.push_back(wsProp);
+      break;
+    case Kernel::Direction::Output:
+      m_outputWorkspaceProps.push_back(wsProp);
+      m_pureOutputWorkspaceProps.push_back(wsProp);
+      break;
+    default:
+      throw std::logic_error(
+          "Unexpected property direction found for property " + prop->name() +
+          " of algorithm " + this->name());
+    }
+  }
 }
 
-//=============================================================================================
-//================================== Execution
-//================================================
-//=============================================================================================
+/**
+ * Cache the histories of any input workspaces so they can be copied over after
+ * algorithm completion.
+ */
+void Algorithm::cacheInputWorkspaceHistories() {
+  if (!trackingHistory())
+    return;
+
+  auto cacheHistories = [this](const Workspace_sptr &ws) {
+    if (auto group = dynamic_cast<const WorkspaceGroup *>(ws.get())) {
+      m_inputWorkspaceHistories.reserve(m_inputWorkspaceHistories.size() +
+                                        group->size());
+      for (const auto &memberWS : *group) {
+        m_inputWorkspaceHistories.emplace_back(memberWS);
+      }
+    } else {
+      m_inputWorkspaceHistories.emplace_back(ws);
+    }
+  };
+  using ArrayPropertyString = ArrayProperty<std::string>;
+  auto isADSValidator = [](const IValidator_sptr &validator) -> bool {
+    if (!validator)
+      return false;
+    if (dynamic_cast<ADSValidator *>(validator.get()))
+      return true;
+    if (const auto compValidator =
+            dynamic_cast<CompositeValidator *>(validator.get()))
+      return compValidator->contains<ADSValidator>();
+
+    return false;
+  };
+
+  // Look over all properties so we can catch an string array properties
+  // with an ADSValidator. ADSValidator indicates that the strings
+  // point to workspace names so we want to pick up the history from these too.
+  const auto &ads = AnalysisDataService::Instance();
+  m_inputWorkspaceHistories.clear();
+  const auto &props = this->getProperties();
+  for (const auto &prop : props) {
+    if (prop->direction() != Direction::Input &&
+        prop->direction() != Direction::InOut)
+      continue;
+
+    if (auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop)) {
+      if (auto ws = wsProp->getWorkspace()) {
+        cacheHistories(ws);
+      } else {
+        Workspace_sptr wsFromADS;
+        try {
+          wsFromADS = ads.retrieve(prop->value());
+        } catch (Exception::NotFoundError &) {
+          continue;
+        }
+        cacheHistories(wsFromADS);
+      }
+    } else if (auto strArrayProp = dynamic_cast<ArrayPropertyString *>(prop)) {
+      if (!isADSValidator(strArrayProp->getValidator()))
+        continue;
+      const auto &wsNames((*strArrayProp)());
+      for (const auto &name : wsNames) {
+        cacheHistories(ads.retrieve(name));
+      }
+    }
+  }
+} // namespace API
 
 //---------------------------------------------------------------------------------------------
 /** Go through the workspace properties of this algorithm
@@ -457,9 +514,6 @@ bool Algorithm::execute() {
     throw std::runtime_error("Algorithm is not initialised:" + this->name());
   }
 
-  // Cache the workspace in/out properties for later use
-  cacheWorkspaceProperties();
-
   // no logging of input if a child algorithm (except for python child algos)
   if (!m_isChildAlgorithm || m_alwaysStoreInADS)
     logAlgorithmInfo();
@@ -469,9 +523,9 @@ bool Algorithm::execute() {
   float timingInit = timer.elapsed(resetTimer);
   if (!validateProperties()) {
     // Reset name on input workspaces to trigger attempt at collection from ADS
-    const std::vector<Property *> &props = getProperties();
+    const auto &props = getProperties();
     for (auto &prop : props) {
-      IWorkspaceProperty *wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
+      auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
       if (wsProp && !(wsProp->getWorkspace())) {
         // Setting it's name to the same one it already had
         prop->setValue(prop->value());
@@ -486,6 +540,10 @@ bool Algorithm::execute() {
   }
   const float timingPropertyValidation = timer.elapsed(resetTimer);
 
+  // All properties are now valid - cache workspace properties and histories
+  cacheWorkspaceProperties();
+  cacheInputWorkspaceHistories();
+
   // ----- Check for processing groups -------------
   // default true so that it has the right value at the check below the catch
   // block should checkGroups throw
@@ -569,6 +627,7 @@ bool Algorithm::execute() {
   // Invoke exec() method of derived class and catch all uncaught exceptions
   try {
     try {
+      setExecuted(false);
       if (!isChild()) {
         m_running = true;
       }
@@ -710,7 +769,7 @@ void Algorithm::store() {
 
   // add any regular/child workspaces first, then add the groups
   for (unsigned int i = 0; i < props.size(); ++i) {
-    IWorkspaceProperty *wsProp = dynamic_cast<IWorkspaceProperty *>(props[i]);
+    auto *wsProp = dynamic_cast<IWorkspaceProperty *>(props[i]);
     if (wsProp) {
       // check if the workspace is a group, if so remember where it is and add
       // it later
@@ -732,8 +791,7 @@ void Algorithm::store() {
   std::vector<int>::const_iterator wsIndex;
   for (wsIndex = groupWsIndicies.begin(); wsIndex != groupWsIndicies.end();
        ++wsIndex) {
-    IWorkspaceProperty *wsProp =
-        dynamic_cast<IWorkspaceProperty *>(props[*wsIndex]);
+    auto *wsProp = dynamic_cast<IWorkspaceProperty *>(props[*wsIndex]);
     if (wsProp) {
       try {
         wsProp->store();
@@ -948,54 +1006,11 @@ void Algorithm::initializeFromProxy(const AlgorithmProxy &proxy) {
 /** Fills History, Algorithm History and Algorithm Parameters
  */
 void Algorithm::fillHistory() {
-  // this is not a child algorithm. Add the history algorithm to the
-  // WorkspaceHistory object.
+  WorkspaceVector outputWorkspaces;
   if (!isChild()) {
-    // Create two vectors to hold a list of pointers to the input & output
-    // workspaces (InOut's go in both)
-    std::vector<Workspace_sptr> inputWorkspaces, outputWorkspaces;
-    std::vector<Workspace_sptr>::iterator outWS;
-    std::vector<Workspace_sptr>::const_iterator inWS;
-
-    findWorkspaceProperties(inputWorkspaces, outputWorkspaces);
-
-    // Loop over the output workspaces
-    for (outWS = outputWorkspaces.begin(); outWS != outputWorkspaces.end();
-         ++outWS) {
-      WorkspaceGroup_sptr wsGroup =
-          boost::dynamic_pointer_cast<WorkspaceGroup>(*outWS);
-
-      // Loop over the input workspaces, making the call that copies their
-      // history to the output ones
-      // (Protection against copy to self is in
-      // WorkspaceHistory::copyAlgorithmHistory)
-      for (inWS = inputWorkspaces.begin(); inWS != inputWorkspaces.end();
-           ++inWS) {
-        (*outWS)->history().addHistory((*inWS)->getHistory());
-
-        // Add history to each child of output workspace group
-        if (wsGroup) {
-          for (size_t i = 0; i < wsGroup->size(); i++) {
-            wsGroup->getItem(i)->history().addHistory((*inWS)->getHistory());
-          }
-        }
-      }
-
-      // Add the history for the current algorithm to all the output workspaces
-      (*outWS)->history().addHistory(m_history);
-
-      // Add history to each child of output workspace group
-      if (wsGroup) {
-        for (size_t i = 0; i < wsGroup->size(); i++) {
-          wsGroup->getItem(i)->history().addHistory(m_history);
-        }
-      }
-    }
-  }
-  // this is a child algorithm, but we still want to keep the history.
-  else if (m_recordHistoryForChild && m_parentHistory) {
-    m_parentHistory->addChildHistory(m_history);
+    findWorkspaces(outputWorkspaces, Direction::Output);
   }
+  fillHistory(outputWorkspaces);
 }
 
 /**
@@ -1007,42 +1022,40 @@ void Algorithm::fillHistory() {
  *can be re-run.
  */
 void Algorithm::linkHistoryWithLastChild() {
-  if (m_recordHistoryForChild) {
-    // iterate over the algorithms output workspaces
-    const std::vector<Property *> &algProperties = getProperties();
-    std::vector<Property *>::const_iterator it;
-    for (it = algProperties.begin(); it != algProperties.end(); ++it) {
-      const IWorkspaceProperty *outputProp =
-          dynamic_cast<IWorkspaceProperty *>(*it);
-      if (outputProp) {
-        // Check we actually have a workspace, it may have been optional
-        Workspace_sptr workspace = outputProp->getWorkspace();
-        if (!workspace)
-          continue;
+  if (!m_recordHistoryForChild)
+    return;
 
-        // Check it's an output workspace
-        if ((*it)->direction() == Kernel::Direction::Output ||
-            (*it)->direction() == Kernel::Direction::InOut) {
-          bool linked = false;
-          // find child histories with anonymous output workspaces
-          auto childHistories = m_history->getChildHistories();
-          auto childIter = childHistories.rbegin();
-          for (; childIter != childHistories.rend() && !linked; ++childIter) {
-            auto props = (*childIter)->getProperties();
-            auto propIter = props.begin();
-            for (; propIter != props.end() && !linked; ++propIter) {
-              // check we have a workspace property
-              if ((*propIter)->direction() == Kernel::Direction::Output ||
-                  (*propIter)->direction() == Kernel::Direction::InOut) {
-                // if the workspaces are equal, then rename the history
-                std::ostringstream os;
-                os << "__TMP" << outputProp->getWorkspace().get();
-                if (os.str() == (*propIter)->value()) {
-                  (*propIter)->setValue((*it)->value());
-                  linked = true;
-                }
-              }
-            }
+  // iterate over the algorithms output workspaces
+  const auto &algProperties = getProperties();
+  for (const auto &prop : algProperties) {
+    if (prop->direction() != Kernel::Direction::Output &&
+        prop->direction() != Kernel::Direction::InOut)
+      continue;
+    const auto *wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
+    if (!wsProp)
+      continue;
+    // Check we actually have a workspace, it may have been optional
+    Workspace_sptr workspace = wsProp->getWorkspace();
+    if (!workspace)
+      continue;
+
+    bool linked = false;
+    // find child histories with anonymous output workspaces
+    const auto &childHistories = m_history->getChildHistories();
+    auto childIter = childHistories.rbegin();
+    for (; childIter != childHistories.rend() && !linked; ++childIter) {
+      const auto &props = (*childIter)->getProperties();
+      auto propIter = props.begin();
+      for (; propIter != props.end() && !linked; ++propIter) {
+        // check we have a workspace property
+        if ((*propIter)->direction() == Kernel::Direction::Output ||
+            (*propIter)->direction() == Kernel::Direction::InOut) {
+          // if the workspaces are equal, then rename the history
+          std::ostringstream os;
+          os << "__TMP" << wsProp->getWorkspace().get();
+          if (os.str() == (*propIter)->value()) {
+            (*propIter)->setValue(prop->value());
+            linked = true;
           }
         }
       }
@@ -1060,111 +1073,57 @@ void Algorithm::trackAlgorithmHistory(
   m_parentHistory = parentHist;
 }
 
-/** Check if we are tracking history for thus algorithm
+/** Check if we are tracking history for this algorithm
  *  @return if we are tracking the history of this algorithm
  */
 bool Algorithm::trackingHistory() {
   return (!isChild() || m_recordHistoryForChild);
 }
 
-/** Populate lists of the input & output workspace properties.
- *  (InOut workspaces go in both lists)
- *  @param inputWorkspaces ::  A reference to a vector for the input workspaces
- *  @param outputWorkspaces :: A reference to a vector for the output workspaces
+/** Populate lists of the workspace properties for a given direction
+ *  (InOut workspaces are included in both input/output)
+ * @param workspaces A reference to a vector for the workspaces
+ * @param direction The direction of the property required for the search
+ * @param checkADS If true, check the ADS for workspace references
+ * if the check on the workspace property value is empty. Most useful for
+ * finding group workspaces that are never stored on the property
  */
-void Algorithm::findWorkspaceProperties(
-    std::vector<Workspace_sptr> &inputWorkspaces,
-    std::vector<Workspace_sptr> &outputWorkspaces) const {
-  // Loop over properties looking for the workspace properties and putting them
-  // in the right list
-  const std::vector<Property *> &algProperties = getProperties();
-  std::vector<Property *>::const_iterator it;
-  for (it = algProperties.begin(); it != algProperties.end(); ++it) {
-    const IWorkspaceProperty *wsProp = dynamic_cast<IWorkspaceProperty *>(*it);
-    if (wsProp) {
-      const Property *wsPropProp = dynamic_cast<Property *>(*it);
-      // Check we actually have a workspace, it may have been optional
-      Workspace_sptr workspace = wsProp->getWorkspace();
-      if (!workspace)
-        continue;
-      unsigned int direction = wsPropProp->direction();
-      if (direction == Direction::Input || direction == Direction::InOut) {
-        inputWorkspaces.emplace_back(workspace);
-      }
-      if (direction == Direction::Output || direction == Direction::InOut) {
-        outputWorkspaces.emplace_back(workspace);
-      }
-    }
-    // If it is a list of strings of workspace names make sure to add history
-    const Mantid::Kernel::PropertyWithValue<std::vector<std::string>>
-        *propProp = dynamic_cast<
-            Mantid::Kernel::PropertyWithValue<std::vector<std::string>> *>(*it);
-    if (propProp && hasAnADSValidator(propProp->getValidator())) {
-      const auto propPropValue = propProp->value();
-      const auto direction = propProp->direction();
-      std::string currentWS = "";
-      for (auto i = 0u; i < propPropValue.size(); ++i) {
-        if (propPropValue[i] == ',') {
-          constructWorkspaceVectorForHistoryHelper(
-              inputWorkspaces, outputWorkspaces, direction, currentWS);
-          currentWS = "";
-        } else {
-          currentWS.push_back(propPropValue[i]);
+void Algorithm::findWorkspaces(WorkspaceVector &workspaces,
+                               unsigned int direction, bool checkADS) const {
+  auto workspaceFromWSProperty =
+      [](const IWorkspaceProperty &prop, const AnalysisDataServiceImpl &ads,
+         const std::string &strValue, bool checkADS) {
+        auto workspace = prop.getWorkspace();
+        if (workspace)
+          return workspace;
+
+        // Empty string indicates optional workspace
+        if (checkADS && !strValue.empty()) {
+          return ads.retrieve(strValue);
         }
-      }
-      constructWorkspaceVectorForHistoryHelper(
-          inputWorkspaces, outputWorkspaces, direction, currentWS);
-    }
-  }
-}
-
-bool Algorithm::hasAnADSValidator(const IValidator_sptr propProp) const {
-  const Mantid::API::ADSValidator *ADSPropPropValidator =
-      dynamic_cast<Mantid::API::ADSValidator *>(propProp.get());
-  if (ADSPropPropValidator) {
+        return Workspace_sptr();
+      };
+  auto appendWS = [&workspaces](const Workspace_sptr &workspace) {
+    if (!workspace)
+      return false;
+    workspaces.emplace_back(workspace);
     return true;
-  }
-
-  const Mantid::Kernel::CompositeValidator *propPropCompValidator =
-      dynamic_cast<Mantid::Kernel::CompositeValidator *>(propProp.get());
-  if (propPropCompValidator) {
-    const std::list<IValidator_sptr> validatorList =
-        propPropCompValidator->getChildren();
-    for (IValidator_sptr i : validatorList) {
-      if (hasAnADSValidator(i) == true) {
-        return true;
-      }
+  };
+
+  // Additional output properties can be declared on the fly
+  // so we need a fresh loop over the properties
+  const auto &algProperties = getProperties();
+  const auto &ads = AnalysisDataService::Instance();
+  for (const auto &prop : algProperties) {
+    const unsigned int propDirection = prop->direction();
+    if (propDirection != direction && propDirection != Direction::InOut)
+      continue;
+    if (const auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop)) {
+      appendWS(workspaceFromWSProperty(*wsProp, ads, prop->value(), checkADS));
     }
   }
-  return false;
 }
 
-void Algorithm::constructWorkspaceVectorForHistoryHelper(
-    std::vector<Workspace_sptr> &inputWorkspaces,
-    std::vector<Workspace_sptr> &outputWorkspaces, const unsigned int direction,
-    std::string &currentWS) const {
-  const auto &ADS = AnalysisDataService::Instance();
-  try {
-    if (direction == Direction::Input || direction == Direction::InOut) {
-      inputWorkspaces.emplace_back(ADS.retrieveWS<Workspace>(currentWS));
-    }
-  } catch (const Mantid::Kernel::Exception::NotFoundError &error) {
-    const std::string errorMsg(error.what());
-    g_log.information("The ADS was unable to find the input workspaces "
-                      "when attaching history: " +
-                      errorMsg);
-  }
-  try {
-    if (direction == Direction::Output || direction == Direction::InOut) {
-      outputWorkspaces.emplace_back(ADS.retrieveWS<Workspace>(currentWS));
-    }
-  } catch (const Mantid::Kernel::Exception::NotFoundError &error) {
-    const std::string errorMsg(error.what());
-    g_log.information("The ADS was unable to find the output workspaces "
-                      "when attaching history: " +
-                      errorMsg);
-  }
-}
 /** Sends out algorithm parameter information to the logger */
 void Algorithm::logAlgorithmInfo() const {
   auto &logger = getLogger();
@@ -1216,17 +1175,15 @@ bool Algorithm::checkGroups() {
   size_t numGroups = 0;
   bool processGroups = false;
 
-  // Unroll the groups or single inputs into vectors of workspace
-  m_groups.clear();
+  // Unroll the groups or single inputs into vectors of workspaces
+  const auto &ads = AnalysisDataService::Instance();
+  m_unrolledInputWorkspaces.clear();
   m_groupWorkspaces.clear();
   for (auto inputWorkspaceProp : m_inputWorkspaceProps) {
     auto prop = dynamic_cast<Property *>(inputWorkspaceProp);
     auto wsGroupProp = dynamic_cast<WorkspaceProperty<WorkspaceGroup> *>(prop);
-    std::vector<Workspace_sptr> thisGroup;
-
-    Workspace_sptr ws = inputWorkspaceProp->getWorkspace();
-    WorkspaceGroup_sptr wsGroup =
-        boost::dynamic_pointer_cast<WorkspaceGroup>(ws);
+    auto ws = inputWorkspaceProp->getWorkspace();
+    auto wsGroup = boost::dynamic_pointer_cast<WorkspaceGroup>(ws);
 
     // Workspace groups are NOT returned by IWP->getWorkspace() most of the
     // time because WorkspaceProperty is templated by <MatrixWorkspace> and
@@ -1234,8 +1191,7 @@ bool Algorithm::checkGroups() {
     if (!wsGroup && prop && !prop->value().empty()) {
       // So try to use the name in the AnalysisDataService
       try {
-        wsGroup = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(
-            prop->value());
+        wsGroup = ads.retrieveWS<WorkspaceGroup>(prop->value());
       } catch (Exception::NotFoundError &) { /* Do nothing */
       }
     }
@@ -1245,25 +1201,17 @@ bool Algorithm::checkGroups() {
     if (wsGroup && !wsGroupProp) {
       numGroups++;
       processGroups = true;
-      std::vector<std::string> names = wsGroup->getNames();
-      for (auto &name : names) {
-        Workspace_sptr memberWS =
-            AnalysisDataService::Instance().retrieve(name);
-        if (!memberWS)
-          throw std::invalid_argument("One of the members of " +
-                                      wsGroup->getName() + ", " + name +
-                                      " was not found!.");
-        thisGroup.push_back(memberWS);
-      }
+      m_unrolledInputWorkspaces.emplace_back(wsGroup->getAllItems());
     } else {
       // Single Workspace. Treat it as a "group" with only one member
       if (ws)
-        thisGroup.push_back(ws);
+        m_unrolledInputWorkspaces.emplace_back(WorkspaceVector{ws});
+      else
+        m_unrolledInputWorkspaces.emplace_back(WorkspaceVector{});
     }
 
     // Add to the list of groups
-    m_groups.push_back(thisGroup);
-    m_groupWorkspaces.push_back(wsGroup);
+    m_groupWorkspaces.emplace_back(wsGroup);
   }
 
   // No groups? Get out.
@@ -1276,8 +1224,8 @@ bool Algorithm::checkGroups() {
   // Size of the single or of all the groups
   m_groupSize = 1;
   m_groupsHaveSimilarNames = true;
-  for (size_t i = 0; i < m_groups.size(); i++) {
-    std::vector<Workspace_sptr> &thisGroup = m_groups[i];
+  for (size_t i = 0; i < m_unrolledInputWorkspaces.size(); i++) {
+    const auto &thisGroup = m_unrolledInputWorkspaces[i];
     // We're ok with empty groups if the workspace property is optional
     if (thisGroup.empty() && !m_inputWorkspaceProps[i]->isOptional())
       throw std::invalid_argument("Empty group passed as input");
@@ -1328,7 +1276,6 @@ bool Algorithm::doCallProcessGroups(
   startTime = Mantid::Types::Core::DateAndTime::getCurrentTime();
   // Start a timer
   Timer timer;
-
   bool completed = false;
   try {
     // Call the concrete algorithm's processGroups method
@@ -1357,46 +1304,25 @@ bool Algorithm::doCallProcessGroups(
   interruption_point();
 
   if (completed) {
-    // in the base processGroups each individual exec stores its outputs
-    if (!m_usingBaseProcessGroups && m_alwaysStoreInADS)
-      this->store();
-
     // Get how long this algorithm took to run
     const float duration = timer.elapsed();
-    // Log that execution has completed.
-    reportCompleted(duration, true /* this is for group processing*/);
 
     m_history = boost::make_shared<AlgorithmHistory>(this, startTime, duration,
                                                      ++g_execCount);
-
     if (trackingHistory() && m_history) {
+      // find any further outputs created by the execution
+      WorkspaceVector outputWorkspaces;
+      const bool checkADS{true};
+      findWorkspaces(outputWorkspaces, Direction::Output, checkADS);
+      fillHistory(outputWorkspaces);
+    }
 
-      std::vector<Workspace_sptr> inputWorkspaces, outputWorkspaces;
-      findWorkspaceProperties(inputWorkspaces, outputWorkspaces);
-
-      // We need to find the workspaces to add the history to.
-      if (outputWorkspaces.size() == 0 && inputWorkspaces.size() == 0) {
-        outputWorkspaces.insert(outputWorkspaces.end(),
-                                m_groupWorkspaces.begin(),
-                                m_groupWorkspaces.end());
-      } else if (outputWorkspaces.size() == 0) {
-        outputWorkspaces = inputWorkspaces;
-      }
+    // in the base processGroups each individual exec stores its outputs
+    if (!m_usingBaseProcessGroups && m_alwaysStoreInADS)
+      this->store();
 
-      for (const auto &outputWorkspace : outputWorkspaces) {
-        auto outputGroupWS =
-            boost::dynamic_pointer_cast<WorkspaceGroup>(outputWorkspace);
-        if (outputGroupWS) {
-          // Put history of the call into each child
-          for (auto i = 0; i < outputGroupWS->getNumberOfEntries(); ++i) {
-            outputGroupWS->getItem(i)->history().addHistory(m_history);
-          }
-        } else if (outputWorkspace) {
-          // If it's a valid pointer add history else skip for optionals
-          outputWorkspace->history().addHistory(m_history);
-        }
-      }
-    }
+    // Log that execution has completed.
+    reportCompleted(duration, true /* this is for group processing*/);
   }
 
   setExecuted(completed);
@@ -1406,6 +1332,52 @@ bool Algorithm::doCallProcessGroups(
   return completed;
 }
 
+/**
+ * If this algorithm is not a child then copy history between the inputs and
+ * outputs and add a record for this algorithm. If the algorithm is a child
+ * attach the child history to the parent if requested.
+ *  @param outputWorkspaces :: A reference to a vector for the output
+ * workspaces. Used in the non-child case.
+ */
+void Algorithm::fillHistory(
+    const std::vector<Workspace_sptr> &outputWorkspaces) {
+  // this is not a child algorithm. Add the history algorithm to the
+  // WorkspaceHistory object.
+  if (!isChild()) {
+    auto copyHistoryToGroup = [](const Workspace &in, WorkspaceGroup &out) {
+      for (auto &outGroupItem : out) {
+        outGroupItem->history().addHistory(in.getHistory());
+      }
+    };
+
+    for (auto &outWS : outputWorkspaces) {
+      auto outWSGroup = boost::dynamic_pointer_cast<WorkspaceGroup>(outWS);
+      // Copy the history from the cached input workspaces to the output ones
+      for (const auto &inputWS : m_inputWorkspaceHistories) {
+        if (outWSGroup) {
+          copyHistoryToGroup(*inputWS, *outWSGroup);
+        } else {
+          outWS->history().addHistory(inputWS->getHistory());
+        }
+      }
+      // Add history for this operation
+      if (outWSGroup) {
+        for (auto &outGroupItem : *outWSGroup) {
+          outGroupItem->history().addHistory(m_history);
+        }
+      } else {
+        // Add the history for the current algorithm to all the output
+        // workspaces
+        outWS->history().addHistory(m_history);
+      }
+    }
+  }
+  // this is a child algorithm, but we still want to keep the history.
+  else if (m_recordHistoryForChild && m_parentHistory) {
+    m_parentHistory->addChildHistory(m_history);
+  }
+}
+
 //--------------------------------------------------------------------------------------------
 /** Process WorkspaceGroup inputs.
  *
@@ -1457,8 +1429,8 @@ bool Algorithm::processGroups() {
     std::string outputBaseName;
 
     // ---------- Set all the input workspaces ----------------------------
-    for (size_t iwp = 0; iwp < m_groups.size(); iwp++) {
-      std::vector<Workspace_sptr> &thisGroup = m_groups[iwp];
+    for (size_t iwp = 0; iwp < m_unrolledInputWorkspaces.size(); iwp++) {
+      std::vector<Workspace_sptr> &thisGroup = m_unrolledInputWorkspaces[iwp];
       if (!thisGroup.empty()) {
         // By default (for a single group) point to the first/only workspace
         Workspace_sptr ws = thisGroup[0];
@@ -1516,7 +1488,8 @@ bool Algorithm::processGroups() {
         // by ADS)
         if (inputProp != m_inputWorkspaceProps.end()) {
           const auto &inputGroup =
-              m_groups[inputProp - m_inputWorkspaceProps.begin()];
+              m_unrolledInputWorkspaces[inputProp -
+                                        m_inputWorkspaceProps.begin()];
           if (!inputGroup.empty())
             outName = inputGroup[entry]->getName();
         }
@@ -1574,8 +1547,8 @@ bool Algorithm::processGroups() {
 void Algorithm::copyNonWorkspaceProperties(IAlgorithm *alg, int periodNum) {
   if (!alg)
     throw std::runtime_error("Algorithm not created!");
-  std::vector<Property *> props = this->getProperties();
-  for (auto prop : props) {
+  const auto &props = this->getProperties();
+  for (const auto &prop : props) {
     if (prop) {
       IWorkspaceProperty *wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
       // Copy the property using the string
diff --git a/Framework/API/src/AnalysisDataServiceObserver.cpp b/Framework/API/src/AnalysisDataServiceObserver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3dde21ea6cd5c35a5078da9bdd62c61cb7dee6c7
--- /dev/null
+++ b/Framework/API/src/AnalysisDataServiceObserver.cpp
@@ -0,0 +1,315 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+
+#include "MantidAPI/AnalysisDataServiceObserver.h"
+
+namespace {
+template <typename Observer>
+void modifyObserver(const bool turnOn, bool &isObserving, Observer &observer) {
+  if (turnOn && !isObserving) {
+    AnalysisDataService::Instance().notificationCenter.addObserver(observer);
+  } else if (!turnOn && isObserving) {
+    AnalysisDataService::Instance().notificationCenter.removeObserver(observer);
+  }
+  isObserving = turnOn;
+}
+} // namespace
+
+namespace Mantid {
+namespace API {
+
+AnalysisDataServiceObserver::AnalysisDataServiceObserver()
+    : m_addObserver(*this, &AnalysisDataServiceObserver::_addHandle),
+      m_replaceObserver(*this, &AnalysisDataServiceObserver::_replaceHandle),
+      m_deleteObserver(*this, &AnalysisDataServiceObserver::_deleteHandle),
+      m_clearObserver(*this, &AnalysisDataServiceObserver::_clearHandle),
+      m_renameObserver(*this, &AnalysisDataServiceObserver::_renameHandle),
+      m_groupObserver(*this, &AnalysisDataServiceObserver::_groupHandle),
+      m_unGroupObserver(*this, &AnalysisDataServiceObserver::_unGroupHandle),
+      m_groupUpdatedObserver(*this,
+                             &AnalysisDataServiceObserver::_groupUpdateHandle) {
+}
+
+AnalysisDataServiceObserver::~AnalysisDataServiceObserver() {
+  // Turn off/remove all observers
+  this->observeAll(false);
+}
+
+// ------------------------------------------------------------
+// Observe Methods
+// ------------------------------------------------------------
+
+/**
+ * @brief Function will turn on/off all observers for the ADS
+ *
+ * @param turnOn bool; if this is True, then if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeAll(bool turnOn) {
+  this->observeAdd(turnOn);
+  this->observeReplace(turnOn);
+  this->observeDelete(turnOn);
+  this->observeClear(turnOn);
+  this->observeRename(turnOn);
+  this->observeGroup(turnOn);
+  this->observeUnGroup(turnOn);
+  this->observeGroupUpdate(turnOn);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for when a workspace
+ * is added to it.
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeAdd(bool turnOn) {
+  modifyObserver(turnOn, m_observingAdd, m_addObserver);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for when a workspace
+ * is replaced
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeReplace(bool turnOn) {
+  modifyObserver(turnOn, m_observingReplace, m_replaceObserver);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for when a workspace
+ * is deleted.
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeDelete(bool turnOn) {
+  modifyObserver(turnOn, m_observingDelete, m_deleteObserver);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for when the ADS is
+ * cleared.
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeClear(bool turnOn) {
+  modifyObserver(turnOn, m_observingClear, m_clearObserver);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for when a workspace
+ * is renamed
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeRename(bool turnOn) {
+  modifyObserver(turnOn, m_observingRename, m_renameObserver);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for when a group is
+ * added/created in the ADS
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeGroup(bool turnOn) {
+  modifyObserver(turnOn, m_observingGroup, m_groupObserver);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for when a group is
+ * removed/delete from the ADS
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeUnGroup(bool turnOn) {
+  modifyObserver(turnOn, m_observingUnGroup, m_unGroupObserver);
+}
+
+/**
+ * @brief Function will add/remove the observer to the ADS for if a workspace is
+ * added to a group or removed.
+ *
+ * @param turnOn bool; if this is True then, if not already present, the
+ * observer will be added else removed if it's false.
+ */
+void AnalysisDataServiceObserver::observeGroupUpdate(bool turnOn) {
+  modifyObserver(turnOn, m_observingGroupUpdate, m_groupUpdatedObserver);
+}
+
+// ------------------------------------------------------------
+// Virtual Methods
+// ------------------------------------------------------------
+/**
+ * @brief If anyChange to the ADS occurs then this function will trigger, works
+ * by overloading this class and overriding this function.
+ */
+void AnalysisDataServiceObserver::anyChangeHandle() {}
+
+/**
+ * @brief If a workspace is added to the ADS, then this function will trigger,
+ * works by overloading this class and overriding this function.
+ *
+ * @param wsName std::string; the name of the workspace added
+ * @param ws Workspace_sptr; the Workspace that is added
+ */
+void AnalysisDataServiceObserver::addHandle(
+    const std::string &wsName, const Mantid::API::Workspace_sptr &ws) {
+  UNUSED_ARG(wsName)
+  UNUSED_ARG(ws)
+}
+
+/**
+ * @brief If a workspace is replaced in the ADS, then this function will
+ * trigger, works by overloading this class and overriding this function
+ *
+ * @param wsName std::string; the name of the workspace replacing
+ * @param ws Workspace_sptr; the Workspace that is replacing
+ */
+void AnalysisDataServiceObserver::replaceHandle(
+    const std::string &wsName, const Mantid::API::Workspace_sptr &ws) {
+  UNUSED_ARG(wsName)
+  UNUSED_ARG(ws)
+}
+
+/**
+ * @brief If a workspace is deleted from the ADS, then this function will
+ * trigger, works by overloading this class and overriding this function
+ *
+ * @param wsName std::string; the name of the workspace
+ * @param ws Workspace_sptr; the Workspace that is deleted
+ */
+void AnalysisDataServiceObserver::deleteHandle(
+    const std::string &wsName, const Mantid::API::Workspace_sptr &ws) {
+  UNUSED_ARG(wsName)
+  UNUSED_ARG(ws)
+}
+
+/**
+ * @brief If the ADS is cleared, then this function will trigger, works by
+ * overloading this class and overriding this function
+ */
+void AnalysisDataServiceObserver::clearHandle() {}
+
+/**
+ * @brief If a workspace is renamed in the ADS, then this function will trigger,
+ * works by overloading this class and overriding this function
+ *
+ * @param wsName std::string; the name of the workspace
+ * @param newName std::string; the new name of the workspace
+ */
+void AnalysisDataServiceObserver::renameHandle(const std::string &wsName,
+                                               const std::string &newName) {
+  UNUSED_ARG(wsName)
+  UNUSED_ARG(newName)
+}
+
+/**
+ * @brief If a group is created/added to the ADS, then this function will
+ * trigger, works by overloading this class and overriding this function
+ *
+ * @param wsName std::string; the name of the workspace
+ * @param ws Workspace_sptr; the WorkspaceGroup that was added/created
+ */
+void AnalysisDataServiceObserver::groupHandle(const std::string &wsName,
+                                              const Workspace_sptr &ws) {
+  UNUSED_ARG(wsName)
+  UNUSED_ARG(ws)
+}
+
+/**
+ * @brief If a group is removed from the ADS, then this function will trigger,
+ * works by overloading this class and overriding this function
+ *
+ * @param wsName std::string; the name of the workspace
+ * @param ws Workspace_sptr; the WorkspaceGroup that was ungrouped
+ */
+void AnalysisDataServiceObserver::unGroupHandle(const std::string &wsName,
+                                                const Workspace_sptr &ws) {
+  UNUSED_ARG(wsName)
+  UNUSED_ARG(ws)
+}
+
+/**
+ * @brief If a group has a workspace added/removed in the ADS, then this
+ * function will trigger, works by overloading this class and overriding this
+ * function.
+ *
+ * @param wsName std::string; the name of the workspace
+ * @param ws Workspace_sptr; the WorkspaceGroup that was updated
+ */
+void AnalysisDataServiceObserver::groupUpdateHandle(const std::string &wsName,
+                                                    const Workspace_sptr &ws) {
+  UNUSED_ARG(wsName)
+  UNUSED_ARG(ws)
+}
+
+// ------------------------------------------------------------
+// Private Methods
+// ------------------------------------------------------------
+void AnalysisDataServiceObserver::_addHandle(
+    const Poco::AutoPtr<AnalysisDataServiceImpl::AddNotification> &pNf) {
+  this->anyChangeHandle();
+  this->addHandle(pNf->objectName(), pNf->object());
+}
+
+void AnalysisDataServiceObserver::_replaceHandle(
+    const Poco::AutoPtr<AnalysisDataServiceImpl::AfterReplaceNotification>
+        &pNf) {
+  this->anyChangeHandle();
+  this->replaceHandle(pNf->objectName(), pNf->object());
+}
+
+void AnalysisDataServiceObserver::_deleteHandle(
+    const Poco::AutoPtr<AnalysisDataServiceImpl::PreDeleteNotification> &pNf) {
+  this->anyChangeHandle();
+  this->deleteHandle(pNf->objectName(), pNf->object());
+}
+
+void AnalysisDataServiceObserver::_clearHandle(
+    const Poco::AutoPtr<AnalysisDataServiceImpl::ClearNotification> &pNf) {
+  UNUSED_ARG(pNf)
+  this->anyChangeHandle();
+  this->clearHandle();
+}
+
+void AnalysisDataServiceObserver::_renameHandle(
+    const Poco::AutoPtr<AnalysisDataServiceImpl::RenameNotification> &pNf) {
+  this->anyChangeHandle();
+  this->renameHandle(pNf->objectName(), pNf->newObjectName());
+}
+
+void AnalysisDataServiceObserver::_groupHandle(
+    const Poco::AutoPtr<AnalysisDataServiceImpl::GroupWorkspacesNotification>
+        &pNf) {
+  this->anyChangeHandle();
+  this->groupHandle(pNf->objectName(), pNf->object());
+}
+
+void AnalysisDataServiceObserver::_unGroupHandle(
+    const Poco::AutoPtr<
+        AnalysisDataServiceImpl::UnGroupingWorkspaceNotification> &pNf) {
+  this->anyChangeHandle();
+  this->unGroupHandle(pNf->objectName(), pNf->object());
+}
+
+void AnalysisDataServiceObserver::_groupUpdateHandle(
+    const Poco::AutoPtr<AnalysisDataServiceImpl::GroupUpdatedNotification>
+        &pNf) {
+  this->anyChangeHandle();
+  this->groupUpdateHandle(pNf->objectName(), pNf->object());
+}
+
+} // namespace API
+} // namespace Mantid
\ No newline at end of file
diff --git a/Framework/API/src/FileFinder.cpp b/Framework/API/src/FileFinder.cpp
index 392db921a8dce9f2659e3b90bd4613bd63f9280c..711d273c4fb6ab590847d5e6e5c660c00589bd31 100644
--- a/Framework/API/src/FileFinder.cpp
+++ b/Framework/API/src/FileFinder.cpp
@@ -308,32 +308,6 @@ FileFinderImpl::makeFileName(const std::string &hint,
   return filename;
 }
 
-/**
- * Find the file given a hint. If the name contains a dot(.) then it is assumed
- * that it is already a file stem
- * otherwise calls makeFileName internally.
- * @param hintstr :: The name hint, format: [INSTR]1234[.ext]
- * @param exts :: Optional list of allowed extensions. Only those extensions
- * found in both
- *  facilities extension list and exts will be used in the search. If an
- * extension is given in hint
- *  this argument is ignored.
- * @return The full path to the file or empty string if not found
- */
-std::string FileFinderImpl::findRun(const std::string &hintstr,
-                                    const std::set<std::string> &exts) const {
-  std::string hint = Kernel::Strings::strip(hintstr);
-  g_log.debug() << "set findRun(\'" << hintstr << "\', exts[" << exts.size()
-                << "])\n";
-  if (hint.empty())
-    return "";
-  std::vector<std::string> exts_v;
-  if (!exts.empty())
-    exts_v.assign(exts.begin(), exts.end());
-
-  return this->findRun(hint, exts_v);
-}
-
 /**
  * Determine the extension from a filename.
  *
@@ -424,9 +398,9 @@ FileFinderImpl::getArchiveSearch(const Kernel::FacilityInfo &facility) const {
   return archs;
 }
 
-std::string
-FileFinderImpl::findRun(const std::string &hintstr,
-                        const std::vector<std::string> &exts) const {
+std::string FileFinderImpl::findRun(const std::string &hintstr,
+                                    const std::vector<std::string> &exts,
+                                    const bool useExtsOnly) const {
   std::string hint = Kernel::Strings::strip(hintstr);
   g_log.debug() << "vector findRun(\'" << hint << "\', exts[" << exts.size()
                 << "])\n";
@@ -513,45 +487,11 @@ FileFinderImpl::findRun(const std::string &hintstr,
   if (!extension.empty())
     uniqueExts.push_back(extension);
 
-  auto cend = exts.end();
-  for (auto cit = exts.begin(); cit != cend; ++cit) {
-    if (getCaseSensitive()) // prune case variations - this is a hack, see above
-    {
-      std::string transformed(*cit);
-      std::transform(cit->begin(), cit->end(), transformed.begin(), tolower);
-      auto searchItr =
-          std::find(uniqueExts.begin(), uniqueExts.end(), transformed);
-      if (searchItr != uniqueExts.end())
-        continue;
-      std::transform(cit->begin(), cit->end(), transformed.begin(), toupper);
-      searchItr = std::find(uniqueExts.begin(), uniqueExts.end(), transformed);
-      if (searchItr == uniqueExts.end())
-        uniqueExts.push_back(*cit);
-    } else {
-      auto searchItr = std::find(uniqueExts.begin(), uniqueExts.end(), *cit);
-      if (searchItr == uniqueExts.end())
-        uniqueExts.push_back(*cit);
-    }
-  }
-  cend = extensions.end();
-  for (auto cit = extensions.begin(); cit != cend; ++cit) {
-    if (getCaseSensitive()) // prune case variations - this is a hack, see above
-    {
-      std::string transformed(*cit);
-      std::transform(cit->begin(), cit->end(), transformed.begin(), tolower);
-      auto searchItr =
-          std::find(uniqueExts.begin(), uniqueExts.end(), transformed);
-      if (searchItr != uniqueExts.end())
-        continue;
-      std::transform(cit->begin(), cit->end(), transformed.begin(), toupper);
-      searchItr = std::find(uniqueExts.begin(), uniqueExts.end(), transformed);
-      if (searchItr == uniqueExts.end())
-        uniqueExts.push_back(*cit);
-    } else {
-      auto searchItr = std::find(uniqueExts.begin(), uniqueExts.end(), *cit);
-      if (searchItr == uniqueExts.end())
-        uniqueExts.push_back(*cit);
-    }
+  // If provided exts are empty, or useExtsOnly is false,
+  // we want to include facility exts as well
+  getUniqueExtensions(exts, uniqueExts);
+  if (exts.empty() || !useExtsOnly) {
+    getUniqueExtensions(extensions, uniqueExts);
   }
 
   // determine which archive search facilities to use
@@ -570,17 +510,50 @@ FileFinderImpl::findRun(const std::string &hintstr,
   return "";
 }
 
+/**
+ * Given a set of already determined extensions and new extensions,
+ * create a set of all extensions.
+ * If not in an extension-is-case-sensitive environment, only add the
+ * lower case OR upper case version of the extension
+ * @param extensionsToAdd :: a vector of extensions to add
+ * @param uniqueExts :: a vector of currently included extensions
+ */
+void FileFinderImpl::getUniqueExtensions(
+    const std::vector<std::string> &extensionsToAdd,
+    std::vector<std::string> &uniqueExts) const {
+  const bool isCaseSensitive = getCaseSensitive();
+  for (const auto &cit : extensionsToAdd) {
+    std::string transformed(cit);
+    if (!isCaseSensitive) {
+      std::transform(cit.begin(), cit.end(), transformed.begin(), tolower);
+    }
+    const auto searchItr =
+        std::find(uniqueExts.begin(), uniqueExts.end(), transformed);
+    if (searchItr == uniqueExts.end()) {
+      uniqueExts.push_back(transformed);
+    }
+  }
+}
+
 /**
  * Find a list of files file given a hint. Calls findRun internally.
  * @param hintstr :: Comma separated list of hints to findRun method.
  *  Can also include ranges of runs, e.g. 123-135 or equivalently 123-35.
  *  Only the beginning of a range can contain an instrument name.
+ * @param exts :: Vector of allowed file extensions. Optional.
+ *                If provided, this provides the only extensions searched for.
+ *                If not provided, facility extensions used.
+ * @param useExtsOnly :: Optional bool. If it's true (and exts is not empty),
+                           search the for the file using exts only.
+                           If it's false, use exts AND facility extensions.
  * @return A vector of full paths or empty vector
  * @throw std::invalid_argument if the argument is malformed
  * @throw Exception::NotFoundError if a file could not be found
  */
 std::vector<std::string>
-FileFinderImpl::findRuns(const std::string &hintstr) const {
+FileFinderImpl::findRuns(const std::string &hintstr,
+                         const std::vector<std::string> &exts,
+                         const bool useExtsOnly) const {
   std::string hint = Kernel::Strings::strip(hintstr);
   g_log.debug() << "findRuns hint = " << hint << "\n";
   std::vector<std::string> res;
@@ -638,7 +611,7 @@ FileFinderImpl::findRuns(const std::string &hintstr) const {
         run = std::to_string(irun);
         while (run.size() < nZero)
           run.insert(0, "0");
-        std::string path = findRun(p1.first + run);
+        std::string path = findRun(p1.first + run, exts, useExtsOnly);
         if (!path.empty()) {
           res.push_back(path);
         } else {
@@ -646,7 +619,7 @@ FileFinderImpl::findRuns(const std::string &hintstr) const {
         }
       }
     } else {
-      std::string path = findRun(*h);
+      std::string path = findRun(*h, exts, useExtsOnly);
       if (!path.empty()) {
         res.push_back(path);
       } else {
diff --git a/Framework/API/src/MultiPeriodGroupWorker.cpp b/Framework/API/src/MultiPeriodGroupWorker.cpp
index 05989d434becb08ecf3987e696f06be32f73a769..b1ac752930f225425f1c4cb24faa3301a624b1de 100644
--- a/Framework/API/src/MultiPeriodGroupWorker.cpp
+++ b/Framework/API/src/MultiPeriodGroupWorker.cpp
@@ -93,9 +93,7 @@ MultiPeriodGroupWorker::findMultiPeriodGroups(
   } else {
     using WorkspaceVector = std::vector<boost::shared_ptr<Workspace>>;
     WorkspaceVector inWorkspaces;
-    WorkspaceVector outWorkspaces;
-    sourceAlg->findWorkspaceProperties(inWorkspaces, outWorkspaces);
-    UNUSED_ARG(outWorkspaces);
+    sourceAlg->findWorkspaces(inWorkspaces, Direction::Input);
     for (auto &inWorkspace : inWorkspaces) {
       tryAddInputWorkspaceToInputGroups(
           inWorkspace, vecMultiPeriodWorkspaceGroups, vecWorkspaceGroups);
diff --git a/Framework/API/src/Sample.cpp b/Framework/API/src/Sample.cpp
index a6a06f5b07d363412697dce657bbd395287ebf83..aefb03a631a09f14f9f584b19ec19c9f2c6089de 100644
--- a/Framework/API/src/Sample.cpp
+++ b/Framework/API/src/Sample.cpp
@@ -144,8 +144,8 @@ const SampleEnvironment &Sample::getEnvironment() const {
  * @param env :: A pointer to a created sample environment. This takes
  * ownership of the object.
  */
-void Sample::setEnvironment(SampleEnvironment *env) {
-  m_environment = boost::shared_ptr<SampleEnvironment>(env);
+void Sample::setEnvironment(std::unique_ptr<SampleEnvironment> env) {
+  m_environment = boost::shared_ptr<SampleEnvironment>(std::move(env));
 }
 
 /** Return a const reference to the OrientedLattice of this sample
diff --git a/Framework/API/src/WorkspaceFactory.cpp b/Framework/API/src/WorkspaceFactory.cpp
index 30dedbc1fe13fbb5d4707218919d8824470e3bbd..bc8b78456c8f83a02e5d0dc056e1052220827107 100644
--- a/Framework/API/src/WorkspaceFactory.cpp
+++ b/Framework/API/src/WorkspaceFactory.cpp
@@ -5,6 +5,7 @@
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAPI/WorkspaceFactory.h"
+#include "MantidAPI/BinEdgeAxis.h"
 #include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidAPI/MatrixWorkspace.h"
@@ -127,21 +128,26 @@ void WorkspaceFactoryImpl::initializeFromParent(
 
   // deal with axis
   for (size_t i = 0; i < parent.m_axes.size(); ++i) {
-    const size_t newAxisLength = child.getAxis(i)->length();
-    const size_t oldAxisLength = parent.getAxis(i)->length();
-
-    if (!differentSize || newAxisLength == oldAxisLength) {
-      // Need to delete the existing axis created in init above
-      delete child.m_axes[i];
+    if (parent.m_axes[i]->isSpectra()) {
+      // By default the child already has a spectra axis which
+      // does not need to get cloned from the parent.
+      continue;
+    }
+    const bool isBinEdge =
+        dynamic_cast<const BinEdgeAxis *const>(parent.m_axes[i]) != nullptr;
+    const size_t newAxisLength =
+        child.m_axes[i]->length() + (isBinEdge ? 1 : 0);
+    const size_t oldAxisLength = parent.m_axes[i]->length();
+
+    // Need to delete the existing axis created in init above
+    delete child.m_axes[i];
+    child.m_axes[i] = nullptr;
+    if (newAxisLength == oldAxisLength) {
       // Now set to a copy of the parent workspace's axis
       child.m_axes[i] = parent.m_axes[i]->clone(&child);
     } else {
-      if (!parent.getAxis(i)->isSpectra()) // WHY???
-      {
-        delete child.m_axes[i];
-        // Call the 'different length' clone variant
-        child.m_axes[i] = parent.m_axes[i]->clone(newAxisLength, &child);
-      }
+      // Call the 'different length' clone variant
+      child.m_axes[i] = parent.m_axes[i]->clone(newAxisLength, &child);
     }
   }
 }
diff --git a/Framework/API/src/WorkspaceGroup.cpp b/Framework/API/src/WorkspaceGroup.cpp
index 25f8e4f5328404d28a7e6f787f88a6a058ce4503..9a9581f9491c1cd8b9fd836a73c000605eb5c8b6 100644
--- a/Framework/API/src/WorkspaceGroup.cpp
+++ b/Framework/API/src/WorkspaceGroup.cpp
@@ -51,8 +51,8 @@ const std::string WorkspaceGroup::toString() const {
  * Turn on/off observing delete and rename notifications to update the group
  * accordingly
  * It can be useful to turn them off when constructing the group.
- * @param observeADS :: If true observe the ADS notifications, otherwise disable
- * them
+ * @param observeADS :: If true observe the ADS notifications, otherwise
+ * disable them
  */
 void WorkspaceGroup::observeADSNotifications(const bool observeADS) {
   if (observeADS) {
@@ -105,9 +105,10 @@ void WorkspaceGroup::sortMembersByName() {
 }
 
 /**
- * Adds a workspace to the group. The workspace does not have to be in the ADS
- * @param workspace :: A shared pointer to a workspace to add. If the workspace
- * already exists give a warning.
+ * Adds a workspace to the group. The workspace does not have to be in the
+ * ADS
+ * @param workspace :: A shared pointer to a workspace to add. If the
+ * workspace already exists give a warning.
  */
 void WorkspaceGroup::addWorkspace(const Workspace_sptr &workspace) {
   std::lock_guard<std::recursive_mutex> _lock(m_mutex);
@@ -188,8 +189,8 @@ Workspace_sptr WorkspaceGroup::getItem(const size_t index) const {
 /**
  * Return the workspace by name
  * @param wsName The name of the workspace
- * @throws an out_of_range error if the workspace's name not contained in the
- * group's list of workspace names
+ * @throws an out_of_range error if the workspace's name not contained in
+ * the group's list of workspace names
  */
 Workspace_sptr WorkspaceGroup::getItem(const std::string &wsName) const {
   std::lock_guard<std::recursive_mutex> _lock(m_mutex);
@@ -228,8 +229,8 @@ void WorkspaceGroup::removeByADS(const std::string &wsName) {
   }
 }
 
-/// Print the names of all the workspaces in this group to the logger (at debug
-/// level)
+/// Print the names of all the workspaces in this group to the logger (at
+/// debug level)
 void WorkspaceGroup::print() const {
   std::lock_guard<std::recursive_mutex> _lock(m_mutex);
   for (const auto &workspace : m_workspaces) {
@@ -268,7 +269,8 @@ std::vector<Workspace_sptr>::iterator WorkspaceGroup::end() {
   return m_workspaces.end();
 }
 
-/** Returns a const iterator pointing to the past-the-end element in the group.
+/** Returns a const iterator pointing to the past-the-end element in the
+ * group.
  *
  * @return  A const iterator pointing to the last workspace in this
  *          workspace group.
@@ -278,8 +280,8 @@ std::vector<Workspace_sptr>::const_iterator WorkspaceGroup::end() const {
 }
 
 /**
- * Remove a workspace pointed to by an index. The workspace remains in the ADS
- * if it was there
+ * Remove a workspace pointed to by an index. The workspace remains in the
+ * ADS if it was there
  *
  * @param index :: Index of a workspace to delete.
  */
@@ -287,8 +289,8 @@ void WorkspaceGroup::removeItem(const size_t index) {
   std::lock_guard<std::recursive_mutex> _lock(m_mutex);
   // do not allow this way of removing for groups in the ADS
   if (!this->getName().empty()) {
-    throw std::runtime_error(
-        "AnalysisDataService must be used to remove a workspace from group.");
+    throw std::runtime_error("AnalysisDataService must be used to remove a "
+                             "workspace from group.");
   }
   if (index >= this->size()) {
     std::ostringstream os;
@@ -334,7 +336,8 @@ void WorkspaceGroup::workspaceDeleteHandle(
  * Callback when a before-replace notification is received
  * Replaces a member if it was replaced in the ADS and checks
  * for duplicate members within the group
- * @param notice :: A pointer to a workspace before-replace notification object
+ * @param notice :: A pointer to a workspace before-replace notification
+ * object
  */
 void WorkspaceGroup::workspaceBeforeReplaceHandle(
     Mantid::API::WorkspaceBeforeReplaceNotification_ptr notice) {
@@ -446,10 +449,10 @@ bool WorkspaceGroup::isMultiperiod() const {
 
 /**
  * @param workspaceToCheck :: A workspace to check.
- * @param level :: The current nesting level. Intended for internal use only by
- * WorkspaceGroup.
- * @return :: True if the worspace is found in any of the nested groups in this
- * group.
+ * @param level :: The current nesting level. Intended for internal use only
+ * by WorkspaceGroup.
+ * @return :: True if the worspace is found in any of the nested groups in
+ * this group.
  */
 bool WorkspaceGroup::isInGroup(const Workspace &workspaceToCheck,
                                size_t level) const {
diff --git a/Framework/API/src/WorkspaceHistory.cpp b/Framework/API/src/WorkspaceHistory.cpp
index fcbee3af1d06da28785b6171e3e0763b2e3cc556..582a14b3d62130730c1ccfa774062f934335042f 100644
--- a/Framework/API/src/WorkspaceHistory.cpp
+++ b/Framework/API/src/WorkspaceHistory.cpp
@@ -93,13 +93,21 @@ void WorkspaceHistory::addHistory(const WorkspaceHistory &otherHistory) {
     this->addHistory(algHistory);
   }
 
-  std::unordered_set<AlgorithmHistory_sptr, AlgorithmHistoryHasher,
-                     AlgorithmHistoryComparator>
-      set;
-  for (auto i : m_algorithms)
-    set.insert(i);
-  m_algorithms.assign(set.begin(), set.end());
-  std::sort(m_algorithms.begin(), m_algorithms.end(), AlgorithmHistorySearch());
+  using UniqueAlgorithmHistories =
+      std::unordered_set<AlgorithmHistory_sptr, AlgorithmHistoryHasher,
+                         AlgorithmHistoryComparator>;
+  // It is faster to default construct a set/unordered_set and insert the
+  // elements than use the range-based constructor directly.
+  // See https://stackoverflow.com/a/24477023:
+  //   "the constructor actually construct a new node for every element, before
+  //   checking its value to determine if it should actually be inserted."
+  UniqueAlgorithmHistories uniqueHistories;
+  for (const auto &algorithmHistory : m_algorithms) {
+    uniqueHistories.insert(algorithmHistory);
+  }
+  m_algorithms.assign(std::begin(uniqueHistories), std::end(uniqueHistories));
+  std::sort(std::begin(m_algorithms), std::end(m_algorithms),
+            AlgorithmHistorySearch());
 }
 
 /// Append an AlgorithmHistory to this WorkspaceHistory
diff --git a/Framework/API/test/AlgorithmTest.h b/Framework/API/test/AlgorithmTest.h
index 2d6c805eee6f909847b896675bb6a44a600bb377..25cc32798122167669c9d59d8e49433a5c7bded8 100644
--- a/Framework/API/test/AlgorithmTest.h
+++ b/Framework/API/test/AlgorithmTest.h
@@ -12,10 +12,12 @@
 #include "FakeAlgorithms.h"
 #include "MantidAPI/Algorithm.tcc"
 #include "MantidAPI/AlgorithmFactory.h"
+#include "MantidAPI/AlgorithmHistory.h"
 #include "MantidAPI/FrameworkManager.h"
 #include "MantidAPI/HistogramValidator.h"
 #include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
+#include "MantidAPI/WorkspaceHistory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/Property.h"
@@ -58,21 +60,20 @@ public:
   }
 
   void exec() override {
-    boost::shared_ptr<WorkspaceTester> out1 =
-        boost::make_shared<WorkspaceTester>();
+    const std::string outName = getPropertyValue("InputWorkspace1") + "+" +
+                                getPropertyValue("InputWorkspace2") + "+" +
+                                getPropertyValue("InOutWorkspace");
+    auto out1 = boost::make_shared<WorkspaceTester>();
     out1->initialize(10, 10, 10);
-    boost::shared_ptr<WorkspaceTester> out2 =
-        boost::make_shared<WorkspaceTester>();
-    out2->initialize(10, 10, 10);
-    std::string outName = getPropertyValue("InputWorkspace1") + "+" +
-                          getPropertyValue("InputWorkspace2") + "+" +
-                          getPropertyValue("InOutWorkspace");
     out1->setTitle(outName);
-    out2->setTitle(outName);
-    double val = getProperty("Number");
-    out1->dataY(0)[0] = val;
+    out1->dataY(0)[0] = getProperty("Number");
     setProperty("OutputWorkspace1", out1);
-    setProperty("OutputWorkspace2", out2);
+    if (!getPropertyValue("OutputWorkspace2").empty()) {
+      auto out2 = boost::make_shared<WorkspaceTester>();
+      out2->initialize(10, 10, 10);
+      out2->setTitle(outName);
+      setProperty("OutputWorkspace2", out2);
+    }
   }
 };
 DECLARE_ALGORITHM(StubbedWorkspaceAlgorithm)
@@ -214,6 +215,8 @@ public:
     Mantid::API::AlgorithmFactory::Instance().unsubscribe("ToyAlgorithmTwo", 1);
   }
 
+  void setUp() override { AnalysisDataService::Instance().clear(); }
+
   void testAlgorithm() {
     std::string theName = alg.name();
     TS_ASSERT(!theName.compare("ToyAlgorithm"));
@@ -572,32 +575,33 @@ public:
    * @param group1 :: name of the group. Do nothing if blank.
    * @param contents1 :: comma-sep names of fake workspaces in the group
    *        Make no group if blank, just 1 workspace
+   * @return The new WorkspaceGroup object
    */
-  void makeWorkspaceGroup(std::string group1, std::string contents1) {
+  Workspace_sptr makeWorkspaceGroup(std::string group1, std::string contents1) {
+    auto &ads = AnalysisDataService::Instance();
     if (contents1.empty()) {
       if (group1.empty())
-        return;
-      boost::shared_ptr<WorkspaceTester> ws =
-          boost::make_shared<WorkspaceTester>();
-      AnalysisDataService::Instance().addOrReplace(group1, ws);
-      return;
+        return Workspace_sptr();
+      auto ws = boost::make_shared<WorkspaceTester>();
+      ads.addOrReplace(group1, ws);
+      return ws;
     }
 
     std::vector<std::string> names;
     boost::split(names, contents1,
                  boost::algorithm::detail::is_any_ofF<char>(","));
     if (names.size() >= 1) {
-      WorkspaceGroup_sptr wsGroup = WorkspaceGroup_sptr(new WorkspaceGroup());
-      AnalysisDataService::Instance().addOrReplace(group1, wsGroup);
-      std::vector<std::string>::iterator it = names.begin();
-      for (; it != names.end(); it++) {
-        boost::shared_ptr<WorkspaceTester> ws =
-            boost::make_shared<WorkspaceTester>();
+      auto wsGroup = WorkspaceGroup_sptr(new WorkspaceGroup());
+      ads.addOrReplace(group1, wsGroup);
+      for (const auto &name : names) {
+        auto ws = boost::make_shared<WorkspaceTester>();
         ws->initialize(10, 10, 10);
-        AnalysisDataService::Instance().addOrReplace(*it, ws);
-        wsGroup->add(*it);
+        ads.addOrReplace(name, ws);
+        wsGroup->add(name);
       }
+      return wsGroup;
     }
+    return Workspace_sptr();
   }
 
   //------------------------------------------------------------------------
@@ -650,7 +654,6 @@ public:
 
   /// All groups are the same size
   void test_processGroups_allSameSize() {
-    Mantid::API::AnalysisDataService::Instance().clear();
     WorkspaceGroup_sptr group = do_test_groups(
         "A", "A_1,A_2,A_3", "B", "B_1,B_2,B_3", "C", "C_1,C_2,C_3");
 
@@ -762,7 +765,6 @@ public:
 
   /// Rewrite first input group
   void test_processGroups_rewriteFirstGroup() {
-    Mantid::API::AnalysisDataService::Instance().clear();
     WorkspaceGroup_sptr group =
         do_test_groups("D", "D1,D2,D3", "B", "B1,B2,B3", "C", "C1,C2,C3");
 
@@ -777,7 +779,6 @@ public:
 
   /// Rewrite second group
   void test_processGroups_rewriteSecondGroup() {
-    Mantid::API::AnalysisDataService::Instance().clear();
     WorkspaceGroup_sptr group =
         do_test_groups("A", "A1,A2,A3", "D", "D1,D2,D3", "C", "C1,C2,C3");
 
@@ -792,7 +793,6 @@ public:
 
   /// Rewrite multiple group
   void test_processGroups_rewriteMultipleGroup() {
-    Mantid::API::AnalysisDataService::Instance().clear();
     WorkspaceGroup_sptr group =
         do_test_groups("A", "A1,A2,A3", "D", "D1,D2,D3", "D", "D1,D2,D3");
 
@@ -805,6 +805,74 @@ public:
     TS_ASSERT_EQUALS(ws3->getTitle(), "A3+D3+D3");
   }
 
+  void doHistoryCopyTest(const std::string &inputWSName,
+                         const std::string &outputWSName) {
+    auto inputWS = boost::make_shared<WorkspaceTester>();
+    inputWS->history().addHistory(boost::make_shared<AlgorithmHistory>(
+        "Load", 1, "b5b65a94-e656-468e-987c-644288fac655"));
+    auto &ads = AnalysisDataService::Instance();
+    ads.addOrReplace(inputWSName, inputWS);
+
+    StubbedWorkspaceAlgorithm nextStep;
+    nextStep.initialize();
+    nextStep.setPropertyValue("InputWorkspace1", inputWSName);
+    nextStep.setPropertyValue("OutputWorkspace1", outputWSName);
+    nextStep.execute();
+
+    auto outputWS = ads.retrieve(outputWSName);
+    const auto &outputHistory = outputWS->history();
+    TS_ASSERT_EQUALS(2, outputHistory.size());
+    TS_ASSERT_EQUALS("Load", outputHistory.getAlgorithmHistory(0)->name());
+    TS_ASSERT_EQUALS("StubbedWorkspaceAlgorithm",
+                     outputHistory.getAlgorithmHistory(1)->name());
+  }
+
+  void test_singleInputWorkspaceHistoryCopiedToOutputWorkspace() {
+    doHistoryCopyTest("copyHistoryIn", "copyHistoryOut");
+  }
+
+  void test_singleInputWorkspaceHistoryCopiedToReplacedOutputWorkspace() {
+    doHistoryCopyTest("copyHistoryInOut", "copyHistoryInOut");
+  }
+
+  void doHistoryCopyOnGroupsTest(const std::string &inputWSName,
+                                 const std::string &outputWSName) {
+    using Mantid::Types::Core::DateAndTime;
+    const auto group =
+        boost::dynamic_pointer_cast<WorkspaceGroup>(makeWorkspaceGroup(
+            inputWSName, inputWSName + "_1," + inputWSName + "_2"));
+    const DateAndTime execDate{
+        Mantid::Types::Core::DateAndTime::getCurrentTime()};
+    for (auto &item : *group) {
+      item->history().addHistory(boost::make_shared<AlgorithmHistory>(
+          "Load", 1, "49ea7cb9-6172-4e5c-acf5-c3edccd0bb27", execDate));
+    }
+    auto &ads = AnalysisDataService::Instance();
+    StubbedWorkspaceAlgorithm nextStep;
+    nextStep.initialize();
+    nextStep.setPropertyValue("InputWorkspace1", inputWSName);
+    nextStep.setPropertyValue("OutputWorkspace1", outputWSName);
+    nextStep.execute();
+
+    auto outputGroup = ads.retrieveWS<WorkspaceGroup>(outputWSName);
+    TS_ASSERT(outputGroup);
+    for (auto &item : *outputGroup) {
+      const auto &outputHistory = item->history();
+      TS_ASSERT_EQUALS(2, outputHistory.size());
+      TS_ASSERT_EQUALS("Load", outputHistory.getAlgorithmHistory(0)->name());
+      TS_ASSERT_EQUALS("StubbedWorkspaceAlgorithm",
+                       outputHistory.getAlgorithmHistory(1)->name());
+    }
+  }
+
+  void test_InputWorkspaceGroupHistoryCopiedToOutputWorkspaceGroup() {
+    doHistoryCopyOnGroupsTest("copyHistoryGroupIn", "copyHistoryGroupOut");
+  }
+
+  void test_InputWorkspaceGroupHistoryCopiedToReplacedOutputWorkspaceGroup() {
+    doHistoryCopyOnGroupsTest("copyHistoryGroupInOut", "copyHistoryGroupInOut");
+  }
+
   /**
    * Test declaring an algorithm property and retrieving as const
    * and non-const
diff --git a/Framework/API/test/AnalysisDataServiceObserverTest.h b/Framework/API/test/AnalysisDataServiceObserverTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..c16b3ff1e7577af64fac65fd900e39f248921b77
--- /dev/null
+++ b/Framework/API/test/AnalysisDataServiceObserverTest.h
@@ -0,0 +1,243 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef ANALYSISDATASERVICEOBSERVERTEST_H_
+#define ANALYSISDATASERVICEOBSERVERTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/AnalysisDataServiceObserver.h"
+#include "MantidAPI/FrameworkManager.h"
+#include "MantidAPI/MatrixWorkspace.h"
+
+using namespace Mantid::API;
+
+class FakeAnalysisDataServiceObserver
+    : public Mantid::API::AnalysisDataServiceObserver {
+
+public:
+  FakeAnalysisDataServiceObserver()
+      : m_anyChangeHandleCalled(false), m_addHandleCalled(false),
+        m_replaceHandleCalled(false), m_deleteHandleCalled(false),
+        m_clearHandleCalled(false), m_renameHandleCalled(false),
+        m_groupHandleCalled(false), m_unGroupHandleCalled(false),
+        m_groupUpdateHandleCalled(false) {
+    this->observeAll(false);
+  }
+
+  ~FakeAnalysisDataServiceObserver() { this->observeAll(false); }
+
+  void anyChangeHandle() override { m_anyChangeHandleCalled = true; }
+  void addHandle(const std::string &wsName, const Workspace_sptr &ws) override {
+    UNUSED_ARG(wsName)
+    UNUSED_ARG(ws)
+    m_addHandleCalled = true;
+  }
+  void replaceHandle(const std::string &wsName,
+                     const Workspace_sptr &ws) override {
+    UNUSED_ARG(wsName)
+    UNUSED_ARG(ws)
+    m_replaceHandleCalled = true;
+  }
+  void deleteHandle(const std::string &wsName,
+                    const Workspace_sptr &ws) override {
+    UNUSED_ARG(wsName)
+    UNUSED_ARG(ws)
+    m_deleteHandleCalled = true;
+  }
+  void clearHandle() override { m_clearHandleCalled = true; }
+  void renameHandle(const std::string &wsName,
+                    const std::string &newName) override {
+    UNUSED_ARG(wsName)
+    UNUSED_ARG(newName)
+    m_renameHandleCalled = true;
+  }
+  void groupHandle(const std::string &wsName,
+                   const Workspace_sptr &ws) override {
+    UNUSED_ARG(wsName)
+    UNUSED_ARG(ws)
+    m_groupHandleCalled = true;
+  }
+  void unGroupHandle(const std::string &wsName,
+                     const Workspace_sptr &ws) override {
+    UNUSED_ARG(wsName)
+    UNUSED_ARG(ws)
+    m_unGroupHandleCalled = true;
+  }
+  void groupUpdateHandle(const std::string &wsName,
+                         const Workspace_sptr &ws) override {
+    UNUSED_ARG(wsName)
+    UNUSED_ARG(ws)
+    m_groupUpdateHandleCalled = true;
+  }
+
+public:
+  bool m_anyChangeHandleCalled, m_addHandleCalled, m_replaceHandleCalled,
+      m_deleteHandleCalled, m_clearHandleCalled, m_renameHandleCalled,
+      m_groupHandleCalled, m_unGroupHandleCalled, m_groupUpdateHandleCalled;
+};
+
+class AnalysisDataServiceObserverTest : public CxxTest::TestSuite {
+private:
+  AnalysisDataServiceImpl &ads;
+  std::unique_ptr<FakeAnalysisDataServiceObserver> m_mockInheritingClass;
+
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static AnalysisDataServiceObserverTest *createSuite() {
+    return new AnalysisDataServiceObserverTest();
+  }
+  static void destroySuite(AnalysisDataServiceObserverTest *suite) {
+    delete suite;
+  }
+
+  AnalysisDataServiceObserverTest()
+      : ads(AnalysisDataService::Instance()),
+        m_mockInheritingClass(
+            std::make_unique<FakeAnalysisDataServiceObserver>()) {
+    // Loads the framework manager
+    Mantid::API::FrameworkManager::Instance();
+  }
+
+  void setUp() override {
+    ads.clear();
+    m_mockInheritingClass = std::make_unique<FakeAnalysisDataServiceObserver>();
+  }
+
+  void addWorkspaceToADS(std::string name = "dummy") {
+    IAlgorithm_sptr alg =
+        Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+            "CreateSampleWorkspace");
+    alg->setChild(true);
+    alg->initialize();
+    alg->setPropertyValue("OutputWorkspace", name);
+    alg->execute();
+    MatrixWorkspace_sptr ws = alg->getProperty("OutputWorkspace");
+    ads.addOrReplace(name, ws);
+  }
+
+  void test_anyChangeHandle_is_called_on_add() {
+    m_mockInheritingClass->observeAll();
+    addWorkspaceToADS("dummy");
+
+    TS_ASSERT(m_mockInheritingClass->m_anyChangeHandleCalled)
+  }
+
+  void test_addHandle_is_called_on_add() {
+    m_mockInheritingClass->observeAdd();
+    addWorkspaceToADS("dummy");
+
+    TS_ASSERT(m_mockInheritingClass->m_addHandleCalled)
+  }
+
+  void test_deleteHandle_is_called_on_delete() {
+    addWorkspaceToADS();
+
+    m_mockInheritingClass->observeDelete();
+    ads.remove("dummy");
+
+    TS_ASSERT(m_mockInheritingClass->m_deleteHandleCalled)
+  }
+
+  void test_replaceHandle_is_called_on_replace() {
+    addWorkspaceToADS("dummy");
+
+    m_mockInheritingClass->observeReplace();
+    addWorkspaceToADS("dummy");
+
+    TS_ASSERT(m_mockInheritingClass->m_replaceHandleCalled)
+  }
+
+  void test_clearHandle_is_called_on_clear() {
+    addWorkspaceToADS("dummy");
+
+    m_mockInheritingClass->observeClear();
+    ads.clear();
+
+    TS_ASSERT(m_mockInheritingClass->m_clearHandleCalled)
+  }
+
+  void test_renameHandle_is_called_on_rename() {
+    addWorkspaceToADS("dummy");
+
+    m_mockInheritingClass->observeRename();
+    IAlgorithm_sptr alg =
+        Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+            "RenameWorkspace");
+    alg->initialize();
+    alg->setPropertyValue("InputWorkspace", "dummy");
+    alg->setPropertyValue("OutputWorkspace", "dummy2");
+    alg->execute();
+
+    TS_ASSERT(m_mockInheritingClass->m_renameHandleCalled)
+  }
+
+  void test_groupHandle_is_called_on_group_made() {
+    addWorkspaceToADS("dummy");
+    addWorkspaceToADS("dummy2");
+
+    m_mockInheritingClass->observeGroup();
+
+    IAlgorithm_sptr alg =
+        Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+            "GroupWorkspaces");
+    alg->initialize();
+    alg->setPropertyValue("InputWorkspaces", "dummy,dummy2");
+    alg->setPropertyValue("OutputWorkspace", "newGroup");
+    alg->execute();
+
+    TS_ASSERT(m_mockInheritingClass->m_groupHandleCalled)
+  }
+
+  void test_unGroupHandle_is_called_on_un_grouping() {
+    addWorkspaceToADS("dummy");
+    addWorkspaceToADS("dummy2");
+
+    IAlgorithm_sptr alg =
+        Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+            "GroupWorkspaces");
+    alg->initialize();
+    alg->setPropertyValue("InputWorkspaces", "dummy,dummy2");
+    alg->setPropertyValue("OutputWorkspace", "newGroup");
+    alg->execute();
+
+    m_mockInheritingClass->observeUnGroup();
+
+    IAlgorithm_sptr alg2 =
+        Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+            "UnGroupWorkspace");
+    alg2->initialize();
+    alg2->setPropertyValue("InputWorkspace", "newGroup");
+    alg2->execute();
+
+    TS_ASSERT(m_mockInheritingClass->m_unGroupHandleCalled)
+  }
+
+  void test_groupUpdated_is_called_on_group_updated() {
+    addWorkspaceToADS("dummy");
+    addWorkspaceToADS("dummy2");
+    addWorkspaceToADS("dummy3");
+
+    IAlgorithm_sptr alg =
+        Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+            "GroupWorkspaces");
+    alg->initialize();
+    alg->setPropertyValue("InputWorkspaces", "dummy,dummy2");
+    alg->setPropertyValue("OutputWorkspace", "newGroup");
+    alg->execute();
+
+    m_mockInheritingClass->observeGroupUpdate();
+
+    ads.addToGroup("newGroup", "dummy3");
+
+    TS_ASSERT(m_mockInheritingClass->m_groupUpdateHandleCalled)
+  }
+};
+
+#endif /* ANALYSISDATASERVICEOBSERVERTEST_H_ */
\ No newline at end of file
diff --git a/Framework/API/test/FileFinderTest.h b/Framework/API/test/FileFinderTest.h
index 27005b3656503eccd8c6e736adb99ddfb4aa0d63..04df991f430b5779cac73734ceaf0fe00c05fd7c 100644
--- a/Framework/API/test/FileFinderTest.h
+++ b/Framework/API/test/FileFinderTest.h
@@ -335,6 +335,84 @@ public:
     }
   }
 
+  void testThatGetUniqueExtensionsPreservesOrder() {
+    auto &fileFinder = FileFinder::Instance();
+    fileFinder.setCaseSensitive(false);
+
+    const std::vector<std::string> extensions1 = {".RAW", ".b", ".txt"};
+    const std::vector<std::string> extensions2 = {".a", ".raw", ".txt"};
+
+    std::vector<std::string> uniqueExts = {".log"};
+    const std::vector<std::string> expectedExts1 = {".log", ".raw", ".b",
+                                                    ".txt"};
+    const std::vector<std::string> expectedExts2 = {".log", ".raw", ".b",
+                                                    ".txt", ".a"};
+
+    fileFinder.getUniqueExtensions(extensions1, uniqueExts);
+    TS_ASSERT_EQUALS(uniqueExts.size(), expectedExts1.size());
+
+    fileFinder.getUniqueExtensions(extensions2, uniqueExts);
+    TS_ASSERT_EQUALS(uniqueExts.size(),
+                     expectedExts2.size()); // Contain same number of elements
+    auto itVec = expectedExts2.begin();
+    auto itSet = uniqueExts.begin();
+    for (; itVec != expectedExts2.end(); ++itVec, ++itSet) {
+      // Elements are in the same order
+      TS_ASSERT_EQUALS(*itVec, *itSet);
+    }
+  }
+
+  void testThatGetUniqueExtensionsPreservesOrderWithCaseSensitivity() {
+    auto &fileFinder = FileFinder::Instance();
+    fileFinder.setCaseSensitive(true);
+
+    const std::vector<std::string> extensions1 = {".RAW", ".b", ".txt"};
+    const std::vector<std::string> extensions2 = {".a", ".raw", ".txt"};
+
+    std::vector<std::string> uniqueExts = {".log"};
+    const std::vector<std::string> expectedExts1 = {".log", ".RAW", ".b",
+                                                    ".txt"};
+    const std::vector<std::string> expectedExts2 = {".log", ".RAW", ".b",
+                                                    ".txt", ".a",   ".raw"};
+
+    fileFinder.getUniqueExtensions(extensions1, uniqueExts);
+    TS_ASSERT_EQUALS(uniqueExts.size(), expectedExts1.size());
+
+    fileFinder.getUniqueExtensions(extensions2, uniqueExts);
+    TS_ASSERT_EQUALS(uniqueExts.size(),
+                     expectedExts2.size()); // Contain same number of elements
+    auto itVec = expectedExts2.begin();
+    auto itSet = uniqueExts.begin();
+    for (; itVec != expectedExts2.end(); ++itVec, ++itSet) {
+      // Elements are in the same order
+      TS_ASSERT_EQUALS(*itVec, *itSet);
+    }
+  }
+
+  void testFindRunWithOverwriteExtensionsAndIncorrectExtensions() {
+    std::string path;
+
+    // This file is .nxs or .RAW
+    const std::vector<std::string> incorrect_extension = {".txt"};
+    path =
+        FileFinder::Instance().findRun("MUSR15189", incorrect_extension, true);
+    TS_ASSERT_EQUALS(path, "");
+  }
+
+  void testFindRunWithOverwriteExtensionsAndOneCorrectExtension() {
+    std::string path;
+
+    // This file is .nxs or .RAW
+    // returns a .nxs if no extensions passed in
+    const std::vector<std::string> extensions = {".a", ".txt", ".nxs"};
+    path = FileFinder::Instance().findRun("MUSR15189", extensions, true);
+    std::string actualExtension = "";
+    if (!path.empty()) {
+      actualExtension = path.substr(path.size() - 4, 4);
+    }
+    TS_ASSERT_EQUALS(actualExtension, ".nxs");
+  }
+
   void testFindAddFiles() {
     // create a test file to find
     Poco::File file("LOQ00111-add.raw");
diff --git a/Framework/API/test/SampleTest.h b/Framework/API/test/SampleTest.h
index 6b73e2df9e74b9dfb165eff2f23df170d0f0fb50..65fa08fdf43e672636b745199fd1fe90ed29d78c 100644
--- a/Framework/API/test/SampleTest.h
+++ b/Framework/API/test/SampleTest.h
@@ -62,15 +62,14 @@ public:
   test_That_An_Environment_Can_Be_Set_And_The_Same_Environment_Is_Returned() {
     Sample sample;
     const std::string envName("TestKit");
-    SampleEnvironment *kit =
-        new SampleEnvironment(envName, boost::make_shared<const Container>(""));
+    auto kit = std::make_unique<SampleEnvironment>(
+        envName, boost::make_shared<const Container>(""));
     kit->add(boost::make_shared<const CSGObject>());
 
-    TS_ASSERT_THROWS_NOTHING(sample.setEnvironment(kit));
+    TS_ASSERT_THROWS_NOTHING(sample.setEnvironment(std::move(kit)));
 
     const SampleEnvironment &sampleKit = sample.getEnvironment();
     // Test that this references the correct object
-    TS_ASSERT_EQUALS(&sampleKit, kit);
     TS_ASSERT_EQUALS(sampleKit.name(), envName);
     TS_ASSERT_EQUALS(sampleKit.nelements(), 2);
   }
diff --git a/Framework/Algorithms/CMakeLists.txt b/Framework/Algorithms/CMakeLists.txt
index b25448dccdee91276b4fe8a0377ef79f5f96b8c2..2c9e58755e71e90664f6df8bc98d02911ff0ccf6 100644
--- a/Framework/Algorithms/CMakeLists.txt
+++ b/Framework/Algorithms/CMakeLists.txt
@@ -1043,4 +1043,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Algorithms ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS Algorithms INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/BinaryOperation.h b/Framework/Algorithms/inc/MantidAlgorithms/BinaryOperation.h
index 2f4c5017c4bcc4b04319a87d189e3e2df5fdccf8..09131b4b35ac9cd2fc37208f9dccf83829ecacce 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/BinaryOperation.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/BinaryOperation.h
@@ -14,6 +14,7 @@
 #include "MantidAPI/Workspace_fwd.h"
 #include "MantidDataObjects/EventList.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/System.h"
 
 namespace Mantid {
@@ -102,41 +103,29 @@ protected:
   /** Carries out the binary operation on a single spectrum, with another
    *spectrum as the right-hand operand.
    *
-   *  @param lhsX :: The X values, made available if required.
-   *  @param lhsY :: The vector of lhs data values
-   *  @param lhsE :: The vector of lhs error values
-   *  @param rhsY :: The vector of rhs data values
-   *  @param rhsE :: The vector of rhs error values
-   *  @param YOut :: The vector to hold the data values resulting from the
-   *operation
-   *  @param EOut :: The vector to hold the error values resulting from the
-   *operation
+   *  @param lhs :: Lhs histogram data
+   *  @param rhs :: Rhs histogram data
+   *  @param YOut :: Data values resulting from the operation
+   *  @param EOut :: Drror values resulting from the operation
    */
-  virtual void performBinaryOperation(const MantidVec &lhsX,
-                                      const MantidVec &lhsY,
-                                      const MantidVec &lhsE,
-                                      const MantidVec &rhsY,
-                                      const MantidVec &rhsE, MantidVec &YOut,
-                                      MantidVec &EOut) = 0;
+  virtual void performBinaryOperation(const HistogramData::Histogram &lhs,
+                                      const HistogramData::Histogram &rhs,
+                                      HistogramData::HistogramY &YOut,
+                                      HistogramData::HistogramE &EOut) = 0;
 
   /** Carries out the binary operation when the right hand operand is a single
    *number.
    *
-   *  @param lhsX :: The X values, made available if required.
-   *  @param lhsY :: The vector of lhs data values
-   *  @param lhsE :: The vector of lhs error values
+   *  @param lhs :: Lhs histogram data
    *  @param rhsY :: The rhs data value
-   *  @param rhsE :: The rhs error value
-   *  @param YOut :: The vector to hold the data values resulting from the
-   *operation
-   *  @param EOut :: The vector to hold the error values resulting from the
-   *operation
+   *  @param rhsE :: The lhs data value
+   *  @param YOut :: Data values resulting from the operation
+   *  @param EOut :: Error values resulting from the operation
    */
-  virtual void performBinaryOperation(const MantidVec &lhsX,
-                                      const MantidVec &lhsY,
-                                      const MantidVec &lhsE, const double rhsY,
-                                      const double rhsE, MantidVec &YOut,
-                                      MantidVec &EOut) = 0;
+  virtual void performBinaryOperation(const HistogramData::Histogram &lhs,
+                                      const double rhsY, const double rhsE,
+                                      HistogramData::HistogramY &YOut,
+                                      HistogramData::HistogramE &EOut) = 0;
 
   // ===================================== EVENT LIST BINARY OPERATIONS
   // ==========================================
@@ -155,9 +144,9 @@ protected:
    * with another (histogrammed) spectrum as the right-hand operand.
    *
    *  @param lhs :: Reference to the EventList that will be modified in place.
-   *  @param rhsX :: The vector of rhs X bin boundaries
-   *  @param rhsY :: The vector of rhs data values
-   *  @param rhsE :: The vector of rhs error values
+   *  @param rhsX :: Rhs X bin boundaries
+   *  @param rhsY :: Rhs data values
+   *  @param rhsE :: Rhs error values
    */
   virtual void performEventBinaryOperation(DataObjects::EventList &lhs,
                                            const MantidVec &rhsX,
@@ -193,9 +182,9 @@ protected:
   /** Only overridden by operations that affect the properties of the run (e.g.
    * Plus
    *  where the proton currents (charges) are added). Otherwise it does nothing.
-   *  @param lhs :: one of the workspaces to operate on
-   *  @param rhs :: the other workspace
-   *  @param ans :: the output workspace
+   *  @param lhs :: One of the workspaces to operate on
+   *  @param rhs :: The other workspace
+   *  @param ans :: The output workspace
    */
 
   virtual void operateOnRun(const API::Run &lhs, const API::Run &rhs,
@@ -229,6 +218,10 @@ protected:
   bool m_AllowDifferentNumberSpectra{false};
   /// Flag to clear RHS workspace in binary operation
   bool m_ClearRHSWorkspace{false};
+  /// Cache for LHS workspace's blocksize
+  size_t m_lhsBlocksize;
+  /// Cache for RHS workspace's blocksize
+  size_t m_rhsBlocksize;
 
   //------ Requirements -----------
 
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CommutativeBinaryOperation.h b/Framework/Algorithms/inc/MantidAlgorithms/CommutativeBinaryOperation.h
index dde6b296101fef4a24f54d93f0e0e4bc08befc4a..c8922438d213c734706587389d97bfb888156a5c 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/CommutativeBinaryOperation.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/CommutativeBinaryOperation.h
@@ -4,12 +4,9 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-#ifndef MANTID_ALGORITHM_COMMUTATIVEBINARYOPERATION_H_
-#define MANTID_ALGORITHM_COMMUTATIVEBINARYOPERATION_H_
+#ifndef MANTID_ALGORITHMS_COMMUTATIVEBINARYOPERATION_H_
+#define MANTID_ALGORITHMS_COMMUTATIVEBINARYOPERATION_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/BinaryOperation.h"
 
 namespace Mantid {
@@ -38,4 +35,4 @@ protected:
 } // namespace Algorithms
 } // namespace Mantid
 
-#endif /*MANTID_ALGORITHM_COMMUTATIVEBINARYOPERATION_H_*/
+#endif /*MANTID_ALGORITHMS_COMMUTATIVEBINARYOPERATION_H_*/
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Divide.h b/Framework/Algorithms/inc/MantidAlgorithms/Divide.h
index add31a64e015df48de3887db6b2c2346c5e7d2b2..82ac1ef4bec2508e5a0d12f2d0084ae52a59718b 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Divide.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Divide.h
@@ -4,12 +4,9 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-#ifndef MANTID_ALGORITHM_DIVIDE_H_
-#define MANTID_ALGORITHM_DIVIDE_H_
+#ifndef MANTID_ALGORITHMS_DIVIDE_H_
+#define MANTID_ALGORITHMS_DIVIDE_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/BinaryOperation.h"
 
 namespace Mantid {
@@ -21,8 +18,8 @@ the init() & exec()  methods.
 
 Required Properties:
 <UL>
-<LI> InputWorkspace1 - The name of the workspace </LI>
-<LI> InputWorkspace2 - The name of the workspace </LI>
+<LI> LHSWorkspace - The name of the workspace </LI>
+<LI> RHSWorkspace - The name of the workspace </LI>
 <LI> OutputWorkspace - The name of the workspace in which to store the division
 data </LI>
 </UL>
@@ -50,14 +47,14 @@ private:
   void init() override;
   void exec() override;
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const MantidVec &rhsY,
-                              const MantidVec &rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const double rhsY,
-                              const double rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const HistogramData::Histogram &rhs,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const double rhsY, const double rhsE,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
   void setOutputUnits(const API::MatrixWorkspace_const_sptr lhs,
                       const API::MatrixWorkspace_const_sptr rhs,
                       API::MatrixWorkspace_sptr out) override;
@@ -86,4 +83,4 @@ private:
 } // namespace Algorithms
 } // namespace Mantid
 
-#endif /*MANTID_ALGORITHM_DIVIDE_H_*/
+#endif /*MANTID_ALGORITHMS_DIVIDE_H_*/
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Minus.h b/Framework/Algorithms/inc/MantidAlgorithms/Minus.h
index b2d6f754f7754a7f1496544acf5a7e39e09a85c0..bb7b0fb8df8f63f736293cdbdd43a841143331da 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Minus.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Minus.h
@@ -4,12 +4,8 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-#ifndef MANTID_ALGORITHM_MINUS_H_
-#define MANTID_ALGORITHM_MINUS_H_
-
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
+#ifndef MANTID_ALGORITHMS_MINUS_H_
+#define MANTID_ALGORITHMS_MINUS_H_
 #include "MantidAlgorithms/BinaryOperation.h"
 
 namespace Mantid {
@@ -21,8 +17,8 @@ the init() & exec() methods.
 
 Required Properties:
 <UL>
-<LI> InputWorkspace1 - The name of the workspace </LI>
-<LI> InputWorkspace2 - The name of the workspace </LI>
+<LI> LSHWorkspace - The name of the workspace </LI>
+<LI> RHSWorkspace - The name of the workspace </LI>
 <LI> OutputWorkspace - The name of the workspace in which to store the
 difference data </LI>
 </UL>
@@ -50,14 +46,14 @@ public:
 
 private:
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const MantidVec &rhsY,
-                              const MantidVec &rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const double rhsY,
-                              const double rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const HistogramData::Histogram &rhs,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const double rhsY, const double rhsE,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
   void performEventBinaryOperation(DataObjects::EventList &lhs,
                                    const DataObjects::EventList &rhs) override;
   void performEventBinaryOperation(DataObjects::EventList &lhs,
@@ -81,4 +77,4 @@ private:
 } // namespace Algorithms
 } // namespace Mantid
 
-#endif /*MANTID_ALGORITHM_MINUS_H_*/
+#endif /*MANTID_ALGORITHMS_MINUS_H_*/
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Multiply.h b/Framework/Algorithms/inc/MantidAlgorithms/Multiply.h
index e869ecfbd6384e86aa47f32ab7044a3dd22319f5..33d5949fc4d5ec508fc63802fe9d78bd92f0f420 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Multiply.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Multiply.h
@@ -4,12 +4,9 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-#ifndef MANTID_ALGORITHM_MULTIPLY_H_
-#define MANTID_ALGORITHM_MULTIPLY_H_
+#ifndef MANTID_ALGORITHMS_MULTIPLY_H_
+#define MANTID_ALGORITHMS_MULTIPLY_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/CommutativeBinaryOperation.h"
 
 namespace Mantid {
@@ -21,8 +18,8 @@ the init()&+ exec()  methods.
 
 Required Properties:
 <UL>
-<LI> InputWorkspace1 - The name of the workspace </LI>
-<LI> InputWorkspace2 - The name of the workspace </LI>
+<LI> LHSWorkspace - The name of the workspace </LI>
+<LI> RHSWorkspace - The name of the workspace </LI>
 <LI> OutputWorkspace - The name of the workspace in which to store the product
 data </LI>
 </UL>
@@ -48,14 +45,14 @@ public:
 
 private:
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const MantidVec &rhsY,
-                              const MantidVec &rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const double rhsY,
-                              const double rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const HistogramData::Histogram &rhs,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const double rhsY, const double rhsE,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
 
   void setOutputUnits(const API::MatrixWorkspace_const_sptr lhs,
                       const API::MatrixWorkspace_const_sptr rhs,
@@ -81,4 +78,4 @@ private:
 } // namespace Algorithms
 } // namespace Mantid
 
-#endif /*MANTID_ALGORITHM_MULTIPLY_H_*/
+#endif /*MANTID_ALGORITHMS_MULTIPLY_H_*/
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Plus.h b/Framework/Algorithms/inc/MantidAlgorithms/Plus.h
index b3e65b75fcb1a7a7b6a64d0a8e986a75ca422675..db9ec2f07900ddd1e5e5b4dcfa9abb08d6c580f6 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Plus.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Plus.h
@@ -4,12 +4,10 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-#ifndef MANTID_ALGORITHM_PLUS_H_
-#define MANTID_ALGORITHM_PLUS_H_
+#ifndef MANTID_ALGORITHMS_PLUS_H_
+#define MANTID_ALGORITHMS_PLUS_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
+#include "MantidAPI/MatrixWorkspace_fwd.h"
 #include "MantidAPI/Run.h"
 #include "MantidAlgorithms/CommutativeBinaryOperation.h"
 #include "MantidDataObjects/EventWorkspace.h"
@@ -17,14 +15,14 @@
 namespace Mantid {
 namespace Algorithms {
 /**
-Plus performs the difference of two input workspaces.
+Plus performs the addition of two input workspaces.
 It inherits from the Algorithm class, and overrides
 the init() & exec() methods.
 
 Required Properties:
 <UL>
-<LI> InputWorkspace1 - The name of the workspace </LI>
-<LI> InputWorkspace2 - The name of the workspace </LI>
+<LI> LHSWorkspace - The name of the workspace </LI>
+<LI> RHSWorkspace - The name of the workspace </LI>
 <LI> OutputWorkspace - The name of the workspace in which to store the added
 data </LI>
 </UL>
@@ -50,14 +48,14 @@ public:
 
 private:
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const MantidVec &rhsY,
-                              const MantidVec &rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const double rhsY,
-                              const double rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const HistogramData::Histogram &rhs,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const double rhsY, const double rhsE,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
   void performEventBinaryOperation(DataObjects::EventList &lhs,
                                    const DataObjects::EventList &rhs) override;
   void performEventBinaryOperation(DataObjects::EventList &lhs,
@@ -85,4 +83,4 @@ private:
 } // namespace Algorithms
 } // namespace Mantid
 
-#endif /*MANTID_ALGORITHM_PLUS_H_*/
+#endif /*MANTID_ALGORITHMS_PLUS_H_*/
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/PoissonErrors.h b/Framework/Algorithms/inc/MantidAlgorithms/PoissonErrors.h
index 9f10e2b7cb285d3e8071c0927cb4c05eb805cc5b..547b6bdfe3c14b21b06e5b8e48355443214a3058 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/PoissonErrors.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/PoissonErrors.h
@@ -7,9 +7,6 @@
 #ifndef MANTID_ALGORITHMS_POISSONERRORS_H_
 #define MANTID_ALGORITHMS_POISSONERRORS_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/BinaryOperation.h"
 
 namespace Mantid {
@@ -52,14 +49,14 @@ public:
 
 private:
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const MantidVec &rhsY,
-                              const MantidVec &rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const double rhsY,
-                              const double rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const HistogramData::Histogram &rhs,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const double rhsY, const double rhsE,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
   std::string checkSizeCompatibility(
       const API::MatrixWorkspace_const_sptr lhs,
       const API::MatrixWorkspace_const_sptr rhs) const override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RadiusSum.h b/Framework/Algorithms/inc/MantidAlgorithms/RadiusSum.h
index 6b48cf6c34b533af2b45d79e184bc9cc6d7d5f72..87e4c462b302b81e0d3b77b65f06c67635f13d5a 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RadiusSum.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RadiusSum.h
@@ -62,7 +62,7 @@ private:
   double getMaxDistance(const Kernel::V3D &centre,
                         const std::vector<double> &boundary_limits);
 
-  void setUpOutputWorkspace(std::vector<double> &values);
+  void setUpOutputWorkspace(const std::vector<double> &values);
 
   int getBinForPixelPos(const Kernel::V3D &pos);
 
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h b/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h
index 2af82cb9170eed4888b904c0075215ed6169c8d5..5b47790a9830ee6d544be97090bce9984f81bd8b 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h
@@ -49,6 +49,8 @@ public:
 private:
   /// Overwrites Algorithm method.
   void init() override;
+  /// Pass groups in as they are to this algorithm
+  bool checkGroups() override { return false; }
   /// Overwrites Algorithm method.
   void exec() override;
 
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/TimeAtSampleStrategy.h b/Framework/Algorithms/inc/MantidAlgorithms/TimeAtSampleStrategy.h
index 8a7063175b1f847acaa8b21321db3512b7227f60..e315b2d241c4cf9eeff0b8535867fcfb07084560 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/TimeAtSampleStrategy.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/TimeAtSampleStrategy.h
@@ -13,7 +13,7 @@ namespace Mantid {
 namespace Algorithms {
 
 /**
- * @brief The Correction struct
+ * @brief The Correction struct to be applied as factor * TOF + offset
  * offset:: TOF offset in unit of TOF
  * factor:  TOF correction factor to multiply with
  */
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/WeightedMean.h b/Framework/Algorithms/inc/MantidAlgorithms/WeightedMean.h
index 3ba36ad90d40ce873ce88ae39d5af1f1029a3552..acce27c3238856e933639d12e5b4a561a980757f 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/WeightedMean.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/WeightedMean.h
@@ -7,10 +7,8 @@
 #ifndef MANTID_ALGORITHMS_WEIGHTEDMEAN_H_
 #define MANTID_ALGORITHMS_WEIGHTEDMEAN_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAlgorithms/BinaryOperation.h"
+#include "MantidAlgorithms/CommutativeBinaryOperation.h"
+#include "MantidHistogramData/Histogram.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -27,7 +25,7 @@ namespace Algorithms {
     @author Robert Dalgliesh, ISIS, RAL
     @date 12/1/2010
  */
-class DLLExport WeightedMean : public BinaryOperation {
+class DLLExport WeightedMean : public CommutativeBinaryOperation {
 public:
   const std::string name() const override { return "WeightedMean"; }
   /// Summary of algorithms purpose
@@ -42,14 +40,14 @@ public:
 
 private:
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const MantidVec &rhsY,
-                              const MantidVec &rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const double rhsY,
-                              const double rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const HistogramData::Histogram &rhs,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const double rhsY, const double rhsE,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override;
   bool
   checkCompatibility(const API::MatrixWorkspace_const_sptr lhs,
                      const API::MatrixWorkspace_const_sptr rhs) const override;
diff --git a/Framework/Algorithms/src/AbsorptionCorrection.cpp b/Framework/Algorithms/src/AbsorptionCorrection.cpp
index f74a8aa03e12675626433206ca857c168a8c8cd1..0f7d7d1d8234d504556f47d7b5a95760d34482b1 100644
--- a/Framework/Algorithms/src/AbsorptionCorrection.cpp
+++ b/Framework/Algorithms/src/AbsorptionCorrection.cpp
@@ -5,11 +5,12 @@
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/AbsorptionCorrection.h"
+#include "MantidAPI/HistoWorkspace.h"
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/Sample.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/IDetector.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Objects/ShapeFactory.h"
@@ -32,6 +33,7 @@ using namespace Geometry;
 using HistogramData::interpolateLinearInplace;
 using namespace Kernel;
 using namespace Mantid::PhysicalConstants;
+using namespace Mantid::DataObjects;
 
 AbsorptionCorrection::AbsorptionCorrection()
     : API::Algorithm(), m_inputWS(), m_sampleObject(nullptr), m_L1s(),
@@ -109,8 +111,7 @@ void AbsorptionCorrection::exec() {
   retrieveBaseProperties();
 
   // Create the output workspace
-  MatrixWorkspace_sptr correctionFactors =
-      WorkspaceFactory::Instance().create(m_inputWS);
+  MatrixWorkspace_sptr correctionFactors = create<HistoWorkspace>(*m_inputWS);
   correctionFactors->setDistribution(
       true);                       // The output of this is a distribution
   correctionFactors->setYUnit(""); // Need to explicitly set YUnit to nothing
@@ -138,6 +139,11 @@ void AbsorptionCorrection::exec() {
 
   // Calculate the cached values of L1 and element volumes.
   initialiseCachedDistances();
+  if (m_L1s.empty()) {
+    throw std::runtime_error(
+        "Failed to define any initial scattering gauge volume for geometry");
+  }
+
   // If sample not at origin, shift cached positions.
   const auto &spectrumInfo = m_inputWS->spectrumInfo();
   const V3D samplePos = spectrumInfo.samplePosition();
diff --git a/Framework/Algorithms/src/ApplyDetailedBalance.cpp b/Framework/Algorithms/src/ApplyDetailedBalance.cpp
index 45d66be1f2af763803e237b5416ad2486729f716..8fc540533a86c24d2647790bdd4d2e2b5862d5b2 100644
--- a/Framework/Algorithms/src/ApplyDetailedBalance.cpp
+++ b/Framework/Algorithms/src/ApplyDetailedBalance.cpp
@@ -6,8 +6,8 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/ApplyDetailedBalance.h"
 #include "MantidAPI/Run.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/PhysicalConstants.h"
@@ -20,6 +20,7 @@
 using std::string;
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
+using namespace Mantid::DataObjects;
 
 namespace Mantid {
 namespace Algorithms {
@@ -57,7 +58,7 @@ void ApplyDetailedBalance::exec() {
   // If input and output workspaces are not the same, create a new workspace for
   // the output
   if (outputWS != inputWS) {
-    outputWS = API::WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<MatrixWorkspace>(*inputWS);
   }
 
   std::string Tstring = getProperty("Temperature");
diff --git a/Framework/Algorithms/src/ApplyFloodWorkspace.cpp b/Framework/Algorithms/src/ApplyFloodWorkspace.cpp
index cd2294d7d2aad6fc6ad20e89ad5a257179efee4b..f737002dee067dec5607d93336b620f0cb6035fa 100644
--- a/Framework/Algorithms/src/ApplyFloodWorkspace.cpp
+++ b/Framework/Algorithms/src/ApplyFloodWorkspace.cpp
@@ -43,6 +43,7 @@ MatrixWorkspace_sptr makeEqualSizes(const MatrixWorkspace_sptr &input,
   auto newFlood =
       WorkspaceFactory::Instance().create(flood, input->getNumberHistograms());
   auto const table = BinaryOperation::buildBinaryOperationTable(input, flood);
+  auto const floodBlocksize = flood->blocksize();
   const ISpectrum *missingSpectrum = nullptr;
   for (size_t i = 0; i < table->size(); ++i) {
     auto const j = (*table)[i];
@@ -50,8 +51,8 @@ MatrixWorkspace_sptr makeEqualSizes(const MatrixWorkspace_sptr &input,
       if (missingSpectrum) {
         newFlood->getSpectrum(i).copyDataFrom(*missingSpectrum);
       } else {
-        newFlood->dataY(i).assign(flood->blocksize(), 1.0);
-        newFlood->dataE(i).assign(flood->blocksize(), 0.0);
+        newFlood->dataY(i).assign(floodBlocksize, 1.0);
+        newFlood->dataE(i).assign(floodBlocksize, 0.0);
         missingSpectrum = &newFlood->getSpectrum(i);
       }
     } else {
diff --git a/Framework/Algorithms/src/ApplyTransmissionCorrection.cpp b/Framework/Algorithms/src/ApplyTransmissionCorrection.cpp
index 142430f764cd2d0412035b108d758b2851866083..2b51542019a67a55e9219e91458e6f38f3b4d962 100644
--- a/Framework/Algorithms/src/ApplyTransmissionCorrection.cpp
+++ b/Framework/Algorithms/src/ApplyTransmissionCorrection.cpp
@@ -7,9 +7,10 @@
 #include "MantidAlgorithms/ApplyTransmissionCorrection.h"
 #include "MantidAPI/HistogramValidator.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceOpOverloads.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/IDetector.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
@@ -24,6 +25,7 @@ using namespace Kernel;
 using namespace API;
 using namespace Geometry;
 using namespace HistogramData;
+using namespace DataObjects;
 
 void ApplyTransmissionCorrection::init() {
   auto wsValidator = boost::make_shared<CompositeValidator>();
@@ -90,7 +92,7 @@ void ApplyTransmissionCorrection::exec() {
   Progress progress(this, 0.0, 1.0, numHists);
 
   // Create a Workspace2D to match the intput workspace
-  MatrixWorkspace_sptr corrWS = WorkspaceFactory::Instance().create(inputWS);
+  MatrixWorkspace_sptr corrWS = create<HistoWorkspace>(*inputWS);
 
   const auto &spectrumInfo = inputWS->spectrumInfo();
 
@@ -137,4 +139,4 @@ void ApplyTransmissionCorrection::exec() {
 }
 
 } // namespace Algorithms
-} // namespace Mantid
+} // namespace Mantid
\ No newline at end of file
diff --git a/Framework/Algorithms/src/BinaryOperation.cpp b/Framework/Algorithms/src/BinaryOperation.cpp
index 97407716ba7b9520d6a544552d670021645bdfc9..4e7f8a22583a7588e4aeb52075be70be50fd4851 100644
--- a/Framework/Algorithms/src/BinaryOperation.cpp
+++ b/Framework/Algorithms/src/BinaryOperation.cpp
@@ -13,17 +13,20 @@
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidDataObjects/EventList.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidDataObjects/WorkspaceSingleValue.h"
 #include "MantidGeometry/Instrument/ParameterMap.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/Timer.h"
 #include "MantidKernel/Unit.h"
-
 #include <boost/make_shared.hpp>
 
 using namespace Mantid::Geometry;
 using namespace Mantid::API;
 using namespace Mantid::Kernel;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using std::size_t;
 
 namespace Mantid {
@@ -105,7 +108,7 @@ bool BinaryOperation::handleSpecialDivideMinus() {
     } else if (this->name() == "Minus") {
       // x - workspace = x + (workspace * -1)
       MatrixWorkspace_sptr minusOne =
-          WorkspaceFactory::Instance().create("WorkspaceSingleValue", 1, 1, 1);
+          create<WorkspaceSingleValue>(1, Points(1));
       minusOne->dataY(0)[0] = -1.0;
       minusOne->dataE(0)[0] = 0.0;
 
@@ -148,6 +151,9 @@ void BinaryOperation::exec() {
   m_rhs = getProperty(inputPropName2());
   m_AllowDifferentNumberSpectra = getProperty("AllowDifferentNumberSpectra");
 
+  m_lhsBlocksize = m_lhs->blocksize();
+  m_rhsBlocksize = m_rhs->blocksize();
+
   // Special handling for 1-WS and 1/WS.
   if (this->handleSpecialDivideMinus())
     return;
@@ -182,6 +188,7 @@ void BinaryOperation::exec() {
     // Flip the workspaces left and right
     std::swap(m_lhs, m_rhs);
     std::swap(m_elhs, m_erhs);
+    std::swap(m_lhsBlocksize, m_rhsBlocksize);
   }
 
   // Check that the input workspaces are compatible
@@ -234,7 +241,7 @@ void BinaryOperation::exec() {
     //   (b) it has been, but it's not the correct dimensions
     if ((m_out != m_lhs && m_out != m_rhs) ||
         (m_out == m_rhs && (m_lhs->size() > m_rhs->size()))) {
-      m_out = WorkspaceFactory::Instance().create(m_lhs);
+      m_out = create<HistoWorkspace>(*m_lhs);
     }
   }
 
@@ -261,7 +268,7 @@ void BinaryOperation::exec() {
   }
   // Single column on rhs; if the RHS is an event workspace with one bin, it is
   // treated as a scalar.
-  else if ((m_rhs->blocksize() == 1) && !m_do2D_even_for_SingleColumn_on_rhs) {
+  else if ((m_rhsBlocksize == 1) && !m_do2D_even_for_SingleColumn_on_rhs) {
     doSingleColumn();
   } else // The two are both 2D and should be the same size (except if LHS is an
          // event workspace)
@@ -315,15 +322,15 @@ bool BinaryOperation::checkCompatibility(
   const std::string rhs_unitID = (rhs_unit ? rhs_unit->unitID() : "");
 
   // Check the workspaces have the same units and distribution flag
-  if (lhs_unitID != rhs_unitID && lhs->blocksize() > 1 &&
-      rhs->blocksize() > 1) {
+  if (lhs_unitID != rhs_unitID && m_lhsBlocksize > 1 && m_rhsBlocksize > 1) {
     g_log.error("The two workspace are not compatible because they have "
                 "different units on the X axis.");
     return false;
   }
 
   // Check the size compatibility
-  std::string checkSizeCompatibilityResult = checkSizeCompatibility(lhs, rhs);
+  const std::string checkSizeCompatibilityResult =
+      checkSizeCompatibility(lhs, rhs);
   if (!checkSizeCompatibilityResult.empty()) {
     throw std::invalid_argument(checkSizeCompatibilityResult);
   }
@@ -380,7 +387,7 @@ std::string BinaryOperation::checkSizeCompatibility(
   }
   // Otherwise they must match both ways, or horizontally or vertically with the
   // other rhs dimension=1
-  if (rhs->blocksize() == 1 &&
+  if (m_rhsBlocksize == 1 &&
       lhs->getNumberHistograms() == rhs->getNumberHistograms())
     return "";
   // Past this point, we require the X arrays to match. Note this only checks
@@ -392,7 +399,7 @@ std::string BinaryOperation::checkSizeCompatibility(
 
   const size_t rhsSpec = rhs->getNumberHistograms();
 
-  if (lhs->blocksize() == rhs->blocksize()) {
+  if (m_lhsBlocksize == m_rhsBlocksize) {
     if (rhsSpec == 1 || lhs->getNumberHistograms() == rhsSpec) {
       return "";
     } else {
@@ -456,8 +463,8 @@ void BinaryOperation::doSingleValue() {
   // single value was masked
 
   // Pull out the single value and its error
-  const double rhsY = m_rhs->readY(0)[0];
-  const double rhsE = m_rhs->readE(0)[0];
+  const double rhsY = m_rhs->y(0)[0];
+  const double rhsE = m_rhs->e(0)[0];
 
   // Now loop over the spectra of the left hand side calling the virtual
   // function
@@ -468,7 +475,7 @@ void BinaryOperation::doSingleValue() {
     PARALLEL_FOR_IF(Kernel::threadSafe(*m_lhs, *m_rhs, *m_out))
     for (int64_t i = 0; i < numHists; ++i) {
       PARALLEL_START_INTERUPT_REGION
-      m_out->setX(i, m_lhs->refX(i));
+      m_out->setSharedX(i, m_lhs->sharedX(i));
       performEventBinaryOperation(m_eout->getSpectrum(i), rhsY, rhsE);
       m_progress->report(this->name());
       PARALLEL_END_INTERUPT_REGION
@@ -479,15 +486,14 @@ void BinaryOperation::doSingleValue() {
     PARALLEL_FOR_IF(Kernel::threadSafe(*m_lhs, *m_rhs, *m_out))
     for (int64_t i = 0; i < numHists; ++i) {
       PARALLEL_START_INTERUPT_REGION
-      m_out->setX(i, m_lhs->refX(i));
+      m_out->setSharedX(i, m_lhs->sharedX(i));
       // Get reference to output vectors here to break any sharing outside the
       // function call below
       // where the order of argument evaluation is not guaranteed (if it's L->R
       // there would be a data race)
-      MantidVec &outY = m_out->dataY(i);
-      MantidVec &outE = m_out->dataE(i);
-      performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i), m_lhs->readE(i),
-                             rhsY, rhsE, outY, outE);
+      HistogramData::HistogramY &outY = m_out->mutableY(i);
+      HistogramData::HistogramE &outE = m_out->mutableE(i);
+      performBinaryOperation(m_lhs->histogram(i), rhsY, rhsE, outY, outE);
       m_progress->report(this->name());
       PARALLEL_END_INTERUPT_REGION
     }
@@ -515,10 +521,8 @@ void BinaryOperation::doSingleColumn() {
     PARALLEL_FOR_IF(Kernel::threadSafe(*m_lhs, *m_rhs, *m_out))
     for (int64_t i = 0; i < numHists; ++i) {
       PARALLEL_START_INTERUPT_REGION
-      const double rhsY = m_rhs->readY(i)[0];
-      const double rhsE = m_rhs->readE(i)[0];
-
-      // m_out->setX(i, m_lhs->refX(i)); //unnecessary - that was copied before.
+      const double rhsY = m_rhs->y(i)[0];
+      const double rhsE = m_rhs->e(i)[0];
       if (propagateSpectraMask(lhsSpectrumInfo, rhsSpectrumInfo, i, *m_out,
                                outSpectrumInfo)) {
         performEventBinaryOperation(m_eout->getSpectrum(i), rhsY, rhsE);
@@ -532,20 +536,19 @@ void BinaryOperation::doSingleColumn() {
     PARALLEL_FOR_IF(Kernel::threadSafe(*m_lhs, *m_rhs, *m_out))
     for (int64_t i = 0; i < numHists; ++i) {
       PARALLEL_START_INTERUPT_REGION
-      const double rhsY = m_rhs->readY(i)[0];
-      const double rhsE = m_rhs->readE(i)[0];
+      const double rhsY = m_rhs->y(i)[0];
+      const double rhsE = m_rhs->e(i)[0];
 
-      m_out->setX(i, m_lhs->refX(i));
+      m_out->setSharedX(i, m_lhs->sharedX(i));
       if (propagateSpectraMask(lhsSpectrumInfo, rhsSpectrumInfo, i, *m_out,
                                outSpectrumInfo)) {
         // Get reference to output vectors here to break any sharing outside the
         // function call below
         // where the order of argument evaluation is not guaranteed (if it's
         // L->R there would be a data race)
-        MantidVec &outY = m_out->dataY(i);
-        MantidVec &outE = m_out->dataE(i);
-        performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i),
-                               m_lhs->readE(i), rhsY, rhsE, outY, outE);
+        HistogramData::HistogramY &outY = m_out->mutableY(i);
+        HistogramData::HistogramE &outE = m_out->mutableE(i);
+        performBinaryOperation(m_lhs->histogram(i), rhsY, rhsE, outY, outE);
       }
       m_progress->report(this->name());
       PARALLEL_END_INTERUPT_REGION
@@ -578,9 +581,6 @@ void BinaryOperation::doSingleSpectrum() {
       PARALLEL_FOR_IF(Kernel::threadSafe(*m_lhs, *m_rhs, *m_out))
       for (int64_t i = 0; i < numHists; ++i) {
         PARALLEL_START_INTERUPT_REGION
-        // m_out->setX(i,m_lhs->refX(i)); //unnecessary - that was copied
-        // before.
-
         // Perform the operation on the event list on the output (== lhs)
         performEventBinaryOperation(m_eout->getSpectrum(i), rhs_spectrum);
         m_progress->report(this->name());
@@ -601,8 +601,6 @@ void BinaryOperation::doSingleSpectrum() {
       PARALLEL_FOR_IF(Kernel::threadSafe(*m_lhs, *m_rhs, *m_out))
       for (int64_t i = 0; i < numHists; ++i) {
         PARALLEL_START_INTERUPT_REGION
-        // m_out->setX(i,m_lhs->refX(i)); //unnecessary - that was copied
-        // before.
         // Perform the operation on the event list on the output (== lhs)
         performEventBinaryOperation(m_eout->getSpectrum(i), rhsX, rhsY, rhsE);
         m_progress->report(this->name());
@@ -617,8 +615,7 @@ void BinaryOperation::doSingleSpectrum() {
     //  will be used instead)
 
     // Pull m_out the m_rhs spectrum
-    const MantidVec &rhsY = m_rhs->readY(0);
-    const MantidVec &rhsE = m_rhs->readE(0);
+    const auto rhs = m_rhs->histogram(0);
 
     // Now loop over the spectra of the left hand side calling the virtual
     // function
@@ -627,15 +624,14 @@ void BinaryOperation::doSingleSpectrum() {
     PARALLEL_FOR_IF(Kernel::threadSafe(*m_lhs, *m_rhs, *m_out))
     for (int64_t i = 0; i < numHists; ++i) {
       PARALLEL_START_INTERUPT_REGION
-      m_out->setX(i, m_lhs->refX(i));
+      m_out->setSharedX(i, m_lhs->sharedX(i));
       // Get reference to output vectors here to break any sharing outside the
       // function call below
       // where the order of argument evaluation is not guaranteed (if it's L->R
       // there would be a data race)
-      MantidVec &outY = m_out->dataY(i);
-      MantidVec &outE = m_out->dataE(i);
-      performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i), m_lhs->readE(i),
-                             rhsY, rhsE, outY, outE);
+      HistogramData::HistogramY &outY = m_out->mutableY(i);
+      HistogramData::HistogramE &outE = m_out->mutableE(i);
+      performBinaryOperation(m_lhs->histogram(i), rhs, outY, outE);
       m_progress->report(this->name());
       PARALLEL_END_INTERUPT_REGION
     }
@@ -748,7 +744,7 @@ void BinaryOperation::do2D(bool mismatchedSpectra) {
     for (int64_t i = 0; i < numHists; ++i) {
       PARALLEL_START_INTERUPT_REGION
       m_progress->report(this->name());
-      m_out->setX(i, m_lhs->refX(i));
+      m_out->setSharedX(i, m_lhs->sharedX(i));
       int64_t rhs_wi = i;
       if (mismatchedSpectra && table) {
         rhs_wi = (*table)[i];
@@ -765,11 +761,10 @@ void BinaryOperation::do2D(bool mismatchedSpectra) {
       // function call below
       // where the order of argument evaluation is not guaranteed (if it's L->R
       // there would be a data race)
-      MantidVec &outY = m_out->dataY(i);
-      MantidVec &outE = m_out->dataE(i);
-      performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i), m_lhs->readE(i),
-                             m_rhs->readY(rhs_wi), m_rhs->readE(rhs_wi), outY,
-                             outE);
+      HistogramData::HistogramY &outY = m_out->mutableY(i);
+      HistogramData::HistogramE &outE = m_out->mutableE(i);
+      performBinaryOperation(m_lhs->histogram(i), m_rhs->histogram(rhs_wi),
+                             outY, outE);
 
       // Free up memory on the RHS if that is possible
       if (m_ClearRHSWorkspace)
@@ -832,9 +827,9 @@ void BinaryOperation::performEventBinaryOperation(
  * with another (histogrammed) spectrum as the right-hand operand.
  *
  *  @param lhs :: Reference to the EventList that will be modified in place.
- *  @param rhsX :: The vector of rhs X bin boundaries
- *  @param rhsY :: The vector of rhs data values
- *  @param rhsE :: The vector of rhs error values
+ *  @param rhsX :: Rhs X bin boundaries
+ *  @param rhsY :: Rhs data values
+ *  @param rhsE :: Rhs error values
  */
 void BinaryOperation::performEventBinaryOperation(DataObjects::EventList &lhs,
                                                   const MantidVec &rhsX,
diff --git a/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp b/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp
index f23a237bd14afb0dbbb4eb45d6fa8dd3b12d992a..64adee333fc8b708c64c343e4298db405569263e 100644
--- a/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp
+++ b/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp
@@ -5,13 +5,14 @@
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/CalculateCarpenterSampleCorrection.h"
+#include "MantidAPI/HistoWorkspace.h"
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/Sample.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/Material.h"
@@ -23,7 +24,7 @@ namespace Algorithms {
 DECLARE_ALGORITHM(CalculateCarpenterSampleCorrection) // Register the class
                                                       // into the algorithm
                                                       // factory
-
+using namespace DataObjects;
 using namespace Kernel;
 using namespace API;
 using Mantid::DataObjects::EventWorkspace;
@@ -414,8 +415,7 @@ void CalculateCarpenterSampleCorrection::calculate_ms_correction(
 
 MatrixWorkspace_sptr CalculateCarpenterSampleCorrection::createOutputWorkspace(
     const MatrixWorkspace_sptr &inputWksp, const std::string ylabel) const {
-  MatrixWorkspace_sptr outputWS =
-      WorkspaceFactory::Instance().create(inputWksp);
+  MatrixWorkspace_sptr outputWS = create<HistoWorkspace>(*inputWksp);
   // The algorithm computes the signal values at bin centres so they should
   // be treated as a distribution
   outputWS->setDistribution(true);
diff --git a/Framework/Algorithms/src/CalculateCountRate.cpp b/Framework/Algorithms/src/CalculateCountRate.cpp
index d76b56968cbbbed4dae000cb25c78093f6c46964..2ed73de1868f8326b8d9d9925aebc464dd53898f 100644
--- a/Framework/Algorithms/src/CalculateCountRate.cpp
+++ b/Framework/Algorithms/src/CalculateCountRate.cpp
@@ -6,6 +6,13 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/CalculateCountRate.h"
 
+#include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/Axis.h"
+#include "MantidAPI/NumericAxis.h"
+#include "MantidAPI/Run.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MandatoryValidator.h"
@@ -15,15 +22,11 @@
 #include "MantidKernel/UnitFactory.h"
 #include "MantidKernel/make_unique.h"
 
-#include "MantidAPI/AlgorithmManager.h"
-#include "MantidAPI/Axis.h"
-#include "MantidAPI/NumericAxis.h"
-#include "MantidAPI/Run.h"
-#include "MantidAPI/WorkspaceFactory.h"
-
-#include "MantidDataObjects/Workspace2D.h"
 #include <numeric>
 
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
+
 namespace Mantid {
 namespace Algorithms {
 
@@ -621,9 +624,7 @@ void CalculateCountRate::checkAndInitVisWorkspace() {
   int numXBins = getProperty("XResolution");
   std::string RangeUnits = getProperty("RangeUnits");
 
-  m_visWs = boost::dynamic_pointer_cast<DataObjects::Workspace2D>(
-      API::WorkspaceFactory::Instance().create("Workspace2D", numTBins,
-                                               numXBins + 1, numXBins));
+  m_visWs = create<Workspace2D>(numTBins, BinEdges(numXBins + 1));
   m_visWs->setTitle(visWSName);
 
   double Xmax = m_XRangeMax;
diff --git a/Framework/Algorithms/src/CalculateFlatBackground.cpp b/Framework/Algorithms/src/CalculateFlatBackground.cpp
index e198dd19cabcc7820646e420d005d991f500ff58..f4af7be9e98e4ac4b11ceccf169bf3459abf254d 100644
--- a/Framework/Algorithms/src/CalculateFlatBackground.cpp
+++ b/Framework/Algorithms/src/CalculateFlatBackground.cpp
@@ -9,15 +9,17 @@
 #include "MantidAPI/IFunction.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceOpOverloads.h"
 #include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/IDetector.h"
 #include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/EnabledWhenProperty.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/VectorHelper.h"
+
 #include <algorithm>
 #include <boost/lexical_cast.hpp>
 #include <climits>
@@ -31,6 +33,7 @@ DECLARE_ALGORITHM(CalculateFlatBackground)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
 
 /// Enumeration for the different operating modes.
 enum class Modes { LINEAR_FIT, MEAN, MOVING_AVERAGE };
@@ -168,7 +171,7 @@ void CalculateFlatBackground::exec() {
   // If input and output workspaces are not the same, create a new workspace for
   // the output
   if (outputWS != inputWS) {
-    outputWS = WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<MatrixWorkspace>(*inputWS);
   }
 
   // For logging purposes.
@@ -382,8 +385,7 @@ void CalculateFlatBackground::Mean(const HistogramData::Histogram &histogram,
 void CalculateFlatBackground::LinearFit(
     const HistogramData::Histogram &histogram, double &background,
     double &variance, const double startX, const double endX) {
-  MatrixWorkspace_sptr WS = WorkspaceFactory::Instance().create(
-      "Workspace2D", 1, histogram.x().size(), histogram.y().size());
+  MatrixWorkspace_sptr WS = create<Workspace2D>(1, histogram);
   WS->setHistogram(0, histogram);
   IAlgorithm_sptr childAlg = createChildAlgorithm("Fit");
 
diff --git a/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp b/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp
index 865aff8b9478926b7852531b2aedf9a2e0b57adc..0e55bf577e1663f6b802c9aef644d4dc5634e328 100644
--- a/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp
+++ b/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp
@@ -11,10 +11,12 @@
 #include "MantidAPI/CommonBinsValidator.h"
 #include "MantidAPI/HistogramValidator.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceOpOverloads.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidDataObjects/WorkspaceSingleValue.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -27,6 +29,8 @@ DECLARE_ALGORITHM(CalculateTransmissionBeamSpreader)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
+using namespace HistogramData;
 using std::size_t;
 
 void CalculateTransmissionBeamSpreader::init() {
@@ -163,7 +167,7 @@ void CalculateTransmissionBeamSpreader::exec() {
 
   // Beam spreader transmission
   MatrixWorkspace_sptr spreader_trans =
-      WorkspaceFactory::Instance().create("WorkspaceSingleValue", 1, 1, 1);
+      create<WorkspaceSingleValue>(1, Points(1));
   spreader_trans->setYUnit("");
   spreader_trans->setDistribution(true);
   spreader_trans->mutableX(0)[0] = 0.0;
diff --git a/Framework/Algorithms/src/CalculateZscore.cpp b/Framework/Algorithms/src/CalculateZscore.cpp
index 8caa61e7e4bf083601e4e3be12ccc4f3d40c3ee2..48ba9b1315f96332539d58c70b89aee7e9596196 100644
--- a/Framework/Algorithms/src/CalculateZscore.cpp
+++ b/Framework/Algorithms/src/CalculateZscore.cpp
@@ -5,11 +5,12 @@
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/CalculateZscore.h"
-
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/Statistics.h"
 
@@ -19,6 +20,7 @@ using namespace Mantid;
 using namespace Mantid::API;
 using namespace Mantid::Kernel;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using namespace std;
 
 namespace Mantid {
@@ -66,9 +68,11 @@ void CalculateZscore::exec() {
   size_t sizex = inpWS->x(0).size();
   size_t sizey = inpWS->y(0).size();
 
-  Workspace2D_sptr outWS = boost::dynamic_pointer_cast<Workspace2D>(
-      WorkspaceFactory::Instance().create("Workspace2D", numspec, sizex,
-                                          sizey));
+  HistogramBuilder builder;
+  builder.setX(sizex);
+  builder.setY(sizey);
+  builder.setDistribution(inpWS->isDistribution());
+  Workspace2D_sptr outWS = create<Workspace2D>(numspec, builder.build());
 
   Progress progress(this, 0.0, 1.0, numspec);
 
diff --git a/Framework/Algorithms/src/ChopData.cpp b/Framework/Algorithms/src/ChopData.cpp
index 1f3c335c70061908c64b7c269dbcf90c0f3b9ff1..18d497604b5ea9a14124d72bcb7591cb6fffa51f 100644
--- a/Framework/Algorithms/src/ChopData.cpp
+++ b/Framework/Algorithms/src/ChopData.cpp
@@ -6,10 +6,12 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/ChopData.h"
 #include "MantidAPI/HistogramValidator.h"
+#include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/SpectraAxisValidator.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/MultiThreaded.h"
 
@@ -18,6 +20,8 @@ namespace Algorithms {
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
+using namespace DataObjects;
+using namespace HistogramData;
 
 DECLARE_ALGORITHM(ChopData)
 
@@ -42,7 +46,7 @@ void ChopData::init() {
 
 void ChopData::exec() {
   // Get the input workspace
-  MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
+  MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
   const std::string output = getPropertyValue("OutputWorkspace");
   const double step = getProperty("Step");
   const int chops = getProperty("NChops");
@@ -70,9 +74,8 @@ void ChopData::exec() {
     // Select the spectrum that is to be used to compare the sections of the
     // workspace
     // This will generally be the monitor spectrum.
-    MatrixWorkspace_sptr monitorWS;
-    monitorWS = WorkspaceFactory::Instance().create(inputWS, 1);
-    monitorWS->setHistogram(0, inputWS->histogram(monitorWi));
+    MatrixWorkspace_sptr monitorWS =
+        create<MatrixWorkspace>(*inputWS, 1, inputWS->histogram(monitorWi));
 
     int lowest = 0;
 
@@ -132,8 +135,7 @@ void ChopData::exec() {
     size_t nbins = indexHigh - indexLow;
 
     MatrixWorkspace_sptr workspace =
-        Mantid::API::WorkspaceFactory::Instance().create(inputWS, nHist,
-                                                         nbins + 1, nbins);
+        create<MatrixWorkspace>(*inputWS, BinEdges(nbins + 1));
 
     // Copy over X, Y and E data
     PARALLEL_FOR_IF(Kernel::threadSafe(*inputWS, *workspace))
diff --git a/Framework/Algorithms/src/CommutativeBinaryOperation.cpp b/Framework/Algorithms/src/CommutativeBinaryOperation.cpp
index be9f18e7f0ea104b1ed164c5ef63f48178f755f2..b20f75726bb2b49652a8957aeafb7c9348ec940c 100644
--- a/Framework/Algorithms/src/CommutativeBinaryOperation.cpp
+++ b/Framework/Algorithms/src/CommutativeBinaryOperation.cpp
@@ -4,9 +4,6 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/CommutativeBinaryOperation.h"
 
 namespace Mantid {
diff --git a/Framework/Algorithms/src/CompareWorkspaces.cpp b/Framework/Algorithms/src/CompareWorkspaces.cpp
index 6c37c7e6b5e095cb49f1af161415b5305013000d..e73fcac8f5cd18bbe03fb8a0c9d5bdbceeffeab9 100644
--- a/Framework/Algorithms/src/CompareWorkspaces.cpp
+++ b/Framework/Algorithms/src/CompareWorkspaces.cpp
@@ -14,7 +14,6 @@
 #include "MantidAPI/Run.h"
 #include "MantidAPI/Sample.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
@@ -179,7 +178,7 @@ void CompareWorkspaces::init() {
           "Messages", "compare_msgs", Direction::Output),
       "TableWorkspace containing messages about any mismatches detected");
 
-  m_messages = WorkspaceFactory::Instance().createTable("TableWorkspace");
+  m_messages = boost::make_shared<TableWorkspace>();
   m_messages->addColumn("str", "Message");
   m_messages->addColumn("str", "Workspace 1");
   m_messages->addColumn("str", "Workspace 2");
diff --git a/Framework/Algorithms/src/ConjoinXRuns.cpp b/Framework/Algorithms/src/ConjoinXRuns.cpp
index a690c3904925d7906d2ec2e66b3f737fddbf65c8..00e3155cd7ff95564680b6316b6fe7f9d7493697 100644
--- a/Framework/Algorithms/src/ConjoinXRuns.cpp
+++ b/Framework/Algorithms/src/ConjoinXRuns.cpp
@@ -11,12 +11,13 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Run.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidAPI/WorkspaceHistory.h"
 #include "MantidAlgorithms/RunCombinationHelpers/RunCombinationHelper.h"
 #include "MantidAlgorithms/RunCombinationHelpers/SampleLogsBehaviour.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidHistogramData/HistogramDx.h"
 #include "MantidHistogramData/HistogramE.h"
 #include "MantidHistogramData/HistogramX.h"
@@ -36,6 +37,8 @@ namespace Algorithms {
 using namespace API;
 using namespace Kernel;
 using namespace RunCombinationOptions;
+using namespace DataObjects;
+using namespace HistogramData;
 
 namespace {
 static const std::string INPUT_WORKSPACE_PROPERTY = "InputWorkspaces";
@@ -157,7 +160,7 @@ std::map<std::string, std::string> ConjoinXRuns::validateInputs() {
             "Workspace " + ws->getName() +
             " has different number of points per histogram\n";
       }
-      m_inputWS.push_back(ws);
+      m_inputWS.emplace_back(ws);
     }
   }
 
@@ -210,7 +213,7 @@ std::string ConjoinXRuns::checkLogEntry(MatrixWorkspace_sptr ws) const {
 
         // try if numeric time series, then the size must match to the
         // blocksize
-        const int blocksize = static_cast<int>(ws->blocksize());
+        const int blocksize = static_cast<int>(ws->y(0).size());
 
         TimeSeriesProperty<double> *timeSeriesDouble(nullptr);
         TimeSeriesProperty<int> *timeSeriesInt(nullptr);
@@ -251,7 +254,7 @@ std::string ConjoinXRuns::checkLogEntry(MatrixWorkspace_sptr ws) const {
 std::vector<double> ConjoinXRuns::getXAxis(MatrixWorkspace_sptr ws) const {
 
   std::vector<double> axis;
-  axis.reserve(ws->blocksize());
+  axis.reserve(ws->y(0).size());
   auto &run = ws->run();
   // try time series first
   TimeSeriesProperty<double> *timeSeriesDouble(nullptr);
@@ -307,10 +310,11 @@ void ConjoinXRuns::joinSpectrum(int64_t wsIndex) {
   std::vector<double> axis;
   std::vector<double> x;
   std::vector<double> xerrors;
-  spectrum.reserve(m_outWS->blocksize());
-  errors.reserve(m_outWS->blocksize());
-  axis.reserve(m_outWS->blocksize());
-  size_t index = static_cast<size_t>(wsIndex);
+  const size_t index = static_cast<size_t>(wsIndex);
+  const auto ySize = m_outWS->y(index).size();
+  spectrum.reserve(ySize);
+  errors.reserve(ySize);
+  axis.reserve(m_outWS->x(index).size());
   for (const auto &input : m_inputWS) {
     const auto &y = input->y(index);
     spectrum.insert(spectrum.end(), y.begin(), y.end());
@@ -329,10 +333,10 @@ void ConjoinXRuns::joinSpectrum(int64_t wsIndex) {
     }
   }
   if (!xerrors.empty())
-    m_outWS->setPointStandardDeviations(index, xerrors);
-  m_outWS->mutableY(index) = spectrum;
-  m_outWS->mutableE(index) = errors;
-  m_outWS->mutableX(index) = axis;
+    m_outWS->setPointStandardDeviations(index, std::move(xerrors));
+  m_outWS->mutableY(index) = std::move(spectrum);
+  m_outWS->mutableE(index) = std::move(errors);
+  m_outWS->mutableX(index) = std::move(axis);
 }
 
 //----------------------------------------------------------------------------------------------
@@ -387,14 +391,14 @@ void ConjoinXRuns::exec() {
   // to be skipped, to compute the size of the output respectively.
   MatrixWorkspace_sptr temp = first->clone();
 
-  size_t outBlockSize = (*it)->blocksize();
+  size_t outBlockSize = (*it)->y(0).size();
   // First sequentially merge the sample logs
   for (++it; it != m_inputWS.end(); ++it) {
     // attempt to merge the sample logs
     try {
       sampleLogsBehaviour.mergeSampleLogs(*it, temp);
       sampleLogsBehaviour.setUpdatedSampleLogs(temp);
-      outBlockSize += (*it)->blocksize();
+      outBlockSize += (*it)->y(0).size();
     } catch (std::invalid_argument &e) {
       if (sampleLogsFailBehaviour == SKIP_BEHAVIOUR) {
         g_log.error() << "Could not join workspace: " << (*it)->getName()
@@ -425,8 +429,7 @@ void ConjoinXRuns::exec() {
   // now get the size of the output
   size_t numSpec = first->getNumberHistograms();
 
-  m_outWS = WorkspaceFactory::Instance().create(first, numSpec, outBlockSize,
-                                                outBlockSize);
+  m_outWS = create<MatrixWorkspace>(*first, Points(outBlockSize));
 
   // copy over the merged sample logs from the temp
   m_outWS->mutableRun() = temp->run();
diff --git a/Framework/Algorithms/src/ConvertSpectrumAxis.cpp b/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
index 9c60660acc4d923d649cc164c06705e8dc705c1e..77227d39bc6311bdec96f1413752f43672ce4bfc 100644
--- a/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
+++ b/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
@@ -10,8 +10,10 @@
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectraAxisValidator.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -30,7 +32,8 @@ DECLARE_ALGORITHM(ConvertSpectrumAxis)
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
-
+using namespace DataObjects;
+using namespace HistogramData;
 namespace {
 constexpr double rad2deg = 180. / M_PI;
 }
@@ -151,8 +154,12 @@ void ConvertSpectrumAxis::exec() {
   }
   // Create the output workspace. Can't re-use the input one because we'll be
   // re-ordering the spectra.
-  MatrixWorkspace_sptr outputWS = WorkspaceFactory::Instance().create(
-      inputWS, indexMap.size(), nxBins, nBins);
+  HistogramBuilder builder;
+  builder.setX(nxBins);
+  builder.setY(nBins);
+  builder.setDistribution(inputWS->isDistribution());
+  MatrixWorkspace_sptr outputWS =
+      create<MatrixWorkspace>(*inputWS, indexMap.size(), builder.build());
   // Now set up a new, numeric axis holding the theta values corresponding to
   // each spectrum
   auto const newAxis = new NumericAxis(indexMap.size());
diff --git a/Framework/Algorithms/src/ConvertSpectrumAxis2.cpp b/Framework/Algorithms/src/ConvertSpectrumAxis2.cpp
index 3c32827ff26c2401c4cf49597ef4a4769b6522e1..ac059dd9aa914764c28b4ce2bb4d2a3e5062db45 100644
--- a/Framework/Algorithms/src/ConvertSpectrumAxis2.cpp
+++ b/Framework/Algorithms/src/ConvertSpectrumAxis2.cpp
@@ -10,9 +10,11 @@
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectraAxisValidator.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -32,6 +34,8 @@ DECLARE_ALGORITHM(ConvertSpectrumAxis2)
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
+using namespace DataObjects;
+using namespace HistogramData;
 
 void ConvertSpectrumAxis2::init() {
   // Validator for Input Workspace
@@ -225,8 +229,12 @@ MatrixWorkspace_sptr ConvertSpectrumAxis2::createOutputWorkspace(
   NumericAxis *newAxis = nullptr;
   if (m_toOrder) {
     // Can not re-use the input one because the spectra are re-ordered.
-    outputWorkspace = WorkspaceFactory::Instance().create(
-        inputWS, m_indexMap.size(), inputWS->x(0).size(), inputWS->y(0).size());
+    HistogramBuilder builder;
+    builder.setX(inputWS->x(0).size());
+    builder.setY(inputWS->y(0).size());
+    builder.setDistribution(inputWS->isDistribution());
+    outputWorkspace =
+        create<MatrixWorkspace>(*inputWS, m_indexMap.size(), builder.build());
     std::vector<double> axis;
     axis.reserve(m_indexMap.size());
     for (const auto &it : m_indexMap) {
diff --git a/Framework/Algorithms/src/ConvertTableToMatrixWorkspace.cpp b/Framework/Algorithms/src/ConvertTableToMatrixWorkspace.cpp
index 80e20d161ff5908fc14e129b5c9f0fb2a7c964d0..27366c2ed18e6cff466af4221bc6d7ef469b1e11 100644
--- a/Framework/Algorithms/src/ConvertTableToMatrixWorkspace.cpp
+++ b/Framework/Algorithms/src/ConvertTableToMatrixWorkspace.cpp
@@ -11,7 +11,8 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidKernel/MandatoryValidator.h"
 #include "MantidKernel/Unit.h"
 #include "MantidKernel/UnitFactory.h"
@@ -25,6 +26,7 @@ DECLARE_ALGORITHM(ConvertTableToMatrixWorkspace)
 using namespace Kernel;
 using namespace API;
 using namespace HistogramData;
+using namespace DataObjects;
 
 void ConvertTableToMatrixWorkspace::init() {
   declareProperty(make_unique<WorkspaceProperty<API::ITableWorkspace>>(
@@ -57,8 +59,7 @@ void ConvertTableToMatrixWorkspace::exec() {
   auto X = inputWorkspace->getColumn(columnX)->numeric_fill<>();
   auto Y = inputWorkspace->getColumn(columnY)->numeric_fill<>();
 
-  MatrixWorkspace_sptr outputWorkspace =
-      WorkspaceFactory::Instance().create("Workspace2D", 1, nrows, nrows);
+  MatrixWorkspace_sptr outputWorkspace = create<Workspace2D>(1, Points(nrows));
 
   outputWorkspace->mutableX(0).assign(X.begin(), X.end());
   outputWorkspace->mutableY(0).assign(Y.begin(), Y.end());
diff --git a/Framework/Algorithms/src/ConvertToConstantL2.cpp b/Framework/Algorithms/src/ConvertToConstantL2.cpp
index 6817490d4a212da87742a5d9491b928758aa1942..c91fca9ca5336e158b879c5bf2e7f860f893997d 100644
--- a/Framework/Algorithms/src/ConvertToConstantL2.cpp
+++ b/Framework/Algorithms/src/ConvertToConstantL2.cpp
@@ -8,9 +8,9 @@
 #include "MantidAPI/HistogramValidator.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidGeometry/Instrument/ParameterMap.h"
 #include "MantidKernel/CompositeValidator.h"
@@ -27,6 +27,7 @@ namespace Algorithms {
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
+using namespace DataObjects;
 
 // Register the class into the algorithm factory
 DECLARE_ALGORITHM(ConvertToConstantL2)
@@ -63,7 +64,7 @@ void ConvertToConstantL2::initWorkspaces() {
   // If input and output workspaces are not the same, create a new workspace for
   // the output
   if (m_outputWS != this->m_inputWS) {
-    m_outputWS = API::WorkspaceFactory::Instance().create(m_inputWS);
+    m_outputWS = create<MatrixWorkspace>(*m_inputWS);
   }
 
   m_wavelength = getRunProperty("wavelength");
diff --git a/Framework/Algorithms/src/ConvertToMatrixWorkspace.cpp b/Framework/Algorithms/src/ConvertToMatrixWorkspace.cpp
index ab3124aea545723d1e52e953099dcc146621f127..e1a58403aa5e0f4e2fac2a2000f7efc2fd0bf8bd 100644
--- a/Framework/Algorithms/src/ConvertToMatrixWorkspace.cpp
+++ b/Framework/Algorithms/src/ConvertToMatrixWorkspace.cpp
@@ -8,8 +8,9 @@
 // Includes
 //----------------------------------------------------------------------
 #include "MantidAlgorithms/ConvertToMatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -51,7 +52,7 @@ void ConvertToMatrixWorkspace::exec() {
 
     // Create the output workspace. This will copy many aspects fron the input
     // one.
-    outputWorkspace = WorkspaceFactory::Instance().create(inputWorkspace);
+    outputWorkspace = create<Workspace2D>(*inputWorkspace);
 
     // ...but not the data, so do that here.
     PARALLEL_FOR_IF(Kernel::threadSafe(*inputWorkspace, *outputWorkspace))
diff --git a/Framework/Algorithms/src/ConvertUnits.cpp b/Framework/Algorithms/src/ConvertUnits.cpp
index 3d949b0102a478530641f6f088977f2c26524701..c82f6f0a836e0c67c0742009dbae4ea9973aeb21 100644
--- a/Framework/Algorithms/src/ConvertUnits.cpp
+++ b/Framework/Algorithms/src/ConvertUnits.cpp
@@ -10,11 +10,12 @@
 #include "MantidAPI/CommonBinsValidator.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -734,8 +735,7 @@ API::MatrixWorkspace_sptr ConvertUnits::removeUnphysicalBins(
     MantidVec::difference_type bins = X0.cend() - start;
     MantidVec::difference_type first = start - X0.cbegin();
 
-    result =
-        WorkspaceFactory::Instance().create(workspace, numSpec, bins, bins - 1);
+    result = create<MatrixWorkspace>(*workspace, BinEdges(bins));
 
     for (size_t i = 0; i < numSpec; ++i) {
       auto &X = workspace->x(i);
@@ -765,8 +765,7 @@ API::MatrixWorkspace_sptr ConvertUnits::removeUnphysicalBins(
     g_log.debug() << maxBins << '\n';
     // Now create an output workspace large enough for the longest 'good'
     // range
-    result = WorkspaceFactory::Instance().create(workspace, numSpec, maxBins,
-                                                 maxBins - 1);
+    result = create<MatrixWorkspace>(*workspace, numSpec, BinEdges(maxBins));
     // Next, loop again copying in the correct range for each spectrum
     for (int64_t j = 0; j < int64_t(numSpec); ++j) {
       auto edges = workspace->binEdges(j);
diff --git a/Framework/Algorithms/src/CopySample.cpp b/Framework/Algorithms/src/CopySample.cpp
index 4734956da4217b72ba5bfc4d699649439b75f8fd..0d6777320c526f976c23ed6b2595f7cc91207cac 100644
--- a/Framework/Algorithms/src/CopySample.cpp
+++ b/Framework/Algorithms/src/CopySample.cpp
@@ -167,8 +167,10 @@ void CopySample::copyParameters(Sample &from, Sample &to, bool nameFlag,
                                 bool orientationOnlyFlag) {
   if (nameFlag)
     to.setName(from.getName());
-  if (environmentFlag)
-    to.setEnvironment(new SampleEnvironment(from.getEnvironment()));
+  if (environmentFlag) {
+    to.setEnvironment(
+        std::make_unique<SampleEnvironment>(from.getEnvironment()));
+  }
   if (shapeFlag) {
     Material rhsMaterial;
     if (materialFlag) {
diff --git a/Framework/Algorithms/src/CorrectKiKf.cpp b/Framework/Algorithms/src/CorrectKiKf.cpp
index 19aeeb1d15e3c28b340f51dcf0701ff8a4f500c7..b716b1b811bf30d629adb7a390e17d0dd44be0cf 100644
--- a/Framework/Algorithms/src/CorrectKiKf.cpp
+++ b/Framework/Algorithms/src/CorrectKiKf.cpp
@@ -7,10 +7,10 @@
 #include "MantidAlgorithms/CorrectKiKf.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/IDetector.h"
 #include "MantidGeometry/Instrument/ParameterMap.h"
 #include "MantidKernel/BoundedValidator.h"
@@ -69,7 +69,7 @@ void CorrectKiKf::exec() {
   // If input and output workspaces are not the same, create a new workspace for
   // the output
   if (outputWS != inputWS) {
-    outputWS = API::WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<MatrixWorkspace>(*inputWS);
   }
 
   const size_t size = inputWS->blocksize();
diff --git a/Framework/Algorithms/src/CorrectToFile.cpp b/Framework/Algorithms/src/CorrectToFile.cpp
index 6f4eaaff87ff12a99b75ae6be972878531574eeb..97156180b6b106b12a6fd7431d3d4df73e185895 100644
--- a/Framework/Algorithms/src/CorrectToFile.cpp
+++ b/Framework/Algorithms/src/CorrectToFile.cpp
@@ -8,14 +8,16 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/FileProperty.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/Unit.h"
 #include "MantidKernel/UnitFactory.h"
 
 namespace Mantid {
 namespace Algorithms {
-using namespace Mantid::API;
+using namespace API;
+using namespace DataObjects;
 
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(CorrectToFile)
@@ -54,9 +56,7 @@ void CorrectToFile::exec() {
   MatrixWorkspace_sptr rkhInput = loadInFile(getProperty("Filename"));
   // Only create the output workspace if it's not the same as the input one
   MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
-  if (outputWS != toCorrect) {
-    outputWS = WorkspaceFactory::Instance().create(toCorrect);
-  }
+  outputWS = create<HistoWorkspace>(*toCorrect);
   const std::string operation = getProperty("WorkspaceOperation");
 
   if (getPropertyValue("FirstColumnValue") == "SpectrumNumber") {
diff --git a/Framework/Algorithms/src/CreateEPP.cpp b/Framework/Algorithms/src/CreateEPP.cpp
index f2b0f25024cad4064d32046b49d2feed6c349420..23010b27a774731f7d23a0c7c51e1addffa24af8 100644
--- a/Framework/Algorithms/src/CreateEPP.cpp
+++ b/Framework/Algorithms/src/CreateEPP.cpp
@@ -10,8 +10,8 @@
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/UnitConversion.h"
@@ -110,7 +110,7 @@ void CreateEPP::exec() {
       getProperty(PropertyNames::INPUT_WORKSPACE);
   const auto &spectrumInfo = inputWS->spectrumInfo();
   API::ITableWorkspace_sptr outputWS =
-      API::WorkspaceFactory::Instance().createTable("TableWorkspace");
+      boost::make_shared<DataObjects::TableWorkspace>();
   addEPPColumns(outputWS);
   const double sigma = getProperty(PropertyNames::SIGMA);
   const size_t spectraCount = spectrumInfo.size();
diff --git a/Framework/Algorithms/src/CreateLogPropertyTable.cpp b/Framework/Algorithms/src/CreateLogPropertyTable.cpp
index 1135ca795298f40105ff261ace70d23e581b24bf..b9507ea9fa8b09127b0d8854313c0e1031e1f0a7 100644
--- a/Framework/Algorithms/src/CreateLogPropertyTable.cpp
+++ b/Framework/Algorithms/src/CreateLogPropertyTable.cpp
@@ -10,8 +10,8 @@
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MandatoryValidator.h"
@@ -113,8 +113,7 @@ void CreateLogPropertyTable::exec() {
   }
 
   // Set up output table.
-  boost::shared_ptr<ITableWorkspace> outputTable =
-      WorkspaceFactory::Instance().createTable();
+  auto outputTable = boost::make_shared<DataObjects::TableWorkspace>();
   // One column for each property.
   for (const auto &propName : propNames)
     outputTable->addColumn("str", propName);
diff --git a/Framework/Algorithms/src/CrossCorrelate.cpp b/Framework/Algorithms/src/CrossCorrelate.cpp
index 5b6fc3c3d248bec3a7db2ea9890c7339bbc23260..00ed1de3fc68bd67a0286fd9e348eb7f6039d953 100644
--- a/Framework/Algorithms/src/CrossCorrelate.cpp
+++ b/Framework/Algorithms/src/CrossCorrelate.cpp
@@ -9,13 +9,14 @@
 //----------------------------------------------------------------------
 #include "MantidAlgorithms/CrossCorrelate.h"
 #include "MantidAPI/RawCountValidator.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/VectorHelper.h"
 #include <boost/iterator/counting_iterator.hpp>
-
 #include <numeric>
 #include <sstream>
 
@@ -27,6 +28,8 @@ DECLARE_ALGORITHM(CrossCorrelate)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
+using namespace HistogramData;
 
 /// Initialisation method.
 void CrossCorrelate::init() {
@@ -145,7 +148,7 @@ void CrossCorrelate::exec() {
     throw std::runtime_error("Range is not valid");
 
   MatrixWorkspace_sptr out =
-      WorkspaceFactory::Instance().create(inputWS, nspecs, npoints, npoints);
+      create<HistoWorkspace>(*inputWS, nspecs, Points(npoints));
 
   // Calculate the mean value of the reference spectrum and associated error
   // squared
@@ -172,12 +175,14 @@ void CrossCorrelate::exec() {
   // Now copy the other spectra
   bool is_distrib = inputWS->isDistribution();
 
-  std::vector<double> XX(npoints);
-  for (int i = 0; i < npoints; ++i) {
-    XX[i] = static_cast<double>(i - nY + 2);
+  {
+    std::vector<double> XX(npoints);
+    for (int i = 0; i < npoints; ++i) {
+      XX[i] = static_cast<double>(i - nY + 2);
+    }
+    out->mutableX(0) = std::move(XX);
   }
   // Initialise the progress reporting object
-  out->mutableX(0) = XX;
   m_progress = make_unique<Progress>(this, 0.0, 1.0, nspecs);
   PARALLEL_FOR_IF(Kernel::threadSafe(*inputWS, *out))
   for (int i = 0; i < nspecs; ++i) // Now loop on all spectra
diff --git a/Framework/Algorithms/src/DetectorEfficiencyCor.cpp b/Framework/Algorithms/src/DetectorEfficiencyCor.cpp
index f64f3109f0525bba17da9682d70beb819d7e7110..5bd7eedd60de6ff018006e12d130dd9e422d07ee 100644
--- a/Framework/Algorithms/src/DetectorEfficiencyCor.cpp
+++ b/Framework/Algorithms/src/DetectorEfficiencyCor.cpp
@@ -10,8 +10,8 @@
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidGeometry/Instrument/ParameterMap.h"
@@ -34,6 +34,7 @@ DECLARE_ALGORITHM(DetectorEfficiencyCor)
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
+using namespace DataObjects;
 
 namespace {
 
@@ -187,7 +188,7 @@ void DetectorEfficiencyCor::retrieveProperties() {
   // If input and output workspaces are not the same, create a new workspace for
   // the output
   if (m_outputWS != m_inputWS) {
-    m_outputWS = WorkspaceFactory::Instance().create(m_inputWS);
+    m_outputWS = create<MatrixWorkspace>(*m_inputWS);
   }
 }
 
@@ -214,8 +215,8 @@ void DetectorEfficiencyCor::correctForEfficiency(
   auto &yout = m_outputWS->mutableY(spectraIn);
   auto &eout = m_outputWS->mutableE(spectraIn);
   // Need the original values so this is not a reference
-  auto yValues = m_inputWS->y(spectraIn);
-  auto eValues = m_inputWS->e(spectraIn);
+  const auto yValues = m_inputWS->y(spectraIn);
+  const auto eValues = m_inputWS->e(spectraIn);
 
   // Storage for the reciprocal wave vectors that are calculated as the
   // correction proceeds
diff --git a/Framework/Algorithms/src/DiffractionFocussing.cpp b/Framework/Algorithms/src/DiffractionFocussing.cpp
index 58f8d7d10793abc1ebdbc87dc60e502d2f12f76d..be20de7ea77766ba7731a3a00ac4eac261c5585b 100644
--- a/Framework/Algorithms/src/DiffractionFocussing.cpp
+++ b/Framework/Algorithms/src/DiffractionFocussing.cpp
@@ -8,7 +8,8 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/FileProperty.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidIndexing/IndexInfo.h"
 #include "MantidKernel/Unit.h"
 
@@ -29,6 +30,8 @@ DiffractionFocussing::DiffractionFocussing()
 }
 
 using namespace Kernel;
+using namespace HistogramData;
+
 using API::FileProperty;
 using API::MatrixWorkspace;
 using API::MatrixWorkspace_sptr;
@@ -134,8 +137,8 @@ void DiffractionFocussing::exec() {
   // Create a new workspace that's the right size for the meaningful spectra and
   // copy them in
   int64_t newSize = tmpW->blocksize();
-  API::MatrixWorkspace_sptr outputW = API::WorkspaceFactory::Instance().create(
-      tmpW, resultIndeces.size(), newSize + 1, newSize);
+  API::MatrixWorkspace_sptr outputW = DataObjects::create<API::MatrixWorkspace>(
+      *tmpW, resultIndeces.size(), BinEdges(newSize + 1));
 
   std::vector<Indexing::SpectrumNumber> specNums;
   const auto &tmpIndices = tmpW->indexInfo();
diff --git a/Framework/Algorithms/src/DiffractionFocussing2.cpp b/Framework/Algorithms/src/DiffractionFocussing2.cpp
index 777eb1fde1f0bc02a200705c2b0d027918e8351d..fbdb9bbf475d8f5be46a07b82993b42425217e10 100644
--- a/Framework/Algorithms/src/DiffractionFocussing2.cpp
+++ b/Framework/Algorithms/src/DiffractionFocussing2.cpp
@@ -670,4 +670,4 @@ size_t DiffractionFocussing2::setupGroupToWSIndices() {
 }
 
 } // namespace Algorithms
-} // namespace Mantid
+} // namespace Mantid
\ No newline at end of file
diff --git a/Framework/Algorithms/src/Divide.cpp b/Framework/Algorithms/src/Divide.cpp
index bd6387ef2b35a7d6d2f4eb0bb0b8adbdf81dc431..e1b6af0bf6944cfa74a7e06e886ab8838d6f1641 100644
--- a/Framework/Algorithms/src/Divide.cpp
+++ b/Framework/Algorithms/src/Divide.cpp
@@ -4,9 +4,6 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/Divide.h"
 
 using namespace Mantid::API;
@@ -32,20 +29,16 @@ void Divide::exec() {
   BinaryOperation::exec();
 }
 
-void Divide::performBinaryOperation(const MantidVec &lhsX,
-                                    const MantidVec &lhsY,
-                                    const MantidVec &lhsE,
-                                    const MantidVec &rhsY,
-                                    const MantidVec &rhsE, MantidVec &YOut,
-                                    MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
-
-  const int bins = static_cast<int>(lhsE.size());
+void Divide::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                    const HistogramData::Histogram &rhs,
+                                    HistogramData::HistogramY &YOut,
+                                    HistogramData::HistogramE &EOut) {
+  const int bins = static_cast<int>(lhs.e().size());
 
   for (int j = 0; j < bins; ++j) {
     // Get references to the input Y's
-    const double leftY = lhsY[j];
-    const double rightY = rhsY[j];
+    const double leftY = lhs.y()[j];
+    const double rightY = rhs.y()[j];
 
     //  error dividing two uncorrelated numbers, re-arrange so that you don't
     //  get infinity if leftY==0 (when rightY=0 the Y value and the result will
@@ -54,23 +47,19 @@ void Divide::performBinaryOperation(const MantidVec &lhsX,
     // (Sa c/a)2 + (Sb c/b)2 = (Sc)2
     // = (Sa 1/b)2 + (Sb (a/b2))2
     // (Sc)2 = (1/b)2( (Sa)2 + (Sb a/b)2 )
-    EOut[j] =
-        sqrt(pow(lhsE[j], 2) + pow(leftY * rhsE[j] / rightY, 2)) / fabs(rightY);
+    EOut[j] = sqrt(pow(lhs.e()[j], 2) + pow(leftY * rhs.e()[j] / rightY, 2)) /
+              fabs(rightY);
 
     // Copy the result last in case one of the input workspaces is also any
     // output
     YOut[j] = leftY / rightY;
-    ;
   }
 }
 
-void Divide::performBinaryOperation(const MantidVec &lhsX,
-                                    const MantidVec &lhsY,
-                                    const MantidVec &lhsE, const double rhsY,
-                                    const double rhsE, MantidVec &YOut,
-                                    MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
-
+void Divide::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                    const double rhsY, const double rhsE,
+                                    HistogramData::HistogramY &YOut,
+                                    HistogramData::HistogramE &EOut) {
   if (rhsY == 0 && m_warnOnZeroDivide)
     g_log.warning() << "Division by zero: the RHS is a single-valued vector "
                        "with value zero."
@@ -78,13 +67,13 @@ void Divide::performBinaryOperation(const MantidVec &lhsX,
 
   // Do the right-hand part of the error calculation just once
   const double rhsFactor = pow(rhsE / rhsY, 2);
-  const int bins = static_cast<int>(lhsE.size());
+  const int bins = static_cast<int>(lhs.e().size());
   for (int j = 0; j < bins; ++j) {
     // Get reference to input Y
-    const double leftY = lhsY[j];
+    const double leftY = lhs.y()[j];
 
     // see comment in the function above for the error formula
-    EOut[j] = sqrt(pow(lhsE[j], 2) + pow(leftY, 2) * rhsFactor) / fabs(rhsY);
+    EOut[j] = sqrt(pow(lhs.e()[j], 2) + pow(leftY, 2) * rhsFactor) / fabs(rhsY);
     // Copy the result last in case one of the input workspaces is also any
     // output
     YOut[j] = leftY / rhsY;
@@ -100,7 +89,7 @@ void Divide::setOutputUnits(const API::MatrixWorkspace_const_sptr lhs,
   }
   // If the Y units match, then the output will be a distribution and will be
   // dimensionless
-  else if (lhs->YUnit() == rhs->YUnit() && rhs->blocksize() > 1) {
+  else if (lhs->YUnit() == rhs->YUnit() && m_rhsBlocksize > 1) {
     out->setYUnit("");
     out->setDistribution(true);
   }
@@ -216,11 +205,10 @@ std::string Divide::checkSizeCompatibility(
   // If RHS only has one value (1D vertical), the number of histograms needs to
   // match.
   // Each lhs spectrum will be divided by that scalar
-  // std::cout << "rhs->blocksize() " << rhs->blocksize() << '\n';
   // Are we allowing the division by different # of spectra, using detector IDs
   // to match up?
   if (m_AllowDifferentNumberSpectra ||
-      (rhs->blocksize() == 1 &&
+      (m_rhsBlocksize == 1 &&
        lhs->getNumberHistograms() == rhs->getNumberHistograms())) {
     return "";
   }
diff --git a/Framework/Algorithms/src/EstimateResolutionDiffraction.cpp b/Framework/Algorithms/src/EstimateResolutionDiffraction.cpp
index 0eb81698799bca38656fea038dc051be42f3b234..547b114354f5ad75b87d18a8911645aa800da3dd 100644
--- a/Framework/Algorithms/src/EstimateResolutionDiffraction.cpp
+++ b/Framework/Algorithms/src/EstimateResolutionDiffraction.cpp
@@ -81,9 +81,8 @@ void EstimateResolutionDiffraction::init() {
   auto positiveDeltaTOF = boost::make_shared<BoundedValidator<double>>();
   positiveDeltaTOF->setLower(0.);
   positiveDeltaTOF->setLowerExclusive(true);
-  declareProperty(
-      "DeltaTOF", 0., positiveDeltaTOF,
-      "DeltaT as the resolution of TOF with unit microsecond (10^-6m).");
+  declareProperty("DeltaTOF", 0., positiveDeltaTOF,
+                  "DeltaT as the resolution of TOF with unit microsecond");
 
   auto positiveWavelength = boost::make_shared<BoundedValidator<double>>();
   positiveWavelength->setLower(0.);
diff --git a/Framework/Algorithms/src/ExportTimeSeriesLog.cpp b/Framework/Algorithms/src/ExportTimeSeriesLog.cpp
index dba47373726eaa697c6d7243f55883802a43a0c0..467f50b90262027872d1c57577a68d9df6f39ef8 100644
--- a/Framework/Algorithms/src/ExportTimeSeriesLog.cpp
+++ b/Framework/Algorithms/src/ExportTimeSeriesLog.cpp
@@ -9,13 +9,13 @@
 #include "MantidAPI/FileProperty.h"
 #include "MantidAPI/IEventList.h"
 #include "MantidAPI/Run.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidDataObjects/EventList.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/Events.h"
 #include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/System.h"
 #include "MantidKernel/TimeSeriesProperty.h"
@@ -28,6 +28,7 @@ using namespace Mantid;
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using Mantid::Types::Core::DateAndTime;
 
 using namespace std;
@@ -247,12 +248,7 @@ void ExportTimeSeriesLog::setupWorkspace2D(
     outsize = static_cast<size_t>(numentries);
 
   // Create 2D workspace
-  m_outWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
-      WorkspaceFactory::Instance().create("Workspace2D", nspec, outsize,
-                                          outsize));
-  if (!m_outWS)
-    throw runtime_error(
-        "Unable to create a Workspace2D casted to MatrixWorkspace.");
+  m_outWS = create<Workspace2D>(nspec, Points(outsize));
 
   auto &vecX = m_outWS->mutableX(0);
   auto &vecY = m_outWS->mutableY(0);
diff --git a/Framework/Algorithms/src/ExtractFFTSpectrum.cpp b/Framework/Algorithms/src/ExtractFFTSpectrum.cpp
index 989d8c7188f712d6056eb04f28251370fa3d6480..19c58c4628a3c6405a33747f23846f2ee9383209 100644
--- a/Framework/Algorithms/src/ExtractFFTSpectrum.cpp
+++ b/Framework/Algorithms/src/ExtractFFTSpectrum.cpp
@@ -10,7 +10,7 @@
 #include "MantidAlgorithms/ExtractFFTSpectrum.h"
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/MultiThreaded.h"
 #include "MantidKernel/Unit.h"
@@ -24,6 +24,7 @@ DECLARE_ALGORITHM(ExtractFFTSpectrum)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
 
 void ExtractFFTSpectrum::init() {
   declareProperty(
@@ -47,7 +48,7 @@ void ExtractFFTSpectrum::exec() {
   MatrixWorkspace_sptr inputImagWS = getProperty("InputImagWorkspace");
   const int fftPart = getProperty("FFTPart");
   const int numHists = static_cast<int>(inputWS->getNumberHistograms());
-  MatrixWorkspace_sptr outputWS = WorkspaceFactory::Instance().create(inputWS);
+  MatrixWorkspace_sptr outputWS = create<MatrixWorkspace>(*inputWS);
 
   Progress prog(this, 0.0, 1.0, numHists);
 
diff --git a/Framework/Algorithms/src/ExtractSpectra2.cpp b/Framework/Algorithms/src/ExtractSpectra2.cpp
index c5b3dbfcabf98bf1fc4c1ca0a708aa45b92bbe65..3480c2ddc5214b269b11cc08d93a93e930f52ffb 100644
--- a/Framework/Algorithms/src/ExtractSpectra2.cpp
+++ b/Framework/Algorithms/src/ExtractSpectra2.cpp
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/ExtractSpectra2.h"
 #include "MantidAPI/Algorithm.tcc"
+#include "MantidAPI/BinEdgeAxis.h"
 #include "MantidAPI/NumericAxis.h"
 #include "MantidAPI/TextAxis.h"
 #include "MantidDataObjects/EventWorkspace.h"
@@ -68,12 +69,15 @@ void ExtractSpectra2::exec() {
   Axis *inAxis1(nullptr);
   TextAxis *outTxtAxis(nullptr);
   NumericAxis *outNumAxis(nullptr);
+  bool isBinEdgeAxis(false);
   if (inputWS->axes() > 1) {
     inAxis1 = inputWS->getAxis(1);
     auto outAxis1 = outputWS->getAxis(1);
     outTxtAxis = dynamic_cast<TextAxis *>(outAxis1);
-    if (!outTxtAxis)
+    if (!outTxtAxis) {
       outNumAxis = dynamic_cast<NumericAxis *>(outAxis1);
+      isBinEdgeAxis = dynamic_cast<BinEdgeAxis *>(inAxis1) != nullptr;
+    }
   }
 
   Progress prog(this, 0.0, 1.0, indexSet.size());
@@ -96,6 +100,17 @@ void ExtractSpectra2::exec() {
     prog.report();
   }
 
+  if (isBinEdgeAxis) {
+    if (!indexSet.isContiguous()) {
+      throw std::invalid_argument("Cannot extract non-contiguous set of "
+                                  "spectra when the vertical axis has bin "
+                                  "edges.");
+    }
+    const auto outIndex = indexSet.size();
+    const auto inIndex = indexSet[indexSet.size() - 1] + 1;
+    outNumAxis->setValue(outIndex, inAxis1->operator()(inIndex));
+  }
+
   setProperty("OutputWorkspace", std::move(outputWS));
 }
 
diff --git a/Framework/Algorithms/src/FFT.cpp b/Framework/Algorithms/src/FFT.cpp
index 4cf73af92ebb65a983ef5a9a35a6fc95e9c270ce..81acadcb049ef4a02fd33fac8565714f32cd0296 100644
--- a/Framework/Algorithms/src/FFT.cpp
+++ b/Framework/Algorithms/src/FFT.cpp
@@ -10,7 +10,9 @@
 #include "MantidAlgorithms/FFT.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/TextAxis.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/EnabledWhenProperty.h"
 #include "MantidKernel/EqualBinsChecker.h"
@@ -36,6 +38,8 @@ DECLARE_ALGORITHM(FFT)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
+using namespace HistogramData;
 
 /// Initialisation method. Declares properties to be used in algorithm.
 void FFT::init() {
@@ -111,7 +115,8 @@ void FFT::exec() {
     addPositiveOnly = true;
   }
 
-  m_outWS = WorkspaceFactory::Instance().create(m_inWS, nOut, nPoints, nPoints);
+  m_outWS = create<HistoWorkspace>(*m_inWS, nOut, Points(nPoints));
+
   for (int i = 0; i < nOut; ++i)
     m_outWS->getSpectrum(i).setDetectorID(static_cast<detid_t>(i + 1));
 
diff --git a/Framework/Algorithms/src/FFTDerivative.cpp b/Framework/Algorithms/src/FFTDerivative.cpp
index 55d244b9e9a3e4831f9c20b715ed35b34373a666..f989a2442b3bf6e2840603cf5eed32e84d252401 100644
--- a/Framework/Algorithms/src/FFTDerivative.cpp
+++ b/Framework/Algorithms/src/FFTDerivative.cpp
@@ -6,7 +6,9 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/FFTDerivative.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/BoundedValidator.h"
 
 #include <algorithm>
@@ -23,6 +25,8 @@ DECLARE_ALGORITHM(FFTDerivative)
 
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 
 void FFTDerivative::init() {
   declareProperty(
@@ -52,10 +56,13 @@ void FFTDerivative::execComplexFFT() {
   // Workspace for holding a copy of a spectrum. Each spectrum is symmetrized to
   // minimize
   // possible edge effects.
+
+  HistogramBuilder builder;
+  builder.setX(nx + ny);
+  builder.setY(ny + ny);
+  builder.setDistribution(inWS->isDistribution());
   MatrixWorkspace_sptr copyWS =
-      boost::dynamic_pointer_cast<Mantid::API::MatrixWorkspace>(
-          Mantid::API::WorkspaceFactory::Instance().create(inWS, 1, nx + ny,
-                                                           ny + ny));
+      create<MatrixWorkspace>(*inWS, 1, builder.build());
 
   for (size_t spec = 0; spec < n; ++spec) {
     symmetriseSpectrum(inWS->histogram(spec), copyWS->mutableX(0),
@@ -93,8 +100,7 @@ void FFTDerivative::execComplexFFT() {
     }
 
     if (!outWS) {
-      outWS = boost::dynamic_pointer_cast<Mantid::API::MatrixWorkspace>(
-          Mantid::API::WorkspaceFactory::Instance().create(inWS));
+      outWS = create<MatrixWorkspace>(*inWS);
     }
 
     // Save the upper half of the inverse transform for output
diff --git a/Framework/Algorithms/src/FFTSmooth.cpp b/Framework/Algorithms/src/FFTSmooth.cpp
index b8a911eb23f05ec1f4fe6698de2f67f2802b553d..735e7e9a123af25c4eb79143cc864908d4a5534c 100644
--- a/Framework/Algorithms/src/FFTSmooth.cpp
+++ b/Framework/Algorithms/src/FFTSmooth.cpp
@@ -10,10 +10,12 @@
 #include "MantidAlgorithms/FFTSmooth.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/TextAxis.h"
-#include "MantidAPI/WorkspaceFactory.h"
-#include "MantidKernel/Exception.h"
-
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/BoundedValidator.h"
+#include "MantidKernel/Exception.h"
 #include "MantidKernel/ListValidator.h"
 
 namespace Mantid {
@@ -24,6 +26,8 @@ DECLARE_ALGORITHM(FFTSmooth)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
+using namespace HistogramData;
 
 /// Initialisation method. Declares properties to be used in algorithm.
 void FFTSmooth::init() {
@@ -58,8 +62,12 @@ void FFTSmooth::exec() {
   // Symmetrize the input spectrum
   int dn = static_cast<int>(m_inWS->y(0).size());
 
-  API::MatrixWorkspace_sptr symmWS = API::WorkspaceFactory::Instance().create(
-      "Workspace2D", 1, m_inWS->x(0).size() + dn, m_inWS->y(0).size() + dn);
+  HistogramBuilder builder;
+  builder.setX(m_inWS->x(0).size() + dn);
+  builder.setY(m_inWS->y(0).size() + dn);
+  builder.setDistribution(m_inWS->isDistribution());
+  API::MatrixWorkspace_sptr symmWS =
+      create<Workspace2D>(*m_inWS, 1, builder.build());
 
   double dx = (m_inWS->x(spec).back() - m_inWS->x(spec).front()) /
               static_cast<double>(m_inWS->x(spec).size() - 1);
@@ -121,8 +129,11 @@ void FFTSmooth::exec() {
   API::MatrixWorkspace_sptr tmpWS = fft->getProperty("OutputWorkspace");
 
   // Create output
-  API::MatrixWorkspace_sptr outWS = API::WorkspaceFactory::Instance().create(
-      m_inWS, 1, m_inWS->x(0).size(), m_inWS->y(0).size());
+  builder.setX(m_inWS->x(0).size());
+  builder.setY(m_inWS->y(0).size());
+  builder.setDistribution(m_inWS->isDistribution());
+  API::MatrixWorkspace_sptr outWS =
+      create<MatrixWorkspace>(*m_inWS, 1, builder.build());
 
   dn = static_cast<int>(tmpWS->blocksize()) / 2;
 
@@ -144,8 +155,11 @@ void FFTSmooth::truncate(int n) {
   if (ny == 0)
     ny = 1;
   int nx = m_unfilteredWS->isHistogramData() ? ny + 1 : ny;
-  m_filteredWS =
-      API::WorkspaceFactory::Instance().create(m_unfilteredWS, 2, nx, ny);
+  HistogramBuilder builder;
+  builder.setX(nx);
+  builder.setY(ny);
+  builder.setDistribution(m_unfilteredWS->isDistribution());
+  m_filteredWS = create<MatrixWorkspace>(*m_unfilteredWS, 2, builder.build());
 
   auto &Yr = m_unfilteredWS->y(0);
   auto &Yi = m_unfilteredWS->y(1);
@@ -178,8 +192,11 @@ void FFTSmooth::zero(int n) {
   if (ny == 0)
     ny = 1;
 
-  m_filteredWS =
-      API::WorkspaceFactory::Instance().create(m_unfilteredWS, 2, mx, my);
+  HistogramBuilder builder;
+  builder.setX(mx);
+  builder.setY(my);
+  builder.setDistribution(m_unfilteredWS->isDistribution());
+  m_filteredWS = create<MatrixWorkspace>(*m_unfilteredWS, 2, builder.build());
 
   m_filteredWS->setSharedX(0, m_unfilteredWS->sharedX(0));
   m_filteredWS->setSharedX(1, m_unfilteredWS->sharedX(0));
diff --git a/Framework/Algorithms/src/FilterEvents.cpp b/Framework/Algorithms/src/FilterEvents.cpp
index 08ddd95a774fcf0c3f92ab95d3bcb54c6d5dd29b..5a46a62ea0c757f77ffe8b7677cbbbde5ae5ee55 100644
--- a/Framework/Algorithms/src/FilterEvents.cpp
+++ b/Framework/Algorithms/src/FilterEvents.cpp
@@ -10,7 +10,6 @@
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidAlgorithms/TimeAtSampleStrategyDirect.h"
@@ -18,8 +17,10 @@
 #include "MantidAlgorithms/TimeAtSampleStrategyIndirect.h"
 #include "MantidDataObjects/SplittersWorkspace.h"
 #include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
 #include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -37,6 +38,7 @@ using namespace Mantid;
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using namespace Mantid::Geometry;
 using Types::Core::DateAndTime;
 
@@ -65,7 +67,7 @@ FilterEvents::FilterEvents()
       m_filterByPulseTime(false), m_informationWS(), m_hasInfoWS(),
       m_progress(0.), m_outputWSNameBase(), m_toGroupWS(false),
       m_vecSplitterTime(), m_vecSplitterGroup(), m_splitSampleLogs(false),
-      m_useDBSpectrum(false), m_dbWSIndex(-1), m_tofCorrType(),
+      m_useDBSpectrum(false), m_dbWSIndex(-1), m_tofCorrType(NoneCorrect),
       m_specSkipType(), m_vecSkip(), m_isSplittersRelativeTime(false),
       m_filterStartTime(0), m_runStartTime(0) {}
 
@@ -81,7 +83,9 @@ void FilterEvents::init() {
                   "An input SpilltersWorskpace for filtering");
 
   declareProperty("OutputWorkspaceBaseName", "OutputWorkspace",
-                  "The base name to use for the output workspace");
+                  "The base name to use for the output workspace. The output "
+                  "workspace names are a combination of this and the index in "
+                  "splitter.");
 
   declareProperty(
       Kernel::make_unique<WorkspaceProperty<TableWorkspace>>(
@@ -184,15 +188,22 @@ std::map<std::string, std::string> FilterEvents::validateInputs() {
   std::map<std::string, std::string> result;
 
   // check the splitters workspace for special behavior
-  API::Workspace_const_sptr wksp = this->getProperty(SPLITER_PROP_NAME);
+  API::Workspace_const_sptr splitter = this->getProperty(SPLITER_PROP_NAME);
   // SplittersWorkspace is a special type that needs no further checking
-  if (!bool(boost::dynamic_pointer_cast<const SplittersWorkspace>(wksp))) {
-    const auto table = boost::dynamic_pointer_cast<const TableWorkspace>(wksp);
+  if (bool(boost::dynamic_pointer_cast<const SplittersWorkspace>(splitter))) {
+    if (boost::dynamic_pointer_cast<const SplittersWorkspace>(splitter)
+            ->rowCount() == 0)
+      result[SPLITER_PROP_NAME] = "SplittersWorkspace must have rows defined";
+  } else {
+    const auto table =
+        boost::dynamic_pointer_cast<const TableWorkspace>(splitter);
     const auto matrix =
-        boost::dynamic_pointer_cast<const MatrixWorkspace>(wksp);
+        boost::dynamic_pointer_cast<const MatrixWorkspace>(splitter);
     if (bool(table)) {
       if (table->columnCount() != 3)
         result[SPLITER_PROP_NAME] = "TableWorkspace must have 3 columns";
+      else if (table->rowCount() == 0)
+        result[SPLITER_PROP_NAME] = "TableWorkspace must have rows defined";
     } else if (bool(matrix)) {
       if (matrix->getNumberHistograms() == 1) {
         if (!matrix->isHistogramData())
@@ -206,6 +217,30 @@ std::map<std::string, std::string> FilterEvents::validateInputs() {
     }
   }
 
+  const string correctiontype = getPropertyValue("CorrectionToSample");
+  if (correctiontype == "Direct") {
+    double ei = getProperty("IncidentEnergy");
+    if (isEmpty(ei)) {
+      EventWorkspace_const_sptr inputWS = this->getProperty("InputWorkspace");
+      if (!inputWS->run().hasProperty("Ei")) {
+        const string msg(
+            "InputWorkspace does not have Ei. Must specify IncidentEnergy");
+        result["CorrectionToSample"] = msg;
+        result["IncidentEnergy"] = msg;
+      }
+    }
+  } else if (correctiontype == "Customized") {
+    TableWorkspace_const_sptr correctionWS =
+        getProperty("DetectorTOFCorrectionWorkspace");
+    if (!correctionWS) {
+      const string msg(
+          "Must specify correction workspace with CorrectionToSample=Custom");
+      result["CorrectionToSample"] = msg;
+      result["DetectorTOFCorrectionWorkspace"] = msg;
+    }
+  }
+  // "None" and "Elastic" and "Indirect" don't require extra information
+
   return result;
 }
 
@@ -221,9 +256,9 @@ void FilterEvents::exec() {
   // Parse splitters
   m_progress = 0.0;
   progress(m_progress, "Processing SplittersWorkspace.");
-  if (m_useSplittersWorkspace)
+  if (m_useSplittersWorkspace) // SplittersWorkspace the class in nanoseconds
     processSplittersWorkspace();
-  else if (m_useArbTableSplitters)
+  else if (m_useArbTableSplitters) // TableWorkspace in seconds
     processTableSplittersWorkspace();
   else
     processMatrixSplitterWorkspace();
@@ -437,14 +472,6 @@ void FilterEvents::processAlgorithmProperties() {
     throw runtime_error("Impossible situation!");
   }
 
-  if (m_tofCorrType == CustomizedCorrect) {
-    // Customized correciton
-    m_detCorrectWorkspace = getProperty("DetectorTOFCorrectionWorkspace");
-    if (!m_detCorrectWorkspace)
-      throw runtime_error("In case of customized TOF correction, correction "
-                          "workspace must be given!");
-  }
-
   // Spectrum skip
   string skipappr = getPropertyValue("SpectrumWithoutDetector");
   if (skipappr == "Skip")
@@ -518,10 +545,12 @@ void FilterEvents::groupOutputWorkspace() {
   }
 
   // set the group workspace as output workspace
-  declareProperty(
-      make_unique<WorkspaceProperty<WorkspaceGroup>>(
-          "OutputWorkspace", groupname, Direction::Output),
-      "Name of the workspace to be created as the output of grouping ");
+  if (!this->existsProperty("OutputWorkspace")) {
+    declareProperty(
+        make_unique<WorkspaceProperty<WorkspaceGroup>>(
+            "OutputWorkspace", groupname, Direction::Output),
+        "Name of the workspace to be created as the output of grouping ");
+  }
 
   AnalysisDataServiceImpl &ads = AnalysisDataService::Instance();
   API::WorkspaceGroup_sptr workspace_group =
@@ -951,6 +980,14 @@ void FilterEvents::processMatrixSplitterWorkspace() {
   return;
 }
 
+namespace {
+// offset_ns - an offset from the GPS epoch
+int64_t timeInSecondsToNanoseconds(const int64_t offset_ns,
+                                   const double time_sec) {
+  return offset_ns + static_cast<int64_t>(time_sec * 1.E9);
+}
+} // anonymous namespace
+
 //----------------------------------------------------------------------------------------------
 /** process the input splitters given by a TableWorkspace
  * The method will transfer the start/stop time to "m_vecSplitterTime"
@@ -959,8 +996,6 @@ void FilterEvents::processMatrixSplitterWorkspace() {
  *"m_wsGroupIndexTargetMap".
  * Also, "m_maxTargetIndex" is set up to record the highest target group/index,
  * i.e., max value of m_vecSplitterGroup
- *
- * @brief FilterEvents::processTableSplittersWorkspace
  */
 void FilterEvents::processTableSplittersWorkspace() {
   // check input workspace's validity
@@ -986,27 +1021,26 @@ void FilterEvents::processTableSplittersWorkspace() {
   size_t num_rows = m_splitterTableWorkspace->rowCount();
   for (size_t irow = 0; irow < num_rows; ++irow) {
     // get start and stop time in second
-    double start_time = m_splitterTableWorkspace->cell_cast<double>(irow, 0);
-    double stop_time = m_splitterTableWorkspace->cell_cast<double>(irow, 1);
-    std::string target = m_splitterTableWorkspace->cell<std::string>(irow, 2);
-
-    int64_t start_64 =
-        filter_shift_time + static_cast<int64_t>(start_time * 1.E9);
-    int64_t stop_64 =
-        filter_shift_time + static_cast<int64_t>(stop_time * 1.E9);
+    const auto start_time = timeInSecondsToNanoseconds(
+        filter_shift_time,
+        m_splitterTableWorkspace->cell_cast<double>(irow, 0));
+    const auto stop_time = timeInSecondsToNanoseconds(
+        filter_shift_time,
+        m_splitterTableWorkspace->cell_cast<double>(irow, 1));
+    const auto target = m_splitterTableWorkspace->cell<std::string>(irow, 2);
 
     if (m_vecSplitterTime.empty()) {
       // first splitter: push the start time to vector
-      m_vecSplitterTime.push_back(start_64);
-    } else if (start_64 - m_vecSplitterTime.back() > TOLERANCE) {
+      m_vecSplitterTime.push_back(start_time);
+    } else if (start_time - m_vecSplitterTime.back() > TOLERANCE) {
       // the start time is way behind previous splitter's stop time
       // create a new splitter and set the time interval in the middle to target
       // -1
-      m_vecSplitterTime.push_back(start_64);
+      m_vecSplitterTime.push_back(start_time);
       // NOTE: use index = 0 for un-defined slot
       m_vecSplitterGroup.push_back(UNDEFINED_SPLITTING_TARGET);
       found_undefined_splitter = true;
-    } else if (abs(start_64 - m_vecSplitterTime.back()) < TOLERANCE) {
+    } else if (abs(start_time - m_vecSplitterTime.back()) < TOLERANCE) {
       // new splitter's start time is same (within tolerance) as the stop time
       // of the previous
       ;
@@ -1017,21 +1051,12 @@ void FilterEvents::processTableSplittersWorkspace() {
     }
 
     // convert string-target to integer target
-    bool addnew = false;
     int int_target(-1);
-    if (m_targetIndexMap.empty()) {
-      addnew = true;
-    } else {
-      std::map<std::string, int>::iterator mapiter =
-          m_targetIndexMap.find(target);
-      if (mapiter == m_targetIndexMap.end())
-        addnew = true;
-      else
-        int_target = mapiter->second;
-    }
+    const auto &mapiter = m_targetIndexMap.find(target);
 
-    // add a new ordered-integer-target
-    if (addnew) {
+    if (mapiter != m_targetIndexMap.end()) {
+      int_target = mapiter->second;
+    } else {
       // target is not in map
       int_target = max_target_index;
       m_targetIndexMap.insert(std::pair<std::string, int>(target, int_target));
@@ -1041,7 +1066,7 @@ void FilterEvents::processTableSplittersWorkspace() {
     }
 
     // add start time, stop time and 'target
-    m_vecSplitterTime.push_back(stop_64);
+    m_vecSplitterTime.push_back(stop_time);
     m_vecSplitterGroup.push_back(int_target);
   } // END-FOR (irow)
 
@@ -1148,11 +1173,13 @@ void FilterEvents::createOutputWorkspaces() {
 
       // create these output properties
       if (!this->m_toGroupWS) {
-        declareProperty(
-            Kernel::make_unique<
-                API::WorkspaceProperty<DataObjects::EventWorkspace>>(
-                propertynamess.str(), wsname.str(), Direction::Output),
-            "Output");
+        if (!this->existsProperty(propertynamess.str())) {
+          declareProperty(
+              Kernel::make_unique<
+                  API::WorkspaceProperty<DataObjects::EventWorkspace>>(
+                  propertynamess.str(), wsname.str(), Direction::Output),
+              "Output");
+        }
         setProperty(propertynamess.str(), optws);
       }
 
@@ -1241,11 +1268,13 @@ void FilterEvents::createOutputWorkspacesMatrixCase() {
 
     // Set (property) to output workspace and set to ADS
     if (m_toGroupWS) {
-      declareProperty(
-          Kernel::make_unique<
-              API::WorkspaceProperty<DataObjects::EventWorkspace>>(
-              propertynamess.str(), wsname.str(), Direction::Output),
-          "Output");
+      if (!this->existsProperty(propertynamess.str())) {
+        declareProperty(
+            Kernel::make_unique<
+                API::WorkspaceProperty<DataObjects::EventWorkspace>>(
+                propertynamess.str(), wsname.str(), Direction::Output),
+            "Output");
+      }
       setProperty(propertynamess.str(), optws);
 
       g_log.debug() << "  Property Name = " << propertynamess.str() << "\n";
@@ -1332,11 +1361,13 @@ void FilterEvents::createOutputWorkspacesTableSplitterCase() {
       } else {
         propertynamess << "OutputWorkspace_" << wsgroup;
       }
-      declareProperty(
-          Kernel::make_unique<
-              API::WorkspaceProperty<DataObjects::EventWorkspace>>(
-              propertynamess.str(), wsname.str(), Direction::Output),
-          "Output");
+      if (!this->existsProperty(propertynamess.str())) {
+        declareProperty(
+            Kernel::make_unique<
+                API::WorkspaceProperty<DataObjects::EventWorkspace>>(
+                propertynamess.str(), wsname.str(), Direction::Output),
+            "Output");
+      }
       setProperty(propertynamess.str(), optws);
 
       g_log.debug() << "  Property Name = " << propertynamess.str() << "\n";
@@ -1367,8 +1398,7 @@ void FilterEvents::createOutputWorkspacesTableSplitterCase() {
 void FilterEvents::setupDetectorTOFCalibration() {
   // Set output correction workspace and set to output
   const size_t numhist = m_eventWS->getNumberHistograms();
-  MatrixWorkspace_sptr corrws = boost::dynamic_pointer_cast<MatrixWorkspace>(
-      WorkspaceFactory::Instance().create("Workspace2D", numhist, 2, 2));
+  MatrixWorkspace_sptr corrws = create<Workspace2D>(numhist, Points(2));
   setProperty("OutputTOFCorrectionWorkspace", corrws);
 
   // Set up the size of correction and output correction workspace
@@ -1413,16 +1443,16 @@ TimeAtSampleStrategy *FilterEvents::setupElasticTOFCorrection() const {
 TimeAtSampleStrategy *FilterEvents::setupDirectTOFCorrection() const {
 
   // Get incident energy Ei
-  double ei = 0.;
-  if (m_eventWS->run().hasProperty("Ei")) {
-    Kernel::Property *eiprop = m_eventWS->run().getProperty("Ei");
-    ei = boost::lexical_cast<double>(eiprop->value());
-    g_log.debug() << "Using stored Ei value " << ei << "\n";
-  } else {
-    ei = getProperty("IncidentEnergy");
-    if (isEmpty(ei))
+  double ei = getProperty("IncidentEnergy");
+  if (isEmpty(ei)) {
+    if (m_eventWS->run().hasProperty("Ei")) {
+      ei = m_eventWS->run().getLogAsSingleValue("Ei");
+      g_log.debug() << "Using stored Ei value " << ei << "\n";
+    } else {
       throw std::invalid_argument(
           "No Ei value has been set or stored within the run information.");
+    }
+  } else {
     g_log.debug() << "Using user-input Ei value " << ei << "\n";
   }
 
@@ -1442,6 +1472,7 @@ TimeAtSampleStrategy *FilterEvents::setupIndirectTOFCorrection() const {
  */
 void FilterEvents::setupCustomizedTOFCorrection() {
   // Check input workspace
+  m_detCorrectWorkspace = getProperty("DetectorTOFCorrectionWorkspace");
   vector<string> colnames = m_detCorrectWorkspace->getColumnNames();
   bool hasshift = false;
   if (colnames.size() < 2)
diff --git a/Framework/Algorithms/src/FindCenterOfMassPosition.cpp b/Framework/Algorithms/src/FindCenterOfMassPosition.cpp
index c559c288b3ba9f2ca118d0000595bb1ce7b83745..a9aedf3a9e47cab83c9c53fe6f4ca645ee44d93c 100644
--- a/Framework/Algorithms/src/FindCenterOfMassPosition.cpp
+++ b/Framework/Algorithms/src/FindCenterOfMassPosition.cpp
@@ -9,8 +9,8 @@
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
@@ -27,6 +27,7 @@ DECLARE_ALGORITHM(FindCenterOfMassPosition)
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
+using namespace DataObjects;
 
 void FindCenterOfMassPosition::init() {
   auto wsValidator = boost::make_shared<CompositeValidator>();
@@ -215,7 +216,7 @@ void FindCenterOfMassPosition::exec() {
     setPropertyValue("OutputWorkspace", output);
 
     Mantid::API::ITableWorkspace_sptr m_result =
-        Mantid::API::WorkspaceFactory::Instance().createTable("TableWorkspace");
+        boost::make_shared<TableWorkspace>();
     m_result->addColumn("str", "Name");
     m_result->addColumn("double", "Value");
 
diff --git a/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp b/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp
index 5b1d804149a8a983185883649a3e68ceaf0add9e..70cbfc4a81a32ce38caa4eca928078f102bca20d 100644
--- a/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp
+++ b/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp
@@ -13,12 +13,13 @@
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventList.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/PhysicalConstants.h"
-
 namespace Mantid {
 namespace Algorithms {
 
@@ -260,7 +261,7 @@ void FindCenterOfMassPosition2::exec() {
     setPropertyValue("OutputWorkspace", output);
 
     Mantid::API::ITableWorkspace_sptr m_result =
-        Mantid::API::WorkspaceFactory::Instance().createTable("TableWorkspace");
+        boost::make_shared<TableWorkspace>();
     m_result->addColumn("str", "Name");
     m_result->addColumn("double", "Value");
 
diff --git a/Framework/Algorithms/src/FindEPP.cpp b/Framework/Algorithms/src/FindEPP.cpp
index 7232915041ae4ba146dfe2bf71a71e5352fb8c45..0c10fc9e3c640a7825c28ea302598f11920ba340 100644
--- a/Framework/Algorithms/src/FindEPP.cpp
+++ b/Framework/Algorithms/src/FindEPP.cpp
@@ -6,7 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAlgorithms/FindEPP.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidKernel/make_unique.h"
 
 #include <cmath>
@@ -17,6 +17,7 @@ namespace Algorithms {
 
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
+using namespace Mantid::DataObjects;
 
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(FindEPP)
@@ -189,7 +190,7 @@ void FindEPP::fitGaussian(int64_t index) {
  */
 void FindEPP::initWorkspace() {
 
-  m_outWS = WorkspaceFactory::Instance().createTable("TableWorkspace");
+  m_outWS = boost::make_shared<TableWorkspace>();
 
   const std::vector<std::string> columns = {
       "PeakCentre", "PeakCentreError", "Sigma", "SigmaError",
diff --git a/Framework/Algorithms/src/FindPeakBackground.cpp b/Framework/Algorithms/src/FindPeakBackground.cpp
index f75351ad3ac77191ecaa2848304b12249d70c098..59e6f9681206bfa6aa6a9c942150c2c5ecf026ec 100644
--- a/Framework/Algorithms/src/FindPeakBackground.cpp
+++ b/Framework/Algorithms/src/FindPeakBackground.cpp
@@ -7,7 +7,6 @@
 #include "MantidAlgorithms/FindPeakBackground.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidAlgorithms/FindPeaks.h"
 #include "MantidDataObjects/TableWorkspace.h"
@@ -375,7 +374,7 @@ void FindPeakBackground::setFitWindow(const std::vector<double> &fitwindow) {
  */
 void FindPeakBackground::createOutputWorkspaces() {
   // Set up output table workspace
-  m_outPeakTableWS = WorkspaceFactory::Instance().createTable("TableWorkspace");
+  m_outPeakTableWS = boost::make_shared<TableWorkspace>();
   m_outPeakTableWS->addColumn("int", "wksp_index");
   m_outPeakTableWS->addColumn("int", "peak_min_index");
   m_outPeakTableWS->addColumn("int", "peak_max_index");
diff --git a/Framework/Algorithms/src/FindPeaks.cpp b/Framework/Algorithms/src/FindPeaks.cpp
index 8a49f6075c79773b8a2cff1e6d26c9a78f951ffe..4a9431b3af643c0a0dbaabf8c43561e0b4d5318a 100644
--- a/Framework/Algorithms/src/FindPeaks.cpp
+++ b/Framework/Algorithms/src/FindPeaks.cpp
@@ -11,8 +11,8 @@
 #include "MantidAPI/FuncMinimizerFactory.h"
 #include "MantidAPI/FunctionFactory.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAlgorithms/FitPeak.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
 #include "MantidIndexing/GlobalSpectrumIndex.h"
 #include "MantidIndexing/IndexInfo.h"
@@ -290,7 +290,7 @@ void FindPeaks::processAlgorithmProperties() {
 /** Generate a table workspace for output peak parameters
  */
 void FindPeaks::generateOutputPeakParameterTable() {
-  m_outPeakTableWS = WorkspaceFactory::Instance().createTable("TableWorkspace");
+  m_outPeakTableWS = boost::make_shared<TableWorkspace>();
   m_outPeakTableWS->addColumn("int", "spectrum");
 
   if (m_rawPeaksTable) {
diff --git a/Framework/Algorithms/src/FitPeak.cpp b/Framework/Algorithms/src/FitPeak.cpp
index 877df5564496721e82d128c21d6d96ceb74114aa..427c6c2d522fbab1731d91787ad6f197d452b18c 100644
--- a/Framework/Algorithms/src/FitPeak.cpp
+++ b/Framework/Algorithms/src/FitPeak.cpp
@@ -14,24 +14,27 @@
 #include "MantidAPI/FunctionFactory.h"
 #include "MantidAPI/FunctionProperty.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/MultiDomainFunction.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/IValidator.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/StartsWithValidator.h"
 
-#include "MantidAPI/MultiDomainFunction.h"
-
 #include "boost/algorithm/string.hpp"
 #include "boost/algorithm/string/trim.hpp"
 
 using namespace Mantid;
 using namespace Mantid::API;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using namespace Mantid::Kernel;
 using Mantid::HistogramData::HistogramX;
 
@@ -356,8 +359,11 @@ API::MatrixWorkspace_sptr FitOneSinglePeak::genFitWindowWS() {
   size_t ishift = i_maxFitX + 1;
   if (ishift >= vecY.size())
     ysize = vecY.size() - i_minFitX;
-  MatrixWorkspace_sptr purePeakWS =
-      WorkspaceFactory::Instance().create("Workspace2D", 1, size, ysize);
+
+  HistogramBuilder builder;
+  builder.setX(size);
+  builder.setY(ysize);
+  MatrixWorkspace_sptr purePeakWS = create<Workspace2D>(1, builder.build());
 
   auto &vecX = m_dataWS->x(m_wsIndex);
   auto &vecE = m_dataWS->e(m_wsIndex);
@@ -495,7 +501,7 @@ void FitOneSinglePeak::highBkgdFit() {
     size_t numpts = i_maxFitX - i_minFitX;
     size_t shift = static_cast<size_t>(static_cast<double>(numpts) / 6.);
     i_minPeakX += shift;
-    auto Xdata = m_dataWS->x(m_wsIndex);
+    const auto &Xdata = m_dataWS->x(m_wsIndex);
     if (i_minPeakX >= Xdata.size())
       i_minPeakX = Xdata.size() - 1;
     m_minPeakX = Xdata[i_minPeakX];
@@ -1514,9 +1520,10 @@ void FitPeak::setupOutput(
   size_t sizex = vecoutx.size();
   size_t sizey = vecoutx.size();
 
-  MatrixWorkspace_sptr outws = boost::dynamic_pointer_cast<MatrixWorkspace>(
-      WorkspaceFactory::Instance().create("Workspace2D", nspec, sizex, sizey));
-
+  HistogramBuilder builder;
+  builder.setX(sizex);
+  builder.setY(sizey);
+  MatrixWorkspace_sptr outws = create<Workspace2D>(nspec, builder.build());
   // Calculate again
   FunctionDomain1DVector domain(vecoutx);
   FunctionValues values(domain);
diff --git a/Framework/Algorithms/src/FitPeaks.cpp b/Framework/Algorithms/src/FitPeaks.cpp
index 8cf6bd7bebae2de3d0e24e50de51c159d05596c4..5c824d6319287bd68b5f92ec5666b7462f293631 100644
--- a/Framework/Algorithms/src/FitPeaks.cpp
+++ b/Framework/Algorithms/src/FitPeaks.cpp
@@ -16,12 +16,14 @@
 #include "MantidAPI/FunctionProperty.h"
 #include "MantidAPI/MultiDomainFunction.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidAlgorithms/FindPeakBackground.h"
 #include "MantidDataObjects/TableWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidHistogramData/EstimatePolynomial.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidHistogramData/HistogramIterator.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
@@ -36,6 +38,7 @@
 using namespace Mantid;
 using namespace Mantid::API;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using namespace Mantid::Kernel;
 using Mantid::HistogramData::Histogram;
 using namespace std;
@@ -705,16 +708,17 @@ void FitPeaks::processInputFitRanges() {
               << " with the number of peaks " << m_numPeaksToFit << " to fit.";
         throw std::invalid_argument(errss.str());
       }
+      const auto &peakWindowX = m_peakWindowWorkspace->x(wi);
 
       // check window range against peak center
       size_t window_index = window_index_start + wi;
       size_t center_index = window_index - center_index_start;
+      const auto &peakCenterX = m_peakCenterWorkspace->x(center_index);
 
       for (size_t ipeak = 0; ipeak < m_numPeaksToFit; ++ipeak) {
-        double left_w_bound =
-            m_peakWindowWorkspace->x(wi)[ipeak * 2]; // TODO getting on y
-        double right_w_bound = m_peakWindowWorkspace->x(wi)[ipeak * 2 + 1];
-        double center = m_peakCenterWorkspace->x(center_index)[ipeak];
+        double left_w_bound = peakWindowX[ipeak * 2]; // TODO getting on y
+        double right_w_bound = peakWindowX[ipeak * 2 + 1];
+        double center = peakCenterX[ipeak];
         if (!(left_w_bound < center && center < right_w_bound)) {
           std::stringstream errss;
           errss << "Workspace index " << wi
@@ -1286,7 +1290,7 @@ void FitPeaks::calculateFittedPeaks(
 
       // use domain and function to calcualte
       // get the range of start and stop to construct a function domain
-      auto vec_x = m_fittedPeakWS->x(static_cast<size_t>(iws));
+      const auto &vec_x = m_fittedPeakWS->x(static_cast<size_t>(iws));
       std::pair<double, double> peakwindow =
           getPeakFitWindow(static_cast<size_t>(iws), ipeak);
       std::vector<double>::const_iterator start_x_iter =
@@ -1873,8 +1877,10 @@ FitPeaks::createMatrixWorkspace(const std::vector<double> &vec_x,
   size_t size = vec_x.size();
   size_t ysize = vec_y.size();
 
-  MatrixWorkspace_sptr matrix_ws =
-      WorkspaceFactory::Instance().create("Workspace2D", 1, size, ysize);
+  HistogramBuilder builder;
+  builder.setX(std::move(size));
+  builder.setY(std::move(ysize));
+  MatrixWorkspace_sptr matrix_ws = create<Workspace2D>(1, builder.build());
 
   auto &dataX = matrix_ws->mutableX(0);
   auto &dataY = matrix_ws->mutableY(0);
@@ -1894,8 +1900,8 @@ void FitPeaks::generateOutputPeakPositionWS() {
   // create output workspace for peak positions: can be partial spectra to input
   // workspace
   size_t num_hist = m_stopWorkspaceIndex - m_startWorkspaceIndex + 1;
-  m_outputPeakPositionWorkspace = WorkspaceFactory::Instance().create(
-      "Workspace2D", num_hist, m_numPeaksToFit, m_numPeaksToFit);
+  m_outputPeakPositionWorkspace =
+      create<Workspace2D>(num_hist, Points(m_numPeaksToFit));
   // set default
   for (size_t wi = 0; wi < num_hist; ++wi) {
     // convert to workspace index of input data workspace
@@ -1973,8 +1979,7 @@ void FitPeaks::generateFittedParametersValueWorkspaces() {
     param_vec.emplace_back(m_bkgdFunction->parameterName(iparam));
 
   // parameter value table
-  m_fittedParamTable =
-      WorkspaceFactory::Instance().createTable("TableWorkspace");
+  m_fittedParamTable = boost::make_shared<TableWorkspace>();
   setupParameterTableWorkspace(m_fittedParamTable, param_vec, true);
 
   // for error workspace
@@ -1986,8 +1991,7 @@ void FitPeaks::generateFittedParametersValueWorkspaces() {
     m_fitErrorTable = nullptr;
   } else {
     // create table and set up parameter table
-    m_fitErrorTable =
-        WorkspaceFactory::Instance().createTable("TableWorkspace");
+    m_fitErrorTable = boost::make_shared<TableWorkspace>();
     setupParameterTableWorkspace(m_fitErrorTable, param_vec, false);
   }
 
@@ -2007,17 +2011,8 @@ void FitPeaks::generateCalculatedPeaksWS() {
     return;
   }
 
-  // create a wokspace with same number of input matrix workspace
-  m_fittedPeakWS = API::WorkspaceFactory::Instance().create(m_inputMatrixWS);
-  for (size_t iws = 0; iws < m_fittedPeakWS->getNumberHistograms(); ++iws) {
-    auto out_vecx = m_fittedPeakWS->histogram(iws).x();
-    auto in_vecx = m_inputMatrixWS->histogram(iws).x();
-    for (size_t j = 0; j < out_vecx.size(); ++j) {
-      m_fittedPeakWS->dataX(iws)[j] = in_vecx[j];
-    }
-  }
-
-  return;
+  // create a wokspace with same size as in the input matrix workspace
+  m_fittedPeakWS = create<Workspace2D>(*m_inputMatrixWS);
 }
 
 //----------------------------------------------------------------------------------------------
diff --git a/Framework/Algorithms/src/GenerateEventsFilter.cpp b/Framework/Algorithms/src/GenerateEventsFilter.cpp
index 7170af58f509c06d2b6ce4f93b9751f3b600421e..2c87466a76c78aaa4417bcb829862d7b4a5cf90e 100644
--- a/Framework/Algorithms/src/GenerateEventsFilter.cpp
+++ b/Framework/Algorithms/src/GenerateEventsFilter.cpp
@@ -7,8 +7,11 @@
 #include "MantidAlgorithms/GenerateEventsFilter.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/VisibleWhenProperty.h"
@@ -18,6 +21,8 @@
 using namespace Mantid;
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using Types::Core::DateAndTime;
 using Types::Core::time_duration;
 
@@ -228,8 +233,7 @@ void GenerateEventsFilter::processInOutWorkspaces() {
     // Using default
     title = "Splitters";
   }
-  m_filterInfoWS =
-      API::WorkspaceFactory::Instance().createTable("TableWorkspace");
+  m_filterInfoWS = m_filterInfoWS = boost::make_shared<TableWorkspace>();
   m_filterInfoWS->setTitle(title);
   m_filterInfoWS->addColumn("int", "workspacegroup");
   m_filterInfoWS->addColumn("str", "title");
@@ -1755,8 +1759,7 @@ void GenerateEventsFilter::generateSplittersInMatrixWorkspace() {
     throw runtime_error("Logic error on splitter vectors' size. ");
   }
 
-  m_filterWS =
-      API::WorkspaceFactory::Instance().create("Workspace2D", 1, sizex, sizey);
+  m_filterWS = create<Workspace2D>(1, BinEdges(sizex));
   auto &dataX = m_filterWS->mutableX(0);
   for (size_t i = 0; i < sizex; ++i) {
     // x is in the unit as second
@@ -1788,10 +1791,8 @@ void GenerateEventsFilter::generateSplittersInMatrixWorkspaceParallel() {
   ++numtimes;
 
   size_t sizex = numtimes;
-  size_t sizey = numtimes - 1;
 
-  m_filterWS =
-      API::WorkspaceFactory::Instance().create("Workspace2D", 1, sizex, sizey);
+  m_filterWS = create<Workspace2D>(1, BinEdges(sizex));
   auto &dataX = m_filterWS->mutableX(0);
   auto &dataY = m_filterWS->mutableY(0);
 
diff --git a/Framework/Algorithms/src/GeneratePeaks.cpp b/Framework/Algorithms/src/GeneratePeaks.cpp
index 36753d0d30b66e39f1c7fdbabc1a9b979f72dc61..4bee188f94eab06ac92477f3e28100f03dafcb57 100644
--- a/Framework/Algorithms/src/GeneratePeaks.cpp
+++ b/Framework/Algorithms/src/GeneratePeaks.cpp
@@ -11,9 +11,9 @@
 #include "MantidAPI/FunctionValues.h"
 #include "MantidAPI/IBackgroundFunction.h"
 #include "MantidAPI/SpectraAxis.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidIndexing/IndexInfo.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/ListValidator.h"
@@ -705,10 +705,12 @@ API::MatrixWorkspace_sptr GeneratePeaks::createOutputWorkspace() {
           << "Both binning parameters and input workspace are given. "
           << "Using input worksapce to generate output workspace!\n";
 
-    outputWS = API::WorkspaceFactory::Instance().create(
-        inputWS, inputWS->getNumberHistograms(), inputWS->x(0).size(),
-        inputWS->y(0).size());
+    HistogramBuilder builder;
+    builder.setX(inputWS->x(0).size());
+    builder.setY(inputWS->y(0).size());
 
+    builder.setDistribution(inputWS->isDistribution());
+    outputWS = create<MatrixWorkspace>(*inputWS, builder.build());
     // Only copy the X-values from spectra with peaks specified in the table
     // workspace.
     for (const auto &iws : m_spectraSet) {
diff --git a/Framework/Algorithms/src/GetAllEi.cpp b/Framework/Algorithms/src/GetAllEi.cpp
index 4f17258bdf965bec3dc948296856c362fbb6285c..62ec476d2915eb032e00277a79d45ae6a578637e 100644
--- a/Framework/Algorithms/src/GetAllEi.cpp
+++ b/Framework/Algorithms/src/GetAllEi.cpp
@@ -9,8 +9,8 @@
 #include "MantidAPI/HistoWorkspace.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
 #include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/IComponent.h"
 #include "MantidGeometry/Instrument.h"
@@ -31,7 +31,8 @@
 namespace Mantid {
 namespace Algorithms {
 
-using namespace HistogramData;
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 DECLARE_ALGORITHM(GetAllEi)
 
 /// Empty default constructor
@@ -383,8 +384,7 @@ void GetAllEi::exec() {
   std::sort(peaks.begin(), peaks.end());
 
   // finalize output
-  auto result_ws = API::WorkspaceFactory::Instance().create("Workspace2D", 1,
-                                                            nPeaks, nPeaks);
+  auto result_ws = create<Workspace2D>(1, Points(nPeaks));
 
   HistogramX peaks_positions(peaks.size());
   std::transform(peaks.cbegin(), peaks.cend(), peaks_positions.begin(),
@@ -399,7 +399,7 @@ void GetAllEi::exec() {
 
   result_ws->setPoints(0, peaks_positions);
 
-  setProperty("OutputWorkspace", result_ws);
+  setProperty("OutputWorkspace", std::move(result_ws));
 }
 /**Auxiliary method to print guess chopper energies in debug mode
  *
diff --git a/Framework/Algorithms/src/GetDetOffsetsMultiPeaks.cpp b/Framework/Algorithms/src/GetDetOffsetsMultiPeaks.cpp
index 50b9f629c46ab73d9e7e72b623af11d572e06759..642ae0b1d76a3fbff28a02af0274297ca497070e 100644
--- a/Framework/Algorithms/src/GetDetOffsetsMultiPeaks.cpp
+++ b/Framework/Algorithms/src/GetDetOffsetsMultiPeaks.cpp
@@ -14,11 +14,12 @@
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/MaskWorkspace.h"
 #include "MantidDataObjects/OffsetsWorkspace.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/StartsWithValidator.h"
@@ -30,6 +31,9 @@
 
 #include <sstream>
 
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
+
 namespace Mantid {
 namespace Algorithms {
 namespace {
@@ -1166,8 +1170,7 @@ void GetDetOffsetsMultiPeaks::createInformationWorkspaces() {
   }
 
   // Create resolution (delta(d)/d) workspace
-  m_resolutionWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
-      WorkspaceFactory::Instance().create("Workspace2D", numspec, 1, 1));
+  m_resolutionWS = create<Workspace2D>(numspec, Points(1));
 }
 
 //----------------------------------------------------------------------------------------------
diff --git a/Framework/Algorithms/src/GetTimeSeriesLogInformation.cpp b/Framework/Algorithms/src/GetTimeSeriesLogInformation.cpp
index 9a4664d2dd7303cc77edaac8aa0a4e2e610bc126..a3facbb925fe6c1114694b0abfa471d7ab5c85b3 100644
--- a/Framework/Algorithms/src/GetTimeSeriesLogInformation.cpp
+++ b/Framework/Algorithms/src/GetTimeSeriesLogInformation.cpp
@@ -7,11 +7,12 @@
 #include "MantidAlgorithms/GetTimeSeriesLogInformation.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidDataObjects/EventList.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/TimeSeriesProperty.h"
 #include <algorithm>
@@ -20,6 +21,7 @@
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using Mantid::Types::Core::DateAndTime;
 
 using namespace std;
@@ -375,9 +377,7 @@ Workspace2D_sptr GetTimeSeriesLogInformation::calDistributions(
   g_log.notice() << "Distribution has " << numbins << " bins.  Delta T = ("
                  << dtmin << ", " << dtmax << ")\n";
 
-  Workspace2D_sptr distws = boost::dynamic_pointer_cast<Workspace2D>(
-      API::WorkspaceFactory::Instance().create("Workspace2D", 1, numbins,
-                                               numbins));
+  Workspace2D_sptr distws = create<Workspace2D>(1, Points(numbins));
   auto &vecDeltaT = distws->mutableX(0);
   auto &vecCount = distws->mutableY(0);
 
diff --git a/Framework/Algorithms/src/He3TubeEfficiency.cpp b/Framework/Algorithms/src/He3TubeEfficiency.cpp
index 1cbbf3a9a238c034d8668ef0867a0f3b62d3bbf7..e40e97fa1b3aa8cc5b77b5756759dbac3d153f3f 100644
--- a/Framework/Algorithms/src/He3TubeEfficiency.cpp
+++ b/Framework/Algorithms/src/He3TubeEfficiency.cpp
@@ -9,10 +9,10 @@
 #include "MantidAPI/HistogramValidator.h"
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/ParameterMap.h"
 #include "MantidGeometry/Objects/IObject.h"
@@ -36,6 +36,7 @@ const double TOL = 1.0e-8;
 namespace Mantid {
 namespace Algorithms {
 
+using namespace DataObjects;
 using namespace HistogramData;
 // Register the class into the algorithm factory
 DECLARE_ALGORITHM(He3TubeEfficiency)
@@ -100,7 +101,7 @@ void He3TubeEfficiency::exec() {
   m_outputWS = this->getProperty("OutputWorkspace");
 
   if (m_outputWS != m_inputWS) {
-    m_outputWS = API::WorkspaceFactory::Instance().create(m_inputWS);
+    m_outputWS = create<API::MatrixWorkspace>(*m_inputWS);
   }
 
   // Get the detector parameters
diff --git a/Framework/Algorithms/src/HyspecScharpfCorrection.cpp b/Framework/Algorithms/src/HyspecScharpfCorrection.cpp
index 2d48288635a34979746bf3934212d310a65b07dd..0585d4896e61ec5e163ffb1976b8e8e7efcd5c24 100644
--- a/Framework/Algorithms/src/HyspecScharpfCorrection.cpp
+++ b/Framework/Algorithms/src/HyspecScharpfCorrection.cpp
@@ -8,9 +8,9 @@
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/ReferenceFrame.h"
 #include "MantidKernel/BoundedValidator.h"
@@ -21,6 +21,7 @@ namespace Algorithms {
 
 using Mantid::API::WorkspaceProperty;
 using Mantid::Kernel::Direction;
+using namespace DataObjects;
 
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(HyspecScharpfCorrection)
@@ -105,7 +106,7 @@ void HyspecScharpfCorrection::exec() {
   // If input and output workspaces are not the same, create a new workspace for
   // the output
   if (m_outputWS != m_inputWS) {
-    m_outputWS = API::WorkspaceFactory::Instance().create(m_inputWS);
+    m_outputWS = create<API::MatrixWorkspace>(*m_inputWS);
   }
 
   const auto &spectrumInfo = m_inputWS->spectrumInfo();
diff --git a/Framework/Algorithms/src/IQTransform.cpp b/Framework/Algorithms/src/IQTransform.cpp
index 81ab760d9a6e5f53bde1f799102e212330e77022..04bad45d65b735af24cef675dda61f50eed273b1 100644
--- a/Framework/Algorithms/src/IQTransform.cpp
+++ b/Framework/Algorithms/src/IQTransform.cpp
@@ -12,8 +12,11 @@
 #include "MantidAPI/IncreasingAxisValidator.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/RawCountValidator.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
@@ -21,6 +24,9 @@
 #include "MantidKernel/Unit.h"
 #include "MantidKernel/VectorHelper.h"
 
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
+
 namespace Mantid {
 namespace Algorithms {
 
@@ -118,7 +124,7 @@ void IQTransform::exec() {
   // Create the output workspace
   const size_t length = tmpWS->blocksize();
   MatrixWorkspace_sptr outputWS =
-      WorkspaceFactory::Instance().create(inputWS, 1, length, length);
+      create<MatrixWorkspace>(*inputWS, 1, Points(length));
   m_label->setLabel("");
   outputWS->setYUnit("");
   // Copy the data over. Assume single spectrum input (output will be).
diff --git a/Framework/Algorithms/src/IdentifyNoisyDetectors.cpp b/Framework/Algorithms/src/IdentifyNoisyDetectors.cpp
index b74186cd1c232f950f36dc02ba14ca9c39c6ecd3..0563eebe1eac0077039826ff56bb4498209ca8bf 100644
--- a/Framework/Algorithms/src/IdentifyNoisyDetectors.cpp
+++ b/Framework/Algorithms/src/IdentifyNoisyDetectors.cpp
@@ -8,8 +8,9 @@
 #include "MantidAPI/HistogramValidator.h"
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/SpectraAxisValidator.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/CompositeValidator.h"
 #include <numeric>
 
@@ -18,6 +19,7 @@ namespace Algorithms {
 using namespace Kernel;
 using namespace API;
 using namespace HistogramData;
+using namespace DataObjects;
 
 DECLARE_ALGORITHM(IdentifyNoisyDetectors)
 
@@ -55,10 +57,10 @@ void IdentifyNoisyDetectors::exec() {
 
   // Create the output workspace a single value for each spectra.
   MatrixWorkspace_sptr outputWs;
-  outputWs = WorkspaceFactory::Instance().create(inputWS, nHist, 1, 1);
+  outputWs = create<MatrixWorkspace>(*inputWS, Points(1));
 
   MatrixWorkspace_sptr stdDevWs;
-  stdDevWs = WorkspaceFactory::Instance().create(outputWs);
+  stdDevWs = create<MatrixWorkspace>(*outputWs);
 
   progress.report("Integrating...");
 
diff --git a/Framework/Algorithms/src/Integration.cpp b/Framework/Algorithms/src/Integration.cpp
index 760294cd2ec482eeab318d27c0be21f35ad66d74..7f3defdab3afbe5f885c09e205af5ca537192dd2 100644
--- a/Framework/Algorithms/src/Integration.cpp
+++ b/Framework/Algorithms/src/Integration.cpp
@@ -10,9 +10,12 @@
 #include "MantidAlgorithms/Integration.h"
 #include "MantidAPI/NumericAxis.h"
 #include "MantidAPI/TextAxis.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/RebinnedOutput.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/VectorHelper.h"
@@ -29,6 +32,7 @@ DECLARE_ALGORITHM(Integration)
 using namespace Kernel;
 using namespace API;
 using namespace DataObjects;
+using namespace HistogramData;
 
 /** Initialisation method.
  *
@@ -156,9 +160,9 @@ void Integration::exec() {
   }
 
   // Create the 2D workspace (with 1 bin) for the output
-  MatrixWorkspace_sptr outputWorkspace =
-      API::WorkspaceFactory::Instance().create(
-          localworkspace, maxWsIndex - minWsIndex + 1, 2, 1);
+
+  MatrixWorkspace_sptr outputWorkspace = create<Workspace2D>(
+      *localworkspace, maxWsIndex - minWsIndex + 1, BinEdges(2));
   auto rebinned_input =
       boost::dynamic_pointer_cast<const RebinnedOutput>(localworkspace);
   auto rebinned_output =
diff --git a/Framework/Algorithms/src/InterpolatingRebin.cpp b/Framework/Algorithms/src/InterpolatingRebin.cpp
index 9e9bc2e607cfec36a74484d0bbb7a96e1ee8a158..8b7c1e80de316ccf05aaccdc277ff90553eab926 100644
--- a/Framework/Algorithms/src/InterpolatingRebin.cpp
+++ b/Framework/Algorithms/src/InterpolatingRebin.cpp
@@ -7,7 +7,10 @@
 #include "MantidAlgorithms/InterpolatingRebin.h"
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/RebinParamsValidator.h"
 #include "MantidKernel/VectorHelper.h"
@@ -27,6 +30,7 @@ DECLARE_ALGORITHM(InterpolatingRebin)
 using namespace Kernel;
 using namespace API;
 using namespace HistogramData;
+using namespace DataObjects;
 
 /** Only calls its parent's (Rebin) init()
  *
@@ -75,7 +79,7 @@ void InterpolatingRebin::exec() {
   const int nHists = static_cast<int>(inputW->getNumberHistograms());
   // make output Workspace the same type as the input but with the new axes
   MatrixWorkspace_sptr outputW =
-      WorkspaceFactory::Instance().create(inputW, nHists, ntcnew, ntcnew - 1);
+      create<MatrixWorkspace>(*inputW, BinEdges(ntcnew));
   // Copy over the 'vertical' axis
   if (inputW->axes() > 1)
     outputW->replaceAxis(1, inputW->getAxis(1)->clone(outputW.get()));
diff --git a/Framework/Algorithms/src/MagFormFactorCorrection.cpp b/Framework/Algorithms/src/MagFormFactorCorrection.cpp
index 95b1ccdea1950196a623007b084f485fa2d6731c..523c47f5e07e853ad8c3864c5d24912266d16a59 100644
--- a/Framework/Algorithms/src/MagFormFactorCorrection.cpp
+++ b/Framework/Algorithms/src/MagFormFactorCorrection.cpp
@@ -8,12 +8,15 @@
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MagneticIon.h"
 #include "MantidKernel/Unit.h"
 #include "MantidKernel/UnitFactory.h"
-
 using namespace Mantid::PhysicalConstants;
 
 namespace Mantid {
@@ -25,6 +28,8 @@ DECLARE_ALGORITHM(MagFormFactorCorrection)
 using namespace Kernel;
 using namespace API;
 using namespace PhysicalConstants;
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 
 void MagFormFactorCorrection::init() {
   declareProperty(
@@ -94,8 +99,10 @@ void MagFormFactorCorrection::exec() {
     FF.push_back(ion.analyticalFormFactor(Qval * Qval));
   }
   if (!ffwsStr.empty()) {
-    MatrixWorkspace_sptr ffws = API::WorkspaceFactory::Instance().create(
-        "Workspace2D", 1, Qvals.size(), FF.size());
+    HistogramBuilder builder;
+    builder.setX(Qvals.size());
+    builder.setY(FF.size());
+    MatrixWorkspace_sptr ffws = create<Workspace2D>(1, builder.build());
     ffws->mutableX(0).assign(Qvals.begin(), Qvals.end());
     ffws->mutableY(0).assign(FF.begin(), FF.end());
     ffws->getAxis(0)->unit() =
diff --git a/Framework/Algorithms/src/MaxEnt.cpp b/Framework/Algorithms/src/MaxEnt.cpp
index 040765573e5cc58b8c139a82e256707bf1d4a697..977c0f8d6523a20d5d2b3468f35dd3a4f1b7a7b8 100644
--- a/Framework/Algorithms/src/MaxEnt.cpp
+++ b/Framework/Algorithms/src/MaxEnt.cpp
@@ -8,13 +8,17 @@
 #include "MantidAPI/EqualBinSizesValidator.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/TextAxis.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAlgorithms/MaxEnt/MaxentEntropyNegativeValues.h"
 #include "MantidAlgorithms/MaxEnt/MaxentEntropyPositiveValues.h"
 #include "MantidAlgorithms/MaxEnt/MaxentSpaceComplex.h"
 #include "MantidAlgorithms/MaxEnt/MaxentSpaceReal.h"
 #include "MantidAlgorithms/MaxEnt/MaxentTransformFourier.h"
 #include "MantidAlgorithms/MaxEnt/MaxentTransformMultiFourier.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidHistogramData/LinearGenerator.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -32,6 +36,8 @@ using Mantid::HistogramData::Points;
 
 using namespace API;
 using namespace Kernel;
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(MaxEnt)
@@ -425,18 +431,19 @@ void MaxEnt::exec() {
     nSpecConcat = nImageSpec;
     nImageSpec = 1;
   }
-  outImageWS = WorkspaceFactory::Instance().create(inWS, 2 * nImageSpec,
-                                                   npoints, npoints);
+  outImageWS = create<MatrixWorkspace>(*inWS, 2 * nImageSpec, Points(npoints));
   for (size_t i = 0; i < outImageWS->getNumberHistograms(); ++i)
     outImageWS->getSpectrum(i).setDetectorID(static_cast<detid_t>(i + 1));
-  outDataWS = WorkspaceFactory::Instance().create(inWS, 2 * nDataSpec, npointsX,
-                                                  npoints);
+  HistogramBuilder builder;
+  builder.setX(npointsX);
+  builder.setY(npoints);
+  builder.setDistribution(inWS->isDistribution());
+  outDataWS = create<MatrixWorkspace>(*inWS, 2 * nDataSpec, builder.build());
+
   for (size_t i = 0; i < outDataWS->getNumberHistograms(); ++i)
     outDataWS->getSpectrum(i).setDetectorID(static_cast<detid_t>(i + 1));
-  outEvolChi =
-      WorkspaceFactory::Instance().create(inWS, nImageSpec, nIter, nIter);
-  outEvolTest =
-      WorkspaceFactory::Instance().create(inWS, nImageSpec, nIter, nIter);
+  outEvolChi = create<MatrixWorkspace>(*inWS, nImageSpec, Points(nIter));
+  outEvolTest = create<MatrixWorkspace>(*inWS, nImageSpec, Points(nIter));
 
   npoints = complexImage ? npoints * 2 : npoints;
   std::vector<size_t> iterationCounts;
diff --git a/Framework/Algorithms/src/MaxMin.cpp b/Framework/Algorithms/src/MaxMin.cpp
index ebaa7df2fc43c71548de9bdf01929ff5c32602d6..ef22b2fbb539666980fd6523dd775660dbe123c6 100644
--- a/Framework/Algorithms/src/MaxMin.cpp
+++ b/Framework/Algorithms/src/MaxMin.cpp
@@ -9,7 +9,10 @@
 //----------------------------------------------------------------------
 #include "MantidAlgorithms/MaxMin.h"
 #include "MantidAPI/HistogramValidator.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 
 namespace Mantid {
@@ -20,6 +23,8 @@ DECLARE_ALGORITHM(MaxMin)
 
 using namespace Kernel;
 using namespace API;
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 
 /** Initialisation method.
  *
@@ -89,9 +94,10 @@ void MaxMin::exec() {
   }
 
   // Create the 1D workspace for the output
-  MatrixWorkspace_sptr outputWorkspace =
-      API::WorkspaceFactory::Instance().create(localworkspace,
-                                               MaxSpec - MinSpec + 1, 2, 1);
+  MatrixWorkspace_sptr outputWorkspace;
+
+  outputWorkspace = create<HistoWorkspace>(*localworkspace,
+                                           MaxSpec - MinSpec + 1, BinEdges(2));
 
   Progress progress(this, 0.0, 1.0, (MaxSpec - MinSpec + 1));
   PARALLEL_FOR_IF(Kernel::threadSafe(*localworkspace, *outputWorkspace))
diff --git a/Framework/Algorithms/src/Minus.cpp b/Framework/Algorithms/src/Minus.cpp
index 2ea763c5a1b13a6d87e1abfe48d9d15adbd02293..5d8accfc4ffa1d1f7058fa391f9ae6a7d26fc3e6 100644
--- a/Framework/Algorithms/src/Minus.cpp
+++ b/Framework/Algorithms/src/Minus.cpp
@@ -4,9 +4,6 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/Minus.h"
 #include "MantidKernel/VectorHelper.h"
 
@@ -20,30 +17,28 @@ DECLARE_ALGORITHM(Minus)
 
 const std::string Minus::alias() const { return "Subtract"; }
 
-void Minus::performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                                   const MantidVec &lhsE, const MantidVec &rhsY,
-                                   const MantidVec &rhsE, MantidVec &YOut,
-                                   MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
-  std::transform(lhsY.begin(), lhsY.end(), rhsY.begin(), YOut.begin(),
+void Minus::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                   const HistogramData::Histogram &rhs,
+                                   HistogramData::HistogramY &YOut,
+                                   HistogramData::HistogramE &EOut) {
+  std::transform(lhs.y().begin(), lhs.y().end(), rhs.y().begin(), YOut.begin(),
                  std::minus<double>());
-  std::transform(lhsE.begin(), lhsE.end(), rhsE.begin(), EOut.begin(),
+  std::transform(lhs.e().begin(), lhs.e().end(), rhs.e().begin(), EOut.begin(),
                  VectorHelper::SumGaussError<double>());
 }
 
-void Minus::performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                                   const MantidVec &lhsE, const double rhsY,
-                                   const double rhsE, MantidVec &YOut,
-                                   MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
-  std::transform(lhsY.begin(), lhsY.end(), YOut.begin(),
+void Minus::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                   const double rhsY, const double rhsE,
+                                   HistogramData::HistogramY &YOut,
+                                   HistogramData::HistogramE &EOut) {
+  std::transform(lhs.y().begin(), lhs.y().end(), YOut.begin(),
                  std::bind2nd(std::minus<double>(), rhsY));
   // Only do E if non-zero, otherwise just copy
   if (rhsE != 0)
-    std::transform(lhsE.begin(), lhsE.end(), EOut.begin(),
+    std::transform(lhs.e().begin(), lhs.e().end(), EOut.begin(),
                    std::bind2nd(VectorHelper::SumGaussError<double>(), rhsE));
   else
-    EOut = lhsE;
+    EOut = lhs.e();
 }
 
 // ===================================== EVENT LIST BINARY OPERATIONS
diff --git a/Framework/Algorithms/src/ModeratorTzero.cpp b/Framework/Algorithms/src/ModeratorTzero.cpp
index 79d4cc41bdf029ccaec73e5236b61d0382953fae..377bdfecad4864425258dcd1d7c67ad47db03114 100644
--- a/Framework/Algorithms/src/ModeratorTzero.cpp
+++ b/Framework/Algorithms/src/ModeratorTzero.cpp
@@ -8,12 +8,14 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventList.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/muParser_Silent.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/UnitFactory.h"
 
@@ -29,6 +31,7 @@ using namespace Mantid::Kernel;
 using namespace Mantid::API;
 using namespace Mantid::Geometry;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 
 ModeratorTzero::ModeratorTzero()
     : Mantid::API::Algorithm(),
@@ -104,7 +107,7 @@ void ModeratorTzero::exec() {
   // Check whether input == output to see whether a new workspace is required.
   if (outputWS != inputWS) {
     // Create new workspace for output from old
-    outputWS = WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<MatrixWorkspace>(*inputWS);
   }
 
   // calculate tof shift once for all neutrons if emode==Direct
diff --git a/Framework/Algorithms/src/ModeratorTzeroLinear.cpp b/Framework/Algorithms/src/ModeratorTzeroLinear.cpp
index a553b590e28f2f900f9f97bf6eee3a083a861f07..68c554347945cbe405d9535956711d7b0987e3ce 100644
--- a/Framework/Algorithms/src/ModeratorTzeroLinear.cpp
+++ b/Framework/Algorithms/src/ModeratorTzeroLinear.cpp
@@ -8,10 +8,12 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/PhysicalConstants.h"
 #include "MantidKernel/UnitFactory.h"
 
@@ -27,6 +29,7 @@ using namespace Mantid::Kernel;
 using namespace Mantid::API;
 using namespace Mantid::Geometry;
 using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 
 // A reference to the logger is provided by the base class, it is called g_log.
 // It is used to print out information, warning and error messages
@@ -133,7 +136,7 @@ void ModeratorTzeroLinear::exec() {
   // Check whether input = output to see whether a new workspace is required.
   if (outputWS != inputWS) {
     // Create new workspace for output from old
-    outputWS = WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<MatrixWorkspace>(*inputWS);
   }
 
   // do the shift in X
diff --git a/Framework/Algorithms/src/MonitorEfficiencyCorUser.cpp b/Framework/Algorithms/src/MonitorEfficiencyCorUser.cpp
index 162c886ce7f696e1c29242f195408092b6475778..15ed94613ba37ee0abb63ca9eda0476dd8095a47 100644
--- a/Framework/Algorithms/src/MonitorEfficiencyCorUser.cpp
+++ b/Framework/Algorithms/src/MonitorEfficiencyCorUser.cpp
@@ -9,9 +9,12 @@
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Run.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/muParser_Silent.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidHistogramData/HistogramMath.h"
 #include "MantidKernel/MultiThreaded.h"
 #include "MantidKernel/Strings.h"
@@ -22,6 +25,8 @@ namespace Algorithms {
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
+using namespace DataObjects;
+using namespace HistogramData;
 
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(MonitorEfficiencyCorUser)
@@ -47,7 +52,7 @@ void MonitorEfficiencyCorUser::exec() {
   // If input and output workspaces are not the same, create a new workspace for
   // the output
   if (m_outputWS != this->m_inputWS) {
-    m_outputWS = API::WorkspaceFactory::Instance().create(m_inputWS);
+    m_outputWS = create<MatrixWorkspace>(*m_inputWS);
   }
   m_Ei = m_inputWS->run().getPropertyValueAsType<double>("Ei");
 
diff --git a/Framework/Algorithms/src/Multiply.cpp b/Framework/Algorithms/src/Multiply.cpp
index bddcfcbb2bfe6ca2576437708d3200aa6ae53909..f078e9affbf8a9d884dedc6a42cf0ca44593fae9 100644
--- a/Framework/Algorithms/src/Multiply.cpp
+++ b/Framework/Algorithms/src/Multiply.cpp
@@ -4,9 +4,6 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-// Includes
-//----------------------------------------------------------------------
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/Multiply.h"
 
 using namespace Mantid::API;
@@ -19,25 +16,22 @@ namespace Algorithms {
 // Register the class into the algorithm factory
 DECLARE_ALGORITHM(Multiply)
 
-void Multiply::performBinaryOperation(const MantidVec &lhsX,
-                                      const MantidVec &lhsY,
-                                      const MantidVec &lhsE,
-                                      const MantidVec &rhsY,
-                                      const MantidVec &rhsE, MantidVec &YOut,
-                                      MantidVec &EOut) {
-  UNUSED_ARG(lhsX);
-  const size_t bins = lhsE.size();
+void Multiply::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                      const HistogramData::Histogram &rhs,
+                                      HistogramData::HistogramY &YOut,
+                                      HistogramData::HistogramE &EOut) {
+  const size_t bins = lhs.e().size();
   for (size_t j = 0; j < bins; ++j) {
     // Get references to the input Y's
-    const double leftY = lhsY[j];
-    const double rightY = rhsY[j];
+    const double leftY = lhs.y()[j];
+    const double rightY = rhs.y()[j];
 
     // error multiplying two uncorrelated numbers, re-arrange so that you don't
     // get infinity if leftY or rightY == 0
     // (Sa/a)2 + (Sb/b)2 = (Sc/c)2
     // (Sc)2 = (Sa c/a)2 + (Sb c/b)2
     //       = (Sa b)2 + (Sb a)2
-    EOut[j] = sqrt(pow(lhsE[j] * rightY, 2) + pow(rhsE[j] * leftY, 2));
+    EOut[j] = sqrt(pow(lhs.e()[j] * rightY, 2) + pow(rhs.e()[j] * leftY, 2));
 
     // Copy the result last in case one of the input workspaces is also any
     // output
@@ -45,19 +39,17 @@ void Multiply::performBinaryOperation(const MantidVec &lhsX,
   }
 }
 
-void Multiply::performBinaryOperation(const MantidVec &lhsX,
-                                      const MantidVec &lhsY,
-                                      const MantidVec &lhsE, const double rhsY,
-                                      const double rhsE, MantidVec &YOut,
-                                      MantidVec &EOut) {
-  UNUSED_ARG(lhsX);
-  const size_t bins = lhsE.size();
+void Multiply::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                      const double rhsY, const double rhsE,
+                                      HistogramData::HistogramY &YOut,
+                                      HistogramData::HistogramE &EOut) {
+  const size_t bins = lhs.e().size();
   for (size_t j = 0; j < bins; ++j) {
     // Get reference to input Y
-    const double leftY = lhsY[j];
+    const double leftY = lhs.y()[j];
 
     // see comment in the function above for the error formula
-    EOut[j] = sqrt(pow(lhsE[j] * rhsY, 2) + pow(rhsE * leftY, 2));
+    EOut[j] = sqrt(pow(lhs.e()[j] * rhsY, 2) + pow(rhsE * leftY, 2));
 
     // Copy the result last in case one of the input workspaces is also any
     // output
@@ -131,7 +123,7 @@ void Multiply::checkRequirements() {
   m_flipSides = (m_rhs->size() > m_lhs->size());
 
   // Both are vertical columns with one bin?
-  if ((m_rhs->blocksize() == 1) && (m_lhs->blocksize() == 1)) {
+  if ((m_rhsBlocksize == 1) && (m_lhsBlocksize == 1)) {
     // Flip it if the RHS is event and you could keep events
     if (m_erhs && !m_elhs)
       m_flipSides = true;
@@ -185,7 +177,7 @@ std::string Multiply::checkSizeCompatibility(
     // RHS only has one value (1D vertical), so the number of histograms needs
     // to match.
     // Each lhs spectrum will be divided by that scalar
-    if (rhs->blocksize() == 1 &&
+    if (m_rhsBlocksize == 1 &&
         lhs->getNumberHistograms() == rhs->getNumberHistograms())
       return "";
 
diff --git a/Framework/Algorithms/src/MultiplyRange.cpp b/Framework/Algorithms/src/MultiplyRange.cpp
index 26184591259d3cecd44733442e58a8a36ac47fb2..727d667e3d013b46d7322cb94c2e22eaeb278d6e 100644
--- a/Framework/Algorithms/src/MultiplyRange.cpp
+++ b/Framework/Algorithms/src/MultiplyRange.cpp
@@ -9,7 +9,10 @@
 //----------------------------------------------------------------------
 #include "MantidAlgorithms/MultiplyRange.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 
 namespace Mantid {
@@ -20,6 +23,8 @@ DECLARE_ALGORITHM(MultiplyRange)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
+using namespace HistogramData;
 
 void MultiplyRange::init() {
   // Declare an input workspace property.
@@ -69,7 +74,7 @@ void MultiplyRange::exec() {
   // Only create the output workspace if it's different to the input one
   MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
   if (outputWS != inputWS) {
-    outputWS = WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<MatrixWorkspace>(*inputWS);
     setProperty("OutputWorkspace", outputWS);
   }
 
diff --git a/Framework/Algorithms/src/NormaliseToMonitor.cpp b/Framework/Algorithms/src/NormaliseToMonitor.cpp
index 5b34e20a99c4c29949aaec6542a8207fdce57860..45234084a9e6f121ab0682724761e4554e5de038 100644
--- a/Framework/Algorithms/src/NormaliseToMonitor.cpp
+++ b/Framework/Algorithms/src/NormaliseToMonitor.cpp
@@ -10,12 +10,15 @@
 #include "MantidAPI/SingleCountValidator.h"
 #include "MantidAPI/SpectraAxis.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceOpOverloads.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/IDetector.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
 #include "MantidKernel/EnabledWhenProperty.h"
@@ -686,7 +689,7 @@ void NormaliseToMonitor::normaliseBinByBin(
     if (inputEvent) {
       outputWorkspace = inputWorkspace->clone();
     } else
-      outputWorkspace = WorkspaceFactory::Instance().create(inputWorkspace);
+      outputWorkspace = create<MatrixWorkspace>(*inputWorkspace);
   }
   auto outputEvent =
       boost::dynamic_pointer_cast<EventWorkspace>(outputWorkspace);
@@ -694,6 +697,7 @@ void NormaliseToMonitor::normaliseBinByBin(
   const auto &inputSpecInfo = inputWorkspace->spectrumInfo();
   const auto &monitorSpecInfo = m_monitor->spectrumInfo();
 
+  const auto specLength = inputWorkspace->blocksize();
   for (auto &workspaceIndex : m_workspaceIndexes) {
     // Get hold of the monitor spectrum
     const auto &monX = m_monitor->binEdges(workspaceIndex);
@@ -708,7 +712,6 @@ void NormaliseToMonitor::normaliseBinByBin(
       this->normalisationFactor(monX, monY, monE);
 
     const size_t numHists = inputWorkspace->getNumberHistograms();
-    auto specLength = inputWorkspace->blocksize();
     // Flag set when a division by 0 is found
     bool hasZeroDivision = false;
     Progress prog(this, 0.0, 1.0, numHists);
diff --git a/Framework/Algorithms/src/PDCalibration.cpp b/Framework/Algorithms/src/PDCalibration.cpp
index 9bcc9e62d213ab5f7c15943882459493ef2b908c..01537c9dee95b4acafb663369f75ddfc7c20b9cf 100644
--- a/Framework/Algorithms/src/PDCalibration.cpp
+++ b/Framework/Algorithms/src/PDCalibration.cpp
@@ -11,15 +11,17 @@
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/MaskWorkspace.h"
 #include "MantidDataObjects/SpecialWorkspace2D.h"
 #include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/IDetector.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayBoundedValidator.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
@@ -40,6 +42,8 @@
 namespace Mantid {
 namespace Algorithms {
 
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 using Mantid::API::FileProperty;
 using Mantid::API::MatrixWorkspace;
 using Mantid::API::MatrixWorkspace_sptr;
@@ -1198,11 +1202,10 @@ PDCalibration::createTOFPeakCenterFitWindowWorkspaces(
   // create workspaces
   size_t numspec = dataws->getNumberHistograms();
   size_t numpeaks = m_peaksInDspacing.size();
-  MatrixWorkspace_sptr peak_pos_ws = API::WorkspaceFactory::Instance().create(
-      "Workspace2D", numspec, numpeaks, numpeaks);
+  MatrixWorkspace_sptr peak_pos_ws =
+      create<Workspace2D>(numspec, Points(numpeaks));
   MatrixWorkspace_sptr peak_window_ws =
-      API::WorkspaceFactory::Instance().create("Workspace2D", numspec,
-                                               numpeaks * 2, numpeaks * 2);
+      create<Workspace2D>(numspec, Points(numpeaks * 2));
 
   const int64_t NUM_HIST = static_cast<int64_t>(dataws->getNumberHistograms());
   API::Progress prog(this, 0., .2, NUM_HIST);
diff --git a/Framework/Algorithms/src/PDFFourierTransform.cpp b/Framework/Algorithms/src/PDFFourierTransform.cpp
index d1a09b20f13376c20f344779bf83aa28340ab051..fb0665a8f2d1208d0392b8527689e282ab638d02 100644
--- a/Framework/Algorithms/src/PDFFourierTransform.cpp
+++ b/Framework/Algorithms/src/PDFFourierTransform.cpp
@@ -8,8 +8,10 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/Sample.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidHistogramData/LinearGenerator.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -32,6 +34,7 @@ DECLARE_ALGORITHM(PDFFourierTransform)
 
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
+using namespace DataObjects;
 
 namespace { // anonymous namespace
 /// Crystalline PDF
@@ -329,8 +332,7 @@ void PDFFourierTransform::exec() {
   bool filter = getProperty("Filter");
 
   // create the output workspace
-  API::MatrixWorkspace_sptr outputWS =
-      WorkspaceFactory::Instance().create("Workspace2D", 1, sizer, sizer);
+  API::MatrixWorkspace_sptr outputWS = create<Workspace2D>(1, Points(sizer));
   outputWS->getAxis(0)->unit() = UnitFactory::Instance().create("Label");
   Unit_sptr unit = outputWS->getAxis(0)->unit();
   boost::shared_ptr<Units::Label> label =
diff --git a/Framework/Algorithms/src/PaddingAndApodization.cpp b/Framework/Algorithms/src/PaddingAndApodization.cpp
index ce6422028aa9ddd02f25348cd70f01d248e55d70..9adb0bb564317a3cd01f7b877eb7cc9cc7b0ed9e 100644
--- a/Framework/Algorithms/src/PaddingAndApodization.cpp
+++ b/Framework/Algorithms/src/PaddingAndApodization.cpp
@@ -12,8 +12,11 @@
 
 #include "MantidAPI/AlgorithmManager.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/Workspace_fwd.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/ListValidator.h"
@@ -27,6 +30,8 @@ namespace Mantid {
 namespace Algorithms {
 
 using namespace Kernel;
+using namespace DataObjects;
+using namespace HistogramData;
 using API::Progress;
 using std::size_t;
 
@@ -72,7 +77,7 @@ void PaddingAndApodization::exec() {
   // Create output workspace with same dimensions as input
   API::MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
   if (inputWS != outputWS) {
-    outputWS = API::WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<API::MatrixWorkspace>(*inputWS);
   }
 
   // Share the X values
diff --git a/Framework/Algorithms/src/Plus.cpp b/Framework/Algorithms/src/Plus.cpp
index bb9b8caf37dd17863a0c87a2b27cad672a635eac..b0683314e6d3b2a2d24b54271812c26c9b83ad86 100644
--- a/Framework/Algorithms/src/Plus.cpp
+++ b/Framework/Algorithms/src/Plus.cpp
@@ -4,9 +4,6 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/Plus.h"
 #include "MantidKernel/VectorHelper.h"
 
@@ -22,31 +19,29 @@ DECLARE_ALGORITHM(Plus)
 // ===================================== HISTOGRAM BINARY OPERATIONS
 // ==========================================
 //---------------------------------------------------------------------------------------------
-void Plus::performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                                  const MantidVec &lhsE, const MantidVec &rhsY,
-                                  const MantidVec &rhsE, MantidVec &YOut,
-                                  MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
-  std::transform(lhsY.begin(), lhsY.end(), rhsY.begin(), YOut.begin(),
+void Plus::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                  const HistogramData::Histogram &rhs,
+                                  HistogramData::HistogramY &YOut,
+                                  HistogramData::HistogramE &EOut) {
+  std::transform(lhs.y().begin(), lhs.y().end(), rhs.y().begin(), YOut.begin(),
                  std::plus<double>());
-  std::transform(lhsE.begin(), lhsE.end(), rhsE.begin(), EOut.begin(),
+  std::transform(lhs.e().begin(), lhs.e().end(), rhs.e().begin(), EOut.begin(),
                  VectorHelper::SumGaussError<double>());
 }
 
 //---------------------------------------------------------------------------------------------
-void Plus::performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                                  const MantidVec &lhsE, const double rhsY,
-                                  const double rhsE, MantidVec &YOut,
-                                  MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
-  std::transform(lhsY.begin(), lhsY.end(), YOut.begin(),
+void Plus::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                  const double rhsY, const double rhsE,
+                                  HistogramData::HistogramY &YOut,
+                                  HistogramData::HistogramE &EOut) {
+  std::transform(lhs.y().begin(), lhs.y().end(), YOut.begin(),
                  std::bind2nd(std::plus<double>(), rhsY));
   // Only do E if non-zero, otherwise just copy
   if (rhsE != 0)
-    std::transform(lhsE.begin(), lhsE.end(), EOut.begin(),
+    std::transform(lhs.e().begin(), lhs.e().end(), EOut.begin(),
                    std::bind2nd(VectorHelper::SumGaussError<double>(), rhsE));
   else
-    EOut = lhsE;
+    EOut = lhs.e();
 }
 
 // ===================================== EVENT LIST BINARY OPERATIONS
diff --git a/Framework/Algorithms/src/PointByPointVCorrection.cpp b/Framework/Algorithms/src/PointByPointVCorrection.cpp
index 83580d0ff123cb2a4688b74279b44868ac25306a..604e6cb919e9830a7ee796ac15f91f144c6c6116 100644
--- a/Framework/Algorithms/src/PointByPointVCorrection.cpp
+++ b/Framework/Algorithms/src/PointByPointVCorrection.cpp
@@ -10,7 +10,7 @@
 #include "MantidAlgorithms/PointByPointVCorrection.h"
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/MatrixWorkspace.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidKernel/VectorHelper.h"
 
@@ -26,6 +26,7 @@ DECLARE_ALGORITHM(PointByPointVCorrection)
 
 using namespace Kernel;
 using namespace API;
+using namespace DataObjects;
 
 /// Default constructor
 PointByPointVCorrection::PointByPointVCorrection() : Algorithm() {}
@@ -183,7 +184,7 @@ void PointByPointVCorrection::check_validity(
   if (out != w1 && out != w2) // Create a new workspace only if it is different
                               // from of the input ones.
   {
-    out = API::WorkspaceFactory::Instance().create(w1);
+    out = create<MatrixWorkspace>(*w1);
     setProperty("OutputWorkspace", out);
   } else if (out == w2) {
     g_log.warning("Any masking in the output workspaces will be taken from the "
diff --git a/Framework/Algorithms/src/PoissonErrors.cpp b/Framework/Algorithms/src/PoissonErrors.cpp
index 53f9fc3c68d871a74fcc1dfe93d7e971f36ee764..e306efc0f1de1ea17433f9c9473f7864fb605a29 100644
--- a/Framework/Algorithms/src/PoissonErrors.cpp
+++ b/Framework/Algorithms/src/PoissonErrors.cpp
@@ -4,9 +4,6 @@
 //     NScD Oak Ridge National Laboratory, European Spallation Source
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/PoissonErrors.h"
 
 using namespace Mantid::API;
@@ -37,39 +34,33 @@ std::string PoissonErrors::checkSizeCompatibility(
   }
 }
 
-void PoissonErrors::performBinaryOperation(const MantidVec &lhsX,
-                                           const MantidVec &lhsY,
-                                           const MantidVec &lhsE,
-                                           const MantidVec &rhsY,
-                                           const MantidVec &rhsE,
-                                           MantidVec &YOut, MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
+void PoissonErrors::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                           const HistogramData::Histogram &rhs,
+                                           HistogramData::HistogramY &YOut,
+                                           HistogramData::HistogramE &EOut) {
   // Just copy over the lhs data
-  YOut = lhsY;
+  YOut = lhs.y();
   // Now make the fractional error the same as it was on the rhs
-  const int bins = static_cast<int>(lhsE.size());
+  const int bins = static_cast<int>(lhs.e().size());
   for (int j = 0; j < bins; ++j) {
-    if (rhsY[j] != 0.0)
-      EOut[j] = rhsE[j] / rhsY[j] * lhsY[j];
+    if (rhs.y()[j] != 0.0)
+      EOut[j] = rhs.e()[j] / rhs.y()[j] * lhs.y()[j];
     else
       EOut[j] = 0.0;
   }
 }
 
-void PoissonErrors::performBinaryOperation(const MantidVec &lhsX,
-                                           const MantidVec &lhsY,
-                                           const MantidVec &lhsE,
+void PoissonErrors::performBinaryOperation(const HistogramData::Histogram &lhs,
                                            const double rhsY, const double rhsE,
-                                           MantidVec &YOut, MantidVec &EOut) {
-  (void)lhsE;
-  (void)lhsX; // Avoid compiler warning
+                                           HistogramData::HistogramY &YOut,
+                                           HistogramData::HistogramE &EOut) {
 
-  assert(lhsX.size() == 1);
+  assert(lhs.x().size() == 1);
   // If we get here we've got two single column workspaces so it's easy.
-  YOut[0] = lhsY[0];
+  YOut[0] = lhs.y()[0];
 
   if (rhsY != 0.0)
-    EOut[0] = rhsE / rhsY * lhsY[0];
+    EOut[0] = rhsE / rhsY * lhs.y()[0];
   else
     EOut[0] = 0.0;
 }
diff --git a/Framework/Algorithms/src/PolarizationCorrectionFredrikze.cpp b/Framework/Algorithms/src/PolarizationCorrectionFredrikze.cpp
index 3983e6f96793f4f08eaaeb43dfbcdbf3fb08e795..be14c278903c215712d97a9ef17201c7eec415c7 100644
--- a/Framework/Algorithms/src/PolarizationCorrectionFredrikze.cpp
+++ b/Framework/Algorithms/src/PolarizationCorrectionFredrikze.cpp
@@ -56,9 +56,8 @@ Instrument_const_sptr fetchInstrument(WorkspaceGroup const *const groupWS) {
 }
 
 void validateInputWorkspace(WorkspaceGroup_sptr &ws) {
-
+  MatrixWorkspace_sptr lastWS;
   for (size_t i = 0; i < ws->size(); ++i) {
-    MatrixWorkspace_sptr lastWS;
 
     Workspace_sptr item = ws->getItem(i);
 
diff --git a/Framework/Algorithms/src/Qhelper.cpp b/Framework/Algorithms/src/Qhelper.cpp
index 7eed4d0cc6af2d8235c882696c20ce67fc3c9c24..f51eadee066db5a895bd49af46a5c8c878ff6690 100644
--- a/Framework/Algorithms/src/Qhelper.cpp
+++ b/Framework/Algorithms/src/Qhelper.cpp
@@ -174,7 +174,7 @@ size_t Qhelper::waveLengthCutOff(API::MatrixWorkspace_const_sptr dataWS,
   R = std::sqrt(R);
 
   const double WMin = l_WCutOver * (l_RCut - R);
-  auto Xs = dataWS->x(wsInd);
+  const auto &Xs = dataWS->x(wsInd);
   return std::lower_bound(Xs.begin(), Xs.end(), WMin) - Xs.begin();
 }
 
diff --git a/Framework/Algorithms/src/RadiusSum.cpp b/Framework/Algorithms/src/RadiusSum.cpp
index a01ae49d45de5232e73780f14734c7a70a0a4577..bee835c8e59f506e8b0019bb70066395d1f4826c 100644
--- a/Framework/Algorithms/src/RadiusSum.cpp
+++ b/Framework/Algorithms/src/RadiusSum.cpp
@@ -603,7 +603,7 @@ double RadiusSum::getMaxDistance(const V3D &centre,
   return max_distance;
 }
 
-void RadiusSum::setUpOutputWorkspace(std::vector<double> &values) {
+void RadiusSum::setUpOutputWorkspace(const std::vector<double> &values) {
 
   g_log.debug() << "Output calculated, setting up the output workspace\n";
 
diff --git a/Framework/Algorithms/src/Rebin2D.cpp b/Framework/Algorithms/src/Rebin2D.cpp
index b4d04b679e475e80e1d7d6ee19521c7e12a3982d..4d8d9eed09ded8f637ffd459d4e04bc2277060d1 100644
--- a/Framework/Algorithms/src/Rebin2D.cpp
+++ b/Framework/Algorithms/src/Rebin2D.cpp
@@ -77,7 +77,8 @@ void Rebin2D::init() {
 void Rebin2D::exec() {
   // Information to form input grid
   MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
-  NumericAxis *oldAxis2 = dynamic_cast<API::NumericAxis *>(inputWS->getAxis(1));
+  const NumericAxis *oldAxis2 =
+      dynamic_cast<API::NumericAxis *>(inputWS->getAxis(1));
   if (!oldAxis2) {
     throw std::invalid_argument(
         "Vertical axis is not a numeric axis, cannot rebin. "
@@ -87,16 +88,9 @@ void Rebin2D::exec() {
   const auto &oldXEdges = inputWS->x(0);
   const size_t numXBins = inputWS->blocksize();
   const size_t numYBins = inputWS->getNumberHistograms();
-  std::vector<double> oldYEdges;
-  if (numYBins == oldAxis2->length()) {
-    // Pt data on axis 2, create bins
-    oldYEdges = oldAxis2->createBinBoundaries();
-  } else {
-    oldYEdges.resize(oldAxis2->length());
-    for (size_t i = 0; i < oldAxis2->length(); ++i) {
-      oldYEdges[i] = (*oldAxis2)(i);
-    }
-  }
+  // This will convert plain NumericAxis to bin edges while
+  // BinEdgeAxis will just return its edges.
+  const std::vector<double> oldYEdges = oldAxis2->createBinBoundaries();
 
   // Output grid and workspace. Fills in the new X and Y bin vectors
   // MantidVecPtr newXBins;
diff --git a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
index af079d05f7a096f95b1f4427628724e05bbbced7..876906e1a8e2bde79605980c10bbb0549709c6df 100644
--- a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
+++ b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
@@ -837,7 +837,7 @@ double ReflectometryReductionOne2::findIvsLamRangeMin(
   const double bTwoTheta = getDetectorTwoThetaRange(spIdx);
 
   // For bLambda, use the average bin size for this spectrum
-  auto xValues = detectorWS->x(spIdx);
+  const auto &xValues = detectorWS->x(spIdx);
   double bLambda = (xValues[xValues.size() - 1] - xValues[0]) /
                    static_cast<int>(xValues.size());
   double dummy = 0.0;
@@ -856,7 +856,7 @@ double ReflectometryReductionOne2::findIvsLamRangeMax(
   const double bTwoTheta = getDetectorTwoThetaRange(spIdx);
 
   // For bLambda, use the average bin size for this spectrum
-  auto xValues = detectorWS->x(spIdx);
+  const auto &xValues = detectorWS->x(spIdx);
   double bLambda = (xValues[xValues.size() - 1] - xValues[0]) /
                    static_cast<int>(xValues.size());
 
diff --git a/Framework/Algorithms/src/SofQWPolygon.cpp b/Framework/Algorithms/src/SofQWPolygon.cpp
index c79f5589721f76310eb9645a423fb856c0b3034a..ddb1c0557cb680fb218a2167544c852eea3a9ab0 100644
--- a/Framework/Algorithms/src/SofQWPolygon.cpp
+++ b/Framework/Algorithms/src/SofQWPolygon.cpp
@@ -49,8 +49,9 @@ void SofQWPolygon::exec() {
   }
 
   // Progress reports & cancellation
-  const size_t nreports(static_cast<size_t>(inputWS->getNumberHistograms() *
-                                            inputWS->blocksize()));
+  const auto blocksize = inputWS->blocksize();
+  const size_t nreports(
+      static_cast<size_t>(inputWS->getNumberHistograms() * blocksize));
   m_progress = boost::shared_ptr<API::Progress>(
       new API::Progress(this, 0.0, 1.0, nreports));
   // Compute input caches
@@ -61,7 +62,7 @@ void SofQWPolygon::exec() {
           *inputWS, getProperty("QAxisBinning"), m_Qout,
           getProperty("EAxisBinning"), m_EmodeProperties);
   setProperty("OutputWorkspace", outputWS);
-  const size_t nenergyBins = inputWS->blocksize();
+  const size_t nenergyBins = blocksize;
 
   const size_t nTheta = m_thetaPts.size();
   const auto &X = inputWS->x(0);
diff --git a/Framework/Algorithms/src/Stitch1D.cpp b/Framework/Algorithms/src/Stitch1D.cpp
index 8e081040c94435da6feb858db6cde4439cc2353e..76502b8f7685995179bf2fa621975185f623a134 100644
--- a/Framework/Algorithms/src/Stitch1D.cpp
+++ b/Framework/Algorithms/src/Stitch1D.cpp
@@ -469,7 +469,7 @@ bool Stitch1D::hasNonzeroErrors(MatrixWorkspace_sptr &ws) {
     PARALLEL_START_INTERUPT_REGION
     if (!hasNonZeroErrors) // Keep checking
     {
-      auto e = ws->e(i);
+      const auto &e = ws->e(i);
       auto it = std::find_if(e.begin(), e.end(), isNonzero);
       if (it != e.end()) {
         PARALLEL_CRITICAL(has_non_zero) {
diff --git a/Framework/Algorithms/src/StripVanadiumPeaks.cpp b/Framework/Algorithms/src/StripVanadiumPeaks.cpp
index 889c38df00882301c2901ff61219d82fcfe79fd6..90465e424f934376e4f5677a845ec4b15471ba2d 100644
--- a/Framework/Algorithms/src/StripVanadiumPeaks.cpp
+++ b/Framework/Algorithms/src/StripVanadiumPeaks.cpp
@@ -176,8 +176,6 @@ void StripVanadiumPeaks::exec() {
         }
       }
 
-      // Save the output
-      outputWS->mutableY(k) = outY;
     } // if the spectrum is to be changed.
     progress.report();
   } // each spectrum
diff --git a/Framework/Algorithms/src/TimeAtSampleStrategyDirect.cpp b/Framework/Algorithms/src/TimeAtSampleStrategyDirect.cpp
index d19f8d97dbdb83cf9ebd93f3f31e28ff68ed11b5..ff68180e289c0344bcfa0e579285584d928dc058 100644
--- a/Framework/Algorithms/src/TimeAtSampleStrategyDirect.cpp
+++ b/Framework/Algorithms/src/TimeAtSampleStrategyDirect.cpp
@@ -30,14 +30,17 @@ TimeAtSampleStrategyDirect::TimeAtSampleStrategyDirect(
     MatrixWorkspace_const_sptr ws, double ei)
     : m_constShift(0) {
 
+  // A constant among all spectra
+  constexpr double TWO_MEV_OVER_MASS =
+      2. * PhysicalConstants::meV / PhysicalConstants::NeutronMass;
+
   // Get L1
-  V3D samplepos = ws->getInstrument()->getSample()->getPos();
-  V3D sourcepos = ws->getInstrument()->getSource()->getPos();
+  const auto &samplepos = ws->getInstrument()->getSample()->getPos();
+  const auto &sourcepos = ws->getInstrument()->getSource()->getPos();
   double l1 = samplepos.distance(sourcepos);
 
   // Calculate constant (to all spectra) shift
-  m_constShift = l1 / std::sqrt(ei * 2. * PhysicalConstants::meV /
-                                PhysicalConstants::NeutronMass);
+  m_constShift = l1 / std::sqrt(ei * TWO_MEV_OVER_MASS);
 }
 
 /**
@@ -51,6 +54,5 @@ Correction Mantid::Algorithms::TimeAtSampleStrategyDirect::calculate(
   // required.
   return Correction(0, m_constShift);
 }
-
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/TimeAtSampleStrategyElastic.cpp b/Framework/Algorithms/src/TimeAtSampleStrategyElastic.cpp
index a005257641b379c67f78750f4eb2a81294865d39..0d7f3732da2714fad7fdfa8d2384bc47f39736ef 100644
--- a/Framework/Algorithms/src/TimeAtSampleStrategyElastic.cpp
+++ b/Framework/Algorithms/src/TimeAtSampleStrategyElastic.cpp
@@ -33,22 +33,20 @@ TimeAtSampleStrategyElastic::TimeAtSampleStrategyElastic(
  */
 Correction
 TimeAtSampleStrategyElastic::calculate(const size_t &workspace_index) const {
-  Correction retvalue(0, 0);
 
   // Calculate TOF ratio
   const double L1s = m_spectrumInfo.l1();
+  double scale;
   if (m_spectrumInfo.isMonitor(workspace_index)) {
     double L1m =
         m_beamDir.scalar_prod(m_spectrumInfo.sourcePosition() -
                               m_spectrumInfo.position(workspace_index));
-    retvalue.factor = std::abs(L1s / L1m);
+    scale = std::abs(L1s / L1m);
   } else {
-    retvalue.factor = L1s / (L1s + m_spectrumInfo.l2(workspace_index));
+    scale = L1s / (L1s + m_spectrumInfo.l2(workspace_index));
   }
 
-  retvalue.offset = 0;
-
-  return retvalue;
+  return Correction(0., scale);
 }
 
 } // namespace Algorithms
diff --git a/Framework/Algorithms/src/TimeAtSampleStrategyIndirect.cpp b/Framework/Algorithms/src/TimeAtSampleStrategyIndirect.cpp
index 8ca7195c0dc16f41e5f0b9d8e0ca395929272d58..ee30d23235f1ac4557f91b60c7611ac3f7204628 100644
--- a/Framework/Algorithms/src/TimeAtSampleStrategyIndirect.cpp
+++ b/Framework/Algorithms/src/TimeAtSampleStrategyIndirect.cpp
@@ -36,44 +36,50 @@ Correction
 TimeAtSampleStrategyIndirect::calculate(const size_t &workspace_index) const {
 
   // A constant among all spectra
-  double twomev_d_mass =
+  constexpr double TWO_MEV_OVER_MASS =
       2. * PhysicalConstants::meV / PhysicalConstants::NeutronMass;
 
-  // Get the parameter map
-  const ParameterMap &pmap = m_ws->constInstrumentParameters();
-
-  double shift;
   const IDetector *det = &m_spectrumInfo.detector(workspace_index);
-  if (!m_spectrumInfo.isMonitor(workspace_index)) {
-    // Get E_fix
-    double efix = 0.;
-    try {
-      Parameter_sptr par = pmap.getRecursive(det, "Efixed");
-      if (par) {
-        efix = par->value<double>();
-      }
-    } catch (std::runtime_error &) {
-      // Throws if a DetectorGroup, use single provided value
-      std::stringstream errmsg;
-      errmsg << "Inelastic instrument detector " << det->getID()
-             << " of spectrum " << workspace_index << " does not have EFixed ";
-      throw std::runtime_error(errmsg.str());
-    }
-
-    double l2 = m_spectrumInfo.l2(workspace_index);
-    shift = -1. * l2 / sqrt(efix * twomev_d_mass);
+  if (m_spectrumInfo.isMonitor(workspace_index)) {
+    // use the same math as TimeAtSampleStrategyElastic
+    const double L1s = m_spectrumInfo.l1();
+    const auto &beamDir =
+        m_ws->getInstrument()->getReferenceFrame()->vecPointingAlongBeam();
+    const double L1m =
+        beamDir.scalar_prod(m_spectrumInfo.sourcePosition() -
+                            m_spectrumInfo.position(workspace_index));
+    const double scale = std::abs(L1s / L1m);
+    return Correction(0., scale);
+  }
 
-  } else {
-    std::stringstream errormsg;
-    errormsg << "Workspace index " << workspace_index << " is a monitor. ";
-    throw std::invalid_argument(errormsg.str());
+  // Get E_fix
+  double efix{0.};
+  try {
+    // Get the parameter map
+    const ParameterMap &pmap = m_ws->constInstrumentParameters();
+    Parameter_sptr par = pmap.getRecursive(det, "Efixed");
+    if (par) {
+      efix = par->value<double>();
+    }
+  } catch (std::runtime_error &) {
+    // Throws if a DetectorGroup, use single provided value
+    std::stringstream errmsg;
+    errmsg << "Inelastic instrument detector " << det->getID()
+           << " of spectrum " << workspace_index << " does not have EFixed ";
+    throw std::runtime_error(errmsg.str());
+  }
+  if (efix <= 0.) {
+    std::stringstream errmsg;
+    errmsg << "Inelastic instrument detector " << det->getID()
+           << " of spectrum " << workspace_index << " does not have EFixed ";
+    throw std::runtime_error(errmsg.str());
   }
 
-  Correction retvalue(0, 0);
-  retvalue.factor = 1.0;
-  retvalue.offset = shift;
+  const double l2 = m_spectrumInfo.l2(workspace_index);
+  const double shift = -1. * l2 / sqrt(efix * TWO_MEV_OVER_MASS);
 
-  return retvalue;
+  // 1.0 * tof + shift
+  return Correction(shift, 1.0);
 }
 
 } // namespace Algorithms
diff --git a/Framework/Algorithms/src/WeightedMean.cpp b/Framework/Algorithms/src/WeightedMean.cpp
index f45a333417f0ca2ebbdb7dc6b59bf84742f7706d..8e9cbda86f37ddb4320fecfbc3ede8c834592cc1 100644
--- a/Framework/Algorithms/src/WeightedMean.cpp
+++ b/Framework/Algorithms/src/WeightedMean.cpp
@@ -49,28 +49,25 @@ std::string WeightedMean::checkSizeCompatibility(
   }
 }
 
-void WeightedMean::performBinaryOperation(const MantidVec &lhsX,
-                                          const MantidVec &lhsY,
-                                          const MantidVec &lhsE,
-                                          const MantidVec &rhsY,
-                                          const MantidVec &rhsE,
-                                          MantidVec &YOut, MantidVec &EOut) {
-  (void)lhsX; // Avoid compiler warning
-  const size_t bins = lhsY.size();
+void WeightedMean::performBinaryOperation(const HistogramData::Histogram &lhs,
+                                          const HistogramData::Histogram &rhs,
+                                          HistogramData::HistogramY &YOut,
+                                          HistogramData::HistogramE &EOut) {
+  const size_t bins = lhs.size();
   for (size_t j = 0; j < bins; ++j) {
-    if (lhsE[j] > 0.0 && rhsE[j] > 0.0) {
-      const double err1 = lhsE[j] * lhsE[j];
-      const double err2 = rhsE[j] * rhsE[j];
-      YOut[j] = (lhsY[j] / err1) + (rhsY[j] / err2);
+    if (lhs.e()[j] > 0.0 && rhs.e()[j] > 0.0) {
+      const double err1 = lhs.e()[j] * lhs.e()[j];
+      const double err2 = rhs.e()[j] * rhs.e()[j];
+      YOut[j] = (lhs.y()[j] / err1) + (rhs.y()[j] / err2);
       EOut[j] = (err1 * err2) / (err1 + err2);
       YOut[j] *= EOut[j];
       EOut[j] = sqrt(EOut[j]);
-    } else if (lhsE[j] > 0.0 && rhsE[j] <= 0.0) {
-      YOut[j] = lhsY[j];
-      EOut[j] = lhsE[j];
-    } else if (lhsE[j] <= 0.0 && rhsE[j] > 0.0) {
-      YOut[j] = rhsY[j];
-      EOut[j] = rhsE[j];
+    } else if (lhs.e()[j] > 0.0 && rhs.e()[j] <= 0.0) {
+      YOut[j] = lhs.y()[j];
+      EOut[j] = lhs.e()[j];
+    } else if (lhs.e()[j] <= 0.0 && rhs.e()[j] > 0.0) {
+      YOut[j] = rhs.y()[j];
+      EOut[j] = rhs.e()[j];
     } else {
       YOut[j] = 0.0;
       EOut[j] = 0.0;
@@ -78,25 +75,23 @@ void WeightedMean::performBinaryOperation(const MantidVec &lhsX,
   }
 }
 
-void WeightedMean::performBinaryOperation(const MantidVec &lhsX,
-                                          const MantidVec &lhsY,
-                                          const MantidVec &lhsE,
+void WeightedMean::performBinaryOperation(const HistogramData::Histogram &lhs,
                                           const double rhsY, const double rhsE,
-                                          MantidVec &YOut, MantidVec &EOut) {
-  UNUSED_ARG(lhsX);
-  assert(lhsX.size() == 1);
+                                          HistogramData::HistogramY &YOut,
+                                          HistogramData::HistogramE &EOut) {
+  assert(lhs.size() == 1);
   // If we get here we've got two single column workspaces so it's easy.
-  if (lhsE[0] > 0.0 && rhsE > 0.0) {
-    const double err1 = lhsE[0] * lhsE[0];
+  if (lhs.e()[0] > 0.0 && rhsE > 0.0) {
+    const double err1 = lhs.e()[0] * lhs.e()[0];
     const double err2 = rhsE * rhsE;
-    YOut[0] = (lhsY[0] / err1) + (rhsY / err2);
+    YOut[0] = (lhs.y()[0] / err1) + (rhsY / err2);
     EOut[0] = (err1 * err2) / (err1 + err2);
     YOut[0] *= EOut[0];
     EOut[0] = sqrt(EOut[0]);
-  } else if (lhsE[0] > 0.0 && rhsE <= 0.0) {
-    YOut[0] = lhsY[0];
-    EOut[0] = lhsE[0];
-  } else if (lhsE[0] <= 0.0 && rhsE > 0.0) {
+  } else if (lhs.e()[0] > 0.0 && rhsE <= 0.0) {
+    YOut[0] = lhs.y()[0];
+    EOut[0] = lhs.e()[0];
+  } else if (lhs.e()[0] <= 0.0 && rhsE > 0.0) {
     YOut[0] = rhsY;
     EOut[0] = rhsE;
   } else {
diff --git a/Framework/Algorithms/test/AnyShapeAbsorptionTest.h b/Framework/Algorithms/test/AnyShapeAbsorptionTest.h
index deada58ef8d4a1fd94bb0bda8d58247e3dcd6745..526810f1f8eab18b138b65faaed92417e0db168c 100644
--- a/Framework/Algorithms/test/AnyShapeAbsorptionTest.h
+++ b/Framework/Algorithms/test/AnyShapeAbsorptionTest.h
@@ -127,8 +127,7 @@ public:
     TS_ASSERT(cyl.isExecuted());
 
     // Using the output of the CylinderAbsorption algorithm is convenient
-    // because
-    // it adds the sample object to the workspace
+    // because it adds the sample object to the workspace
     TS_ASSERT_THROWS_NOTHING(atten2.setPropertyValue("InputWorkspace", cylWS));
     std::string outputWS("factors");
     TS_ASSERT_THROWS_NOTHING(
@@ -199,6 +198,55 @@ public:
     Mantid::API::AnalysisDataService::Instance().remove("gauge");
   }
 
+  void testTinyVolume() {
+    if (!atten.isInitialized())
+      atten.initialize();
+
+    // Create a small test workspace
+    MatrixWorkspace_sptr testWS =
+        WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(1, 10);
+    // Needs to have units of wavelength
+    testWS->getAxis(0)->unit() =
+        Mantid::Kernel::UnitFactory::Instance().create("Wavelength");
+
+    Mantid::Algorithms::FlatPlateAbsorption flat;
+    flat.initialize();
+    TS_ASSERT_THROWS_NOTHING(
+        flat.setProperty<MatrixWorkspace_sptr>("InputWorkspace", testWS));
+    std::string flatWS("flat");
+    TS_ASSERT_THROWS_NOTHING(flat.setPropertyValue("OutputWorkspace", flatWS));
+    TS_ASSERT_THROWS_NOTHING(
+        flat.setPropertyValue("AttenuationXSection", "5.08"));
+    TS_ASSERT_THROWS_NOTHING(
+        flat.setPropertyValue("ScatteringXSection", "5.1"));
+    TS_ASSERT_THROWS_NOTHING(
+        flat.setPropertyValue("SampleNumberDensity", "0.07192"));
+    TS_ASSERT_THROWS_NOTHING(flat.setPropertyValue("SampleHeight", "2.3"));
+    TS_ASSERT_THROWS_NOTHING(flat.setPropertyValue("SampleWidth", "1.8"));
+    // too thin to work in any shapes gauge volume creation
+    TS_ASSERT_THROWS_NOTHING(flat.setPropertyValue("SampleThickness", ".1"));
+    TS_ASSERT_THROWS_NOTHING(flat.execute());
+    TS_ASSERT(flat.isExecuted());
+
+    // Using the output of the FlatPlateAbsorption algorithm is convenient
+    // because it adds the sample object to the workspace
+    TS_ASSERT_THROWS_NOTHING(atten.setPropertyValue("InputWorkspace", flatWS));
+    std::string outputWS("factors");
+    TS_ASSERT_THROWS_NOTHING(
+        atten.setPropertyValue("OutputWorkspace", outputWS));
+    TS_ASSERT_THROWS_NOTHING(
+        atten.setPropertyValue("AttenuationXSection", "5.08"));
+    TS_ASSERT_THROWS_NOTHING(
+        atten.setPropertyValue("ScatteringXSection", "5.1"));
+    TS_ASSERT_THROWS_NOTHING(
+        atten.setPropertyValue("SampleNumberDensity", "0.07192"));
+    atten.setRethrows(true); // needed for the next check to work
+    TS_ASSERT_THROWS(atten.execute(), std::runtime_error);
+    TS_ASSERT(!atten.isExecuted());
+
+    Mantid::API::AnalysisDataService::Instance().remove(flatWS);
+  }
+
 private:
   Mantid::Algorithms::AnyShapeAbsorption atten;
 };
diff --git a/Framework/Algorithms/test/ApplyDetailedBalanceTest.h b/Framework/Algorithms/test/ApplyDetailedBalanceTest.h
index 85a614793ff1369db1f8b0ef8867e9aed08decea..9ef20ae48ddf9aa9804bca230c2f37575703c2bf 100644
--- a/Framework/Algorithms/test/ApplyDetailedBalanceTest.h
+++ b/Framework/Algorithms/test/ApplyDetailedBalanceTest.h
@@ -89,7 +89,7 @@ public:
         alg.setPropertyValue("OutputWorkspace", outputWSname));
     TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Temperature", "x"));
     TS_ASSERT_THROWS_NOTHING(alg.execute());
-    TS_ASSERT(alg.isExecuted());
+    TS_ASSERT(!alg.isExecuted());
     Workspace2D_sptr outws;
     TS_ASSERT_THROWS_ANYTHING(
         outws = AnalysisDataService::Instance().retrieveWS<Workspace2D>(
diff --git a/Framework/Algorithms/test/BinaryOperationTest.h b/Framework/Algorithms/test/BinaryOperationTest.h
index e5b6dbe3566fa37ec766e8bdb95e383bcae71dd6..44a13b341f07609300e3acf9fb7ccf6c89d17bc7 100644
--- a/Framework/Algorithms/test/BinaryOperationTest.h
+++ b/Framework/Algorithms/test/BinaryOperationTest.h
@@ -47,6 +47,8 @@ public:
                                      const MatrixWorkspace_const_sptr ws2) {
     m_lhs = ws1;
     m_rhs = ws2;
+    m_lhsBlocksize = ws1->blocksize();
+    m_rhsBlocksize = ws2->blocksize();
     BinaryOperation::checkRequirements();
     return BinaryOperation::checkSizeCompatibility(ws1, ws2);
   }
@@ -55,17 +57,13 @@ private:
   // Unhide base class method to avoid Intel compiler warning
   using BinaryOperation::checkSizeCompatibility;
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const Mantid::MantidVec &,
-                              const Mantid::MantidVec &,
-                              const Mantid::MantidVec &,
-                              const Mantid::MantidVec &,
-                              const Mantid::MantidVec &, Mantid::MantidVec &,
-                              Mantid::MantidVec &) override {}
-  void performBinaryOperation(const Mantid::MantidVec &,
-                              const Mantid::MantidVec &,
-                              const Mantid::MantidVec &, const double,
-                              const double, Mantid::MantidVec &,
-                              Mantid::MantidVec &) override {}
+  void performBinaryOperation(const HistogramData::Histogram &,
+                              const HistogramData::Histogram &,
+                              HistogramData::HistogramY &,
+                              HistogramData::HistogramE &) override {}
+  void performBinaryOperation(const HistogramData::Histogram &, const double,
+                              const double, HistogramData::HistogramY &,
+                              HistogramData::HistogramE &) override {}
 };
 
 namespace {
diff --git a/Framework/Algorithms/test/CalculateIqtTest.h b/Framework/Algorithms/test/CalculateIqtTest.h
index 637f1f810a0c7507f88c0cbb8a9c7c94316e6e25..75c03b2a811f3c3fa4a093b4bb91aae4c3f20166 100644
--- a/Framework/Algorithms/test/CalculateIqtTest.h
+++ b/Framework/Algorithms/test/CalculateIqtTest.h
@@ -110,8 +110,8 @@ public:
     algorithm->execute();
     MatrixWorkspace_sptr outWorkspace =
         algorithm->getProperty("OutputWorkspace");
-    auto yValues = outWorkspace->y(0);
-    auto eValues = outWorkspace->e(0);
+    const auto &yValues = outWorkspace->y(0);
+    const auto &eValues = outWorkspace->e(0);
     TS_ASSERT_DELTA(yValues[0], 1, 0.0001);
     TS_ASSERT_DELTA(yValues[1], 0, 0.0001);
     TS_ASSERT_DELTA(yValues[4], 0.4831171, 0.0001);
diff --git a/Framework/Algorithms/test/CommutativeBinaryOperationTest.h b/Framework/Algorithms/test/CommutativeBinaryOperationTest.h
index 0376141d9e80352045bdec6406d52c5a2445835e..a63010961f75899e3a32376e0b5be8d3b29aee3a 100644
--- a/Framework/Algorithms/test/CommutativeBinaryOperationTest.h
+++ b/Framework/Algorithms/test/CommutativeBinaryOperationTest.h
@@ -39,6 +39,8 @@ public:
                                      const MatrixWorkspace_const_sptr ws2) {
     m_lhs = ws1;
     m_rhs = ws2;
+    m_lhsBlocksize = ws1->blocksize();
+    m_rhsBlocksize = ws2->blocksize();
     BinaryOperation::checkRequirements();
     return CommutativeBinaryOperation::checkSizeCompatibility(ws1, ws2);
   }
@@ -47,25 +49,20 @@ private:
   // Unhide base class method to avoid Intel compiler warning
   using BinaryOperation::checkSizeCompatibility;
   // Overridden BinaryOperation methods
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const MantidVec &rhsY,
-                              const MantidVec &rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override {
-    UNUSED_ARG(lhsX);
-    UNUSED_ARG(lhsY);
-    UNUSED_ARG(lhsE);
-    UNUSED_ARG(rhsY);
-    UNUSED_ARG(rhsE);
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const HistogramData::Histogram &rhs,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override {
+    UNUSED_ARG(lhs);
+    UNUSED_ARG(rhs);
     UNUSED_ARG(YOut);
     UNUSED_ARG(EOut);
   }
-  void performBinaryOperation(const MantidVec &lhsX, const MantidVec &lhsY,
-                              const MantidVec &lhsE, const double rhsY,
-                              const double rhsE, MantidVec &YOut,
-                              MantidVec &EOut) override {
-    UNUSED_ARG(lhsX);
-    UNUSED_ARG(lhsY);
-    UNUSED_ARG(lhsE);
+  void performBinaryOperation(const HistogramData::Histogram &lhs,
+                              const double rhsY, const double rhsE,
+                              HistogramData::HistogramY &YOut,
+                              HistogramData::HistogramE &EOut) override {
+    UNUSED_ARG(lhs);
     UNUSED_ARG(rhsY);
     UNUSED_ARG(rhsE);
     UNUSED_ARG(YOut);
diff --git a/Framework/Algorithms/test/CopySampleTest.h b/Framework/Algorithms/test/CopySampleTest.h
index 715e35c6b3418f760d211aba31b4506be985ea48..d24adb45a31177f39c4edd261099db9e3738bf73 100644
--- a/Framework/Algorithms/test/CopySampleTest.h
+++ b/Framework/Algorithms/test/CopySampleTest.h
@@ -47,10 +47,10 @@ public:
     const std::string envName("TestKit");
     auto canShape = ComponentCreationHelper::cappedCylinderXML(
         0.5, 1.5, V3D(0.0, 0.0, 0.0), V3D(0., 1.0, 0.), "tube");
-    SampleEnvironment *kit = new SampleEnvironment(
+    auto kit = std::make_unique<SampleEnvironment>(
         envName,
         boost::make_shared<Container>(ShapeFactory().createShape(canShape)));
-    sample.setEnvironment(kit);
+    sample.setEnvironment(std::move(kit));
     OrientedLattice *latt = new OrientedLattice(1.0, 2.0, 3.0, 90, 90, 90);
     sample.setOrientedLattice(latt);
     delete latt;
diff --git a/Framework/Algorithms/test/ExtractFFTSpectrumTest.h b/Framework/Algorithms/test/ExtractFFTSpectrumTest.h
index a21bc0765b21cf739186bb1e9dddaaf2d2067b9d..66bdd6e3f52d989873d12543ae047035c38b6e71 100644
--- a/Framework/Algorithms/test/ExtractFFTSpectrumTest.h
+++ b/Framework/Algorithms/test/ExtractFFTSpectrumTest.h
@@ -87,7 +87,7 @@ public:
     TS_ASSERT_EQUALS(inputWS->blocksize(), outputWS->blocksize());
 
     // Units ( Axis 1 should be the same, Axis 0 should be "Time/ns"
-    TS_ASSERT_EQUALS(inputWS->getAxis(1)->unit(), outputWS->getAxis(1)->unit());
+    TS_ASSERT(*inputWS->getAxis(1)->unit() == *outputWS->getAxis(1)->unit());
     TS_ASSERT_EQUALS(outputWS->getAxis(0)->unit()->caption(), "Time");
     TS_ASSERT_EQUALS(outputWS->getAxis(0)->unit()->label(), "ns");
   }
diff --git a/Framework/Algorithms/test/ExtractSpectra2Test.h b/Framework/Algorithms/test/ExtractSpectra2Test.h
index 818c857e42a70e36a878b0431a391e83cc51bf1b..b8b05a3220598cd725efbba7fea3f3a9373ee3cd 100644
--- a/Framework/Algorithms/test/ExtractSpectra2Test.h
+++ b/Framework/Algorithms/test/ExtractSpectra2Test.h
@@ -10,6 +10,7 @@
 #include <cxxtest/TestSuite.h>
 
 #include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/BinEdgeAxis.h"
 #include "MantidAlgorithms/ExtractSpectra2.h"
 #include "MantidDataObjects/Workspace2D.h"
 #include "MantidDataObjects/WorkspaceCreation.h"
@@ -125,6 +126,51 @@ public:
   }
 
   void test_parallel() { ParallelTestHelpers::runParallel(run_parallel); }
+
+  void test_BinEdgeAxis() {
+    auto input = createWorkspace();
+    BinEdgeAxis *axis = new BinEdgeAxis(input->getNumberHistograms() + 1);
+    for (size_t i = 0; i < axis->length(); ++i) {
+      axis->setValue(i, -2. + static_cast<double>(i));
+    }
+    input->replaceAxis(1, axis);
+    ExtractSpectra2 alg;
+    alg.initialize();
+    alg.setChild(true);
+    alg.setRethrows(true);
+    alg.setProperty("InputWorkspace", std::move(input));
+    alg.setProperty("InputWorkspaceIndexSet", "1-3");
+    alg.setProperty("OutputWorkspace", "out");
+    alg.execute();
+    MatrixWorkspace_sptr out = alg.getProperty("OutputWorkspace");
+    auto outAxis = out->getAxis(1);
+    TS_ASSERT_DIFFERS(dynamic_cast<BinEdgeAxis *>(outAxis), nullptr)
+    TS_ASSERT_EQUALS(outAxis->length(), 4)
+    TS_ASSERT_EQUALS((*outAxis)(0), -1.)
+    TS_ASSERT_EQUALS((*outAxis)(1), 0.)
+    TS_ASSERT_EQUALS((*outAxis)(2), 1.)
+    TS_ASSERT_EQUALS((*outAxis)(3), 2.)
+  }
+
+  void test_BinEdgeAxis_fails_with_non_contiguous_indices() {
+    auto input = createWorkspace();
+    BinEdgeAxis *axis = new BinEdgeAxis(input->getNumberHistograms() + 1);
+    for (size_t i = 0; i < axis->length(); ++i) {
+      axis->setValue(i, -2. + static_cast<double>(i));
+    }
+    input->replaceAxis(1, axis);
+    ExtractSpectra2 alg;
+    alg.initialize();
+    alg.setChild(true);
+    alg.setRethrows(true);
+    alg.setProperty("InputWorkspace", std::move(input));
+    alg.setProperty("InputWorkspaceIndexSet", "1,3");
+    alg.setProperty("OutputWorkspace", "out");
+    TS_ASSERT_THROWS_EQUALS(
+        alg.execute(), const std::invalid_argument &e, e.what(),
+        std::string("Cannot extract non-contiguous set of spectra when the "
+                    "vertical axis has bin edges."))
+  }
 };
 
 #endif /* MANTID_ALGORITHMS_EXTRACTSPECTRA2TEST_H_ */
diff --git a/Framework/Algorithms/test/GeneralisedSecondDifferenceTest.h b/Framework/Algorithms/test/GeneralisedSecondDifferenceTest.h
index 1b33bb12560eb4181d71dc01f9eaad8becdcb255..489a8618bf908da4f8e0c4ac5d951af688135233 100644
--- a/Framework/Algorithms/test/GeneralisedSecondDifferenceTest.h
+++ b/Framework/Algorithms/test/GeneralisedSecondDifferenceTest.h
@@ -60,11 +60,11 @@ public:
     TS_ASSERT_EQUALS(outWs->getNumberHistograms(), 1);
     TS_ASSERT_EQUALS(outWs->blocksize(), 4);
 
-    auto x1 = outWs->x(0);
+    const auto &x1 = outWs->x(0);
     TS_ASSERT_EQUALS(x1[0], 3);
     TS_ASSERT_EQUALS(x1[3], 6);
 
-    auto y1 = outWs->y(0);
+    const auto &y1 = outWs->y(0);
     TS_ASSERT_DELTA(y1[1], -7.0300, 0.0001);
     TS_ASSERT_DELTA(y1[2], -20.0000, 0.0001);
 
diff --git a/Framework/Algorithms/test/GeneratePeaksTest.h b/Framework/Algorithms/test/GeneratePeaksTest.h
index 9dde5e2c4175dd3638ee22e20da2fd4a02a5b920..eed0c6130fc73a21a39249c85f9c6c1e6682d9bf 100644
--- a/Framework/Algorithms/test/GeneratePeaksTest.h
+++ b/Framework/Algorithms/test/GeneratePeaksTest.h
@@ -237,8 +237,8 @@ public:
     TS_ASSERT_EQUALS(peaksws->getNumberHistograms(), 2);
 
     // peak 0:
-    auto p0_x = peaksws->x(0);
-    auto p0_y = peaksws->y(0);
+    const auto &p0_x = peaksws->x(0);
+    const auto &p0_y = peaksws->y(0);
     TS_ASSERT_DELTA(p0_x[200], 2.0, 1.0E-8);
     TS_ASSERT_DELTA(p0_y[200], 5.0, 1.0E-4);
 
@@ -250,8 +250,8 @@ public:
     TS_ASSERT_DELTA(p0_y[800], 10.0, 1.0E-4);
 
     // peak 2:
-    auto p1_x = peaksws->x(1);
-    auto p1_y = peaksws->y(1);
+    const auto &p1_x = peaksws->x(1);
+    const auto &p1_y = peaksws->y(1);
     TS_ASSERT_DELTA(p1_x[400], 4.0, 1.0E-8);
     TS_ASSERT_DELTA(p1_y[400], 20.0, 1.0E-4);
 
@@ -311,8 +311,8 @@ public:
     TS_ASSERT_EQUALS(peaksws->getNumberHistograms(), 5);
 
     // Peak 0:
-    auto p0_x = peaksws->x(0);
-    auto p0_y = peaksws->y(0);
+    const auto &p0_x = peaksws->x(0);
+    const auto &p0_y = peaksws->y(0);
     TS_ASSERT_DELTA(p0_x[50], 2.0, 1.0E-8);
     TS_ASSERT_DELTA(p0_y[50], 5.0, 1.0E-4);
 
@@ -324,8 +324,8 @@ public:
     TS_ASSERT_DELTA(p0_y[350], 10.0, 1.0E-4);
 
     // Peak 2:
-    auto p1_x = peaksws->x(2);
-    auto p1_y = peaksws->y(2);
+    const auto &p1_x = peaksws->x(2);
+    const auto &p1_y = peaksws->y(2);
     TS_ASSERT_DELTA(p1_x[150], 4.0, 1.0E-8);
     TS_ASSERT_DELTA(p1_y[150], 20.0, 1.0E-4);
 
@@ -385,8 +385,8 @@ public:
     TS_ASSERT_EQUALS(peaksws->getNumberHistograms(), 2);
 
     // peak 0:
-    auto p0_x = peaksws->x(0);
-    auto p0_y = peaksws->y(0);
+    const auto &p0_x = peaksws->x(0);
+    const auto &p0_y = peaksws->y(0);
     TS_ASSERT_DELTA(p0_x[200], 2.0, 1.0E-8);
     TS_ASSERT_DELTA(p0_y[200], 10.0, 1.0E-4);
 
@@ -395,8 +395,8 @@ public:
     TS_ASSERT_DELTA(p0_y[800], 20.0, 1.0E-4);
 
     // peak 2:
-    auto p1_x = peaksws->x(1);
-    auto p1_y = peaksws->y(1);
+    const auto &p1_x = peaksws->x(1);
+    const auto &p1_y = peaksws->y(1);
     TS_ASSERT_DELTA(p1_x[400], 4.0, 1.0E-8);
     TS_ASSERT_DELTA(p1_y[400], 24.0, 1.0E-4);
 
@@ -455,8 +455,8 @@ public:
     TS_ASSERT_EQUALS(peaksws->getNumberHistograms(), 1);
 
     // peak 0:
-    auto p0_x = peaksws->x(0);
-    auto p0_y = peaksws->y(0);
+    const auto &p0_x = peaksws->x(0);
+    const auto &p0_y = peaksws->y(0);
     TS_ASSERT_DELTA(p0_x[200], 2.0, 1.0E-8);
     TS_ASSERT_DELTA(p0_y[200], 5.0, 1.0E-4);
 
@@ -514,8 +514,8 @@ public:
     TS_ASSERT_EQUALS(peaksws->getNumberHistograms(), 1);
 
     // peak 0:
-    auto p0_x = peaksws->x(0);
-    auto p0_y = peaksws->y(0);
+    const auto &p0_x = peaksws->x(0);
+    const auto &p0_y = peaksws->y(0);
     TS_ASSERT_DELTA(p0_x[200], 2.0, 1.0E-8);
     TS_ASSERT_DELTA(p0_y[200], 5.0, 1.0E-4);
 
diff --git a/Framework/Algorithms/test/HyspecScharpfCorrectionTest.h b/Framework/Algorithms/test/HyspecScharpfCorrectionTest.h
index 639612d6027baf39924dd86fe9a0e7974810ad12..1b8e1c60231f4dba5b8d797386bb19b161d0f4b9 100644
--- a/Framework/Algorithms/test/HyspecScharpfCorrectionTest.h
+++ b/Framework/Algorithms/test/HyspecScharpfCorrectionTest.h
@@ -53,7 +53,7 @@ public:
     TS_ASSERT(outputWS);
     auto histo = outputWS->histogram(0);
     auto x = histo.points();
-    auto y = histo.y();
+    const auto &y = histo.y();
     for (size_t i = 0; i < x.size(); ++i) {
       if (x[i] < 4) {
         TS_ASSERT_LESS_THAN(y[i], 0);
diff --git a/Framework/Algorithms/test/IndirectFitDataCreationHelperTest.h b/Framework/Algorithms/test/IndirectFitDataCreationHelperTest.h
index d110ed764ef2b88f621fe0a397ba83f9936f73c2..60fa576cdbaaa8094d8f3ed38e5e822b79fa1cf9 100644
--- a/Framework/Algorithms/test/IndirectFitDataCreationHelperTest.h
+++ b/Framework/Algorithms/test/IndirectFitDataCreationHelperTest.h
@@ -12,6 +12,10 @@ using namespace Mantid::IndirectFitDataCreationHelper;
 
 namespace {
 
+std::vector<std::string> getTextAxisLabels() {
+  return {"f0.Width", "f1.Width", "f2.EISF"};
+}
+
 void storeWorkspaceInADS(std::string const &workspaceName,
                          MatrixWorkspace_sptr workspace) {
   SetUpADSWithWorkspace ads(workspaceName, workspace);
@@ -63,6 +67,12 @@ public:
 
   void tearDown() override { AnalysisDataService::Instance().clear(); }
 
+  void test_that_the_constant_variables_have_the_values_expected() {
+    TS_ASSERT_EQUALS(START_X_COLUMN, 2);
+    TS_ASSERT_EQUALS(END_X_COLUMN, 3);
+    TS_ASSERT_EQUALS(EXCLUDE_REGION_COLUMN, 4);
+  }
+
   void
   test_that_createWorkspace_returns_a_workspace_with_the_number_of_spectra_specified() {
     auto const workspace = createWorkspace(10);
@@ -77,6 +87,31 @@ public:
     TS_ASSERT_EQUALS(workspace->getNumberHistograms(), 6);
   }
 
+  void
+  test_that_createWorkspaceWithTextAxis_returns_a_workspace_with_the_number_of_spectra_specified() {
+    auto const workspace = createWorkspaceWithTextAxis(3, getTextAxisLabels());
+    TS_ASSERT(workspace);
+    TS_ASSERT_EQUALS(workspace->getNumberHistograms(), 3);
+  }
+
+  void
+  test_that_createWorkspaceWithTextAxis_returns_a_workspace_with_the_text_axis_labels_specified() {
+    auto const labels = getTextAxisLabels();
+    auto const workspace = createWorkspaceWithTextAxis(3, labels);
+
+    auto const yAxis = workspace->getAxis(1);
+
+    for (auto index = 0u; index < workspace->getNumberHistograms(); ++index)
+      TS_ASSERT_EQUALS(yAxis->label(index), labels[index]);
+  }
+
+  void
+  test_that_createWorkspaceWithTextAxis_throws_when_the_number_of_spectra_is_not_equal_to_the_number_of_labels() {
+    auto const labels = std::vector<std::string>({"f0.Width", "f1.EISF"});
+    TS_ASSERT_THROWS(createWorkspaceWithTextAxis(6, labels),
+                     std::runtime_error);
+  }
+
   void
   test_that_setWorkspaceEFixed_does_not_throw_when_setting_EFixed_values() {
     auto workspace = createInstrumentWorkspace(6, 5);
diff --git a/Framework/Algorithms/test/IntegrateEPPTest.h b/Framework/Algorithms/test/IntegrateEPPTest.h
index c6a3eb9dc806d965f0c70237d09f3f1e2ba5f075..08049c8b408bdabbc8b6c0c0e3daad41fbfc9d63 100644
--- a/Framework/Algorithms/test/IntegrateEPPTest.h
+++ b/Framework/Algorithms/test/IntegrateEPPTest.h
@@ -58,8 +58,8 @@ public:
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), nHist)
     TS_ASSERT_EQUALS(outputWS->blocksize(), 1)
     for (size_t i = 0; i < outputWS->getNumberHistograms(); ++i) {
-      const auto ys = outputWS->y(i);
-      const auto xs = outputWS->x(i);
+      const auto &ys = outputWS->y(i);
+      const auto &xs = outputWS->x(i);
       TS_ASSERT_EQUALS(ys[0], 2.0 * static_cast<double>(i))
       TS_ASSERT_EQUALS(xs[0], 2.5)
       TS_ASSERT_EQUALS(xs[1], 4.5)
diff --git a/Framework/Algorithms/test/MayersSampleCorrectionStrategyTest.h b/Framework/Algorithms/test/MayersSampleCorrectionStrategyTest.h
index 6ce3152c5a877e52f3e1b7e540d55762228728e0..29f1e88eaeccea1fd121708615766b73aa0b6dba 100644
--- a/Framework/Algorithms/test/MayersSampleCorrectionStrategyTest.h
+++ b/Framework/Algorithms/test/MayersSampleCorrectionStrategyTest.h
@@ -121,9 +121,9 @@ public:
 
     auto outHisto = mscat.getCorrectedHisto();
 
-    auto tof = outHisto.x();
-    auto signal = outHisto.y();
-    auto error = outHisto.e();
+    const auto &tof = outHisto.x();
+    const auto &signal = outHisto.y();
+    const auto &error = outHisto.e();
 
     // Check some values
     const double delta(1e-06);
@@ -147,9 +147,9 @@ public:
     MayersSampleCorrectionStrategy mscat(corrPars, histo);
 
     const auto outHisto = mscat.getCorrectedHisto();
-    const auto tof = outHisto.x();
-    const auto signal = outHisto.y();
-    const auto error = outHisto.e();
+    const auto &tof = outHisto.x();
+    const auto &signal = outHisto.y();
+    const auto &error = outHisto.e();
 
     // Check some values
     // Check some values
@@ -174,9 +174,9 @@ public:
     MayersSampleCorrectionStrategy mscat(corrPars, histo);
 
     const auto outHisto = mscat.getCorrectedHisto();
-    const auto tof = outHisto.x();
-    const auto signal = outHisto.y();
-    const auto error = outHisto.e();
+    const auto &tof = outHisto.x();
+    const auto &signal = outHisto.y();
+    const auto &error = outHisto.e();
 
     // Check some values
     // Check some values
diff --git a/Framework/Algorithms/test/MonteCarloAbsorptionTest.h b/Framework/Algorithms/test/MonteCarloAbsorptionTest.h
index b43a162c130c33a6ba6f84a06cddfe2f3ea35a1b..3e6db57c1ef7e59dee5cc0c7f9c59d15516fd0a7 100644
--- a/Framework/Algorithms/test/MonteCarloAbsorptionTest.h
+++ b/Framework/Algorithms/test/MonteCarloAbsorptionTest.h
@@ -65,8 +65,8 @@ void addSample(Mantid::API::MatrixWorkspace_sptr ws,
     canShape->setMaterial(Material(
         "CanMaterial", PhysicalConstants::getNeutronAtom(26, 0), 0.01));
     auto can = boost::make_shared<Container>(canShape);
-    SampleEnvironment *env = new SampleEnvironment("can", can);
-    ws->mutableSample().setEnvironment(env);
+    ws->mutableSample().setEnvironment(
+        std::make_unique<SampleEnvironment>("can", can));
   } else if (environment == Environment::UserBeamSize) {
     auto inst = ws->getInstrument();
     auto &pmap = ws->instrumentParameters();
diff --git a/Framework/Algorithms/test/MonteCarloTesting.h b/Framework/Algorithms/test/MonteCarloTesting.h
index 5d2b3f860bcedfca9a392f7563e18821aa6e791e..4877acb2b9d95a614bf59b9f15faf6ad325ebf1d 100644
--- a/Framework/Algorithms/test/MonteCarloTesting.h
+++ b/Framework/Algorithms/test/MonteCarloTesting.h
@@ -14,9 +14,7 @@
 #include "MantidKernel/Material.h"
 #include "MantidKernel/PseudoRandomNumberGenerator.h"
 #include "MantidKernel/WarningSuppressions.h"
-#include "MantidKernel/make_unique.h"
 #include "MantidTestHelpers/ComponentCreationHelper.h"
-
 #include <boost/make_shared.hpp>
 #include <gmock/gmock.h>
 
@@ -105,7 +103,7 @@ inline Mantid::API::Sample createSamplePlusContainer() {
   }
   auto can = boost::make_shared<Container>(canShape);
   auto environment =
-      Mantid::Kernel::make_unique<SampleEnvironment>("Annulus Container", can);
+      std::make_unique<SampleEnvironment>("Annulus Container", can);
   // Sample volume
   auto sampleCell = ComponentCreationHelper::createCappedCylinder(
       innerRadius, height, centre, upAxis, "sample");
@@ -118,7 +116,7 @@ inline Mantid::API::Sample createSamplePlusContainer() {
   // Sample object
   Sample testSample;
   testSample.setShape(sampleCell);
-  testSample.setEnvironment(environment.release());
+  testSample.setEnvironment(std::move(environment));
   return testSample;
 }
 
diff --git a/Framework/Algorithms/test/MultiplyDivideTest.in.h b/Framework/Algorithms/test/MultiplyDivideTest.in.h
index ff0c553f96827ad10b81b14dfc07332bfdda3db6..48c658ccc517362f260fc02e760a0d4db5823aec 100644
--- a/Framework/Algorithms/test/MultiplyDivideTest.in.h
+++ b/Framework/Algorithms/test/MultiplyDivideTest.in.h
@@ -15,7 +15,6 @@
 #include "MantidAlgorithms/Divide.h"
 #include "MantidAlgorithms/Multiply.h"
 #include "MantidAPI/AnalysisDataService.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidDataObjects/Workspace2D.h"
 #include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/WorkspaceProperty.h"
@@ -97,19 +96,19 @@ public:
     if (DO_DIVIDE)
     {
       a /= 5;
-      TS_ASSERT_EQUALS(a->readY(0)[0], 0.6)
+      TS_ASSERT_EQUALS(a->y(0)[0], 0.6)
       TS_ASSERT_EQUALS(a,b);
       a /= c;
-      TS_ASSERT_EQUALS(a->readY(0)[0], 0.3);
+      TS_ASSERT_EQUALS(a->y(0)[0], 0.3);
       TS_ASSERT_EQUALS(a,b);
     }
     else
     {
       a *= 5;
-      TS_ASSERT_EQUALS(a->readY(0)[0],15.0)
+      TS_ASSERT_EQUALS(a->y(0)[0],15.0)
       TS_ASSERT_EQUALS(a,b);
       a *= c;
-      TS_ASSERT_EQUALS(a->readY(0)[0],30.0);
+      TS_ASSERT_EQUALS(a->y(0)[0],30.0);
       TS_ASSERT_EQUALS(a,b);
     }
   }
@@ -175,11 +174,11 @@ public:
     alg.execute();
     MatrixWorkspace_sptr outWS = alg.getProperty("OutputWorkspace");
     TS_ASSERT_EQUALS(outWS->getNumberHistograms(), nHist);
-    TS_ASSERT_EQUALS(outWS->readY(0)[0], 1.0);
-    TS_ASSERT_EQUALS(outWS->readY(1)[0], 1.0);
-    TS_ASSERT_EQUALS(outWS->readY(2)[0], 1.0);
-    TS_ASSERT_EQUALS(outWS->readY(3)[0], 1.0);
-    TS_ASSERT_EQUALS(outWS->readY(4)[0], 0.0);
+    TS_ASSERT_EQUALS(outWS->y(0)[0], 1.0);
+    TS_ASSERT_EQUALS(outWS->y(1)[0], 1.0);
+    TS_ASSERT_EQUALS(outWS->y(2)[0], 1.0);
+    TS_ASSERT_EQUALS(outWS->y(3)[0], 1.0);
+    TS_ASSERT_EQUALS(outWS->y(4)[0], 0.0);
     }
   }
 
@@ -233,9 +232,9 @@ public:
       work_out3 = value/work_in2;
       // checkData won't work on this one, do a few checks here
       TS_ASSERT_EQUALS( work_out3->size(), work_in2->size() );
-      TS_ASSERT_EQUALS( work_out3->readX(1), work_in2->readX(1) );
-      TS_ASSERT_DELTA( work_out3->readY(2)[6], 0.6,  0.0001 );
-      TS_ASSERT_DELTA( work_out3->readE(3)[4], 0.48, 0.0001 );
+      TS_ASSERT_EQUALS( work_out3->x(1), work_in2->x(1) );
+      TS_ASSERT_DELTA( work_out3->y(2)[6], 0.6,  0.0001 );
+      TS_ASSERT_DELTA( work_out3->e(3)[4], 0.48, 0.0001 );
     }
     else
     {
@@ -438,10 +437,6 @@ public:
       performTest(work_in1,work_in2, true, 4.0, sqrt(12.0), false, false, true);
   }
 
-
-
-
-
   void test_Event_2DSingleSpectrum()
   {
     MatrixWorkspace_sptr work_in1 = eventWS_5x10_50;
@@ -621,13 +616,13 @@ public:
     MatrixWorkspace_sptr work_in1 = WorkspaceCreationHelper::createGroupedEventWorkspace(lhs, 10, 1.0);
     if (lhs2D)
       work_in1 = EventWorkspaceHelpers::convertEventTo2D(work_in1);
-    TS_ASSERT_DELTA( work_in1->readE(0)[0], sqrt( double(lhs_grouping*1.0) ), 1e-5);
+    TS_ASSERT_DELTA( work_in1->e(0)[0], sqrt( double(lhs_grouping*1.0) ), 1e-5);
 
     // Grouped workspace will have rhs_grouping events in each bin (also).
     MatrixWorkspace_sptr work_in2 = WorkspaceCreationHelper::createGroupedEventWorkspace(rhs, 10, 1.0);
     if (rhs2D)
       work_in2 = EventWorkspaceHelpers::convertEventTo2D(work_in2);
-    TS_ASSERT_DELTA( work_in2->readE(0)[0], sqrt( double(rhs_grouping*1.0) ), 1e-5);
+    TS_ASSERT_DELTA( work_in2->e(0)[0], sqrt( double(rhs_grouping*1.0) ), 1e-5);
 
     if (DO_DIVIDE)
       performTest(work_in1,work_in2, !lhs2D, divideValue, divideError, true);
@@ -700,7 +695,7 @@ public:
     else
       mess << "2D";
     mess << "(" << ws->getNumberHistograms() << " spectra," << ws->blocksize() << " bins,";
-    mess << "Y[0][0] = " << ws->readY(0)[0] << ")";
+    mess << "Y[0][0] = " << ws->y(0)[0] << ")";
     return mess.str();
   }
 
@@ -877,20 +872,16 @@ public:
       bool breakOut=false;
       std::string  dummy;
       for (size_t wi=0; wi < work_out1->getNumberHistograms(); wi++) {
-        const auto &xIn = work_in1->readX(wi);
-        const auto &xOut = work_out1->readX(wi);
-        const auto &yOut = work_out1->readY(wi);
-        const auto &eOut = work_out1->readE(wi);
+        const auto &xIn = work_in1->x(wi);
+        const auto &xOut = work_out1->x(wi);
+        const auto &yOut = work_out1->y(wi);
+        const auto &eOut = work_out1->e(wi);
         for (size_t i=0; i < yOut.size(); i++) {
-          //std::ostringstream mess;
-          //mess << message << ", evaluated at wi " << wi << ", i " << i;
           TS_ASSERT_DELTA(xIn[i], xOut[i], 0.0001);
           const double sig3 = yOut[i];
           const double err3 = eOut[i];
           TS_ASSERT_DELTA(sig3, expectedValue, 0.0001);
           TS_ASSERT_DELTA(err3, expectedError, 0.0001);
-          //TSM_ASSERT_DELTA(mess.str(), sig3, expectedValue, 0.0001);
-          //TSM_ASSERT_DELTA(mess.str(), err3, expectedError, 0.0001);
           if (fabs(err3 - expectedError) > 0.001)
           {
             breakOut=true;
@@ -907,21 +898,16 @@ public:
   bool checkDataItem (const MatrixWorkspace_sptr work_in1,  const MatrixWorkspace_sptr work_in2, const MatrixWorkspace_sptr work_out1,
       size_t i, size_t ws2Index)
   {
-    // Avoid going out of bounds! For some of the grouped ones
-//    if (i/work_in1->blocksize() >= work_in1->getNumberHistograms())
-//      return true;
-//    if (ws2Index/work_in2->blocksize() >= work_in2->getNumberHistograms())
-//      return true;
-    double sig1 = work_in1->readY(i/work_in1->blocksize())[i%work_in1->blocksize()];
-    double sig2 = work_in2->readY(ws2Index/work_in2->blocksize())[ws2Index%work_in2->blocksize()];
-    double sig3 = work_out1->readY(i/work_in1->blocksize())[i%work_in1->blocksize()];
+    double sig1 = work_in1->y(i/work_in1->blocksize())[i%work_in1->blocksize()];
+    double sig2 = work_in2->y(ws2Index/work_in2->blocksize())[ws2Index%work_in2->blocksize()];
+    double sig3 = work_out1->y(i/work_in1->blocksize())[i%work_in1->blocksize()];
 
-    TS_ASSERT_DELTA(work_in1->readX(i/work_in1->blocksize())[i%work_in1->blocksize()],
-        work_out1->readX(i/work_in1->blocksize())[i%work_in1->blocksize()], 0.0001);
+    TS_ASSERT_DELTA(work_in1->x(i/work_in1->blocksize())[i%work_in1->blocksize()],
+        work_out1->x(i/work_in1->blocksize())[i%work_in1->blocksize()], 0.0001);
 
-    double err1 = work_in1->readE(i/work_in1->blocksize())[i%work_in1->blocksize()];
-    double err2 = work_in2->readE(ws2Index/work_in2->blocksize())[ws2Index%work_in2->blocksize()];
-    double err3 = work_out1->readE(i/work_in1->blocksize())[i%work_in1->blocksize()];
+    double err1 = work_in1->e(i/work_in1->blocksize())[i%work_in1->blocksize()];
+    double err2 = work_in2->e(ws2Index/work_in2->blocksize())[ws2Index%work_in2->blocksize()];
+    double err3 = work_out1->e(i/work_in1->blocksize())[i%work_in1->blocksize()];
 
     double expectValue, expectError;
     //Compute the expectation
@@ -942,9 +928,6 @@ public:
     return (diff < 0.0001);
   }
 
-
-
-
   void doDivideWithMaskedTest(bool replaceInput)
   {
     const int nHist = 10,nBins=20;
@@ -1003,7 +986,7 @@ public:
       else
       {
         TS_ASSERT_EQUALS(spectrumInfo.isMasked(i), true);
-        double yValue = output->readY(i)[0];
+        double yValue = output->y(i)[0];
         TS_ASSERT_EQUALS(yValue, yValue );
         TS_ASSERT( !std::isinf(yValue) );
       }
@@ -1015,5 +998,33 @@ public:
 
 };
 
+//============================================================================
+/** Performance test with large workspaces. */
+
+class @MULTIPLYDIVIDETEST_CLASS@Performance : public CxxTest::TestSuite
+{
+  Workspace2D_sptr m_ws2D_1, m_ws2D_2;
+
+public:
+  static @MULTIPLYDIVIDETEST_CLASS@Performance *createSuite() { return new @MULTIPLYDIVIDETEST_CLASS@Performance(); }
+  static void destroySuite( @MULTIPLYDIVIDETEST_CLASS@Performance *suite ) { delete suite; }
+
+  void setUp() override
+  {
+    constexpr int histograms{100000};
+    constexpr int bins{1000};
+    m_ws2D_1 = WorkspaceCreationHelper::create2DWorkspace(histograms, bins);
+    m_ws2D_2 = WorkspaceCreationHelper::create2DWorkspace(histograms, bins);
+  }
 
+  void test_large_2D()
+  {
+    constexpr bool doDivide{@MULTIPLYDIVIDETEST_DO_DIVIDE@};
+    if (doDivide) {
+      MatrixWorkspace_sptr out = m_ws2D_1 / m_ws2D_2;
+    } else {
+      MatrixWorkspace_sptr out = m_ws2D_1 * m_ws2D_2;
+    }
+  }
+};
 #endif /*MULTIPLYTEST_H_ or DIVIDETEST_H_*/
diff --git a/Framework/Algorithms/test/PDFFourierTransformTest.h b/Framework/Algorithms/test/PDFFourierTransformTest.h
index f1b45291b87dc65a7ecef9d94c3573057219bcf0..10195d27e3a6f4aba55c959261b7f2ee887b2699 100644
--- a/Framework/Algorithms/test/PDFFourierTransformTest.h
+++ b/Framework/Algorithms/test/PDFFourierTransformTest.h
@@ -121,8 +121,8 @@ public:
     DataObjects::Workspace2D_sptr pdfws =
         boost::dynamic_pointer_cast<DataObjects::Workspace2D>(
             API::AnalysisDataService::Instance().retrieve("PDFGofR"));
-    const auto R = pdfws->x(0);
-    const auto GofR = pdfws->y(0);
+    const auto &R = pdfws->x(0);
+    const auto &GofR = pdfws->y(0);
 
     TS_ASSERT_DELTA(R[0], 0.01, 0.0001);
     TS_ASSERT_DELTA(R[249], 2.5, 0.0001);
@@ -155,8 +155,8 @@ public:
     DataObjects::Workspace2D_sptr pdfws =
         boost::dynamic_pointer_cast<DataObjects::Workspace2D>(
             API::AnalysisDataService::Instance().retrieve("PDFGofR"));
-    const auto R = pdfws->x(0);
-    const auto GofR = pdfws->y(0);
+    const auto &R = pdfws->x(0);
+    const auto &GofR = pdfws->y(0);
 
     TS_ASSERT_DELTA(R[0], 0.01, 0.0001);
     TS_ASSERT_DELTA(R[249], 2.5, 0.0001);
diff --git a/Framework/Algorithms/test/PlusMinusTest.in.h b/Framework/Algorithms/test/PlusMinusTest.in.h
index 6e337f3c1a2226e6a7dea87f36a11f8d5c3f765e..a2511aceacb3f1700c106d2b4d17ed97be513867 100644
--- a/Framework/Algorithms/test/PlusMinusTest.in.h
+++ b/Framework/Algorithms/test/PlusMinusTest.in.h
@@ -15,7 +15,6 @@
 #include "MantidAlgorithms/Plus.h"
 #include "MantidAlgorithms/Rebin.h"
 #include "MantidAPI/AnalysisDataService.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidDataObjects/Workspace2D.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidAPI/WorkspaceOpOverloads.h"
@@ -78,14 +77,10 @@ public:
     delete alg;
   }
 
-
-
-
   //====================================================================================
   //====================================================================================
   //====================================================================================
 
-
   void test_CompoundAssignment()
   {
     Workspace2D_sptr a = WorkspaceCreationHelper::create2DWorkspace(5,5);
@@ -94,19 +89,19 @@ public:
     if (DO_PLUS)
     {
       a += 5;
-      TS_ASSERT_EQUALS(a->readY(0)[0],7);
+      TS_ASSERT_EQUALS(a->y(0)[0],7);
       TS_ASSERT_EQUALS(a,b);
       a += c;
-      TS_ASSERT_EQUALS(a->readY(0)[0],9);
+      TS_ASSERT_EQUALS(a->y(0)[0],9);
       TS_ASSERT_EQUALS(a,b);
     }
     else
     {
       a -= 5;
-      TS_ASSERT_EQUALS(a->readY(0)[0],-3);
+      TS_ASSERT_EQUALS(a->y(0)[0],-3);
       TS_ASSERT_EQUALS(a,b);
       a -= c;
-      TS_ASSERT_EQUALS(a->readY(0)[0],-5);
+      TS_ASSERT_EQUALS(a->y(0)[0],-5);
       TS_ASSERT_EQUALS(a,b);
     }
   }
@@ -154,14 +149,10 @@ public:
     }
   }
 
-
-
-
   //====================================================================================
   //====================================================================================
   //====================================================================================
 
-
   void test_1D_1D()
   {
     MatrixWorkspace_sptr work_in1 = fibWS1d;
@@ -269,7 +260,7 @@ public:
       MatrixWorkspace_sptr work_out3 = value-work_in2;
       // checkData won't work on this one, do a few checks here
       TS_ASSERT_EQUALS( work_out3->size(), work_in2->size() );
-      TS_ASSERT_EQUALS( work_out3->readX(1), work_in2->readX(1) );
+      TS_ASSERT_EQUALS( work_out3->x(1), work_in2->x(1) );
       TS_ASSERT_EQUALS( work_out3->y(2)[6], 3.0 );
       TS_ASSERT_EQUALS( work_out3->e(3)[4], 4.0 );
     }
@@ -473,9 +464,6 @@ public:
     }
   }
 
-
-
-
   void test_Event_Event()
   {
     MatrixWorkspace_sptr work_in1 = eventWS_5x10_50;
@@ -507,7 +495,6 @@ public:
     performTest_fails(work_in1,work_in2, false);
   }
 
-
   void test_EventWithASingleBin_EventWithASingleBin()
   {
     for(int inplace=0; inplace<2;inplace++)
@@ -601,15 +588,8 @@ public:
     }
   }
 
-
-
-
-
-
-
   //============================================================================
 
-
   std::string describe_workspace(const MatrixWorkspace_sptr ws)
   {
     std::ostringstream mess;
@@ -619,7 +599,7 @@ public:
     else
       mess << "2D";
     mess << "(" << ws->getNumberHistograms() << " spectra," << ws->blocksize() << " bins,";
-    mess << "Y[0][0] = " << ws->readY(0)[0] << ")";
+    mess << "Y[0][0] = " << ws->y(0)[0] << ")";
     return mess.str();
   }
 
@@ -725,7 +705,6 @@ public:
         TSM_ASSERT( message, ews_out);
         // The # of events is equal to the sum of the original amount
         TSM_ASSERT_EQUALS( message, ews_out->getNumberEvents(), numEvents1 + numEvents2 );
-        std::cout << ews_out->readY(0)[0] << " after adding (Y\n";
       }
       else
       {
@@ -881,24 +860,19 @@ public:
   bool checkDataItem (const MatrixWorkspace_sptr work_in1,  const MatrixWorkspace_sptr work_in2, const MatrixWorkspace_sptr work_out1,
       size_t i, size_t ws2Index)
   {
-    // Avoid going out of bounds! For some of the grouped ones
-//    if (i/work_in1->blocksize() >= work_in1->getNumberHistograms())
-//      return true;
-//    if (ws2Index/work_in2->blocksize() >= work_in2->getNumberHistograms())
-//      return true;
     const size_t work_in1_blksize = work_in1->blocksize();
     const size_t work_in2_blksize = work_in2->blocksize();
 
-    const double sig1 = work_in1->readY(i/work_in1_blksize)[i%work_in1_blksize];
-    const double sig2 = work_in2->readY(ws2Index/work_in2_blksize)[ws2Index%work_in2_blksize];
-    const double sig3 = work_out1->readY(i/work_in1_blksize)[i%work_in1_blksize];
+    const double sig1 = work_in1->y(i/work_in1_blksize)[i%work_in1_blksize];
+    const double sig2 = work_in2->y(ws2Index/work_in2_blksize)[ws2Index%work_in2_blksize];
+    const double sig3 = work_out1->y(i/work_in1_blksize)[i%work_in1_blksize];
 
-    TS_ASSERT_DELTA(work_in1->readX(i/work_in1_blksize)[i%work_in1_blksize],
-        work_out1->readX(i/work_in1_blksize)[i%work_in1_blksize], 0.0001);
+    TS_ASSERT_DELTA(work_in1->x(i/work_in1_blksize)[i%work_in1_blksize],
+        work_out1->x(i/work_in1_blksize)[i%work_in1_blksize], 0.0001);
 
-    const double err1 = work_in1->readE(i/work_in1_blksize)[i%work_in1_blksize];
-    const double err2 = work_in2->readE(ws2Index/work_in2_blksize)[ws2Index%work_in2_blksize];
-    const double err3 = work_out1->readE(i/work_in1_blksize)[i%work_in1_blksize];
+    const double err1 = work_in1->e(i/work_in1_blksize)[i%work_in1_blksize];
+    const double err2 = work_in2->e(ws2Index/work_in2_blksize)[ws2Index%work_in2_blksize];
+    const double err3 = work_out1->e(i/work_in1_blksize)[i%work_in1_blksize];
 
     double expectValue;
     //Compute the expectation
@@ -917,35 +891,6 @@ public:
     return (diff < 0.0001);
   }
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
   int numBins;
   int numPixels;
   std::string wsName_EW, wsName_2D, wsNameOut;
@@ -961,8 +906,6 @@ public:
       int outputWorkspaceWillBe = 0
       )
   {
-    //lhs->setName("MinusTest_lhs");
-    //rhs->setName("MinusTest_rhs");
     switch (outputWorkspaceWillBe)
     {
     case 0:
@@ -1148,27 +1091,28 @@ public:
 
 class @PLUSMINUSTEST_CLASS@Performance : public CxxTest::TestSuite
 {
-  bool DO_PLUS;
-  Workspace2D_sptr ws2D_1, ws2D_2;
+  Workspace2D_sptr m_ws2D_1, m_ws2D_2;
 
 public:
   static @PLUSMINUSTEST_CLASS@Performance *createSuite() { return new @PLUSMINUSTEST_CLASS@Performance(); }
   static void destroySuite( @PLUSMINUSTEST_CLASS@Performance *suite ) { delete suite; }
 
-  @PLUSMINUSTEST_CLASS@Performance()
-  {
-    DO_PLUS = @PLUSMINUSTEST_DO_PLUS@;
-  }
-  
   void setUp() override
   {
-  	ws2D_1 = WorkspaceCreationHelper::create2DWorkspace(10000 /*histograms*/, 1000/*bins*/);
-   	ws2D_2 = WorkspaceCreationHelper::create2DWorkspace(10000 /*histograms*/, 1000/*bins*/);
+    constexpr int histograms{100000};
+    constexpr int bins{1000};
+    m_ws2D_1 = WorkspaceCreationHelper::create2DWorkspace(histograms, bins);
+    m_ws2D_2 = WorkspaceCreationHelper::create2DWorkspace(histograms, bins);
   }
   
   void test_large_2D()
   {
-  	MatrixWorkspace_sptr out = ws2D_1 * ws2D_2;
+    constexpr bool doPlus{@PLUSMINUSTEST_DO_PLUS@};
+    if (doPlus) {
+      MatrixWorkspace_sptr out = m_ws2D_1 + m_ws2D_2;
+    } else {
+      MatrixWorkspace_sptr out = m_ws2D_1 - m_ws2D_2;
+    }
   }
 
 }; // end of class @PLUSMINUSTEST_CLASS@Performance
diff --git a/Framework/Algorithms/test/PoissonErrorsTest.h b/Framework/Algorithms/test/PoissonErrorsTest.h
index cd8e2311e24963bb7a60a5fcf54ac3e4eafca4d8..2c4612eedef63017dcca8ff80cb8540c33d945b5 100644
--- a/Framework/Algorithms/test/PoissonErrorsTest.h
+++ b/Framework/Algorithms/test/PoissonErrorsTest.h
@@ -32,9 +32,7 @@ public:
   PoissonErrorsTest() {
     inputProp1 = "InputWorkspace";
     inputProp2 = "CountsWorkspace";
-    ;
     outputProp = "OutputWorkspace";
-    ;
   }
 
   void testInit() {
@@ -276,26 +274,26 @@ private:
                      size_t ws2Index) {
     // printf("I=%d\tws2Index=%d\n",i,ws2Index);
     double sig1 =
-        work_in1->dataY(i / work_in1->blocksize())[i % work_in1->blocksize()];
-    double sig2 = work_in2->dataY(
+        work_in1->y(i / work_in1->blocksize())[i % work_in1->blocksize()];
+    double sig2 = work_in2->y(
         ws2Index / work_in2->blocksize())[ws2Index % work_in2->blocksize()];
-    double sig2e = work_in2->dataE(
+    double sig2e = work_in2->e(
         ws2Index / work_in2->blocksize())[ws2Index % work_in2->blocksize()];
     double sig3 =
-        work_out1->dataY(i / work_in1->blocksize())[i % work_in1->blocksize()];
+        work_out1->y(i / work_in1->blocksize())[i % work_in1->blocksize()];
     TS_ASSERT_DELTA(
-        work_in1->dataX(i / work_in1->blocksize())[i % work_in1->blocksize()],
-        work_out1->dataX(i / work_in1->blocksize())[i % work_in1->blocksize()],
+        work_in1->x(i / work_in1->blocksize())[i % work_in1->blocksize()],
+        work_out1->x(i / work_in1->blocksize())[i % work_in1->blocksize()],
         0.0001);
     TS_ASSERT_DELTA(sig1, sig3, 0.0001);
     // double err1 =
-    // work_in1->dataE(i/work_in1->blocksize())[i%work_in1->blocksize()];
+    // work_in1->e(i/work_in1->blocksize())[i%work_in1->blocksize()];
     // double err2 =
-    // work_in2->dataE(ws2Index/work_in2->blocksize())[ws2Index%work_in2->blocksize()];
+    // work_in2->e(ws2Index/work_in2->blocksize())[ws2Index%work_in2->blocksize()];
     double err3((sig2e / sig2) * sig3);
     TS_ASSERT_DELTA(
         err3,
-        work_out1->dataE(i / work_in1->blocksize())[i % work_in1->blocksize()],
+        work_out1->e(i / work_in1->blocksize())[i % work_in1->blocksize()],
         0.0001);
   }
 
diff --git a/Framework/Algorithms/test/Rebin2DTest.h b/Framework/Algorithms/test/Rebin2DTest.h
index 0216510bf56597b97fafa14780c2751e121ec15d..6509fe9f5767f8ec28842c3dee77d665074c64f2 100644
--- a/Framework/Algorithms/test/Rebin2DTest.h
+++ b/Framework/Algorithms/test/Rebin2DTest.h
@@ -7,8 +7,9 @@
 #ifndef MANTID_ALGORITHMS_REBIN2DTEST_H_
 #define MANTID_ALGORITHMS_REBIN2DTEST_H_
 
-#include "MantidAPI/NumericAxis.h"
+#include "MantidAPI/BinEdgeAxis.h"
 #include "MantidAlgorithms/Rebin2D.h"
+#include "MantidDataObjects/RebinnedOutput.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 #include <cxxtest/TestSuite.h>
 
@@ -16,6 +17,7 @@
 
 using Mantid::Algorithms::Rebin2D;
 using namespace Mantid::API;
+using namespace Mantid::DataObjects;
 
 namespace {
 /**
@@ -49,7 +51,7 @@ MatrixWorkspace_sptr makeInputWS(const bool distribution,
       int(nhist), int(nbins), x0, deltax);
 
   // We need something other than a spectrum axis, call this one theta
-  NumericAxis *const thetaAxis = new NumericAxis(nhist + 1);
+  BinEdgeAxis *const thetaAxis = new BinEdgeAxis(nhist + 1);
   for (size_t i = 0; i < nhist + 1; ++i) {
     thetaAxis->setValue(i, -0.5 + static_cast<double>(i));
   }
@@ -184,6 +186,54 @@ public:
     }
   }
 
+  void test_Zero_Area_Bins_NoFractionalBinning() {
+    MatrixWorkspace_sptr inputWS = makeInputWS(false);
+    const auto nhist = inputWS->getNumberHistograms();
+    // Set the vertical 'width' of a single histogram to zero
+    auto thetaAxis = inputWS->getAxis(1);
+    const auto middle = nhist / 2;
+    const auto midValue = thetaAxis->getValue(middle);
+    thetaAxis->setValue(middle - 1, midValue);
+    constexpr bool useFractionalBinning = false;
+    MatrixWorkspace_sptr outputWS = runAlgorithm(
+        inputWS, "5.,2.,15.", "-0.5,10.,9.5", useFractionalBinning);
+    TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
+    const auto expectedY = 2. * 9. * 2.;
+    const auto expectedE = std::sqrt(expectedY);
+    const auto &Ys = outputWS->y(0);
+    const auto &Es = outputWS->e(0);
+    for (size_t i = 0; i < Ys.size(); ++i) {
+      TS_ASSERT(!std::isnan(Ys[i]))
+      TS_ASSERT_DELTA(Ys[i], expectedY, 1e-12)
+      TS_ASSERT_DELTA(Es[i], expectedE, 1e-12)
+    }
+  }
+
+  void test_Zero_Area_Bins_FractionalBinning() {
+    MatrixWorkspace_sptr inputWS = makeInputWS(false);
+    const auto nhist = inputWS->getNumberHistograms();
+    // Set the vertical 'width' of a single histogram to zero
+    auto thetaAxis = inputWS->getAxis(1);
+    const auto middle = nhist / 2;
+    const auto midValue = thetaAxis->getValue(middle);
+    thetaAxis->setValue(middle - 1, midValue);
+    constexpr bool useFractionalBinning = true;
+    MatrixWorkspace_sptr outputWS = runAlgorithm(
+        inputWS, "5.,2.,15.", "-0.5,10.,9.5", useFractionalBinning);
+    const auto &rebinned = *dynamic_cast<RebinnedOutput *>(outputWS.get());
+    TS_ASSERT_EQUALS(rebinned.getNumberHistograms(), 1)
+    const auto expectedY = 2. * 9. * 2.;
+    const auto expectedE = std::sqrt(expectedY);
+    const auto &Fs = rebinned.dataF(0);
+    const auto &Ys = rebinned.y(0);
+    const auto &Es = rebinned.e(0);
+    for (size_t i = 0; i < Ys.size(); ++i) {
+      TS_ASSERT(!std::isnan(Ys[i]))
+      TS_ASSERT_DELTA(Ys[i] * Fs[i], expectedY, 1e-12)
+      TS_ASSERT_DELTA(Es[i] * Fs[i], expectedE, 1e-12)
+    }
+  }
+
 private:
   void checkData(MatrixWorkspace_const_sptr outputWS, const size_t nxvalues,
                  const size_t nhist, const bool dist, const bool onAxis1,
@@ -202,8 +252,6 @@ private:
       const auto &y = outputWS->y(i);
       const auto &e = outputWS->e(i);
       for (size_t j = 0; j < nxvalues - 1; ++j) {
-        std::ostringstream os;
-        os << "Bin " << i << "," << j;
         if (onAxis1) {
           if (small_bins) {
 
@@ -224,6 +272,8 @@ private:
           TS_ASSERT_DELTA(y[j], 1.0, epsilon);
           TS_ASSERT_DELTA(e[j], 0.5, epsilon);
         } else {
+          std::ostringstream os;
+          os << "Bin " << i << "," << j;
           TSM_ASSERT_DELTA(os.str(), y[j], 4.0, epsilon);
           TS_ASSERT_DELTA(e[j], 2.0, epsilon);
         }
@@ -259,19 +309,24 @@ public:
   static void destroySuite(Rebin2DTestPerformance *suite) { delete suite; }
 
   Rebin2DTestPerformance() {
+    constexpr bool distribution = false;
+    constexpr bool perf_test = true;
+    constexpr bool small_bins = false;
     m_inputWS = makeInputWS(distribution, perf_test, small_bins);
   }
 
   void test_On_Large_Workspace() {
-    runAlgorithm(m_inputWS, "100,200,41000", "-0.5,2,499.5");
+    runAlgorithm(m_inputWS, "100,10,41000", "-0.5,0.5,499.5");
+  }
+
+  void test_Use_Fractional_Area() {
+    constexpr bool useFractionalArea = true;
+    runAlgorithm(m_inputWS, "100,10,41000", "-0.5,0.5,499.5",
+                 useFractionalArea);
   }
 
 private:
   MatrixWorkspace_sptr m_inputWS;
-
-  const bool distribution = false;
-  const bool perf_test = true;
-  const bool small_bins = false;
 };
 
 #endif /* MANTID_ALGORITHMS_REBIN2DTEST_H_ */
diff --git a/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h b/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h
index 9671292cdfa4aa391f423b2c887623254c83dcb6..2330f2845496501ede7d8eaf9cc89d2c8a274fe4 100644
--- a/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h
+++ b/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h
@@ -1180,8 +1180,8 @@ public:
 
     MatrixWorkspace_sptr outQBin = alg.getProperty("OutputWorkspaceBinned");
 
-    auto outX = outQBin->x(0);
-    auto outY = outQBin->y(0);
+    const auto &outX = outQBin->x(0);
+    const auto &outY = outQBin->y(0);
 
     TS_ASSERT_DELTA(outX[0], 0.1, 0.0001);
     TS_ASSERT_DELTA(outY[0], 0.0, 0.0001);
@@ -1205,8 +1205,8 @@ public:
 
     MatrixWorkspace_sptr outQbinned = alg.getProperty("OutputWorkspaceBinned");
 
-    auto outX = outQbinned->x(0);
-    auto outY = outQbinned->y(0);
+    const auto &outX = outQbinned->x(0);
+    const auto &outY = outQbinned->y(0);
 
     TS_ASSERT_DELTA(outX[0], 0.1, 0.0001);
     TS_ASSERT_DELTA(outY[0], 0.0, 0.0001);
@@ -1229,8 +1229,8 @@ public:
 
     MatrixWorkspace_sptr outQBin = alg.getProperty("OutputWorkspaceBinned");
 
-    auto outX = outQBin->x(0);
-    auto outY = outQBin->y(0);
+    const auto &outX = outQBin->x(0);
+    const auto &outY = outQBin->y(0);
 
     TS_ASSERT_DELTA(outX[0], 0.009, 0.0001);
     TS_ASSERT_DELTA(outY[0], 0.0006, 0.0001);
@@ -1255,8 +1255,8 @@ public:
 
     MatrixWorkspace_sptr outQBin = alg.getProperty("OutputWorkspaceBinned");
 
-    auto outX = outQBin->x(0);
-    auto outY = outQBin->y(0);
+    const auto &outX = outQBin->x(0);
+    const auto &outY = outQBin->y(0);
 
     TS_ASSERT_DELTA(outX[0], 0.1, 0.0001);
     TS_ASSERT_DELTA(outY[0], 0.0, 0.0001);
@@ -1280,8 +1280,8 @@ public:
 
     MatrixWorkspace_sptr outQBin = alg.getProperty("OutputWorkspaceBinned");
 
-    auto outX = outQBin->x(0);
-    auto outY = outQBin->y(0);
+    const auto &outX = outQBin->x(0);
+    const auto &outY = outQBin->y(0);
 
     TS_ASSERT_DELTA(outX[0], 0.009, 0.0001);
     TS_ASSERT_DELTA(outY[0], 0.0021, 0.0001);
@@ -1306,8 +1306,8 @@ public:
 
     MatrixWorkspace_sptr outQBin = alg.getProperty("OutputWorkspaceBinned");
 
-    auto outX = outQBin->x(0);
-    auto outY = outQBin->y(0);
+    const auto &outX = outQBin->x(0);
+    const auto &outY = outQBin->y(0);
 
     TS_ASSERT_DELTA(outX[0], 0.1, 0.0001);
     TS_ASSERT_DELTA(outY[0], 0.0, 0.0001);
@@ -1331,8 +1331,8 @@ public:
 
     MatrixWorkspace_sptr outQBin = alg.getProperty("OutputWorkspaceBinned");
 
-    auto outX = outQBin->x(0);
-    auto outY = outQBin->y(0);
+    const auto &outX = outQBin->x(0);
+    const auto &outY = outQBin->y(0);
 
     TS_ASSERT_DELTA(outX[0], 0.009, 0.0001);
     TS_ASSERT_DELTA(outY[0], 0.0021, 0.0001);
diff --git a/Framework/Algorithms/test/SassenaFFTTest.h b/Framework/Algorithms/test/SassenaFFTTest.h
index 74120af2476c19505529ee410f51f07730ea53b4..72603f8f3045cd4ce8e382af963c0143e7a154dd 100644
--- a/Framework/Algorithms/test/SassenaFFTTest.h
+++ b/Framework/Algorithms/test/SassenaFFTTest.h
@@ -245,19 +245,20 @@ private:
 
     const double dt = 0.01; // time unit, in picoseconds
     MantidVec xv;
+    xv.reserve(nbins);
     for (size_t i = 0; i < nbins; i++) {
       int j = -static_cast<int>(nbins / 2) + static_cast<int>(i);
-      xv.push_back(dt * static_cast<double>(j));
+      xv.emplace_back(dt * static_cast<double>(j));
     }
 
     double sigma;
     MantidVec yv(nbins);
     // each spectra is a gaussian of same Height but different stdev
     for (size_t i = 0; i < nspectra; i++) {
-      ws->dataX(i) = xv;
+      ws->mutableX(i) = xv;
       sigma = sigma0 / (1 + static_cast<double>(i));
       this->Gaussian(xv, yv, Heigth, sigma);
-      ws->dataY(i) = yv;
+      ws->mutableY(i) = yv;
     }
 
     API::AnalysisDataService::Instance().add(wsName, ws);
diff --git a/Framework/Algorithms/test/SetUncertaintiesTest.h b/Framework/Algorithms/test/SetUncertaintiesTest.h
index 2f17e3c2445780e7db2e797150f999836b3404e0..853188d5649d67a157c8866d7b05824635c64c03 100644
--- a/Framework/Algorithms/test/SetUncertaintiesTest.h
+++ b/Framework/Algorithms/test/SetUncertaintiesTest.h
@@ -90,7 +90,7 @@ public:
   void test_zero() {
     const auto outWS = runAlg("zero");
 
-    const auto E = outWS->e(0);
+    const auto &E = outWS->e(0);
     for (const auto item : E) {
       TS_ASSERT_EQUALS(item, 0.);
     }
@@ -101,8 +101,8 @@ public:
   void test_sqrt() {
     const auto outWS = runAlg("sqrt");
 
-    const auto E = outWS->e(0);
-    const auto Y = outWS->y(0);
+    const auto &E = outWS->e(0);
+    const auto &Y = outWS->y(0);
     for (size_t i = 0; i < E.size(); ++i) {
       TS_ASSERT_DELTA(Y[i], E[i] * E[i], .001);
     }
@@ -113,7 +113,7 @@ public:
   void test_oneIfZero() {
     const auto outWS = runAlg("oneIfZero");
 
-    const auto E = outWS->e(0);
+    const auto &E = outWS->e(0);
     for (const auto item : E) {
       TS_ASSERT(item > 0.);
     }
diff --git a/Framework/Algorithms/test/SortXAxisTest.h b/Framework/Algorithms/test/SortXAxisTest.h
index d5144cd028f4660ede609f709d4fe69909da9dc9..73ff1350fac1b1d4b71ac859eed2379d1579dc73 100644
--- a/Framework/Algorithms/test/SortXAxisTest.h
+++ b/Framework/Algorithms/test/SortXAxisTest.h
@@ -23,9 +23,9 @@ using namespace Mantid::DataObjects;
 using namespace Mantid::HistogramData;
 
 namespace {
-MatrixWorkspace_sptr createWorkspaceE(const std::vector<double> xData,
-                                      const std::vector<double> yData,
-                                      const std::vector<double> eData,
+MatrixWorkspace_sptr createWorkspaceE(const std::vector<double> &xData,
+                                      const std::vector<double> &yData,
+                                      const std::vector<double> &eData,
                                       const int nSpec = 1) {
 
   Workspace2D_sptr outputWorkspace = create<DataObjects::Workspace2D>(
@@ -39,9 +39,9 @@ MatrixWorkspace_sptr createWorkspaceE(const std::vector<double> xData,
   return outputWorkspace;
 }
 
-MatrixWorkspace_sptr createHistoWorkspaceE(const std::vector<double> xData,
-                                           const std::vector<double> yData,
-                                           const std::vector<double> eData,
+MatrixWorkspace_sptr createHistoWorkspaceE(const std::vector<double> &xData,
+                                           const std::vector<double> &yData,
+                                           const std::vector<double> &eData,
                                            const int nSpec = 1) {
 
   Workspace2D_sptr outputWorkspace = create<DataObjects::Workspace2D>(
@@ -55,9 +55,9 @@ MatrixWorkspace_sptr createHistoWorkspaceE(const std::vector<double> xData,
   return outputWorkspace;
 }
 
-MatrixWorkspace_sptr createWorkspaceDx(const std::vector<double> xData,
-                                       const std::vector<double> yData,
-                                       const std::vector<double> dxData,
+MatrixWorkspace_sptr createWorkspaceDx(const std::vector<double> &xData,
+                                       const std::vector<double> &yData,
+                                       const std::vector<double> &dxData,
                                        const int nSpec = 1) {
 
   Workspace2D_sptr outputWorkspace = create<DataObjects::Workspace2D>(
@@ -71,9 +71,9 @@ MatrixWorkspace_sptr createWorkspaceDx(const std::vector<double> xData,
   return outputWorkspace;
 }
 
-MatrixWorkspace_sptr createHistoWorkspaceDx(const std::vector<double> xData,
-                                            const std::vector<double> yData,
-                                            const std::vector<double> dxData,
+MatrixWorkspace_sptr createHistoWorkspaceDx(const std::vector<double> &xData,
+                                            const std::vector<double> &yData,
+                                            const std::vector<double> &dxData,
                                             const int nSpec = 1) {
 
   Workspace2D_sptr outputWorkspace = create<DataObjects::Workspace2D>(
@@ -87,8 +87,8 @@ MatrixWorkspace_sptr createHistoWorkspaceDx(const std::vector<double> xData,
   return outputWorkspace;
 }
 
-MatrixWorkspace_sptr createHistoWorkspace(const std::vector<double> xData,
-                                          const std::vector<double> yData,
+MatrixWorkspace_sptr createHistoWorkspace(const std::vector<double> &xData,
+                                          const std::vector<double> &yData,
                                           const int nSpec = 1) {
 
   Workspace2D_sptr outputWorkspace = create<DataObjects::Workspace2D>(
diff --git a/Framework/Algorithms/test/SumSpectraTest.h b/Framework/Algorithms/test/SumSpectraTest.h
index 4033f785f3b2d47cdd0078be7f8be46594fd3a61..850d3ef4d9e4d9ece9e3368d9eeb57903130a85e 100644
--- a/Framework/Algorithms/test/SumSpectraTest.h
+++ b/Framework/Algorithms/test/SumSpectraTest.h
@@ -622,7 +622,7 @@ public:
     Workspace2D_const_sptr output2D =
         boost::dynamic_pointer_cast<const Workspace2D>(output);
 
-    auto outYVals = output2D->y(0);
+    const auto &outYVals = output2D->y(0);
     // We expect one less because of inf and NaN
     TS_ASSERT_EQUALS(outYVals[0], 2.);
     TS_ASSERT_EQUALS(outYVals[1], 2.);
@@ -659,7 +659,7 @@ public:
     Workspace2D_const_sptr output2D =
         boost::dynamic_pointer_cast<const Workspace2D>(output);
 
-    auto outYVals = output2D->y(0);
+    const auto &outYVals = output2D->y(0);
     // We expect a NaN and an Inf to propagate here
     TS_ASSERT_EQUALS(std::isnormal(outYVals[0]), false);
     TS_ASSERT_EQUALS(std::isnormal(outYVals[1]), false);
diff --git a/Framework/Algorithms/test/TimeAtSampleStrategyDirectTest.h b/Framework/Algorithms/test/TimeAtSampleStrategyDirectTest.h
index d997c8ce86fa7453bf48671048560d6ed0e2a5ae..471e9d4d9f7d4270f06b33ee788d46ba30a353ef 100644
--- a/Framework/Algorithms/test/TimeAtSampleStrategyDirectTest.h
+++ b/Framework/Algorithms/test/TimeAtSampleStrategyDirectTest.h
@@ -57,7 +57,7 @@ public:
     double expectedShift = L1 / std::sqrt(ei * 2. * PhysicalConstants::meV /
                                           PhysicalConstants::NeutronMass);
 
-    TSM_ASSERT_EQUALS("L1 / (L1 + L2)", expectedShift, shift);
+    TSM_ASSERT_DELTA("L1 / (L1 + L2)", expectedShift, shift, 0.0000001);
   }
 };
 
diff --git a/Framework/Algorithms/test/TimeAtSampleStrategyIndirectTest.h b/Framework/Algorithms/test/TimeAtSampleStrategyIndirectTest.h
index 4430eb35a1fd7b2a2d5e70c31456ebb5e40e5cfc..f6e5b2b972b628b58f1f7ff3ac17a744e5c9fed2 100644
--- a/Framework/Algorithms/test/TimeAtSampleStrategyIndirectTest.h
+++ b/Framework/Algorithms/test/TimeAtSampleStrategyIndirectTest.h
@@ -8,6 +8,8 @@
 #define MANTID_ALGORITHMS_TIMEATSAMPLESTRATEGYINDIRECTTEST_H_
 
 #include "MantidAlgorithms/TimeAtSampleStrategyIndirect.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/Instrument/ReferenceFrame.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 #include <cxxtest/TestSuite.h>
 
@@ -28,9 +30,27 @@ public:
     auto ws = WorkspaceCreationHelper::
         create2DWorkspaceWithReflectometryInstrument(); // workspace has
                                                         // monitors
+
+    const size_t monitorIndex = 1; // monitor workspace index.
+    const auto &instrument = ws->getInstrument();
+    auto sample = instrument->getSample();
+    auto source = instrument->getSource();
+
+    const auto &beamDir =
+        instrument->getReferenceFrame()->vecPointingAlongBeam();
+
+    auto monitor = ws->getDetector(monitorIndex);
+
+    const double L1 = source->getPos().distance(sample->getPos());
+
     TimeAtSampleStrategyIndirect strategy(ws);
-    TS_ASSERT_THROWS(strategy.calculate(1 /*monitor index*/),
-                     std::invalid_argument &);
+    const auto correction = strategy.calculate(monitorIndex);
+
+    TSM_ASSERT_EQUALS("L1/L1m",
+                      std::abs(L1 / beamDir.scalar_prod(source->getPos() -
+                                                        monitor->getPos())),
+                      correction.factor);
+    TS_ASSERT_EQUALS(0., correction.offset);
   }
 };
 
diff --git a/Framework/Algorithms/test/WeightedMeanTest.h b/Framework/Algorithms/test/WeightedMeanTest.h
index 1d16ee8685f6118212d285c982617e70d15c08ee..6da20eff43c4189e1c1f31bd4cd727831be96176 100644
--- a/Framework/Algorithms/test/WeightedMeanTest.h
+++ b/Framework/Algorithms/test/WeightedMeanTest.h
@@ -63,19 +63,19 @@ public:
         result = boost::dynamic_pointer_cast<MatrixWorkspace>(
             AnalysisDataService::Instance().retrieve("result")))
     // Check bin boundaries are the same
-    TS_ASSERT_EQUALS(in2->readX(0), result->readX(0))
+    TS_ASSERT_EQUALS(in2->x(0), result->x(0))
     // Pick a bin where both entries are non-zero
-    TS_ASSERT_DELTA(result->readY(0)[1176], 21983.40535, 0.00001)
-    TS_ASSERT_DELTA(result->readE(0)[1176], 104.841321, 0.000001)
+    TS_ASSERT_DELTA(result->y(0)[1176], 21983.40535, 0.00001)
+    TS_ASSERT_DELTA(result->e(0)[1176], 104.841321, 0.000001)
     // Now one where first is zero
-    TS_ASSERT_EQUALS(result->readY(0)[2], 2.0)
-    TS_ASSERT_EQUALS(result->readE(0)[2], M_SQRT2)
+    TS_ASSERT_EQUALS(result->y(0)[2], 2.0)
+    TS_ASSERT_EQUALS(result->e(0)[2], M_SQRT2)
     // And one where second is zero
-    TS_ASSERT_EQUALS(result->readY(0)[113], 97.0)
-    TS_ASSERT_EQUALS(result->readE(0)[113], std::sqrt(97.0))
+    TS_ASSERT_EQUALS(result->y(0)[113], 97.0)
+    TS_ASSERT_EQUALS(result->e(0)[113], std::sqrt(97.0))
     // Finally one where both are zero
-    TS_ASSERT_EQUALS(result->readY(0)[4989], 0.0)
-    TS_ASSERT_EQUALS(result->readE(0)[4989], 0.0)
+    TS_ASSERT_EQUALS(result->y(0)[4989], 0.0)
+    TS_ASSERT_EQUALS(result->e(0)[4989], 0.0)
 
     AnalysisDataService::Instance().remove("first");
     AnalysisDataService::Instance().remove("second");
diff --git a/Framework/Algorithms/test/WienerSmoothTest.h b/Framework/Algorithms/test/WienerSmoothTest.h
index 5511fa08e1f6ba2d4b32b0f391ced3a35b9bb81d..e41d28534402841c1183a7b6d07a2760fac183dc 100644
--- a/Framework/Algorithms/test/WienerSmoothTest.h
+++ b/Framework/Algorithms/test/WienerSmoothTest.h
@@ -610,8 +610,8 @@ private:
 
     for (size_t i = 0; i < ws->getNumberHistograms(); ++i) {
 
-      auto outX = ws->x(i);
-      auto outE = ws->e(i);
+      const auto &outX = ws->x(i);
+      const auto &outE = ws->e(i);
 
       TS_ASSERT(std::equal(outX.begin(), outX.end(), inX.begin()));
       TS_ASSERT(std::equal(outE.begin(), outE.end(), inE.begin()));
diff --git a/Framework/Beamline/CMakeLists.txt b/Framework/Beamline/CMakeLists.txt
index 5d3253bff2446886aa79de5643db8249d09314d2..e56b3252a4bc48b576b5be4716803c2c72c6c84a 100644
--- a/Framework/Beamline/CMakeLists.txt
+++ b/Framework/Beamline/CMakeLists.txt
@@ -54,4 +54,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Beamline ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Beamline INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt
index 0af66ad81f0ea34fc87ac47650768a5dfb00dc62..de1893c3519498614dfce34284e305d26a26017b 100644
--- a/Framework/CMakeLists.txt
+++ b/Framework/CMakeLists.txt
@@ -7,18 +7,15 @@ set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../buildconfig/CMake")
 # Define the project name. Allows building framework as a separate project.
 project ( MantidFramework )
 
-# Set paths to Third_Party for Windows
-if ( NOT THIRD_PARTY_DIR )
-  if ( MSVC )
-    include ( MSVC)
-  elseif ( APPLE )
-    include ( DarwinSetup )
-  endif ()
-endif ()
-
 # If building as a stand-alone project, call our common setup script
 if ( NOT COMMONSETUP_DONE )
-  include ( CommonSetup )
+    if (MSVC)
+        include(MSVC)
+    elseif(APPLE)
+        include(DarwinSetup)
+    endif()
+
+    include ( CommonSetup )
 endif ()
 
 ###########################################################################
@@ -64,6 +61,7 @@ endif ()
 ###########################################################################
 # Now add the packages one-by-one, building up the dependencies as we go
 ###########################################################################
+include (TargetFunctions)
 
 add_custom_target ( FrameworkTests ) # target for all framework tests
 add_dependencies ( check FrameworkTests )
@@ -168,37 +166,43 @@ add_custom_target( Framework DEPENDS ${FRAMEWORK_LIBS} )
 ###########################################################################
 
 # Create instrument directory
-install ( DIRECTORY ../instrument/ DESTINATION ${INBUNDLE}instrument
-          PATTERN "*UNIT_TESTING*" EXCLUDE
-          PATTERN ".gitignore" EXCLUDE
-)
+if(APPLE)
+    set(_BUNDLES ${INBUNDLE} MantidWorkbench.app/)
+else()
+    set(_BUNDLES "./")
+endif()
 
-# Ships .py files but only ship compiled pyd files for supported platforms.
-if ( WIN32 ) # General windows environment
-  if ( CMAKE_SIZEOF_VOID_P EQUAL 8 ) # Recommended way of detecting 64- vs 32-bit build
-    # Excludes .so files & _win32 binaries
-    install ( DIRECTORY ../scripts/ DESTINATION ${INBUNDLE}scripts PATTERN "*.pyc"
-              EXCLUDE PATTERN ".svn" EXCLUDE PATTERN ".gitignore"
-              EXCLUDE PATTERN "*.so" EXCLUDE PATTERN "*_win32.pyd" EXCLUDE PATTERN "CMakeLists.txt"
-              EXCLUDE PATTERN "test" EXCLUDE )
-  else ()
-    # Excludes so files & _win64 binaries
-    install ( DIRECTORY ../scripts/ DESTINATION ${INBUNDLE}scripts PATTERN "*.pyc"
-              EXCLUDE PATTERN ".svn" EXCLUDE PATTERN ".gitignore"
-              EXCLUDE PATTERN "*.so" EXCLUDE PATTERN "*_win64.pyd" EXCLUDE PATTERN "CMakeLists.txt"
-              EXCLUDE PATTERN "test" EXCLUDE )
-  endif ()
-
-  # Also ship mingw libraries for Inelastic fortran code. We need to do a better job here and build things
-  file ( GLOB MINGW_DLLS "${THIRD_PARTY_DIR}/bin/mingw/*.dll" )
-  install ( FILES ${MINGW_DLLS} DESTINATION ${INBUNDLE}scripts/Inelastic )
-else ()
-  # These don't work correctly and the linux ones are in no way general. They really need to be part of the build
-  install ( DIRECTORY ../scripts/ DESTINATION ${INBUNDLE}scripts PATTERN "*.pyc"
+foreach ( _bundle ${_BUNDLES} )
+    install ( DIRECTORY ../instrument/ DESTINATION ${_bundle}instrument
+        PATTERN "*UNIT_TESTING*" EXCLUDE
+        PATTERN ".gitignore" EXCLUDE
+        )
+    # Ships .py files but only ship compiled pyd files for supported platforms.
+    if ( WIN32 ) # General windows environment
+        if ( CMAKE_SIZEOF_VOID_P EQUAL 8 ) # Recommended way of detecting 64- vs 32-bit build
+            # Excludes .so files & _win32 binaries
+            install ( DIRECTORY ../scripts/ DESTINATION ${_bundle}scripts PATTERN "*.pyc"
+                EXCLUDE PATTERN ".svn" EXCLUDE PATTERN ".gitignore"
+                EXCLUDE PATTERN "*.so" EXCLUDE PATTERN "*_win32.pyd" EXCLUDE PATTERN "CMakeLists.txt"
+                EXCLUDE PATTERN "test" EXCLUDE )
+        else ()
+            # Excludes so files & _win64 binaries
+            install ( DIRECTORY ../scripts/ DESTINATION ${_bundle}scripts PATTERN "*.pyc"
+                EXCLUDE PATTERN ".svn" EXCLUDE PATTERN ".gitignore"
+                EXCLUDE PATTERN "*.so" EXCLUDE PATTERN "*_win64.pyd" EXCLUDE PATTERN "CMakeLists.txt"
+                EXCLUDE PATTERN "test" EXCLUDE )
+        endif ()
+
+        # Also ship mingw libraries for Inelastic fortran code. We need to do a better job here and build things
+        file ( GLOB MINGW_DLLS "${THIRD_PARTY_DIR}/bin/mingw/*.dll" )
+        install ( FILES ${MINGW_DLLS} DESTINATION ${_bundle}scripts/Inelastic )
+    else()
+        install ( DIRECTORY ../scripts/ DESTINATION ${_bundle}scripts PATTERN "*.pyc"
             EXCLUDE PATTERN ".svn" EXCLUDE PATTERN ".gitignore"
             EXCLUDE PATTERN "*_win*.pyd" EXCLUDE PATTERN "*_lnx64.so" EXCLUDE PATTERN "CMakeLists.txt"
             EXCLUDE PATTERN "test" EXCLUDE )
-endif ()
+    endif ()
+endforeach()
 
 # THIS MUST BE THE LAST SUB_DIRECTORY ADDED. See Properties/CMakeLists.txt.
 # This is included by the top-level CMakeLists if it is a full build but do it here for a Framework only
diff --git a/Framework/Catalog/CMakeLists.txt b/Framework/Catalog/CMakeLists.txt
index 3eee38799cfa141ec7fc64fac16ecab716da2c39..96797930d06f9bd7be956930418880283248e631 100644
--- a/Framework/Catalog/CMakeLists.txt
+++ b/Framework/Catalog/CMakeLists.txt
@@ -60,4 +60,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Catalog ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Catalog INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/Crystal/CMakeLists.txt b/Framework/Crystal/CMakeLists.txt
index 5d22c05bcba5270d7eeb3d18781b1a238c2cdcb9..6b3e03823599aa2a23cf4a719697a233e437b2c3 100644
--- a/Framework/Crystal/CMakeLists.txt
+++ b/Framework/Crystal/CMakeLists.txt
@@ -58,6 +58,7 @@ set ( SRC_FILES
 	src/SaveLauenorm.cpp
 	src/SelectCellOfType.cpp
 	src/SelectCellWithForm.cpp
+	src/SetCrystalLocation.cpp
 	src/SetGoniometer.cpp
 	src/SetSpecialCoordinates.cpp
 	src/SetUB.cpp
@@ -132,6 +133,7 @@ set ( INC_FILES
 	inc/MantidCrystal/SaveLauenorm.h
 	inc/MantidCrystal/SelectCellOfType.h
 	inc/MantidCrystal/SelectCellWithForm.h
+	inc/MantidCrystal/SetCrystalLocation.h
 	inc/MantidCrystal/SetGoniometer.h
 	inc/MantidCrystal/SetSpecialCoordinates.h
 	inc/MantidCrystal/SetUB.h
@@ -199,6 +201,7 @@ set ( TEST_FILES
 	SaveLauenormTest.h
 	SelectCellOfTypeTest.h
 	SelectCellWithFormTest.h
+    SetCrystalLocationTest.h
 	SetGoniometerTest.h
 	SetSpecialCoordinatesTest.h
 	SetUBTest.h
@@ -245,4 +248,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Crystal ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS Crystal INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
index 5d06df47752bebcc0526fb2e80191e2781596526..b4f7577491ad34528c464e34ad28579450976a58 100644
--- a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
+++ b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
@@ -153,6 +153,11 @@ private:
                 // OptRuns setup
 
   void setUpOptRuns();
+
+  mutable boost::shared_ptr<Geometry::Instrument> instChange;
+  mutable bool hasParameterMap = false;
+  mutable Kernel::V3D sampPos;
+  mutable boost::shared_ptr<const Geometry::ParameterMap> pmapSv;
 };
 } // namespace Crystal
 } // namespace Mantid
diff --git a/Framework/Crystal/inc/MantidCrystal/SetCrystalLocation.h b/Framework/Crystal/inc/MantidCrystal/SetCrystalLocation.h
new file mode 100644
index 0000000000000000000000000000000000000000..4eb14a9af3877b98b603812d486431af32dd93fc
--- /dev/null
+++ b/Framework/Crystal/inc/MantidCrystal/SetCrystalLocation.h
@@ -0,0 +1,57 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+/*
+ * SetCrystalLocation.h
+ *
+ *  Created on: Dec 12, 2018
+ *      Author: Brendan Sullivan
+ */
+
+#ifndef SETCRYSTALLOCATION_H_
+#define SETCRYSTALLOCATION_H_
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidKernel/System.h"
+
+namespace Mantid {
+namespace Crystal {
+
+/** SetCrystalLocation
+
+Description:
+This algorithm provides a convenient interface to sets the
+sample position of an events workspace.
+@author Brendan Sullivan, SNS,ORNL
+@date Dec 20 2018
+*/
+class DLLExport SetCrystalLocation : public API::Algorithm {
+public:
+  const std::string name() const override { return "SetCrystalLocation"; };
+  /// Summary of algorithms purpose
+  const std::string summary() const override {
+    return "This algorithm sets the sample location of the "
+           "input event workspace.";
+  }
+  const std::vector<std::string> seeAlso() const override {
+    return {"OptimizeCrystalPlacement"};
+  }
+
+  int version() const override { return 1; };
+
+  const std::string category() const override {
+    return "Crystal\\Corrections";
+  };
+
+private:
+  void init() override;
+
+  void exec() override;
+};
+} // namespace Crystal
+} // namespace Mantid
+
+#endif /* SETCRYSTALLOCATION_H_ */
diff --git a/Framework/Crystal/src/CentroidPeaks.cpp b/Framework/Crystal/src/CentroidPeaks.cpp
index 55627c3ac9b4f29e5bf245d185d51ec55b21f5e4..1bc4666055af647425ae13ae5ee98ea6f7db6759 100644
--- a/Framework/Crystal/src/CentroidPeaks.cpp
+++ b/Framework/Crystal/src/CentroidPeaks.cpp
@@ -95,6 +95,7 @@ void CentroidPeaks::integrate() {
     }
   }
 
+  const int inBlocksize = static_cast<int>(inWS->blocksize());
   int Edge = getProperty("EdgePixels");
   Progress prog(this, MinPeaks, 1.0, MaxPeaks);
   PARALLEL_FOR_IF(Kernel::threadSafe(*inWS, *peakWS))
@@ -154,7 +155,7 @@ void CentroidPeaks::integrate() {
     col = int(colcentroid / intensity);
     boost::algorithm::clamp(col, 0, nCols - 1);
     chan = int(chancentroid / intensity);
-    boost::algorithm::clamp(chan, 0, static_cast<int>(inWS->blocksize()));
+    boost::algorithm::clamp(chan, 0, inBlocksize);
 
     // Set wavelength to change tof for peak object
     if (!edgePixel(inst, bankName, col, row, Edge)) {
diff --git a/Framework/Crystal/src/FilterPeaks.cpp b/Framework/Crystal/src/FilterPeaks.cpp
index 3fa0520ff9b3a8216bfb444bc72a2788dd22b1d5..b1b1b7aef61de38d16b3bbdb6103e971ab154b5f 100644
--- a/Framework/Crystal/src/FilterPeaks.cpp
+++ b/Framework/Crystal/src/FilterPeaks.cpp
@@ -34,6 +34,8 @@ double QMOD(const Mantid::Geometry::IPeak &p) {
 double SN(const Mantid::Geometry::IPeak &p) {
   return p.getIntensity() / p.getSigmaIntensity();
 }
+
+double RUN(const Mantid::Geometry::IPeak &p) { return p.getRunNumber(); }
 } // namespace
 
 namespace Mantid {
@@ -66,7 +68,7 @@ void FilterPeaks::init() {
 
   std::vector<std::string> filters{"h+k+l",        "h^2+k^2+l^2", "Intensity",
                                    "Signal/Noise", "QMod",        "Wavelength",
-                                   "DSpacing",     "TOF"};
+                                   "DSpacing",     "TOF",         "RunNumber"};
   declareProperty("FilterVariable", "",
                   boost::make_shared<StringListValidator>(filters),
                   "The variable on which to filter the peaks");
@@ -150,6 +152,8 @@ FilterPeaks::FilterFunction FilterPeaks::getFilterVariableFunction(
     filterFunction = &SN;
   else if (filterVariable == "QMod")
     filterFunction = &QMOD;
+  else if (filterVariable == "RunNumber")
+    filterFunction = &RUN;
   else
     throw std::invalid_argument("Unknown FilterVariable: " + filterVariable);
   return filterFunction;
diff --git a/Framework/Crystal/src/OptimizeCrystalPlacement.cpp b/Framework/Crystal/src/OptimizeCrystalPlacement.cpp
index 2b02a4e69a1f9a34c43e7162ecb077950fce8b77..679c7a6c1560961173bc2da4133d85764356fe02 100644
--- a/Framework/Crystal/src/OptimizeCrystalPlacement.cpp
+++ b/Framework/Crystal/src/OptimizeCrystalPlacement.cpp
@@ -118,10 +118,10 @@ void OptimizeCrystalPlacement::init() {
   declareProperty("MaxAngularChange", 5.0,
                   "Max offset in degrees from current settings(def=5)");
 
-  declareProperty("MaxIndexingError", .25,
+  declareProperty("MaxIndexingError", 0.15,
                   "Use only peaks whose fractional "
                   "hkl values are below this "
-                  "tolerance(def=.25)");
+                  "tolerance(def=0.15)");
 
   declareProperty("MaxHKLPeaks2Use", -1.0,
                   "If less than 0 all peaks are used, "
@@ -145,6 +145,12 @@ void OptimizeCrystalPlacement::init() {
                       make_unique<OrEnabledWhenProperties>(
                           "AdjustSampleOffsets", Kernel::IS_EQUAL_TO, "0",
                           "OptimizeGoniometerTilt", Kernel::IS_EQUAL_TO, "0"));
+
+  declareProperty(make_unique<WorkspaceProperty<ITableWorkspace>>(
+                      "OutputNormalisedCovarianceMatrixOptX", "CovarianceInfo",
+                      Direction::Output),
+                  "The name of the TableWorkspace in which to store the final "
+                  "covariance matrix");
 }
 
 /**
@@ -304,8 +310,8 @@ void OptimizeCrystalPlacement::exec() {
     }
   }
 
-  Instrument_const_sptr instr = peaks->getPeak(0).getInstrument();
-  V3D sampPos = instr->getSample()->getPos();
+  // offset of previous sample position so should start at 0
+  V3D sampPos = V3D(0., 0., 0.);
 
   oss << ",SampleXOffset=" << sampPos.X() << ",SampleYOffset=" << sampPos.Y()
       << ",SampleZOffset=" << sampPos.Z();
@@ -371,8 +377,8 @@ void OptimizeCrystalPlacement::exec() {
   //------------------------- Get/Report  Results ------------------
 
   double chisq = fit_alg->getProperty("OutputChi2overDoF");
-  std::cout << "Fit finished. Status="
-            << (std::string)fit_alg->getProperty("OutputStatus") << '\n';
+  g_log.notice() << "Fit finished. Status="
+                 << (std::string)fit_alg->getProperty("OutputStatus") << '\n';
 
   setProperty("Chi2overDoF", chisq);
 
@@ -391,12 +397,6 @@ void OptimizeCrystalPlacement::exec() {
   g_log.notice() << "Output Status=" << OutputStatus << '\n';
 
   //------------------ Fix up Covariance output --------------------
-  declareProperty(make_unique<WorkspaceProperty<ITableWorkspace>>(
-                      "OutputNormalisedCovarianceMatrixOptX", "CovarianceInfo",
-                      Direction::Output),
-                  "The name of the TableWorkspace in which to store the final "
-                  "covariance matrix");
-
   ITableWorkspace_sptr NormCov =
       fit_alg->getProperty("OutputNormalisedCovarianceMatrix");
   setProperty("OutputNormalisedCovarianceMatrixOptX",
@@ -449,8 +449,9 @@ void OptimizeCrystalPlacement::exec() {
   UBinv.Invert();
   UBinv /= (2 * M_PI);
   for (int i = 0; i < outPeaks->getNumberPeaks(); ++i) {
-    outPeaks->getPeak(i).setSamplePos(newSampPos);
-    int RunNum = outPeaks->getPeak(i).getRunNumber();
+    auto &peak = outPeaks->getPeak(i);
+    peak.setSamplePos(peak.getSamplePos() + newSampPos);
+    int RunNum = peak.getRunNumber();
     std::string RunNumStr = std::to_string(RunNum);
     Matrix<double> GonMatrix;
     if (RunNum == prevRunNum ||
@@ -472,12 +473,12 @@ void OptimizeCrystalPlacement::exec() {
       GonMatrix = GonTilt * uniGonio.getR();
       MapRunNum2GonMat[RunNum] = GonMatrix;
     } else {
-      GonMatrix = GonTilt * outPeaks->getPeak(i).getGoniometerMatrix();
+      GonMatrix = GonTilt * peak.getGoniometerMatrix();
       MapRunNum2GonMat[RunNum] = GonMatrix;
     }
 
-    outPeaks->getPeak(i).setGoniometerMatrix(GonMatrix);
-    V3D hkl = UBinv * outPeaks->getPeak(i).getQSampleFrame();
+    peak.setGoniometerMatrix(GonMatrix);
+    V3D hkl = UBinv * peak.getQSampleFrame();
     if (Geometry::IndexingUtils::ValidIndex(hkl, HKLintOffsetMax))
       nIndexed++;
 
diff --git a/Framework/Crystal/src/PeakHKLErrors.cpp b/Framework/Crystal/src/PeakHKLErrors.cpp
index 059fd6191aeaf743624341ffcede102cbe88ce0c..f7a32da50a7c93b372562836a1d15ee8ebc22294 100644
--- a/Framework/Crystal/src/PeakHKLErrors.cpp
+++ b/Framework/Crystal/src/PeakHKLErrors.cpp
@@ -190,26 +190,31 @@ boost::shared_ptr<Geometry::Instrument>
 PeakHKLErrors::getNewInstrument(PeaksWorkspace_sptr Peaks) const {
   Geometry::Instrument_const_sptr instSave = Peaks->getPeak(0).getInstrument();
   auto pmap = boost::make_shared<Geometry::ParameterMap>();
-  boost::shared_ptr<const Geometry::ParameterMap> pmapSv =
-      instSave->getParameterMap();
 
   if (!instSave) {
     g_log.error(" Peaks workspace does not have an instrument");
     throw std::invalid_argument(" Not all peaks have an instrument");
   }
-  auto instChange = boost::shared_ptr<Geometry::Instrument>();
 
-  if (!instSave->isParametrized()) {
-
-    boost::shared_ptr<Geometry::Instrument> instClone(instSave->clone());
-    auto Pinsta = boost::make_shared<Geometry::Instrument>(instSave, pmap);
-
-    instChange = Pinsta;
-  } else // catch(... )
-  {
-    auto P1 = boost::make_shared<Geometry::Instrument>(
-        instSave->baseInstrument(), pmap);
-    instChange = P1;
+  if (!hasParameterMap) {
+    pmapSv = instSave->getParameterMap();
+    hasParameterMap = true;
+    if (!instSave->isParametrized()) {
+
+      boost::shared_ptr<Geometry::Instrument> instClone(instSave->clone());
+      auto Pinsta = boost::make_shared<Geometry::Instrument>(instSave, pmap);
+
+      instChange = Pinsta;
+      IComponent_const_sptr sample = instChange->getSample();
+      sampPos = sample->getRelativePos();
+    } else // catch(... )
+    {
+      auto P1 = boost::make_shared<Geometry::Instrument>(
+          instSave->baseInstrument(), instSave->makeLegacyParameterMap());
+      instChange = P1;
+      IComponent_const_sptr sample = instChange->getSample();
+      sampPos = sample->getRelativePos();
+    }
   }
 
   if (!instChange) {
@@ -219,11 +224,10 @@ PeakHKLErrors::getNewInstrument(PeaksWorkspace_sptr Peaks) const {
   //------------------"clone" orig instruments pmap -------------------
 
   cLone(pmap, instSave, pmapSv);
-  IComponent_const_sptr sample = instChange->getSample();
-  V3D sampPos = sample->getRelativePos();
   V3D sampOffsets(getParameter("SampleXOffset"), getParameter("SampleYOffset"),
                   getParameter("SampleZOffset"));
 
+  IComponent_const_sptr sample = instChange->getSample();
   pmap->addPositionCoordinate(sample.get(), std::string("x"),
                               sampPos.X() + sampOffsets.X());
   pmap->addPositionCoordinate(sample.get(), std::string("y"),
@@ -394,6 +398,10 @@ void PeakHKLErrors::function1D(double *out, const double *xValues,
     } else {
       peak.setGoniometerMatrix(GonRot * peak.getGoniometerMatrix());
     }
+    V3D sampOffsets(getParameter("SampleXOffset"),
+                    getParameter("SampleYOffset"),
+                    getParameter("SampleZOffset"));
+    peak.setSamplePos(peak.getSamplePos() + sampOffsets);
 
     V3D hkl = UBinv * peak.getQSampleFrame();
 
@@ -513,6 +521,10 @@ void PeakHKLErrors::functionDeriv1D(Jacobian *out, const double *xValues,
       chiParamNum = phiParamNum = omegaParamNum = nParams() + 10;
       peak.setGoniometerMatrix(GonRot * peak.getGoniometerMatrix());
     }
+    V3D sampOffsets(getParameter("SampleXOffset"),
+                    getParameter("SampleYOffset"),
+                    getParameter("SampleZOffset"));
+    peak.setSamplePos(peak.getSamplePos() + sampOffsets);
     // NOTE:Use getQLabFrame except for below.
     // For parameters the getGoniometerMatrix should remove GonRot, for derivs
     // wrt GonRot*, wrt chi*,phi*,etc.
diff --git a/Framework/Crystal/src/SetCrystalLocation.cpp b/Framework/Crystal/src/SetCrystalLocation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..86568da261d89f048a44c31d9cf0e326feb065fb
--- /dev/null
+++ b/Framework/Crystal/src/SetCrystalLocation.cpp
@@ -0,0 +1,80 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+/*
+ *
+ * SetCrystalLocation.cpp
+ *
+ *  Created on: Dec 12, 2018
+ *      Author: Brendan Sullivan
+ */
+#include "MantidCrystal/SetCrystalLocation.h"
+
+#include "MantidAPI/IMDEventWorkspace.h"
+#include "MantidAPI/Run.h"
+#include "MantidAPI/Sample.h"
+#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidCrystal/CalibrationHelpers.h"
+#include "MantidCrystal/PeakHKLErrors.h"
+#include "MantidCrystal/SCDCalibratePanels.h"
+#include "MantidDataObjects/EventWorkspace.h"
+#include "MantidGeometry/Crystal/IPeak.h"
+#include "MantidGeometry/Crystal/IndexingUtils.h"
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include "MantidGeometry/Instrument/Goniometer.h"
+#include "MantidKernel/ArrayProperty.h"
+#include "MantidKernel/EnabledWhenProperty.h"
+
+#include <cstdarg>
+
+using namespace Mantid::API;
+using namespace Mantid::DataObjects;
+using namespace Mantid::Kernel;
+using Mantid::Geometry::IndexingUtils;
+using Mantid::Geometry::Instrument_const_sptr;
+using namespace Mantid::Geometry;
+
+namespace Mantid {
+
+namespace Crystal {
+
+DECLARE_ALGORITHM(SetCrystalLocation)
+
+void SetCrystalLocation::init() {
+  declareProperty(make_unique<WorkspaceProperty<EventWorkspace>>(
+                      "InputWorkspace", "", Direction::Input),
+                  "Original event workspace");
+  declareProperty(make_unique<WorkspaceProperty<EventWorkspace>>(
+                      "OutputWorkspace", "", Direction::Output),
+                  "Output event workspace with a modified sample position");
+  declareProperty("NewX", 0.0, "New Absolute X position of crystal.");
+  declareProperty("NewY", 0.0, "New Absolute Y position of crystal.");
+  declareProperty("NewZ", 0.0, "New Absolute Z position of crystal.");
+}
+
+// simple algorithm that changes the sample position of the input
+// event workspace.
+void SetCrystalLocation::exec() {
+  EventWorkspace_sptr events = getProperty("InputWorkspace");
+  EventWorkspace_sptr outEvents = getProperty("OutputWorkspace");
+  const double newX = getProperty("NewX");
+  const double newY = getProperty("NewY");
+  const double newZ = getProperty("NewZ");
+  V3D newSamplePos = V3D(newX, newY, newZ);
+  if (events != outEvents) {
+    outEvents = events->clone();
+  }
+
+  auto &componentInfo = outEvents->mutableComponentInfo();
+  CalibrationHelpers::adjustUpSampleAndSourcePositions(
+      componentInfo.l1(), newSamplePos, componentInfo);
+
+  setProperty("OutputWorkspace", outEvents);
+} // exec
+
+} // namespace Crystal
+
+} // namespace Mantid
diff --git a/Framework/Crystal/test/FindSXPeaksHelperTest.h b/Framework/Crystal/test/FindSXPeaksHelperTest.h
index 44a323c8bac94e13e11424dd9e2a094855336f03..38da7fdfe5bf9c4873160694c24b858923ba2b92 100644
--- a/Framework/Crystal/test/FindSXPeaksHelperTest.h
+++ b/Framework/Crystal/test/FindSXPeaksHelperTest.h
@@ -93,7 +93,7 @@ public:
     // GIVEN
     auto workspace = WorkspaceCreationHelper::create1DWorkspaceConstant(
         10 /*size*/, 1.5 /*value*/, 1. /*error*/, true /*isHisto*/);
-    const auto y = workspace->y(0);
+    const auto &y = workspace->y(0);
 
     // WHEN
     auto backgroundStrategy = AbsoluteBackgroundStrategy(2.);
@@ -109,7 +109,7 @@ public:
     // GIVEN
     auto workspace = WorkspaceCreationHelper::create1DWorkspaceConstant(
         10 /*size*/, 1.5 /*value*/, 1. /*error*/, true /*isHisto*/);
-    const auto y = workspace->y(0);
+    const auto &y = workspace->y(0);
 
     // WHEN
     auto backgroundStrategy = PerSpectrumBackgroundStrategy(1.);
diff --git a/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h b/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
index e44378e49da316f608f9d92103792ad74b661330..cdae219e32f989909f8579428a3374afb3b65f39 100644
--- a/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
+++ b/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
@@ -160,8 +160,8 @@ public:
           }
         }
 
-        wsPtr->mutableY(wsIndex) = dataY;
-        wsPtr->mutableE(wsIndex) = dataE;
+        wsPtr->mutableY(wsIndex) = std::move(dataY);
+        wsPtr->mutableE(wsIndex) = std::move(dataE);
       }
 
     PeaksWorkspace_sptr pks(new PeaksWorkspace());
diff --git a/Framework/Crystal/test/OptimizeCrystalPlacementTest.h b/Framework/Crystal/test/OptimizeCrystalPlacementTest.h
index 4a17be0b0bad8265a7b8b5f0c349136a8b2982d7..1f09b2ea41baa05eb5564b2294a0b8e84f8392d1 100644
--- a/Framework/Crystal/test/OptimizeCrystalPlacementTest.h
+++ b/Framework/Crystal/test/OptimizeCrystalPlacementTest.h
@@ -187,9 +187,10 @@ public:
 
   void test_SamplePosition() {
     auto modPeaksNoFix = calculateBasicPlacement();
-    const auto &peak = modPeaksNoFix->getPeak(0);
+    auto peak = modPeaksNoFix->getPeak(0);
     auto inst = peak.getInstrument();
     const V3D sampPos(.0003, -.00025, .00015);
+    peak.setSamplePos(sampPos);
 
     auto pmap = inst->getParameterMap();
     auto sample = inst->getSample();
@@ -208,9 +209,9 @@ public:
         modPeaksNoFix, {{"KeepGoniometerFixedfor", "5637, 5638"},
                         {"AdjustSampleOffsets", "1"}});
     const auto table = resultsSamplePos.second;
-    TS_ASSERT_DELTA(table->Double(0, 1), 0, .0004);
-    TS_ASSERT_DELTA(table->Double(1, 1), 0, .00024);
-    TS_ASSERT_DELTA(table->Double(2, 1), 0, .0003);
+    TS_ASSERT_DELTA(table->Double(0, 1), -0.0003377231, 1.e-4);
+    TS_ASSERT_DELTA(table->Double(1, 1), 0.0000897573, 1.e-4);
+    TS_ASSERT_DELTA(table->Double(2, 1), -0.0002679569, 1.e-4);
   }
 
 private:
diff --git a/Framework/Crystal/test/SCDCalibratePanelsTest.h b/Framework/Crystal/test/SCDCalibratePanelsTest.h
index 92e273852585beceef3a4017f997ee1c78ff2595..f1b53e34d72d53391a738125dc7a256cf3c9c6fb 100644
--- a/Framework/Crystal/test/SCDCalibratePanelsTest.h
+++ b/Framework/Crystal/test/SCDCalibratePanelsTest.h
@@ -64,7 +64,7 @@ public:
     TS_ASSERT_DELTA(0.0, results->cell<double>(3, 1), 1.2);
     TS_ASSERT_DELTA(0.0, results->cell<double>(4, 1), 1.1);
     TS_ASSERT_DELTA(0.1133, results->cell<double>(5, 1), 0.36);
-    TS_ASSERT_DELTA(1.0024, results->cell<double>(6, 1), 5e-3);
+    TS_ASSERT_DELTA(0.9953, results->cell<double>(6, 1), 1e-2);
     TS_ASSERT_DELTA(0.9986, results->cell<double>(7, 1), 1e-2);
     TS_ASSERT_DELTA(0.2710, results->cell<double>(9, 1), 0.2);
     ITableWorkspace_sptr resultsL1 =
diff --git a/Framework/Crystal/test/SetCrystalLocationTest.h b/Framework/Crystal/test/SetCrystalLocationTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..109be075372f7c53ba5b8bf19c090d9e81b60306
--- /dev/null
+++ b/Framework/Crystal/test/SetCrystalLocationTest.h
@@ -0,0 +1,112 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef SETCRYSTALLOCATIONTEST_H_
+#define SETCRYSTALLOCATIONTEST_H_
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/ITableWorkspace.h"
+#include "MantidCrystal/LoadIsawUB.h"
+#include "MantidCrystal/SetCrystalLocation.h"
+#include "MantidCrystal/ShowPeakHKLOffsets.h"
+#include "MantidDataHandling/Load.h"
+#include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/PeaksWorkspace.h"
+#include <cxxtest/TestSuite.h>
+
+using Mantid::DataHandling::Load;
+using namespace Mantid::DataObjects;
+using Mantid::Crystal::LoadIsawUB;
+using Mantid::Crystal::SetCrystalLocation;
+using Mantid::Crystal::ShowPeakHKLOffsets;
+using Mantid::DataObjects::TableWorkspace;
+using Mantid::Kernel::V3D;
+using namespace Mantid::API;
+
+class SetCrystalLocationTest : public CxxTest::TestSuite {
+public:
+  void test_algo() {
+    std::string WSName = "events";
+    std::string file_name = "BSS_11841_event.nxs";
+    Load loader;
+    TS_ASSERT_THROWS_NOTHING(loader.initialize());
+    TS_ASSERT(loader.isInitialized());
+    loader.setPropertyValue("OutputWorkspace", WSName);
+    loader.setPropertyValue("Filename", file_name);
+    TS_ASSERT(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+
+    auto workspace = AnalysisDataService::Instance().retrieve(WSName);
+    EventWorkspace_sptr events =
+        boost::dynamic_pointer_cast<EventWorkspace>(workspace);
+    TS_ASSERT(events);
+    auto inst = events->getInstrument();
+    TS_ASSERT(inst);
+    auto sample = inst->getSample();
+    TS_ASSERT(sample);
+
+    SetCrystalLocation algo;
+    TS_ASSERT_THROWS_NOTHING(algo.initialize());
+    TS_ASSERT(algo.isInitialized());
+    algo.setProperty("InputWorkspace", WSName);
+    algo.setProperty("OutputWorkspace", WSName);
+    algo.setProperty("NewX", 1.0);
+    algo.setProperty("NewY", -0.30);
+    algo.setProperty("NewZ", 10.0);
+
+    // Check the sample is at the origin by default
+    V3D sampPos0 = sample->getPos();
+    TS_ASSERT_DELTA(sampPos0.X(), 0.0, 1.e-3);
+    TS_ASSERT_DELTA(sampPos0.Y(), 0.0, 1.e-3);
+    TS_ASSERT_DELTA(sampPos0.Z(), 0.0, 1.e-3);
+
+    // Move the sample to (1.0, -0.3, 10.0)
+    TS_ASSERT(algo.execute());
+    TS_ASSERT(algo.isExecuted());
+
+    // Check that the sample moved
+    V3D sampPos1 = sample->getPos();
+    TS_ASSERT_DELTA(sampPos1.X(), 1.0, 1.e-3);
+    TS_ASSERT_DELTA(sampPos1.Y(), -0.30, 1.e-3);
+    TS_ASSERT_DELTA(sampPos1.Z(), 10.0, 1.e-3);
+
+    // Try it on a separate workspace
+    SetCrystalLocation algo2;
+    TS_ASSERT_THROWS_NOTHING(algo2.initialize());
+    TS_ASSERT(algo2.isInitialized());
+    algo2.setProperty("InputWorkspace", WSName);
+    algo2.setProperty("OutputWorkspace", "events_new");
+    algo2.setProperty("NewX", 2.0);
+    algo2.setProperty("NewY", 4.0);
+    algo2.setProperty("NewZ", 0.0);
+    TS_ASSERT(algo2.execute());
+    TS_ASSERT(algo2.isExecuted());
+
+    // Check that the original is unchanged.  "Events" should be at
+    // the same sampPos1 = (1.0, -0.3, 10.0)
+    V3D sampPos2 = sample->getPos();
+    TS_ASSERT_DELTA(sampPos2.X(), 1.0, 1.e-3);
+    TS_ASSERT_DELTA(sampPos2.Y(), -0.30, 1.e-3);
+    TS_ASSERT_DELTA(sampPos2.Z(), 10.0, 1.e-3);
+
+    // Get pointers to the new workspace
+    auto workspace_new = AnalysisDataService::Instance().retrieve("events_new");
+    EventWorkspace_sptr events_new =
+        boost::dynamic_pointer_cast<EventWorkspace>(workspace_new);
+    TS_ASSERT(events_new)
+    auto inst_new = events_new->getInstrument();
+    TS_ASSERT(inst_new);
+    auto sample_new = inst_new->getSample();
+    TS_ASSERT(sample_new);
+
+    // the new workspace should be at (2.,4.,0.,)
+    V3D sampPos3 = sample_new->getPos();
+    TS_ASSERT_DELTA(sampPos3.X(), 2.0, 1.e-3);
+    TS_ASSERT_DELTA(sampPos3.Y(), 4.0, 1.e-3);
+    TS_ASSERT_DELTA(sampPos3.Z(), 0.0, 1.e-3);
+  }
+};
+
+#endif /* SETCRYSTALLOCATIONTEST_H_ */
diff --git a/Framework/CurveFitting/CMakeLists.txt b/Framework/CurveFitting/CMakeLists.txt
index f9a74672b869cb5da318ecc2f39b2a0e3c9405de..affc2b967a35cf81daa26973248d7979af7381b1 100644
--- a/Framework/CurveFitting/CMakeLists.txt
+++ b/Framework/CurveFitting/CMakeLists.txt
@@ -510,4 +510,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS CurveFitting ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS CurveFitting INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp b/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp
index ba61aba96214670d3f09abe0bce44deb2b90ed87..8d5a26b6817cbd511cd7faca1dbbf9646804b93b 100644
--- a/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp
+++ b/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp
@@ -482,7 +482,7 @@ void LeBailFit::execPatternCalculation() {
   bool resultphysical = calculateDiffractionPattern(
       m_dataWS->x(m_wsIndex), m_dataWS->y(m_wsIndex), true, true, emptyvec,
       vecY, rfactor);
-  m_outputWS->mutableY(CALDATAINDEX) = vecY;
+  m_outputWS->mutableY(CALDATAINDEX) = std::move(vecY);
 
   // Calculate background
   m_outputWS->mutableY(INPUTBKGDINDEX) =
@@ -659,8 +659,8 @@ void LeBailFit::execRefineBackground() {
 
   //   (3: peak without background, 4: input background)
   // m_backgroundFunction->function(domain, values);
-  m_outputWS->mutableY(CALBKGDINDEX) = backgroundvalues;
-  m_outputWS->mutableY(CALPUREPEAKINDEX) = valueVec;
+  m_outputWS->mutableY(CALBKGDINDEX) = std::move(backgroundvalues);
+  m_outputWS->mutableY(CALPUREPEAKINDEX) = std::move(valueVec);
 
   // 5. Output background to table workspace
   auto outtablews = boost::make_shared<TableWorkspace>();
diff --git a/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp b/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp
index 8504ee1d244fe6ce2eff614d5b4f02d2c46654f5..c452269a0b0c0d6fd08c6327842017a9137214fa 100644
--- a/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp
+++ b/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp
@@ -168,14 +168,14 @@ void NormaliseByPeakArea::createOutputWorkspaces(
   if (m_sumResults) {
     // Copy over xvalues & assign "high" initial error values to simplify
     // symmetrisation calculation
-    double high(1e6);
+    constexpr double high(1e6);
 
     m_yspaceWS->setSharedX(0, yspaceIn->sharedX(0));
     m_fittedWS->setSharedX(0, yspaceIn->sharedX(0));
     m_symmetrisedWS->setSharedX(0, yspaceIn->sharedX(0));
     m_yspaceWS->mutableE(0) = high;
-    m_fittedWS->mutableE(0) = high;
-    m_symmetrisedWS->mutableE(0) = high;
+    m_fittedWS->setSharedE(0, m_yspaceWS->sharedE(0));
+    m_symmetrisedWS->setSharedE(0, m_yspaceWS->sharedE(0));
   }
 
   setUnitsToMomentum(m_yspaceWS);
diff --git a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp
index 90165ffd3af82be1294b61294b1f32e7c5caf0af..b9d29624520cb369fc21252d67dd37e27e23be28 100644
--- a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp
+++ b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp
@@ -29,6 +29,16 @@ namespace {
 using namespace Mantid::API;
 using namespace Mantid::Kernel;
 
+WorkspaceGroup_sptr getADSGroupWorkspace(const std::string &workspaceName) {
+  return AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(
+      workspaceName);
+}
+
+MatrixWorkspace_sptr getADSMatrixWorkspace(const std::string &workspaceName) {
+  return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
+      workspaceName);
+}
+
 MatrixWorkspace_sptr convertSpectrumAxis(MatrixWorkspace_sptr inputWorkspace,
                                          const std::string &outputName) {
   auto convSpec = AlgorithmManager::Instance().create("ConvertSpectrumAxis");
@@ -40,8 +50,7 @@ MatrixWorkspace_sptr convertSpectrumAxis(MatrixWorkspace_sptr inputWorkspace,
   convSpec->execute();
   // Attempting to use getProperty("OutputWorkspace") on algorithm results in a
   // nullptr being returned
-  return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
-      outputName);
+  return getADSMatrixWorkspace(outputName);
 }
 
 MatrixWorkspace_sptr cloneWorkspace(MatrixWorkspace_sptr inputWorkspace,
@@ -150,8 +159,7 @@ std::vector<MatrixWorkspace_sptr> extractWorkspaces(const std::string &input) {
   std::vector<MatrixWorkspace_sptr> workspaces;
 
   auto extractWorkspace = [&](const std::string &name) {
-    workspaces.emplace_back(
-        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name));
+    workspaces.emplace_back(getADSMatrixWorkspace(name));
   };
 
   boost::regex reg("([^,;]+),");
@@ -302,6 +310,7 @@ runParameterProcessingWithGrouping(IAlgorithm &processingAlgorithm,
   }
   return createGroup(results);
 }
+
 } // namespace
 
 namespace Mantid {
@@ -511,9 +520,7 @@ void QENSFitSequential::exec() {
       processParameterTable(performFit(inputString, outputBaseName));
   const auto resultWs =
       processIndirectFitParameters(parameterWs, getDatasetGrouping(workspaces));
-  const auto groupWs =
-      AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(
-          outputBaseName + "_Workspaces");
+  const auto groupWs = getADSGroupWorkspace(outputBaseName + "_Workspaces");
   AnalysisDataService::Instance().addOrReplace(
       getPropertyValue("OutputWorkspace"), resultWs);
 
@@ -522,6 +529,7 @@ void QENSFitSequential::exec() {
                      inputWorkspaces);
   else
     renameWorkspaces(groupWs, spectra, outputBaseName, "_Workspace");
+
   copyLogs(resultWs, workspaces);
 
   const bool doExtractMembers = getProperty("ExtractMembers");
@@ -690,9 +698,9 @@ void QENSFitSequential::renameGroupWorkspace(
     std::string const &currentName, std::vector<std::string> const &spectra,
     std::string const &outputBaseName, std::string const &endOfSuffix) {
   if (AnalysisDataService::Instance().doesExist(currentName)) {
-    auto const pdfGroup =
-        AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(currentName);
-    renameWorkspaces(pdfGroup, spectra, outputBaseName, endOfSuffix);
+    auto const group = getADSGroupWorkspace(currentName);
+    if (group)
+      renameWorkspaces(group, spectra, outputBaseName, endOfSuffix);
   }
 }
 
diff --git a/Framework/CurveFitting/src/Algorithms/RefinePowderInstrumentParameters.cpp b/Framework/CurveFitting/src/Algorithms/RefinePowderInstrumentParameters.cpp
index a5fe48380eaedd8f3e24dd578eb5632de3311b8d..4fbb3613ce120feeaaf9f33d0d8675ea8d012d29 100644
--- a/Framework/CurveFitting/src/Algorithms/RefinePowderInstrumentParameters.cpp
+++ b/Framework/CurveFitting/src/Algorithms/RefinePowderInstrumentParameters.cpp
@@ -366,12 +366,14 @@ void RefinePowderInstrumentParameters::fitInstrumentParameters() {
   stringstream zss;
   zss << setw(20) << "d_h" << setw(20) << "Z DataY" << setw(20) << "Z ModelY"
       << setw(20) << "Z DiffY" << setw(20) << "DiffY\n";
+  const auto &X = m_dataWS->x(0);
+  const auto &Y = m_dataWS->y(2);
   for (size_t i = 0; i < z0.size(); ++i) {
-    double d_h = m_dataWS->x(0)[i];
+    double d_h = X[i];
     double zdatay = z0[i];
     double zmodely = z1[i];
     double zdiffy = z2[i];
-    double diffy = m_dataWS->y(2)[i];
+    double diffy = Y[i];
     zss << setw(20) << d_h << setw(20) << zdatay << setw(20) << zmodely
         << setw(20) << zdiffy << setw(20) << diffy << '\n';
   }
diff --git a/Framework/CurveFitting/test/Algorithms/FitTest.h b/Framework/CurveFitting/test/Algorithms/FitTest.h
index 12f1e6cf8c3296af1949669dfe3965cc2f7c47ab..e0c0a31b73e812f8580824f5e28280dcb96a9a41 100644
--- a/Framework/CurveFitting/test/Algorithms/FitTest.h
+++ b/Framework/CurveFitting/test/Algorithms/FitTest.h
@@ -2047,7 +2047,7 @@ public:
     auto ws =
         WorkspaceFactory::Instance().create("Workspace2D", 1, nbins, nbins);
     FunctionDomain1DVector x(-10, 10, nbins);
-    ws->dataX(0) = x.toVector();
+    ws->mutableX(0) = x.toVector();
     {
       Fit fit;
       fit.initialize();
@@ -2058,7 +2058,7 @@ public:
       fit.execute();
       auto res = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
           "out_Workspace");
-      auto y = res->y(1);
+      const auto &y = res->y(1);
       TS_ASSERT_DIFFERS(y.front(), 0.0);
       TS_ASSERT_DIFFERS(y.back(), 0.0);
     }
@@ -2073,7 +2073,7 @@ public:
       fit.execute();
       auto res = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
           "out_Workspace");
-      auto y = res->y(1);
+      const auto &y = res->y(1);
       for (size_t i = 0; i < 25; ++i) {
         TS_ASSERT_EQUALS(y[i], 0.0);
         TS_ASSERT_EQUALS(y[nbins - i - 1], 0.0);
diff --git a/Framework/CurveFitting/test/Algorithms/PawleyFitTest.h b/Framework/CurveFitting/test/Algorithms/PawleyFitTest.h
index 26e5fc4bc919369516a9076f16cf49c823d6bb9b..96824bb4ba2082ecfb36d65024091e8640187b1e 100644
--- a/Framework/CurveFitting/test/Algorithms/PawleyFitTest.h
+++ b/Framework/CurveFitting/test/Algorithms/PawleyFitTest.h
@@ -248,14 +248,13 @@ private:
 
     FunctionDomain1DVector xValues(xMin, xMax, n);
     FunctionValues yValues(xValues);
-    std::vector<double> eValues(n, 1.0);
 
     siFn->function(xValues, yValues);
 
     ws->mutableX(0) = xValues.toVector();
     ws->mutableY(0) = yValues.toVector();
     ws->mutableY(0) += bg;
-    ws->mutableE(0) = eValues;
+    ws->mutableE(0) = 1.0;
     WorkspaceCreationHelper::addNoise(ws, 0, -0.5, 0.5);
 
     ws->getAxis(0)->setUnit(unit);
diff --git a/Framework/CurveFitting/test/FuncMinimizers/ErrorMessagesTest.h b/Framework/CurveFitting/test/FuncMinimizers/ErrorMessagesTest.h
index 95f0d45ead4298c2ad9d36e250dcefe08ab42107..e63cde2a474e032dd6d36aaed138f2ba49f10c99 100644
--- a/Framework/CurveFitting/test/FuncMinimizers/ErrorMessagesTest.h
+++ b/Framework/CurveFitting/test/FuncMinimizers/ErrorMessagesTest.h
@@ -277,8 +277,8 @@ private:
     size_t nbins = x.size();
     auto ws =
         WorkspaceFactory::Instance().create("Workspace2D", 1, nbins, nbins);
-    ws->dataX(0) = x;
-    ws->dataY(0) = y;
+    ws->mutableX(0) = x;
+    ws->mutableY(0) = y;
     return ws;
   }
 };
diff --git a/Framework/CurveFitting/test/Functions/CrystalFieldMultiSpectrumTest.h b/Framework/CurveFitting/test/Functions/CrystalFieldMultiSpectrumTest.h
index 1200db37d30ddf47f545919f0f2facffda696f96..7bf47a0ac53b7c46965fb9b9f4725f0210f6bda5 100644
--- a/Framework/CurveFitting/test/Functions/CrystalFieldMultiSpectrumTest.h
+++ b/Framework/CurveFitting/test/Functions/CrystalFieldMultiSpectrumTest.h
@@ -557,10 +557,10 @@ private:
     fun.function(domain, y);
     for (size_t i = 0; i < nSpec; ++i) {
       auto x = static_cast<const FunctionDomain1DVector &>(domain.getDomain(i));
-      ws->dataX(i) = x.toVector();
+      ws->mutableX(i) = x.toVector();
       auto n = x.size();
       auto from = y.getPointerToCalculated(i * n);
-      ws->dataY(i).assign(from, from + n);
+      ws->mutableY(i).assign(from, from + n);
     }
     return ws;
   }
diff --git a/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h b/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h
index 37b886327ce990ce97b4db7f1310ff524c189632..ddfc6cdd12693bc3ba32c11bead1d13f0802943b 100644
--- a/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h
+++ b/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h
@@ -874,8 +874,8 @@ private:
     FunctionDomain1DVector x(x0, x1, nbins);
     FunctionValues y(x);
     fun.function(x, y);
-    ws->dataX(0) = x.toVector();
-    ws->dataY(0) = y.toVector();
+    ws->mutableX(0) = x.toVector();
+    ws->mutableY(0) = y.toVector();
     return ws;
   }
 };
diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt
index 3482694e01f61bd9f5e999c17576ceb7151c330d..aec5cf0780f4f1553fbc5b5d1d9bc2b85f0cf7c2 100644
--- a/Framework/DataHandling/CMakeLists.txt
+++ b/Framework/DataHandling/CMakeLists.txt
@@ -65,7 +65,7 @@
 	src/LoadILLIndirect2.cpp
 	src/LoadILLPolarizationFactors.cpp
 	src/LoadILLReflectometry.cpp
-	src/LoadILLSANS.cpp
+    	src/LoadILLSANS.cpp
 	src/LoadILLTOF2.cpp
 	src/LoadISISNexus2.cpp
 	src/LoadISISPolarizationEfficiencies.cpp
@@ -108,6 +108,7 @@
 	src/LoadRawHelper.cpp
 	src/LoadRawSpectrum0.cpp
 	src/LoadSampleShape.cpp
+	src/LoadSampleEnvironment.cpp
 	src/LoadSESANS.cpp
 	src/LoadSINQFocus.cpp
 	src/LoadSNSspec.cpp
@@ -141,7 +142,6 @@
 	src/RenameLog.cpp
 	src/RotateInstrumentComponent.cpp
 	src/RotateSource.cpp
-	src/SNSDataArchive.cpp
 	src/SaveANSTOAscii.cpp
 	src/SaveAscii.cpp
 	src/SaveAscii2.cpp
@@ -164,7 +164,7 @@
 	src/SaveILLCosmosAscii.cpp
 	src/SaveISISNexus.cpp
 	src/SaveIsawDetCal.cpp
-	src/SaveMask.cpp
+  src/SaveMask.cpp
 	src/SaveNISTDAT.cpp
 	src/SaveNXSPE.cpp
 	src/SaveNXTomo.cpp
@@ -177,7 +177,7 @@
 	src/SavePHX.cpp
 	src/SaveParameterFile.cpp
 	src/SaveRKH.cpp
-	src/SaveReflectometryAscii.cpp
+  src/SaveReflectometryAscii.cpp
 	src/SaveReflCustomAscii.cpp
 	src/SaveReflThreeColumnAscii.cpp
 	src/SaveSESANS.cpp
@@ -301,6 +301,7 @@ set ( INC_FILES
 	inc/MantidDataHandling/LoadRawHelper.h
 	inc/MantidDataHandling/LoadRawSpectrum0.h
 	inc/MantidDataHandling/LoadSampleShape.h
+	inc/MantidDataHandling/LoadSampleEnvironment.h
 	inc/MantidDataHandling/LoadSESANS.h
 	inc/MantidDataHandling/LoadSINQFocus.h
 	inc/MantidDataHandling/LoadSNSspec.h
@@ -335,7 +336,6 @@ set ( INC_FILES
 	inc/MantidDataHandling/RenameLog.h
 	inc/MantidDataHandling/RotateInstrumentComponent.h
 	inc/MantidDataHandling/RotateSource.h
-	inc/MantidDataHandling/SNSDataArchive.h
 	inc/MantidDataHandling/SaveANSTOAscii.h
 	inc/MantidDataHandling/SaveAscii.h
 	inc/MantidDataHandling/SaveAscii2.h
@@ -371,7 +371,7 @@ set ( INC_FILES
 	inc/MantidDataHandling/SavePHX.h
 	inc/MantidDataHandling/SaveParameterFile.h
 	inc/MantidDataHandling/SaveRKH.h
-	inc/MantidDataHandling/SaveReflectometryAscii.h
+  inc/MantidDataHandling/SaveReflectometryAscii.h
 	inc/MantidDataHandling/SaveReflCustomAscii.h
 	inc/MantidDataHandling/SaveReflThreeColumnAscii.h
 	inc/MantidDataHandling/SaveSESANS.h
@@ -486,7 +486,9 @@ set ( TEST_FILES
 	LoadRawBin0Test.h
 	LoadRawSaveNxsLoadNxsTest.h
 	LoadRawSpectrum0Test.h
+	LoadStlTest.h
 	LoadSampleShapeTest.h
+	LoadSampleEnvironmentTest.h
 	LoadSESANSTest.h
 	LoadSINQFocusTest.h
 	LoadSNSspecTest.h
@@ -518,7 +520,6 @@ set ( TEST_FILES
 	RenameLogTest.h
 	RotateInstrumentComponentTest.h
 	RotateSourceTest.h
-	SNSDataArchiveTest.h
 	SaveANSTOAsciiTest.h
 	SaveAscii2Test.h
 	SaveAsciiTest.h
@@ -552,7 +553,7 @@ set ( TEST_FILES
 	SavePHXTest.h
 	SaveParameterFileTest.h
 	SaveRKHTest.h
-	SaveReflectometryAsciiTest.h
+  SaveReflectometryAsciiTest.h
 	SaveReflCustomAsciiTest.h
 	SaveReflThreeColumnAsciiTest.h
 	SaveSESANSTest.h
@@ -610,4 +611,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS DataHandling ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS DataHandling INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h b/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h
index c37df542799ba051db2b1d5a2e9954c1a6df4892..3cf2c9c3e29552becbc0b28ebe09364aa02fb911 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h
@@ -35,7 +35,8 @@ public:
   /// Algorithm's version
   int version() const override { return (1); }
   const std::vector<std::string> seeAlso() const override {
-    return {"AbsorptionCorrection", "SetSampleMaterial", "CopySample"};
+    return {"SetSample", "AbsorptionCorrection", "SetSampleMaterial",
+            "CopySample"};
   }
   /// Algorithm's category for identification
   const std::string category() const override { return "Sample;"; }
diff --git a/Framework/DataHandling/inc/MantidDataHandling/ISISDataArchive.h b/Framework/DataHandling/inc/MantidDataHandling/ISISDataArchive.h
index a098258eb840b77ec002f8e0741c68da470b378e..f2f61365f27e5a545fdce8b23660f1a88237ed8b 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/ISISDataArchive.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/ISISDataArchive.h
@@ -13,6 +13,7 @@
 #include "MantidAPI/IArchiveSearch.h"
 #include "MantidKernel/System.h"
 
+#include <sstream>
 #include <string>
 
 namespace Mantid {
@@ -31,9 +32,16 @@ public:
   getArchivePath(const std::set<std::string> &filenames,
                  const std::vector<std::string> &exts) const override;
 
-private:
-  /// Queries the archive & returns the path to a single file.
+  /// Public and virtual for testing purposes
+  virtual std::string
+  getCorrectExtension(const std::string &path,
+                      const std::vector<std::string> &exts) const;
   std::string getPath(const std::string &fName) const;
+
+protected:
+  /// Queries the archive & returns the path to a single file.
+  virtual std::ostringstream sendRequest(const std::string &fName) const;
+  virtual bool fileExists(const std::string &path) const;
 };
 } // namespace DataHandling
 } // namespace Mantid
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h b/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h
index 549a68d77d87e2501fed72631df30b1f1a8c82b6..006e80d7f7502ebd4579a180165e7fb0e85e8e10 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h
@@ -91,11 +91,11 @@ void ReadEventFile(IReader &loader, IEventHandler &handler, IProgress &progress,
                    int32_t def_clock_scale, bool use_tx_chopper) {
   // read file headers (base header then packed-format header)
   EventFileHeader_Base hdr_base;
-  if (!loader.read(reinterpret_cast<int8_t *>(&hdr_base), sizeof(hdr_base)))
+  if (!loader.read(reinterpret_cast<char *>(&hdr_base), sizeof(hdr_base)))
     throw std::runtime_error("unable to load EventFileHeader-Base");
 
   EventFileHeader_Packed hdr_packed;
-  if (!loader.read(reinterpret_cast<int8_t *>(&hdr_packed), sizeof(hdr_packed)))
+  if (!loader.read(reinterpret_cast<char *>(&hdr_packed), sizeof(hdr_packed)))
     throw std::runtime_error("unable to load EventFileHeader-Packed");
 
   if (hdr_base.magic_number != EVENTFILEHEADER_BASE_MAGIC_NUMBER)
@@ -185,7 +185,7 @@ void ReadEventFile(IReader &loader, IEventHandler &handler, IProgress &progress,
 
     // read next byte
     uint8_t ch;
-    if (!loader.read(reinterpret_cast<int8_t *>(&ch), 1))
+    if (!loader.read(reinterpret_cast<char *>(&ch), 1))
       break;
 
     int32_t nbits_ch_used = 0; // no bits used initially, 8 to go
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadAsciiStl.h b/Framework/DataHandling/inc/MantidDataHandling/LoadAsciiStl.h
index 9559fe2e6f7a74f7b71c77f9d2bc35b2869e1a3e..efe155be8575641942f7c85380f9405542fe8ffe 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadAsciiStl.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadAsciiStl.h
@@ -1,17 +1,31 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #ifndef MANTID_DATAHANDLING_LOADASCIISTL_H_
 #define MANTID_DATAHANDLING_LOADASCIISTL_H_
 #include "MantidDataHandling/LoadStl.h"
-#include "MantidGeometry/Objects/MeshObject.h"
-#include "MantidKernel/V3D.h"
-#include <fstream>
+#include <iosfwd>
 namespace Mantid {
+
+namespace Kernel {
+class V3D;
+}
+
+namespace Geometry {
+class MeshObject;
+}
 namespace DataHandling {
 
 class DLLExport LoadAsciiStl : LoadStl {
 public:
   LoadAsciiStl(std::string filename) : LoadStl(filename) {}
+  LoadAsciiStl(std::string filename, ReadMaterial::MaterialParameters params)
+      : LoadStl(filename, params) {}
   std::unique_ptr<Geometry::MeshObject> readStl() override;
-  bool isAsciiSTL();
+  static bool isAsciiSTL(std::string filename);
 
 private:
   int m_lineNumber = 0;
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h b/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h
index e141f4c63d9c925b06c935dfc5aad15e633824a7..61d61b1b25402027102bd980ce5f689d8dcfcac9 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h
@@ -1,10 +1,21 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #ifndef MANTID_DATAHANDLING_LOADBINARYSTL_H_
 #define MANTID_DATAHANDLING_LOADBINARYSTL_H_
 #include "MantidDataHandling/LoadStl.h"
-#include "MantidGeometry/Objects/MeshObject.h"
-#include "MantidKernel/BinaryStreamReader.h"
 
 namespace Mantid {
+namespace Kernel {
+class BinaryStreamReader;
+}
+
+namespace Geometry {
+class MeshObject;
+}
 namespace DataHandling {
 
 class DLLExport LoadBinaryStl : LoadStl {
@@ -14,12 +25,13 @@ public:
   static constexpr uint32_t TRIANGLE_COUNT_DATA_SIZE = 4;
   static constexpr uint32_t VECTOR_DATA_SIZE = 12;
   LoadBinaryStl(std::string filename) : LoadStl(filename) {}
+  LoadBinaryStl(std::string filename, ReadMaterial::MaterialParameters params)
+      : LoadStl(filename, params) {}
   std::unique_ptr<Geometry::MeshObject> readStl() override;
-  bool isBinarySTL();
+  static bool isBinarySTL(std::string filename);
 
 private:
-  uint32_t getNumberTriangles(Kernel::BinaryStreamReader);
-  void readTriangle(Kernel::BinaryStreamReader);
+  void readTriangle(Kernel::BinaryStreamReader, uint32_t &vertexCount);
 };
 
 } // namespace DataHandling
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h b/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h
index 829ac8d3fd84ce7c27ee7d9bde58324982297b9f..d74c777d737a099e26a9fcbf17fb9e8de4dedb84 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h
@@ -20,24 +20,6 @@ using ANSTO::EventVector_pt;
 
 /*
 Loads an ANSTO EMU event file and stores it in an event workspace.
-LoadEMU is an algorithm and as such inherits  from the Algorithm class,
-via DataHandlingCommand, and overrides the init() & exec() methods.
-
-Required Properties:
-<UL>
-<LI> Filename - Name of and path to the input event file</LI>
-<LI> OutputWorkspace - Name of the workspace which stores the data</LI>
-</UL>
-
-Optional Properties:
-<UL>
-<LI> Mask - The input filename of the mask data</LI>
-<LI> SelectDetectorTubes - Range of detector tubes to be loaded</LI>
-<LI> OverrideDopplerPhase - Override the Doppler phase (degrees)</LI>
-<LI> FilterByTimeStart - Only include events after the start time</LI>
-<LI> FilterByTimeStop - Only include events before the stop time</LI>
-<LI> LoadAsRawDopplerTime - Save event time relative the Doppler</LI>
-</UL>
 
 @author Geish Miladinovic (ANSTO)
 
@@ -63,42 +45,32 @@ File change history is stored at: <https://github.com/mantidproject/mantid>.
 Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
 
-class DLLExport LoadEMU : public API::IFileLoader<Kernel::FileDescriptor> {
+template <typename FD> class LoadEMU : public API::IFileLoader<FD> {
 
-public:
-  // description
-  int version() const override { return 1; }
-  const std::vector<std::string> seeAlso() const override {
-    return {"Load", "LoadQKK"};
-  }
-  const std::string name() const override { return "LoadEMU"; }
-  const std::string category() const override { return "DataHandling\\ANSTO"; }
-  const std::string summary() const override {
-    return "Loads an EMU data file into a workspace.";
-  }
-
-  // returns a confidence value that this algorithm can load a specified file
-  int confidence(Kernel::FileDescriptor &descriptor) const override;
+protected:
+  using Base = API::IFileLoader<FD>;
 
-private:
-  // initialisation
-  void init() override;
+  // detailed init and exec code
+  void init(bool hdfLoader);
+  void exec(const std::string &hdfFile, const std::string &eventFile);
 
-  // execution
-  void exec() override;
-
-  // region of intreset
-  static std::vector<bool> createRoiVector(const std::string &seltubes,
-                                           const std::string &maskfile);
+private:
+  using Base::exec;
+  using Base::init;
+  // region of intereset
+  std::vector<bool> createRoiVector(const std::string &seltubes,
+                                    const std::string &maskfile);
 
+protected:
   // load parameters from input file
-  void loadParameters(ANSTO::Tar::File &tarFile, API::LogManager &logm);
+  void loadParameters(const std::string &hdfFile, API::LogManager &logm);
+  void loadEnvironParameters(const std::string &hdfFile, API::LogManager &logm);
 
   // load the instrument definition and instrument parameters
   void loadInstrument();
 
   // create workspace
-  void createWorkspace(ANSTO::Tar::File &tarFile);
+  void createWorkspace(const std::string &title);
 
   // dynamically update the neutronic position
   void loadDetectorL2Values();
@@ -107,28 +79,110 @@ private:
   // load and log the Doppler parameters
   void loadDopplerParameters(API::LogManager &logm);
 
+  // calibrate doppler phase
+  void calibrateDopplerPhase(const std::vector<size_t> &eventCounts,
+                             const std::vector<EventVector_pt> &eventVectors);
+  void dopplerTimeToTOF(std::vector<EventVector_pt> &eventVectors,
+                        double &minTOF, double &maxTOF);
+
   // prepare event storage
   void prepareEventStorage(ANSTO::ProgressTracker &prog,
-                           std::vector<size_t> &eventCounts,
+                           const std::vector<size_t> &eventCounts,
                            std::vector<EventVector_pt> &eventVectors);
 
   // set up the detector masks
   void setupDetectorMasks(std::vector<bool> &roi);
 
-  // binary file access
-  template <class EventProcessor>
-  static void loadEvents(API::Progress &prog, const char *progMsg,
-                         ANSTO::Tar::File &tarFile,
-                         EventProcessor &eventProcessor);
-
   // shared member variables
   DataObjects::EventWorkspace_sptr m_localWorkspace;
   std::vector<double> m_detectorL2;
+  int32_t m_datasetIndex;
+  std::string m_startRun;
 
   // Doppler characteristics
   double m_dopplerAmpl;
   double m_dopplerFreq;
   double m_dopplerPhase;
+  int32_t m_dopplerRun;
+  bool m_calibrateDoppler;
+};
+
+// Implemented the two classes explicitly rather than through specialization as
+// the instantiation and linking did not behave consistently across platforms.
+
+extern template class LoadEMU<Kernel::FileDescriptor>;
+extern template class LoadEMU<Kernel::NexusDescriptor>;
+
+/** LoadEMUTar : Loads a merged ANSTO EMU Hdf and event file into a workspace.
+
+Required Properties:
+<UL>
+<LI> Filename - Name of and path to the input event file</LI>
+<LI> OutputWorkspace - Name of the workspace which stores the data</LI>
+</UL>
+
+Optional Properties:
+<UL>
+<LI> Mask - The input filename of the mask data</LI>
+<LI> SelectDetectorTubes - Range of detector tubes to be loaded</LI>
+<LI> OverrideDopplerFrequency - Override the Doppler frequency (Hz)</LI>
+<LI> OverrideDopplerPhase - Override the Doppler phase (degrees)</LI>
+<LI> CalibrateDopplerPhase - Calibrate the Doppler phase prior to TOF</LI>
+<LI> LoadAsRawDopplerTime - Save event time relative the Doppler</LI>
+<LI> FilterByTimeStart - Only include events after the start time</LI>
+<LI> FilterByTimeStop - Only include events before the stop time</LI>
+</UL>
+
+*/
+class DLLExport LoadEMUTar : public LoadEMU<Kernel::FileDescriptor> {
+public:
+  int version() const override;
+  const std::vector<std::string> seeAlso() const override;
+  const std::string category() const override;
+  const std::string name() const override;
+  const std::string summary() const override;
+  int confidence(Kernel::FileDescriptor &descriptor) const override;
+
+private:
+  void exec() override;
+  void init() override;
+};
+
+/** LoadEMUHdf : Loads an ANSTO EMU Hdf and linked event file into a workspace.
+
+Required Properties:
+<UL>
+<LI> Filename - Name of and path to the input event file</LI>
+<LI> OutputWorkspace - Name of the workspace which stores the data</LI>
+</UL>
+
+Optional Properties:
+<UL>
+<LI> Mask - The input filename of the mask data</LI>
+<LI> SelectDetectorTubes - Range of detector tubes to be loaded</LI>
+<LI> OverrideDopplerFrequency - Override the Doppler frequency (Hz)</LI>
+<LI> OverrideDopplerPhase - Override the Doppler phase (degrees)</LI>
+<LI> CalibrateDopplerPhase - Calibrate the Doppler phase prior to TOF</LI>
+<LI> LoadAsRawDopplerTime - Save event time relative the Doppler</LI>
+<LI> FilterByTimeStart - Only include events after the start time</LI>
+<LI> FilterByTimeStop - Only include events before the stop time</LI>
+<LI> PathToBinaryEventFile - Rel or abs path to event file linked to hdf</LI>
+<LI> SelectDataset - Select the linked event dataset</LI>
+</UL>
+
+*/
+class DLLExport LoadEMUHdf : public LoadEMU<Kernel::NexusDescriptor> {
+public:
+  int version() const override;
+  const std::vector<std::string> seeAlso() const override;
+  const std::string category() const override;
+  const std::string name() const override;
+  const std::string summary() const override;
+  int confidence(Kernel::NexusDescriptor &descriptor) const override;
+
+private:
+  void exec() override;
+  void init() override;
 };
 
 } // namespace DataHandling
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadSampleEnvironment.h b/Framework/DataHandling/inc/MantidDataHandling/LoadSampleEnvironment.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca19c4d87309f43976180b9f60f5325fddbae6b5
--- /dev/null
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadSampleEnvironment.h
@@ -0,0 +1,62 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef DATAHANDLING_LOAD_ENVIRONMENT_H_
+#define DATAHANDLING_LOAD_ENVIRONMENT_H_
+
+#include "MantidAPI/Algorithm.h"
+
+namespace Mantid {
+namespace Geometry {
+class MeshObject;
+}
+namespace DataHandling {
+/**  Load Environment into the sample of a workspace, either replacing the
+   current environment, or replacing it, you may also set the material
+
+     The following file types are supported
+
+       STL file with suffix .stl
+ */
+
+class DLLExport LoadSampleEnvironment : public Mantid::API::Algorithm {
+public:
+  /// Algorithm's name for identification overriding a virtual method
+  const std::string name() const override { return "LoadSampleEnvironment"; };
+  /// Summary of algorithms purpose
+  const std::string summary() const override {
+    return "The algorithm loads an Environment into the instrument of a "
+           "workspace "
+           "at the sample.";
+  }
+
+  /// Algorithm's version for identification overriding a virtual method
+  int version() const override { return 1; };
+  /// Related algorithms
+  const std::vector<std::string> seeAlso() const override {
+    return {"CreateSampleEnvironment", "CopySample", "SetSampleMaterial",
+            "LoadSampleShape"};
+  }
+  /// Algorithm's category for identification overriding a virtual method
+  const std::string category() const override {
+    return "DataHandling\\Instrument";
+  }
+  boost::shared_ptr<Geometry::MeshObject>
+  translate(boost::shared_ptr<Geometry::MeshObject> environmentMesh);
+  boost::shared_ptr<Geometry::MeshObject>
+  rotate(boost::shared_ptr<Geometry::MeshObject> environmentMesh);
+  std::map<std::string, std::string> validateInputs() override;
+
+private:
+  // Implement abstract Algorithm methods
+  void init() override;
+  void exec() override;
+};
+
+} // end namespace DataHandling
+} // namespace Mantid
+
+#endif /* DATAHANDLING_LOAD_ENVIRONMENT_H_ */
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadSampleShape.h b/Framework/DataHandling/inc/MantidDataHandling/LoadSampleShape.h
index f30ebe1176d5e4b724ce8f614dc61d7e77cc8484..225902df063bec8a329e275e84f5c614092026f2 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadSampleShape.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadSampleShape.h
@@ -36,7 +36,8 @@ public:
   int version() const override { return 1; };
   /// Related algorithms
   const std::vector<std::string> seeAlso() const override {
-    return {"CreateSampleShape", "CopySample", "SetSampleMaterial"};
+    return {"CreateSampleShape", "CopySample", "SetSampleMaterial",
+            "LoadSampleEnvironment"};
   }
   /// Algorithm's category for identification overriding a virtual method
   const std::string category() const override {
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadStl.h b/Framework/DataHandling/inc/MantidDataHandling/LoadStl.h
index ee78eafd159ea53cce6432611fb80e1bd1ede4e1..13813bcbb8c34d8713a1f8a6963b059d9e4cbc23 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadStl.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadStl.h
@@ -1,24 +1,65 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #ifndef MANTID_DATAHANDLING_LOADSTL_H_
 #define MANTID_DATAHANDLING_LOADSTL_H_
-#include "MantidGeometry/Objects/MeshObject.h"
-#include <MantidKernel/V3D.h>
-
+#include "MantidDataHandling/ReadMaterial.h"
+#include "MantidKernel/Logger.h"
+#include "MantidKernel/V3D.h"
+#include <boost/functional/hash.hpp>
+#include <functional>
+#include <unordered_set>
+namespace {
+Mantid::Kernel::Logger g_logstl("LoadStl");
+}
 namespace Mantid {
+
+namespace Geometry {
+class MeshObject;
+}
 namespace DataHandling {
 
+struct HashV3DPair {
+  size_t operator()(const std::pair<Kernel::V3D, uint32_t> &v) const {
+    size_t seed = std::hash<double>{}(v.first.X());
+    boost::hash_combine(seed, v.first.Y());
+    boost::hash_combine(seed, v.first.Z());
+    return seed;
+  }
+};
+
+struct V3DTrueComparator {
+  bool operator()(const std::pair<Kernel::V3D, uint32_t> &v1,
+                  const std::pair<Kernel::V3D, uint32_t> &v2) const {
+    const Kernel::V3D diff = v1.first - v2.first;
+    const double nanoMetre = 1e-9;
+    return diff.norm() < nanoMetre;
+  }
+};
+
 class DLLExport LoadStl {
 public:
-  LoadStl(std::string filename) : m_filename(filename) {}
+  LoadStl(std::string filename) : m_filename(filename), m_setMaterial(false) {}
+  LoadStl(std::string filename, ReadMaterial::MaterialParameters params)
+      : m_filename(filename), m_setMaterial(true), m_params(params) {}
   virtual std::unique_ptr<Geometry::MeshObject> readStl() = 0;
 
 protected:
-  uint16_t addSTLVertex(Kernel::V3D &vertex);
-  bool areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2);
-  std::string m_filename;
-  std::vector<uint16_t> m_triangle;
+  bool areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2) const;
+  void changeToVector();
+  const std::string m_filename;
+  bool m_setMaterial;
+  ReadMaterial::MaterialParameters m_params;
+  std::vector<uint32_t> m_triangle;
   std::vector<Kernel::V3D> m_verticies;
+  std::unordered_set<std::pair<Kernel::V3D, uint32_t>, HashV3DPair,
+                     V3DTrueComparator>
+      vertexSet;
 };
 
 } // namespace DataHandling
 } // namespace Mantid
-#endif /* MANTID_DATAHANDLING_LOADSTL_H_ */
\ No newline at end of file
+#endif /* MANTID_DATAHANDLING_LOADSTL_H_ */
diff --git a/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h b/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h
index 34ba4f463d36b795874d33a7694463635dffd4ea..020d572225daf2705f733d543539f5f5ae6ace8d 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h
@@ -59,14 +59,14 @@ public:
    * @param params A struct containing all the parameters to be set.
    * @returns A map containing the relevent failure messages, if any.
    */
-  static ValidationErrors validateInputs(MaterialParameters params);
+  static ValidationErrors validateInputs(const MaterialParameters &params);
   /**
    * Set the parameters to build the material to the builder,
    * taking into account which values were and weren't set.
    *
    * @param params A struct containing all the parameters to be set.
    */
-  void setMaterialParameters(MaterialParameters params);
+  void setMaterialParameters(const MaterialParameters &params);
   /**
    * Construct the material,
    *
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SNSDataArchive.h b/Framework/DataHandling/inc/MantidDataHandling/SNSDataArchive.h
deleted file mode 100644
index d31f5a113f1e2058b035b5c9ed659863e8ba12cf..0000000000000000000000000000000000000000
--- a/Framework/DataHandling/inc/MantidDataHandling/SNSDataArchive.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2010 ISIS Rutherford Appleton Laboratory UKRI,
-//     NScD Oak Ridge National Laboratory, European Spallation Source
-//     & Institut Laue - Langevin
-// SPDX - License - Identifier: GPL - 3.0 +
-#ifndef MANTID_DATAHANDLING_SNSDATAARCHIVE_H_
-#define MANTID_DATAHANDLING_SNSDATAARCHIVE_H_
-
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/IArchiveSearch.h"
-#include "MantidKernel/System.h"
-
-#include <string>
-
-namespace Mantid {
-namespace DataHandling {
-/**
- This class is for searching the SNS data archive
- @date 02/22/2012
- */
-
-class DLLExport SNSDataArchive : public API::IArchiveSearch {
-public:
-  /// Find the archive location of a set of files.
-  std::string
-  getArchivePath(const std::set<std::string> &filenames,
-                 const std::vector<std::string> &exts) const override;
-};
-} // namespace DataHandling
-} // namespace Mantid
-
-#endif /* MANTID_DATAHANDLING_SNSDATAARCHIVE_H_ */
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SetSample.h b/Framework/DataHandling/inc/MantidDataHandling/SetSample.h
index 2303dfda7c29f50625634ed61f55ff68117c353d..dfe9085670a3f70d350b9acec87b8077cc9d504b 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SetSample.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SetSample.h
@@ -26,7 +26,7 @@ public:
   const std::string name() const override final;
   int version() const override final;
   const std::vector<std::string> seeAlso() const override {
-    return {"SetSampleMaterial", "CopySample", "SetBeam"};
+    return {"SetSampleMaterial", "CreateSampleShape", "CopySample", "SetBeam"};
   }
   const std::string category() const override final;
   const std::string summary() const override final;
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h b/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h
index 1cc15c249f081ca52abee07f08a2024a144b8fb6..f9b7bdff274be9297edec06a1f649bfb6d291e2e 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h
@@ -33,7 +33,7 @@ public:
   /// Algorithm's version
   int version() const override;
   const std::vector<std::string> seeAlso() const override {
-    return {"AbsorptionCorrection", "CreateSampleShape",
+    return {"SetSample", "AbsorptionCorrection", "CreateSampleShape",
             "CalculateSampleTransmission"};
   }
   /// Algorithm's category for identification
diff --git a/Framework/DataHandling/src/DownloadInstrument.cpp b/Framework/DataHandling/src/DownloadInstrument.cpp
index 8fccdbeda0105e8f735820635b6d29b856febd94..308a72fa5bc04f9e09c5e986e466cb32c5265268 100644
--- a/Framework/DataHandling/src/DownloadInstrument.cpp
+++ b/Framework/DataHandling/src/DownloadInstrument.cpp
@@ -91,6 +91,12 @@ void DownloadInstrument::init() {
 void DownloadInstrument::exec() {
   StringToStringMap fileMap;
   setProperty("FileDownloadCount", 0);
+
+  // to aid in general debugging, always ask github for what the rate limit
+  // status is. This doesn't count against rate limit.
+  GitHubApiHelper inetHelper;
+  g_log.information(inetHelper.getRateLimitDescription());
+
   try {
     fileMap = processRepository();
   } catch (Mantid::Kernel::Exception::InternetError &ex) {
@@ -117,16 +123,34 @@ void DownloadInstrument::exec() {
 
   for (auto &itMap : fileMap) {
     // download a file
-    doDownloadFile(itMap.first, itMap.second);
     if (boost::algorithm::ends_with(itMap.second, "Facilities.xml")) {
       g_log.notice("A new Facilities.xml file has been downloaded, this will "
                    "take effect next time Mantid is started.");
+    } else {
+      g_log.information() << "Downloading \"" << itMap.second << "\" from \""
+                          << itMap.first << "\"\n";
     }
+    doDownloadFile(itMap.first, itMap.second);
   }
 
   setProperty("FileDownloadCount", static_cast<int>(fileMap.size()));
 }
 
+namespace {
+// Converts a json chunk to a url for the raw file contents.
+std::string getDownloadUrl(Json::Value &contents) {
+  std::string url = contents.get("download_url", "").asString();
+  if (url.empty()) { // guess it from html url
+    url = contents.get("html_url", "").asString();
+    if (url.empty())
+      throw std::runtime_error("Failed to find download link");
+    url = url + "?raw=1";
+  }
+
+  return url;
+}
+} // namespace
+
 DownloadInstrument::StringToStringMap DownloadInstrument::processRepository() {
   // get the instrument directories
   auto instrumentDirs =
@@ -198,8 +222,7 @@ DownloadInstrument::StringToStringMap DownloadInstrument::processRepository() {
     if (filePath.getExtension() != "xml")
       continue;
     std::string sha = serverElement.get("sha", "").asString();
-    std::string htmlUrl =
-        getDownloadableRepoUrl(serverElement.get("html_url", "").asString());
+    std::string downloadUrl = getDownloadUrl(serverElement);
 
     // Find shas
     std::string localSha = getValueOrDefault(localShas, name, "");
@@ -208,14 +231,14 @@ DownloadInstrument::StringToStringMap DownloadInstrument::processRepository() {
     // this will also catch when file is only present on github (as local sha
     // will be "")
     if ((sha != installSha) && (sha != localSha)) {
-      fileMap.emplace(htmlUrl,
+      fileMap.emplace(downloadUrl,
                       filePath.toString()); // ACTION - DOWNLOAD to localPath
     } else if ((!localSha.empty()) && (sha == installSha) &&
                (sha != localSha)) // matches install, but different local
     {
-      fileMap.emplace(
-          htmlUrl,
-          filePath.toString()); // ACTION - DOWNLOAD to localPath and overwrite
+      fileMap.emplace(downloadUrl, filePath.toString()); // ACTION - DOWNLOAD to
+                                                         // localPath and
+                                                         // overwrite
     }
   }
 
@@ -333,15 +356,6 @@ size_t DownloadInstrument::removeOrphanedFiles(
   return filesToDelete.size();
 }
 
-/** Converts a github file page to a downloadable url for the file.
- * @param filename a github file page url
- * @returns a downloadable url for the file
- **/
-const std::string
-DownloadInstrument::getDownloadableRepoUrl(const std::string &filename) const {
-  return filename + "?raw=1";
-}
-
 /** Download a url and fetch it inside the local path given.
 This calls Kernel/InternetHelper, but is wrapped in this method to allow mocking
 in the unit tests.
diff --git a/Framework/DataHandling/src/ISISDataArchive.cpp b/Framework/DataHandling/src/ISISDataArchive.cpp
index 385890db76a8abaed08e7d21ea707b2572693970..2e148030744810baf7359fc231426bf5937dbbbc 100644
--- a/Framework/DataHandling/src/ISISDataArchive.cpp
+++ b/Framework/DataHandling/src/ISISDataArchive.cpp
@@ -17,8 +17,6 @@
 #include <Poco/File.h>
 #include <Poco/Path.h>
 
-#include <sstream>
-
 namespace Mantid {
 namespace DataHandling {
 namespace {
@@ -37,64 +35,106 @@ const char *URL_PREFIX = "http://data.isis.rl.ac.uk/where.py/unixdir?name=";
 }
 
 /**
- * Query the ISIS archive for a set of filenames & extensions. The method goes
- * through each extension
- * and checks whether it can find a match with each filename in the filenames
- * list. The first match is returned.
+ * Query the ISIS archive for a set of filenames and vector of extensions. The
+ * method gets a path to each of the filenames, and then loops over the
+ * extensions to find the correct file.
  * @param filenames :: A set of filenames without extensions
- * @param exts :: A list of extensions to try in order with each filename
+ * @param exts :: A vector of file extensions to search over.
  * @returns The full path to the first found
  */
 std::string
 ISISDataArchive::getArchivePath(const std::set<std::string> &filenames,
                                 const std::vector<std::string> &exts) const {
-  for (const auto &filename : filenames) {
-    g_log.debug() << filename << ")\n";
-  }
-  for (const auto &ext : exts) {
-    g_log.debug() << ext << ")\n";
+  if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG)) {
+    for (const auto &filename : filenames) {
+      g_log.debug() << filename << ")\n";
+    }
+    for (const auto &ext : exts) {
+      g_log.debug() << ext << ")\n";
+    }
   }
 
-  for (const auto &ext : exts) {
-    for (const auto &filename : filenames) {
-      const std::string fullPath = getPath(filename + ext);
+  for (const auto &filename : filenames) {
+    const std::string path_without_extension = getPath(filename);
+    if (!path_without_extension.empty()) {
+      std::string fullPath = getCorrectExtension(path_without_extension, exts);
       if (!fullPath.empty())
         return fullPath;
-    } // it
-  }   // ext
+    }
+  }
   return "";
 }
 
 /**
- * Calls a web service to get a full path to a file.
- * Only returns a full path string if the file exists
+ * Gets the path to the file, or most recent set of files.
  * @param fName :: The file name.
- * @return The path to the file or an empty string in case of error/non-existing
- * file.
+ * @return The path to the file or an empty string in case of empty filename
  */
 std::string ISISDataArchive::getPath(const std::string &fName) const {
   g_log.debug() << "ISISDataArchive::getPath() - fName=" << fName << "\n";
   if (fName.empty())
     return ""; // Avoid pointless call to service
 
+  std::ostringstream os = sendRequest(fName);
+  os << Poco::Path::separator() << fName;
+  const std::string expectedPath = os.str();
+  return expectedPath;
+}
+
+/** Calls a web service to get a path to a file.
+ * If the file does not exist, returns path to most recent run.
+ * The ISIS web service return a path independent of the file extension of the
+ * file provided. Thus the call to web service uses a file WITHOUT an extension.
+ * @param fName :: The file name.
+ * @return ostringstream object containing path to file (without filename
+ * itself)
+ */
+std::ostringstream
+ISISDataArchive::sendRequest(const std::string &fName) const {
   Kernel::InternetHelper inetHelper;
   std::ostringstream os;
   try {
     inetHelper.sendRequest(URL_PREFIX + fName, os);
-
-    os << Poco::Path::separator() << fName;
-    try {
-      const std::string expectedPath = os.str();
-      if (Poco::File(expectedPath).exists())
-        return expectedPath;
-    } catch (Poco::Exception &) {
-    }
   } catch (Kernel::Exception::InternetError &ie) {
-    g_log.warning() << "Could not access archive index " << ie.what();
+    g_log.warning() << "Could not access archive index." << ie.what();
   }
+  return os;
+}
 
+/**
+ * Given a path to a file, this searches over possible
+ * extensions to find the full path.
+ * Only returns a full path string if the file exists.
+ * @param path :: The path to the file without an extension.
+ * @param exts :: vector of possible file extensions to search over.
+ * @return The full path to the file or an empty string in case of
+ * error/non-existing file.
+ */
+std::string ISISDataArchive::getCorrectExtension(
+    const std::string &path, const std::vector<std::string> &exts) const {
+  for (const auto &ext : exts) {
+    std::string temp_path = path + ext;
+    if (fileExists(temp_path))
+      return temp_path;
+  }
   return "";
 }
 
+/**
+ * Checks if the given file path exists or not.
+ * This code is in a separate function so it
+ * can be mocked out in testing.
+ * @param path :: The path to the file (including extension)
+ * @return A bool. Whether or not the file exists.
+ */
+bool ISISDataArchive::fileExists(const std::string &path) const {
+  try {
+    if (Poco::File(path).exists())
+      return true;
+  } catch (Poco::Exception &) {
+  }
+  return false;
+}
+
 } // namespace DataHandling
 } // namespace Mantid
diff --git a/Framework/DataHandling/src/JoinISISPolarizationEfficiencies.cpp b/Framework/DataHandling/src/JoinISISPolarizationEfficiencies.cpp
index a622a10c415d0aa87672b10af65a86bacf33f94a..6230bc9173cc08577904a9735b801b6f38eca67e 100644
--- a/Framework/DataHandling/src/JoinISISPolarizationEfficiencies.cpp
+++ b/Framework/DataHandling/src/JoinISISPolarizationEfficiencies.cpp
@@ -134,9 +134,7 @@ MatrixWorkspace_sptr JoinISISPolarizationEfficiencies::createEfficiencies(
 
   for (size_t i = 0; i < interpolatedWorkspaces.size(); ++i) {
     auto &ws = interpolatedWorkspaces[i];
-    outWS->mutableX(i) = ws->x(0);
-    outWS->mutableY(i) = ws->y(0);
-    outWS->mutableE(i) = ws->e(0);
+    outWS->setHistogram(i, ws->histogram(0));
     axis1->setLabel(i, labels[i]);
   }
 
diff --git a/Framework/DataHandling/src/LoadAsciiStl.cpp b/Framework/DataHandling/src/LoadAsciiStl.cpp
index 9e3e22f3ccef2bbb5f4f9111813915750f94c078..5ccb388d749c457546003c1a7ba24ab5caa76b50 100644
--- a/Framework/DataHandling/src/LoadAsciiStl.cpp
+++ b/Framework/DataHandling/src/LoadAsciiStl.cpp
@@ -1,4 +1,11 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidDataHandling/LoadAsciiStl.h"
+#include "MantidGeometry/Objects/MeshObject.h"
 #include "MantidKernel/Exception.h"
 #include "MantidKernel/make_unique.h"
 #include <boost/algorithm/string.hpp>
@@ -8,8 +15,12 @@
 namespace Mantid {
 namespace DataHandling {
 
-bool LoadAsciiStl::isAsciiSTL() {
-  std::ifstream file(m_filename.c_str());
+bool LoadAsciiStl::isAsciiSTL(std::string filename) {
+  std::ifstream file(filename.c_str());
+  if (!file) {
+    // if the file cannot be read then it is not a valid asciiStl File
+    return false;
+  }
   std::string line;
   getline(file, line);
   boost::trim(line);
@@ -22,20 +33,43 @@ std::unique_ptr<Geometry::MeshObject> LoadAsciiStl::readStl() {
   getline(file, line);
   m_lineNumber++;
   Kernel::V3D t1, t2, t3;
+  uint32_t vertexCount = 0;
   while (readSTLTriangle(file, t1, t2, t3)) {
     // Add triangle if all 3 vertices are distinct
     if (!areEqualVertices(t1, t2) && !areEqualVertices(t1, t3) &&
         !areEqualVertices(t2, t3)) {
-      m_triangle.emplace_back(addSTLVertex(t1));
-      m_triangle.emplace_back(addSTLVertex(t2));
-      m_triangle.emplace_back(addSTLVertex(t3));
+      auto vertexPair = std::pair<Kernel::V3D, uint32_t>(t1, vertexCount);
+      auto emplacementResult = vertexSet.insert(vertexPair);
+      if (emplacementResult.second) {
+        vertexCount++;
+      }
+      m_triangle.emplace_back(emplacementResult.first->second);
+      vertexPair = std::pair<Kernel::V3D, uint32_t>(t2, vertexCount);
+      emplacementResult = vertexSet.insert(vertexPair);
+      if (emplacementResult.second) {
+        vertexCount++;
+      }
+      m_triangle.emplace_back(emplacementResult.first->second);
+      vertexPair = std::pair<Kernel::V3D, uint32_t>(t3, vertexCount);
+      emplacementResult = vertexSet.insert(vertexPair);
+      if (emplacementResult.second) {
+        vertexCount++;
+      }
+      m_triangle.emplace_back(emplacementResult.first->second);
     }
   }
-  // Use efficient constructor of MeshObject
-  std::unique_ptr<Geometry::MeshObject> retVal =
-      Kernel::make_unique<Geometry::MeshObject>(std::move(m_triangle),
-                                                std::move(m_verticies),
-                                                Mantid::Kernel::Material());
+  changeToVector();
+  Mantid::Kernel::Material material;
+  if (m_setMaterial) {
+    g_logstl.information("Setting Material");
+    ReadMaterial reader;
+    reader.setMaterialParameters(m_params);
+    material = *(reader.buildMaterial());
+  } else {
+    material = Mantid::Kernel::Material();
+  }
+  auto retVal = std::make_unique<Geometry::MeshObject>(
+      std::move(m_triangle), std::move(m_verticies), material);
   return retVal;
 }
 
diff --git a/Framework/DataHandling/src/LoadBinaryStl.cpp b/Framework/DataHandling/src/LoadBinaryStl.cpp
index 36992f6f12b4764422d47b6a2a112510b385619c..4620d354d52ba3ad711e23c349c98975b12a3eab 100644
--- a/Framework/DataHandling/src/LoadBinaryStl.cpp
+++ b/Framework/DataHandling/src/LoadBinaryStl.cpp
@@ -1,12 +1,41 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidDataHandling/LoadBinaryStl.h"
+#include "MantidGeometry/Objects/MeshObject.h"
+#include "MantidKernel/BinaryStreamReader.h"
+#include "MantidKernel/MultiThreaded.h"
 #include <Poco/File.h>
+#include <boost/functional/hash.hpp>
+#include <boost/make_shared.hpp>
+#include <chrono>
 #include <fstream>
-
+#include <string>
+#include <vector>
 namespace Mantid {
 namespace DataHandling {
 
-bool LoadBinaryStl::isBinarySTL() {
-  Poco::File stlFile = Poco::File(m_filename);
+namespace {
+uint32_t getNumberTriangles(Kernel::BinaryStreamReader streamReader,
+                            const int header) {
+  uint32_t numberTrianglesLong;
+  // skip header
+  streamReader.moveStreamToPosition(header);
+  // Read the number of triangles
+  streamReader >> numberTrianglesLong;
+  return numberTrianglesLong;
+}
+} // namespace
+
+bool LoadBinaryStl::isBinarySTL(std::string filename) {
+  Poco::File stlFile = Poco::File(filename);
+  if (!stlFile.exists()) {
+    // if the file cannot be read then it is not a valid binary Stl File
+    return false;
+  }
   auto fileSize = stlFile.getSize();
   if (fileSize < HEADER_SIZE + TRIANGLE_COUNT_DATA_SIZE) {
     // File is smaller than header plus number of triangles, cannot be binary
@@ -14,9 +43,9 @@ bool LoadBinaryStl::isBinarySTL() {
     return false;
   }
   uint32_t numberTrianglesLong;
-  std::ifstream myFile(m_filename.c_str(), std::ios::in | std::ios::binary);
+  std::ifstream myFile(filename.c_str(), std::ios::in | std::ios::binary);
   Kernel::BinaryStreamReader streamReader = Kernel::BinaryStreamReader(myFile);
-  numberTrianglesLong = getNumberTriangles(streamReader);
+  numberTrianglesLong = getNumberTriangles(streamReader, HEADER_SIZE);
   myFile.close();
   if (!(fileSize == (HEADER_SIZE + TRIANGLE_COUNT_DATA_SIZE +
                      (numberTrianglesLong * TRIANGLE_DATA_SIZE)))) {
@@ -28,40 +57,48 @@ bool LoadBinaryStl::isBinarySTL() {
   return true;
 }
 
-uint32_t
-LoadBinaryStl::getNumberTriangles(Kernel::BinaryStreamReader streamReader) {
-  uint32_t numberTrianglesLong;
-  // skip header
-  streamReader.moveStreamToPosition(HEADER_SIZE);
-  // Read the number of triangles
-  streamReader >> numberTrianglesLong;
-  return numberTrianglesLong;
-}
-
 std::unique_ptr<Geometry::MeshObject> LoadBinaryStl::readStl() {
   std::ifstream myFile(m_filename.c_str(), std::ios::in | std::ios::binary);
 
   Kernel::BinaryStreamReader streamReader = Kernel::BinaryStreamReader(myFile);
-  const auto numberTrianglesLong = getNumberTriangles(streamReader);
+  const auto numberTrianglesLong =
+      getNumberTriangles(streamReader, HEADER_SIZE);
   uint32_t nextToRead =
       HEADER_SIZE + TRIANGLE_COUNT_DATA_SIZE + VECTOR_DATA_SIZE;
   // now read in all the triangles
-
+  m_triangle.reserve(3 * numberTrianglesLong);
+  m_verticies.reserve(3 * numberTrianglesLong);
+  g_logstl.debug("Began reading " + std::to_string(numberTrianglesLong) +
+                 " triangles.");
+  uint32_t vertexCount = 0;
   for (uint32_t i = 0; i < numberTrianglesLong; i++) {
+
     // find next triangle, skipping the normal and attribute
     streamReader.moveStreamToPosition(nextToRead);
-    readTriangle(streamReader);
+    readTriangle(streamReader, vertexCount);
     nextToRead += TRIANGLE_DATA_SIZE;
   }
+  changeToVector();
+  m_verticies.shrink_to_fit();
+  m_triangle.shrink_to_fit();
+  g_logstl.debug("Read All");
   myFile.close();
-  std::unique_ptr<Geometry::MeshObject> retVal =
-      std::unique_ptr<Geometry::MeshObject>(new Geometry::MeshObject(
-          std::move(m_triangle), std::move(m_verticies),
-          Mantid::Kernel::Material()));
+  Mantid::Kernel::Material material;
+  if (m_setMaterial) {
+    g_logstl.information("Setting Material");
+    ReadMaterial reader;
+    reader.setMaterialParameters(m_params);
+    material = *(reader.buildMaterial());
+  } else {
+    material = Mantid::Kernel::Material();
+  }
+  auto retVal = std::make_unique<Geometry::MeshObject>(
+      std::move(m_triangle), std::move(m_verticies), material);
   return retVal;
 }
 
-void LoadBinaryStl::readTriangle(Kernel::BinaryStreamReader streamReader) {
+void LoadBinaryStl::readTriangle(Kernel::BinaryStreamReader streamReader,
+                                 uint32_t &vertexCount) {
   // read in the verticies
   for (int i = 0; i < 3; i++) {
     float xVal;
@@ -71,8 +108,14 @@ void LoadBinaryStl::readTriangle(Kernel::BinaryStreamReader streamReader) {
     streamReader >> yVal;
     streamReader >> zVal;
     Kernel::V3D vec = Kernel::V3D(double(xVal), double(yVal), double(zVal));
-    // add index of new vertex to triangle
-    m_triangle.push_back(addSTLVertex(vec));
+    auto vertexPair = std::pair<Kernel::V3D, uint32_t>(vec, vertexCount);
+    auto emplacementResult = vertexSet.insert(vertexPair);
+    // check if the value was new to the map and increase the value to assign to
+    // the next if so
+    if (emplacementResult.second) {
+      vertexCount++;
+    }
+    m_triangle.emplace_back(emplacementResult.first->second);
   }
 }
 
diff --git a/Framework/DataHandling/src/LoadDaveGrp.cpp b/Framework/DataHandling/src/LoadDaveGrp.cpp
index d587de34a0643ac73bf7d8c3a14294e377cf79cc..930c658f287b4a1d61112822225f4345db1cf5f6 100644
--- a/Framework/DataHandling/src/LoadDaveGrp.cpp
+++ b/Framework/DataHandling/src/LoadDaveGrp.cpp
@@ -181,8 +181,9 @@ void LoadDaveGrp::setWorkspaceAxes(API::MatrixWorkspace_sptr workspace,
                                    const std::vector<double> &yAxis) const {
 
   auto verticalAxis = workspace->getAxis(1);
+  auto sharedX = Kernel::make_cow<HistogramData::HistogramX>(xAxis);
   for (size_t i = 0; i < nGroups; i++) {
-    workspace->mutableX(i) = xAxis;
+    workspace->setSharedX(i, sharedX);
     verticalAxis->setValue(i, yAxis.at(i));
   }
 }
diff --git a/Framework/DataHandling/src/LoadEMU.cpp b/Framework/DataHandling/src/LoadEMU.cpp
index 3fe201297385a994454244fee3b9a6a3f9c3599f..6d25324d26c274818b130f09e18ebab925575a98 100644
--- a/Framework/DataHandling/src/LoadEMU.cpp
+++ b/Framework/DataHandling/src/LoadEMU.cpp
@@ -18,27 +18,30 @@
 #include "MantidKernel/UnitFactory.h"
 #include "MantidNexus/NexusClasses.h"
 
+#include <boost/filesystem.hpp>
 #include <boost/math/special_functions/round.hpp>
+#include <boost/math/tools/minima.hpp>
 
 #include <Poco/AutoPtr.h>
 #include <Poco/TemporaryFile.h>
 #include <Poco/Util/PropertyFileConfiguration.h>
 
-#include <math.h>
+#include <algorithm>
+#include <cmath>
+#include <cstdio>
+#include <fstream>
 
 namespace Mantid {
 namespace DataHandling {
 
-using namespace Kernel;
-
 namespace {
 
 // number of physical detectors
 constexpr size_t HORIZONTAL_TUBES = 16;
 constexpr size_t VERTICAL_TUBES = 35;
-constexpr size_t DETECTORS = HORIZONTAL_TUBES + VERTICAL_TUBES;
+constexpr size_t DETECTOR_TUBES = HORIZONTAL_TUBES + VERTICAL_TUBES;
 // analysed and direct detectors
-constexpr size_t HISTO_BINS_X = DETECTORS * 2;
+constexpr size_t HISTO_BINS_X = DETECTOR_TUBES * 2;
 constexpr size_t HISTO_BINS_Y = 1024;
 constexpr size_t HISTO_BINS_Y_DENUMERATOR = 16;
 constexpr size_t PIXELS_PER_TUBE = HISTO_BINS_Y / HISTO_BINS_Y_DENUMERATOR;
@@ -55,24 +58,41 @@ constexpr size_t Progress_Total =
 constexpr char FilenameStr[] = "Filename";
 constexpr char MaskStr[] = "Mask";
 constexpr char SelectDetectorTubesStr[] = "SelectDetectorTubes";
+constexpr char SelectDatasetStr[] = "SelectDataset";
+constexpr char OverrideDopplerFreqStr[] = "OverrideDopplerFrequency";
 constexpr char OverrideDopplerPhaseStr[] = "OverrideDopplerPhase";
 constexpr char FilterByTimeStartStr[] = "FilterByTimeStart";
 constexpr char FilterByTimeStopStr[] = "FilterByTimeStop";
 constexpr char RawDopplerTimeStr[] = "LoadAsRawDopplerTime";
+constexpr char CalibrateDopplerPhaseStr[] = "CalibrateDopplerPhase";
+constexpr char PathToBinaryStr[] = "BinaryEventPath";
 
 // Common pairing of limits
 using TimeLimits = std::pair<double, double>;
 
+template <typename Type>
+void AddSinglePointTimeSeriesProperty(API::LogManager &logManager,
+                                      const std::string &time,
+                                      const std::string &name,
+                                      const Type value) {
+  // create time series property and add single value
+  auto p = new Kernel::TimeSeriesProperty<Type>(name);
+  p->addValue(time, value);
+
+  // add to log manager
+  logManager.addProperty(p);
+}
+
 // Utility functions for loading values with defaults
 // Single value properties only support int, double, string and bool
 template <typename Type>
 Type GetNeXusValue(NeXus::NXEntry &entry, const std::string &path,
-                   const Type &defval) {
+                   const Type &defval, int32_t index) {
   try {
     NeXus::NXDataSetTyped<Type> dataSet = entry.openNXDataSet<Type>(path);
     dataSet.load();
 
-    return *dataSet();
+    return dataSet()[index];
   } catch (std::runtime_error &) {
     return defval;
   }
@@ -80,12 +100,12 @@ Type GetNeXusValue(NeXus::NXEntry &entry, const std::string &path,
 // string and double are special cases
 template <>
 double GetNeXusValue<double>(NeXus::NXEntry &entry, const std::string &path,
-                             const double &defval) {
+                             const double &defval, int32_t index) {
   try {
     NeXus::NXDataSetTyped<float> dataSet = entry.openNXDataSet<float>(path);
     dataSet.load();
 
-    return *dataSet();
+    return dataSet()[index];
   } catch (std::runtime_error &) {
     return defval;
   }
@@ -93,7 +113,7 @@ double GetNeXusValue<double>(NeXus::NXEntry &entry, const std::string &path,
 template <>
 std::string GetNeXusValue<std::string>(NeXus::NXEntry &entry,
                                        const std::string &path,
-                                       const std::string &defval) {
+                                       const std::string &defval, int32_t) {
 
   try {
     NeXus::NXChar dataSet = entry.openNXChar(path);
@@ -108,22 +128,36 @@ std::string GetNeXusValue<std::string>(NeXus::NXEntry &entry,
 template <typename T>
 void MapNeXusToProperty(NeXus::NXEntry &entry, const std::string &path,
                         const T &defval, API::LogManager &logManager,
-                        const std::string &name, const T &factor) {
+                        const std::string &name, const T &factor,
+                        int32_t index) {
 
-  T value = GetNeXusValue<T>(entry, path, defval);
+  T value = GetNeXusValue<T>(entry, path, defval, index);
   logManager.addProperty<T>(name, value * factor);
 }
 
 // sting is a special case
 template <>
-void MapNeXusToProperty<std::string>(
-    NeXus::NXEntry &entry, const std::string &path, const std::string &defval,
-    API::LogManager &logManager, const std::string &name, const std::string &) {
-
-  std::string value = GetNeXusValue<std::string>(entry, path, defval);
+void MapNeXusToProperty<std::string>(NeXus::NXEntry &entry,
+                                     const std::string &path,
+                                     const std::string &defval,
+                                     API::LogManager &logManager,
+                                     const std::string &name,
+                                     const std::string &, int32_t index) {
+
+  std::string value = GetNeXusValue<std::string>(entry, path, defval, index);
   logManager.addProperty<std::string>(name, value);
 }
 
+template <typename T>
+void MapNeXusToSeries(NeXus::NXEntry &entry, const std::string &path,
+                      const T &defval, API::LogManager &logManager,
+                      const std::string &time, const std::string &name,
+                      const T &factor, int32_t index) {
+
+  T value = GetNeXusValue<T>(entry, path, defval, index);
+  AddSinglePointTimeSeriesProperty<T>(logManager, time, name, value * factor);
+}
+
 // map the comma separated range of indexes to the vector via a lambda function
 // throws an exception if it is outside the vector range
 //
@@ -189,13 +223,15 @@ double invert(double y, const F &f, double x0 = 0.0, const double eps = 1e-16) {
 // the detectors is not constant so it needs to store the distance for each
 // Note that observation time and TOF are in usec
 //
+using TofData = std::tuple<double, double>;
+
 class ConvertTOF {
   const double m_w;
   const double m_phi;
   const double m_L0;
   const double m_v2;
   const double m_A;
-  std::vector<double> m_L2;
+  const std::vector<double> &m_L2;
 
   inline double L1(double t) const { return m_L0 + m_A * sin(m_w * t + m_phi); }
 
@@ -209,7 +245,7 @@ public:
       : m_w(2 * M_PI * freq), m_phi(M_PI * phase / 180.0), m_L0(L1), m_v2(v2),
         m_A(Amp), m_L2(L2) {}
 
-  double directTOF(size_t detID, double tobs) const {
+  TofData directTOF(size_t detID, double tobs) const {
 
     // observation time and tof are in usec
     auto tn = [=](double t) { return t + (L1(t) + m_L2[detID]) / v1(t); };
@@ -217,12 +253,12 @@ public:
     double tsec = tobs * 1.0e-6;
     double t0 = tsec - (m_L0 + m_L2[detID]) / m_v2;
     double tinv = invert(tsec, tn, t0);
-    double tof = m_L0 / v1(tinv) + m_L2[detID] / m_v2;
+    double tof = (m_L0 + m_L2[detID]) / v1(tinv);
 
-    return tof * 1.0e6;
+    return TofData(tinv * 1.0e6, tof * 1.0e6);
   }
 
-  double analysedTOF(size_t detID, double tobs) const {
+  TofData analysedTOF(size_t detID, double tobs) const {
     // observation time and tof are in usec
     auto tn = [=](double t) { return t + L1(t) / v1(t) + m_L2[detID] / m_v2; };
 
@@ -231,8 +267,69 @@ public:
     double t = invert(tsec, tn, t0);
     double tof = m_L0 / v1(t) + m_L2[detID] / m_v2;
 
-    return tof * 1.0e6;
+    return TofData(t * 1.0e6, tof * 1.0e6);
+  }
+};
+
+// calculate mean of a subset of the vector
+double maskedMean(const std::vector<double> &vec,
+                  const std::vector<bool> &mask) {
+  if (vec.size() == 0 || vec.size() != mask.size())
+    throw std::runtime_error("masked mean of empty or mismatched vectors");
+  double sum = 0.0;
+  size_t count = 0;
+  for (size_t i = 0; i != vec.size(); i++) {
+    if (!mask[i])
+      continue;
+    sum += vec[i];
+    count++;
   }
+  if (count == 0)
+    throw std::runtime_error("mean of empty vector");
+  return sum / static_cast<double>(count);
+}
+
+// calculate stdev fro a subset of the vector
+double maskedStdev(const std::vector<double> &vec,
+                   const std::vector<bool> &mask) {
+
+  auto avg = maskedMean(vec, mask);
+  size_t count = 0;
+  double sum = 0.0;
+  for (size_t i = 0; i != vec.size(); i++) {
+    if (!mask[i])
+      continue;
+    sum += (vec[i] - avg) * (vec[i] - avg);
+    count++;
+  }
+  return std::sqrt(sum / static_cast<double>(count));
+}
+
+// Simple reader that is compatible with the ASNTO event file loader
+class FileLoader {
+  std::ifstream _ifs;
+  size_t _size;
+
+public:
+  explicit FileLoader(const char *filename)
+      : _ifs(filename, std::ios::binary | std::ios::in) {
+    if (!_ifs.is_open() || _ifs.fail())
+      throw std::runtime_error("unable to open file");
+
+    _ifs.seekg(0, _ifs.end);
+    _size = _ifs.tellg();
+    _ifs.seekg(0, _ifs.beg);
+  }
+
+  bool read(char *s, std::streamsize n) {
+    return static_cast<bool>(_ifs.read(s, n));
+  }
+
+  size_t size() { return _size; }
+
+  size_t position() { return _ifs.tellg(); }
+
+  size_t selected_position() { return _ifs.tellg(); }
 };
 
 } // anonymous namespace
@@ -298,6 +395,13 @@ public:
     // length test in seconds
     return m_framePeriod * static_cast<double>(m_frames) * 1.0e-6;
   }
+
+  inline int64_t frameStart() const {
+    // returns time in nanoseconds from start of test
+    auto start = m_framePeriod * static_cast<double>(m_frames);
+    return static_cast<int64_t>(start * 1.0e3);
+  }
+
   void addEvent(size_t x, size_t p, double tdop, double taux) {
 
     // check if in time boundaries
@@ -308,7 +412,7 @@ public:
     auto y = static_cast<size_t>(p / HISTO_BINS_Y_DENUMERATOR);
 
     // determine detector id and check limits
-    if (x >= DETECTORS || y >= m_stride)
+    if (x >= DETECTOR_TUBES || y >= m_stride)
       return;
 
     // map the raw detector index to the physical model
@@ -318,7 +422,7 @@ public:
     // longer background chopper rate
     double ptaux = fmod(taux, m_gatePeriod);
     if (ptaux >= m_directTaux.first && ptaux <= m_directTaux.second)
-      xid = xid + DETECTORS;
+      xid = xid + DETECTOR_TUBES;
     else if (!(ptaux >= m_analysedTaux.first && ptaux <= m_analysedTaux.second))
       return;
 
@@ -366,22 +470,34 @@ protected:
   const ConvertTOF &m_convertTOF;
   double m_tofMin;
   double m_tofMax;
+  int64_t m_startTime;
   bool m_saveAsTOF;
 
   void addEventImpl(size_t id, size_t x, size_t, double tobs) override {
 
-    // convert observation time to tof
+    // get the absolute time for the start of the frame
+    auto offset = m_startTime + frameStart();
+
+    // convert observation time to tof and set the pulse time
+    // relative to the start of the doppler cycle
     double tof = tobs;
-    if (m_saveAsTOF)
-      tof = x < DETECTORS ? m_convertTOF.analysedTOF(id, tobs)
-                          : m_convertTOF.directTOF(id, tobs);
+
+    if (m_saveAsTOF) {
+      double pulse;
+      if (x < DETECTOR_TUBES)
+        std::tie(pulse, tof) = m_convertTOF.analysedTOF(id, tobs);
+      else
+        std::tie(pulse, tof) = m_convertTOF.directTOF(id, tobs);
+      offset += static_cast<int64_t>(pulse * 1e3);
+    }
 
     if (m_tofMin > tof)
       m_tofMin = tof;
     if (m_tofMax < tof)
       m_tofMax = tof;
 
-    m_eventVectors[id]->push_back(tof);
+    auto ev = Types::Event::TofEvent(tof, Types::Core::DateAndTime(offset));
+    m_eventVectors[id]->push_back(ev);
   }
 
 public:
@@ -390,109 +506,114 @@ public:
                 const double framePeriod, const double gatePeriod,
                 const TimeLimits &timeBoundary, const TimeLimits &directLimits,
                 const TimeLimits &analysedLimits, ConvertTOF &convert,
-                std::vector<EventVector_pt> &eventVectors, bool saveAsTOF)
+                std::vector<EventVector_pt> &eventVectors, int64_t startTime,
+                bool saveAsTOF)
       : EventProcessor(roi, mapIndex, stride, framePeriod, gatePeriod,
                        timeBoundary, directLimits, analysedLimits),
         m_eventVectors(eventVectors), m_convertTOF(convert),
         m_tofMin(std::numeric_limits<double>::max()),
-        m_tofMax(std::numeric_limits<double>::min()), m_saveAsTOF(saveAsTOF) {}
+        m_tofMax(std::numeric_limits<double>::min()), m_startTime(startTime),
+        m_saveAsTOF(saveAsTOF) {}
 
   double tofMin() const { return m_tofMin <= m_tofMax ? m_tofMin : 0.0; }
   double tofMax() const { return m_tofMin <= m_tofMax ? m_tofMax : 0.0; }
 };
-} // namespace EMU
 
-// register the algorithm into the AlgorithmFactory
-DECLARE_FILELOADER_ALGORITHM(LoadEMU)
+template <typename EP>
+void loadEvents(API::Progress &prog, const char *progMsg,
+                const std::string &eventFile, EP &eventProcessor) {
 
-/**
- * Return the confidence value that this algorithm can load the file
- * @param descriptor A descriptor for the file
- * @returns An integer specifying the confidence level. 0 indicates it will not
- * be used
- */
-int LoadEMU::confidence(Kernel::FileDescriptor &descriptor) const {
-  if (descriptor.extension() != ".tar")
-    return 0;
+  using namespace ANSTO;
 
-  ANSTO::Tar::File file(descriptor.filename());
-  if (!file.good())
-    return 0;
+  prog.doReport(progMsg);
 
-  size_t hdfFiles = 0;
-  size_t binFiles = 0;
-  const std::vector<std::string> &subFiles = file.files();
-  for (const auto &subFile : subFiles) {
-    auto len = subFile.length();
-    if ((len > 4) &&
-        (subFile.find_first_of("\\/", 0, 2) == std::string::npos)) {
-      if ((subFile.rfind(".hdf") == len - 4) &&
-          (subFile.compare(0, 3, "EMU") == 0))
-        hdfFiles++;
-      else if (subFile.rfind(".bin") == len - 4)
-        binFiles++;
-    }
-  }
+  FileLoader loader(eventFile.c_str());
 
-  return (hdfFiles == 1) && (binFiles == 1) ? 50 : 0;
+  // for progress notifications
+  ANSTO::ProgressTracker progTracker(prog, progMsg, loader.size(),
+                                     Progress_LoadBinFile);
+
+  ReadEventFile(loader, eventProcessor, progTracker, 100, false);
 }
 
-/**
- * Initialise the algorithm. Declare properties which can be set before
- * execution (input) and
- * read from after the execution (output).
- */
-void LoadEMU::init() {
+} // namespace EMU
+
+/// Declares the properties for the two loader variants. Adds the path option
+/// to the binary file and dataset set index if it is the \p hdfLoader.
+template <typename FD> void LoadEMU<FD>::init(bool hdfLoader) {
+
   // Specify file extensions which can be associated with a specific file.
   std::vector<std::string> exts;
 
   // Declare the Filename algorithm property. Mandatory. Sets the path to the
   // file to load.
   exts.clear();
-  exts.emplace_back(".tar");
-  declareProperty(Kernel::make_unique<API::FileProperty>(
-                      FilenameStr, "", API::FileProperty::Load, exts),
-                  "The input filename of the stored data");
+  if (hdfLoader)
+    exts.emplace_back(".hdf");
+  else
+    exts.emplace_back(".tar");
+  Base::declareProperty(Kernel::make_unique<API::FileProperty>(
+                            FilenameStr, "", API::FileProperty::Load, exts),
+                        "The input filename of the stored data");
+
+  if (hdfLoader) {
+    Base::declareProperty(
+        PathToBinaryStr, "",
+        "Relative or absolute path to the compressed binary\n"
+        "event file linked to the HDF file, eg /storage/data/");
+  }
 
   // mask
   exts.clear();
   exts.emplace_back(".xml");
-  declareProperty(Kernel::make_unique<API::FileProperty>(
-                      MaskStr, "", API::FileProperty::OptionalLoad, exts),
-                  "The input filename of the mask data");
+  Base::declareProperty(Kernel::make_unique<API::FileProperty>(
+                            MaskStr, "", API::FileProperty::OptionalLoad, exts),
+                        "The input filename of the mask data");
 
-  declareProperty(SelectDetectorTubesStr, "",
-                  "Comma separated range of detectors tubes to be loaded,\n"
-                  "  e.g. 16,19-45,47");
+  Base::declareProperty(
+      SelectDetectorTubesStr, "",
+      "Comma separated range of detectors tubes to be loaded,\n"
+      "  eg 16,19-45,47");
 
-  declareProperty(
+  Base::declareProperty(
       Kernel::make_unique<API::WorkspaceProperty<API::IEventWorkspace>>(
           "OutputWorkspace", "", Kernel::Direction::Output));
 
-  declareProperty(OverrideDopplerPhaseStr, EMPTY_DBL(),
-                  "Override the Doppler phase, in degrees.");
+  if (hdfLoader) {
+    Base::declareProperty(SelectDatasetStr, 0,
+                          "Select the index for the dataset to be loaded.");
+  }
 
-  declareProperty(RawDopplerTimeStr, false,
-                  "Import file as observed time relative the Doppler "
-                  "drive, in microsecs.");
+  Base::declareProperty(OverrideDopplerFreqStr, EMPTY_DBL(),
+                        "Override the Doppler frequency, in Hertz.");
 
-  declareProperty(FilterByTimeStartStr, 0.0,
-                  "Only include events after the provided start time, in "
-                  "seconds (relative to the start of the run).");
+  Base::declareProperty(OverrideDopplerPhaseStr, EMPTY_DBL(),
+                        "Override the Doppler phase, in degrees.");
 
-  declareProperty(FilterByTimeStopStr, EMPTY_DBL(),
-                  "Only include events before the provided stop time, in "
-                  "seconds (relative to the start of the run).");
+  Base::declareProperty(CalibrateDopplerPhaseStr, false,
+                        "Calibrate the Doppler phase prior to TOF conversion,\n"
+                        "ignored if imported as Doppler time or phase entered");
+
+  Base::declareProperty(RawDopplerTimeStr, false,
+                        "Import file as observed time relative the Doppler\n"
+                        "drive, in microsecs.");
+
+  Base::declareProperty(FilterByTimeStartStr, 0.0,
+                        "Only include events after the provided start time, in "
+                        "seconds (relative to the start of the run).");
+
+  Base::declareProperty(FilterByTimeStopStr, EMPTY_DBL(),
+                        "Only include events before the provided stop time, in "
+                        "seconds (relative to the start of the run).");
 
   std::string grpOptional = "Filters";
-  setPropertyGroup(FilterByTimeStartStr, grpOptional);
-  setPropertyGroup(FilterByTimeStopStr, grpOptional);
+  Base::setPropertyGroup(FilterByTimeStartStr, grpOptional);
+  Base::setPropertyGroup(FilterByTimeStopStr, grpOptional);
 }
 
-/**
- * Creates an event workspace and sets the title.
- */
-void LoadEMU::createWorkspace(ANSTO::Tar::File &tarFile) {
+/// Creates an event workspace and sets the \p title.
+template <typename FD>
+void LoadEMU<FD>::createWorkspace(const std::string &title) {
 
   // Create the workspace
   m_localWorkspace = boost::make_shared<DataObjects::EventWorkspace>();
@@ -504,58 +625,53 @@ void LoadEMU::createWorkspace(ANSTO::Tar::File &tarFile) {
   m_localWorkspace->setYUnit("Counts");
 
   // set title
-  const std::vector<std::string> &subFiles = tarFile.files();
-  for (const auto &subFile : subFiles)
-    if (subFile.compare(0, 3, "EMU") == 0) {
-      std::string title = subFile;
-
-      if (title.rfind(".hdf") == title.length() - 4)
-        title.resize(title.length() - 4);
-
-      if (title.rfind(".nx") == title.length() - 3)
-        title.resize(title.length() - 3);
-
-      m_localWorkspace->setTitle(title);
-      break;
-    }
+  m_localWorkspace->setTitle(title);
 }
 
-/**
- * Execute the algorithm. The steps involved are:
- *   Get the instrument properties and load options
- *   Create the workspace
- *   Load the instrument from the IDF
- *   Reposition the relevant neutronic values for model based on the parameters
- *   Load the data values and convert to TOF
- *   Setting up the masks
- */
-void LoadEMU::exec() {
+/// Execute the algorithm using the \p hdfFile and \p eventFile.
+/// The steps involved are:
+///   Create the workspace
+///   Get the instrument properties and load options
+///   Load the instrument from the IDF
+///   Reposition the relevant neutronic values for model based on the parameters
+///   Load the data values and convert to TOF
+///   Setting up the masks
+template <typename FD>
+void LoadEMU<FD>::exec(const std::string &hdfFile,
+                       const std::string &eventFile) {
+
+  namespace fs = boost::filesystem;
 
   // Create workspace
   // ----------------
-  std::string filename = getPropertyValue(FilenameStr);
-  ANSTO::Tar::File tarFile(filename);
-  if (!tarFile.good())
-    throw std::invalid_argument("invalid EMU file");
-  createWorkspace(tarFile);
+  fs::path p = hdfFile;
+  for (; !p.extension().empty();)
+    p = p.stem();
+  std::string title = p.generic_string();
+  createWorkspace(title);
   API::LogManager &logManager = m_localWorkspace->mutableRun();
   API::Progress prog(this, 0.0, 1.0, Progress_Total);
 
   // Load instrument and workspace properties
   // ----------------------------------------
-  loadParameters(tarFile, logManager);
+  logManager.addProperty(SelectDatasetStr, m_datasetIndex);
+  loadParameters(hdfFile, logManager);
   prog.doReport("creating instrument");
   loadInstrument();
 
-  // Get the region of interest and filters
+  // Get the region of interest and filters and save to log
   //
-  std::string maskfile = getPropertyValue(MaskStr);
-  std::string seltubes = getPropertyValue(SelectDetectorTubesStr);
+  std::string maskfile = Base::getPropertyValue(MaskStr);
+  std::string seltubes = Base::getPropertyValue(SelectDetectorTubesStr);
+  logManager.addProperty(SelectDetectorTubesStr, seltubes);
+  logManager.addProperty(MaskStr, maskfile);
+
   std::vector<bool> roi = createRoiVector(seltubes, maskfile);
-  double timeMaxBoundary = getProperty(FilterByTimeStopStr);
-  if (isEmpty(timeMaxBoundary))
+  double timeMaxBoundary = Base::getProperty(FilterByTimeStopStr);
+  if (Base::isEmpty(timeMaxBoundary))
     timeMaxBoundary = std::numeric_limits<double>::infinity();
-  TimeLimits timeBoundary(getProperty(FilterByTimeStartStr), timeMaxBoundary);
+  TimeLimits timeBoundary(Base::getProperty(FilterByTimeStartStr),
+                          timeMaxBoundary);
 
   // lambda to simplify loading instrument parameters
   auto instr = m_localWorkspace->getInstrument();
@@ -568,14 +684,14 @@ void LoadEMU::exec() {
   // sequence starting from 0
   //
   double sampleAnalyser = iparam("SampleAnalyser");
-  auto endID = static_cast<detid_t>(DETECTORS * PIXELS_PER_TUBE);
+  auto endID = static_cast<detid_t>(DETECTOR_TUBES * PIXELS_PER_TUBE);
   for (detid_t detID = 0; detID < endID; detID++)
     updateNeutronicPostions(detID, sampleAnalyser);
 
   // get the detector map from raw input to a physical detector
   //
   std::string dmapStr = instr->getParameterAsString("DetectorMap");
-  std::vector<size_t> detMapIndex = std::vector<size_t>(DETECTORS, 0);
+  std::vector<size_t> detMapIndex = std::vector<size_t>(DETECTOR_TUBES, 0);
   mapRangeToIndex(dmapStr, detMapIndex, [](size_t n) { return n; });
 
   // Collect the L2 distances, Doppler characteristics and
@@ -587,12 +703,8 @@ void LoadEMU::exec() {
   double framePeriod =
       1.0e6 / m_dopplerFreq; // period and max direct as microsec
   double sourceSample = iparam("SourceSample");
-  ConvertTOF convertTOF(m_dopplerAmpl, m_dopplerFreq, m_dopplerPhase,
-                        sourceSample, v2, m_detectorL2);
-
-  Types::Core::DateAndTime start_time(
-      logManager.getPropertyValueAsType<std::string>("StartTime"));
-  std::string time_str = start_time.toISO8601String();
+  ConvertTOF convertTOF(m_dopplerAmpl * m_dopplerRun, m_dopplerFreq,
+                        m_dopplerPhase, sourceSample, v2, m_detectorL2);
 
   // Load the events file
   // --------------------
@@ -614,48 +726,76 @@ void LoadEMU::exec() {
                             1000.0 * iparam("AnalysedTauxMax"));
 
   // fabs because the value can be negative
-  double gatePeriod = 1.0e6 / fabs(logManager.getPropertyValueAsType<double>(
-                                  "GraphiteChopperFrequency"));
+  double gatePeriod =
+      1.0e6 /
+      fabs(logManager.getTimeSeriesProperty<double>("GraphiteChopperFrequency")
+               ->firstValue());
 
   // count total events per pixel and reserve necessary memory
   EMU::EventCounter eventCounter(roi, detMapIndex, PIXELS_PER_TUBE, framePeriod,
                                  gatePeriod, timeBoundary, directLimits,
                                  analysedLimits, eventCounts);
-  loadEvents(prog, "loading neutron counts", tarFile, eventCounter);
+  EMU::loadEvents(prog, "loading neutron counts", eventFile, eventCounter);
   ANSTO::ProgressTracker progTracker(prog, "creating neutron event lists",
                                      numberHistograms, Progress_ReserveMemory);
   prepareEventStorage(progTracker, eventCounts, eventVectors);
 
-  // now perfrom the actual event collection and TOF convert if necessary
-  bool saveAsTOF = !getProperty(RawDopplerTimeStr);
-  EMU::EventAssigner eventAssigner(
-      roi, detMapIndex, PIXELS_PER_TUBE, framePeriod, gatePeriod, timeBoundary,
-      directLimits, analysedLimits, convertTOF, eventVectors, saveAsTOF);
-  loadEvents(prog, "loading neutron events (TOF)", tarFile, eventAssigner);
+  // now perform the actual event collection and TOF convert if necessary
+  // if a phase calibration is required then load it as raw doppler time
+  // perform the calibration and then convert to TOF
+  Types::Core::DateAndTime startTime(m_startRun);
+  auto start_nanosec = startTime.totalNanoseconds();
+  bool saveAsTOF = !Base::getProperty(RawDopplerTimeStr);
+  bool loadAsTOF = !m_calibrateDoppler && saveAsTOF;
+  EMU::EventAssigner eventAssigner(roi, detMapIndex, PIXELS_PER_TUBE,
+                                   framePeriod, gatePeriod, timeBoundary,
+                                   directLimits, analysedLimits, convertTOF,
+                                   eventVectors, start_nanosec, loadAsTOF);
+  EMU::loadEvents(prog, "loading neutron events (TOF)", eventFile,
+                  eventAssigner);
+
+  // perform a calibration and then TOF conversion if necessary
+  // and update the tof limits
+  auto minTOF = eventAssigner.tofMin();
+  auto maxTOF = eventAssigner.tofMax();
+  if (m_calibrateDoppler) {
+    calibrateDopplerPhase(eventCounts, eventVectors);
+    if (saveAsTOF) {
+      dopplerTimeToTOF(eventVectors, minTOF, maxTOF);
+    }
+  }
+  AddSinglePointTimeSeriesProperty<double>(logManager, m_startRun,
+                                           "DopplerPhase", m_dopplerPhase);
 
   // just to make sure the bins hold it all and setup the detector masks
   m_localWorkspace->setAllX(
-      HistogramData::BinEdges{std::max(0.0, floor(eventAssigner.tofMin())),
-                              eventAssigner.tofMax() + 1});
+      HistogramData::BinEdges{std::max(0.0, floor(minTOF)), maxTOF + 1});
   setupDetectorMasks(roi);
 
   // set log values
   auto frame_count = static_cast<int>(eventCounter.numFrames());
+  AddSinglePointTimeSeriesProperty<int>(logManager, m_startRun, "frame_count",
+                                        frame_count);
 
+  std::string filename = Base::getPropertyValue(FilenameStr);
   logManager.addProperty("filename", filename);
-  logManager.addProperty("frame_count", frame_count);
 
   Types::Core::time_duration duration = boost::posix_time::microseconds(
       static_cast<boost::int64_t>(eventCounter.duration() * 1.0e6));
-  Types::Core::DateAndTime end_time(start_time + duration);
-  logManager.addProperty("start_time", start_time.toISO8601String());
-  logManager.addProperty("end_time", end_time.toISO8601String());
+  Types::Core::DateAndTime endTime(startTime + duration);
+  logManager.addProperty("start_time", startTime.toISO8601String());
+  logManager.addProperty("end_time", endTime.toISO8601String());
+  logManager.addProperty<double>("dur", eventCounter.duration());
+
+  // Finally add the time-series parameter explicitly
+  loadEnvironParameters(hdfFile, logManager);
 
-  setProperty("OutputWorkspace", m_localWorkspace);
+  Base::setProperty("OutputWorkspace", m_localWorkspace);
 }
 
-// set up the detector masks
-void LoadEMU::setupDetectorMasks(std::vector<bool> &roi) {
+/// Set up the detector masks to the region of interest \p roi.
+template <typename FD>
+void LoadEMU<FD>::setupDetectorMasks(std::vector<bool> &roi) {
 
   // count total number of masked bins
   size_t maskedBins = 0;
@@ -672,17 +812,20 @@ void LoadEMU::setupDetectorMasks(std::vector<bool> &roi) {
       if (!roi[i])
         maskIndexList[maskIndex++] = i;
 
-    API::IAlgorithm_sptr maskingAlg = createChildAlgorithm("MaskDetectors");
+    API::IAlgorithm_sptr maskingAlg =
+        Base::createChildAlgorithm("MaskDetectors");
     maskingAlg->setProperty("Workspace", m_localWorkspace);
     maskingAlg->setProperty("WorkspaceIndexList", maskIndexList);
     maskingAlg->executeAsChildAlg();
   }
 }
 
-// prepare the event storage
-void LoadEMU::prepareEventStorage(ANSTO::ProgressTracker &progTracker,
-                                  std::vector<size_t> &eventCounts,
-                                  std::vector<EventVector_pt> &eventVectors) {
+/// Allocate space for the event storage in \p eventVectors after the
+/// \p eventCounts have been determined.
+template <typename FD>
+void LoadEMU<FD>::prepareEventStorage(
+    ANSTO::ProgressTracker &progTracker, const std::vector<size_t> &eventCounts,
+    std::vector<EventVector_pt> &eventVectors) {
 
   size_t numberHistograms = eventCounts.size();
   for (size_t i = 0; i != numberHistograms; ++i) {
@@ -701,14 +844,30 @@ void LoadEMU::prepareEventStorage(ANSTO::ProgressTracker &progTracker,
   progTracker.complete();
 }
 
-// get and log the Doppler parameters
-void LoadEMU::loadDopplerParameters(API::LogManager &logm) {
+/// Get the Doppler parameters and record to the log manager, \p logm.
+template <typename FD>
+void LoadEMU<FD>::loadDopplerParameters(API::LogManager &logm) {
 
   auto instr = m_localWorkspace->getInstrument();
-  m_dopplerFreq = logm.getPropertyValueAsType<double>("DopplerFrequency");
-  m_dopplerAmpl = logm.getPropertyValueAsType<double>("DopplerAmplitude");
-  m_dopplerPhase = getProperty(OverrideDopplerPhaseStr);
-  if (isEmpty(m_dopplerPhase)) {
+
+  // use nominal frequency based on amp and velocity unless overridden
+  m_dopplerAmpl =
+      logm.getTimeSeriesProperty<double>("DopplerAmplitude")->firstValue();
+  m_dopplerRun =
+      logm.getTimeSeriesProperty<int32_t>("DopplerRun")->firstValue();
+  m_dopplerFreq = Base::getProperty(OverrideDopplerFreqStr);
+  if (Base::isEmpty(m_dopplerFreq)) {
+    auto doppVel =
+        logm.getTimeSeriesProperty<double>("DopplerVelocity")->firstValue();
+    m_dopplerFreq = 0.5 * doppVel / (M_PI * m_dopplerAmpl);
+  }
+  AddSinglePointTimeSeriesProperty<double>(logm, m_startRun, "DopplerFrequency",
+                                           m_dopplerFreq);
+
+  m_dopplerPhase = Base::getProperty(OverrideDopplerPhaseStr);
+  m_calibrateDoppler = Base::getProperty(CalibrateDopplerPhaseStr) &&
+                       Base::isEmpty(m_dopplerPhase);
+  if (Base::isEmpty(m_dopplerPhase)) {
     // sinusoidal motion crossing a threshold with a delay
     double doppThreshold =
         instr->getNumberParameter("DopplerReferenceThreshold")[0];
@@ -717,11 +876,135 @@ void LoadEMU::loadDopplerParameters(API::LogManager &logm) {
         180.0 - asin(0.001 * doppThreshold / m_dopplerAmpl) * 180.0 / M_PI +
         doppDelay * m_dopplerFreq;
   }
-  logm.addProperty<double>("DopplerPhase", m_dopplerPhase);
+
+  // problem adding 'bool' to log
+  int32_t calPhase = (m_calibrateDoppler ? 1 : 0);
+  logm.addProperty("CalibratePhase", calPhase);
+}
+
+/// Calibrate the doppler phase based on the analysed events using
+/// the \p eventCounts and \p eventVectors.
+template <typename FD>
+void LoadEMU<FD>::calibrateDopplerPhase(
+    const std::vector<size_t> &eventCounts,
+    const std::vector<EventVector_pt> &eventVectors) {
+
+  // get the doppler parameters
+  auto instr = m_localWorkspace->getInstrument();
+  double v2 = instr->getNumberParameter("AnalysedV2")[0];
+  double l1 = instr->getNumberParameter("SourceSample")[0];
+
+  // get the number of analysed events and initial doppler time
+  auto startID = static_cast<size_t>(HORIZONTAL_TUBES * PIXELS_PER_TUBE);
+  auto endID = static_cast<size_t>(DETECTOR_TUBES * PIXELS_PER_TUBE);
+  size_t numEvents = 0;
+  for (size_t i = startID; i < endID; i++)
+    numEvents += eventCounts[i];
+  if (numEvents == 0)
+    throw std::runtime_error("no analysed events for phase calibration");
+  std::vector<double> nVel(numEvents);
+  std::vector<size_t> nMap(numEvents);
+  std::vector<bool> nCnd(numEvents);
+  constexpr size_t NHIST = 100;
+  std::vector<int> histogram(NHIST + 1, 0);
+
+  // define the cost function to optimize phase
+  auto costFn = [&, this](double phase) {
+    ConvertTOF convTOF(m_dopplerAmpl * m_dopplerRun, m_dopplerFreq, phase, l1,
+                       v2, m_detectorL2);
+
+    // convert each analysed event to source velocity
+    size_t ix = 0;
+    double tof, pulse;
+    for (size_t i = startID; i < endID; i++) {
+      for (auto const &x : *eventVectors[i]) {
+        std::tie(pulse, tof) = convTOF.analysedTOF(i, x.tof());
+        auto tof1 = 1e-6 * tof - m_detectorL2[i] / v2;
+        nVel[ix++] = l1 / tof1;
+      }
+    }
+
+    // now histogram the data and create the map from velocity to hist
+    auto ixlim = std::minmax_element(nVel.begin(), nVel.end());
+    auto vmin = nVel[ixlim.first - nVel.begin()];
+    auto vmax = nVel[ixlim.second - nVel.begin()];
+    int maxHist = 0;
+    std::fill(histogram.begin(), histogram.end(), 0);
+    auto delta = (vmax - vmin) / NHIST;
+    for (size_t i = 0; i < numEvents; i++) {
+      auto v = nVel[i];
+      auto j = static_cast<size_t>(std::floor((v - vmin) / delta));
+      histogram[j]++;
+      if (histogram[j] > maxHist)
+        maxHist = histogram[j];
+      nMap[i] = j;
+    }
+
+    // determine the points above the 25% threshold
+    int minLevel = static_cast<int>(maxHist / 4);
+    for (size_t i = 0; i < numEvents; i++) {
+      nCnd[i] = (histogram[nMap[i]] >= minLevel ? true : false);
+    }
+
+    // calculate the standard deviation for the points above the threshold
+    auto cost = maskedStdev(nVel, nCnd);
+    return cost;
+  };
+
+  // call the optimizer and update the doppler phase value
+  // limit the optimization to 30 iterations
+  int bits = std::numeric_limits<double>::digits;
+  boost::uintmax_t itn = 30;
+  using boost::math::tools::brent_find_minima;
+  auto minPhase = m_dopplerPhase - 5.0;
+  auto maxPhase = m_dopplerPhase + 5.0;
+  auto r = brent_find_minima(costFn, minPhase, maxPhase, bits, itn);
+  m_dopplerPhase = r.first;
+}
+
+/// Convert the doppler time to TOF for all the events in \p eventVectors and
+/// time of flight range as \p minTOF and \p maxTOF.
+template <typename FD>
+void LoadEMU<FD>::dopplerTimeToTOF(std::vector<EventVector_pt> &eventVectors,
+                                   double &minTOF, double &maxTOF) {
+
+  // get the doppler parameters and initialise TOD converter
+  auto instr = m_localWorkspace->getInstrument();
+  double v2 = instr->getNumberParameter("AnalysedV2")[0];
+  double l1 = instr->getNumberParameter("SourceSample")[0];
+  ConvertTOF convTOF(m_dopplerAmpl * m_dopplerRun, m_dopplerFreq,
+                     m_dopplerPhase, l1, v2, m_detectorL2);
+
+  // run through all the events noting that analysed event are in
+  // the bottom half of the detector ids
+  auto start = true;
+  auto directID = static_cast<size_t>(DETECTOR_TUBES * PIXELS_PER_TUBE);
+  for (size_t id = 0; id < eventVectors.size(); id++) {
+    for (auto &x : *eventVectors[id]) {
+      double tof, pulse;
+      if (id < directID)
+        std::tie(pulse, tof) = convTOF.analysedTOF(id, x.tof());
+      else
+        std::tie(pulse, tof) = convTOF.directTOF(id, x.tof());
+
+      // update the pulse time and tof
+      int64_t pulseTime = x.pulseTime().totalNanoseconds();
+      pulseTime += static_cast<int64_t>(pulse * 1000);
+      x = Types::Event::TofEvent(tof, Types::Core::DateAndTime(pulseTime));
+
+      if (start) {
+        minTOF = maxTOF = x.tof();
+        start = false;
+      } else {
+        minTOF = std::min(minTOF, x.tof());
+        maxTOF = std::max(maxTOF, x.tof());
+      }
+    }
+  }
 }
 
-// Recovers the L2 neutronic distance for each detector.
-void LoadEMU::loadDetectorL2Values() {
+/// Recovers the L2 neutronic distance for each detector.
+template <typename FD> void LoadEMU<FD>::loadDetectorL2Values() {
 
   m_detectorL2 = std::vector<double>(HISTOGRAMS);
   const auto &detectorInfo = m_localWorkspace->detectorInfo();
@@ -733,15 +1016,11 @@ void LoadEMU::loadDetectorL2Values() {
   }
 }
 
-// update the neutronic positins
-void LoadEMU::updateNeutronicPostions(detid_t detID, double sampleAnalyser) {
-
-  // get the instrument
-
-  // get the list of indirect horizontal detectors
-
-  // for each detector get the current position and scale
-  // the detectors
+/// Update the neutronic position for the \p detID using the distance
+/// from the source and the sample to analyser distance, \p sampleAnalyser.
+template <typename FD>
+void LoadEMU<FD>::updateNeutronicPostions(detid_t detID,
+                                          double sampleAnalyser) {
 
   Geometry::Instrument_const_sptr instrument =
       m_localWorkspace->getInstrument();
@@ -763,10 +1042,11 @@ void LoadEMU::updateNeutronicPostions(detid_t detID, double sampleAnalyser) {
   }
 }
 
-// region of interest is defined by the selected detectors and the mask
-//
-std::vector<bool> LoadEMU::createRoiVector(const std::string &selected,
-                                           const std::string &maskfile) {
+/// Region of interest is defined by the \p selected detectors and the
+/// \p maskfile.
+template <typename FD>
+std::vector<bool> LoadEMU<FD>::createRoiVector(const std::string &selected,
+                                               const std::string &maskfile) {
 
   std::vector<bool> result(HISTOGRAMS, true);
 
@@ -804,87 +1084,89 @@ std::vector<bool> LoadEMU::createRoiVector(const std::string &selected,
   return result;
 }
 
-// load parameters from input file
-void LoadEMU::loadParameters(ANSTO::Tar::File &tarFile, API::LogManager &logm) {
-
-  // extract log and hdf file
-  const std::vector<std::string> &files = tarFile.files();
-  auto file_it =
-      std::find_if(files.cbegin(), files.cend(), [](const std::string &file) {
-        return file.rfind(".hdf") == file.length() - 4;
-      });
-  if (file_it != files.end()) {
-    tarFile.select(file_it->c_str());
-    // extract hdf file into tmp file
-    Poco::TemporaryFile hdfFile;
-    boost::shared_ptr<FILE> handle(fopen(hdfFile.path().c_str(), "wb"), fclose);
-    if (handle) {
-      // copy content
-      char buffer[4096];
-      size_t bytesRead;
-      while (0 != (bytesRead = tarFile.read(buffer, sizeof(buffer))))
-        fwrite(buffer, bytesRead, 1, handle.get());
-      handle.reset();
-
-      NeXus::NXRoot root(hdfFile.path());
-      NeXus::NXEntry entry = root.openFirstEntry();
-
-      MapNeXusToProperty<std::string>(entry, "sample/name", "unknown", logm,
-                                      "SampleName", "");
-      MapNeXusToProperty<std::string>(entry, "sample/description", "unknown",
-                                      logm, "SampleDescription", "");
-      MapNeXusToProperty<std::string>(
-          entry, "start_time", "2000-01-01T00:00:00", logm, "StartTime", "");
-
-      MapNeXusToProperty<double>(entry, "instrument/doppler/ctrl/amplitude",
-                                 75.0, logm, "DopplerAmplitude", 0.001);
-      double speedToFreq =
-          0.5 / (M_PI * m_localWorkspace->run().getPropertyValueAsType<double>(
-                            "DopplerAmplitude"));
-      MapNeXusToProperty<double>(entry, "instrument/doppler/ctrl/velocity", 4.7,
-                                 logm, "DopplerFrequency", speedToFreq);
-
-      MapNeXusToProperty<double>(entry, "instrument/chpr/background/actspeed",
-                                 1272.8, logm, "BackgroundChopperFrequency",
-                                 1.0 / 60);
-      MapNeXusToProperty<double>(entry, "instrument/chpr/graphite/actspeed",
-                                 2545.6, logm, "GraphiteChopperFrequency",
-                                 1.0 / 60);
-      MapNeXusToProperty<double>(entry, "instrument/hztubegap", 0.02, logm,
-                                 "HorizontalTubesGap", 1.0);
-      MapNeXusToProperty<int32_t>(entry, "monitor/bm1_counts", 0, logm,
-                                  "MonitorCounts", 1);
-
-      // temp fix for source position when loading IDF
-      MapNeXusToProperty<double>(entry, "instrument/doppler/tosource", 2.035,
-                                 logm, "SourceSample", 1.0);
-    }
+/// Load parameters from input \p hdfFile and save to the log manager, \p logm.
+template <typename FD>
+void LoadEMU<FD>::loadParameters(const std::string &hdfFile,
+                                 API::LogManager &logm) {
+
+  NeXus::NXRoot root(hdfFile);
+  NeXus::NXEntry entry = root.openFirstEntry();
+
+  MapNeXusToProperty<std::string>(entry, "sample/name", "unknown", logm,
+                                  "SampleName", "", 0);
+  MapNeXusToProperty<std::string>(entry, "sample/description", "unknown", logm,
+                                  "SampleDescription", "", 0);
+
+  // if dataset index > 0 need to add an offset to the start time
+  Types::Core::DateAndTime startTime(GetNeXusValue<std::string>(
+      entry, "start_time", "2000-01-01T00:00:00", 0));
+  if (m_datasetIndex > 0) {
+    auto baseTime =
+        GetNeXusValue<int32_t>(entry, "instrument/detector/start_time", 0, 0);
+    auto nthTime = GetNeXusValue<int32_t>(
+        entry, "instrument/detector/start_time", 0, m_datasetIndex);
+
+    Types::Core::time_duration duration = boost::posix_time::microseconds(
+        static_cast<boost::int64_t>((nthTime - baseTime) * 1.0e6));
+    Types::Core::DateAndTime startDataset(startTime + duration);
+    m_startRun = startDataset.toISO8601String();
+  } else {
+    m_startRun = startTime.toISO8601String();
   }
 
-  // patching
-  file_it = std::find(files.cbegin(), files.cend(), "History.log");
-  if (file_it != files.cend()) {
-    tarFile.select(file_it->c_str());
-    std::string logContent;
-    logContent.resize(tarFile.selected_size());
-    tarFile.read(&logContent[0], logContent.size());
-    std::istringstream data(logContent);
-    Poco::AutoPtr<Poco::Util::PropertyFileConfiguration> conf(
-        new Poco::Util::PropertyFileConfiguration(data));
-
-    if (conf->hasProperty("SampleName")) {
-      std::string name = conf->getString("SampleName");
-      logm.addProperty("SampleName", name);
-    }
+  MapNeXusToSeries<double>(entry, "instrument/doppler/ctrl/amplitude", 75.0,
+                           logm, m_startRun, "DopplerAmplitude", 0.001,
+                           m_datasetIndex);
+  MapNeXusToSeries<double>(entry, "instrument/doppler/ctrl/velocity", 4.7, logm,
+                           m_startRun, "DopplerVelocity", 1, m_datasetIndex);
+  MapNeXusToSeries<int>(entry, "instrument/doppler/ctrl/run_cmd", 1, logm,
+                        m_startRun, "DopplerRun", 1, m_datasetIndex);
+
+  MapNeXusToSeries<double>(entry, "instrument/chpr/background/actspeed", 1272.8,
+                           logm, m_startRun, "BackgroundChopperFrequency",
+                           1.0 / 60, 0);
+  MapNeXusToSeries<double>(entry, "instrument/chpr/graphite/actspeed", 2545.6,
+                           logm, m_startRun, "GraphiteChopperFrequency",
+                           1.0 / 60, 0);
+  // hz tube gap or equivalent to be added later - reverts to default
+  MapNeXusToSeries<double>(entry, "instrument/hztubegap", 0.02, logm,
+                           m_startRun, "horizontal_tubes_gap", 1.0, 0);
+  MapNeXusToSeries<int32_t>(entry, "monitor/bm1_counts", 0, logm, m_startRun,
+                            "MonitorCounts", 1, m_datasetIndex);
+
+  // fix for source position when loading IDF
+  MapNeXusToProperty<double>(entry, "instrument/doppler/tosource", 2.035, logm,
+                             "SourceSample", 1.0, 0);
+}
+
+/// Load the environment variables from the \p hdfFile and save as
+/// time series to the log manager, \p logm.
+template <typename FD>
+void LoadEMU<FD>::loadEnvironParameters(const std::string &hdfFile,
+                                        API::LogManager &logm) {
+
+  NeXus::NXRoot root(hdfFile);
+  NeXus::NXEntry entry = root.openFirstEntry();
+  auto time_str = logm.getPropertyValueAsType<std::string>("end_time");
+
+  // load the environment variables for the dataset loaded
+  std::vector<std::string> tags = {"P01PS03", "P01PSP03", "T01S00",  "T01S05",
+                                   "T01S06",  "T01SP00",  "T01SP06", "T02S00",
+                                   "T02S04",  "T02S05",   "T02S06",  "T02SP00",
+                                   "T02SP06"};
+
+  for (const auto &tag : tags) {
+    MapNeXusToSeries<double>(entry, "data/" + tag, 0.0, logm, time_str,
+                             "env_" + tag, 1.0, m_datasetIndex);
   }
 }
 
-// load the instrument definition and instrument parameters
-void LoadEMU::loadInstrument() {
+/// Load the instrument definition.
+template <typename FD> void LoadEMU<FD>::loadInstrument() {
 
   // loads the IDF and parameter file
   API::IAlgorithm_sptr loadInstrumentAlg =
-      createChildAlgorithm("LoadInstrument");
+      Base::createChildAlgorithm("LoadInstrument");
   loadInstrumentAlg->setProperty("Workspace", m_localWorkspace);
   loadInstrumentAlg->setPropertyValue("InstrumentName", "EMUau");
   loadInstrumentAlg->setProperty("RewriteSpectraMap",
@@ -892,31 +1174,218 @@ void LoadEMU::loadInstrument() {
   loadInstrumentAlg->executeAsChildAlg();
 }
 
-// read counts/events from binary file
-template <class EventProcessor>
-void LoadEMU::loadEvents(API::Progress &prog, const char *progMsg,
-                         ANSTO::Tar::File &tarFile,
-                         EventProcessor &eventProcessor) {
+//--------- explicit template instantiation -----------
 
-  using namespace ANSTO;
+// Instantiate base class for LoadEMU's
+template class LoadEMU<Kernel::FileDescriptor>;
+template class LoadEMU<Kernel::NexusDescriptor>;
 
-  prog.doReport(progMsg);
+// -------- EMU Hdf loader -----------------------
+
+/// Algorithm's version for identification. @see Algorithm::version
+int LoadEMUHdf::version() const { return 1; }
+
+/// Similar algorithms. @see Algorithm::seeAlso
+const std::vector<std::string> LoadEMUHdf::seeAlso() const {
+  return {"Load", "LoadQKK"};
+}
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string LoadEMUHdf::category() const { return "DataHandling\\ANSTO"; }
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string LoadEMUHdf::name() const { return "LoadEMUHdf"; }
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string LoadEMUHdf::summary() const {
+  return "Loads an EMU Hdf and linked event file into a workspace.";
+}
+
+/// Return the confidence as an integer value that this algorithm can
+/// load the file \p descriptor.
+int LoadEMUHdf::confidence(Kernel::NexusDescriptor &descriptor) const {
+  if (descriptor.extension() != ".hdf")
+    return 0;
+
+  if (descriptor.pathExists("/entry1/site_name") &&
+      descriptor.pathExists("/entry1/instrument/doppler/ctrl/velocity") &&
+      descriptor.pathExists("/entry1/instrument/doppler/ctrl/amplitude") &&
+      descriptor.pathExists("/entry1/instrument/detector/daq_dirname") &&
+      descriptor.pathExists("/entry1/instrument/detector/dataset_number") &&
+      descriptor.pathExists("/entry1/data/hmm_total_t_ds0") &&
+      descriptor.pathExists("/entry1/data/hmm_total_t_ds1") &&
+      descriptor.pathExists("/entry1/data/hmm_total_xt_ds0") &&
+      descriptor.pathExists("/entry1/data/hmm_total_xt_ds1")) {
+    return 80;
+  } else {
+    return 0;
+  }
+}
+
+/// Initialise the algorithm and declare the properties for the
+/// nexus descriptor.
+void LoadEMUHdf::init() { LoadEMU<Kernel::NexusDescriptor>::init(true); }
+
+/// Execute the algorithm. Establishes the filepath to the event file
+/// from the HDF link and the path provided and invokes the common
+// exec() function that works with the two files.
+void LoadEMUHdf::exec() {
+
+  namespace fs = boost::filesystem;
 
-  // select bin file
-  int64_t fileSize = 0;
+  // Open the hdf file and find the dirname and dataset number
+  std::string hdfFile = Base::getPropertyValue(FilenameStr);
+  std::string evtPath = Base::getPropertyValue(PathToBinaryStr);
+  if (evtPath.empty())
+    evtPath = "./";
+
+  // if relative ./ or ../ then append to the directory for the hdf file
+  if (evtPath.rfind("./") == 0 || evtPath.rfind("../") == 0) {
+    fs::path hp = hdfFile;
+    evtPath = fs::canonical(evtPath, hp.parent_path()).generic_string();
+  }
+
+  // dataset index to be loaded
+  m_datasetIndex = Base::getProperty(SelectDatasetStr);
+
+  // if path provided build the file path
+  if (fs::is_directory(evtPath)) {
+    NeXus::NXRoot root(hdfFile);
+    NeXus::NXEntry entry = root.openFirstEntry();
+    auto eventDir = GetNeXusValue<std::string>(
+        entry, "instrument/detector/daq_dirname", "./", 0);
+    auto dataset = GetNeXusValue<int32_t>(
+        entry, "instrument/detector/dataset_number", 0, m_datasetIndex);
+
+    // build the path to the event file as if a relative or absolute
+    // path is passed:
+    //   'relpath/[daq_dirname]/DATASET_[n]/EOS.bin' or the
+    char buffer[255] = {};
+    snprintf(buffer, sizeof(buffer), "%s/DATASET_%d/EOS.bin", eventDir.c_str(),
+             dataset);
+    fs::path path = evtPath;
+    path /= buffer;
+    path = fs::absolute(path);
+    evtPath = path.generic_string();
+  }
+
+  // finally check that the event file exists
+  if (!fs::is_regular_file(evtPath)) {
+    std::string msg = "Check path, cannot open binary event file: " + evtPath;
+    throw std::runtime_error(msg);
+  }
+
+  LoadEMU<Kernel::NexusDescriptor>::exec(hdfFile, evtPath);
+}
+
+// -------- EMU Tar loader -----------------------
+
+/// Algorithm's version for identification. @see Algorithm::version
+int LoadEMUTar::version() const { return 1; }
+
+/// Similar algorithms. @see Algorithm::seeAlso
+const std::vector<std::string> LoadEMUTar::seeAlso() const {
+  return {"Load", "LoadQKK"};
+}
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string LoadEMUTar::category() const { return "DataHandling\\ANSTO"; }
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string LoadEMUTar::name() const { return "LoadEMU"; }
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string LoadEMUTar::summary() const {
+  return "Loads an EMU tar file, containing the Hdf and event file, into a "
+         "workspace.";
+}
+
+/// Return the confidence as an integer value that this algorithm can
+/// load the file \p descriptor.
+int LoadEMUTar::confidence(Kernel::FileDescriptor &descriptor) const {
+  if (descriptor.extension() != ".tar")
+    return 0;
+
+  ANSTO::Tar::File file(descriptor.filename());
+  if (!file.good())
+    return 0;
+
+  size_t hdfFiles = 0;
+  size_t binFiles = 0;
+  const std::vector<std::string> &subFiles = file.files();
+  for (const auto &subFile : subFiles) {
+    auto len = subFile.length();
+    if ((len > 4) &&
+        (subFile.find_first_of("\\/", 0, 2) == std::string::npos)) {
+      if ((subFile.rfind(".hdf") == len - 4) &&
+          (subFile.compare(0, 3, "EMU") == 0))
+        hdfFiles++;
+      else if (subFile.rfind(".bin") == len - 4)
+        binFiles++;
+    }
+  }
+
+  return (hdfFiles == 1) && (binFiles == 1) ? 50 : 0;
+}
+
+/// Initialise the algorithm and declare the standard properties for the
+/// general file descriptor.
+void LoadEMUTar::init() { LoadEMU<Kernel::FileDescriptor>::init(false); }
+
+/// Execute the algorithm. Extracts the hdf and event file from the tar
+/// and invokes the invokes the common exec() function that works with
+/// the two files.
+void LoadEMUTar::exec() {
+
+  // Opens the tar and extracts the hdf and event data to temporary files
+  std::string filename = Base::getPropertyValue(FilenameStr);
+  ANSTO::Tar::File tarFile(filename);
+  if (!tarFile.good())
+    throw std::invalid_argument("invalid EMU tar file");
+
+  // dataset selection not supported in tar version - order is not guaranteed
+  m_datasetIndex = 0;
+
+  // lambda functions to find the first file of extension and to extract
+  // the file
   const std::vector<std::string> &files = tarFile.files();
-  for (const auto &file : files)
-    if (file.rfind(".bin") == file.length() - 4) {
-      tarFile.select(file.c_str());
-      fileSize = tarFile.selected_size();
-      break;
+  auto selectFile = [&](const std::string &ext) {
+    auto itf = std::find_if(files.cbegin(), files.cend(),
+                            [&ext](const std::string &file) {
+                              return file.rfind(ext) == file.length() - 4;
+                            });
+    if (itf == files.end())
+      throw std::runtime_error("missing tar file data");
+    else
+      tarFile.select(itf->c_str());
+  };
+  auto extractFile = [&](Poco::TemporaryFile &tfile) {
+    boost::shared_ptr<FILE> handle(fopen(tfile.path().c_str(), "wb"), fclose);
+    if (handle) {
+      // copy content
+      char buffer[4096];
+      size_t bytesRead;
+      while (0 != (bytesRead = tarFile.read(buffer, sizeof(buffer))))
+        fwrite(buffer, bytesRead, 1, handle.get());
+      handle.reset();
     }
+  };
 
-  // for progress notifications
-  ANSTO::ProgressTracker progTracker(prog, progMsg, fileSize,
-                                     Progress_LoadBinFile);
+  // extract hdf file into tmp file
+  selectFile(".hdf");
+  Poco::TemporaryFile hdfFile;
+  extractFile(hdfFile);
 
-  ReadEventFile(tarFile, eventProcessor, progTracker, 100, false);
+  // extract the event file
+  selectFile(".bin");
+  Poco::TemporaryFile eventFile;
+  extractFile(eventFile);
+
+  // call the common loader
+  LoadEMU<Kernel::FileDescriptor>::exec(hdfFile.path(), eventFile.path());
 }
+
+// register the algorithms into the AlgorithmFactory
+DECLARE_FILELOADER_ALGORITHM(LoadEMUTar)
+DECLARE_NEXUS_FILELOADER_ALGORITHM(LoadEMUHdf)
+
 } // namespace DataHandling
-} // namespace Mantid
\ No newline at end of file
+} // namespace Mantid
diff --git a/Framework/DataHandling/src/LoadEventNexus.cpp b/Framework/DataHandling/src/LoadEventNexus.cpp
index db598919e3fb1a77c7700002cbf5ed6644d0d18d..981e5ee436d1bdc4b548b16e13550b61a1b3bb5a 100644
--- a/Framework/DataHandling/src/LoadEventNexus.cpp
+++ b/Framework/DataHandling/src/LoadEventNexus.cpp
@@ -688,6 +688,17 @@ void LoadEventNexus::loadEvents(API::Progress *const prog,
     m_ws->mutableRun().addProperty("run_start", run_start.toISO8601String(),
                                    true);
   }
+  // set more properties on the workspace
+  try {
+    // this is a static method that is why it is passing the
+    // file object and the file path
+    loadEntryMetadata<EventWorkspaceCollection_sptr>(m_filename, m_ws,
+                                                     m_top_entry_name);
+  } catch (std::runtime_error &e) {
+    // Missing metadata is not a fatal error. Log and go on with your life
+    g_log.error() << "Error loading metadata: " << e.what() << '\n';
+  }
+
   m_ws->setNPeriods(
       static_cast<size_t>(nPeriods),
       periodLog); // This is how many workspaces we are going to make.
@@ -776,17 +787,6 @@ void LoadEventNexus::loadEvents(API::Progress *const prog,
   if (AnalysisDataService::Instance().doesExist(outName))
     AnalysisDataService::Instance().remove(outName);
 
-  // set more properties on the workspace
-  try {
-    // this is a static method that is why it is passing the
-    // file object and the file path
-    loadEntryMetadata<EventWorkspaceCollection_sptr>(m_filename, m_ws,
-                                                     m_top_entry_name);
-  } catch (std::runtime_error &e) {
-    // Missing metadata is not a fatal error. Log and go on with your life
-    g_log.error() << "Error loading metadata: " << e.what() << '\n';
-  }
-
   // --------------------------- Time filtering
   // ------------------------------------
   double filter_time_start_sec, filter_time_stop_sec;
diff --git a/Framework/DataHandling/src/LoadILLDiffraction.cpp b/Framework/DataHandling/src/LoadILLDiffraction.cpp
index f6c988ab87e65506da8bf1660c4aabbd5e185b29..24fae957d1204f39eed37dee150a8b7365e401dc 100644
--- a/Framework/DataHandling/src/LoadILLDiffraction.cpp
+++ b/Framework/DataHandling/src/LoadILLDiffraction.cpp
@@ -514,8 +514,8 @@ void LoadILLDiffraction::fillStaticInstrumentScan(const NXUInt &data,
                                                   const NXDouble &scan,
                                                   const NXFloat &twoTheta0) {
 
-  std::vector<double> axis = getAxis(scan);
-  std::vector<double> monitor = getMonitor(scan);
+  const std::vector<double> axis = getAxis(scan);
+  const std::vector<double> monitor = getMonitor(scan);
 
   // Assign monitor counts
   m_outWorkspace->mutableX(0) = axis;
diff --git a/Framework/DataHandling/src/LoadILLIndirect2.cpp b/Framework/DataHandling/src/LoadILLIndirect2.cpp
index fba534e89ef8e5d98cd14a863a3c06a88940275b..86764e0f632c30a36971e928e630d10d0c6d0a07 100644
--- a/Framework/DataHandling/src/LoadILLIndirect2.cpp
+++ b/Framework/DataHandling/src/LoadILLIndirect2.cpp
@@ -289,7 +289,7 @@ void LoadILLIndirect2::loadDataIntoTheWorkSpace(
   for (size_t im = 0; im < m_numberOfMonitors; im++) {
 
     if (im > 0) {
-      m_localWorkspace->dataX(im) = m_localWorkspace->readX(0);
+      m_localWorkspace->setSharedX(im, m_localWorkspace->sharedX(0));
     }
 
     // Assign Y
diff --git a/Framework/DataHandling/src/LoadMcStasNexus.cpp b/Framework/DataHandling/src/LoadMcStasNexus.cpp
index 2535489b0edd9b4f9800eb6128a40ec19ac5f37c..4c928a35b7e0d93299d29669a26607838caee8dd 100644
--- a/Framework/DataHandling/src/LoadMcStasNexus.cpp
+++ b/Framework/DataHandling/src/LoadMcStasNexus.cpp
@@ -155,7 +155,7 @@ void LoadMcStasNexus::exec() {
       ws->setYUnit(axis2Name);
       ws->replaceAxis(1, axis2);
 
-      ws->mutableX(0) = axis1Values;
+      ws->mutableX(0) = std::move(axis1Values);
 
       for (size_t wsIndex = 0; wsIndex < axis2Length; ++wsIndex) {
         auto &dataY = ws->mutableY(wsIndex);
diff --git a/Framework/DataHandling/src/LoadSampleEnvironment.cpp b/Framework/DataHandling/src/LoadSampleEnvironment.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a0fd6bf8e247eab190a32ddd7ca3bc51e5caea76
--- /dev/null
+++ b/Framework/DataHandling/src/LoadSampleEnvironment.cpp
@@ -0,0 +1,324 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidDataHandling/LoadSampleEnvironment.h"
+#include "MantidDataHandling/LoadAsciiStl.h"
+#include "MantidDataHandling/LoadBinaryStl.h"
+#include "MantidDataHandling/ReadMaterial.h"
+#include "MantidGeometry/Instrument/Container.h"
+#include "MantidGeometry/Instrument/SampleEnvironment.h"
+#include "MantidGeometry/Objects/MeshObject.h"
+
+#include "MantidAPI/FileProperty.h"
+#include "MantidAPI/InstrumentValidator.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/Sample.h"
+
+#include "MantidKernel/ArrayProperty.h"
+#include "MantidKernel/BoundedValidator.h"
+#include "MantidKernel/EnabledWhenProperty.h"
+#include "MantidKernel/Exception.h"
+#include "MantidKernel/MandatoryValidator.h"
+
+#include <Poco/File.h>
+#include <boost/algorithm/string.hpp>
+#include <cmath>
+#include <fstream>
+
+namespace Mantid {
+namespace DataHandling {
+
+// Register the algorithm into the algorithm factory
+DECLARE_ALGORITHM(LoadSampleEnvironment)
+
+using namespace Kernel;
+using namespace API;
+using namespace Geometry;
+
+void LoadSampleEnvironment::init() {
+  auto wsValidator = boost::make_shared<API::InstrumentValidator>();
+  ;
+
+  // input workspace
+  declareProperty(make_unique<WorkspaceProperty<>>(
+                      "InputWorkspace", "", Direction::Input, wsValidator),
+                  "The name of the workspace containing the instrument to add "
+                  "the Environment");
+
+  // Environment file
+  const std::vector<std::string> extensions{".stl"};
+  declareProperty(
+      make_unique<FileProperty>("Filename", "", FileProperty::Load, extensions),
+      "The path name of the file containing the Environment");
+
+  // Output workspace
+  declareProperty(make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
+                                                   Direction::Output),
+                  "The name of the workspace that will contain the loaded "
+                  "Environment of the sample");
+
+  // Environment Name
+  declareProperty("EnvironmentName", "Environment");
+
+  // New Can or Add
+  declareProperty("Add", false);
+
+  // Vector to translate mesh
+  declareProperty(
+      make_unique<ArrayProperty<double>>("TranslationVector", "0,0,0"),
+      "Vector by which to translate the loaded environment");
+
+  // Matrix to rotate mesh
+  declareProperty(make_unique<ArrayProperty<double>>(
+                      "RotationMatrix", "1.0,0.0,0.0,0.0,1.0,0.0,1.0,0.0,0.0"),
+                  "Rotation Matrix in format x1,x2,x3,y1,y2,y3,z1,z2,z3");
+
+  declareProperty("SetMaterial", false);
+
+  // properties for SetMaterial
+
+  declareProperty("ChemicalFormula", "",
+                  "The chemical formula, see examples in documentation");
+
+  declareProperty("AtomicNumber", 0, "The atomic number");
+  declareProperty("MassNumber", 0,
+                  "Mass number if ion (use 0 for default mass sensity)");
+  auto mustBePositive = boost::make_shared<BoundedValidator<double>>();
+  mustBePositive->setLower(0.0);
+  declareProperty("SampleNumberDensity", EMPTY_DBL(), mustBePositive,
+                  "This number density of the sample in number of "
+                  "atoms per cubic angstrom will be used instead of "
+                  "calculated");
+  declareProperty("ZParameter", EMPTY_DBL(), mustBePositive,
+                  "Number of formula units in unit cell");
+  declareProperty("UnitCellVolume", EMPTY_DBL(), mustBePositive,
+                  "Unit cell volume in Angstoms^3. Will be calculated from the "
+                  "OrientedLattice if not supplied.");
+  declareProperty("CoherentXSection", EMPTY_DBL(), mustBePositive,
+                  "Optional:  This coherent cross-section for the sample "
+                  "material in barns will be used instead of tabulated");
+  declareProperty("IncoherentXSection", EMPTY_DBL(), mustBePositive,
+                  "Optional:  This incoherent cross-section for the sample "
+                  "material in barns will be used instead of tabulated");
+  declareProperty("AttenuationXSection", EMPTY_DBL(), mustBePositive,
+                  "Optional:  This absorption cross-section for the sample "
+                  "material in barns will be used instead of tabulated");
+  declareProperty("ScatteringXSection", EMPTY_DBL(), mustBePositive,
+                  "Optional:  This total scattering cross-section (coherent + "
+                  "incoherent) for the sample material in barns will be used "
+                  "instead of tabulated");
+  declareProperty("SampleMassDensity", EMPTY_DBL(), mustBePositive,
+                  "Measured mass density in g/cubic cm of the sample "
+                  "to be used to calculate the number density.");
+
+  // Perform Group Associations.
+  std::string formulaGrp("By Formula or Atomic Number");
+  setPropertyGroup("ChemicalFormula", formulaGrp);
+  setPropertyGroup("AtomicNumber", formulaGrp);
+  setPropertyGroup("MassNumber", formulaGrp);
+  setPropertySettings("ChemicalFormula", make_unique<EnabledWhenProperty>(
+                                             "SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings("AtomicNumber", make_unique<EnabledWhenProperty>(
+                                          "SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings("MassNumber", make_unique<EnabledWhenProperty>(
+                                        "SetMaterial", IS_NOT_DEFAULT));
+
+  std::string densityGrp("Sample Density");
+  setPropertyGroup("SampleNumberDensity", densityGrp);
+  setPropertyGroup("ZParameter", densityGrp);
+  setPropertyGroup("UnitCellVolume", densityGrp);
+  setPropertyGroup("SampleMassDensity", densityGrp);
+  setPropertySettings(
+      "SampleNumberDensity",
+      make_unique<EnabledWhenProperty>("SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings("ZParameter", make_unique<EnabledWhenProperty>(
+                                        "SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings("UnitCellVolume", make_unique<EnabledWhenProperty>(
+                                            "SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings("SampleMassDensity", make_unique<EnabledWhenProperty>(
+                                               "SetMaterial", IS_NOT_DEFAULT));
+
+  std::string specificValuesGrp("Override Cross Section Values");
+  setPropertyGroup("CoherentXSection", specificValuesGrp);
+  setPropertyGroup("IncoherentXSection", specificValuesGrp);
+  setPropertyGroup("AttenuationXSection", specificValuesGrp);
+  setPropertyGroup("ScatteringXSection", specificValuesGrp);
+  setPropertySettings("CoherentXSection", make_unique<EnabledWhenProperty>(
+                                              "SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings("IncoherentXSection", make_unique<EnabledWhenProperty>(
+                                                "SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings(
+      "AttenuationXSection",
+      make_unique<EnabledWhenProperty>("SetMaterial", IS_NOT_DEFAULT));
+  setPropertySettings("ScatteringXSection", make_unique<EnabledWhenProperty>(
+                                                "SetMaterial", IS_NOT_DEFAULT));
+}
+
+std::map<std::string, std::string> LoadSampleEnvironment::validateInputs() {
+  std::map<std::string, std::string> result;
+  if (getProperty("SetMaterial")) {
+    ReadMaterial::MaterialParameters params;
+    params.chemicalSymbol = getPropertyValue("ChemicalFormula");
+    params.atomicNumber = getProperty("AtomicNumber");
+    params.massNumber = getProperty("MassNumber");
+    params.sampleNumberDensity = getProperty("SampleNumberDensity");
+    params.zParameter = getProperty("ZParameter");
+    params.unitCellVolume = getProperty("UnitCellVolume");
+    params.sampleMassDensity = getProperty("SampleMassDensity");
+    result = ReadMaterial::validateInputs(params);
+  }
+  return result;
+}
+
+void LoadSampleEnvironment::exec() {
+
+  MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
+  MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
+
+  if (inputWS != outputWS) {
+    outputWS = inputWS->clone();
+  }
+
+  const std::string filename = getProperty("Filename");
+  const std::ifstream file(filename.c_str());
+  if (!file) {
+    g_log.error("Unable to open file: " + filename);
+    throw Exception::FileError("Unable to open file: ", filename);
+  }
+
+  boost::shared_ptr<MeshObject> environmentMesh = nullptr;
+
+  std::unique_ptr<LoadAsciiStl> asciiStlReader = nullptr;
+  std::unique_ptr<LoadBinaryStl> binaryStlReader = nullptr;
+
+  if (getProperty("SetMaterial")) {
+    ReadMaterial::MaterialParameters params;
+    params.chemicalSymbol = getPropertyValue("ChemicalFormula");
+    params.atomicNumber = getProperty("AtomicNumber");
+    params.massNumber = getProperty("MassNumber");
+    params.sampleNumberDensity = getProperty("SampleNumberDensity");
+    params.zParameter = getProperty("ZParameter");
+    params.unitCellVolume = getProperty("UnitCellVolume");
+    params.sampleMassDensity = getProperty("SampleMassDensity");
+    params.coherentXSection = getProperty("CoherentXSection");
+    params.incoherentXSection = getProperty("IncoherentXSection");
+    params.attenuationXSection = getProperty("AttenuationXSection");
+    params.scatteringXSection = getProperty("ScatteringXSection");
+    binaryStlReader = std::make_unique<LoadBinaryStl>(filename, params);
+    asciiStlReader = std::make_unique<LoadAsciiStl>(filename, params);
+  } else {
+    binaryStlReader = std::make_unique<LoadBinaryStl>(filename);
+    asciiStlReader = std::make_unique<LoadAsciiStl>(filename);
+  }
+
+  if (binaryStlReader->isBinarySTL(filename)) {
+    environmentMesh = binaryStlReader->readStl();
+  } else if (asciiStlReader->isAsciiSTL(filename)) {
+    environmentMesh = asciiStlReader->readStl();
+  } else {
+    throw Kernel::Exception::ParseError(
+        "Could not read file, did not match either STL Format", filename, 0);
+  }
+  environmentMesh = translate(environmentMesh);
+  environmentMesh = rotate(environmentMesh);
+
+  std::string name = getProperty("EnvironmentName");
+  const bool add = getProperty("Add");
+  Sample &sample = outputWS->mutableSample();
+  std::unique_ptr<Geometry::SampleEnvironment> environment = nullptr;
+  if (add) {
+    environment =
+        std::make_unique<Geometry::SampleEnvironment>(sample.getEnvironment());
+    environment->add(environmentMesh);
+  } else {
+    auto can = boost::make_shared<Container>(environmentMesh);
+    environment = std::make_unique<Geometry::SampleEnvironment>(name, can);
+  }
+  // Put Environment into sample.
+
+  std::string debugString =
+      "Environment has: " + std::to_string(environment->nelements()) +
+      " elements.";
+
+  sample.setEnvironment(std::move(environment));
+
+  auto translatedVertices = environmentMesh->getVertices();
+  if (g_log.is(Logger::Priority::PRIO_DEBUG)) {
+    int i = 0;
+    for (double vertex : translatedVertices) {
+      i++;
+      g_log.debug(std::to_string(vertex));
+      if (i % 3 == 0) {
+        g_log.debug("\n");
+      }
+    }
+  }
+  // get the material name and number density for debug
+  const auto outMaterial =
+      outputWS->sample().getEnvironment().container()->material();
+  debugString += "\n"
+                 "Environment Material: " +
+                 outMaterial.name();
+  debugString += "\n"
+                 "Environment Material Number Density: " +
+                 std::to_string(outMaterial.numberDensity());
+  // Set output workspace
+  setProperty("OutputWorkspace", outputWS);
+  g_log.debug(debugString);
+}
+
+/**
+ * translates the environment by a provided matrix
+ * @param environmentMesh The environment to translate
+ * @returns a shared pointer to the newly translated environment
+ */
+boost::shared_ptr<MeshObject> LoadSampleEnvironment::translate(
+    boost::shared_ptr<MeshObject> environmentMesh) {
+  const std::vector<double> translationVector =
+      getProperty("TranslationVector");
+  std::vector<double> checkVector = std::vector<double>(3, 0.0);
+  if (translationVector != checkVector) {
+    if (translationVector.size() != 3) {
+      throw std::invalid_argument(
+          "Invalid Translation vector, must have exactly 3 dimensions");
+    }
+    Kernel::V3D translate = Kernel::V3D(
+        translationVector[0], translationVector[1], translationVector[2]);
+    environmentMesh->translate(translate);
+  }
+  return environmentMesh;
+}
+
+/**
+ * Rotates the environment by a provided matrix
+ * @param environmentMesh The environment to rotate
+ * @returns a shared pointer to the newly rotated environment
+ */
+boost::shared_ptr<MeshObject>
+LoadSampleEnvironment::rotate(boost::shared_ptr<MeshObject> environmentMesh) {
+  const std::vector<double> rotationMatrix = getProperty("RotationMatrix");
+  double valueList[] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0};
+  std::vector<double> checkVector1 =
+      std::vector<double>(std::begin(valueList), std::end(valueList));
+  if (rotationMatrix != checkVector1) {
+    if (rotationMatrix.size() != 9) {
+
+      throw std::invalid_argument(
+          "Invalid Rotation Matrix, must have exactly 9 values, not: " +
+          std::to_string(rotationMatrix.size()));
+    }
+    Kernel::Matrix<double> rotation = Kernel::Matrix<double>(rotationMatrix);
+    double determinant = rotation.determinant();
+    if (!(std::abs(determinant) == 1.0)) {
+      throw std::invalid_argument("Invalid Rotation Matrix");
+    }
+    environmentMesh->rotate(rotation);
+  }
+  return environmentMesh;
+}
+
+} // namespace DataHandling
+} // namespace Mantid
diff --git a/Framework/DataHandling/src/LoadSampleShape.cpp b/Framework/DataHandling/src/LoadSampleShape.cpp
index 22e65d1143578a80915523edd6b7995e951fc400..76ed9e02aa13d56a095b4266412d50636a8a0791 100644
--- a/Framework/DataHandling/src/LoadSampleShape.cpp
+++ b/Framework/DataHandling/src/LoadSampleShape.cpp
@@ -50,10 +50,10 @@ bool getOFFline(std::ifstream &file, std::string &line) {
   return true;
 }
 
-void readOFFVertices(std::ifstream &file, uint16_t nVertices,
+void readOFFVertices(std::ifstream &file, uint32_t nVertices,
                      std::vector<V3D> &vertices) {
   std::string line;
-  for (uint16_t i = 0; i < nVertices; i++) {
+  for (uint32_t i = 0; i < nVertices; i++) {
     if (getOFFline(file, line)) {
       std::vector<std::string> tokens;
       boost::split(tokens, line, boost::is_any_of(" "),
@@ -72,12 +72,12 @@ void readOFFVertices(std::ifstream &file, uint16_t nVertices,
   }
 }
 
-void readOFFTriangles(std::ifstream &file, uint16_t nTriangles,
-                      std::vector<uint16_t> &triangleIndices) {
+void readOFFTriangles(std::ifstream &file, uint32_t nTriangles,
+                      std::vector<uint32_t> &triangleIndices) {
   std::string line;
-  uint16_t t1, t2, t3;
+  uint32_t t1, t2, t3;
   size_t nFaceVertices;
-  for (uint16_t i = 0; i < nTriangles; i++) {
+  for (uint32_t i = 0; i < nTriangles; i++) {
     if (getOFFline(file, line)) {
       std::vector<std::string> tokens;
       boost::split(tokens, line, boost::is_any_of(" "),
@@ -85,9 +85,9 @@ void readOFFTriangles(std::ifstream &file, uint16_t nTriangles,
       if (tokens.size() >= 4) {
         nFaceVertices = boost::lexical_cast<size_t>(tokens[0]);
         if (nFaceVertices == 3) {
-          t1 = boost::lexical_cast<uint16_t>(tokens[1]);
-          t2 = boost::lexical_cast<uint16_t>(tokens[2]);
-          t3 = boost::lexical_cast<uint16_t>(tokens[3]);
+          t1 = boost::lexical_cast<uint32_t>(tokens[1]);
+          t2 = boost::lexical_cast<uint32_t>(tokens[2]);
+          t3 = boost::lexical_cast<uint32_t>(tokens[3]);
         } else {
           throw std::runtime_error("OFF face is not a triangle.");
         }
@@ -105,10 +105,10 @@ void readOFFTriangles(std::ifstream &file, uint16_t nTriangles,
 }
 
 std::unique_ptr<MeshObject> readOFFMeshObject(std::ifstream &file) {
-  std::vector<uint16_t> triangleIndices;
+  std::vector<uint32_t> triangleIndices;
   std::vector<V3D> vertices;
-  uint16_t nVertices;
-  uint16_t nTriangles;
+  uint32_t nVertices;
+  uint32_t nTriangles;
 
   std::string line;
   // Get number of vetrtices and faces
@@ -117,8 +117,8 @@ std::unique_ptr<MeshObject> readOFFMeshObject(std::ifstream &file) {
     boost::split(tokens, line, boost::is_any_of(" "), boost::token_compress_on);
     if (tokens.size() == 3) {
       try {
-        nVertices = boost::lexical_cast<uint16_t>(tokens[0]);
-        nTriangles = boost::lexical_cast<uint16_t>(tokens[1]);
+        nVertices = boost::lexical_cast<uint32_t>(tokens[0]);
+        nTriangles = boost::lexical_cast<uint32_t>(tokens[1]);
       } catch (...) {
         throw std::runtime_error("Error in reading numbers of OFF vertices and "
                                  "triangles, which may be too large");
@@ -188,14 +188,14 @@ void LoadSampleShape::exec() {
     outputWS = inputWS->clone();
   }
 
-  std::string filename = getProperty("Filename");
+  const std::string filename = getProperty("Filename");
   std::ifstream file(filename.c_str());
   if (!file) {
     g_log.error("Unable to open file: " + filename);
     throw Exception::FileError("Unable to open file: ", filename);
   }
 
-  std::string filetype = filename.substr(filename.size() - 3);
+  const std::string filetype = filename.substr(filename.size() - 3);
 
   boost::shared_ptr<MeshObject> shape = nullptr;
   if (filetype == "off") {
@@ -203,10 +203,10 @@ void LoadSampleShape::exec() {
   } else /* stl */ {
     auto asciiStlReader = LoadAsciiStl(filename);
     auto binaryStlReader = LoadBinaryStl(filename);
-    if (asciiStlReader.isAsciiSTL()) {
-      shape = asciiStlReader.readStl();
-    } else if (binaryStlReader.isBinarySTL()) {
+    if (binaryStlReader.isBinarySTL(filename)) {
       shape = binaryStlReader.readStl();
+    } else if (asciiStlReader.isAsciiSTL(filename)) {
+      shape = asciiStlReader.readStl();
     } else {
       throw Kernel::Exception::ParseError(
           "Could not read file, did not match either STL Format", filename, 0);
diff --git a/Framework/DataHandling/src/LoadStl.cpp b/Framework/DataHandling/src/LoadStl.cpp
index f075134197b34dd2ddb9bf35d9c8efa0b6406271..8d33bc0325c25a3e3ff3510ba50251744758156d 100644
--- a/Framework/DataHandling/src/LoadStl.cpp
+++ b/Framework/DataHandling/src/LoadStl.cpp
@@ -1,27 +1,30 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidDataHandling/LoadStl.h"
+#include "MantidKernel/V3D.h"
 #include <Poco/File.h>
 
+#include <functional>
+
 namespace Mantid {
 namespace DataHandling {
 
-// Adds vertex to list if distinct and returns index to vertex added or equal
-uint16_t LoadStl::addSTLVertex(Kernel::V3D &vertex) {
-  for (uint16_t i = 0; i < m_verticies.size(); ++i) {
-    if (areEqualVertices(vertex, m_verticies[i])) {
-      return i;
-    }
-  }
-  m_verticies.emplace_back(vertex);
-  uint16_t index = static_cast<uint16_t>(m_verticies.size() - 1);
-  if (index != m_verticies.size() - 1) {
-    throw std::runtime_error("Too many vertices in solid");
-  }
-  return index;
+bool LoadStl::areEqualVertices(Kernel::V3D const &v1,
+                               Kernel::V3D const &v2) const {
+  const Kernel::V3D diff = v1 - v2;
+  const double nanoMetre = 1e-9;
+  return diff.norm() < nanoMetre; // This is 1 nanometre for a unit of a metre.
 }
 
-bool LoadStl::areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2) {
-  Kernel::V3D diff = v1 - v2;
-  return diff.norm() < 1e-9; // This is 1 nanometre for a unit of a metre.
+void LoadStl::changeToVector() {
+  m_verticies.resize(vertexSet.size());
+  for (auto const &mapValue : vertexSet) {
+    m_verticies[mapValue.second] = mapValue.first;
+  }
 }
 } // namespace DataHandling
 } // namespace Mantid
\ No newline at end of file
diff --git a/Framework/DataHandling/src/ORNLDataArchive.cpp b/Framework/DataHandling/src/ORNLDataArchive.cpp
index c3ec9081bb3543a801f757d18aa4862aaccaca5f..8eadaf1ceb8c66824fe8cc32caae83c2f9a787cb 100644
--- a/Framework/DataHandling/src/ORNLDataArchive.cpp
+++ b/Framework/DataHandling/src/ORNLDataArchive.cpp
@@ -48,6 +48,7 @@ namespace Mantid {
 namespace DataHandling {
 
 DECLARE_ARCHIVESEARCH(ORNLDataArchive, ORNLDataSearch)
+DECLARE_ARCHIVESEARCH(ORNLDataArchive, SNSDataSearch)
 
 /**
  * ****************
diff --git a/Framework/DataHandling/src/ReadMaterial.cpp b/Framework/DataHandling/src/ReadMaterial.cpp
index bdb9cac3ae5a1ea9d122b26ded630e086d9da3ae..6ef243e395d720ee5526dc4542862b3caa260695 100644
--- a/Framework/DataHandling/src/ReadMaterial.cpp
+++ b/Framework/DataHandling/src/ReadMaterial.cpp
@@ -20,16 +20,39 @@ namespace DataHandling {
  * @param params A struct containing all the parameters to be set.
  * @returns A map containing the relevent failure messages, if any.
  */
-ValidationErrors ReadMaterial::validateInputs(const MaterialParameters params) {
+ValidationErrors
+ReadMaterial::validateInputs(const MaterialParameters &params) {
   ValidationErrors result;
-  if (params.chemicalSymbol.empty()) {
-    if (params.atomicNumber <= 0) {
-      result["ChemicalFormula"] = "Need to specify the material";
+  const bool chemicalSymbol{!params.chemicalSymbol.empty()};
+  const bool atomicNumber{params.atomicNumber != 0};
+  if (!chemicalSymbol && !atomicNumber) {
+    if (isEmpty(params.coherentXSection)) {
+      result["CoherentXSection"] = "The cross section must be specified when "
+                                   "no ChemicalFormula or AtomicNumber is "
+                                   "given.";
     }
-  } else {
-    if (params.atomicNumber > 0)
-      result["AtomicNumber"] =
-          "Cannot specify both ChemicalFormula and AtomicNumber";
+    if (isEmpty(params.incoherentXSection)) {
+      result["IncoherentXSection"] = "The cross section must be specified when "
+                                     "no ChemicalFormula or AtomicNumber is "
+                                     "given.";
+    }
+    if (isEmpty(params.attenuationXSection)) {
+      result["AttenuationXSection"] = "The cross section must be specified "
+                                      "when no ChemicalFormula or AtomicNumber "
+                                      "is given.";
+    }
+    if (isEmpty(params.scatteringXSection)) {
+      result["ScatteringXSection"] = "The cross section must be specified when "
+                                     "no ChemicalFormula or AtomicNumber is "
+                                     "given.";
+    }
+    if (isEmpty(params.sampleNumberDensity)) {
+      result["SampleNumberDensity"] =
+          "The number density must be specified with a use-defined material.";
+    }
+  } else if (chemicalSymbol && atomicNumber) {
+    result["AtomicNumber"] =
+        "Cannot specify both ChemicalFormula and AtomicNumber";
   }
 
   if (params.massNumber > 0 && params.atomicNumber <= 0)
@@ -42,16 +65,16 @@ ValidationErrors ReadMaterial::validateInputs(const MaterialParameters params) {
     }
     if (!isEmpty(params.sampleNumberDensity)) {
       result["ZParameter"] =
-          "Can not give ZParameter with SampleNumberDensity set";
+          "Cannot give ZParameter with SampleNumberDensity set";
     }
     if (!isEmpty(params.sampleMassDensity)) {
       result["SampleMassDensity"] =
-          "Can not give SampleMassDensity with ZParameter set";
+          "Cannot give SampleMassDensity with ZParameter set";
     }
   } else if (!isEmpty(params.sampleNumberDensity)) {
     if (!isEmpty(params.sampleMassDensity)) {
       result["SampleMassDensity"] =
-          "Can not give SampleMassDensity with SampleNumberDensity set";
+          "Cannot give SampleMassDensity with SampleNumberDensity set";
     }
   }
   return result;
@@ -63,7 +86,7 @@ ValidationErrors ReadMaterial::validateInputs(const MaterialParameters params) {
  *
  * @param params A struct containing all the parameters to be set.
  */
-void ReadMaterial::setMaterialParameters(MaterialParameters params) {
+void ReadMaterial::setMaterialParameters(const MaterialParameters &params) {
   setMaterial(params.chemicalSymbol, params.atomicNumber, params.massNumber);
   setNumberDensity(params.sampleMassDensity, params.sampleNumberDensity,
                    params.zParameter, params.unitCellVolume);
@@ -83,9 +106,8 @@ std::unique_ptr<Kernel::Material> ReadMaterial::buildMaterial() {
 void ReadMaterial::setMaterial(const std::string chemicalSymbol,
                                const int atomicNumber, const int massNumber) {
   if (!chemicalSymbol.empty()) {
-    std::cout << "CHEM: " << chemicalSymbol << std::endl;
     builder.setFormula(chemicalSymbol);
-  } else {
+  } else if (atomicNumber != 0) {
     builder.setAtomicNumber(atomicNumber);
     builder.setMassNumber(massNumber);
   }
@@ -120,4 +142,4 @@ bool ReadMaterial::isEmpty(const double toCheck) {
   return std::abs((toCheck - EMPTY_DBL()) / (EMPTY_DBL())) < 1e-8;
 }
 } // namespace DataHandling
-} // namespace Mantid
\ No newline at end of file
+} // namespace Mantid
diff --git a/Framework/DataHandling/src/SNSDataArchive.cpp b/Framework/DataHandling/src/SNSDataArchive.cpp
deleted file mode 100644
index af276e1d0bed47629c23c5f54701eb371428fb7f..0000000000000000000000000000000000000000
--- a/Framework/DataHandling/src/SNSDataArchive.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-//     NScD Oak Ridge National Laboratory, European Spallation Source
-//     & Institut Laue - Langevin
-// SPDX - License - Identifier: GPL - 3.0 +
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include <sstream>
-
-#include "MantidAPI/ArchiveSearchFactory.h"
-#include "MantidDataHandling/SNSDataArchive.h"
-#include "MantidKernel/Exception.h"
-#include "MantidKernel/InternetHelper.h"
-#include "MantidKernel/Logger.h"
-
-#include <boost/algorithm/string.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-
-#include <Poco/AutoPtr.h>
-#include <Poco/DOM/DOMParser.h>
-#include <Poco/DOM/Document.h>
-#include <Poco/DOM/Element.h>
-#include <Poco/DOM/NodeList.h>
-#include <Poco/SAX/InputSource.h>
-
-namespace Mantid {
-namespace DataHandling {
-namespace {
-// Get a reference to the logger
-Kernel::Logger g_log("SNSDataArchive");
-/// Base url for restful web survice
-const std::string
-    BASE_URL("http://icat.sns.gov:2080/icat-rest-ws/datafile/filename/");
-} // namespace
-
-DECLARE_ARCHIVESEARCH(SNSDataArchive, SNSDataSearch)
-
-/**
- * @param filenames : List of files to search
- * @param exts : List of extensions to check against
- * @return list of archive locations
- */
-std::string
-SNSDataArchive::getArchivePath(const std::set<std::string> &filenames,
-                               const std::vector<std::string> &exts) const {
-  g_log.debug() << "getArchivePath([ ";
-  for (const auto &iter : filenames)
-    g_log.debug() << iter << " ";
-  g_log.information() << "], [ ";
-  for (const auto &iter : exts)
-    g_log.debug() << iter << " ";
-  g_log.debug() << "])\n";
-
-  auto iter = filenames.cbegin();
-  std::string filename = *iter;
-
-  // ICAT4 web service take upper case filename such as HYSA_2662
-  std::transform(filename.begin(), filename.end(), filename.begin(), toupper);
-
-  for (const auto &ext : exts) {
-    g_log.debug() << ext << ";";
-  }
-  g_log.debug() << "\n";
-
-  const std::string URL(BASE_URL + filename);
-  g_log.debug() << "URL: " << URL << "\n";
-
-  Kernel::InternetHelper inetHelper;
-  std::ostringstream rs;
-
-  int status = inetHelper.sendRequest(URL, rs);
-
-  // Create a DOM document from the response.
-  Poco::XML::DOMParser parser;
-  std::istringstream istrsource(rs.str());
-  Poco::XML::InputSource source(istrsource);
-  Poco::AutoPtr<Poco::XML::Document> pDoc = parser.parse(&source);
-
-  std::vector<std::string> locations;
-
-  // Everything went fine, return the XML document.
-  // Otherwise look for an error message in the XML document.
-  if (status == Kernel::InternetHelper::HTTP_OK) {
-    std::string location;
-    Poco::AutoPtr<Poco::XML::NodeList> pList =
-        pDoc->getElementsByTagName("location");
-    for (unsigned long i = 0; i < pList->length(); i++) {
-      location = pList->item(i)->innerText();
-      g_log.debug() << "location: " << location << "\n";
-      locations.push_back(location);
-    }
-  }
-
-  for (const auto &ext : exts) {
-    std::string datafile = filename + ext;
-    std::vector<std::string>::const_iterator iter = locations.begin();
-    for (; iter != locations.end(); ++iter) {
-      if (boost::algorithm::ends_with((*iter), datafile)) {
-        return *iter;
-      } // end if
-    }   // end for iter
-
-  } // end for ext
-  return "";
-}
-
-} // namespace DataHandling
-} // namespace Mantid
diff --git a/Framework/DataHandling/src/SaveNXcanSAS.cpp b/Framework/DataHandling/src/SaveNXcanSAS.cpp
index b15b6c312ff978f29eb2c5937ab334098b325781..c645384ad43e2798b5d62b6ab956a62d25e485d4 100644
--- a/Framework/DataHandling/src/SaveNXcanSAS.cpp
+++ b/Framework/DataHandling/src/SaveNXcanSAS.cpp
@@ -701,7 +701,7 @@ void addTransmission(H5::Group &group,
 
   //-----------------------------------------
   // Add Tdev with units
-  const auto transmissionErrors = workspace->e(0);
+  const auto &transmissionErrors = workspace->e(0);
   std::map<std::string, std::string> transmissionErrorAttributes;
   transmissionErrorAttributes.emplace(sasUnitAttr, unit);
 
@@ -711,7 +711,7 @@ void addTransmission(H5::Group &group,
 
   //-----------------------------------------
   // Add lambda with units
-  const auto lambda = workspace->x(0);
+  const auto &lambda = workspace->x(0);
   std::map<std::string, std::string> lambdaAttributes;
   auto lambdaUnit = getUnitFromMDDimension(workspace->getDimension(0));
   if (lambdaUnit.empty() || lambdaUnit == "Angstrom") {
diff --git a/Framework/DataHandling/src/SaveNexusProcessed.cpp b/Framework/DataHandling/src/SaveNexusProcessed.cpp
index f09c44c862e181ca3d5a2428fa4cc28c447f2fe9..b8b18ba1dca7e4f067388cfa2cbae3d8652baba1 100644
--- a/Framework/DataHandling/src/SaveNexusProcessed.cpp
+++ b/Framework/DataHandling/src/SaveNexusProcessed.cpp
@@ -520,16 +520,16 @@ bool SaveNexusProcessed::processGroups() {
     }
   }
 
-  // Only the input workspace property can take group workspaces. Therefore
-  // index = 0.
-  std::vector<Workspace_sptr> &thisGroup = m_groups[0];
-  if (!thisGroup.empty()) {
-    for (size_t entry = 0; entry < m_groupSize; entry++) {
-      Workspace_sptr ws = thisGroup[entry];
+  // If we have arrived here then a WorkspaceGroup was passed to the
+  // InputWorkspace property. Pull out the unrolled workspaces and append an
+  // entry for each one. We only have a single input workspace property declared
+  // so there will only be a single list of unrolled workspaces
+  const auto &workspaces = m_unrolledInputWorkspaces[0];
+  if (!workspaces.empty()) {
+    for (size_t entry = 0; entry < workspaces.size(); entry++) {
+      const Workspace_sptr ws = workspaces[entry];
       this->doExec(ws, nexusFile, true /*keepFile*/, entry);
-      std::stringstream buffer;
-      buffer << "Saving group index " << entry;
-      m_log.information(buffer.str());
+      g_log.information() << "Saving group index " << entry << "\n";
     }
   }
 
diff --git a/Framework/DataHandling/src/SetSample.cpp b/Framework/DataHandling/src/SetSample.cpp
index f2c00b40591fdfeac199585f4774e59a8cf188a7..29f4074a4a3e77cbc2b53768537367de43d59404 100644
--- a/Framework/DataHandling/src/SetSample.cpp
+++ b/Framework/DataHandling/src/SetSample.cpp
@@ -11,6 +11,7 @@
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidGeometry/Instrument/ReferenceFrame.h"
+#include "MantidGeometry/Instrument/SampleEnvironment.h"
 #include "MantidGeometry/Instrument/SampleEnvironmentFactory.h"
 #include "MantidKernel/ConfigService.h"
 #include "MantidKernel/FacilityInfo.h"
@@ -303,8 +304,7 @@ const Geometry::SampleEnvironment *SetSample::setSampleEnvironment(
   SampleEnvironmentFactory factory(std::move(finder));
   auto sampleEnviron =
       factory.create(facilityName, instrumentName, envName, canName);
-  workspace->mutableSample().setEnvironment(sampleEnviron.release());
-
+  workspace->mutableSample().setEnvironment(std::move(sampleEnviron));
   return &(workspace->sample().getEnvironment());
 }
 
diff --git a/Framework/DataHandling/src/SetSampleMaterial.cpp b/Framework/DataHandling/src/SetSampleMaterial.cpp
index 5d4a74c1aaecf554899940e3a5ee88fce8becae6..42a15d98860488984ab3a209ce2a93b8699cc4d0 100644
--- a/Framework/DataHandling/src/SetSampleMaterial.cpp
+++ b/Framework/DataHandling/src/SetSampleMaterial.cpp
@@ -49,7 +49,7 @@ void SetSampleMaterial::init() {
                   "The chemical formula, see examples in documentation");
   declareProperty("AtomicNumber", 0, "The atomic number");
   declareProperty("MassNumber", 0,
-                  "Mass number if ion (use 0 for default mass sensity)");
+                  "Mass number if ion (use 0 for default mass number)");
   auto mustBePositive = boost::make_shared<BoundedValidator<double>>();
   mustBePositive->setLower(0.0);
   declareProperty("SampleNumberDensity", EMPTY_DBL(), mustBePositive,
@@ -62,13 +62,13 @@ void SetSampleMaterial::init() {
                   "Unit cell volume in Angstoms^3. Will be calculated from the "
                   "OrientedLattice if not supplied.");
   declareProperty("CoherentXSection", EMPTY_DBL(), mustBePositive,
-                  "Optional:  This coherent cross-section for the sample "
+                  "This coherent cross-section for the sample "
                   "material in barns will be used instead of tabulated");
   declareProperty("IncoherentXSection", EMPTY_DBL(), mustBePositive,
-                  "Optional:  This incoherent cross-section for the sample "
+                  "This incoherent cross-section for the sample "
                   "material in barns will be used instead of tabulated");
   declareProperty("AttenuationXSection", EMPTY_DBL(), mustBePositive,
-                  "Optional:  This absorption cross-section for the sample "
+                  "This absorption cross-section for the sample "
                   "material in barns will be used instead of tabulated");
   declareProperty("ScatteringXSection", EMPTY_DBL(), mustBePositive,
                   "Optional:  This total scattering cross-section (coherent + "
@@ -115,6 +115,10 @@ std::map<std::string, std::string> SetSampleMaterial::validateInputs() {
   params.zParameter = getProperty("ZParameter");
   params.unitCellVolume = getProperty("UnitCellVolume");
   params.sampleMassDensity = getProperty("SampleMassDensity");
+  params.coherentXSection = getProperty("CoherentXSection");
+  params.incoherentXSection = getProperty("IncoherentXSection");
+  params.attenuationXSection = getProperty("AttenuationXSection");
+  params.scatteringXSection = getProperty("ScatteringXSection");
   auto result = ReadMaterial::validateInputs(params);
 
   return result;
@@ -152,18 +156,15 @@ void SetSampleMaterial::exec() {
   // an ExperimentInfo object has a sample
   ExperimentInfo_sptr expInfo =
       boost::dynamic_pointer_cast<ExperimentInfo>(workspace);
-  if (!bool(expInfo)) {
+  if (!expInfo) {
     throw std::runtime_error("InputWorkspace does not have a sample object");
   }
 
   ReadMaterial reader;
-  params.coherentXSection = getProperty("CoherentXSection");
-  params.incoherentXSection = getProperty("IncoherentXSection");
-  params.attenuationXSection = getProperty("AttenuationXSection");
-  params.scatteringXSection = getProperty("ScatteringXSection");
   reader.setMaterialParameters(params);
 
-  double rho = getProperty("SampleNumberDensity"); // in atoms / Angstroms^3
+  const double rho =
+      getProperty("SampleNumberDensity"); // in atoms / Angstroms^3
 
   // get the scattering information - this will override table values
   // create the material
diff --git a/Framework/DataHandling/test/CompressEventsTest.h b/Framework/DataHandling/test/CompressEventsTest.h
index f3a7b711f854c9333a988f70e36488ba037df57e..64c8568bfb1b09baf39cf1612b9474936a7c9d93 100644
--- a/Framework/DataHandling/test/CompressEventsTest.h
+++ b/Framework/DataHandling/test/CompressEventsTest.h
@@ -12,6 +12,7 @@
 #include "MantidAPI/Axis.h"
 #include "MantidDataHandling/CompressEvents.h"
 #include "MantidDataObjects/Workspace2D.h"
+#include "MantidKernel/Unit.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 
 using Mantid::MantidVecPtr;
@@ -113,8 +114,8 @@ public:
     TS_ASSERT_DELTA(output->readY(0)[1], 2.0, 1e-5);
     TS_ASSERT_DELTA(output->readE(0)[1], M_SQRT2, 1e-5);
     TS_ASSERT_EQUALS(output->YUnit(), input->YUnit());
-    TS_ASSERT_EQUALS(output->getAxis(0)->unit(), input->getAxis(0)->unit());
-    TS_ASSERT_EQUALS(output->getAxis(1)->unit(), input->getAxis(1)->unit());
+    TS_ASSERT(*output->getAxis(0)->unit() == *input->getAxis(0)->unit());
+    TS_ASSERT(*output->getAxis(1)->unit() == *input->getAxis(1)->unit());
   }
 
   // WEIGHTED_NOTIME tests
diff --git a/Framework/DataHandling/test/GroupDetectors2Test.h b/Framework/DataHandling/test/GroupDetectors2Test.h
index d90a8defc2f24f0e4d27e9022b3f4843df9ebc76..8ffdda6b577075d09b182f7417946db294101f9b 100644
--- a/Framework/DataHandling/test/GroupDetectors2Test.h
+++ b/Framework/DataHandling/test/GroupDetectors2Test.h
@@ -1209,7 +1209,7 @@ public:
     for (size_t pix = 0; pix < inputEventWs->getNumberHistograms(); pix++) {
       size_t xAxisSize = inputEventWs->x(pix).size();
       Mantid::HistogramData::HistogramX axisVals(xAxisSize, 1.0);
-      inputEventWs->mutableX(pix) = axisVals;
+      inputEventWs->mutableX(pix) = std::move(axisVals);
       inputEventWs->getSpectrum(pix).addEventQuickly(TofEvent(1000.0));
     }
     setupGroupWS(numGroups);
diff --git a/Framework/DataHandling/test/ISISDataArchiveTest.h b/Framework/DataHandling/test/ISISDataArchiveTest.h
index 5d9a8c90b04473dc6ab61cfc9cd4a9f0fc829649..5c85051226ecef07ca2d818fc11bb5e05c408061 100644
--- a/Framework/DataHandling/test/ISISDataArchiveTest.h
+++ b/Framework/DataHandling/test/ISISDataArchiveTest.h
@@ -9,6 +9,7 @@
 
 #include <cxxtest/TestSuite.h>
 #include <fstream>
+#include <sstream>
 
 #include "MantidAPI/ArchiveSearchFactory.h"
 #include "MantidDataHandling/ISISDataArchive.h"
@@ -16,22 +17,121 @@
 using namespace Mantid::DataHandling;
 using namespace Mantid::API;
 
+class MockOutRequests : public ISISDataArchive {
+public:
+  MockOutRequests()
+      : m_sendRequestReturnVal("/archive/default/path"),
+        m_mockFileExists(true) {}
+
+  void setSendRequestReturnVal(std::string &return_val) {
+    m_sendRequestReturnVal = return_val;
+  }
+
+  void setFileExists(const bool doesFileExist) {
+    m_mockFileExists = doesFileExist;
+  }
+
+protected:
+  /**
+   * Mocked out sendRequest.
+   * sendRequest in ISISDataArchive makes a call to a web service
+   * which returns an ostringstream object containing a string
+   * of the directory containing the file.
+   * (if the file doesn't exist, it returns the newest directory)
+   * This mock returns an ostringstream object containing
+   * m_sendRequestReturnVal
+   * @param fName :: file name without extension. Irrelevant to this mock
+   */
+  std::ostringstream sendRequest(const std::string &fName) const override {
+    (void)fName;
+    std::ostringstream os;
+    os << m_sendRequestReturnVal;
+    return os;
+  }
+
+  /**
+   * Mocked out fileExists.
+   * Mock searchs for files with extensions.
+   * This is a simplistic version. Will return m_mockFileExists
+   * for any `path`.
+   * Except for hard-coded case of path ending in .txt
+   * This exception is to test that
+   * `getCorrectExtension` will loop until it finds
+   * the first acceptable extension.
+   * @param path :: path to file, including extension
+   */
+  bool fileExists(const std::string &path) const override {
+    if (path.substr(path.size() - 4, 4) == ".txt") {
+      return false;
+    }
+    return m_mockFileExists;
+  }
+
+private:
+  // Mimics the directory tree returned by sendRequest
+  // e.g. /archive/ndxloq/Instrument/data/cycle_98_0
+  std::string m_sendRequestReturnVal;
+
+  // Used in mocking fileExists.
+  bool m_mockFileExists;
+};
+
 class ISISDataArchiveTest : public CxxTest::TestSuite {
 public:
-  void xtestSearch() {
-    ISISDataArchive arch;
+  void testGetCorrectExtensionsWithCorrectExtensions() {
+    std::vector<std::string> exts = {".RAW"};
+    std::string filename = "/archive/default/path/hrpd273";
+
+    MockOutRequests arch;
+    const std::string actualPath = arch.getCorrectExtension(filename, exts);
+    TS_ASSERT_EQUALS(actualPath, "/archive/default/path/hrpd273.RAW");
+  }
+
+  void testGetCorrectExtensionsWithInCorrectExtensions() {
+    std::vector<std::string> exts = {".RAW"};
+    std::string filename = "hrpd273";
+
+    MockOutRequests arch;
+    arch.setFileExists(false);
+
+    const std::string actualPath = arch.getCorrectExtension(filename, exts);
+    TS_ASSERT_EQUALS(actualPath, "");
+  }
+
+  void testGetCorrectExtensionsLoopsUntilFindsFirstCorrectExtension() {
+    std::vector<std::string> exts = {".txt", ".RAW"};
+    std::string filename = "/archive/default/path/hrpd273";
+
+    MockOutRequests arch;
+    const std::string actualPath = arch.getCorrectExtension(filename, exts);
+    TS_ASSERT_EQUALS(actualPath, "/archive/default/path/hrpd273.RAW");
+  }
+
+  void testFilenameLoopIgnoresEmptyFilenames() {
+    std::vector<std::string> exts = {".RAW"};
+    std::set<std::string> filenames = {"", "", "", "hrpd273"};
 
-    std::set<std::string> filename;
-    filename.insert("hrpd273");
-    std::vector<std::string> extension = std::vector<std::string>(1, "");
-    std::string path = arch.getArchivePath(filename, extension);
-    std::cout << "(hrpd273)= " << path << '\n';
-    TS_ASSERT_EQUALS(path.substr(path.size() - 18, 10), "cycle_98_0");
+    MockOutRequests arch;
+    const std::string actualPath = arch.getArchivePath(filenames, exts);
 
-    filename.clear();
-    filename.insert("hrpds70");
-    path = arch.getArchivePath(filename, extension);
-    TS_ASSERT(path.empty());
+    std::string expectedPath = "/archive/default/path";
+#ifdef _WIN32
+    expectedPath += "\\";
+#else
+    expectedPath += "/";
+#endif
+
+    TS_ASSERT_EQUALS(actualPath, expectedPath + "hrpd273.RAW");
+  }
+
+  void testGetArchivePathReturnsEmptyStringIfNoFileFound() {
+    std::vector<std::string> exts = {".RAW", ".log"};
+    std::set<std::string> filenames = {"hrpd280", "hrpd273"};
+
+    MockOutRequests arch;
+    arch.setFileExists(false);
+    const std::string actualPath = arch.getArchivePath(filenames, exts);
+    TS_ASSERT_EQUALS(actualPath, "");
   }
 
   void testFactory() {
@@ -39,6 +139,46 @@ public:
         ArchiveSearchFactory::Instance().create("ISISDataSearch");
     TS_ASSERT(arch);
   }
+
+  /*****UN 'x' THE FOLLOWING TESTS WHEN TESTING LOCALLY*****/
+  /**
+   * Un-'x' this test name to test locally.
+   * This tests the file extensions loop.
+   * To run, this tests requires that the ISIS archive is on your local machine.
+   */
+  void xtestgetCorrectExtensionWithCorrectExtensionWithWebCall() {
+    std::string path;
+#ifdef _WIN32
+    path = "\\isis.cclrc.ac.uk\\inst$\\ndxhrpd\\instrument\\data\\cycle_98_"
+           "0\\HRP00273";
+#else
+    path = "/archive/ndxhrpd/Instrument/data/cycle_98_0/HRP00273";
+#endif
+
+    ISISDataArchive arch;
+
+    const std::vector<std::string> correct_exts = {".RAW"};
+    const std::string actualResult =
+        arch.getCorrectExtension(path, correct_exts);
+    TS_ASSERT_EQUALS(actualResult, path + ".RAW");
+  }
+
+  void xtestgetCorrectExtensionWithInCorrectExtensionsWithWebCall() {
+    std::string path;
+#ifdef _WIN32
+    path = "\\isis.cclrc.ac.uk\\inst$\\ndxhrpd\\instrument\\data\\cycle_98_"
+           "0\\HRP00273";
+#else
+    path = "/archive/ndxhrpd/Instrument/data/cycle_98_0/HRP00273";
+#endif
+
+    ISISDataArchive arch;
+
+    const std::vector<std::string> incorrect_exts = {".so", ".txt"};
+    const std::string actualResult =
+        arch.getCorrectExtension(path, incorrect_exts);
+    TS_ASSERT_EQUALS(actualResult, "");
+  }
 };
 
 #endif /*ISISDATAARCHIVETEST_H_*/
diff --git a/Framework/DataHandling/test/LoadAsciiStlTest.h b/Framework/DataHandling/test/LoadAsciiStlTest.h
index 19d67dd4ce207a4de0432575937eb05fe23fb80c..64f6c29fead36eb448d8848d51f4da5e99d30ee9 100644
--- a/Framework/DataHandling/test/LoadAsciiStlTest.h
+++ b/Framework/DataHandling/test/LoadAsciiStlTest.h
@@ -1,3 +1,9 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #ifndef LOAD_ASCIISTL_TEST_H_
 #define LOAD_ASCIISTL_TEST_H_
 
@@ -17,7 +23,6 @@ public:
   static LoadAsciiStlTest *createSuite() { return new LoadAsciiStlTest(); }
   static void destroySuite(LoadAsciiStlTest *suite) { delete suite; }
 
-  void testInit() {}
   void test_cube() {
     std::string path = FileFinder::Instance().getFullPath("cube.stl");
     auto Loader = LoadAsciiStl(path);
@@ -67,13 +72,13 @@ public:
   void test_return_false_on_binary_stl() {
     std::string path = FileFinder::Instance().getFullPath("cubeBin.stl");
     auto Loader = LoadAsciiStl(path);
-    TS_ASSERT(!(Loader.isAsciiSTL()));
+    TS_ASSERT(!(Loader.isAsciiSTL(path)));
   }
 
   void test_return_false_on_invalid_solid() {
     std::string path = FileFinder::Instance().getFullPath("invalid_solid.stl");
     auto Loader = LoadAsciiStl(path);
-    TS_ASSERT(!(Loader.isAsciiSTL()));
+    TS_ASSERT(!(Loader.isAsciiSTL(path)));
   }
 };
 
diff --git a/Framework/DataHandling/test/LoadBinaryStlTest.h b/Framework/DataHandling/test/LoadBinaryStlTest.h
index fd5c3e33cf86ec75a9eb7928a7791002b889eb01..7a0ef1cc073d6d10b187fe5c9a0e6e1821606519 100644
--- a/Framework/DataHandling/test/LoadBinaryStlTest.h
+++ b/Framework/DataHandling/test/LoadBinaryStlTest.h
@@ -1,3 +1,9 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #ifndef LOAD_BINARYSTL_TEST_H_
 #define LOAD_BINARYSTL_TEST_H_
 #include "MantidAPI/FileFinder.h"
@@ -48,7 +54,7 @@ public:
     std::string path =
         FileFinder::Instance().getFullPath("invalid_vertexBin.stl");
     auto loader = LoadBinaryStl(path);
-    TS_ASSERT(!(loader.isBinarySTL()));
+    TS_ASSERT(!(loader.isBinarySTL(path)));
   }
   // check that isBinaryStl returns false if the file contains an incomplete
   // triangle
@@ -56,14 +62,20 @@ public:
     std::string path =
         FileFinder::Instance().getFullPath("invalid_triangleBin.stl");
     auto loader = LoadBinaryStl(path);
-    TS_ASSERT(!(loader.isBinarySTL()));
+    TS_ASSERT(!(loader.isBinarySTL(path)));
   }
 
   void test_fail_ascii_stl() {
     std::string path = FileFinder::Instance().getFullPath("cube.stl");
     auto loader = LoadBinaryStl(path);
-    TS_ASSERT(!(loader.isBinarySTL()));
+    TS_ASSERT(!(loader.isBinarySTL(path)));
   }
-};
 
+  void test_loading_large_stl() {
+    std::string path = FileFinder::Instance().getFullPath("SI-4200-610.stl");
+    auto loader = LoadBinaryStl(path);
+    auto LargeFile = loader.readStl();
+    assert_shape_matches(LargeFile, 174388, 424694, 21218, 1);
+  }
+};
 #endif /* LOAD_BINARYSTL_TEST_H_ */
\ No newline at end of file
diff --git a/Framework/DataHandling/test/LoadEMUauTest.h b/Framework/DataHandling/test/LoadEMUauTest.h
index 4c9dffc97264f2a5f941523bbdfe05a4565c8d2b..e446176af3657fd2dc764e5d1497e14f57e0bc2b 100644
--- a/Framework/DataHandling/test/LoadEMUauTest.h
+++ b/Framework/DataHandling/test/LoadEMUauTest.h
@@ -15,6 +15,12 @@
 #include <Poco/Path.h>
 #include <Poco/TemporaryFile.h>
 
+#ifdef _MSC_VER
+// Disable warning on 'no suitable definition ..' as the extern
+// does not clear the warning. No issue linking.
+#pragma warning(disable : 4661)
+#endif
+
 using namespace Mantid::API;
 using namespace Mantid::Kernel;
 using namespace Mantid::DataHandling;
@@ -23,14 +29,14 @@ using namespace Mantid::DataObjects;
 class LoadEMUauTest : public CxxTest::TestSuite {
 public:
   void test_load_emu_algorithm_init() {
-    LoadEMU algToBeTested;
+    LoadEMUTar algToBeTested;
 
     TS_ASSERT_THROWS_NOTHING(algToBeTested.initialize());
     TS_ASSERT(algToBeTested.isInitialized());
   }
 
   void test_load_emu_algorithm() {
-    LoadEMU algToBeTested;
+    LoadEMUTar algToBeTested;
 
     if (!algToBeTested.isInitialized())
       algToBeTested.initialize();
@@ -72,7 +78,8 @@ public:
 
     // test some data properties
     auto logpm = [&run](std::string tag) {
-      return run.getPropertyValueAsType<double>(tag);
+      return dynamic_cast<TimeSeriesProperty<double> *>(run.getProperty(tag))
+          ->firstValue();
     };
     TS_ASSERT_DELTA(logpm("DopplerFrequency"), 9.974, 1.0e-3);
     TS_ASSERT_DELTA(logpm("DopplerAmplitude"), 0.075, 1.0e-4);
@@ -87,4 +94,4 @@ public:
   }
 };
 
-#endif /*LoadEMUTEST_H_*/
\ No newline at end of file
+#endif /*LoadEMUTEST_H_*/
diff --git a/Framework/DataHandling/test/LoadILLDiffractionTest.h b/Framework/DataHandling/test/LoadILLDiffractionTest.h
index 3e076c3edc90a8fb50822252b185a9b676f64f3d..e9d2127006f444d86589a1f4158bb43ef3b8bada 100644
--- a/Framework/DataHandling/test/LoadILLDiffractionTest.h
+++ b/Framework/DataHandling/test/LoadILLDiffractionTest.h
@@ -15,6 +15,7 @@
 #include "MantidDataHandling/LoadILLDiffraction.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidKernel/ConfigService.h"
+#include "MantidKernel/FacilityInfo.h"
 
 using namespace Mantid::API;
 using namespace Mantid::Kernel;
@@ -32,7 +33,22 @@ public:
   void setUp() override {
     ConfigService::Instance().appendDataSearchSubDir("ILL/D20/");
     ConfigService::Instance().appendDataSearchSubDir("ILL/D2B/");
+
+    m_oldFacility = ConfigService::Instance().getFacility().name();
     ConfigService::Instance().setFacility("ILL");
+
+    m_oldInstrument = ConfigService::Instance().getInstrument().name();
+    ConfigService::Instance().setString("default.instrument", "");
+  }
+
+  void tearDown() override {
+    if (!m_oldFacility.empty()) {
+      ConfigService::Instance().setFacility(m_oldFacility);
+    }
+    if (!m_oldInstrument.empty()) {
+      ConfigService::Instance().setString("default.instrument",
+                                          m_oldInstrument);
+    }
   }
 
   void test_Init() {
@@ -400,6 +416,8 @@ public:
 
 private:
   const double RAD_2_DEG = 180.0 / M_PI;
+  std::string m_oldFacility;
+  std::string m_oldInstrument;
 };
 
 #endif /* MANTID_DATAHANDLING_LOADILLDIFFRACTIONTEST_H_ */
diff --git a/Framework/DataHandling/test/LoadQKKTest.h b/Framework/DataHandling/test/LoadQKKTest.h
index 4cb34ea6a275e0ec23719b1e31239ccb03a42450..6055850102066a18f916c5f42532cd2293232964 100644
--- a/Framework/DataHandling/test/LoadQKKTest.h
+++ b/Framework/DataHandling/test/LoadQKKTest.h
@@ -61,15 +61,15 @@ public:
     for (size_t i = 0; i < data->getNumberHistograms(); ++i) {
       TS_ASSERT_THROWS_NOTHING(spectrumInfo.detector(i));
 
-      auto x = data->x(i);
+      const auto &x = data->x(i);
       TS_ASSERT_EQUALS(x.size(), 2);
       TS_ASSERT_DELTA(x[0], 4.9639999139, 1e-8);
       TS_ASSERT_DELTA(x[1], 5.1039999245, 1e-8);
 
-      auto y = data->y(i);
+      const auto &y = data->y(i);
       TS_ASSERT_DIFFERS(y[0], 0.0);
 
-      auto e = data->e(i);
+      const auto &e = data->e(i);
       TS_ASSERT_DIFFERS(e[0], 0.0);
     }
   }
diff --git a/Framework/DataHandling/test/LoadSampleEnvironmentTest.h b/Framework/DataHandling/test/LoadSampleEnvironmentTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..718ddd7bf5e2da10445a21687e63cc95dcde142d
--- /dev/null
+++ b/Framework/DataHandling/test/LoadSampleEnvironmentTest.h
@@ -0,0 +1,142 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef LOAD_ENVIRONMENTEST_H_
+#define LOAD_ENVIRONMENTEST_H_
+
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/FileFinder.h"
+#include "MantidAPI/FrameworkManager.h"
+#include "MantidAPI/Sample.h"
+#include "MantidDataHandling/LoadBinaryStl.h"
+#include "MantidDataHandling/LoadInstrument.h"
+#include "MantidDataHandling/LoadSampleEnvironment.h"
+#include "MantidGeometry/Instrument/SampleEnvironment.h"
+#include "MantidGeometry/Objects/MeshObject.h"
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include <cxxtest/TestSuite.h>
+
+using namespace Mantid;
+using namespace Mantid::API;
+using namespace Mantid::DataHandling;
+using namespace Mantid::Geometry;
+
+class LoadSampleEnvironmentTest : public CxxTest::TestSuite {
+public:
+  static LoadSampleEnvironmentTest *createSuite() {
+    return new LoadSampleEnvironmentTest();
+  }
+  static void destroySuite(LoadSampleEnvironmentTest *suite) { delete suite; }
+
+  void testInit() {
+
+    LoadSampleEnvironment alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize());
+    TS_ASSERT(alg.isInitialized());
+
+    TSM_ASSERT_EQUALS("should be 19 properties here", 19,
+                      (size_t)(alg.getProperties().size()));
+  }
+
+  void testTranslate() {
+    LoadSampleEnvironment alg;
+    alg.initialize();
+    alg.setProperty("TranslationVector", "5,5,15");
+    boost::shared_ptr<MeshObject> environmentMesh = nullptr;
+    environmentMesh = loadCube();
+    alg.translate(environmentMesh);
+    auto translatedVertices = environmentMesh->getVertices();
+    double arrayToMatch[] = {0,  0, 0,  10, 10, 0,  10, 0,  0,  0, 10, 0,
+                             10, 0, 30, 10, 10, 30, 0,  10, 30, 0, 0,  30};
+    std::vector<double> vectorToMatch =
+        std::vector<double>(std::begin(arrayToMatch), std::end(arrayToMatch));
+    TS_ASSERT(translatedVertices == vectorToMatch);
+  }
+
+  void testTranslateFailWrongSize() {
+    LoadSampleEnvironment alg;
+    alg.initialize();
+    alg.setProperty("TranslationVector", "-1,0,1,0,0,0,0,1");
+    boost::shared_ptr<MeshObject> environmentMesh = nullptr;
+    environmentMesh = loadCube();
+    TS_ASSERT_THROWS(alg.translate(environmentMesh), std::invalid_argument &);
+  }
+
+  void testRotate() {
+    LoadSampleEnvironment alg;
+    alg.initialize();
+    alg.setProperty("RotationMatrix", "0,-1,0,1,0,0,0,0,1");
+    boost::shared_ptr<MeshObject> environmentMesh = nullptr;
+    environmentMesh = loadCube();
+    alg.rotate(environmentMesh);
+    auto rotatedVertices = environmentMesh->getVertices();
+    double arrayToMatch[] = {5, -5, -15, -5, 5, -15, 5,  5,  -15, -5, -5, -15,
+                             5, 5,  15,  -5, 5, 15,  -5, -5, 15,  5,  -5, 15};
+    std::vector<double> vectorToMatch =
+        std::vector<double>(std::begin(arrayToMatch), std::end(arrayToMatch));
+    TS_ASSERT(rotatedVertices == vectorToMatch);
+  }
+
+  void testRotateFailWrongSize() {
+    LoadSampleEnvironment alg;
+    alg.initialize();
+    alg.setProperty("RotationMatrix", "-1,0,1,0,0,0,0,1");
+    boost::shared_ptr<MeshObject> environmentMesh = nullptr;
+    environmentMesh = loadCube();
+    TS_ASSERT_THROWS(alg.rotate(environmentMesh), std::invalid_argument);
+  }
+
+  void testRotateFailInvalidMatrix() {
+    LoadSampleEnvironment alg;
+    alg.initialize();
+    alg.setProperty("RotationMatrix", "6,1,1,4,-2,5,2,8,7");
+    boost::shared_ptr<MeshObject> environmentMesh = nullptr;
+    environmentMesh = loadCube();
+    TS_ASSERT_THROWS(alg.rotate(environmentMesh), std::invalid_argument);
+  }
+
+  void testSetMaterial() {
+    LoadSampleEnvironment alg;
+    alg.initialize();
+    std::string path = FileFinder::Instance().getFullPath("cubeBin.stl");
+    alg.setProperty("Filename", path);
+    alg.setPropertyValue("EnvironmentName", "testName");
+    alg.setProperty("SetMaterial", true);
+    alg.setProperty("AtomicNumber", 1);
+    alg.setProperty("MassNumber", 1);
+    alg.setProperty("SampleNumberDensity", 1.0);
+    const int nvectors(2), nbins(10);
+    MatrixWorkspace_sptr inputWS =
+        WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(nvectors,
+                                                                     nbins);
+    alg.setChild(true);
+    alg.setProperty("InputWorkspace", inputWS);
+    alg.setPropertyValue("OutputWorkspace", "outputWorkspace");
+    alg.execute();
+    TS_ASSERT(alg.isExecuted());
+    MatrixWorkspace_sptr ws = alg.getProperty("OutputWorkspace");
+    const auto &sample(ws->sample());
+    const Geometry::SampleEnvironment environment = sample.getEnvironment();
+    const auto can = environment.container();
+    const auto material = can->material();
+    TSM_ASSERT_EQUALS(("expected elements"), environment.nelements(), 1);
+    TS_ASSERT(can->hasValidShape());
+    TS_ASSERT_EQUALS(environment.name(), "testName");
+    TS_ASSERT_EQUALS(material.numberDensity(), 1);
+    TS_ASSERT_EQUALS(material.name(), "");
+  }
+
+private:
+  // load a cube into a meshobject
+  std::unique_ptr<MeshObject> loadCube() {
+    std::string path = FileFinder::Instance().getFullPath("cubeBin.stl");
+    auto loader = LoadBinaryStl(path);
+    auto cube = loader.readStl();
+    return cube;
+  }
+};
+
+#endif /* LOAD_ENVIRONMENTTEST_H_ */
\ No newline at end of file
diff --git a/Framework/DataHandling/test/LoadStlTest.h b/Framework/DataHandling/test/LoadStlTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..d3752f7b48c77015d6e6225391fb6e10377e8746
--- /dev/null
+++ b/Framework/DataHandling/test/LoadStlTest.h
@@ -0,0 +1,75 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef LOAD_STL_TEST_H_
+#define LOAD_STL_TEST_H_
+#include "MantidDataHandling/LoadStl.h"
+#include "MantidGeometry/Objects/MeshObject.h"
+#include "MantidKernel/V3D.h"
+#include <cxxtest/TestSuite.h>
+#include <functional>
+using namespace Mantid;
+using namespace Mantid::DataHandling;
+using namespace Mantid::Geometry;
+using namespace Mantid::Kernel;
+
+class LoadStlTest : public CxxTest::TestSuite {
+public:
+  static LoadStlTest *createSuite() { return new LoadStlTest(); }
+  static void destroySuite(LoadStlTest *suite) { delete suite; }
+
+  void test_same_V3D_hash_equal() {
+    V3D vertex1 = V3D(0, 0, 0);
+    auto pair1 = std::pair<V3D, uint32_t>(vertex1, 0);
+    auto pair2 = std::pair<V3D, uint32_t>(vertex1, 1);
+    TS_ASSERT_EQUALS(HashV3DPair{}(pair1), HashV3DPair{}(pair2));
+  }
+
+  void test_V3D_same_val_hash_equal() {
+    V3D vertex1 = V3D(0, 0, 0);
+    V3D vertex2 = V3D(0, 0, 0);
+    auto pair1 = std::pair<V3D, uint32_t>(vertex1, 0);
+    auto pair2 = std::pair<V3D, uint32_t>(vertex2, 1);
+    TS_ASSERT_EQUALS(HashV3DPair{}(pair1), HashV3DPair{}(pair2));
+  }
+
+  void test_dif_first_val() {
+    V3D vertex1 = V3D(0, 0, 0);
+    V3D vertex2 = V3D(1, 0, 0);
+    auto pair1 = std::pair<V3D, uint32_t>(vertex1, 0);
+    auto pair2 = std::pair<V3D, uint32_t>(vertex2, 1);
+    TS_ASSERT_DIFFERS(HashV3DPair{}(pair1), HashV3DPair{}(pair2));
+  }
+
+  void test_dif_second_val() {
+    V3D vertex1 = V3D(0, 0, 0);
+    V3D vertex2 = V3D(0, 1, 0);
+    auto pair1 = std::pair<V3D, uint32_t>(vertex1, 0);
+    auto pair2 = std::pair<V3D, uint32_t>(vertex2, 1);
+    TS_ASSERT_DIFFERS(HashV3DPair{}(pair1), HashV3DPair{}(pair2));
+  }
+
+  void test_dif_third_val() {
+    V3D vertex1 = V3D(0, 0, 0);
+    V3D vertex2 = V3D(0, 0, 1);
+    auto pair1 = std::pair<V3D, uint32_t>(vertex1, 0);
+    auto pair2 = std::pair<V3D, uint32_t>(vertex2, 1);
+    TS_ASSERT_DIFFERS(HashV3DPair{}(pair1), HashV3DPair{}(pair2));
+  }
+
+  void test_order_matters() {
+    V3D vertex1 = V3D(1, 0, 0);
+    V3D vertex2 = V3D(0, 1, 0);
+    V3D vertex3 = V3D(0, 0, 1);
+    auto pair1 = std::pair<V3D, uint32_t>(vertex1, 0);
+    auto pair2 = std::pair<V3D, uint32_t>(vertex2, 1);
+    auto pair3 = std::pair<V3D, uint32_t>(vertex3, 2);
+    TS_ASSERT_DIFFERS(HashV3DPair{}(pair1), HashV3DPair{}(pair2));
+    TS_ASSERT_DIFFERS(HashV3DPair{}(pair2), HashV3DPair{}(pair3));
+    TS_ASSERT_DIFFERS(HashV3DPair{}(pair1), HashV3DPair{}(pair3));
+  }
+};
+#endif /* LOAD_STL_TEST_H_ */
\ No newline at end of file
diff --git a/Framework/DataHandling/test/ReadMaterialTest.h b/Framework/DataHandling/test/ReadMaterialTest.h
index 2f7158665844d8c141e91dcbccbced4fbd646166..b73d0c2b0261fc69d27cd4f013262f60fec542c4 100644
--- a/Framework/DataHandling/test/ReadMaterialTest.h
+++ b/Framework/DataHandling/test/ReadMaterialTest.h
@@ -63,18 +63,105 @@ public:
                      "Cannot specify both ChemicalFormula and AtomicNumber")
   }
 
-  void testFailureValidateInputsNoMaterial() {
+  void testFailureValidateInputsNoCoherentXSection() {
     const ReadMaterial::MaterialParameters params = []() -> auto {
       ReadMaterial::MaterialParameters setMaterial;
       setMaterial.atomicNumber = 0;
       setMaterial.massNumber = 0;
+      setMaterial.incoherentXSection = 1.;
+      setMaterial.attenuationXSection = 1.;
+      setMaterial.scatteringXSection = 1.;
+      setMaterial.sampleNumberDensity = 1.;
       return setMaterial;
     }
     ();
 
     auto result = ReadMaterial::validateInputs(params);
+    TS_ASSERT_EQUALS(result.size(), 1)
+    TS_ASSERT_EQUALS(result["CoherentXSection"],
+                     "The cross section must be specified when "
+                     "no ChemicalFormula or AtomicNumber is "
+                     "given.")
+  }
 
-    TS_ASSERT_EQUALS(result["ChemicalFormula"], "Need to specify the material")
+  void testFailureValidateInputsNoIncoherentXSection() {
+    const ReadMaterial::MaterialParameters params = []() -> auto {
+      ReadMaterial::MaterialParameters setMaterial;
+      setMaterial.atomicNumber = 0;
+      setMaterial.massNumber = 0;
+      setMaterial.coherentXSection = 1.;
+      setMaterial.attenuationXSection = 1.;
+      setMaterial.scatteringXSection = 1.;
+      setMaterial.sampleNumberDensity = 1.;
+      return setMaterial;
+    }
+    ();
+
+    auto result = ReadMaterial::validateInputs(params);
+    TS_ASSERT_EQUALS(result.size(), 1)
+    TS_ASSERT_EQUALS(result["IncoherentXSection"],
+                     "The cross section must be specified when "
+                     "no ChemicalFormula or AtomicNumber is "
+                     "given.")
+  }
+  void testFailureValidateInputsNoAttenuationXSection() {
+    const ReadMaterial::MaterialParameters params = []() -> auto {
+      ReadMaterial::MaterialParameters setMaterial;
+      setMaterial.atomicNumber = 0;
+      setMaterial.massNumber = 0;
+      setMaterial.coherentXSection = 1.;
+      setMaterial.incoherentXSection = 1.;
+      setMaterial.scatteringXSection = 1.;
+      setMaterial.sampleNumberDensity = 1.;
+      return setMaterial;
+    }
+    ();
+
+    auto result = ReadMaterial::validateInputs(params);
+    TS_ASSERT_EQUALS(result.size(), 1)
+    TS_ASSERT_EQUALS(result["AttenuationXSection"],
+                     "The cross section must be specified when "
+                     "no ChemicalFormula or AtomicNumber is "
+                     "given.")
+  }
+  void testFailureValidateInputsNoScatteringXSection() {
+    const ReadMaterial::MaterialParameters params = []() -> auto {
+      ReadMaterial::MaterialParameters setMaterial;
+      setMaterial.atomicNumber = 0;
+      setMaterial.massNumber = 0;
+      setMaterial.coherentXSection = 1.;
+      setMaterial.incoherentXSection = 1.;
+      setMaterial.attenuationXSection = 1.;
+      setMaterial.sampleNumberDensity = 1.;
+      return setMaterial;
+    }
+    ();
+
+    auto result = ReadMaterial::validateInputs(params);
+    TS_ASSERT_EQUALS(result.size(), 1)
+    TS_ASSERT_EQUALS(result["ScatteringXSection"],
+                     "The cross section must be specified when "
+                     "no ChemicalFormula or AtomicNumber is "
+                     "given.")
+  }
+  void testFailureValidateInputsNoSampleNumberDensity() {
+    const ReadMaterial::MaterialParameters params = []() -> auto {
+      ReadMaterial::MaterialParameters setMaterial;
+      setMaterial.atomicNumber = 0;
+      setMaterial.massNumber = 0;
+      setMaterial.coherentXSection = 1.;
+      setMaterial.incoherentXSection = 1.;
+      setMaterial.attenuationXSection = 1.;
+      setMaterial.scatteringXSection = 1.;
+      return setMaterial;
+    }
+    ();
+
+    auto result = ReadMaterial::validateInputs(params);
+    TS_ASSERT_EQUALS(result.size(), 1)
+    TS_ASSERT_EQUALS(
+        result["SampleNumberDensity"],
+        "The number density must be specified with a use-defined material.")
   }
 
   void testSuccessfullValidateInputsSampleNumber() {
@@ -138,7 +225,7 @@ public:
     auto result = ReadMaterial::validateInputs(params);
 
     TS_ASSERT_EQUALS(result["ZParameter"],
-                     "Can not give ZParameter with SampleNumberDensity set")
+                     "Cannot give ZParameter with SampleNumberDensity set")
   }
 
   void testFailureValidateInputsZParamWithSampleMass() {
@@ -156,7 +243,7 @@ public:
     auto result = ReadMaterial::validateInputs(params);
 
     TS_ASSERT_EQUALS(result["SampleMassDensity"],
-                     "Can not give SampleMassDensity with ZParameter set")
+                     "Cannot give SampleMassDensity with ZParameter set")
   }
 
   void testFailureValidateInputsZParamWithoutUnitCell() {
@@ -190,7 +277,7 @@ public:
 
     TS_ASSERT_EQUALS(
         result["SampleMassDensity"],
-        "Can not give SampleMassDensity with SampleNumberDensity set")
+        "Cannot give SampleMassDensity with SampleNumberDensity set")
   }
 
   void testMaterialIsCorrect() {
diff --git a/Framework/DataHandling/test/SNSDataArchiveTest.h b/Framework/DataHandling/test/SNSDataArchiveTest.h
deleted file mode 100644
index 3d8d7d588b44351e168cbd82760b51852ebcf6e4..0000000000000000000000000000000000000000
--- a/Framework/DataHandling/test/SNSDataArchiveTest.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-//     NScD Oak Ridge National Laboratory, European Spallation Source
-//     & Institut Laue - Langevin
-// SPDX - License - Identifier: GPL - 3.0 +
-#ifndef SNSDATAARCHIVETEST_H_
-#define SNSDATAARCHIVETEST_H_
-
-#include <cxxtest/TestSuite.h>
-
-#include "MantidAPI/ArchiveSearchFactory.h"
-#include "MantidDataHandling/SNSDataArchive.h"
-
-using namespace Mantid::DataHandling;
-using namespace Mantid::API;
-
-class SNSDataArchiveTest : public CxxTest::TestSuite {
-public:
-  void xtestSearch() {
-    SNSDataArchive arch;
-
-    // PG3 Test case
-    std::set<std::string> filename;
-    filename.insert("PG3_7390");
-    std::vector<std::string> extension =
-        std::vector<std::string>(1, "_event.nxs");
-    std::string path = arch.getArchivePath(filename, extension);
-    TS_ASSERT_EQUALS(path,
-                     "/SNS/PG3/IPTS-2767/0/7390/NeXus/PG3_7390_histo.nxs");
-
-    // BSS Test case
-    filename.clear();
-    filename.insert("BSS_18339");
-    path = arch.getArchivePath(filename, extension);
-    TS_ASSERT_EQUALS(path,
-                     "/SNS/BSS/IPTS-6817/0/18339/NeXus/BSS_18339_event.nxs");
-
-    // HYSA Test case
-    filename.clear();
-    filename.insert("HYSA_2411");
-    extension = std::vector<std::string>(1, ".nxs.h5");
-    path = arch.getArchivePath(filename, extension);
-    TS_ASSERT_EQUALS(path, "/SNS/HYSA/IPTS-8004/nexus/HYSA_2411.nxs.h5");
-
-    // Test a non-existent file
-    filename.clear();
-    filename.insert("mybeamline_666");
-    extension = std::vector<std::string>(1, ".nxs");
-    path = arch.getArchivePath(filename, extension);
-    TS_ASSERT(path.empty());
-  }
-
-  void testFactory() {
-    boost::shared_ptr<IArchiveSearch> arch =
-        ArchiveSearchFactory::Instance().create("SNSDataSearch");
-    TS_ASSERT(arch);
-  }
-};
-
-#endif /*SNSDATAARCHIVETEST_H_*/
diff --git a/Framework/DataHandling/test/SaveANSTOAsciiTest.h b/Framework/DataHandling/test/SaveANSTOAsciiTest.h
index 475ee864a59ef2b0cea52a59a8fcb71785d2c098..5c24ba2940938f1f561be094ecdcb80f6d57baf8 100644
--- a/Framework/DataHandling/test/SaveANSTOAsciiTest.h
+++ b/Framework/DataHandling/test/SaveANSTOAsciiTest.h
@@ -8,16 +8,19 @@
 #define SAVEANSTOASCIITEST_H_
 
 #include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/AnalysisDataService.h"
 #include "MantidDataHandling/SaveANSTOAscii.h"
 #include "MantidDataObjects/Workspace2D.h"
-#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include <Poco/File.h>
 #include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
 #include <cxxtest/TestSuite.h>
 #include <fstream>
 
 using namespace Mantid::API;
 using namespace Mantid::DataHandling;
+using namespace Mantid::HistogramData;
 using namespace Mantid::DataObjects;
 
 class SaveANSTOAsciiTest : public CxxTest::TestSuite {
@@ -30,16 +33,12 @@ public:
     m_filename = "SaveANSTOAsciiTestFile.txt";
     m_name = "SaveANSTOAsciiWS";
     for (int i = 1; i < 11; ++i) {
-      // X, Y and E get [1,2,3,4,5,6,7,8,9,10]
-      // 0 gets [0,0,0,0,0,0,0,0,0,0] and is used to make sure there is no
-      // problem with divide by zero
-      m_dataX.push_back(i);
-      m_dataY.push_back(i);
-      m_dataE.push_back(i);
-      m_data0.push_back(0);
+      m_dataX.emplace_back(i);
+      m_dataY.emplace_back(i);
+      m_dataE.emplace_back(i);
     }
+    m_dataX.emplace_back(11);
   }
-  ~SaveANSTOAsciiTest() override {}
 
   void testExec() {
     // create a new workspace and then delete it later on
@@ -65,8 +64,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4);
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(0)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
     TS_ASSERT_EQUALS(columns.at(3), "0.000000000000000e+00");
     in.close();
 
@@ -95,9 +94,9 @@ public:
     boost::split(columns, fullline, boost::is_any_of("\t"),
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(0)), 0, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(0)), 0., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
     TS_ASSERT_EQUALS(columns.at(3), "0.000000000000000e+00");
     in.close();
 
@@ -127,8 +126,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4);
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(0)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 0, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 0., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
     TS_ASSERT_EQUALS(columns.at(3), "0.000000000000000e+00");
     in.close();
 
@@ -158,8 +157,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4);
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(0)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 0, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 0., 0.01);
     TS_ASSERT_EQUALS(columns.at(3), "0.000000000000000e+00");
     in.close();
 
@@ -190,8 +189,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4);
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(0)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
     TS_ASSERT_EQUALS(columns.at(3), "0.000000000000000e+00");
     in.close();
 
@@ -214,34 +213,22 @@ public:
 
 private:
   void createWS(bool zeroX = false, bool zeroY = false, bool zeroE = false) {
-    MatrixWorkspace_sptr ws = WorkspaceCreationHelper::create2DWorkspace(1, 10);
-    AnalysisDataService::Instance().addOrReplace(m_name, ws);
     // Check if any of X, Y or E should be zeroed to check for divide by zero or
     // similiar
-    if (zeroX) {
-      ws->dataX(0) = m_data0;
-    } else {
-      ws->dataX(0) = m_dataX;
-    }
-
-    if (zeroY) {
-      ws->dataY(0) = m_data0;
-    } else {
-      ws->dataY(0) = m_dataY;
-    }
-
-    if (zeroE) {
-      ws->dataE(0) = m_data0;
-    } else {
-      ws->dataE(0) = m_dataE;
-    }
+    BinEdges edges = zeroX ? BinEdges(11, 0.) : BinEdges(m_dataX);
+    Counts counts = zeroY ? Counts(10, 0.) : Counts(m_dataY);
+    CountStandardDeviations stddev = zeroE ? CountStandardDeviations(10, 0.)
+                                           : CountStandardDeviations(m_dataE);
+    MatrixWorkspace_sptr ws =
+        create<Workspace2D>(1, Histogram(edges, counts, stddev));
+    AnalysisDataService::Instance().addOrReplace(m_name, ws);
   }
   void cleanupafterwards() {
     Poco::File(m_long_filename).remove();
     AnalysisDataService::Instance().remove(m_name);
   }
   std::string m_filename, m_name, m_long_filename;
-  std::vector<double> m_dataX, m_dataY, m_dataE, m_data0;
+  std::vector<double> m_dataX, m_dataY, m_dataE;
 };
 
 #endif /*SAVEANSTOTEST_H_*/
diff --git a/Framework/DataHandling/test/SaveReflCustomAsciiTest.h b/Framework/DataHandling/test/SaveReflCustomAsciiTest.h
index adaa7531dd01ecf2d0eddbded48b3bb430f984e6..06cbb34c3c773e3a9da3b80abb65abf18d9bd7d0 100644
--- a/Framework/DataHandling/test/SaveReflCustomAsciiTest.h
+++ b/Framework/DataHandling/test/SaveReflCustomAsciiTest.h
@@ -8,16 +8,20 @@
 #define SAVEREFLCUSTOMASCIITEST_H_
 
 #include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/AnalysisDataService.h"
 #include "MantidDataHandling/SaveReflCustomAscii.h"
 #include "MantidDataObjects/Workspace2D.h"
-#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Points.h"
 #include <Poco/File.h>
 #include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
 #include <cxxtest/TestSuite.h>
 #include <fstream>
 
 using namespace Mantid::API;
 using namespace Mantid::DataHandling;
+using namespace Mantid::HistogramData;
 using namespace Mantid::DataObjects;
 
 class SaveReflCustomAsciiTest : public CxxTest::TestSuite {
@@ -32,16 +36,12 @@ public:
     m_filename = "SaveReflCustomAsciiFile.txt";
     m_name = "SaveReflCustomAsciiWS";
     for (int i = 1; i < 11; ++i) {
-      // X, Y and E get [1,2,3,4,5,6,7,8,9,10]
-      // 0 gets [0,0,0,0,0,0,0,0,0,0] and is used to make sure there is no
-      // problem with divide by zero
-      m_dataX.push_back(i);
-      m_dataY.push_back(i);
-      m_dataE.push_back(i);
-      m_data0.push_back(0);
+      m_dataX.emplace_back(i);
+      m_dataY.emplace_back(i);
+      m_dataE.emplace_back(i);
     }
+    m_dataX.emplace_back(11.);
   }
-  ~SaveReflCustomAsciiTest() override {}
 
   void testExec() {
     // create a new workspace and then delete it later on
@@ -62,6 +62,7 @@ public:
     std::ifstream in(m_long_filename.c_str());
     std::string fullline;
     headingsTests(in, fullline);
+
     getline(in, fullline);
 
     std::vector<std::string> columns;
@@ -71,8 +72,8 @@ public:
     // the first is black due to the leading separator
     TS_ASSERT(columns.at(0) == "");
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 2.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 2, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 2, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 2., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 2., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -103,9 +104,9 @@ public:
     TS_ASSERT_EQUALS(columns.size(), 4);
     // the first is black due to the leading separator
     TS_ASSERT(columns.at(0) == "");
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 0, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 2, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 2, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 0., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 2., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 2., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -137,8 +138,8 @@ public:
     // the first is black due to the leading separator
     TS_ASSERT(columns.at(0) == "");
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 2.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 0, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 2, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 0., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 2., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -170,15 +171,15 @@ public:
     // the first is black due to the leading separator
     TS_ASSERT(columns.at(0) == "");
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 2.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 2, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 0, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 2., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 0., 0.01);
     in.close();
 
     cleanupafterwards();
   }
   void testParameters() {
     // create a new workspace and then delete it later on
-    createWS(false, false, false, true);
+    createWS(false, false, false);
 
     Mantid::API::IAlgorithm_sptr alg =
         Mantid::API::AlgorithmManager::Instance().create("SaveReflCustomAscii");
@@ -206,8 +207,8 @@ public:
     // the first is black due to the leading separator
     TS_ASSERT(columns.at(0) == "");
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -236,39 +237,22 @@ private:
     } else {
     };
   }
-  void createWS(bool zeroX = false, bool zeroY = false, bool zeroE = false,
-                bool createLogs = false) {
-    createLogs = false;
-    MatrixWorkspace_sptr ws = WorkspaceCreationHelper::create2DWorkspace(1, 10);
-    AnalysisDataService::Instance().addOrReplace(m_name, ws);
+  void createWS(bool zeroX = false, bool zeroY = false, bool zeroE = false) {
     // Check if any of X, Y or E should be zeroed to check for divide by zero or
     // similiar
-    if (zeroX) {
-      ws->dataX(0) = m_data0;
-    } else {
-      ws->dataX(0) = m_dataX;
-    }
-
-    if (zeroY) {
-      ws->dataY(0) = m_data0;
-    } else {
-      ws->dataY(0) = m_dataY;
-    }
-
-    if (zeroE) {
-      ws->dataE(0) = m_data0;
-    } else {
-      ws->dataE(0) = m_dataE;
-    }
-    if (createLogs) {
-    } else {
-    }
+    BinEdges edges = zeroX ? BinEdges(11, 0.) : BinEdges(m_dataX);
+    Counts counts = zeroY ? Counts(10, 0.) : Counts(m_dataY);
+    CountStandardDeviations stddev = zeroE ? CountStandardDeviations(10, 0.)
+                                           : CountStandardDeviations(m_dataE);
+    MatrixWorkspace_sptr ws =
+        create<Workspace2D>(1, Histogram(edges, counts, stddev));
+    AnalysisDataService::Instance().addOrReplace(m_name, ws);
   }
   void cleanupafterwards() {
     Poco::File(m_long_filename).remove();
     AnalysisDataService::Instance().remove(m_name);
   }
   std::string m_filename, m_name, m_long_filename;
-  std::vector<double> m_dataX, m_dataY, m_dataE, m_data0;
+  std::vector<double> m_dataX, m_dataY, m_dataE;
 };
 #endif /*SAVEREFLCUSTOMASCIITEST_H_*/
diff --git a/Framework/DataHandling/test/SaveReflThreeColumnAsciiTest.h b/Framework/DataHandling/test/SaveReflThreeColumnAsciiTest.h
index f1967d6679ff800182369392419ca32a6dc8135e..65f84327359f0e744068111ff460c9fbe6a3a09f 100644
--- a/Framework/DataHandling/test/SaveReflThreeColumnAsciiTest.h
+++ b/Framework/DataHandling/test/SaveReflThreeColumnAsciiTest.h
@@ -8,16 +8,19 @@
 #define SAVEREFLTHREECOLUMNASCIITEST_H_
 
 #include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/AnalysisDataService.h"
 #include "MantidDataHandling/SaveReflThreeColumnAscii.h"
 #include "MantidDataObjects/Workspace2D.h"
-#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include <Poco/File.h>
 #include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
 #include <cxxtest/TestSuite.h>
 #include <fstream>
 
 using namespace Mantid::API;
 using namespace Mantid::DataHandling;
+using namespace Mantid::HistogramData;
 using namespace Mantid::DataObjects;
 
 class SaveReflThreeColumnAsciiTest : public CxxTest::TestSuite {
@@ -34,14 +37,11 @@ public:
     m_filename = "SaveReflThreeColumnAsciiTestFile.txt";
     m_name = "SaveReflThreeColumnAsciiWS";
     for (int i = 1; i < 11; ++i) {
-      // X, Y and E get [1,2,3,4,5,6,7,8,9,10]
-      // 0 gets [0,0,0,0,0,0,0,0,0,0] and is used to make sure there is no
-      // problem with divide by zero
-      m_dataX.push_back(i);
-      m_dataY.push_back(i);
-      m_dataE.push_back(i);
-      m_data0.push_back(0);
+      m_dataX.emplace_back(i);
+      m_dataY.emplace_back(i);
+      m_dataE.emplace_back(i);
     }
+    m_dataX.emplace_back(11);
   }
   ~SaveReflThreeColumnAsciiTest() override {}
 
@@ -70,8 +70,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4); // first blank
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -100,8 +100,8 @@ public:
     boost::split(columns, fullline, boost::is_any_of("\t"),
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4); // first blank
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 0, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 0., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
     // TS_ASSERT((columns.at(3) == "nan") || (columns.at(3) == "inf"));
     in.close();
 
@@ -132,8 +132,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4); // first blank
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 0, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 0., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -163,8 +163,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4); // first blank
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 0, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 0., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -195,8 +195,8 @@ public:
                  boost::token_compress_on);
     TS_ASSERT_EQUALS(columns.size(), 4); // first blank
     TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(1)), 1.5, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1, 0.01);
-    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1, 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(2)), 1., 0.01);
+    TS_ASSERT_DELTA(boost::lexical_cast<double>(columns.at(3)), 1., 0.01);
     in.close();
 
     cleanupafterwards();
@@ -219,34 +219,22 @@ public:
 
 private:
   void createWS(bool zeroX = false, bool zeroY = false, bool zeroE = false) {
-    MatrixWorkspace_sptr ws = WorkspaceCreationHelper::create2DWorkspace(1, 10);
-    AnalysisDataService::Instance().addOrReplace(m_name, ws);
     // Check if any of X, Y or E should be zeroed to check for divide by zero or
     // similiar
-    if (zeroX) {
-      ws->dataX(0) = m_data0;
-    } else {
-      ws->dataX(0) = m_dataX;
-    }
-
-    if (zeroY) {
-      ws->dataY(0) = m_data0;
-    } else {
-      ws->dataY(0) = m_dataY;
-    }
-
-    if (zeroE) {
-      ws->dataE(0) = m_data0;
-    } else {
-      ws->dataE(0) = m_dataE;
-    }
+    BinEdges edges = zeroX ? BinEdges(11, 0.) : BinEdges(m_dataX);
+    Counts counts = zeroY ? Counts(10, 0.) : Counts(m_dataY);
+    CountStandardDeviations stddev = zeroE ? CountStandardDeviations(10, 0.)
+                                           : CountStandardDeviations(m_dataE);
+    MatrixWorkspace_sptr ws =
+        create<Workspace2D>(1, Histogram(edges, counts, stddev));
+    AnalysisDataService::Instance().addOrReplace(m_name, ws);
   }
   void cleanupafterwards() {
     Poco::File(m_long_filename).remove();
     AnalysisDataService::Instance().remove(m_name);
   }
   std::string m_filename, m_name, m_long_filename;
-  std::vector<double> m_dataX, m_dataY, m_dataE, m_data0;
+  std::vector<double> m_dataX, m_dataY, m_dataE;
 };
 
 #endif /*SAVEREFLTHREECOLUMNASCIITEST_H_*/
diff --git a/Framework/DataHandling/test/SaveSESANSTest.h b/Framework/DataHandling/test/SaveSESANSTest.h
index baa5a5d35c993b984d9ecfb22fe8d929edd49be3..55cd9c239bb0733a9629d122ec024f5adc9cafed 100644
--- a/Framework/DataHandling/test/SaveSESANSTest.h
+++ b/Framework/DataHandling/test/SaveSESANSTest.h
@@ -96,9 +96,9 @@ public:
 
     // Check (a small sample of) the values we wrote are correct
     TS_ASSERT_EQUALS(static_cast<int>(data->getNumberHistograms()), 1);
-    auto xValues = data->x(0);
-    auto yValues = data->y(0);
-    auto eValues = data->e(0);
+    const auto &xValues = data->x(0);
+    const auto &yValues = data->y(0);
+    const auto &eValues = data->e(0);
 
     TS_ASSERT_EQUALS(static_cast<int>(xValues.size()), 10);
     TS_ASSERT_EQUALS(static_cast<int>(yValues.size()), 10);
diff --git a/Framework/DataObjects/CMakeLists.txt b/Framework/DataObjects/CMakeLists.txt
index 473e431a63ef38275c811c3c33180ca2cf021851..079752df6d890658861ff05c262f29802642105d 100644
--- a/Framework/DataObjects/CMakeLists.txt
+++ b/Framework/DataObjects/CMakeLists.txt
@@ -236,4 +236,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS DataObjects ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS DataObjects INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/DataObjects/inc/MantidDataObjects/EventList.h b/Framework/DataObjects/inc/MantidDataObjects/EventList.h
index 22e967eaa2547d3e80c3523cc10a13fe1aa1713d..b987dcf74cbfa19781ba3d73f1f9a9e31f428a1b 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/EventList.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/EventList.h
@@ -244,6 +244,7 @@ public:
   void addPulsetime(const double seconds) override;
 
   void maskTof(const double tofMin, const double tofMax) override;
+  void maskCondition(const std::vector<bool> &mask) override;
 
   void getTofs(std::vector<double> &tofs) const override;
   double getTofMin() const override;
@@ -456,6 +457,10 @@ private:
   static std::size_t maskTofHelper(std::vector<T> &events, const double tofMin,
                                    const double tofMax);
   template <class T>
+  static std::size_t maskConditionHelper(std::vector<T> &events,
+                                         const std::vector<bool> &mask);
+
+  template <class T>
   static void getTofsHelper(const std::vector<T> &events,
                             std::vector<double> &tofs);
   template <class T>
diff --git a/Framework/DataObjects/src/EventList.cpp b/Framework/DataObjects/src/EventList.cpp
index 4cef5659ab23fc3ba8aacda5f0d6379534b8362d..1a11e7f60fd3ea938ce813dd3a44af86e5b400ae 100644
--- a/Framework/DataObjects/src/EventList.cpp
+++ b/Framework/DataObjects/src/EventList.cpp
@@ -2695,6 +2695,75 @@ void EventList::maskTof(const double tofMin, const double tofMax) {
     this->clear(false);
 }
 
+// --------------------------------------------------------------------------
+/** Mask out events by the condition vector.
+ * Events are removed from the list.
+ * @param events :: reference to a vector of events to change.
+ * @param mask :: condition vector
+ * @returns The number of events deleted.
+ */
+template <class T>
+std::size_t EventList::maskConditionHelper(std::vector<T> &events,
+                                           const std::vector<bool> &mask) {
+
+  // runs through the two synchronized vectors and delete elements
+  // for condition false
+  auto itm = std::find(mask.begin(), mask.end(), false);
+  auto first = events.begin() + (itm - mask.begin());
+
+  if (itm != mask.end()) {
+    for (auto ite = first; ++ite != events.end() && ++itm != mask.end();) {
+      if (*itm != false) {
+        *first++ = std::move(*ite);
+      }
+    }
+  }
+
+  auto n = events.end() - first;
+  if (n != 0)
+    events.erase(first, events.end());
+
+  return n;
+}
+
+// --------------------------------------------------------------------------
+/**
+ * Mask out events by the condition vector.
+ * Events are removed from the list.
+ * @param mask :: condition vector
+ */
+void EventList::maskCondition(const std::vector<bool> &mask) {
+
+  // mask size must match the number of events
+  if (this->getNumberEvents() != mask.size())
+    throw std::runtime_error("EventList::maskTof: tofMax must be > tofMin");
+
+  // don't do anything with an emply list
+  if (this->getNumberEvents() == 0)
+    return;
+
+  // Convert the list
+  size_t numOrig = 0;
+  size_t numDel = 0;
+  switch (eventType) {
+  case TOF:
+    numOrig = this->events.size();
+    numDel = this->maskConditionHelper(this->events, mask);
+    break;
+  case WEIGHTED:
+    numOrig = this->weightedEvents.size();
+    numDel = this->maskConditionHelper(this->weightedEvents, mask);
+    break;
+  case WEIGHTED_NOTIME:
+    numOrig = this->weightedEventsNoTime.size();
+    numDel = this->maskConditionHelper(this->weightedEventsNoTime, mask);
+    break;
+  }
+
+  if (numDel >= numOrig)
+    this->clear(false);
+}
+
 // --------------------------------------------------------------------------
 /** Get the m_tof member of all events in a list
  *
diff --git a/Framework/DataObjects/src/FractionalRebinning.cpp b/Framework/DataObjects/src/FractionalRebinning.cpp
index 2dfe63eeb30dff937e11a5cef53e009069368fb9..8869b8aa370a4288467541599117affac860985c 100644
--- a/Framework/DataObjects/src/FractionalRebinning.cpp
+++ b/Framework/DataObjects/src/FractionalRebinning.cpp
@@ -552,6 +552,8 @@ void rebinToOutput(const Quadrilateral &inputQ,
   for (size_t y = qstart; y < qend; ++y) {
     const double vlo = verticalAxis[y];
     const double vhi = verticalAxis[y + 1];
+    auto &outY = outputWS.mutableY(y);
+    auto &outE = outputWS.mutableE(y);
     for (size_t xi = x_start; xi < x_end; ++xi) {
       const V2D ll(X[xi], vlo);
       const V2D lr(X[xi + 1], vlo);
@@ -565,7 +567,11 @@ void rebinToOutput(const Quadrilateral &inputQ,
       }
       intersectOverlap.clear();
       if (intersection(outputQ, inputQ, intersectOverlap)) {
-        const double weight = intersectOverlap.area() / inputQ.area();
+        const double overlapArea = intersectOverlap.area();
+        if (overlapArea == 0.) {
+          continue;
+        }
+        const double weight = overlapArea / inputQ.area();
         yValue *= weight;
         double eValue = inE[j];
         if (inputWS->isDistribution()) {
@@ -576,8 +582,8 @@ void rebinToOutput(const Quadrilateral &inputQ,
         }
         eValue = eValue * eValue * weight;
         PARALLEL_CRITICAL(overlap_sum) {
-          outputWS.mutableY(y)[xi] += yValue;
-          outputWS.mutableE(y)[xi] += eValue;
+          outY[xi] += yValue;
+          outE[xi] += eValue;
         }
       }
     }
@@ -667,6 +673,9 @@ void rebinToFractionalOutput(const Quadrilateral &inputQ,
 
   const double variance = error * error;
   for (const auto &ai : areaInfos) {
+    if (ai.weight == 0.) {
+      continue;
+    }
     const double weight = ai.weight / inputQArea;
     PARALLEL_CRITICAL(overlap) {
       outputWS.mutableY(ai.wsIndex)[ai.binIndex] += signal * weight;
diff --git a/Framework/DataObjects/test/EventListTest.h b/Framework/DataObjects/test/EventListTest.h
index d29c5ac56fd25c3840b04d0f3d2fa771159ee64a..4b99079d07dfad0b5a3be51c95101bb147c6bc3b 100644
--- a/Framework/DataObjects/test/EventListTest.h
+++ b/Framework/DataObjects/test/EventListTest.h
@@ -1314,6 +1314,41 @@ public:
     }
   }
 
+  //-----------------------------------------------------------------------------------------------
+  void test_maskCondition_allTypes() {
+    // Go through each possible EventType as the input
+    for (int this_type = 0; this_type < 3; this_type++) {
+      this->fake_uniform_data();
+      el.switchTo(static_cast<EventType>(this_type));
+
+      // tof steps of 5 microseconds, starting at 100 ns, up to 20 msec
+      // How many events did we make?
+      TS_ASSERT_EQUALS(el.getNumberEvents(), 2 * MAX_TOF / BIN_DELTA);
+
+      // Mask out 5-10 milliseconds
+      auto nlen = el.getNumberEvents();
+      std::vector<bool> mask(nlen, true);
+
+      // first check no removal
+      el.maskCondition(mask);
+      TS_ASSERT_EQUALS(el.getNumberEvents(), 2 * MAX_TOF / BIN_DELTA);
+
+      double min = MAX_TOF * 0.25;
+      double max = MAX_TOF * 0.5;
+      for (size_t i = 0; i < nlen; i++) {
+        if ((el.getEvent(i).tof() >= min) && (el.getEvent(i).tof() <= max)) {
+          mask[i] = false;
+        }
+      }
+      el.maskCondition(mask);
+      for (std::size_t i = 0; i < el.getNumberEvents(); i++) {
+        // No tofs in that range
+        TS_ASSERT((el.getEvent(i).tof() < min) || (el.getEvent(i).tof() > max));
+      }
+      TS_ASSERT_EQUALS(el.getNumberEvents(), 0.75 * 2 * MAX_TOF / BIN_DELTA);
+    }
+  }
+
   //-----------------------------------------------------------------------------------------------
   void test_getTofs_and_setTofs() {
     // Go through each possible EventType as the input
diff --git a/Framework/DataObjects/test/RefAxisTest.h b/Framework/DataObjects/test/RefAxisTest.h
index aeb9e936b6c69e3bf723859b55b99afc1db357f9..b2e6bcbf9c35a29c5f179b1d3fd0c1bd30e406d5 100644
--- a/Framework/DataObjects/test/RefAxisTest.h
+++ b/Framework/DataObjects/test/RefAxisTest.h
@@ -31,95 +31,85 @@ public:
 
   RefAxisTest() {
     // Set up two small workspaces for these tests
-    space = new Mantid::DataObjects::Workspace2D;
-    space->initialize(5, 25, 25);
-    space2 = new Mantid::DataObjects::Workspace2D;
-    space2->initialize(1, 5, 5);
+    m_space.reset(new Mantid::DataObjects::Workspace2D);
+    m_space->initialize(5, 5, 5);
+    m_space2.reset(new Mantid::DataObjects::Workspace2D);
+    m_space2->initialize(1, 5, 5);
 
     // Fill them
-    double *a = new double[25];
-    double *b = new double[25];
-    for (int i = 0; i < 25; ++i) {
-      a[i] = i + 0.1;
+    Mantid::MantidVec a(25);
+    for (size_t i = 0; i < a.size(); ++i) {
+      a[i] = static_cast<double>(i) + 0.1;
     }
     for (int j = 0; j < 5; ++j) {
-      space->dataX(j) = Mantid::MantidVec(a + (5 * j), a + (5 * j) + 5);
+      m_space->mutableX(j) =
+          Mantid::MantidVec(a.cbegin() + (5 * j), a.cbegin() + (5 * j) + 5);
     }
-    delete[] a;
-    delete[] b;
 
     // Create the axis that the tests will be performed on
-    refAxis = new RefAxis(space);
-    refAxis->title() = "test axis";
-    refAxis->unit() = UnitFactory::Instance().create("TOF");
-  }
-
-  ~RefAxisTest() override {
-    delete refAxis;
-    delete space;
-    delete space2;
+    m_refAxis.reset(new RefAxis(m_space.get()));
+    m_refAxis->title() = "test axis";
+    m_refAxis->unit() = UnitFactory::Instance().create("TOF");
   }
 
   void testConstructor() {
-    TS_ASSERT_EQUALS(refAxis->title(), "test axis")
-    TS_ASSERT(refAxis->isNumeric())
-    TS_ASSERT(!refAxis->isSpectra())
-    TS_ASSERT_EQUALS(refAxis->unit()->unitID(), "TOF")
-    TS_ASSERT_THROWS(refAxis->spectraNo(0), std::domain_error)
+    TS_ASSERT_EQUALS(m_refAxis->title(), "test axis")
+    TS_ASSERT(m_refAxis->isNumeric())
+    TS_ASSERT(!m_refAxis->isSpectra())
+    TS_ASSERT_EQUALS(m_refAxis->unit()->unitID(), "TOF")
+    TS_ASSERT_THROWS(m_refAxis->spectraNo(0), std::domain_error)
   }
 
   void testClone() {
-    Axis *clonedAxis = refAxis->clone(space2);
-    TS_ASSERT_DIFFERS(clonedAxis, refAxis)
-    TS_ASSERT(dynamic_cast<RefAxis *>(clonedAxis))
+    std::unique_ptr<Axis> clonedAxis(m_refAxis->clone(m_space2.get()));
+    TS_ASSERT_DIFFERS(clonedAxis.get(), m_refAxis.get())
+    TS_ASSERT(clonedAxis)
     TS_ASSERT_EQUALS(clonedAxis->title(), "test axis")
     TS_ASSERT_EQUALS(clonedAxis->unit()->unitID(), "TOF")
     TS_ASSERT(clonedAxis->isNumeric())
     TS_ASSERT_EQUALS((*clonedAxis)(0, 0), 1.0)
     TS_ASSERT_THROWS((*clonedAxis)(0, 1), std::range_error)
-    delete clonedAxis;
   }
 
   void testCloneDifferentLength() {
-    Axis *newRefAxis = refAxis->clone(5, space2);
-    TS_ASSERT_DIFFERS(newRefAxis, refAxis);
+    std::unique_ptr<Axis> newRefAxis(m_refAxis->clone(5, m_space2.get()));
+    TS_ASSERT_DIFFERS(newRefAxis.get(), m_refAxis.get());
     TS_ASSERT(newRefAxis->isNumeric());
     TS_ASSERT_EQUALS(newRefAxis->title(), "test axis");
     TS_ASSERT_EQUALS(newRefAxis->unit()->unitID(), "TOF");
     TS_ASSERT_EQUALS(newRefAxis->length(), 5);
-    space2->dataX(0)[1] = 9.9;
+    m_space2->dataX(0)[1] = 9.9;
     TS_ASSERT_EQUALS((*newRefAxis)(1), 9.9);
-    delete newRefAxis;
   }
 
   void testOperatorBrackets() {
-    TS_ASSERT_EQUALS((*refAxis)(4, 4), 24.1)
-    TS_ASSERT_EQUALS((*refAxis)(0, 2), 10.1)
-    TS_ASSERT_EQUALS((*refAxis)(2, 0), 2.1)
-
-    TS_ASSERT_THROWS((*refAxis)(-1, 0), Exception::IndexError)
-    TS_ASSERT_THROWS((*refAxis)(5, 0), Exception::IndexError)
-    TS_ASSERT_THROWS((*refAxis)(0, -1), std::range_error)
-    TS_ASSERT_THROWS((*refAxis)(0, 5), std::range_error)
+    TS_ASSERT_EQUALS((*m_refAxis)(4, 4), 24.1)
+    TS_ASSERT_EQUALS((*m_refAxis)(0, 2), 10.1)
+    TS_ASSERT_EQUALS((*m_refAxis)(2, 0), 2.1)
+
+    TS_ASSERT_THROWS((*m_refAxis)(-1, 0), Exception::IndexError)
+    TS_ASSERT_THROWS((*m_refAxis)(5, 0), Exception::IndexError)
+    TS_ASSERT_THROWS((*m_refAxis)(0, -1), std::range_error)
+    TS_ASSERT_THROWS((*m_refAxis)(0, 5), std::range_error)
   }
 
   void testSetValue() {
-    TS_ASSERT_THROWS(refAxis->setValue(0, 9.9), std::domain_error)
+    TS_ASSERT_THROWS(m_refAxis->setValue(0, 9.9), std::domain_error)
   }
 
   void testGetMin() {
-    std::unique_ptr<Axis> newRefAxis(refAxis->clone(5, space2));
+    std::unique_ptr<Axis> newRefAxis(m_refAxis->clone(5, m_space2.get()));
     TS_ASSERT_THROWS(newRefAxis->getMin(), std::runtime_error)
   }
 
   void testGetMax() {
-    std::unique_ptr<Axis> newRefAxis(refAxis->clone(5, space2));
+    std::unique_ptr<Axis> newRefAxis(m_refAxis->clone(5, m_space2.get()));
     TS_ASSERT_THROWS(newRefAxis->getMax(), std::runtime_error)
   }
 
 private:
-  MatrixWorkspace *space, *space2;
-  RefAxis *refAxis;
+  std::unique_ptr<MatrixWorkspace> m_space, m_space2;
+  std::unique_ptr<RefAxis> m_refAxis;
 };
 
 #endif /*REFAXISTEST_H_*/
diff --git a/Framework/DataObjects/test/WorkspaceCreationTest.h b/Framework/DataObjects/test/WorkspaceCreationTest.h
index 82eee10415802f66fd473500d74251905831c32f..a7fe8b94993a82c5df0908200f969f18960ede5f 100644
--- a/Framework/DataObjects/test/WorkspaceCreationTest.h
+++ b/Framework/DataObjects/test/WorkspaceCreationTest.h
@@ -9,6 +9,7 @@
 
 #include <cxxtest/TestSuite.h>
 
+#include "MantidAPI/BinEdgeAxis.h"
 #include "MantidAPI/Run.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/SpecialWorkspace2D.h"
@@ -419,6 +420,30 @@ public:
     check_zeroed_data(*ws);
   }
 
+  void test_create_parent_numeric_vertical_axis() {
+    constexpr size_t parentNhist{3};
+    const auto parent = create<Workspace2D>(parentNhist, Histogram(Points{1}));
+    NumericAxis *parentAxis = new NumericAxis({-1.5, -0.5, 2.3});
+    parent->replaceAxis(1, parentAxis);
+    constexpr size_t nhist{2};
+    const auto ws = create<Workspace2D>(*parent, nhist, parent->histogram(0));
+    auto axis = ws->getAxis(1);
+    TS_ASSERT_DIFFERS(dynamic_cast<NumericAxis *>(axis), nullptr)
+    TS_ASSERT_EQUALS(axis->length(), nhist);
+  }
+
+  void test_create_parent_bin_edge_vertical_axis() {
+    constexpr size_t parentNhist{3};
+    const auto parent = create<Workspace2D>(parentNhist, Histogram(Points{1}));
+    BinEdgeAxis *parentAxis = new BinEdgeAxis({-1.5, -0.5, 2.3, 3.4});
+    parent->replaceAxis(1, parentAxis);
+    constexpr size_t nhist{2};
+    const auto ws = create<Workspace2D>(*parent, nhist, parent->histogram(0));
+    auto axis = ws->getAxis(1);
+    TS_ASSERT_DIFFERS(dynamic_cast<BinEdgeAxis *>(axis), nullptr)
+    TS_ASSERT_EQUALS(axis->length(), nhist + 1);
+  }
+
   void test_create_drop_events() {
     auto eventWS = create<EventWorkspace>(1, Histogram(BinEdges(3)));
     auto ws = create<HistoWorkspace>(*eventWS);
diff --git a/Framework/DataObjects/test/WorkspaceSingleValueTest.h b/Framework/DataObjects/test/WorkspaceSingleValueTest.h
index 70e593de178b4efc9d9f5abab509e26f18ef35b4..5987d006df777f4d4a793f928d5400c25f9e5e7d 100644
--- a/Framework/DataObjects/test/WorkspaceSingleValueTest.h
+++ b/Framework/DataObjects/test/WorkspaceSingleValueTest.h
@@ -24,44 +24,44 @@ class WorkspaceSingleValueTest : public CxxTest::TestSuite {
 public:
   void testConstructorDefaults() {
     WorkspaceSingleValue ws;
-    TS_ASSERT_DELTA(0.0, ws.dataX(0)[0], 1e-6);
-    TS_ASSERT_DELTA(0.0, ws.dataY(0)[0], 1e-6);
-    TS_ASSERT_DELTA(0.0, ws.dataE(0)[0], 1e-6);
+    TS_ASSERT_DELTA(0.0, ws.x(0)[0], 1e-6);
+    TS_ASSERT_DELTA(0.0, ws.y(0)[0], 1e-6);
+    TS_ASSERT_DELTA(0.0, ws.e(0)[0], 1e-6);
   }
   void testConstructor() {
     WorkspaceSingleValue ws(1, 2);
-    TS_ASSERT_DELTA(0.0, ws.dataX(0)[0], 1e-6);
-    TS_ASSERT_DELTA(1, ws.dataY(0)[0], 1e-6);
-    TS_ASSERT_DELTA(2, ws.dataE(0)[0], 1e-6);
+    TS_ASSERT_DELTA(0.0, ws.x(0)[0], 1e-6);
+    TS_ASSERT_DELTA(1, ws.y(0)[0], 1e-6);
+    TS_ASSERT_DELTA(2, ws.e(0)[0], 1e-6);
   }
 
   void testClone() {
     WorkspaceSingleValue ws(2.0, 0.1);
     auto cloned = ws.clone();
 
-    TS_ASSERT_EQUALS(ws.dataX(0)[0], cloned->dataX(0)[0]);
-    TS_ASSERT_EQUALS(ws.dataY(0)[0], cloned->dataY(0)[0]);
-    TS_ASSERT_EQUALS(ws.dataE(0)[0], cloned->dataE(0)[0]);
+    TS_ASSERT_EQUALS(ws.x(0)[0], cloned->x(0)[0]);
+    TS_ASSERT_EQUALS(ws.y(0)[0], cloned->y(0)[0]);
+    TS_ASSERT_EQUALS(ws.e(0)[0], cloned->e(0)[0]);
   }
 
   void testsetgetXvector() {
     WorkspaceSingleValue ws;
     Mantid::MantidVec v1(1, 1.1);
-    ws.dataX(0) = v1;
-    TS_ASSERT_EQUALS(v1, ws.dataX(0));
+    ws.mutableX(0) = v1;
+    TS_ASSERT_EQUALS(v1, ws.x(0).rawData());
   }
   void testsetgetYvector() {
     WorkspaceSingleValue ws;
     Mantid::MantidVec v1(1, 1.1);
-    ws.dataY(0) = v1;
-    TS_ASSERT_EQUALS(v1, ws.dataY(0));
+    ws.mutableY(0) = v1;
+    TS_ASSERT_EQUALS(v1, ws.y(0).rawData());
   }
 
   void testsetgetEvector() {
     WorkspaceSingleValue ws;
     Mantid::MantidVec v1(1, 1.1);
-    ws.dataE(0) = v1;
-    TS_ASSERT_EQUALS(v1, ws.dataE(0));
+    ws.mutableE(0) = v1;
+    TS_ASSERT_EQUALS(v1, ws.e(0).rawData());
   }
 
   void testgetNumDims() {
diff --git a/Framework/Geometry/CMakeLists.txt b/Framework/Geometry/CMakeLists.txt
index df23d6fff779ad4bb640631186735bae866f2ee3..6ff724752f42acb983bdd53c9e4d6e2dc9a059f9 100644
--- a/Framework/Geometry/CMakeLists.txt
+++ b/Framework/Geometry/CMakeLists.txt
@@ -49,6 +49,7 @@ set ( SRC_FILES
 	src/Instrument/ComponentHelper.cpp
 	src/Instrument/ComponentInfo.cpp
 	src/Instrument/ComponentInfoBankHelpers.cpp
+	src/Instrument/ComponentInfoIterator.cpp
 	src/Instrument/Container.cpp
 	src/Instrument/Detector.cpp
 	src/Instrument/DetectorGroup.cpp
@@ -106,8 +107,8 @@ set ( SRC_FILES
 	src/Objects/BoundingBox.cpp
 	src/Objects/CSGObject.cpp
 	src/Objects/InstrumentRayTracer.cpp
-	src/Objects/MeshObject2D.cpp
 	src/Objects/MeshObject.cpp
+	src/Objects/MeshObject2D.cpp
 	src/Objects/MeshObjectCommon.cpp
 	src/Objects/RuleItems.cpp
 	src/Objects/Rules.cpp
@@ -198,6 +199,8 @@ set ( INC_FILES
 	inc/MantidGeometry/Instrument/ComponentHelper.h
 	inc/MantidGeometry/Instrument/ComponentInfo.h
 	inc/MantidGeometry/Instrument/ComponentInfoBankHelpers.h
+	inc/MantidGeometry/Instrument/ComponentInfoItem.h
+	inc/MantidGeometry/Instrument/ComponentInfoIterator.h
 	inc/MantidGeometry/Instrument/ComponentVisitor.h
 	inc/MantidGeometry/Instrument/Container.h
 	inc/MantidGeometry/Instrument/Detector.h
@@ -207,9 +210,10 @@ set ( INC_FILES
 	inc/MantidGeometry/Instrument/DetectorInfoIterator.h
 	inc/MantidGeometry/Instrument/FitParameter.h
 	inc/MantidGeometry/Instrument/Goniometer.h
-        inc/MantidGeometry/Instrument/GridDetector.h
+	inc/MantidGeometry/Instrument/GridDetector.h
 	inc/MantidGeometry/Instrument/GridDetectorPixel.h
 	inc/MantidGeometry/Instrument/IDFObject.h
+	inc/MantidGeometry/Instrument/InfoIteratorBase.h
 	inc/MantidGeometry/Instrument/InstrumentDefinitionParser.h
 	inc/MantidGeometry/Instrument/InstrumentVisitor.h
 	inc/MantidGeometry/Instrument/ObjCompAssembly.h
@@ -307,6 +311,7 @@ set ( TEST_FILES
 	CenteringGroupTest.h
 	CompAssemblyTest.h
 	ComponentInfoBankHelpersTest.h
+	ComponentInfoIteratorTest.h
 	ComponentInfoTest.h
 	ComponentParserTest.h
 	ComponentTest.h
@@ -327,8 +332,8 @@ set ( TEST_FILES
 	GeneralFrameTest.h
 	GeneralTest.h
 	GoniometerTest.h
-        GridDetectorTest.h
 	GridDetectorPixelTest.h
+	GridDetectorTest.h
 	GroupTest.h
 	GroupTransformationTest.h
 	HKLFilterTest.h
@@ -490,4 +495,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Geometry  ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Geometry INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h
index fcfc5e8c1e21f76be3dc73c7260a286cfe4ca6c4..15fefd005c235c0769389b66b7d49d69437ca8a2 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h
@@ -9,6 +9,7 @@
 
 #include "MantidBeamline/ComponentType.h"
 #include "MantidGeometry/DllConfig.h"
+#include "MantidGeometry/Instrument/ComponentInfoIterator.h"
 #include "MantidGeometry/Objects/BoundingBox.h"
 #include "MantidKernel/DateAndTime.h"
 #include <boost/shared_ptr.hpp>
@@ -140,9 +141,18 @@ public:
                                        Types::Core::DateAndTime> &interval);
   size_t scanCount() const;
   void merge(const ComponentInfo &other);
+
+  ComponentInfoIterator<ComponentInfo> begin();
+  ComponentInfoIterator<ComponentInfo> end();
+  const ComponentInfoIterator<const ComponentInfo> cbegin();
+  const ComponentInfoIterator<const ComponentInfo> cend();
+
   friend class Instrument;
 };
 
+using ComponentInfoIt = ComponentInfoIterator<ComponentInfo>;
+using ComponentInfoConstIt = ComponentInfoIterator<const ComponentInfo>;
+
 } // namespace Geometry
 } // namespace Mantid
 
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoItem.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoItem.h
new file mode 100644
index 0000000000000000000000000000000000000000..2814998359dec3a106e87d7b00b3ed97c2c2a611
--- /dev/null
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoItem.h
@@ -0,0 +1,55 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_GEOMETRY_COMPONENTINFOITEM_H_
+#define MANTID_GEOMETRY_COMPONENTINFOITEM_H_
+
+#include <MantidKernel/Quat.h>
+#include <MantidKernel/V3D.h>
+#include <vector>
+
+namespace Mantid {
+namespace Geometry {
+
+/** ComponentInfoItem
+ * Return type for ComponentInfoIterators. Provides AOS type access to
+ * ComponentInfo
+ */
+template <typename T> class ComponentInfoItem {
+public:
+  ComponentInfoItem(T &componentInfo, const size_t index)
+      : m_componentInfo(&componentInfo), m_index(index) {}
+
+  bool isDetector() const { return m_componentInfo->isDetector(m_index); }
+  std::vector<size_t> detectorsInSubtree() const {
+    return m_componentInfo->detectorsInSubtree(m_index);
+  }
+  std::vector<size_t> componentsInSubtree() const {
+    return m_componentInfo->componentsInSubtree(m_index);
+  }
+  const std::vector<size_t> &children() const {
+    return m_componentInfo->children(m_index);
+  }
+  Kernel::V3D position() const { return m_componentInfo->position(m_index); }
+  Kernel::Quat rotation() const { return m_componentInfo->rotation(m_index); }
+  size_t parent() const { return m_componentInfo->parent(m_index); }
+  bool hasParent() const { return m_componentInfo->hasParent(m_index); }
+  Kernel::V3D scaleFactor() const {
+    return m_componentInfo->scaleFactor(m_index);
+  }
+  std::string name() const { return m_componentInfo->name(m_index); }
+  size_t index() const { return m_index; }
+
+  // Non-owning pointer. A reference makes the class unable to define an
+  // assignment operator that we need.
+  T *m_componentInfo;
+  size_t m_index;
+};
+
+} // namespace Geometry
+} // namespace Mantid
+
+#endif /* MANTID_GEOMETRY_COMPONENTINFOITEM_H_ */
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoIterator.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoIterator.h
new file mode 100644
index 0000000000000000000000000000000000000000..949bc55a7c59844d2678a93b4638ccef09b0bea0
--- /dev/null
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoIterator.h
@@ -0,0 +1,26 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_GEOMETRY_COMPONENTINFOITERATOR_H_
+#define MANTID_GEOMETRY_COMPONENTINFOITERATOR_H_
+
+#include "MantidGeometry/Instrument/ComponentInfoItem.h"
+#include "MantidGeometry/Instrument/InfoIteratorBase.h"
+
+namespace Mantid {
+namespace Geometry {
+
+/** ComponentInfoIterator for random access iteration over ComponentInfo
+ */
+template <typename T>
+class ComponentInfoIterator : public InfoIteratorBase<T, ComponentInfoItem> {
+public:
+  using InfoIteratorBase<T, ComponentInfoItem>::InfoIteratorBase;
+};
+} // namespace Geometry
+} // namespace Mantid
+
+#endif /* MANTID_GEOMETRY_COMPONENTINFOITERATOR_H_ */
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h
index 3699f43492230bf8f19cd3c5a948a755f55ce83e..659fa2804a03d28b3456400e7f45bdcf308f366a 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h
@@ -53,6 +53,10 @@ public:
     return m_detectorInfo->rotation(m_index);
   }
 
+  double l2() const { return m_detectorInfo->l2(m_index); }
+
+  size_t index() const { return m_index; }
+
   DetectorInfoItem(T &detectorInfo, const size_t index)
       : m_detectorInfo(&detectorInfo), m_index(index) {}
 
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h
index 3b40de2d809b94523fa02e271e7fc506ab2ff450..4c604e26da0a9274ca2cf762263056baa4665e0a 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h
@@ -8,80 +8,22 @@
 #define MANTID_GEOMETRY_DETECTORINFOITERATOR_H_
 
 #include "MantidGeometry/Instrument/DetectorInfoItem.h"
-#include <boost/iterator/iterator_facade.hpp>
-
-using Mantid::Geometry::DetectorInfoItem;
+#include "MantidGeometry/Instrument/InfoIteratorBase.h"
 
 namespace Mantid {
 namespace Geometry {
 
-/** DetectorInfoIterator
-
-DetectorInfoIterator allows users of the DetectorInfo object access to data
-via an iterator. The iterator works as a slice view in that the index is
-incremented and all items accessible at that index are made available via the
-iterator.
+/** DetectorInfoIterator for random access iteration over DetectorInfo
 
+Random access iterator for iteration over DetectorInfo
 @author Bhuvan Bezawada, STFC
 @date 2018
 */
 template <typename T>
-class DetectorInfoIterator
-    : public boost::iterator_facade<DetectorInfoIterator<T>,
-                                    DetectorInfoItem<T> &,
-                                    boost::random_access_traversal_tag> {
-
+class DetectorInfoIterator : public InfoIteratorBase<T, DetectorInfoItem> {
 public:
-  DetectorInfoIterator(T &detectorInfo, const size_t index)
-      : m_item(detectorInfo, index) {}
-
-private:
-  // Allow boost iterator access
-  friend class boost::iterator_core_access;
-
-  // Iterator methods
-  void advance(int64_t delta) {
-    m_item.m_index =
-        delta < 0 ? std::max(static_cast<uint64_t>(0),
-                             static_cast<uint64_t>(m_item.m_index) + delta)
-                  : std::min(m_item.m_detectorInfo->size(),
-                             m_item.m_index + static_cast<size_t>(delta));
-  }
-
-  // This could cause a segmentation fault if a user goes past the end of the
-  // iterator and tries to index into the n+1 th element (which would not
-  // exist). Adding range checks to all the above methods may slow down
-  // performance though.
-  void increment() {
-    if (m_item.m_index < m_item.m_detectorInfo->size()) {
-      ++m_item.m_index;
-    }
-  }
-
-  void decrement() {
-    if (m_item.m_index > 0) {
-      --m_item.m_index;
-    }
-  }
-
-  size_t getIndex() const { return m_item.m_index; }
-
-  void setIndex(const size_t index) { m_item.m_index = index; }
-
-  bool equal(const DetectorInfoIterator<T> &other) const {
-    return getIndex() == other.getIndex();
-  }
-
-  DetectorInfoItem<T> &dereference() const { return m_item; }
-
-  uint64_t distance_to(const DetectorInfoIterator<T> &other) const {
-    return static_cast<uint64_t>(other.getIndex()) -
-           static_cast<uint64_t>(getIndex());
-  }
-
-  mutable DetectorInfoItem<T> m_item;
+  using InfoIteratorBase<T, DetectorInfoItem>::InfoIteratorBase;
 };
-
 } // namespace Geometry
 } // namespace Mantid
 
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/InfoIteratorBase.h b/Framework/Geometry/inc/MantidGeometry/Instrument/InfoIteratorBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e82ae163573aab3475489607d36f0635601af1a
--- /dev/null
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/InfoIteratorBase.h
@@ -0,0 +1,90 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_GEOMETRY_INFOITERATORBASE_H_
+#define MANTID_GEOMETRY_INFOITERATORBASE_H_
+
+#include <algorithm>
+#include <boost/iterator/iterator_facade.hpp>
+
+namespace Mantid {
+namespace Geometry {
+
+/** InfoIterator
+
+Base to allow users of the Info objects (DetectorInfo etc) access to data
+via a random access iterator.
+
+Note that the reference type (InfoItem<T>) causes the iterator to be treated as
+std::input_iterator for the purposes of many std algorithms such as
+std::advance. See https://en.cppreference.com/w/cpp/iterator/advance for example
+*/
+template <typename T, template <typename> class InfoItem>
+class InfoIteratorBase
+    : public boost::iterator_facade<InfoIteratorBase<T, InfoItem>, InfoItem<T>,
+                                    boost::random_access_traversal_tag,
+                                    InfoItem<T>> {
+
+public:
+  /**
+   * Constructor for base iterator
+   * @param info : Info object (T) to provide iterator ontop of.
+   * @param index : start point of iterator
+   * @param totalSize : Represents maximum length of info. i.e. total number of
+   * items that can be iterated over.
+   */
+  InfoIteratorBase(T &info, const size_t index, const size_t totalSize)
+      : m_item(info, index), m_totalSize(totalSize) {
+    if (index > totalSize)
+      throw std::invalid_argument(
+          "Iterator start point cannot be greater than maximum size");
+  }
+
+private:
+  // Allow boost iterator access
+  friend class boost::iterator_core_access;
+
+  void advance(int64_t delta) {
+    m_item.m_index =
+        delta < 0 ? std::max(static_cast<uint64_t>(0),
+                             static_cast<uint64_t>(m_item.m_index) + delta)
+                  : std::min(m_totalSize,
+                             m_item.m_index + static_cast<size_t>(delta));
+  }
+
+  bool equal(const InfoIteratorBase<T, InfoItem> &other) const {
+    return getIndex() == other.getIndex();
+  }
+
+  void increment() {
+    if (m_item.m_index < m_totalSize) {
+      ++m_item.m_index;
+    }
+  }
+
+  void decrement() {
+    if (m_item.m_index > 0) {
+      --m_item.m_index;
+    }
+  }
+
+  size_t getIndex() const { return m_item.m_index; }
+
+  void setIndex(const size_t index) { m_item.m_index = index; }
+
+  InfoItem<T> dereference() const { return m_item; }
+
+  uint64_t distance_to(const InfoIteratorBase<T, InfoItem> &other) const {
+    return static_cast<uint64_t>(other.getIndex()) -
+           static_cast<uint64_t>(getIndex());
+  }
+
+  InfoItem<T> m_item;
+  size_t m_totalSize;
+};
+} // namespace Geometry
+} // namespace Mantid
+#endif /* MANTID_GEOMETRY_INFOITERATORBASE_H_ */
diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h
index e57513c99a6b416cb0b54d95c7f3918de92c122e..10c8e4b98167c0396d9501207719e39306c4d08f 100644
--- a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h
+++ b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h
@@ -43,17 +43,17 @@ class vtkGeometryCacheWriter;
 
 Mesh Object of Triangles assumed to form one or more
 non-intersecting closed surfaces enclosing separate volumes.
-The number of vertices is limited to 2^16 based on index type. For 2D Meshes see
+The number of vertices is limited to 2^32 based on index type. For 2D Meshes see
 Mesh2DObject
 */
 class MANTID_GEOMETRY_DLL MeshObject : public IObject {
 public:
   /// Constructor
-  MeshObject(const std::vector<uint16_t> &faces,
+  MeshObject(const std::vector<uint32_t> &faces,
              const std::vector<Kernel::V3D> &vertices,
-             const Kernel::Material &material);
+             const Kernel::Material material);
   /// Constructor
-  MeshObject(std::vector<uint16_t> &&faces, std::vector<Kernel::V3D> &&vertices,
+  MeshObject(std::vector<uint32_t> &&faces, std::vector<Kernel::V3D> &&vertices,
              const Kernel::Material &&material);
 
   /// Copy constructor
@@ -133,6 +133,8 @@ public:
   size_t numberOfTriangles() const;
   std::vector<uint32_t> getTriangles() const;
 
+  void rotate(const Kernel::Matrix<double> &);
+  void translate(Kernel::V3D);
   void updateGeometryHandler();
 
 private:
@@ -170,7 +172,7 @@ private:
 
   /// Contents
   /// Triangles are specified by indices into a list of vertices.
-  std::vector<uint16_t> m_triangles;
+  std::vector<uint32_t> m_triangles;
   std::vector<Kernel::V3D> m_vertices;
   /// material composition
   Kernel::Material m_material;
diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h
index e1374a0994ef078cf2cd89ffd6b57955104d92fc..19392be677a0cd0e64ea9ad754bf845637a985f4 100644
--- a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h
+++ b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h
@@ -31,11 +31,11 @@ class GeometryHandler;
 class MANTID_GEOMETRY_DLL MeshObject2D : public IObject {
 public:
   /// Constructor
-  MeshObject2D(const std::vector<uint16_t> &faces,
+  MeshObject2D(const std::vector<uint32_t> &faces,
                const std::vector<Kernel::V3D> &vertices,
                const Kernel::Material &material);
   /// Constructor
-  MeshObject2D(std::vector<uint16_t> &&faces,
+  MeshObject2D(std::vector<uint32_t> &&faces,
                std::vector<Kernel::V3D> &&vertices,
                const Kernel::Material &&material);
 
@@ -97,7 +97,7 @@ private:
   void initialize();
   /// Triangles are specified by indices into a list of vertices. Offset is
   /// always 3.
-  std::vector<uint16_t> m_triangles;
+  std::vector<uint32_t> m_triangles;
   /// Vertices
   std::vector<Kernel::V3D> m_vertices;
   /// Material composition
diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObjectCommon.h b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObjectCommon.h
index f0d61427f5005cd2238f1f61de82f1d933c40544..fc87882568479c93da6ed57b5adf1beeaa5d5e11 100644
--- a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObjectCommon.h
+++ b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObjectCommon.h
@@ -47,8 +47,6 @@ rayIntersectsTriangle(const Kernel::V3D &start, const Kernel::V3D &direction,
                       int &entryExit);
 
 MANTID_GEOMETRY_DLL void checkVertexLimit(size_t nVertices);
-MANTID_GEOMETRY_DLL std::vector<uint32_t>
-getTriangles_uint32(const std::vector<uint16_t> &input);
 MANTID_GEOMETRY_DLL const BoundingBox &
 getBoundingBox(const std::vector<Kernel::V3D> &vertices, BoundingBox &cacheBB);
 MANTID_GEOMETRY_DLL void
diff --git a/Framework/Geometry/src/Instrument/ComponentInfo.cpp b/Framework/Geometry/src/Instrument/ComponentInfo.cpp
index d15a38008cd0c609daeadae94a40d2276683bb66..c7506abf23c33126be0e6da6925a033d5b0fac8a 100644
--- a/Framework/Geometry/src/Instrument/ComponentInfo.cpp
+++ b/Framework/Geometry/src/Instrument/ComponentInfo.cpp
@@ -440,5 +440,21 @@ void ComponentInfo::merge(const ComponentInfo &other) {
   m_componentInfo->merge(*other.m_componentInfo);
 }
 
+ComponentInfoIt ComponentInfo::begin() {
+  return ComponentInfoIt(*this, 0, size());
+}
+
+ComponentInfoIt ComponentInfo::end() {
+  return ComponentInfoIt(*this, size(), size());
+}
+
+const ComponentInfoConstIt ComponentInfo::cbegin() {
+  return ComponentInfoConstIt(*this, 0, size());
+}
+
+const ComponentInfoConstIt ComponentInfo::cend() {
+  return ComponentInfoConstIt(*this, size(), size());
+}
+
 } // namespace Geometry
 } // namespace Mantid
diff --git a/Framework/Geometry/src/Instrument/ComponentInfoIterator.cpp b/Framework/Geometry/src/Instrument/ComponentInfoIterator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6f836773d1a5ecea63d909ff291c8e7b872c0006
--- /dev/null
+++ b/Framework/Geometry/src/Instrument/ComponentInfoIterator.cpp
@@ -0,0 +1,5 @@
+#include "MantidGeometry/Instrument/ComponentInfoIterator.h"
+
+namespace Mantid {
+namespace Geometry {} // namespace Geometry
+} // namespace Mantid
diff --git a/Framework/Geometry/src/Instrument/DetectorInfo.cpp b/Framework/Geometry/src/Instrument/DetectorInfo.cpp
index 5f8d6656a1816f61b8f526c02ff6807c87e8a67a..d09e3c348a6001232aa0ecd68ae9aa448004ae16 100644
--- a/Framework/Geometry/src/Instrument/DetectorInfo.cpp
+++ b/Framework/Geometry/src/Instrument/DetectorInfo.cpp
@@ -332,11 +332,11 @@ DetectorInfo::scanIntervals() const {
 }
 
 const DetectorInfoConstIt DetectorInfo::cbegin() const {
-  return DetectorInfoConstIt(*this, 0);
+  return DetectorInfoConstIt(*this, 0, size());
 }
 
 const DetectorInfoConstIt DetectorInfo::cend() const {
-  return DetectorInfoConstIt(*this, size());
+  return DetectorInfoConstIt(*this, size(), size());
 }
 
 const Geometry::IDetector &DetectorInfo::getDetector(const size_t index) const {
@@ -358,10 +358,14 @@ DetectorInfo::getDetectorPtr(const size_t index) const {
 }
 
 // Begin method for iterator
-DetectorInfoIt DetectorInfo::begin() { return DetectorInfoIt(*this, 0); }
+DetectorInfoIt DetectorInfo::begin() {
+  return DetectorInfoIt(*this, 0, size());
+}
 
 // End method for iterator
-DetectorInfoIt DetectorInfo::end() { return DetectorInfoIt(*this, size()); }
+DetectorInfoIt DetectorInfo::end() {
+  return DetectorInfoIt(*this, size(), size());
+}
 
 } // namespace Geometry
 } // namespace Mantid
diff --git a/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp b/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp
index 698e678bee4c76d426000d2d399697752af783bd..46cf946ddb857a5ac4ac0b7dc6cc9144be6c522d 100644
--- a/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp
+++ b/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp
@@ -2610,7 +2610,7 @@ InstrumentDefinitionParser::writeAndApplyCache(
   IDFObject_const_sptr usedCache = firstChoiceCache;
   auto cachingOption = WroteGeomCache;
 
-  g_log.information("Geometry cache is not available");
+  g_log.notice("Geometry cache is not available");
   try {
     Poco::File dir = usedCache->getParentDirectory();
     if (dir.path().empty() || !dir.exists() || !dir.canWrite()) {
@@ -2627,7 +2627,7 @@ InstrumentDefinitionParser::writeAndApplyCache(
                              "attempting to write cache.\n");
   }
   const std::string cacheFullPath = usedCache->getFileFullPathStr();
-  g_log.information() << "Creating cache in " << cacheFullPath << "\n";
+  g_log.notice() << "Creating cache in " << cacheFullPath << "\n";
   // create a vtk writer
   std::map<std::string, boost::shared_ptr<Geometry::IObject>>::iterator objItr;
   boost::shared_ptr<Mantid::Geometry::vtkGeometryCacheWriter> writer(
diff --git a/Framework/Geometry/src/Objects/MeshObject.cpp b/Framework/Geometry/src/Objects/MeshObject.cpp
index 138d52abb8586dce88200038361f645e188ed342..71d9468fdd3b829d77cca2df441823f1adddfb30 100644
--- a/Framework/Geometry/src/Objects/MeshObject.cpp
+++ b/Framework/Geometry/src/Objects/MeshObject.cpp
@@ -20,16 +20,16 @@
 namespace Mantid {
 namespace Geometry {
 
-MeshObject::MeshObject(const std::vector<uint16_t> &faces,
+MeshObject::MeshObject(const std::vector<uint32_t> &faces,
                        const std::vector<Kernel::V3D> &vertices,
-                       const Kernel::Material &material)
+                       const Kernel::Material material)
     : m_boundingBox(), m_id("MeshObject"), m_triangles(faces),
       m_vertices(vertices), m_material(material) {
 
   initialize();
 }
 
-MeshObject::MeshObject(std::vector<uint16_t> &&faces,
+MeshObject::MeshObject(std::vector<uint32_t> &&faces,
                        std::vector<Kernel::V3D> &&vertices,
                        const Kernel::Material &&material)
     : m_boundingBox(), m_id("MeshObject"), m_triangles(std::move(faces)),
@@ -473,6 +473,18 @@ boost::shared_ptr<GeometryHandler> MeshObject::getGeometryHandler() const {
   return m_handler;
 }
 
+void MeshObject::rotate(const Kernel::Matrix<double> &rotationMatrix) {
+  for (Kernel::V3D &vertex : m_vertices) {
+    vertex.rotate(rotationMatrix);
+  }
+}
+
+void MeshObject::translate(Kernel::V3D translationVector) {
+  for (Kernel::V3D &vertex : m_vertices) {
+    vertex = vertex + translationVector;
+  }
+}
+
 /**
  * Updates the geometry handler if needed
  */
@@ -488,9 +500,7 @@ size_t MeshObject::numberOfTriangles() const { return m_triangles.size() / 3; }
 /**
  * get faces
  */
-std::vector<uint32_t> MeshObject::getTriangles() const {
-  return MeshObjectCommon::getTriangles_uint32(m_triangles);
-}
+std::vector<uint32_t> MeshObject::getTriangles() const { return m_triangles; }
 
 /**
  * get number of points
diff --git a/Framework/Geometry/src/Objects/MeshObject2D.cpp b/Framework/Geometry/src/Objects/MeshObject2D.cpp
index b0d255cd02812758c6388b53df919bfdd14fafc1..d77a22f8d82c6975c813ae26f30a53c6031daf16 100644
--- a/Framework/Geometry/src/Objects/MeshObject2D.cpp
+++ b/Framework/Geometry/src/Objects/MeshObject2D.cpp
@@ -59,10 +59,9 @@ bool allCoplanar(const std::vector<Kernel::V3D> &vertices,
   const auto nz = normal[2];
   const auto k = nx * v0.X() + ny * v0.Y() + nz * v0.Z();
   const auto denom = normal.norm();
-  const static double tolerance =
-      1e-9; // Fixed Tolerance. Too expensive to calculate
-            // based on machine uncertaintly for each
-            // vertex.
+  const static double tolerance = 1e-9; // Fixed Tolerance. Too expensive to
+                                        // calculate based on machine
+                                        // uncertaintly for each vertex.
 
   for (const auto &vertex : vertices) {
     auto d = (nx * vertex.X() + ny * vertex.Y() + nz * vertex.Z() - k) / denom;
@@ -108,7 +107,7 @@ namespace {
  * @param vertex3 :: Third vertex of triangle
  * @returns true if the specified triangle exists
  */
-bool getTriangle(const size_t index, const std::vector<uint16_t> &triangles,
+bool getTriangle(const size_t index, const std::vector<uint32_t> &triangles,
                  const std::vector<Kernel::V3D> &vertices, Kernel::V3D &vertex1,
                  Kernel::V3D &vertex2, Kernel::V3D &vertex3) {
   bool triangleExists = index < triangles.size() / 3;
@@ -146,7 +145,7 @@ bool MeshObject2D::pointsCoplanar(const std::vector<Kernel::V3D> &vertices) {
 /**
  * Constructor
  */
-MeshObject2D::MeshObject2D(const std::vector<uint16_t> &faces,
+MeshObject2D::MeshObject2D(const std::vector<uint32_t> &faces,
                            const std::vector<Kernel::V3D> &vertices,
                            const Kernel::Material &material)
     : m_triangles(faces), m_vertices(vertices), m_material(material) {
@@ -156,7 +155,7 @@ MeshObject2D::MeshObject2D(const std::vector<uint16_t> &faces,
 /**
  * Move constructor
  */
-MeshObject2D::MeshObject2D(std::vector<uint16_t> &&faces,
+MeshObject2D::MeshObject2D(std::vector<uint32_t> &&faces,
                            std::vector<Kernel::V3D> &&vertices,
                            const Kernel::Material &&material)
     : m_triangles(std::move(faces)), m_vertices(std::move(vertices)),
@@ -207,10 +206,11 @@ bool MeshObject2D::isValid(const Kernel::V3D &point) const {
 
   static const double tolerance = 1e-9;
   if (distanceToPlane(point) < tolerance) {
-    for (size_t i = 0; i < m_vertices.size(); i += 3) {
+    for (size_t i = 0; i < m_triangles.size(); i += 3) {
 
-      if (MeshObjectCommon::isOnTriangle(point, m_vertices[i],
-                                         m_vertices[i + 1], m_vertices[i + 2]))
+      if (MeshObjectCommon::isOnTriangle(point, m_vertices[m_triangles[i]],
+                                         m_vertices[m_triangles[i + 1]],
+                                         m_vertices[m_triangles[i + 2]]))
         return true;
     }
   }
@@ -393,10 +393,7 @@ std::vector<double> MeshObject2D::getVertices() const {
   return MeshObjectCommon::getVertices(m_vertices);
 }
 
-std::vector<uint32_t> MeshObject2D::getTriangles() const {
-
-  return MeshObjectCommon::getTriangles_uint32(m_triangles);
-}
+std::vector<uint32_t> MeshObject2D::getTriangles() const { return m_triangles; }
 
 void MeshObject2D::GetObjectGeom(detail::ShapeInfo::GeometryShape &,
                                  std::vector<Kernel::V3D> &, double &,
diff --git a/Framework/Geometry/src/Objects/MeshObjectCommon.cpp b/Framework/Geometry/src/Objects/MeshObjectCommon.cpp
index 368cff0fa3426ede810b886b5d82b8d42062666f..4b1e80dae712c2ad77757ba02f170f0ad4676faa 100644
--- a/Framework/Geometry/src/Objects/MeshObjectCommon.cpp
+++ b/Framework/Geometry/src/Objects/MeshObjectCommon.cpp
@@ -190,30 +190,13 @@ bool rayIntersectsTriangle(const Kernel::V3D &start,
 }
 
 void checkVertexLimit(size_t nVertices) {
-  if (nVertices > std::numeric_limits<uint16_t>::max()) {
+  if (nVertices >= std::numeric_limits<uint32_t>::max()) {
     throw std::invalid_argument(
         "Too many vertices (" + std::to_string(nVertices) +
-        "). MeshObject cannot have more than 65535 vertices.");
+        "). MeshObject cannot have more than 2^32 vertices.");
   }
 }
 
-/**
- * Converts triangle indices from unit16 to unit32
- * @param input
- * @return indices as unit32 vector
- */
-std::vector<uint32_t> getTriangles_uint32(const std::vector<uint16_t> &input) {
-  std::vector<uint32_t> faces;
-  size_t nFaceCorners = input.size();
-  if (nFaceCorners > 0) {
-    faces.resize(static_cast<std::size_t>(nFaceCorners));
-    for (size_t i = 0; i < nFaceCorners; ++i) {
-      faces[i] = static_cast<int>(input[i]);
-    }
-  }
-  return faces;
-}
-
 /**
  * Takes input vertices and calculates bounding box. Returns the bounding box.
  *
diff --git a/Framework/Geometry/test/ComponentInfoIteratorTest.h b/Framework/Geometry/test/ComponentInfoIteratorTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..f978dac916f5ee1692e8755a9127d48f09fe1e00
--- /dev/null
+++ b/Framework/Geometry/test/ComponentInfoIteratorTest.h
@@ -0,0 +1,89 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_GEOMETRY_COMPONENTINFOITERATORTEST_H_
+#define MANTID_GEOMETRY_COMPONENTINFOITERATORTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include "MantidGeometry/Instrument/ComponentInfoItem.h"
+#include "MantidGeometry/Instrument/ComponentInfoIterator.h"
+#include "MantidGeometry/Instrument/Detector.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidGeometry/Instrument/InstrumentVisitor.h"
+#include "MantidTestHelpers/ComponentCreationHelper.h"
+#include <iterator>
+
+using namespace ComponentCreationHelper;
+using namespace Mantid::Geometry;
+
+class ComponentInfoIteratorTest : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static ComponentInfoIteratorTest *createSuite() {
+    return new ComponentInfoIteratorTest();
+  }
+  static void destroySuite(ComponentInfoIteratorTest *suite) { delete suite; }
+
+  std::unique_ptr<Mantid::Geometry::ComponentInfo>
+  create_component_info_object() {
+
+    // Create a very basic instrument to visit
+    auto visitee = createMinimalInstrument(V3D(0, 0, 0),   // Source position
+                                           V3D(10, 0, 0),  // Sample position
+                                           V3D(11, 0, 0)); // Detector position
+
+    // Create the instrument visitor
+    InstrumentVisitor visitor(visitee);
+
+    // Return the DetectorInfo object
+    return InstrumentVisitor::makeWrappers(*visitee, nullptr).first;
+  }
+
+  void test_iterator_cbegin() {
+    auto componentInfo = create_component_info_object();
+    auto iter = componentInfo->cbegin();
+    // Check we start at the correct place
+    TS_ASSERT(iter != componentInfo->cend());
+    TS_ASSERT_EQUALS(iter->m_index, 0);
+  }
+  void test_iterator_cend() {
+    auto componentInfo = create_component_info_object();
+    auto iter = componentInfo->cend();
+    // Check we end at the correct place
+    TS_ASSERT(iter != componentInfo->cbegin());
+    TS_ASSERT_EQUALS(iter->m_index, componentInfo->size());
+  }
+  void test_increment_upwards() {
+    // Iterator starts at component index 0 (detectors usually) and finishes at
+    // root.
+    auto componentInfo = create_component_info_object();
+    ComponentInfoConstIt it(*componentInfo, 0, componentInfo->size());
+    TS_ASSERT(it->isDetector());
+    std::advance(it, componentInfo->size() - 1);
+    TS_ASSERT(!it->isDetector()); // Root is not a detector
+  }
+
+  void test_detector_components_behave_as_expected() {
+
+    auto componentInfo = create_component_info_object();
+    size_t detectorCount = 0;
+    for (auto item : *componentInfo) {
+      if (item.isDetector()) {
+        ++detectorCount;
+        TS_ASSERT_EQUALS(item.detectorsInSubtree().size(), 1);  // Self
+        TS_ASSERT_EQUALS(item.componentsInSubtree().size(), 1); // Self
+        TS_ASSERT_EQUALS(item.children().size(),
+                         0); // Detectors have no children
+      }
+    }
+    TS_ASSERT_EQUALS(detectorCount, 1); // See instrument description above
+  }
+};
+
+#endif /* MANTID_GEOMETRY_COMPONENTINFOITERATORTEST_H_ */
diff --git a/Framework/Geometry/test/DetectorInfoIteratorTest.h b/Framework/Geometry/test/DetectorInfoIteratorTest.h
index bcf5aadeaa39594ba2f03c25317cf64ebda3ee8e..4626086642ea537c7a47e42aa1102194d2d80123 100644
--- a/Framework/Geometry/test/DetectorInfoIteratorTest.h
+++ b/Framework/Geometry/test/DetectorInfoIteratorTest.h
@@ -14,8 +14,10 @@
 #include "MantidGeometry/Instrument/DetectorInfoIterator.h"
 #include "MantidGeometry/Instrument/InstrumentVisitor.h"
 #include "MantidTestHelpers/ComponentCreationHelper.h"
-
 #include <cxxtest/TestSuite.h>
+#include <iterator>
+#include <type_traits>
+#include <typeinfo>
 
 using namespace ComponentCreationHelper;
 using namespace Mantid::Geometry;
@@ -146,6 +148,26 @@ public:
     TS_ASSERT(iter == detectorInfo->cbegin());
   }
 
+  void test_iterator_catagory() {
+    // Characterisation tests
+    using ItTag =
+        typename std::iterator_traits<DetectorInfoConstIt>::iterator_category;
+    using InputItTag = std::input_iterator_tag;
+    using BidirectionalItTag = std::bidirectional_iterator_tag;
+    const static bool inputit = std::is_convertible<ItTag, InputItTag>::value;
+    const static bool bidirectionalit =
+        std::is_convertible<ItTag, BidirectionalItTag>::value;
+    TSM_ASSERT("Iterator expected to be treated as input_iterator", inputit);
+    // Assert below. Iterator not bidirectional. This is why decrement via
+    // std::advance is not supported. Iterator reference must be true reference
+    // to support this.
+    TSM_ASSERT(
+        "Iterator expected not to be treated as legacy bidirectional iterator",
+        !bidirectionalit);
+
+    // see https://en.cppreference.com/w/cpp/iterator/advance
+  }
+
   void test_iterator_advance_and_positions() {
     // Get the DetectorInfo object
     auto detectorInfo = create_detector_info_object();
@@ -161,12 +183,12 @@ public:
 
     // Go backwards 2 places
     xValue = 15.0;
-    std::advance(iter, -2);
+    iter -= 2;
     TS_ASSERT_EQUALS(iter->position().X(), xValue)
 
     // Go to the start
-    std::advance(iter, -4);
-    TS_ASSERT(iter == detectorInfo->cbegin());
+    iter -= 4;
+    TS_ASSERT_EQUALS(iter, detectorInfo->cbegin());
   }
 
   void test_copy_iterator_and_positions() {
diff --git a/Framework/Geometry/test/MeshObject2DTest.h b/Framework/Geometry/test/MeshObject2DTest.h
index 7d0f1a2e79b0eb6a83b060929baf37a40e4bde67..3309a6da1a2d87dbaf6f6ff73a96853b1f642871 100644
--- a/Framework/Geometry/test/MeshObject2DTest.h
+++ b/Framework/Geometry/test/MeshObject2DTest.h
@@ -30,14 +30,14 @@ MeshObject2D makeSimpleTriangleMesh() {
   vertices.emplace_back(V3D(-1, 0, 0));
   vertices.emplace_back(V3D(1, 0, 0));
   vertices.emplace_back(V3D(0, 1, 0));
-  std::vector<uint16_t> triangles;
+  std::vector<uint32_t> triangles;
   triangles.insert(triangles.end(), {0, 1, 2});
   return MeshObject2D(triangles, vertices, Mantid::Kernel::Material());
 }
 MeshObject2D makeTrapezoidMesh(const V3D &a, const V3D &b, const V3D &c,
                                const V3D &d) {
   std::vector<V3D> vertices{a, b, c, d};
-  std::vector<uint16_t> triangles;
+  std::vector<uint32_t> triangles;
   triangles.insert(triangles.end(), {0, 1, 2});
   triangles.insert(triangles.end(), {2, 3, 0});
   return MeshObject2D(triangles, vertices, Mantid::Kernel::Material());
@@ -108,7 +108,7 @@ public:
     vertices.emplace_back(V3D(0, 0, 0));
     vertices.emplace_back(V3D(1, 0, 0));
 
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(),
                      {0, 1, 1}); // invalid, but doesn't matter for this test
 
@@ -131,7 +131,7 @@ public:
     vertices.emplace_back(V3D(1, 0, 0));
     vertices.emplace_back(V3D(2, 0, 0));
 
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(), {0, 1, 2});
 
     // Test constructor taking lvalue references
@@ -155,7 +155,7 @@ public:
     vertices.emplace_back(V3D(1, 1, 0));
     vertices.emplace_back(V3D(1, 0, 1));
 
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(), {0, 1, 2});
 
     // Test constructor taking lvalue references
@@ -176,9 +176,9 @@ public:
     vertices.emplace_back(V3D(-1, 0, 0));
     vertices.emplace_back(V3D(1, 0, 0));
     vertices.emplace_back(V3D(0, 1, 0));
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(), {0, 1, 2});
-
+#
     MeshObject2D mesh(triangles, vertices, Mantid::Kernel::Material());
     TS_ASSERT(mesh.hasValidShape());
     TS_ASSERT_EQUALS(mesh.volume(), 0);
@@ -224,7 +224,7 @@ public:
         V3D{-halfSideLength, halfSideLength, observerDistance},
         V3D{halfSideLength, halfSideLength, observerDistance},
         V3D{halfSideLength, -halfSideLength, observerDistance}};
-    std::vector<uint16_t> triangles{2, 1, 0, 0, 3, 2};
+    std::vector<uint32_t> triangles{2, 1, 0, 0, 3, 2};
     MeshObject2D mesh(triangles, vertices, Mantid::Kernel::Material{});
     double solidAngle = mesh.solidAngle(V3D{0, 0, 0});
     TS_ASSERT_DELTA(solidAngle, expected, 1e-3);
@@ -249,7 +249,7 @@ public:
         V3D{-halfSideLength, halfSideLength, observerDistance},
         V3D{halfSideLength, halfSideLength, observerDistance},
         V3D{halfSideLength, -halfSideLength, observerDistance}};
-    std::vector<uint16_t> triangles{2, 1, 0, 0, 3, 2};
+    std::vector<uint32_t> triangles{2, 1, 0, 0, 3, 2};
     // Scaling square uniformly (and reducing distance to origin by same
     // factor), yields same angular area 4pi/6
     V3D scaleFactor{0.5, 0.5, 0.5};
diff --git a/Framework/Geometry/test/MeshObjectCommonTest.h b/Framework/Geometry/test/MeshObjectCommonTest.h
index 6799b51410a03c67d7dd6798c74bd4fc360067d1..128f661a04526d2241ba5be85019b2597c04e9c9 100644
--- a/Framework/Geometry/test/MeshObjectCommonTest.h
+++ b/Framework/Geometry/test/MeshObjectCommonTest.h
@@ -2,6 +2,7 @@
 #define MANTID_GEOMETRY_MESHOBJECTCOMMONTEST_H_
 
 #include <cxxtest/TestSuite.h>
+#include <limits>
 
 #include "MantidGeometry/Objects/MeshObjectCommon.h"
 #include "MantidKernel/V3D.h"
@@ -118,6 +119,12 @@ public:
     TS_ASSERT(
         !MeshObjectCommon::isOnTriangle(p3 + V3D(0, 0.0001, 0), p1, p2, p3));
   }
+
+  void testTooManyVertices() {
+    TS_ASSERT_THROWS(MeshObjectCommon::checkVertexLimit(
+                         std::numeric_limits<uint32_t>::max()),
+                     std::invalid_argument &);
+  }
 };
 
 #endif /* MANTID_GEOMETRY_MESHOBJECTCOMMONTEST_H_ */
diff --git a/Framework/Geometry/test/MeshObjectTest.h b/Framework/Geometry/test/MeshObjectTest.h
index 3d1d8d7351919b4be687ee51f33e313275bc506f..fe76d899a4f50eabc9b16a7c6b2a9f0f48486168 100644
--- a/Framework/Geometry/test/MeshObjectTest.h
+++ b/Framework/Geometry/test/MeshObjectTest.h
@@ -7,9 +7,9 @@
 #ifndef MANTID_TESTMESHOBJECT__
 #define MANTID_TESTMESHOBJECT__
 
-#include "MantidGeometry/Objects/MeshObject.h"
-
 #include "MantidGeometry/Math/Algebra.h"
+#include "MantidGeometry/Objects/MeshObject.h"
+#include "MantidGeometry/Objects/MeshObjectCommon.h"
 #include "MantidGeometry/Objects/ShapeFactory.h"
 #include "MantidGeometry/Objects/Track.h"
 #include "MantidGeometry/Rendering/GeometryHandler.h"
@@ -74,7 +74,7 @@ std::unique_ptr<MeshObject> createCube(const double size, const V3D &centre) {
   vertices.emplace_back(centre + V3D(max, min, min));
   vertices.emplace_back(centre + V3D(min, min, min));
 
-  std::vector<uint16_t> triangles;
+  std::vector<uint32_t> triangles;
   // top face of cube - z max
   triangles.insert(triangles.end(), {0, 1, 2});
   triangles.insert(triangles.end(), {2, 1, 3});
@@ -124,7 +124,7 @@ std::unique_ptr<MeshObject> createOctahedron() {
   vertices.emplace_back(V3D(0, -u, 0));
   vertices.emplace_back(V3D(0, 0, -u));
 
-  std::vector<uint16_t> triangles;
+  std::vector<uint32_t> triangles;
   // +++ face
   triangles.insert(triangles.end(), {0, 1, 2});
   //++- face
@@ -168,7 +168,7 @@ std::unique_ptr<MeshObject> createLShape() {
   vertices.emplace_back(V3D(1, 2, 1));
   vertices.emplace_back(V3D(0, 2, 1));
 
-  std::vector<uint16_t> triangles;
+  std::vector<uint32_t> triangles;
   // z min
   triangles.insert(triangles.end(), {0, 5, 1});
   triangles.insert(triangles.end(), {1, 3, 2});
@@ -214,7 +214,7 @@ public:
     vertices.emplace_back(V3D(0, 1, 0));
     vertices.emplace_back(V3D(0, 0, 1));
 
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(), {1, 2, 3});
     triangles.insert(triangles.end(), {2, 1, 0});
     triangles.insert(triangles.end(), {3, 0, 1});
@@ -235,13 +235,6 @@ public:
     TS_ASSERT(cloned);
   }
 
-  void testTooManyVertices() {
-    auto tooManyVertices = std::vector<V3D>(70000);
-    auto triangles = std::vector<uint16_t>(1000);
-    TS_ASSERT_THROWS_ANYTHING(
-        MeshObject(triangles, tooManyVertices, Mantid::Kernel::Material()));
-  }
-
   void testMaterial() {
     using Mantid::Kernel::Material;
     std::vector<V3D> vertices;
@@ -250,7 +243,7 @@ public:
     vertices.emplace_back(V3D(0, 1, 0));
     vertices.emplace_back(V3D(0, 0, 1));
 
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(), {1, 2, 3});
     triangles.insert(triangles.end(), {2, 1, 0});
     triangles.insert(triangles.end(), {3, 0, 1});
@@ -963,6 +956,40 @@ public:
     TS_ASSERT_THROWS_NOTHING(geom_obj->getTriangles());
     TS_ASSERT_THROWS_NOTHING(geom_obj->getVertices());
   }
+
+  void testRotation()
+  /* Test Rotating a mesh */
+  {
+    auto lShape = createLShape();
+    const double valueList[] = {0, -1, 0, 1, 0, 0, 0, 0, 1};
+    const std::vector<double> rotationMatrix =
+        std::vector<double>(std::begin(valueList), std::end(valueList));
+    const Kernel::Matrix<double> rotation =
+        Kernel::Matrix<double>(rotationMatrix);
+
+    const double checkList[] = {0,  0, 0, 0,  2, 0, -1, 2, 0, -1, 1, 0,
+                                -2, 1, 0, -2, 0, 0, 0,  0, 1, 0,  2, 1,
+                                -1, 2, 1, -1, 1, 1, -2, 1, 1, -2, 0, 1};
+    auto checkVector =
+        std::vector<double>(std::begin(checkList), std::end(checkList));
+
+    TS_ASSERT_THROWS_NOTHING(lShape->rotate(rotation));
+    auto rotated = lShape->getVertices();
+    TS_ASSERT_DELTA(rotated, checkVector, 1e-8);
+  }
+  void testTranslation()
+  /* Test Translating a mesh */
+  {
+    auto octahedron = createOctahedron();
+    V3D translation = V3D(1, 2, 3);
+    const double checkList[] = {2, 2, 3, 1, 3, 3, 1, 2, 4,
+                                0, 2, 3, 1, 1, 3, 1, 2, 2};
+    auto checkVector =
+        std::vector<double>(std::begin(checkList), std::end(checkList));
+    TS_ASSERT_THROWS_NOTHING(octahedron->translate(translation));
+    auto moved = octahedron->getVertices();
+    TS_ASSERT_DELTA(moved, checkVector, 1e-8);
+  }
 };
 
 // -----------------------------------------------------------------------------
@@ -982,6 +1009,22 @@ public:
         smallCube(createCube(0.2)) {
     testPoints = create_test_points();
     testRays = create_test_rays();
+    translation = create_translation_vector();
+    rotation = create_rotation_matrix();
+  }
+
+  void test_rotate(const Kernel::Matrix<double> &) {
+    const size_t number(10000);
+    for (size_t i = 0; i < number; ++i) {
+      octahedron->rotate(rotation);
+    }
+  }
+
+  void test_translate(Kernel::V3D) {
+    const size_t number(10000);
+    for (size_t i = 0; i < number; ++i) {
+      octahedron->translate(translation);
+    }
   }
 
   void test_isOnSide() {
@@ -1121,6 +1164,19 @@ public:
     return output;
   }
 
+  V3D create_translation_vector() {
+    V3D translate = Kernel::V3D(5, 5, 15);
+    return translate;
+  }
+
+  Kernel::Matrix<double> create_rotation_matrix() {
+    double valueList[] = {0, -1, 0, 1, 0, 0, 0, 0, 1};
+    const std::vector<double> rotationMatrix =
+        std::vector<double>(std::begin(valueList), std::end(valueList));
+    Kernel::Matrix<double> rotation = Kernel::Matrix<double>(rotationMatrix);
+    return rotation;
+  }
+
 private:
   Mantid::Kernel::MersenneTwister rng;
   std::unique_ptr<MeshObject> octahedron;
@@ -1128,6 +1184,8 @@ private:
   std::unique_ptr<MeshObject> smallCube;
   std::vector<V3D> testPoints;
   std::vector<Track> testRays;
+  V3D translation;
+  Kernel::Matrix<double> rotation;
 };
 
 #endif // MANTID_TESTMESHOBJECT__
diff --git a/Framework/HistogramData/CMakeLists.txt b/Framework/HistogramData/CMakeLists.txt
index 7176078f828b9355353e1f620446cd4917ea9650..bc933f241209ca3bdf4db27505fe73ab26ac89b9 100644
--- a/Framework/HistogramData/CMakeLists.txt
+++ b/Framework/HistogramData/CMakeLists.txt
@@ -137,4 +137,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS HistogramData ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS HistogramData INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/Indexing/CMakeLists.txt b/Framework/Indexing/CMakeLists.txt
index ab99025def540a58d9f039f1cb1b3c4d69e17256..e11a541b1a63ca303f7a3f41115dcd6f40f90998 100644
--- a/Framework/Indexing/CMakeLists.txt
+++ b/Framework/Indexing/CMakeLists.txt
@@ -92,4 +92,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Indexing ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Indexing INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/Indexing/inc/MantidIndexing/IndexSet.h b/Framework/Indexing/inc/MantidIndexing/IndexSet.h
index dfb3c25774aa69eeff63db6be74df21ebbca5295..ed50cbb1229484d106900bd8f78fb414a784ce2e 100644
--- a/Framework/Indexing/inc/MantidIndexing/IndexSet.h
+++ b/Framework/Indexing/inc/MantidIndexing/IndexSet.h
@@ -91,6 +91,8 @@ public:
     return m_indices[index];
   }
 
+  bool isContiguous() const noexcept;
+
 protected:
   ~IndexSet() = default;
 
@@ -139,6 +141,20 @@ IndexSet<T>::IndexSet(const std::vector<size_t> &indices, size_t fullRange)
   m_size = m_indices.size();
 }
 
+/**
+ * Check if the index range is contiguous and in ascending order.
+ */
+template <class T> bool IndexSet<T>::isContiguous() const noexcept {
+  if (!m_isRange || m_indices.size() > 1) {
+    for (size_t i = 0; i < m_indices.size() - 1; ++i) {
+      if (m_indices[i] + 1 != m_indices[i + 1]) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 } // namespace detail
 } // namespace Indexing
 } // namespace Mantid
diff --git a/Framework/Indexing/test/IndexSetTest.h b/Framework/Indexing/test/IndexSetTest.h
index 916f4429dd1990f9e9c9f9eccc7666c94cb2126b..7108f6770e5cbcda0bf5ca9ff08a792546440161 100644
--- a/Framework/Indexing/test/IndexSetTest.h
+++ b/Framework/Indexing/test/IndexSetTest.h
@@ -137,6 +137,17 @@ public:
     TS_ASSERT_THROWS_NOTHING(--it2);
     TS_ASSERT_EQUALS(*it2, 0);
   }
+
+  void test_isContiguous() {
+    const IndexSetTester empty{};
+    TS_ASSERT(empty.isContiguous())
+    const IndexSetTester range(3);
+    TS_ASSERT(range.isContiguous())
+    const IndexSetTester manualRange({3, 4, 5}, 6);
+    TS_ASSERT(manualRange.isContiguous())
+    IndexSetTester nonContiguous({2, 1, 3}, 4);
+    TS_ASSERT(!nonContiguous.isContiguous())
+  }
 };
 
 #endif /* MANTID_INDEXING_INDEXSETTEST_H_ */
diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt
index 02ba8ed36e241aebaae702f3b054e909ed1eb85a..a7e6585ff5e0965e821e8409aaedf90337b69f80 100644
--- a/Framework/Kernel/CMakeLists.txt
+++ b/Framework/Kernel/CMakeLists.txt
@@ -624,7 +624,7 @@ endif()
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Kernel ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Kernel INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
 
 # Create the properties file for the installer
 set ( MANTID_ROOT_BUILD ${MANTID_ROOT} )
@@ -680,5 +680,7 @@ configure_file ( ../Properties/Mantid.properties.template
                  ${CMAKE_CURRENT_BINARY_DIR}/Mantid.properties.install
 )
 install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/Mantid.properties.install
-          DESTINATION ${BIN_DIR} RENAME Mantid.properties
-)
+          DESTINATION ${BIN_DIR} RENAME Mantid.properties )
+
+install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/Mantid.properties.install
+          DESTINATION ${WORKBENCH_BIN_DIR} RENAME Mantid.properties )
diff --git a/Framework/Kernel/inc/MantidKernel/Atom.h b/Framework/Kernel/inc/MantidKernel/Atom.h
index 3fbc3856d0135036911104939c31e5fbdb9303e5..5541eabc33da7588127e1dc22482f05ebe75ce94 100644
--- a/Framework/Kernel/inc/MantidKernel/Atom.h
+++ b/Framework/Kernel/inc/MantidKernel/Atom.h
@@ -9,7 +9,6 @@
 
 #include "MantidKernel/DllConfig.h"
 #include "MantidKernel/NeutronAtom.h"
-#include <ostream>
 #include <string>
 
 namespace Mantid {
diff --git a/Framework/Kernel/inc/MantidKernel/CompositeValidator.h b/Framework/Kernel/inc/MantidKernel/CompositeValidator.h
index b3175d7fd4095696524529fb29aa97ab4a970786..34784d8cb30130b296d538efd9b764a71e2718de 100644
--- a/Framework/Kernel/inc/MantidKernel/CompositeValidator.h
+++ b/Framework/Kernel/inc/MantidKernel/CompositeValidator.h
@@ -52,7 +52,18 @@ public:
   template <typename T, typename U> void add(const U &arg) {
     this->add(boost::make_shared<T>(arg));
   }
-  std::list<IValidator_sptr> getChildren() const { return m_children; };
+  /// Returns true if the child list contains a validator of the specified
+  /// template type
+  template <typename T> bool contains() {
+    for (const auto &validator : m_children) {
+      // avoid boost::dynamic_pointer cast to avoid constructing
+      // a temporary shared_ptr type
+      if (dynamic_cast<T *>(validator.get())) {
+        return true;
+      }
+    }
+    return false;
+  }
 
 private:
   /// Verify the value with the child validators
diff --git a/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in b/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in
index 6c53112895a60f5f99cd2a0309a718f050d95888..71c7ade9b8b91f9d4ec9c574bcefe34603f30140 100644
--- a/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in
+++ b/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in
@@ -24,6 +24,14 @@ public:
 
   void reset() override;
   bool isAuthenticated();
+  /**
+   * String describing the rate limit status. This uses a url that github claims
+   * will never count against your REST API limit.
+   * https://developer.github.com/v3/rate_limit/
+   *
+   * @return description of the status or an empty string
+   */
+  std::string getRateLimitDescription();
 
 protected:
   virtual void processResponseHeaders(const Poco::Net::HTTPResponse &res) override;
diff --git a/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h b/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h
index 8bbc61bdf5942739af4f4c273c7130677d20aa80..15300b47fa6c5ad804428d2a79dd790b490c5383 100644
--- a/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h
+++ b/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h
@@ -10,8 +10,6 @@
 #include "MantidKernel/DllConfig.h"
 #include "MantidKernel/Material.h"
 #include <boost/optional/optional.hpp>
-#include <string>
-#include <tuple>
 
 namespace Mantid {
 // Forward declare
@@ -58,7 +56,7 @@ private:
   double getOrCalculateRho(const Material::ChemicalFormula &formula) const;
 
   std::string m_name;
-  std::unique_ptr<Material::ChemicalFormula> m_formula;
+  Material::ChemicalFormula m_formula;
   boost::optional<int> m_atomicNo;
   int m_massNo;
   boost::optional<double> m_numberDensity, m_zParam, m_cellVol, m_massDensity;
diff --git a/Framework/Kernel/inc/MantidKernel/NeutronAtom.h b/Framework/Kernel/inc/MantidKernel/NeutronAtom.h
index 387105a173da7d1580d88f3e6507099d160ffd62..7e811695e0a9a4ab73a0181693773396e3dc61ce 100644
--- a/Framework/Kernel/inc/MantidKernel/NeutronAtom.h
+++ b/Framework/Kernel/inc/MantidKernel/NeutronAtom.h
@@ -109,7 +109,7 @@ MANTID_KERNEL_DLL NeutronAtom getNeutronNoExceptions(const uint16_t z_number,
 MANTID_KERNEL_DLL NeutronAtom getNeutronNoExceptions(const NeutronAtom &other);
 
 /// Utility function to calculate scattering lengths from cross-sections.
-MANTID_KERNEL_DLL void calculateScatteringLengths(NeutronAtom *atom);
+MANTID_KERNEL_DLL void calculateScatteringLengths(NeutronAtom &atom);
 
 } // Namespace PhysicalConstants
 } // Namespace Mantid
diff --git a/Framework/Kernel/src/Atom.cpp b/Framework/Kernel/src/Atom.cpp
index f79ede438ee6e784d2b712f5b7e45e06ca4a3cf3..f984613496c5235af4f6163bdc0f8b28e63c259e 100644
--- a/Framework/Kernel/src/Atom.cpp
+++ b/Framework/Kernel/src/Atom.cpp
@@ -9,10 +9,7 @@
 #include "MantidKernel/PhysicalConstants.h"
 
 #include <algorithm>
-#include <cmath>
 #include <sstream>
-#include <stdexcept>
-#include <string>
 
 namespace Mantid {
 namespace PhysicalConstants {
@@ -3214,7 +3211,10 @@ bool compareAtoms(const Atom &left, const Atom &right) {
  * @return The atom corresponding to the given Z and A
  */
 Atom getAtom(const uint16_t z_number, const uint16_t a_number) {
-  Atom temp("junk", z_number, a_number, NAN, NAN, NAN);
+  Atom temp("junk", z_number, a_number,
+            std::numeric_limits<double>::quiet_NaN(),
+            std::numeric_limits<double>::quiet_NaN(),
+            std::numeric_limits<double>::quiet_NaN());
 
   Atom *result =
       std::lower_bound(&(ATOMS[0]), &(ATOMS[NUM_ATOMS]), temp, compareAtoms);
diff --git a/Framework/Kernel/src/GitHubApiHelper.cpp b/Framework/Kernel/src/GitHubApiHelper.cpp
index 9f3efcebf479dd3bb8c60b0553f1888867a3bb86..766161771964c9715277e80ac143fb8c75ac8da1 100644
--- a/Framework/Kernel/src/GitHubApiHelper.cpp
+++ b/Framework/Kernel/src/GitHubApiHelper.cpp
@@ -14,7 +14,7 @@
 #include <Poco/URI.h>
 
 #include <boost/lexical_cast.hpp>
-
+#include <json/json.h>
 #include <map>
 #include <ostream>
 #include <string>
@@ -32,7 +32,21 @@ using std::string;
 namespace {
 // anonymous namespace for some utility functions
 /// static Logger object
-Logger g_log("InternetHelper");
+Logger g_log("GitHubApiHelper");
+
+const std::string RATE_LIMIT_URL("https://api.github.com/rate_limit");
+
+std::string formatRateLimit(const int rateLimit, const int remaining,
+                            const int expires) {
+  DateAndTime expiresDateAndTime;
+  expiresDateAndTime.set_from_time_t(expires);
+
+  std::stringstream msg;
+  msg << "GitHub API limited to " << remaining << " of " << rateLimit
+      << " calls left. Resets at " << expiresDateAndTime.toISO8601String()
+      << "Z";
+  return msg.str();
+}
 } // namespace
 
 //----------------------------------------------------------------------------------------------
@@ -64,28 +78,47 @@ void GitHubApiHelper::processResponseHeaders(
   // get github api rate limit information if available;
   int rateLimitRemaining = 0;
   int rateLimitLimit;
-  DateAndTime rateLimitReset;
+  int rateLimitReset;
   try {
     rateLimitLimit =
         boost::lexical_cast<int>(res.get("X-RateLimit-Limit", "-1"));
     rateLimitRemaining =
         boost::lexical_cast<int>(res.get("X-RateLimit-Remaining", "-1"));
-    rateLimitReset.set_from_time_t(
-        boost::lexical_cast<int>(res.get("X-RateLimit-Reset", "0")));
+    rateLimitReset =
+        boost::lexical_cast<int>(res.get("X-RateLimit-Reset", "0"));
   } catch (boost::bad_lexical_cast const &) {
     rateLimitLimit = -1;
   }
   if (rateLimitLimit > -1) {
-    g_log.debug() << "GitHub API " << rateLimitRemaining << " of "
-                  << rateLimitLimit << " calls left. Resets at "
-                  << rateLimitReset.toSimpleString() << " GMT\n";
+    g_log.debug(
+        formatRateLimit(rateLimitLimit, rateLimitRemaining, rateLimitReset));
   }
 }
 
+std::string GitHubApiHelper::getRateLimitDescription() {
+  std::stringstream responseStream;
+  this->sendRequest(RATE_LIMIT_URL, responseStream);
+  Json::Reader reader;
+  Json::Value root;
+  if (!reader.parse(responseStream, root)) {
+    return "Failed to parse json document from \"" + RATE_LIMIT_URL + "\"";
+  }
+
+  const auto &rateInfo = root.get("rate", "");
+  if (rateInfo.empty())
+    return std::string();
+
+  const int limit = rateInfo.get("limit", -1).asInt();
+  const int remaining = rateInfo.get("remaining", -1).asInt();
+  const int expires = rateInfo.get("reset", 0).asInt();
+
+  return formatRateLimit(limit, remaining, expires);
+}
+
 int GitHubApiHelper::processAnonymousRequest(
     const Poco::Net::HTTPResponse &response, Poco::URI &uri,
     std::ostream &responseStream) {
-  if (isAuthenticated()) {
+  if (!isAuthenticated()) {
     g_log.debug("Repeating API call anonymously\n");
     removeHeader("Authorization");
     return this->sendRequest(uri.toString(), responseStream);
@@ -110,7 +143,10 @@ int GitHubApiHelper::sendRequestAndProcess(HTTPClientSession &session,
   if (retStatus == HTTP_OK ||
       (retStatus == HTTP_CREATED && m_method == HTTPRequest::HTTP_POST)) {
     Poco::StreamCopier::copyStream(rs, responseStream);
-    processResponseHeaders(*m_response);
+    if (m_response)
+      processResponseHeaders(*m_response);
+    else
+      g_log.warning("Response is null pointer");
     return retStatus;
   } else if ((retStatus == HTTP_FORBIDDEN && isAuthenticated()) ||
              (retStatus == HTTP_UNAUTHORIZED) ||
diff --git a/Framework/Kernel/src/InternetHelper.cpp b/Framework/Kernel/src/InternetHelper.cpp
index c71a1583afeae5f0f5ff88ecb34d752ea8221dbb..01a11d40f195758c1de03dd5e6d38673dece8f88 100644
--- a/Framework/Kernel/src/InternetHelper.cpp
+++ b/Framework/Kernel/src/InternetHelper.cpp
@@ -160,7 +160,10 @@ int InternetHelper::sendRequestAndProcess(HTTPClientSession &session,
   if (retStatus == HTTP_OK ||
       (retStatus == HTTP_CREATED && m_method == HTTPRequest::HTTP_POST)) {
     Poco::StreamCopier::copyStream(rs, responseStream);
-    processResponseHeaders(*m_response);
+    if (m_response)
+      processResponseHeaders(*m_response);
+    else
+      g_log.warning("Response is null pointer");
     return retStatus;
   } else if (isRelocated(retStatus)) {
     return this->processRelocation(*m_response, responseStream);
@@ -174,7 +177,7 @@ int InternetHelper::processRelocation(const HTTPResponse &response,
                                       std::ostream &responseStream) {
   std::string newLocation = response.get("location", "");
   if (!newLocation.empty()) {
-    g_log.information() << "url relocated to " << newLocation;
+    g_log.information() << "url relocated to " << newLocation << "\n";
     return this->sendRequest(newLocation, responseStream);
   } else {
     g_log.warning("Apparent relocation did not give new location\n");
@@ -413,8 +416,8 @@ behaviour.
 int InternetHelper::downloadFile(const std::string &urlFile,
                                  const std::string &localFilePath) {
   int retStatus = 0;
-  g_log.debug() << "DownloadFile : " << urlFile << " to file: " << localFilePath
-                << '\n';
+  g_log.debug() << "DownloadFile from \"" << urlFile << "\" to file: \""
+                << localFilePath << "\"\n";
 
   Poco::TemporaryFile tempFile;
   Poco::FileStream tempFileStream(tempFile.path());
diff --git a/Framework/Kernel/src/MaterialBuilder.cpp b/Framework/Kernel/src/MaterialBuilder.cpp
index 8a59a82cc204f56441b4d24e77f27b1d276549fb..7fef5ea3e5542bf71ec5b5f8b137721ad5be57d3 100644
--- a/Framework/Kernel/src/MaterialBuilder.cpp
+++ b/Framework/Kernel/src/MaterialBuilder.cpp
@@ -19,9 +19,7 @@ namespace Kernel {
 
 namespace {
 inline bool isEmpty(const boost::optional<double> value) {
-  if (!value)
-    return true;
-  return (value == Mantid::EMPTY_DBL());
+  return !value || value == Mantid::EMPTY_DBL();
 }
 } // namespace
 
@@ -67,7 +65,7 @@ MaterialBuilder &MaterialBuilder::setFormula(const std::string &formula) {
   }
   using ChemicalFormula = Material::ChemicalFormula;
   try {
-    m_formula = Mantid::Kernel::make_unique<ChemicalFormula>(
+    m_formula = ChemicalFormula(
         ChemicalFormula(Material::parseChemicalFormula(formula)));
   } catch (std::runtime_error &exc) {
     throw std::invalid_argument(
@@ -83,7 +81,7 @@ MaterialBuilder &MaterialBuilder::setFormula(const std::string &formula) {
  * @return A reference to the this object to allow chaining
  */
 MaterialBuilder &MaterialBuilder::setAtomicNumber(int atomicNumber) {
-  if (m_formula) {
+  if (!m_formula.empty()) {
     throw std::runtime_error("MaterialBuilder::setAtomicNumber() - Formula "
                              "already set, cannot use atomic number aswell.");
   }
@@ -206,19 +204,19 @@ MaterialBuilder &MaterialBuilder::setAbsorptionXSection(double xsec) {
  */
 Material MaterialBuilder::build() const {
   Material::ChemicalFormula formula;
-  double density;
 
-  if (m_formula) {
-    formula = Material::ChemicalFormula(*m_formula);
+  if (!m_formula.empty()) {
+    formula = Material::ChemicalFormula(m_formula);
   } else if (m_atomicNo) {
     formula = createCompositionFromAtomicNumber();
-  } else {
-    throw std::runtime_error("MaterialBuilder::createNeutronAtom() - Please "
-                             "specify one of chemical formula or atomic "
-                             "number.");
+  } else if (!m_totalXSection || !m_cohXSection || !m_incXSection ||
+             !m_absSection || !m_numberDensity) {
+    throw std::runtime_error("Please specify one of chemical formula or atomic "
+                             "number or all cross sections and a number "
+                             "density.");
   }
 
-  density = getOrCalculateRho(formula);
+  const double density = getOrCalculateRho(formula);
   if (hasOverrideNeutronProperties()) {
     PhysicalConstants::NeutronAtom neutron = generateCustomNeutron();
     return Material(m_name, neutron, density);
@@ -251,15 +249,15 @@ MaterialBuilder::createCompositionFromAtomicNumber() const {
  */
 double MaterialBuilder::getOrCalculateRho(
     const Material::ChemicalFormula &formula) const {
-  // first calculate the total number of atoms
+  if (m_numberDensity) {
+    return m_numberDensity.get();
+  }
   double totalNumAtoms = 0.;
   for (const auto &formulaUnit : formula) {
     totalNumAtoms += formulaUnit.multiplicity;
   }
 
-  if (m_numberDensity) {
-    return m_numberDensity.get();
-  } else if (m_zParam && m_cellVol) {
+  if (m_zParam && m_cellVol) {
     return totalNumAtoms * m_zParam.get() / m_cellVol.get();
   } else if (m_massDensity) {
     // g / cc -> atoms / Angstrom^3
@@ -269,8 +267,8 @@ double MaterialBuilder::getOrCalculateRho(
     }
     return (m_massDensity.get() * totalNumAtoms / rmm) *
            PhysicalConstants::N_A * 1e-24;
-  } else if (m_formula && m_formula->size() == 1) {
-    return m_formula->at(0).atom->number_density;
+  } else if (!m_formula.empty() && m_formula.size() == 1) {
+    return m_formula.front().atom->number_density;
   } else {
     throw std::runtime_error(
         "The number density could not be determined. Please "
@@ -280,15 +278,8 @@ double MaterialBuilder::getOrCalculateRho(
 }
 
 bool MaterialBuilder::hasOverrideNeutronProperties() const {
-  if (!isEmpty(m_totalXSection))
-    return true;
-  if (!isEmpty(m_cohXSection))
-    return true;
-  if (!isEmpty(m_incXSection))
-    return true;
-  if (!isEmpty(m_absSection))
-    return true;
-  return false;
+  return !isEmpty(m_totalXSection) || !isEmpty(m_cohXSection) ||
+         !isEmpty(m_incXSection) || !isEmpty(m_absSection);
 }
 
 PhysicalConstants::NeutronAtom MaterialBuilder::generateCustomNeutron() const {
@@ -299,19 +290,26 @@ PhysicalConstants::NeutronAtom MaterialBuilder::generateCustomNeutron() const {
     auto atom = getAtom(static_cast<uint16_t>(m_atomicNo.get()),
                         static_cast<uint16_t>(m_massNo));
     neutronAtom = atom.neutron;
-  } else {
+    overrideNeutronProperties(neutronAtom);
+  } else if (!m_formula.empty()) {
     double totalNumAtoms = 0.;
-    for (const auto &formulaUnit : *m_formula) {
+    for (const auto &formulaUnit : m_formula) {
       neutronAtom =
           neutronAtom + formulaUnit.multiplicity * formulaUnit.atom->neutron;
       totalNumAtoms += formulaUnit.multiplicity;
     }
     neutronAtom = (1. / totalNumAtoms) * neutronAtom;
+    overrideNeutronProperties(neutronAtom);
+  } else {
+    neutronAtom.coh_scatt_xs = *m_cohXSection;
+    neutronAtom.inc_scatt_xs = *m_incXSection;
+    neutronAtom.tot_scatt_xs = *m_totalXSection;
+    neutronAtom.abs_scatt_xs = *m_absSection;
+    calculateScatteringLengths(neutronAtom);
   }
   neutronAtom.a_number = 0; // signifies custom neutron atom
   neutronAtom.z_number = 0; // signifies custom neutron atom
 
-  overrideNeutronProperties(neutronAtom);
   return neutronAtom;
 }
 
diff --git a/Framework/Kernel/src/NeutronAtom.cpp b/Framework/Kernel/src/NeutronAtom.cpp
index 21006afafeb4eee40a62e1ba51cda683b1e970d4..4344b8b5e30287fb187587f1c8e68b6e474284fe 100644
--- a/Framework/Kernel/src/NeutronAtom.cpp
+++ b/Framework/Kernel/src/NeutronAtom.cpp
@@ -26,19 +26,19 @@ namespace {
 const double INV_FOUR_PI = 1. / (4. * M_PI);
 }
 
-void calculateScatteringLengths(NeutronAtom *atom) {
+void calculateScatteringLengths(NeutronAtom &atom) {
   // 1 barn = 100 fm
-  atom->tot_scatt_length = 10. * std::sqrt(atom->tot_scatt_xs * INV_FOUR_PI);
+  atom.tot_scatt_length = 10. * std::sqrt(atom.tot_scatt_xs * INV_FOUR_PI);
 
-  if (atom->coh_scatt_length_img == 0.)
-    atom->coh_scatt_length = fabs(atom->coh_scatt_length_real);
+  if (atom.coh_scatt_length_img == 0.)
+    atom.coh_scatt_length = fabs(atom.coh_scatt_length_real);
   else
-    atom->coh_scatt_length = 10. * std::sqrt(atom->coh_scatt_xs * INV_FOUR_PI);
+    atom.coh_scatt_length = 10. * std::sqrt(atom.coh_scatt_xs * INV_FOUR_PI);
 
-  if (atom->inc_scatt_length_img == 0.)
-    atom->inc_scatt_length = fabs(atom->inc_scatt_length_real);
+  if (atom.inc_scatt_length_img == 0.)
+    atom.inc_scatt_length = fabs(atom.inc_scatt_length_real);
   else
-    atom->inc_scatt_length = 10. * std::sqrt(atom->inc_scatt_xs * INV_FOUR_PI);
+    atom.inc_scatt_length = 10. * std::sqrt(atom.inc_scatt_xs * INV_FOUR_PI);
 }
 
 /**
@@ -59,7 +59,7 @@ NeutronAtom::NeutronAtom(const uint16_t z, const double coh_b_real,
       coh_scatt_length_img(0.), inc_scatt_length_real(inc_b_real),
       inc_scatt_length_img(0.), coh_scatt_xs(coh_xs), inc_scatt_xs(inc_xs),
       tot_scatt_xs(tot_xs), abs_scatt_xs(abs_xs) {
-  calculateScatteringLengths(this);
+  calculateScatteringLengths(*this);
 }
 /**
  * Atom constructor
@@ -80,7 +80,7 @@ NeutronAtom::NeutronAtom(const uint16_t z, const uint16_t a,
       coh_scatt_length_img(0.), inc_scatt_length_real(inc_b_real),
       inc_scatt_length_img(0.), coh_scatt_xs(coh_xs), inc_scatt_xs(inc_xs),
       tot_scatt_xs(tot_xs), abs_scatt_xs(abs_xs) {
-  calculateScatteringLengths(this);
+  calculateScatteringLengths(*this);
 }
 /**
  * Atom constructor
@@ -104,7 +104,7 @@ NeutronAtom::NeutronAtom(const uint16_t z, const uint16_t a,
       coh_scatt_length_img(coh_b_img), inc_scatt_length_real(inc_b_real),
       inc_scatt_length_img(inc_b_img), coh_scatt_xs(coh_xs),
       inc_scatt_xs(inc_xs), tot_scatt_xs(tot_xs), abs_scatt_xs(abs_xs) {
-  calculateScatteringLengths(this);
+  calculateScatteringLengths(*this);
 }
 
 NeutronAtom::NeutronAtom(const NeutronAtom &other)
@@ -115,7 +115,7 @@ NeutronAtom::NeutronAtom(const NeutronAtom &other)
       inc_scatt_length_img(other.inc_scatt_length_img),
       coh_scatt_xs(other.coh_scatt_xs), inc_scatt_xs(other.inc_scatt_xs),
       tot_scatt_xs(other.tot_scatt_xs), abs_scatt_xs(other.abs_scatt_xs) {
-  calculateScatteringLengths(this);
+  calculateScatteringLengths(*this);
 }
 
 NeutronAtom &NeutronAtom::operator=(const NeutronAtom &other) {
@@ -133,7 +133,7 @@ NeutronAtom &NeutronAtom::operator=(const NeutronAtom &other) {
   tot_scatt_xs = other.tot_scatt_xs;
   abs_scatt_xs = other.abs_scatt_xs;
 
-  calculateScatteringLengths(this);
+  calculateScatteringLengths(*this);
 
   return *this;
 }
@@ -144,11 +144,18 @@ NeutronAtom &NeutronAtom::operator=(const NeutronAtom &other) {
  * initialize them.
  */
 NeutronAtom::NeutronAtom()
-    : z_number(0), a_number(0), coh_scatt_length_real(NAN),
-      coh_scatt_length_img(NAN), inc_scatt_length_real(NAN),
-      inc_scatt_length_img(NAN), coh_scatt_xs(NAN), inc_scatt_xs(NAN),
-      tot_scatt_xs(NAN), abs_scatt_xs(NAN), tot_scatt_length(NAN),
-      coh_scatt_length(NAN), inc_scatt_length(NAN) {}
+    : z_number(0), a_number(0),
+      coh_scatt_length_real(std::numeric_limits<double>::quiet_NaN()),
+      coh_scatt_length_img(std::numeric_limits<double>::quiet_NaN()),
+      inc_scatt_length_real(std::numeric_limits<double>::quiet_NaN()),
+      inc_scatt_length_img(std::numeric_limits<double>::quiet_NaN()),
+      coh_scatt_xs(std::numeric_limits<double>::quiet_NaN()),
+      inc_scatt_xs(std::numeric_limits<double>::quiet_NaN()),
+      tot_scatt_xs(std::numeric_limits<double>::quiet_NaN()),
+      abs_scatt_xs(std::numeric_limits<double>::quiet_NaN()),
+      tot_scatt_length(std::numeric_limits<double>::quiet_NaN()),
+      coh_scatt_length(std::numeric_limits<double>::quiet_NaN()),
+      inc_scatt_length(std::numeric_limits<double>::quiet_NaN()) {}
 
 /// @cond
 static const NeutronAtom H(1, -3.7390, 0., 1.7568, 80.26, 82.02, 0.3326);
diff --git a/Framework/Kernel/src/VectorHelper.cpp b/Framework/Kernel/src/VectorHelper.cpp
index 223fc4fec9d98da0f2d6a23b745f5bcc627d35ad..0c7d9638c31c42e745ca54648cb47cea24e2c656 100644
--- a/Framework/Kernel/src/VectorHelper.cpp
+++ b/Framework/Kernel/src/VectorHelper.cpp
@@ -391,7 +391,7 @@ void convertToBinCentre(const std::vector<double> &bin_edges,
  */
 void convertToBinBoundary(const std::vector<double> &bin_centers,
                           std::vector<double> &bin_edges) {
-  const std::vector<double>::size_type n = bin_centers.size();
+  const auto n = bin_centers.size();
 
   // Special case empty input: output is also empty
   if (n == 0) {
diff --git a/Framework/Kernel/test/CompositeValidatorTest.h b/Framework/Kernel/test/CompositeValidatorTest.h
index 23f929842e39f9bebe2f09e4ab6b758351c20514..d176d078eb2ecebb53cf530298ae7acf937d5104 100644
--- a/Framework/Kernel/test/CompositeValidatorTest.h
+++ b/Framework/Kernel/test/CompositeValidatorTest.h
@@ -89,6 +89,27 @@ public:
     TS_ASSERT_EQUALS(comp.isValid(70), "");  // In range of val2
     TS_ASSERT_DIFFERS(comp.isValid(55), ""); // Not in range
   }
+
+  void test_ContainsReturnsTrueIfListContainsType() {
+    CompositeValidator comp;
+    auto val1 = boost::make_shared<BoundedValidator<int>>(1, 50);
+    auto val2 = boost::make_shared<BoundedValidator<double>>(60, 100);
+    comp.add(val1);
+    comp.add(val2);
+
+    TS_ASSERT(comp.contains<BoundedValidator<int>>());
+    TS_ASSERT(comp.contains<BoundedValidator<double>>());
+  }
+
+  void test_ContainsReturnsFalseIfListDoesNotContainType() {
+    CompositeValidator comp;
+    auto val1 = boost::make_shared<BoundedValidator<int>>(1, 50);
+    auto val2 = boost::make_shared<BoundedValidator<double>>(60, 100);
+    comp.add(val1);
+    comp.add(val2);
+
+    TS_ASSERT_EQUALS(false, comp.contains<StringListValidator>());
+  }
 };
 
 #endif /* MANTID_KERNEL_COMPOSITEVALIDATORTEST_H_ */
diff --git a/Framework/Kernel/test/MaterialBuilderTest.h b/Framework/Kernel/test/MaterialBuilderTest.h
index 6f967bfd900e3d02af42e6aabbc2eb713f2f567e..8032a821f98133e310a3ace7db73a1e68dce196f 100644
--- a/Framework/Kernel/test/MaterialBuilderTest.h
+++ b/Framework/Kernel/test/MaterialBuilderTest.h
@@ -9,6 +9,7 @@
 
 #include <cxxtest/TestSuite.h>
 
+#include "MantidKernel/Atom.h"
 #include "MantidKernel/EmptyValues.h"
 #include "MantidKernel/MaterialBuilder.h"
 #include "MantidKernel/NeutronAtom.h"
@@ -84,6 +85,23 @@ public:
     TS_ASSERT_DELTA(mat.absorbXSection(), 4.6, 0.0001);
   }
 
+  void test_Build_From_Cross_Sections() {
+    MaterialBuilder builder;
+    Material mat = builder.setNumberDensity(0.1)
+                       .setTotalScatterXSection(2.3)
+                       .setCoherentXSection(0.5)
+                       .setIncoherentXSection(5.0)
+                       .setAbsorptionXSection(0.23)
+                       .build();
+    TS_ASSERT_EQUALS(mat.chemicalFormula().size(), 1)
+    TS_ASSERT_EQUALS(mat.chemicalFormula().front().atom->symbol, "user")
+    TS_ASSERT_EQUALS(mat.numberDensity(), 0.1)
+    TS_ASSERT_EQUALS(mat.totalScatterXSection(), 2.3)
+    TS_ASSERT_EQUALS(mat.cohScatterXSection(), 0.5)
+    TS_ASSERT_EQUALS(mat.incohScatterXSection(), 5.0)
+    TS_ASSERT_EQUALS(mat.absorbXSection(), 0.23)
+  }
+
   void test_Number_Density_Set_By_Formula_ZParameter_And_Cell_Volume() {
     MaterialBuilder builder;
     auto mat = builder.setName("Nickel")
@@ -173,6 +191,51 @@ public:
         "provide the number density, ZParameter and unit "
         "cell volume or mass density.")
   }
+
+  void test_User_Defined_Material_Without_NumberDensity_Throws() {
+    MaterialBuilder builder;
+    builder = builder.setTotalScatterXSection(2.3)
+                  .setCoherentXSection(0.5)
+                  .setIncoherentXSection(5.0)
+                  .setAbsorptionXSection(0.23);
+    TS_ASSERT_THROWS(builder.build(), std::runtime_error)
+  }
+
+  void test_User_Defined_Material_Without_TotalScatterXSection_Throws() {
+    MaterialBuilder builder;
+    builder = builder.setNumberDensity(0.1)
+                  .setCoherentXSection(0.5)
+                  .setIncoherentXSection(5.0)
+                  .setAbsorptionXSection(0.23);
+    TS_ASSERT_THROWS(builder.build(), std::runtime_error)
+  }
+
+  void test_User_Defined_Material_Without_CoherentXSection_Throws() {
+    MaterialBuilder builder;
+    builder = builder.setNumberDensity(0.1)
+                  .setTotalScatterXSection(2.3)
+                  .setIncoherentXSection(5.0)
+                  .setAbsorptionXSection(0.23);
+    TS_ASSERT_THROWS(builder.build(), std::runtime_error)
+  }
+
+  void test_User_Defined_Material_Without_IncoherentXSection_Throws() {
+    MaterialBuilder builder;
+    builder = builder.setNumberDensity(0.1)
+                  .setTotalScatterXSection(2.3)
+                  .setCoherentXSection(5.0)
+                  .setAbsorptionXSection(0.23);
+    TS_ASSERT_THROWS(builder.build(), std::runtime_error)
+  }
+
+  void test_User_Defined_Material_Without_AbsorptionXSection_Throws() {
+    MaterialBuilder builder;
+    builder = builder.setNumberDensity(0.1)
+                  .setTotalScatterXSection(2.3)
+                  .setCoherentXSection(5.0)
+                  .setIncoherentXSection(5.0);
+    TS_ASSERT_THROWS(builder.build(), std::runtime_error)
+  }
 };
 
 #endif /* MANTID_KERNEL_MATERIALBUILDERTEST_H_ */
diff --git a/Framework/LiveData/CMakeLists.txt b/Framework/LiveData/CMakeLists.txt
index b6dcc75863db9529b56feebc25f0991641c69a6c..bab543cef2bc166cc02d056c97eda12e4e961572 100644
--- a/Framework/LiveData/CMakeLists.txt
+++ b/Framework/LiveData/CMakeLists.txt
@@ -143,4 +143,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS LiveData ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS LiveData INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/MDAlgorithms/CMakeLists.txt b/Framework/MDAlgorithms/CMakeLists.txt
index 2466834c2b39827ea16971142a44ea2db31c0339..217db1efef7c7b9c3311733e9acea3d4d6608685 100644
--- a/Framework/MDAlgorithms/CMakeLists.txt
+++ b/Framework/MDAlgorithms/CMakeLists.txt
@@ -71,6 +71,7 @@ set ( SRC_FILES
 	src/LoadSQW2.cpp
 	src/LogarithmMD.cpp
 	src/MDEventWSWrapper.cpp
+	src/MDNorm.cpp
 	src/MDNormDirectSC.cpp
 	src/MDNormSCD.cpp
 	src/MDTransfAxisNames.cpp
@@ -202,6 +203,7 @@ set ( INC_FILES
 	inc/MantidMDAlgorithms/LoadSQW2.h
 	inc/MantidMDAlgorithms/LogarithmMD.h
 	inc/MantidMDAlgorithms/MDEventWSWrapper.h
+	inc/MantidMDAlgorithms/MDNorm.h
 	inc/MantidMDAlgorithms/MDNormDirectSC.h
 	inc/MantidMDAlgorithms/MDNormSCD.h
 	inc/MantidMDAlgorithms/MDTransfAxisNames.h
@@ -429,4 +431,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS MDAlgorithms ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS MDAlgorithms INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/MDNorm.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/MDNorm.h
new file mode 100644
index 0000000000000000000000000000000000000000..f701575edc8e6130e635629f11a53f0358cf2288
--- /dev/null
+++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/MDNorm.h
@@ -0,0 +1,100 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_MDALGORITHMS_MDNORM_H_
+#define MANTID_MDALGORITHMS_MDNORM_H_
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidGeometry/Crystal/SymmetryOperationFactory.h"
+#include "MantidMDAlgorithms/DllConfig.h"
+#include "MantidMDAlgorithms/SlicingAlgorithm.h"
+
+namespace Mantid {
+namespace MDAlgorithms {
+
+/** MDNormalization : Bin single crystal diffraction or direct geometry
+ * inelastic data and calculate the corresponding statistical weight
+ */
+class MANTID_MDALGORITHMS_DLL MDNorm : public API::Algorithm {
+public:
+  MDNorm();
+  const std::string name() const override;
+  int version() const override;
+  const std::string category() const override;
+  const std::string summary() const override;
+  const std::vector<std::string> seeAlso() const override {
+    return {"CropWorkspaceForMDNorm", "MDNormSCD", "MDNormDirectSC",
+            "RecalculateTrajectoriesExtents"};
+  }
+
+private:
+  void init() override;
+  void exec() override;
+  std::map<std::string, std::string> validateInputs() override final;
+  std::string QDimensionName(std::vector<double> projection);
+  std::map<std::string, std::string> getBinParameters();
+  void createNormalizationWS(const DataObjects::MDHistoWorkspace &dataWS);
+  DataObjects::MDHistoWorkspace_sptr
+  binInputWS(std::vector<Geometry::SymmetryOperation> symmetryOps);
+  std::vector<coord_t>
+  getValuesFromOtherDimensions(bool &skipNormalization,
+                               uint16_t expInfoIndex = 0) const;
+  void cacheDimensionXValues();
+  void calculateNormalization(const std::vector<coord_t> &otherValues,
+                              Geometry::SymmetryOperation so,
+                              uint16_t expInfoIndex, size_t soIndex);
+  void calculateIntersections(std::vector<std::array<double, 4>> &intersections,
+                              const double theta, const double phi,
+                              Kernel::DblMatrix transform, double lowvalue,
+                              double highvalue);
+  void calcIntegralsForIntersections(const std::vector<double> &xValues,
+                                     const API::MatrixWorkspace &integrFlux,
+                                     size_t sp, std::vector<double> &yValues);
+
+  /// Normalization workspace
+  DataObjects::MDHistoWorkspace_sptr m_normWS;
+  /// Input workspace
+  API::IMDEventWorkspace_sptr m_inputWS;
+  /// flag for reciprocal lattice units
+  bool m_isRLU;
+  /// The projection vectors
+  std::vector<double> m_Q0Basis{1., 0., 0.}, m_Q1Basis{0., 1., 0.},
+      m_Q2Basis{0., 0., 1.};
+  /// UB matrix
+  Mantid::Kernel::DblMatrix m_UB;
+  /// W matrix
+  Mantid::Kernel::DblMatrix m_W;
+  /** matrix for transforming from intersections to positions in the
+  normalization workspace */
+  Mantid::Kernel::Matrix<coord_t> m_transformation;
+  /// cached X values along dimensions h,k,l. dE
+  std::vector<double> m_hX, m_kX, m_lX, m_eX;
+  /// index of h,k,l, dE dimensions in the output workspaces
+  size_t m_hIdx, m_kIdx, m_lIdx, m_eIdx;
+  /// number of experimentInfo objects
+  size_t m_numExptInfos;
+  /// number of symmetry operations
+  size_t m_numSymmOps;
+  /// Cached value of incident energy dor direct geometry
+  double m_Ei;
+  /// Flag indicating if the input workspace is from diffraction
+  bool m_diffraction;
+  /// Flag to accumulate normalization
+  bool m_accumulate;
+  /// Flag to indicate that the energy dimension is integrated
+  bool m_dEIntegrated;
+  /// Sample position
+  Kernel::V3D m_samplePos;
+  /// Beam direction
+  Kernel::V3D m_beamDir;
+  /// ki-kf for Inelastic convention; kf-ki for Crystallography convention
+  std::string convention;
+};
+
+} // namespace MDAlgorithms
+} // namespace Mantid
+
+#endif /* MANTID_MDALGORITHMS_MDNORM_H_ */
diff --git a/Framework/MDAlgorithms/src/IntegratePeaksMD.cpp b/Framework/MDAlgorithms/src/IntegratePeaksMD.cpp
index 29638c4356c0df3e5aa2d70edd15b2ce013d0ae1..5107462c87d072a817131800eb82af74de12e309 100644
--- a/Framework/MDAlgorithms/src/IntegratePeaksMD.cpp
+++ b/Framework/MDAlgorithms/src/IntegratePeaksMD.cpp
@@ -542,7 +542,7 @@ void IntegratePeaksMD::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
             FunctionFactory::Instance().createInitialized(fun_str.str());
         boost::shared_ptr<const CompositeFunction> fun =
             boost::dynamic_pointer_cast<const CompositeFunction>(ifun);
-        const auto x = wsProfile2D->x(i);
+        const auto &x = wsProfile2D->x(i);
         wsFit2D->setSharedX(i, wsProfile2D->sharedX(i));
         wsDiff2D->setSharedX(i, wsProfile2D->sharedX(i));
 
diff --git a/Framework/MDAlgorithms/src/MDNorm.cpp b/Framework/MDAlgorithms/src/MDNorm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ae857bc10064accb569b77d3096743e8505b7f9f
--- /dev/null
+++ b/Framework/MDAlgorithms/src/MDNorm.cpp
@@ -0,0 +1,1291 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+
+#include "MantidMDAlgorithms/MDNorm.h"
+#include "MantidAPI/CommonBinsValidator.h"
+#include "MantidAPI/IMDEventWorkspace.h"
+#include "MantidAPI/InstrumentValidator.h"
+#include "MantidAPI/Run.h"
+#include "MantidAPI/Sample.h"
+#include "MantidAPI/SpectrumInfo.h"
+#include "MantidDataObjects/MDHistoWorkspace.h"
+#include "MantidGeometry/Crystal/OrientedLattice.h"
+#include "MantidGeometry/Crystal/PointGroupFactory.h"
+#include "MantidGeometry/Crystal/SpaceGroupFactory.h"
+#include "MantidGeometry/Crystal/SymmetryOperationFactory.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/MDGeometry/HKL.h"
+#include "MantidGeometry/MDGeometry/MDFrameFactory.h"
+#include "MantidGeometry/MDGeometry/QSample.h"
+#include "MantidKernel/ArrayLengthValidator.h"
+#include "MantidKernel/ArrayProperty.h"
+#include "MantidKernel/CompositeValidator.h"
+#include "MantidKernel/ConfigService.h"
+#include "MantidKernel/Exception.h"
+#include "MantidKernel/Strings.h"
+#include "MantidKernel/VisibleWhenProperty.h"
+#include <boost/lexical_cast.hpp>
+
+namespace Mantid {
+namespace MDAlgorithms {
+
+using namespace Mantid::Kernel;
+using namespace Mantid::API;
+using namespace Mantid::Geometry;
+using namespace Mantid::DataObjects;
+
+namespace {
+using VectorDoubleProperty = Kernel::PropertyWithValue<std::vector<double>>;
+// function to  compare two intersections (h,k,l,Momentum) by Momentum
+bool compareMomentum(const std::array<double, 4> &v1,
+                     const std::array<double, 4> &v2) {
+  return (v1[3] < v2[3]);
+}
+
+// k=sqrt(energyToK * E)
+constexpr double energyToK = 8.0 * M_PI * M_PI *
+                             PhysicalConstants::NeutronMass *
+                             PhysicalConstants::meV * 1e-20 /
+                             (PhysicalConstants::h * PhysicalConstants::h);
+
+// compare absolute values of integers
+static bool abs_compare(int a, int b) { return (std::abs(a) < std::abs(b)); }
+} // namespace
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(MDNorm)
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Constructor
+ */
+MDNorm::MDNorm()
+    : m_normWS(), m_inputWS(), m_isRLU(false), m_UB(3, 3, true),
+      m_W(3, 3, true), m_transformation(), m_hX(), m_kX(), m_lX(), m_eX(),
+      m_hIdx(-1), m_kIdx(-1), m_lIdx(-1), m_eIdx(-1), m_numExptInfos(0),
+      m_Ei(0.0), m_diffraction(true), m_accumulate(false),
+      m_dEIntegrated(false), m_samplePos(), m_beamDir(), convention("") {}
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string MDNorm::name() const { return "MDNorm"; }
+
+/// Algorithm's version for identification. @see Algorithm::version
+int MDNorm::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string MDNorm::category() const {
+  return "MDAlgorithms\\Normalisation";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string MDNorm::summary() const {
+  return "Bins multidimensional data and calculate the normalization on the "
+         "same grid";
+}
+
+//----------------------------------------------------------------------------------------------
+/** Initialize the algorithm's properties.
+ */
+void MDNorm::init() {
+  declareProperty(make_unique<WorkspaceProperty<API::IMDEventWorkspace>>(
+                      "InputWorkspace", "", Kernel::Direction::Input),
+                  "An input MDEventWorkspace. Must be in Q_sample frame.");
+
+  // RLU and settings
+  declareProperty("RLU", true,
+                  "Use reciprocal lattice units. If false, use Q_sample");
+  setPropertyGroup("RLU", "Q projections RLU");
+
+  auto mustBe3D = boost::make_shared<Kernel::ArrayLengthValidator<double>>(3);
+  std::vector<double> Q0(3, 0.), Q1(3, 0), Q2(3, 0);
+  Q0[0] = 1.;
+  Q1[1] = 1.;
+  Q2[2] = 1.;
+
+  declareProperty(
+      make_unique<ArrayProperty<double>>("QDimension0", Q0, mustBe3D),
+      "The first Q projection axis - Default is (1,0,0)");
+  setPropertySettings("QDimension0", make_unique<Kernel::VisibleWhenProperty>(
+                                         "RLU", IS_EQUAL_TO, "1"));
+  setPropertyGroup("QDimension0", "Q projections RLU");
+
+  declareProperty(
+      make_unique<ArrayProperty<double>>("QDimension1", Q1, mustBe3D),
+      "The second Q projection axis - Default is (0,1,0)");
+  setPropertySettings("QDimension1", make_unique<Kernel::VisibleWhenProperty>(
+                                         "RLU", IS_EQUAL_TO, "1"));
+  setPropertyGroup("QDimension1", "Q projections RLU");
+
+  declareProperty(
+      make_unique<ArrayProperty<double>>("QDimension2", Q2, mustBe3D),
+      "The thirdtCalculateCover Q projection axis - Default is (0,0,1)");
+  setPropertySettings("QDimension2", make_unique<Kernel::VisibleWhenProperty>(
+                                         "RLU", IS_EQUAL_TO, "1"));
+  setPropertyGroup("QDimension2", "Q projections RLU");
+
+  // vanadium
+  auto fluxValidator = boost::make_shared<CompositeValidator>();
+  fluxValidator->add<InstrumentValidator>();
+  fluxValidator->add<CommonBinsValidator>();
+  auto solidAngleValidator = fluxValidator->clone();
+  declareProperty(
+      make_unique<WorkspaceProperty<>>(
+          "SolidAngleWorkspace", "", Direction::Input,
+          API::PropertyMode::Optional, solidAngleValidator),
+      "An input workspace containing integrated vanadium "
+      "(a measure of the solid angle).\n"
+      "Mandatory for diffraction, optional for direct geometry inelastic");
+  declareProperty(
+      make_unique<WorkspaceProperty<>>("FluxWorkspace", "", Direction::Input,
+                                       API::PropertyMode::Optional,
+                                       fluxValidator),
+      "An input workspace containing momentum dependent flux.\n"
+      "Mandatory for diffraction. No effect on direct geometry inelastic");
+  setPropertyGroup("SolidAngleWorkspace", "Vanadium normalization");
+  setPropertyGroup("FluxWorkspace", "Vanadium normalization");
+
+  // Define slicing
+  for (std::size_t i = 0; i < 6; i++) {
+    std::string propName = "Dimension" + Strings::toString(i) + "Name";
+    std::string propBinning = "Dimension" + Strings::toString(i) + "Binning";
+    std::string defaultName = "";
+    if (i < 3) {
+      defaultName = "QDimension" + Strings::toString(i);
+    }
+    declareProperty(Kernel::make_unique<PropertyWithValue<std::string>>(
+                        propName, defaultName, Direction::Input),
+                    "Name for the " + Strings::toString(i) +
+                        "th dimension. Leave blank for NONE.");
+    auto atMost3 = boost::make_shared<ArrayLengthValidator<double>>(0, 3);
+    std::vector<double> temp;
+    declareProperty(
+        Kernel::make_unique<ArrayProperty<double>>(propBinning, temp, atMost3),
+        "Binning for the " + Strings::toString(i) + "th dimension.\n" +
+            "- Leave blank for complete integration\n" +
+            "- One value is interpreted as step\n"
+            "- Two values are interpreted integration interval\n" +
+            "- Three values are interpreted as min, step, max");
+    setPropertyGroup(propName, "Binning");
+    setPropertyGroup(propBinning, "Binning");
+  }
+
+  // symmetry operations
+  declareProperty(Kernel::make_unique<PropertyWithValue<std::string>>(
+                      "SymmetryOperations", "", Direction::Input),
+                  "If specified the symmetry will be applied, "
+                  "can be space group name, point group name, or list "
+                  "individual symmetries.");
+
+  // temporary workspaces
+  declareProperty(make_unique<WorkspaceProperty<IMDHistoWorkspace>>(
+                      "TemporaryDataWorkspace", "", Direction::Input,
+                      PropertyMode::Optional),
+                  "An input MDHistoWorkspace used to accumulate data from "
+                  "multiple MDEventWorkspaces. If unspecified a blank "
+                  "MDHistoWorkspace will be created.");
+  declareProperty(make_unique<WorkspaceProperty<IMDHistoWorkspace>>(
+                      "TemporaryNormalizationWorkspace", "", Direction::Input,
+                      PropertyMode::Optional),
+                  "An input MDHistoWorkspace used to accumulate normalization "
+                  "from multiple MDEventWorkspaces. If unspecified a blank "
+                  "MDHistoWorkspace will be created.");
+  setPropertyGroup("TemporaryDataWorkspace", "Temporary workspaces");
+  setPropertyGroup("TemporaryNormalizationWorkspace", "Temporary workspaces");
+
+  declareProperty(make_unique<WorkspaceProperty<API::Workspace>>(
+                      "OutputWorkspace", "", Kernel::Direction::Output),
+                  "A name for the normalized output MDHistoWorkspace.");
+  declareProperty(make_unique<WorkspaceProperty<API::Workspace>>(
+                      "OutputDataWorkspace", "", Kernel::Direction::Output),
+                  "A name for the output data MDHistoWorkspace.");
+  declareProperty(make_unique<WorkspaceProperty<Workspace>>(
+                      "OutputNormalizationWorkspace", "", Direction::Output),
+                  "A name for the output normalization MDHistoWorkspace.");
+}
+
+//----------------------------------------------------------------------------------------------
+/// Validate the input workspace @see Algorithm::validateInputs
+std::map<std::string, std::string> MDNorm::validateInputs() {
+  std::map<std::string, std::string> errorMessage;
+
+  // Check for input workspace frame
+  Mantid::API::IMDEventWorkspace_sptr inputWS =
+      this->getProperty("InputWorkspace");
+  if (inputWS->getNumDims() < 3) {
+    errorMessage.emplace("InputWorkspace",
+                         "The input workspace must be at least 3D");
+  } else {
+    for (size_t i = 0; i < 3; i++) {
+      if (inputWS->getDimension(i)->getMDFrame().name() !=
+          Mantid::Geometry::QSample::QSampleName) {
+        errorMessage.emplace("InputWorkspace",
+                             "The input workspace must be in Q_sample");
+      }
+    }
+  }
+  // Check if the vanadium is available for diffraction
+  bool diffraction = true;
+  if ((inputWS->getNumDims() > 3) &&
+      (inputWS->getDimension(3)->getName() == "DeltaE")) {
+    diffraction = false;
+  }
+  if (diffraction) {
+    API::MatrixWorkspace_const_sptr solidAngleWS =
+        getProperty("SolidAngleWorkspace");
+    API::MatrixWorkspace_const_sptr fluxWS = getProperty("FluxWorkspace");
+    if (solidAngleWS == nullptr) {
+      errorMessage.emplace("SolidAngleWorkspace",
+                           "SolidAngleWorkspace is required for diffraction");
+    }
+    if (fluxWS == nullptr) {
+      errorMessage.emplace("FluxWorkspace",
+                           "FluxWorkspace is required for diffraction");
+    }
+  }
+  // Check for property MDNorm_low and MDNorm_high
+  size_t nExperimentInfos = inputWS->getNumExperimentInfo();
+  if (nExperimentInfos == 0) {
+    errorMessage.emplace("InputWorkspace",
+                         "There must be at least one experiment info");
+  } else {
+    for (size_t iExpInfo = 0; iExpInfo < nExperimentInfos; iExpInfo++) {
+      auto &currentExptInfo =
+          *(inputWS->getExperimentInfo(static_cast<uint16_t>(iExpInfo)));
+      if (!currentExptInfo.run().hasProperty("MDNorm_low")) {
+        errorMessage.emplace("InputWorkspace", "Missing MDNorm_low log. Please "
+                                               "use CropWorkspaceForMDNorm "
+                                               "before converting to MD");
+      }
+      if (!currentExptInfo.run().hasProperty("MDNorm_high")) {
+        errorMessage.emplace("InputWorkspace",
+                             "Missing MDNorm_high log. Please use "
+                             "CropWorkspaceForMDNorm before converting to MD");
+      }
+    }
+  }
+  // check projections and UB
+  if (getProperty("RLU")) {
+    DblMatrix W = DblMatrix(3, 3);
+    std::vector<double> Q0Basis = getProperty("QDimension0");
+    std::vector<double> Q1Basis = getProperty("QDimension1");
+    std::vector<double> Q2Basis = getProperty("QDimension2");
+    W.setColumn(0, Q0Basis);
+    W.setColumn(1, Q1Basis);
+    W.setColumn(2, Q2Basis);
+    if (fabs(W.determinant()) < 1e-5) {
+      errorMessage.emplace("QDimension0",
+                           "The projection dimensions are coplanar or zero");
+      errorMessage.emplace("QDimension1",
+                           "The projection dimensions are coplanar or zero");
+      errorMessage.emplace("QDimension2",
+                           "The projection dimensions are coplanar or zero");
+    }
+    if (!inputWS->getExperimentInfo(0)->sample().hasOrientedLattice()) {
+      errorMessage.emplace("InputWorkspace",
+                           "There is no oriented lattice "
+                           "associated with the input workspace. "
+                           "Use SetUB algorithm");
+    }
+  }
+  // check dimension names
+  std::vector<std::string> originalDimensionNames;
+  for (size_t i = 3; i < inputWS->getNumDims(); i++) {
+    originalDimensionNames.push_back(inputWS->getDimension(i)->getName());
+  }
+  originalDimensionNames.push_back("QDimension0");
+  originalDimensionNames.push_back("QDimension1");
+  originalDimensionNames.push_back("QDimension2");
+  std::vector<std::string> selectedDimensions;
+  for (std::size_t i = 0; i < 6; i++) {
+    std::string propName = "Dimension" + Strings::toString(i) + "Name";
+    std::string dimName = getProperty(propName);
+    std::string binningName = "Dimension" + Strings::toString(i) + "Binning";
+    std::vector<double> binning = getProperty(binningName);
+    if (!dimName.empty()) {
+      auto it = std::find(originalDimensionNames.begin(),
+                          originalDimensionNames.end(), dimName);
+      if (it == originalDimensionNames.end()) {
+        errorMessage.emplace(propName,
+                             "Name '" + dimName +
+                                 "' is not one of the "
+                                 "original workspace names or a Q dimension");
+      } else {
+        // make sure dimension is unique
+        auto itSel = std::find(selectedDimensions.begin(),
+                               selectedDimensions.end(), dimName);
+        if (itSel == selectedDimensions.end()) {
+          selectedDimensions.push_back(dimName);
+        } else {
+          errorMessage.emplace(propName,
+                               "Name '" + dimName + "' was already selected");
+        }
+      }
+    } else {
+      if (!binning.empty()) {
+        errorMessage.emplace(
+            binningName,
+            "There should be no binning if the dimension name is empty");
+      }
+    }
+  }
+  // since Q dimensions can be non - orthogonal, all must be present
+  if ((std::find(selectedDimensions.begin(), selectedDimensions.end(),
+                 "QDimension0") == selectedDimensions.end()) ||
+      (std::find(selectedDimensions.begin(), selectedDimensions.end(),
+                 "QDimension1") == selectedDimensions.end()) ||
+      (std::find(selectedDimensions.begin(), selectedDimensions.end(),
+                 "QDimension2") == selectedDimensions.end())) {
+    for (std::size_t i = 0; i < 6; i++) {
+      std::string propName = "Dimension" + Strings::toString(i) + "Name";
+      errorMessage.emplace(
+          propName,
+          "All of QDimension0, QDimension1, QDimension2 must be present");
+    }
+  }
+  // symmetry operations
+  std::string symOps = this->getProperty("SymmetryOperations");
+  if (!symOps.empty()) {
+    bool isSpaceGroup =
+        Geometry::SpaceGroupFactory::Instance().isSubscribed(symOps);
+    bool isPointGroup =
+        Geometry::PointGroupFactory::Instance().isSubscribed(symOps);
+    if (!isSpaceGroup && !isPointGroup) {
+      try {
+        Geometry::SymmetryOperationFactory::Instance().createSymOps(symOps);
+      } catch (const Mantid::Kernel::Exception::ParseError &) {
+        errorMessage.emplace("SymmetryOperations",
+                             "The input is not a space group, a point group, "
+                             "or a list of symmetry operations");
+      }
+    }
+  }
+  return errorMessage;
+}
+
+//----------------------------------------------------------------------------------------------
+/** Execute the algorithm.
+ */
+void MDNorm::exec() {
+  convention = Kernel::ConfigService::Instance().getString("Q.convention");
+  // symmetry operations
+  std::string symOps = this->getProperty("SymmetryOperations");
+  std::vector<Geometry::SymmetryOperation> symmetryOps;
+  if (symOps.empty()) {
+    symOps = "x,y,z";
+  }
+  if (Geometry::SpaceGroupFactory::Instance().isSubscribed(symOps)) {
+    auto spaceGroup =
+        Geometry::SpaceGroupFactory::Instance().createSpaceGroup(symOps);
+    auto pointGroup = spaceGroup->getPointGroup();
+    symmetryOps = pointGroup->getSymmetryOperations();
+  } else if (Geometry::PointGroupFactory::Instance().isSubscribed(symOps)) {
+    auto pointGroup =
+        Geometry::PointGroupFactory::Instance().createPointGroup(symOps);
+    symmetryOps = pointGroup->getSymmetryOperations();
+  } else {
+    symmetryOps =
+        Geometry::SymmetryOperationFactory::Instance().createSymOps(symOps);
+  }
+  g_log.debug() << "Symmetry operations\n";
+  for (auto so : symmetryOps) {
+    g_log.debug() << so.identifier() << "\n";
+  }
+  m_numSymmOps = symmetryOps.size();
+
+  m_isRLU = getProperty("RLU");
+  // get the workspaces
+  m_inputWS = this->getProperty("InputWorkspace");
+  const auto &exptInfoZero = *(m_inputWS->getExperimentInfo(0));
+  auto source = exptInfoZero.getInstrument()->getSource();
+  auto sample = exptInfoZero.getInstrument()->getSample();
+  if (source == nullptr || sample == nullptr) {
+    throw Kernel::Exception::InstrumentDefinitionError(
+        "Instrument not sufficiently defined: failed to get source and/or "
+        "sample");
+  }
+  m_samplePos = sample->getPos();
+  m_beamDir = m_samplePos - source->getPos();
+  m_beamDir.normalize();
+  if ((m_inputWS->getNumDims() > 3) &&
+      (m_inputWS->getDimension(3)->getName() == "DeltaE")) {
+    m_diffraction = false;
+    if (exptInfoZero.run().hasProperty("Ei")) {
+      Kernel::Property *eiprop = exptInfoZero.run().getProperty("Ei");
+      m_Ei = boost::lexical_cast<double>(eiprop->value());
+      if (m_Ei <= 0) {
+        throw std::invalid_argument(
+            "Ei stored in the workspace is not positive");
+      }
+    } else {
+      throw std::invalid_argument("Could not find Ei value in the workspace.");
+    }
+  }
+  auto outputDataWS = binInputWS(symmetryOps);
+
+  createNormalizationWS(*outputDataWS);
+  this->setProperty("OutputNormalizationWorkspace", m_normWS);
+  this->setProperty("OutputDataWorkspace", outputDataWS);
+
+  m_numExptInfos = outputDataWS->getNumExperimentInfo();
+  // loop over all experiment infos
+  for (uint16_t expInfoIndex = 0; expInfoIndex < m_numExptInfos;
+       expInfoIndex++) {
+    // Check for other dimensions if we could measure anything in the original
+    // data
+    bool skipNormalization = false;
+    const std::vector<coord_t> otherValues =
+        getValuesFromOtherDimensions(skipNormalization, expInfoIndex);
+
+    cacheDimensionXValues();
+
+    if (!skipNormalization) {
+      size_t symmOpsIndex = 0;
+      for (const auto &so : symmetryOps) {
+        calculateNormalization(otherValues, so, expInfoIndex, symmOpsIndex);
+        symmOpsIndex++;
+      }
+
+    } else {
+      g_log.warning("Binning limits are outside the limits of the MDWorkspace. "
+                    "Not applying normalization.");
+    }
+    // if more than one experiment info, keep accumulating
+    m_accumulate = true;
+  }
+  IAlgorithm_sptr divideMD = createChildAlgorithm("DivideMD", 0.99, 1.);
+  divideMD->setProperty("LHSWorkspace", outputDataWS);
+  divideMD->setProperty("RHSWorkspace", m_normWS);
+  divideMD->setPropertyValue("OutputWorkspace",
+                             getPropertyValue("OutputWorkspace"));
+  divideMD->executeAsChildAlg();
+  API::IMDWorkspace_sptr out = divideMD->getProperty("OutputWorkspace");
+  this->setProperty("OutputWorkspace", out);
+}
+
+/**
+ *
+ * @param projection - a vector with 3 elements, containing a
+ *   description of the projection ("1,-1,0" for "[H,-H,0]")
+ * @return string containing the name
+ */
+std::string MDNorm::QDimensionName(std::vector<double> projection) {
+  std::vector<double>::iterator result;
+  result = std::max_element(projection.begin(), projection.end(), abs_compare);
+  std::vector<char> symbol{'H', 'K', 'L'};
+  char character = symbol[std::distance(projection.begin(), result)];
+  std::stringstream name;
+  name << "[";
+  for (size_t i = 0; i < 3; i++) {
+    if (projection[i] == 0) {
+      name << "0";
+    } else if (projection[i] == 1) {
+      name << character;
+    } else if (projection[i] == -1) {
+      name << "-" << character;
+    } else {
+      name << std::defaultfloat << std::setprecision(3) << projection[i]
+           << character;
+    }
+    if (i != 2) {
+      name << ",";
+    }
+  }
+  name << "]";
+  return name.str();
+}
+
+/**
+ * Calculate binning parameters
+ * @return map of parameters to be passed to BinMD (non axis-aligned)
+ */
+std::map<std::string, std::string> MDNorm::getBinParameters() {
+  std::map<std::string, std::string> parameters;
+  std::stringstream extents;
+  std::stringstream bins;
+  std::vector<std::string> originalDimensionNames;
+  originalDimensionNames.push_back("QDimension0");
+  originalDimensionNames.push_back("QDimension1");
+  originalDimensionNames.push_back("QDimension2");
+  for (size_t i = 3; i < m_inputWS->getNumDims(); i++) {
+    originalDimensionNames.push_back(m_inputWS->getDimension(i)->getName());
+  }
+
+  if (m_isRLU) {
+    m_Q0Basis = getProperty("QDimension0");
+    m_Q1Basis = getProperty("QDimension1");
+    m_Q2Basis = getProperty("QDimension2");
+    m_UB =
+        m_inputWS->getExperimentInfo(0)->sample().getOrientedLattice().getUB() *
+        2 * M_PI;
+  }
+
+  std::vector<double> W(m_Q0Basis);
+  W.insert(W.end(), m_Q1Basis.begin(), m_Q1Basis.end());
+  W.insert(W.end(), m_Q2Basis.begin(), m_Q2Basis.end());
+  m_W = DblMatrix(W);
+  m_W.Transpose();
+
+  // Find maximum Q
+  auto &exptInfo0 = *(m_inputWS->getExperimentInfo(static_cast<uint16_t>(0)));
+  auto upperLimitsVector =
+      (*(dynamic_cast<Kernel::PropertyWithValue<std::vector<double>> *>(
+          exptInfo0.getLog("MDNorm_high"))))();
+  double maxQ;
+  if (m_diffraction) {
+    maxQ = 2. * (*std::max_element(upperLimitsVector.begin(),
+                                   upperLimitsVector.end()));
+  } else {
+    double Ei;
+    double maxDE =
+        *std::max_element(upperLimitsVector.begin(), upperLimitsVector.end());
+    auto loweLimitsVector =
+        (*(dynamic_cast<Kernel::PropertyWithValue<std::vector<double>> *>(
+            exptInfo0.getLog("MDNorm_low"))))();
+    double minDE =
+        *std::min_element(loweLimitsVector.begin(), loweLimitsVector.end());
+    if (exptInfo0.run().hasProperty("Ei")) {
+      Kernel::Property *eiprop = exptInfo0.run().getProperty("Ei");
+      Ei = boost::lexical_cast<double>(eiprop->value());
+      if (Ei <= 0) {
+        throw std::invalid_argument(
+            "Ei stored in the workspace is not positive");
+      }
+    } else {
+      throw std::invalid_argument("Could not find Ei value in the workspace.");
+    }
+    const double energyToK = 8.0 * M_PI * M_PI *
+                             PhysicalConstants::NeutronMass *
+                             PhysicalConstants::meV * 1e-20 /
+                             (PhysicalConstants::h * PhysicalConstants::h);
+    double ki = std::sqrt(energyToK * Ei);
+    double kfmin = std::sqrt(energyToK * (Ei - minDE));
+    double kfmax = std::sqrt(energyToK * (Ei - maxDE));
+
+    maxQ = ki + std::max(kfmin, kfmax);
+  }
+  size_t basisVectorIndex = 0;
+  std::vector<coord_t> transformation;
+  for (std::size_t i = 0; i < 6; i++) {
+    std::string propName = "Dimension" + Strings::toString(i) + "Name";
+    std::string binningName = "Dimension" + Strings::toString(i) + "Binning";
+    std::string dimName = getProperty(propName);
+    std::vector<double> binning = getProperty(binningName);
+    std::string bv = "BasisVector";
+    if (!dimName.empty()) {
+      std::string property = bv + Strings::toString(basisVectorIndex);
+      std::stringstream propertyValue;
+      propertyValue << dimName;
+      // get the index in the original workspace
+      auto dimIndex =
+          std::distance(originalDimensionNames.begin(),
+                        std::find(originalDimensionNames.begin(),
+                                  originalDimensionNames.end(), dimName));
+      auto dimension = m_inputWS->getDimension(dimIndex);
+      propertyValue << "," << dimension->getMDUnits().getUnitLabel().ascii();
+      for (size_t j = 0; j < originalDimensionNames.size(); j++) {
+        if (j == static_cast<size_t>(dimIndex)) {
+          propertyValue << ",1";
+          transformation.push_back(1.);
+        } else {
+          propertyValue << ",0";
+          transformation.push_back(0.);
+        }
+      }
+      parameters.emplace(property, propertyValue.str());
+      // get the extents an number of bins
+      coord_t dimMax = dimension->getMaximum();
+      coord_t dimMin = dimension->getMinimum();
+      if (m_isRLU) {
+        Mantid::Geometry::OrientedLattice ol;
+        ol.setUB(m_UB * m_W); // note that this is already multiplied by 2Pi
+        if (dimIndex == 0) {
+          dimMax = static_cast<coord_t>(ol.a() * maxQ);
+          dimMin = -dimMax;
+        } else if (dimIndex == 1) {
+          dimMax = static_cast<coord_t>(ol.b() * maxQ);
+          dimMin = -dimMax;
+        } else if (dimIndex == 2) {
+          dimMax = static_cast<coord_t>(ol.c() * maxQ);
+          dimMin = -dimMax;
+        }
+      }
+      if (binning.size() == 0) {
+        // only one bin, integrating from min to max
+        extents << dimMin << "," << dimMax << ",";
+        bins << 1 << ",";
+      } else if (binning.size() == 2) {
+        // only one bin, integrating from min to max
+        extents << binning[0] << "," << binning[1] << ",";
+        bins << 1 << ",";
+      } else if (binning.size() == 1) {
+        auto step = binning[0];
+        double nsteps = (dimMax - dimMin) / step;
+        if (nsteps + 1 - std::ceil(nsteps) >= 1e-4) {
+          nsteps = std::ceil(nsteps);
+        } else {
+          nsteps = std::floor(nsteps);
+        }
+        bins << static_cast<int>(nsteps) << ",";
+        extents << dimMin << "," << dimMin + nsteps * step << ",";
+      } else if (binning.size() == 3) {
+        dimMin = static_cast<coord_t>(binning[0]);
+        auto step = binning[1];
+        dimMax = static_cast<coord_t>(binning[2]);
+        double nsteps = (dimMax - dimMin) / step;
+        if (nsteps + 1 - std::ceil(nsteps) >= 1e-4) {
+          nsteps = std::ceil(nsteps);
+        } else {
+          nsteps = std::floor(nsteps);
+        }
+        bins << static_cast<int>(nsteps) << ",";
+        extents << dimMin << "," << dimMin + nsteps * step << ",";
+      }
+      basisVectorIndex++;
+    }
+  }
+  parameters.emplace("OutputExtents", extents.str());
+  parameters.emplace("OutputBins", bins.str());
+  m_transformation = Mantid::Kernel::Matrix<coord_t>(
+      transformation,
+      static_cast<size_t>((transformation.size()) / m_inputWS->getNumDims()),
+      m_inputWS->getNumDims());
+  return parameters;
+}
+
+/**
+ * Create & cached the normalization workspace
+ * @param dataWS The binned workspace that will be used for the data
+ */
+void MDNorm::createNormalizationWS(
+    const DataObjects::MDHistoWorkspace &dataWS) {
+  // Copy the MDHisto workspace, and change signals and errors to 0.
+  boost::shared_ptr<IMDHistoWorkspace> tmp =
+      this->getProperty("TemporaryNormalizationWorkspace");
+  m_normWS = boost::dynamic_pointer_cast<MDHistoWorkspace>(tmp);
+  if (!m_normWS) {
+    m_normWS = dataWS.clone();
+    m_normWS->setTo(0., 0., 0.);
+  } else {
+    m_accumulate = true;
+  }
+}
+
+/**
+ * Runs the BinMD algorithm on the input to provide the output workspace
+ * All slicing algorithm properties are passed along
+ * @return MDHistoWorkspace as a result of the binning
+ */
+DataObjects::MDHistoWorkspace_sptr
+MDNorm::binInputWS(std::vector<Geometry::SymmetryOperation> symmetryOps) {
+  Mantid::API::IMDHistoWorkspace_sptr tempDataWS =
+      this->getProperty("TemporaryDataWorkspace");
+  Mantid::API::Workspace_sptr outputWS;
+  std::map<std::string, std::string> parameters = getBinParameters();
+  double soIndex = 0;
+  std::vector<size_t> qDimensionIndices;
+  for (auto so : symmetryOps) {
+    // calculate dimensions for binning
+    DblMatrix soMatrix(3, 3);
+    auto v = so.transformHKL(V3D(1, 0, 0));
+    soMatrix.setColumn(0, v);
+    v = so.transformHKL(V3D(0, 1, 0));
+    soMatrix.setColumn(1, v);
+    v = so.transformHKL(V3D(0, 0, 1));
+    soMatrix.setColumn(2, v);
+
+    DblMatrix Qtransform;
+    if (m_isRLU) {
+      Qtransform = m_UB * soMatrix * m_W;
+    } else {
+      Qtransform = soMatrix * m_W;
+    }
+
+    // bin the data
+    double fraction = 1. / static_cast<double>(symmetryOps.size());
+    IAlgorithm_sptr binMD = createChildAlgorithm(
+        "BinMD", soIndex * 0.3 * fraction, (soIndex + 1) * 0.3 * fraction);
+    binMD->setPropertyValue("AxisAligned", "0");
+    binMD->setProperty("InputWorkspace", m_inputWS);
+    binMD->setProperty("TemporaryDataWorkspace", tempDataWS);
+    binMD->setPropertyValue("NormalizeBasisVectors", "0");
+    binMD->setPropertyValue("OutputWorkspace",
+                            getPropertyValue("OutputDataWorkspace"));
+    // set binning properties
+    size_t qindex = 0;
+    for (auto const &p : parameters) {
+      auto key = p.first;
+      auto value = p.second;
+
+      std::stringstream basisVector;
+      std::vector<double> projection(m_inputWS->getNumDims(), 0.);
+      if (value.find("QDimension0") != std::string::npos) {
+        m_hIdx = qindex;
+        if (!m_isRLU) {
+          projection[0] = 1.;
+          basisVector << "Q_sample_x,A^{-1}";
+        } else {
+          qDimensionIndices.push_back(qindex);
+          projection[0] = Qtransform[0][0];
+          projection[1] = Qtransform[1][0];
+          projection[2] = Qtransform[2][0];
+          basisVector << QDimensionName(m_Q0Basis) << ", r.l.u.";
+        }
+      } else if (value.find("QDimension1") != std::string::npos) {
+        m_kIdx = qindex;
+        if (!m_isRLU) {
+          projection[1] = 1.;
+          basisVector << "Q_sample_y,A^{-1}";
+        } else {
+          qDimensionIndices.push_back(qindex);
+          projection[0] = Qtransform[0][1];
+          projection[1] = Qtransform[1][1];
+          projection[2] = Qtransform[2][1];
+          basisVector << QDimensionName(m_Q1Basis) << ", r.l.u.";
+        }
+      } else if (value.find("QDimension2") != std::string::npos) {
+        m_lIdx = qindex;
+        if (!m_isRLU) {
+          projection[2] = 1.;
+          basisVector << "Q_sample_z,A^{-1}";
+        } else {
+          qDimensionIndices.push_back(qindex);
+          projection[0] = Qtransform[0][2];
+          projection[1] = Qtransform[1][2];
+          projection[2] = Qtransform[2][2];
+          basisVector << QDimensionName(m_Q2Basis) << ", r.l.u.";
+        }
+      } else if (value.find("DeltaE") != std::string::npos) {
+        m_eIdx = qindex;
+        m_dEIntegrated = false;
+      }
+      if (!basisVector.str().empty()) {
+        for (auto const &proji : projection) {
+          basisVector << "," << proji;
+        }
+        value = basisVector.str();
+      }
+      if (value.find("DeltaE") != std::string::npos) {
+        m_eIdx = qindex;
+      }
+      g_log.debug() << "Binning parameter " << key << " value: " << value
+                    << "\n";
+      binMD->setPropertyValue(key, value);
+      qindex++;
+    }
+    // execute algorithm
+    binMD->executeAsChildAlg();
+    outputWS = binMD->getProperty("OutputWorkspace");
+
+    // set the temporary workspace to be the output workspace, so it keeps
+    // adding different symmetries
+    tempDataWS = boost::dynamic_pointer_cast<MDHistoWorkspace>(outputWS);
+    soIndex += 1;
+  }
+
+  auto outputMDHWS = boost::dynamic_pointer_cast<MDHistoWorkspace>(outputWS);
+  // set MDUnits for Q dimensions
+  if (m_isRLU) {
+    Mantid::Geometry::MDFrameArgument argument(Mantid::Geometry::HKL::HKLName,
+                                               "r.l.u.");
+    auto mdFrameFactory = Mantid::Geometry::makeMDFrameFactoryChain();
+    Mantid::Geometry::MDFrame_uptr hklFrame = mdFrameFactory->create(argument);
+    for (size_t i : qDimensionIndices) {
+      auto mdHistoDimension = boost::const_pointer_cast<
+          Mantid::Geometry::MDHistoDimension>(
+          boost::dynamic_pointer_cast<const Mantid::Geometry::MDHistoDimension>(
+              outputMDHWS->getDimension(i)));
+      mdHistoDimension->setMDFrame(*hklFrame);
+    }
+  }
+
+  outputMDHWS->setDisplayNormalization(Mantid::API::NoNormalization);
+  return outputMDHWS;
+}
+
+/**
+ * Retrieve logged values from non-HKL dimensions
+ * @param skipNormalization [InOut] Updated to false if any values are outside
+ * range measured by input workspace
+ * @param expInfoIndex current experiment info index
+ * @return A vector of values from other dimensions to be include in normalized
+ * MD position calculation
+ */
+std::vector<coord_t>
+MDNorm::getValuesFromOtherDimensions(bool &skipNormalization,
+                                     uint16_t expInfoIndex) const {
+  const auto &currentRun = m_inputWS->getExperimentInfo(expInfoIndex)->run();
+
+  std::vector<coord_t> otherDimValues;
+  for (size_t i = 3; i < m_inputWS->getNumDims(); i++) {
+    const auto dimension = m_inputWS->getDimension(i);
+    coord_t inputDimMin = static_cast<float>(dimension->getMinimum());
+    coord_t inputDimMax = static_cast<float>(dimension->getMaximum());
+    coord_t outputDimMin(0), outputDimMax(0);
+    bool isIntegrated = true;
+
+    for (size_t j = 0; j < m_transformation.numRows(); j++) {
+      if (m_transformation[j][i] == 1) {
+        isIntegrated = false;
+        outputDimMin = m_normWS->getDimension(j)->getMinimum();
+        outputDimMax = m_normWS->getDimension(j)->getMaximum();
+      }
+    }
+    if (dimension->getName() == "DeltaE") {
+      if ((inputDimMax < outputDimMin) || (inputDimMin > outputDimMax)) {
+        skipNormalization = true;
+      }
+    } else {
+      coord_t value = static_cast<coord_t>(currentRun.getLogAsSingleValue(
+          dimension->getName(), Mantid::Kernel::Math::TimeAveragedMean));
+      otherDimValues.push_back(value);
+      if (value < inputDimMin || value > inputDimMax) {
+        skipNormalization = true;
+      }
+      if ((!isIntegrated) && (value < outputDimMin || value > outputDimMax)) {
+        skipNormalization = true;
+      }
+    }
+  }
+  return otherDimValues;
+}
+
+/**
+ * Stores the X values from each H,K,L, and optionally DeltaE dimension as
+ * member variables
+ */
+void MDNorm::cacheDimensionXValues() {
+  auto &hDim = *m_normWS->getDimension(m_hIdx);
+  m_hX.resize(hDim.getNBoundaries());
+  for (size_t i = 0; i < m_hX.size(); ++i) {
+    m_hX[i] = hDim.getX(i);
+  }
+  auto &kDim = *m_normWS->getDimension(m_kIdx);
+  m_kX.resize(kDim.getNBoundaries());
+  for (size_t i = 0; i < m_kX.size(); ++i) {
+    m_kX[i] = kDim.getX(i);
+  }
+
+  auto &lDim = *m_normWS->getDimension(m_lIdx);
+  m_lX.resize(lDim.getNBoundaries());
+  for (size_t i = 0; i < m_lX.size(); ++i) {
+    m_lX[i] = lDim.getX(i);
+  }
+
+  if ((!m_diffraction) && (!m_dEIntegrated)) {
+    // NOTE: store k final instead
+    auto &eDim = *m_normWS->getDimension(m_eIdx);
+    m_eX.resize(eDim.getNBoundaries());
+    for (size_t i = 0; i < m_eX.size(); ++i) {
+      double temp = m_Ei - eDim.getX(i);
+      temp = std::max(temp, 0.);
+      m_eX[i] = std::sqrt(energyToK * temp);
+    }
+  }
+}
+
+/**
+ * Computed the normalization for the input workspace. Results are stored in
+ * m_normWS
+ * @param otherValues - values for dimensions other than Q or DeltaE
+ * @param so - symmetry operation
+ * @param expInfoIndex - current experiment info index
+ * @param soIndex - the index of symmetry operation (for progress purposes)
+ */
+void MDNorm::calculateNormalization(const std::vector<coord_t> &otherValues,
+                                    Geometry::SymmetryOperation so,
+                                    uint16_t expInfoIndex, size_t soIndex) {
+  const auto &currentExptInfo = *(m_inputWS->getExperimentInfo(expInfoIndex));
+  std::vector<double> lowValues, highValues;
+  auto *lowValuesLog = dynamic_cast<VectorDoubleProperty *>(
+      currentExptInfo.getLog("MDNorm_low"));
+  lowValues = (*lowValuesLog)();
+  auto *highValuesLog = dynamic_cast<VectorDoubleProperty *>(
+      currentExptInfo.getLog("MDNorm_high"));
+  highValues = (*highValuesLog)();
+
+  DblMatrix R = currentExptInfo.run().getGoniometerMatrix();
+  DblMatrix soMatrix(3, 3);
+  auto v = so.transformHKL(V3D(1, 0, 0));
+  soMatrix.setColumn(0, v);
+  v = so.transformHKL(V3D(0, 1, 0));
+  soMatrix.setColumn(1, v);
+  v = so.transformHKL(V3D(0, 0, 1));
+  soMatrix.setColumn(2, v);
+  soMatrix.Invert();
+  DblMatrix Qtransform = R * m_UB * soMatrix * m_W;
+  Qtransform.Invert();
+  const double protonCharge = currentExptInfo.run().getProtonCharge();
+  const auto &spectrumInfo = currentExptInfo.spectrumInfo();
+
+  // Mappings
+  const int64_t ndets = static_cast<int64_t>(spectrumInfo.size());
+  detid2index_map fluxDetToIdx;
+  detid2index_map solidAngDetToIdx;
+  bool haveSA = false;
+  API::MatrixWorkspace_const_sptr solidAngleWS =
+      getProperty("SolidAngleWorkspace");
+  API::MatrixWorkspace_const_sptr integrFlux = getProperty("FluxWorkspace");
+  if (solidAngleWS != nullptr) {
+    haveSA = true;
+    solidAngDetToIdx = solidAngleWS->getDetectorIDToWorkspaceIndexMap();
+  }
+  if (m_diffraction) {
+    fluxDetToIdx = integrFlux->getDetectorIDToWorkspaceIndexMap();
+  }
+
+  const size_t vmdDims = (m_diffraction) ? 3 : 4;
+  std::vector<std::atomic<signal_t>> signalArray(m_normWS->getNPoints());
+  std::vector<std::array<double, 4>> intersections;
+  std::vector<double> xValues, yValues;
+  std::vector<coord_t> pos, posNew;
+
+  double progStep = 0.7 / static_cast<double>(m_numExptInfos * m_numSymmOps);
+  double progIndex = static_cast<double>(soIndex + expInfoIndex * m_numSymmOps);
+  auto prog =
+      make_unique<API::Progress>(this, 0.3 + progStep * progIndex,
+                                 0.3 + progStep * (1. + progIndex), ndets);
+
+  bool safe = true;
+  if (m_diffraction) {
+    safe = Kernel::threadSafe(*integrFlux);
+  }
+  // cppcheck-suppress syntaxError
+PRAGMA_OMP(parallel for private(intersections, xValues, yValues, pos, posNew) if (safe))
+for (int64_t i = 0; i < ndets; i++) {
+  PARALLEL_START_INTERUPT_REGION
+
+  if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i) ||
+      spectrumInfo.isMasked(i)) {
+    continue;
+  }
+
+  const auto &detector = spectrumInfo.detector(i);
+  double theta = detector.getTwoTheta(m_samplePos, m_beamDir);
+  double phi = detector.getPhi();
+  // If the dtefctor is a group, this should be the ID of the first detector
+  const auto detID = detector.getID();
+
+  // Intersections
+  this->calculateIntersections(intersections, theta, phi, Qtransform,
+                               lowValues[i], highValues[i]);
+  if (intersections.empty())
+    continue;
+  // Get solid angle for this contribution
+  double solid = protonCharge;
+  if (haveSA) {
+    solid =
+        solidAngleWS->y(solidAngDetToIdx.find(detID)->second)[0] * protonCharge;
+  }
+
+  if (m_diffraction) {
+    // -- calculate integrals for the intersection --
+    // momentum values at intersections
+    auto intersectionsBegin = intersections.begin();
+    // copy momenta to xValues
+    xValues.resize(intersections.size());
+    yValues.resize(intersections.size());
+    auto x = xValues.begin();
+    for (auto it = intersectionsBegin; it != intersections.end(); ++it, ++x) {
+      *x = (*it)[3];
+    }
+    // get the flux spetrum number
+    size_t wsIdx = fluxDetToIdx.find(detID)->second;
+    // calculate integrals at momenta from xValues by interpolating between
+    // points in spectrum sp
+    // of workspace integrFlux. The result is stored in yValues
+    calcIntegralsForIntersections(xValues, *integrFlux, wsIdx, yValues);
+  }
+
+  // Compute final position in HKL
+  // pre-allocate for efficiency and copy non-hkl dim values into place
+  pos.resize(vmdDims + otherValues.size());
+  std::copy(otherValues.begin(), otherValues.end(), pos.begin() + vmdDims);
+
+  auto intersectionsBegin = intersections.begin();
+  for (auto it = intersectionsBegin + 1; it != intersections.end(); ++it) {
+    const auto &curIntSec = *it;
+    const auto &prevIntSec = *(it - 1);
+    // the full vector isn't used so compute only what is necessary
+    double delta, eps;
+    if (m_diffraction) {
+      delta = curIntSec[3] - prevIntSec[3];
+      eps = 1e-7;
+    } else {
+      delta = (curIntSec[3] * curIntSec[3] - prevIntSec[3] * prevIntSec[3]) /
+              energyToK;
+      eps = 1e-10;
+    }
+    if (delta < eps)
+      continue; // Assume zero contribution if difference is small
+    // Average between two intersections for final position
+    std::transform(curIntSec.data(), curIntSec.data() + vmdDims,
+                   prevIntSec.data(), pos.begin(),
+                   [](const double rhs, const double lhs) {
+                     return static_cast<coord_t>(0.5 * (rhs + lhs));
+                   });
+    signal_t signal;
+    if (m_diffraction) {
+      // index of the current intersection
+      size_t k = static_cast<size_t>(std::distance(intersectionsBegin, it));
+      // signal = integral between two consecutive intersections
+      signal = (yValues[k] - yValues[k - 1]) * solid;
+    } else {
+      // transform kf to energy transfer
+      pos[3] = static_cast<coord_t>(m_Ei - pos[3] * pos[3] / energyToK);
+      // signal = energy distance between two consecutive intersections *solid
+      // angle *PC
+      signal = solid * delta;
+    }
+    m_transformation.multiplyPoint(pos, posNew);
+    size_t linIndex = m_normWS->getLinearIndexAtCoord(posNew.data());
+    if (linIndex == size_t(-1))
+      continue;
+    Mantid::Kernel::AtomicOp(signalArray[linIndex], signal,
+                             std::plus<signal_t>());
+  }
+
+  prog->report();
+
+  PARALLEL_END_INTERUPT_REGION
+}
+PARALLEL_CHECK_INTERUPT_REGION
+if (m_accumulate) {
+  std::transform(
+      signalArray.cbegin(), signalArray.cend(), m_normWS->getSignalArray(),
+      m_normWS->getSignalArray(),
+      [](const std::atomic<signal_t> &a, const signal_t &b) { return a + b; });
+} else {
+  std::copy(signalArray.cbegin(), signalArray.cend(),
+            m_normWS->getSignalArray());
+}
+m_accumulate = true;
+}
+
+/**
+ * Calculate the points of intersection for the given detector with cuboid
+ * surrounding the detector position in HKL
+ * @param intersections A list of intersections in HKL space
+ * @param theta Polar angle withd detector
+ * @param phi Azimuthal angle with detector
+ * @param transform Matrix to convert frm Q_lab to HKL (2Pi*R *UB*W*SO)^{-1}
+ * @param lowvalue The lowest momentum or energy transfer for the trajectory
+ * @param highvalue The highest momentum or energy transfer for the trajectory
+ */
+void MDNorm::calculateIntersections(
+    std::vector<std::array<double, 4>> &intersections, const double theta,
+    const double phi, Kernel::DblMatrix transform, double lowvalue,
+    double highvalue) {
+  V3D qout(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)),
+      qin(0., 0., 1);
+
+  qout = transform * qout;
+  qin = transform * qin;
+  if (convention == "Crystallography") {
+    qout *= -1;
+    qin *= -1;
+  }
+  double kfmin, kfmax, kimin, kimax;
+  if (m_diffraction) {
+    kimin = lowvalue;
+    kimax = highvalue;
+    kfmin = kimin;
+    kfmax = kimax;
+  } else {
+    kimin = std::sqrt(energyToK * m_Ei);
+    kimax = kimin;
+    kfmin = std::sqrt(energyToK * (m_Ei - highvalue));
+    kfmax = std::sqrt(energyToK * (m_Ei - lowvalue));
+  }
+
+  double hStart = qin.X() * kimin - qout.X() * kfmin,
+         hEnd = qin.X() * kimax - qout.X() * kfmax;
+  double kStart = qin.Y() * kimin - qout.Y() * kfmin,
+         kEnd = qin.Y() * kimax - qout.Y() * kfmax;
+  double lStart = qin.Z() * kimin - qout.Z() * kfmin,
+         lEnd = qin.Z() * kimax - qout.Z() * kfmax;
+
+  double eps = 1e-10;
+  auto hNBins = m_hX.size();
+  auto kNBins = m_kX.size();
+  auto lNBins = m_lX.size();
+  auto eNBins = m_eX.size();
+  intersections.clear();
+  intersections.reserve(hNBins + kNBins + lNBins + eNBins + 2);
+
+  // calculate intersections with planes perpendicular to h
+  if (fabs(hStart - hEnd) > eps) {
+    double fmom = (kfmax - kfmin) / (hEnd - hStart);
+    double fk = (kEnd - kStart) / (hEnd - hStart);
+    double fl = (lEnd - lStart) / (hEnd - hStart);
+    for (size_t i = 0; i < hNBins; i++) {
+      double hi = m_hX[i];
+      if (((hStart - hi) * (hEnd - hi) < 0)) {
+        // if hi is between hStart and hEnd, then ki and li will be between
+        // kStart, kEnd and lStart, lEnd and momi will be between kfmin and
+        // kfmax
+        double ki = fk * (hi - hStart) + kStart;
+        double li = fl * (hi - hStart) + lStart;
+        if ((ki >= m_kX[0]) && (ki <= m_kX[kNBins - 1]) && (li >= m_lX[0]) &&
+            (li <= m_lX[lNBins - 1])) {
+          double momi = fmom * (hi - hStart) + kfmin;
+          intersections.push_back({{hi, ki, li, momi}});
+        }
+      }
+    }
+  }
+  // calculate intersections with planes perpendicular to k
+  if (fabs(kStart - kEnd) > eps) {
+    double fmom = (kfmax - kfmin) / (kEnd - kStart);
+    double fh = (hEnd - hStart) / (kEnd - kStart);
+    double fl = (lEnd - lStart) / (kEnd - kStart);
+    for (size_t i = 0; i < kNBins; i++) {
+      double ki = m_kX[i];
+      if (((kStart - ki) * (kEnd - ki) < 0)) {
+        // if ki is between kStart and kEnd, then hi and li will be between
+        // hStart, hEnd and lStart, lEnd and momi will be between kfmin and
+        // kfmax
+        double hi = fh * (ki - kStart) + hStart;
+        double li = fl * (ki - kStart) + lStart;
+        if ((hi >= m_hX[0]) && (hi <= m_hX[hNBins - 1]) && (li >= m_lX[0]) &&
+            (li <= m_lX[lNBins - 1])) {
+          double momi = fmom * (ki - kStart) + kfmin;
+          intersections.push_back({{hi, ki, li, momi}});
+        }
+      }
+    }
+  }
+
+  // calculate intersections with planes perpendicular to l
+  if (fabs(lStart - lEnd) > eps) {
+    double fmom = (kfmax - kfmin) / (lEnd - lStart);
+    double fh = (hEnd - hStart) / (lEnd - lStart);
+    double fk = (kEnd - kStart) / (lEnd - lStart);
+
+    for (size_t i = 0; i < lNBins; i++) {
+      double li = m_lX[i];
+      if (((lStart - li) * (lEnd - li) < 0)) {
+        double hi = fh * (li - lStart) + hStart;
+        double ki = fk * (li - lStart) + kStart;
+        if ((hi >= m_hX[0]) && (hi <= m_hX[hNBins - 1]) && (ki >= m_kX[0]) &&
+            (ki <= m_kX[kNBins - 1])) {
+          double momi = fmom * (li - lStart) + kfmin;
+          intersections.push_back({{hi, ki, li, momi}});
+        }
+      }
+    }
+  }
+  // intersections with dE
+  if (!m_dEIntegrated) {
+    for (size_t i = 0; i < eNBins; i++) {
+      double kfi = m_eX[i];
+      if ((kfi - kfmin) * (kfi - kfmax) <= 0) {
+        double h = qin.X() * kimin - qout.X() * kfi;
+        double k = qin.Y() * kimin - qout.Y() * kfi;
+        double l = qin.Z() * kimin - qout.Z() * kfi;
+        if ((h >= m_hX[0]) && (h <= m_hX[hNBins - 1]) && (k >= m_kX[0]) &&
+            (k <= m_kX[kNBins - 1]) && (l >= m_lX[0]) &&
+            (l <= m_lX[lNBins - 1])) {
+          intersections.push_back({{h, k, l, kfi}});
+        }
+      }
+    }
+  }
+
+  // endpoints
+  if ((hStart >= m_hX[0]) && (hStart <= m_hX[hNBins - 1]) &&
+      (kStart >= m_kX[0]) && (kStart <= m_kX[kNBins - 1]) &&
+      (lStart >= m_lX[0]) && (lStart <= m_lX[lNBins - 1])) {
+    intersections.push_back({{hStart, kStart, lStart, kfmin}});
+  }
+  if ((hEnd >= m_hX[0]) && (hEnd <= m_hX[hNBins - 1]) && (kEnd >= m_kX[0]) &&
+      (kEnd <= m_kX[kNBins - 1]) && (lEnd >= m_lX[0]) &&
+      (lEnd <= m_lX[lNBins - 1])) {
+    intersections.push_back({{hEnd, kEnd, lEnd, kfmax}});
+  }
+
+  // sort intersections by final momentum
+  std::stable_sort(intersections.begin(), intersections.end(), compareMomentum);
+}
+
+/**
+ * Linearly interpolate between the points in integrFlux at xValues and save the
+ * results in yValues.
+ * @param xValues :: X-values at which to interpolate
+ * @param integrFlux :: A workspace with the spectra to interpolate
+ * @param sp :: A workspace index for a spectrum in integrFlux to interpolate.
+ * @param yValues :: A vector to save the results.
+ */
+void MDNorm::calcIntegralsForIntersections(
+    const std::vector<double> &xValues, const API::MatrixWorkspace &integrFlux,
+    size_t sp, std::vector<double> &yValues) {
+  assert(xValues.size() == yValues.size());
+
+  // the x-data from the workspace
+  const auto &xData = integrFlux.x(sp);
+  const double xStart = xData.front();
+  const double xEnd = xData.back();
+
+  // the values in integrFlux are expected to be integrals of a non-negative
+  // function
+  // ie they must make a non-decreasing function
+  const auto &yData = integrFlux.y(sp);
+  size_t spSize = yData.size();
+
+  const double yMin = 0.0;
+  const double yMax = yData.back();
+
+  size_t nData = xValues.size();
+  // all integrals below xStart must be 0
+  if (xValues[nData - 1] < xStart) {
+    std::fill(yValues.begin(), yValues.end(), yMin);
+    return;
+  }
+
+  // all integrals above xEnd must be equal tp yMax
+  if (xValues[0] > xEnd) {
+    std::fill(yValues.begin(), yValues.end(), yMax);
+    return;
+  }
+
+  size_t i = 0;
+  // integrals below xStart must be 0
+  while (i < nData - 1 && xValues[i] < xStart) {
+    yValues[i] = yMin;
+    i++;
+  }
+  size_t j = 0;
+  for (; i < nData; i++) {
+    // integrals above xEnd must be equal tp yMax
+    if (j >= spSize - 1) {
+      yValues[i] = yMax;
+    } else {
+      double xi = xValues[i];
+      while (j < spSize - 1 && xi > xData[j])
+        j++;
+      // if x falls onto an interpolation point return the corresponding y
+      if (xi == xData[j]) {
+        yValues[i] = yData[j];
+      } else if (j == spSize - 1) {
+        // if we get above xEnd it's yMax
+        yValues[i] = yMax;
+      } else if (j > 0) {
+        // interpolate between the consecutive points
+        double x0 = xData[j - 1];
+        double x1 = xData[j];
+        double y0 = yData[j - 1];
+        double y1 = yData[j];
+        yValues[i] = y0 + (y1 - y0) * (xi - x0) / (x1 - x0);
+      } else // j == 0
+      {
+        yValues[i] = yMin;
+      }
+    }
+  }
+}
+
+} // namespace MDAlgorithms
+} // namespace Mantid
diff --git a/Framework/MDAlgorithms/src/MDNormDirectSC.cpp b/Framework/MDAlgorithms/src/MDNormDirectSC.cpp
index b72af9c85c0d5ad491c13d4fe54c77e16632ae5b..9a8c800f82e991b258454892f9b8451187f7f3dd 100644
--- a/Framework/MDAlgorithms/src/MDNormDirectSC.cpp
+++ b/Framework/MDAlgorithms/src/MDNormDirectSC.cpp
@@ -49,7 +49,7 @@ MDNormDirectSC::MDNormDirectSC()
     : m_normWS(), m_inputWS(), m_hmin(0.0f), m_hmax(0.0f), m_kmin(0.0f),
       m_kmax(0.0f), m_lmin(0.0f), m_lmax(0.0f), m_dEmin(0.f), m_dEmax(0.f),
       m_Ei(0.), m_ki(0.), m_kfmin(0.), m_kfmax(0.), m_hIntegrated(true),
-      m_kIntegrated(true), m_lIntegrated(true), m_dEIntegrated(false),
+      m_kIntegrated(true), m_lIntegrated(true), m_dEIntegrated(true),
       m_rubw(3, 3), m_hIdx(-1), m_kIdx(-1), m_lIdx(-1), m_eIdx(-1), m_hX(),
       m_kX(), m_lX(), m_eX(), m_samplePos(), m_beamDir() {}
 
diff --git a/Framework/MDAlgorithms/test/WeightedMeanMDTest.h b/Framework/MDAlgorithms/test/WeightedMeanMDTest.h
index afb04234b1bdad0362784638f42edbd0d6075276..50c8bfe60c78b8a25a2f8952046a10c1f3eab787 100644
--- a/Framework/MDAlgorithms/test/WeightedMeanMDTest.h
+++ b/Framework/MDAlgorithms/test/WeightedMeanMDTest.h
@@ -205,9 +205,9 @@ public:
     // Create some input data. Signal values as two offset sine waves.
     using VecDouble = std::vector<double>;
     VecDouble s1, s2, e1, e2, x;
-    double theta_shift = 0.4;
+    const double theta_shift = 0.4;
     for (size_t i = 0; i < 40; ++i) {
-      double theta = 0.02 * double(i) * M_PI;
+      const double theta = 0.02 * double(i) * M_PI;
       s1.push_back(std::sin(theta));
       e1.push_back(std::sin(theta));
       s2.push_back(std::sin(theta + theta_shift));
diff --git a/Framework/MPIAlgorithms/CMakeLists.txt b/Framework/MPIAlgorithms/CMakeLists.txt
index efecb04503e3ab88fc1e4e7bf54756acab729ca8..67e7473a611395afe04837c7558517432b26aa44 100644
--- a/Framework/MPIAlgorithms/CMakeLists.txt
+++ b/Framework/MPIAlgorithms/CMakeLists.txt
@@ -37,4 +37,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS MPIAlgorithms ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS MPIAlgorithms INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/Muon/CMakeLists.txt b/Framework/Muon/CMakeLists.txt
index a37eed87108b3383ab2c5dfeda77b84c3de3e836..14af87ee07fa33779b5726af12fd4a45ebf8ee66 100644
--- a/Framework/Muon/CMakeLists.txt
+++ b/Framework/Muon/CMakeLists.txt
@@ -107,4 +107,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Muon ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS Muon INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/Muon/inc/MantidMuon/CalMuonDetectorPhases.h b/Framework/Muon/inc/MantidMuon/CalMuonDetectorPhases.h
index 70e10a03b3a7ded435dc971bb7f0edc749e66a87..8deac0094b854f8ec03e50a6088e45330efd26bd 100644
--- a/Framework/Muon/inc/MantidMuon/CalMuonDetectorPhases.h
+++ b/Framework/Muon/inc/MantidMuon/CalMuonDetectorPhases.h
@@ -57,13 +57,13 @@ private:
   removeExpDecay(const API::MatrixWorkspace_sptr &wsInput);
   /// Fit the workspace
   void fitWorkspace(const API::MatrixWorkspace_sptr &ws, double freq,
-                    std::string groupName, API::ITableWorkspace_sptr &resTab,
+                    std::string groupName, API::ITableWorkspace_sptr resTab,
                     API::WorkspaceGroup_sptr &resGroup);
   /// Create the fitting function as string
   std::string createFittingFunction(double freq, bool fixFreq);
   /// Extract asymmetry and phase from fitting results
-  void extractDetectorInfo(const API::ITableWorkspace_sptr &paramTab,
-                           const API::ITableWorkspace_sptr &resultsTab,
+  void extractDetectorInfo(API::ITableWorkspace &paramTab,
+                           API::ITableWorkspace &resultsTab,
                            const Indexing::SpectrumNumber spectrumNumber);
   /// Find frequency to use in sequential fit
   double getFrequency(const API::MatrixWorkspace_sptr &ws);
diff --git a/Framework/Muon/src/CalMuonDeadTime.cpp b/Framework/Muon/src/CalMuonDeadTime.cpp
index 74c277c8f3a1b5b1eee0a1390ff8f76778965b1f..978033da35fb637eef07d094def1dca58512d06f 100644
--- a/Framework/Muon/src/CalMuonDeadTime.cpp
+++ b/Framework/Muon/src/CalMuonDeadTime.cpp
@@ -11,7 +11,7 @@
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/PhysicalConstants.h"
 
@@ -22,6 +22,7 @@ namespace Mantid {
 namespace Algorithms {
 
 using namespace Kernel;
+using namespace DataObjects;
 
 // Register the class into the algorithm factory
 DECLARE_ALGORITHM(CalMuonDeadTime)
@@ -95,8 +96,7 @@ void CalMuonDeadTime::exec() {
 
   // Do the initial setup of the ouput table-workspace
 
-  API::ITableWorkspace_sptr outTable =
-      API::WorkspaceFactory::Instance().createTable("TableWorkspace");
+  API::ITableWorkspace_sptr outTable = boost::make_shared<TableWorkspace>();
   outTable->addColumn("int", "spectrum");
   outTable->addColumn("double", "dead-time");
 
diff --git a/Framework/Muon/src/CalMuonDetectorPhases.cpp b/Framework/Muon/src/CalMuonDetectorPhases.cpp
index c88d4bf8d710a8214cbdeb8ea2bba5b7e1eb80a9..7e0b14d20e7946ddd790a601dd86477fb389b778 100644
--- a/Framework/Muon/src/CalMuonDetectorPhases.cpp
+++ b/Framework/Muon/src/CalMuonDetectorPhases.cpp
@@ -15,8 +15,8 @@
 #include "MantidAPI/MultiDomainFunction.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/TableRow.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidIndexing/IndexInfo.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/PhysicalConstants.h"
@@ -30,6 +30,7 @@ namespace Mantid {
 namespace Algorithms {
 
 using namespace Kernel;
+using namespace DataObjects;
 
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(CalMuonDetectorPhases)
@@ -127,7 +128,7 @@ void CalMuonDetectorPhases::exec() {
   double freq = getFrequency(tempWS);
 
   // Create the output workspaces
-  auto tab = API::WorkspaceFactory::Instance().createTable("TableWorkspace");
+  TableWorkspace_sptr tab = boost::make_shared<TableWorkspace>();
   auto group = boost::make_shared<API::WorkspaceGroup>();
 
   // Get the name of 'DataFitted'
@@ -152,7 +153,7 @@ void CalMuonDetectorPhases::exec() {
  */
 void CalMuonDetectorPhases::fitWorkspace(const API::MatrixWorkspace_sptr &ws,
                                          double freq, std::string groupName,
-                                         API::ITableWorkspace_sptr &resTab,
+                                         API::ITableWorkspace_sptr resTab,
                                          API::WorkspaceGroup_sptr &resGroup) {
 
   int nhist = static_cast<int>(ws->getNumberHistograms());
@@ -172,13 +173,12 @@ void CalMuonDetectorPhases::fitWorkspace(const API::MatrixWorkspace_sptr &ws,
   const static std::string success = "success";
   for (int wsIndex = 0; wsIndex < nhist; wsIndex++) {
     reportProgress(wsIndex, nhist);
-    auto yValues = ws->y(wsIndex);
+    const auto &yValues = ws->y(wsIndex);
     auto emptySpectrum = std::all_of(yValues.begin(), yValues.end(),
                                      [](double value) { return value == 0.; });
     if (emptySpectrum) {
       g_log.warning("Spectrum " + std::to_string(wsIndex) + " is empty");
-      auto tab =
-          API::WorkspaceFactory::Instance().createTable("TableWorkspace");
+      TableWorkspace_sptr tab = boost::make_shared<TableWorkspace>();
       tab->addColumn("str", "Name");
       tab->addColumn("double", "Value");
       tab->addColumn("double", "Error");
@@ -191,7 +191,7 @@ void CalMuonDetectorPhases::fitWorkspace(const API::MatrixWorkspace_sptr &ws,
         }
       }
 
-      extractDetectorInfo(tab, resTab, indexInfo.spectrumNumber(wsIndex));
+      extractDetectorInfo(*tab, *resTab, indexInfo.spectrumNumber(wsIndex));
 
     } else {
       auto fit = createChildAlgorithm("Fit");
@@ -217,7 +217,7 @@ void CalMuonDetectorPhases::fitWorkspace(const API::MatrixWorkspace_sptr &ws,
       // Now we have our fitting results stored in tab
       // but we need to extract the relevant information, i.e.
       // the detector phases (parameter 'p') and asymmetries ('A')
-      extractDetectorInfo(tab, resTab, indexInfo.spectrumNumber(wsIndex));
+      extractDetectorInfo(*tab, *resTab, indexInfo.spectrumNumber(wsIndex));
     }
   }
 }
@@ -229,12 +229,11 @@ void CalMuonDetectorPhases::fitWorkspace(const API::MatrixWorkspace_sptr &ws,
  * @param spectrumNumber :: [input] Spectrum number
  */
 void CalMuonDetectorPhases::extractDetectorInfo(
-    const API::ITableWorkspace_sptr &paramTab,
-    const API::ITableWorkspace_sptr &resultsTab,
+    API::ITableWorkspace &paramTab, API::ITableWorkspace &resultsTab,
     const Indexing::SpectrumNumber spectrumNumber) {
 
-  double asym = paramTab->Double(0, 1);
-  double phase = paramTab->Double(2, 1);
+  double asym = paramTab.Double(0, 1);
+  double phase = paramTab.Double(2, 1);
   // If asym<0, take the absolute value and add \pi to phase
   // f(x) = A * cos( w * x - p) = -A * cos( w * x - p - PI)
   if (asym < 0) {
@@ -247,7 +246,7 @@ void CalMuonDetectorPhases::extractDetectorInfo(
     phase = phase - factor * 2. * M_PI;
   }
   // Copy parameters to new row in results table
-  API::TableRow row = resultsTab->appendRow();
+  API::TableRow row = resultsTab.appendRow();
   row << static_cast<int>(spectrumNumber) << asym << phase;
 }
 
diff --git a/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp b/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp
index aee33457b2058a34eabd68194196910781ce37fc..bef7a4c392556488b58f3762dd1af93d34dcf454 100644
--- a/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp
+++ b/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp
@@ -13,8 +13,8 @@
 #include "MantidAPI/IFunction.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Run.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/Workspace_fwd.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/PhysicalConstants.h"
 #include "MantidMuon/MuonAlgorithmHelper.h"
@@ -26,6 +26,7 @@
 namespace Mantid {
 namespace Algorithms {
 
+using namespace Mantid::DataObjects;
 using namespace Kernel;
 using API::Progress;
 using std::size_t;
@@ -121,11 +122,10 @@ void EstimateMuonAsymmetryFromCounts::exec() {
   // Create output workspace with same dimensions as input
   API::MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
   if (inputWS != outputWS) {
-    outputWS = API::WorkspaceFactory::Instance().create(inputWS);
+    outputWS = create<API::MatrixWorkspace>(*inputWS);
   }
   bool extraData = getProperty("OutputUnNormData");
-  API::MatrixWorkspace_sptr unnormWS =
-      API::WorkspaceFactory::Instance().create(outputWS);
+  API::MatrixWorkspace_sptr unnormWS = create<API::MatrixWorkspace>(*outputWS);
   double startX = getProperty("StartX");
   double endX = getProperty("EndX");
   const Mantid::API::Run &run = inputWS->run();
@@ -197,7 +197,7 @@ void EstimateMuonAsymmetryFromCounts::exec() {
     outputWS->setHistogram(
         specNum, normaliseCounts(inputWS->histogram(specNum), numGoodFrames));
     if (extraData) {
-      unnormWS->mutableX(specNum) = outputWS->x(specNum);
+      unnormWS->setSharedX(specNum, outputWS->sharedX(specNum));
       unnormWS->mutableY(specNum) = outputWS->y(specNum);
       unnormWS->mutableE(specNum) = outputWS->e(specNum);
     }
diff --git a/Framework/Muon/src/PhaseQuadMuon.cpp b/Framework/Muon/src/PhaseQuadMuon.cpp
index b893c72cb535be74d88a50d38eca61818764c100..714e8b361ac3e8b306cbd5880aa034d56dcbe791 100644
--- a/Framework/Muon/src/PhaseQuadMuon.cpp
+++ b/Framework/Muon/src/PhaseQuadMuon.cpp
@@ -9,10 +9,15 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidAPI/MatrixWorkspaceValidator.h"
-#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/PhysicalConstants.h"
 #include "MantidKernel/Unit.h"
 
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
+
 namespace {
 const std::array<std::string, 2> phaseNames = {{"phase", "phi"}};
 const std::array<std::string, 3> asymmNames = {{"asymmetry", "asymm", "asym"}};
@@ -291,7 +296,7 @@ PhaseQuadMuon::squash(const API::MatrixWorkspace_sptr &ws,
   const size_t npoints = ws->blocksize();
   // Create and populate output workspace
   API::MatrixWorkspace_sptr ows =
-      API::WorkspaceFactory::Instance().create(ws, 2, npoints + 1, npoints);
+      create<API::MatrixWorkspace>(*ws, 2, BinEdges(npoints + 1));
 
   // X
   ows->setSharedX(0, ws->sharedX(0));
diff --git a/Framework/Muon/src/PlotAsymmetryByLogValue.cpp b/Framework/Muon/src/PlotAsymmetryByLogValue.cpp
index c9ba0fe357302f100a096331733bc23276401fd4..802cf1ea84e27e274907b718c657027079751537 100644
--- a/Framework/Muon/src/PlotAsymmetryByLogValue.cpp
+++ b/Framework/Muon/src/PlotAsymmetryByLogValue.cpp
@@ -8,15 +8,19 @@
 #include <vector>
 
 #include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/FileFinder.h"
 #include "MantidAPI/FileProperty.h"
 #include "MantidAPI/Progress.h"
 #include "MantidAPI/Run.h"
 #include "MantidAPI/ScopedWorkspace.h"
 #include "MantidAPI/TableRow.h"
 #include "MantidAPI/TextAxis.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/Histogram.h"
+#include "MantidHistogramData/HistogramBuilder.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MandatoryValidator.h"
@@ -24,8 +28,9 @@
 #include "MantidKernel/TimeSeriesProperty.h"
 #include "MantidMuon/PlotAsymmetryByLogValue.h"
 #include "Poco/File.h"
-#include <MantidAPI/FileFinder.h>
 
+using namespace Mantid::DataObjects;
+using namespace Mantid::HistogramData;
 namespace // anonymous
 {
 
@@ -186,19 +191,16 @@ void PlotAsymmetryByLogValue::exec() {
   // Create the 2D workspace for the output
   int nplots = !m_greenY.empty() ? 4 : 1;
   size_t npoints = m_logValue.size();
-  MatrixWorkspace_sptr outWS = WorkspaceFactory::Instance().create(
-      "Workspace2D",
-      nplots,  //  the number of plots
-      npoints, //  the number of data points on a plot
-      npoints  //  it's not a histogram
+  MatrixWorkspace_sptr outWS = create<Workspace2D>(
+      nplots,         //  the number of plots
+      Points(npoints) //  the number of data points on a plot
   );
   // Populate output workspace with data
   populateOutputWorkspace(outWS, nplots);
   // Assign the result to the output workspace property
   setProperty("OutputWorkspace", outWS);
 
-  outWS = WorkspaceFactory::Instance().create("Workspace2D", nplots + 1,
-                                              npoints, npoints);
+  outWS = create<Workspace2D>(nplots + 1, Points(npoints));
   // Populate ws holding current results
   saveResultsToADS(outWS, nplots + 1);
 }
@@ -588,8 +590,7 @@ Workspace_sptr
 PlotAsymmetryByLogValue::createCustomGrouping(const std::vector<int> &fwd,
                                               const std::vector<int> &bwd) {
 
-  ITableWorkspace_sptr group =
-      WorkspaceFactory::Instance().createTable("TableWorkspace");
+  ITableWorkspace_sptr group = boost::make_shared<TableWorkspace>();
   group->addColumn("vector_int", "group");
   TableRow row = group->appendRow();
   row << fwd;
@@ -747,10 +748,12 @@ void PlotAsymmetryByLogValue::calcIntAsymmetry(MatrixWorkspace_sptr ws_red,
                                                MatrixWorkspace_sptr ws_green,
                                                double &Y, double &E) {
   if (!m_int) { //  "Differential asymmetry"
-
-    MatrixWorkspace_sptr tmpWS = WorkspaceFactory::Instance().create(
-        ws_red, 1, ws_red->x(0).size(), ws_red->y(0).size());
-
+    HistogramBuilder builder;
+    builder.setX(ws_red->x(0).size());
+    builder.setY(ws_red->y(0).size());
+    builder.setDistribution(ws_red->isDistribution());
+    MatrixWorkspace_sptr tmpWS =
+        create<MatrixWorkspace>(*ws_red, 1, builder.build());
     for (size_t i = 0; i < tmpWS->y(0).size(); i++) {
       double FNORM = ws_green->y(0)[i] + ws_red->y(0)[i];
       FNORM = FNORM != 0.0 ? 1.0 / FNORM : 1.0;
diff --git a/Framework/Muon/src/RRFMuon.cpp b/Framework/Muon/src/RRFMuon.cpp
index 3f9ae72f428f25df043fc26cff3736470d8c8a1b..a1ea099e8c6a511c66bc28d7d3aa49a2a24a1401 100644
--- a/Framework/Muon/src/RRFMuon.cpp
+++ b/Framework/Muon/src/RRFMuon.cpp
@@ -95,10 +95,10 @@ void RRFMuon::exec() {
   // Put results into output workspace
   // Real RRF polarization
   outputWs->setSharedX(0, inputWs->sharedX(0));
-  outputWs->mutableY(0) = rrfRe;
+  outputWs->mutableY(0) = std::move(rrfRe);
   // Imaginary RRF polarization
   outputWs->setSharedX(1, inputWs->sharedX(1));
-  outputWs->mutableY(1) = rrfIm;
+  outputWs->mutableY(1) = std::move(rrfIm);
 
   // Set output workspace
   setProperty("OutputWorkspace", outputWs);
diff --git a/Framework/Muon/test/PlotAsymmetryByLogValueTest.h b/Framework/Muon/test/PlotAsymmetryByLogValueTest.h
index e4def21c460a8f798bd7f113730cb25f87f08bff..af319ec7f00f905eae5c0b3221fa8c609ffb48c1 100644
--- a/Framework/Muon/test/PlotAsymmetryByLogValueTest.h
+++ b/Framework/Muon/test/PlotAsymmetryByLogValueTest.h
@@ -142,7 +142,7 @@ public:
     TS_ASSERT(outWS);
     TS_ASSERT_EQUALS(outWS->blocksize(), 2);
     TS_ASSERT_EQUALS(outWS->getNumberHistograms(), 4);
-    const auto Y = outWS->y(0);
+    const auto &Y = outWS->y(0);
     TS_ASSERT_DELTA(Y[0], 0.0128845, 0.001);
     TS_ASSERT_DELTA(Y[1], 0.0224898, 0.00001);
 
@@ -178,7 +178,7 @@ public:
     TS_ASSERT(outWS);
     TS_ASSERT_EQUALS(outWS->blocksize(), 2);
     TS_ASSERT_EQUALS(outWS->getNumberHistograms(), 4);
-    const auto Y = outWS->y(0);
+    const auto &Y = outWS->y(0);
     TS_ASSERT_DELTA(Y[0], -0.01236, 0.001);
     TS_ASSERT_DELTA(Y[1], 0.019186, 0.00001);
   }
@@ -286,7 +286,7 @@ public:
     TS_ASSERT_EQUALS(outWs->blocksize(), 2);
     TS_ASSERT_EQUALS(outWs->getNumberHistograms(), 1);
 
-    const auto Y = outWs->y(0);
+    const auto &Y = outWs->y(0);
 
     TS_ASSERT_DELTA(Y[0], 0.15214, 0.00001);
     TS_ASSERT_DELTA(Y[1], 0.14492, 0.00001);
@@ -319,7 +319,7 @@ public:
     TS_ASSERT_EQUALS(outWs->blocksize(), 2);
     TS_ASSERT_EQUALS(outWs->getNumberHistograms(), 1);
 
-    const auto Y = outWs->y(0);
+    const auto &Y = outWs->y(0);
 
     TS_ASSERT_DELTA(Y[0], 0.151202, 0.00001);
     TS_ASSERT_DELTA(Y[1], 0.144008, 0.00001);
@@ -354,10 +354,10 @@ public:
     TS_ASSERT_EQUALS(outWs->blocksize(), 2);
     TS_ASSERT_EQUALS(outWs->getNumberHistograms(), 4);
 
-    const auto YDiff = outWs->y(0);
-    const auto EDiff = outWs->e(0);
-    const auto YSum = outWs->y(3);
-    const auto ESum = outWs->e(3);
+    const auto &YDiff = outWs->y(0);
+    const auto &EDiff = outWs->e(0);
+    const auto &YSum = outWs->y(3);
+    const auto &ESum = outWs->e(3);
 
     TS_ASSERT_DELTA(YDiff[0], 0.001135, 0.000001);
     TS_ASSERT_DELTA(EDiff[0], 0.001805, 0.000001);
@@ -394,7 +394,7 @@ public:
     TS_ASSERT_EQUALS(outWs->blocksize(), 2);
     TS_ASSERT_EQUALS(outWs->getNumberHistograms(), 1);
 
-    const auto Y = outWs->y(0);
+    const auto &Y = outWs->y(0);
     TS_ASSERT_DELTA(Y[0], 0.14700, 0.00001);
     TS_ASSERT_DELTA(Y[1], 0.13042, 0.00001);
   }
@@ -428,7 +428,7 @@ public:
 
     // Now we want to test X values (log values) in the output workspace
     // rather than asymmetry (Y values)
-    const auto X = outWs->x(0);
+    const auto &X = outWs->x(0);
 
     TS_ASSERT_DELTA(X[0], 178.740476, 0.00001);
     TS_ASSERT_DELTA(X[1], 178.849998, 0.00001);
diff --git a/Framework/Nexus/CMakeLists.txt b/Framework/Nexus/CMakeLists.txt
index 4fc2b608daf37ec3fc2e4b15a30945d59efbcd3d..579141245627e6649a9df542d0706026f07193b4 100644
--- a/Framework/Nexus/CMakeLists.txt
+++ b/Framework/Nexus/CMakeLists.txt
@@ -41,4 +41,4 @@ target_link_libraries ( Nexus LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${MANT
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Nexus ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Nexus INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/NexusGeometry/CMakeLists.txt b/Framework/NexusGeometry/CMakeLists.txt
index 9439d186cf8e41608a3322580a15fce838c6dcef..30e05039f64c08967663e37a0158b2d19f982703 100644
--- a/Framework/NexusGeometry/CMakeLists.txt
+++ b/Framework/NexusGeometry/CMakeLists.txt
@@ -48,4 +48,5 @@ target_link_libraries( NexusGeometry LINK_PRIVATE ${MANTIDLIBS} ${HDF5_LIBRARIES
 
 # Add the unit tests directory
 add_subdirectory ( test )
-install ( TARGETS NexusGeometry ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR})
+
+mtd_install_targets( TARGETS NexusGeometry INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusShapeFactory.h b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusShapeFactory.h
index b584d3c123355685e5226c717e5133e4912be6d1..485a544591c145a3113a548998e03e7c936d7c38 100644
--- a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusShapeFactory.h
+++ b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusShapeFactory.h
@@ -35,19 +35,19 @@ createCylinder(const Eigen::Matrix<double, 3, 3> &pointsDef);
 
 /// Creates a triangular mesh shape based on ready triangulated polygons
 DLLExport std::unique_ptr<const Geometry::IObject>
-createMesh(std::vector<uint16_t> &&triangularFaces,
+createMesh(std::vector<uint32_t> &&triangularFaces,
            std::vector<Mantid::Kernel::V3D> &&vertices);
 
 /// Creates a triangular mesh shape based on OFF (Object File Format) polygon
 /// inputs https://en.wikipedia.org/wiki/OFF_(file_format)
 DLLExport std::unique_ptr<const Geometry::IObject>
-createFromOFFMesh(const std::vector<uint16_t> &faceIndices,
-                  const std::vector<uint16_t> &windingOrder,
+createFromOFFMesh(const std::vector<uint32_t> &faceIndices,
+                  const std::vector<uint32_t> &windingOrder,
                   const std::vector<float> &nexusVertices);
 
 DLLExport std::unique_ptr<const Geometry::IObject>
-createFromOFFMesh(const std::vector<uint16_t> &faceIndices,
-                  const std::vector<uint16_t> &windingOrder,
+createFromOFFMesh(const std::vector<uint32_t> &faceIndices,
+                  const std::vector<uint32_t> &windingOrder,
                   const std::vector<Eigen::Vector3d> &nexusVertices);
 } // namespace NexusShapeFactory
 } // namespace NexusGeometry
diff --git a/Framework/NexusGeometry/src/NexusGeometryParser.cpp b/Framework/NexusGeometry/src/NexusGeometryParser.cpp
index b74b36c21134a5f8b412e097b9494d4feec3c143..a216c31ed7c685200ecec4fa67cb30ae84d04a5a 100644
--- a/Framework/NexusGeometry/src/NexusGeometryParser.cpp
+++ b/Framework/NexusGeometry/src/NexusGeometryParser.cpp
@@ -477,28 +477,28 @@ parseNexusCylinder(const Group &shapeGroup) {
 // Parse OFF (mesh) nexus geometry
 boost::shared_ptr<const Geometry::IObject>
 parseNexusMesh(const Group &shapeGroup) {
-  const std::vector<uint16_t> faceIndices = convertVector<int32_t, uint16_t>(
+  const std::vector<uint32_t> faceIndices = convertVector<int32_t, uint32_t>(
       get1DDataset<int32_t>("faces", shapeGroup));
-  const std::vector<uint16_t> windingOrder = convertVector<int32_t, uint16_t>(
+  const std::vector<uint32_t> windingOrder = convertVector<int32_t, uint32_t>(
       get1DDataset<int32_t>("winding_order", shapeGroup));
   const auto vertices = get1DDataset<float>("vertices", shapeGroup);
   return NexusShapeFactory::createFromOFFMesh(faceIndices, windingOrder,
                                               vertices);
 }
 
-void extractFacesAndIDs(const std::vector<uint16_t> &detFaces,
-                        const std::vector<uint16_t> &windingOrder,
+void extractFacesAndIDs(const std::vector<uint32_t> &detFaces,
+                        const std::vector<uint32_t> &windingOrder,
                         const std::vector<float> &vertices,
                         const std::unordered_map<int, uint32_t> &detIdToIndex,
                         const size_t vertsPerFace,
                         std::vector<std::vector<Eigen::Vector3d>> &detFaceVerts,
-                        std::vector<std::vector<uint16_t>> &detFaceIndices,
-                        std::vector<std::vector<uint16_t>> &detWindingOrder,
+                        std::vector<std::vector<uint32_t>> &detFaceIndices,
+                        std::vector<std::vector<uint32_t>> &detWindingOrder,
                         std::vector<int32_t> &detIds) {
   const size_t vertStride = 3;
   size_t detFaceIndex = 1;
   std::fill(detFaceIndices.begin(), detFaceIndices.end(),
-            std::vector<uint16_t>(1, 0));
+            std::vector<uint32_t>(1, 0));
   for (size_t i = 0; i < windingOrder.size(); i += vertsPerFace) {
     auto detFaceId = detFaces[detFaceIndex];
     // Id -> Index
@@ -511,27 +511,27 @@ void extractFacesAndIDs(const std::vector<uint16_t> &detFaces,
     for (size_t v = 0; v < vertsPerFace; ++v) {
       const auto vi = windingOrder[i + v] * vertStride;
       detVerts.emplace_back(vertices[vi], vertices[vi + 1], vertices[vi + 2]);
-      detWinding.push_back(static_cast<uint16_t>(detWinding.size()));
+      detWinding.push_back(static_cast<uint32_t>(detWinding.size()));
     }
     // Index -> Id
     detIds[detIndex] = detFaceId;
-    detIndices.push_back(static_cast<uint16_t>(detVerts.size()));
+    detIndices.push_back(static_cast<uint32_t>(detVerts.size()));
     // Detector faces is 2N detectors
     detFaceIndex += 2;
   }
 }
 
 void parseNexusMeshAndAddDetectors(
-    const std::vector<uint16_t> &detFaces,
-    const std::vector<uint16_t> &faceIndices,
-    const std::vector<uint16_t> &windingOrder,
+    const std::vector<uint32_t> &detFaces,
+    const std::vector<uint32_t> &faceIndices,
+    const std::vector<uint32_t> &windingOrder,
     const std::vector<float> &vertices, const size_t numDets,
     const std::unordered_map<int, uint32_t> &detIdToIndex,
     const std::string &name, InstrumentBuilder &builder) {
   auto vertsPerFace = windingOrder.size() / faceIndices.size();
   std::vector<std::vector<Eigen::Vector3d>> detFaceVerts(numDets);
-  std::vector<std::vector<uint16_t>> detFaceIndices(numDets);
-  std::vector<std::vector<uint16_t>> detWindingOrder(numDets);
+  std::vector<std::vector<uint32_t>> detFaceIndices(numDets);
+  std::vector<std::vector<uint32_t>> detWindingOrder(numDets);
   std::vector<int> detIds(numDets);
 
   extractFacesAndIDs(detFaces, windingOrder, vertices, detIdToIndex,
@@ -563,11 +563,11 @@ void parseAndAddBank(const Group &shapeGroup, InstrumentBuilder &builder,
                      const std::string &bankName) {
   // Load mapping between detector IDs and faces, winding order of vertices for
   // faces, and face corner vertices.
-  const std::vector<uint16_t> detFaces = convertVector<int32_t, uint16_t>(
+  const std::vector<uint32_t> detFaces = convertVector<int32_t, uint32_t>(
       get1DDataset<int32_t>("detector_faces", shapeGroup));
-  const std::vector<uint16_t> faceIndices = convertVector<int32_t, uint16_t>(
+  const std::vector<uint32_t> faceIndices = convertVector<int32_t, uint32_t>(
       get1DDataset<int32_t>("faces", shapeGroup));
-  const std::vector<uint16_t> windingOrder = convertVector<int32_t, uint16_t>(
+  const std::vector<uint32_t> windingOrder = convertVector<int32_t, uint32_t>(
       get1DDataset<int32_t>("winding_order", shapeGroup));
   const auto vertices = get1DDataset<float>("vertices", shapeGroup);
 
diff --git a/Framework/NexusGeometry/src/NexusShapeFactory.cpp b/Framework/NexusGeometry/src/NexusShapeFactory.cpp
index 911b6ddd9e1f33f84827f020cd73a85482bbd204..d498c295eea39730421eba12151e5e88a58d6561 100644
--- a/Framework/NexusGeometry/src/NexusShapeFactory.cpp
+++ b/Framework/NexusGeometry/src/NexusShapeFactory.cpp
@@ -47,8 +47,8 @@ std::unique_ptr<const Geometry::IObject> createCylinderShape(
   return std::unique_ptr<const Geometry::IObject>(shape.release());
 }
 
-void createTrianglesFromPolygon(const std::vector<uint16_t> &windingOrder,
-                                std::vector<uint16_t> &triangularFaces,
+void createTrianglesFromPolygon(const std::vector<uint32_t> &windingOrder,
+                                std::vector<uint32_t> &triangularFaces,
                                 int &startOfFace, int &endOfFace) {
   int polygonOrder = endOfFace - startOfFace;
   auto first = windingOrder.begin() + startOfFace;
@@ -63,16 +63,16 @@ void createTrianglesFromPolygon(const std::vector<uint16_t> &windingOrder,
   startOfFace = endOfFace; // start of the next face
 }
 
-std::vector<uint16_t>
-createTriangularFaces(const std::vector<uint16_t> &faceIndices,
-                      const std::vector<uint16_t> &windingOrder) {
+std::vector<uint32_t>
+createTriangularFaces(const std::vector<uint32_t> &faceIndices,
+                      const std::vector<uint32_t> &windingOrder) {
 
   // Elements 0 to 2 are the indices of the vertices vector corresponding to the
   // vertices of the first triangle.
   // Elements 3 to 5 are for the second triangle, and so on.
   // The order of the vertices is the winding order of the triangle, determining
   // the face normal by right-hand rule
-  std::vector<uint16_t> triangularFaces;
+  std::vector<uint32_t> triangularFaces;
 
   int startOfFace = 0;
   int endOfFace = 0;
@@ -156,10 +156,10 @@ createCylinder(const Eigen::Matrix<double, 3, 3> &pointsDef) {
 }
 
 std::unique_ptr<const Geometry::IObject>
-createFromOFFMesh(const std::vector<uint16_t> &faceIndices,
-                  const std::vector<uint16_t> &windingOrder,
+createFromOFFMesh(const std::vector<uint32_t> &faceIndices,
+                  const std::vector<uint32_t> &windingOrder,
                   const std::vector<float> &nexusVertices) {
-  std::vector<uint16_t> triangularFaces =
+  std::vector<uint32_t> triangularFaces =
       createTriangularFaces(faceIndices, windingOrder);
 
   std::vector<Mantid::Kernel::V3D> vertices;
@@ -190,10 +190,10 @@ toVectorV3D(const std::vector<Eigen::Vector3d> &nexusVertices) {
 }
 
 std::unique_ptr<const Geometry::IObject>
-createFromOFFMesh(const std::vector<uint16_t> &faceIndices,
-                  const std::vector<uint16_t> &windingOrder,
+createFromOFFMesh(const std::vector<uint32_t> &faceIndices,
+                  const std::vector<uint32_t> &windingOrder,
                   const std::vector<Eigen::Vector3d> &nexusVertices) {
-  std::vector<uint16_t> triangularFaces =
+  std::vector<uint32_t> triangularFaces =
       createTriangularFaces(faceIndices, windingOrder);
 
   return NexusShapeFactory::createMesh(std::move(triangularFaces),
@@ -201,7 +201,7 @@ createFromOFFMesh(const std::vector<uint16_t> &faceIndices,
 }
 
 std::unique_ptr<const Geometry::IObject>
-createMesh(std::vector<uint16_t> &&triangularFaces,
+createMesh(std::vector<uint32_t> &&triangularFaces,
            std::vector<Mantid::Kernel::V3D> &&vertices) {
 
   if (Geometry::MeshObject2D::pointsCoplanar(vertices))
diff --git a/Framework/NexusGeometry/test/NexusShapeFactoryTest.h b/Framework/NexusGeometry/test/NexusShapeFactoryTest.h
index ea7c353e905243261d1fa69094cf2f8ca734312e..d392974f3de761836d91b2b318064e9c51dd99e1 100644
--- a/Framework/NexusGeometry/test/NexusShapeFactoryTest.h
+++ b/Framework/NexusGeometry/test/NexusShapeFactoryTest.h
@@ -29,7 +29,7 @@ public:
     vertices.emplace_back(V3D(-1, 0, 0));
     vertices.emplace_back(V3D(1, 0, 0));
     vertices.emplace_back(V3D(0, 1, 0));
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(), {0, 1, 2});
 
     auto obj = createMesh(std::move(triangles), std::move(vertices));
@@ -46,7 +46,7 @@ public:
     vertices.emplace_back(V3D(1, 0, 0));
     vertices.emplace_back(V3D(0, 1, 0));
     vertices.emplace_back(V3D(0, 1, 1));
-    std::vector<uint16_t> triangles;
+    std::vector<uint32_t> triangles;
     triangles.insert(triangles.end(), {0, 1, 2, 1, 3, 2, 3, 0, 2});
 
     auto obj = createMesh(std::move(triangles), std::move(vertices));
@@ -59,8 +59,8 @@ public:
 class NexusShapeFactoryTestPerformance : public CxxTest::TestSuite {
 private:
   std::vector<Eigen::Vector3d> m_vertices;
-  std::vector<uint16_t> m_facesIndices;
-  std::vector<uint16_t> m_windingOrder;
+  std::vector<uint32_t> m_facesIndices;
+  std::vector<uint32_t> m_windingOrder;
 
   template <typename T>
   void appendTo(std::vector<T> &destination, unsigned int value) {
@@ -79,7 +79,7 @@ public:
 
   NexusShapeFactoryTestPerformance() {
     // Make inputs. Repeated squares
-    for (uint16_t i = 0; i < 10000; ++i) {
+    for (uint32_t i = 0; i < 10000; ++i) {
       m_vertices.emplace_back(Eigen::Vector3d(0 + i, 1, 0));
       m_vertices.emplace_back(Eigen::Vector3d(0 + i, 1, 0));
       /*
@@ -89,13 +89,13 @@ public:
        *     x           x     x
        */
 
-      appendTo<uint16_t>(m_facesIndices, (i * 4));
-      appendTo<uint16_t>(m_facesIndices, ((i + 1) * 4));
+      appendTo<uint32_t>(m_facesIndices, (i * 4));
+      appendTo<uint32_t>(m_facesIndices, ((i + 1) * 4));
       if (i % 2 != 0) {
-        appendTo<uint16_t>(m_windingOrder, (i * 2));
-        appendTo<uint16_t>(m_windingOrder, (i * 2 + 1));
-        appendTo<uint16_t>(m_windingOrder, (i * 2 + 2));
-        appendTo<uint16_t>(m_windingOrder, (i * 2 + 3));
+        appendTo<uint32_t>(m_windingOrder, (i * 2));
+        appendTo<uint32_t>(m_windingOrder, (i * 2 + 1));
+        appendTo<uint32_t>(m_windingOrder, (i * 2 + 2));
+        appendTo<uint32_t>(m_windingOrder, (i * 2 + 3));
       }
     }
   }
diff --git a/Framework/Parallel/CMakeLists.txt b/Framework/Parallel/CMakeLists.txt
index 61602eb53605cbfeaf09a34a2bc670bb7c173520..2b20a07f495e81157fe77939e62d5b8a1f42fa0a 100644
--- a/Framework/Parallel/CMakeLists.txt
+++ b/Framework/Parallel/CMakeLists.txt
@@ -76,4 +76,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Parallel ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Parallel INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
diff --git a/Framework/PythonInterface/CMakeLists.txt b/Framework/PythonInterface/CMakeLists.txt
index ad78194c942f509f22abc61b319c91728d07a608..1bc7b5e83c569ab44936e3b32cf81b7bca2c36e1 100644
--- a/Framework/PythonInterface/CMakeLists.txt
+++ b/Framework/PythonInterface/CMakeLists.txt
@@ -121,3 +121,7 @@ add_subdirectory( test )
 install ( DIRECTORY plugins/ DESTINATION ${PLUGINS_DIR}/python
           PATTERN "*.pyc" EXCLUDE
           PATTERN ".svn" EXCLUDE )
+
+install ( DIRECTORY plugins/ DESTINATION ${WORKBENCH_PLUGINS_DIR}/python
+          PATTERN "*.pyc" EXCLUDE
+          PATTERN ".svn" EXCLUDE )
diff --git a/Framework/PythonInterface/core/CMakeLists.txt b/Framework/PythonInterface/core/CMakeLists.txt
index 47afc4f136343346e0614ffa93dca8697ce828fa..624f0d412a352e0fe3245146420f4239a460e00e 100644
--- a/Framework/PythonInterface/core/CMakeLists.txt
+++ b/Framework/PythonInterface/core/CMakeLists.txt
@@ -51,6 +51,4 @@ elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
   set_target_properties ( ${_target_name} PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}" )
 endif ()
 
-# Installation
-install ( TARGETS PythonInterfaceCore ${SYSTEM_PACKAGE_TARGET} DESTINATION
-          ${LIB_DIR} )
+mtd_install_targets( TARGETS PythonInterfaceCore INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR} )
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/api/AnalysisDataServiceObserverAdapter.h b/Framework/PythonInterface/inc/MantidPythonInterface/api/AnalysisDataServiceObserverAdapter.h
new file mode 100644
index 0000000000000000000000000000000000000000..e466b745c4f9606e38252b1a4ab1ce82f88a21c5
--- /dev/null
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/api/AnalysisDataServiceObserverAdapter.h
@@ -0,0 +1,58 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_PYTHONINTERFACE_ANALYSISDATASERVICEOBSERVERADAPTER_H_
+#define MANTID_PYTHONINTERFACE_ANALYSISDATASERVICEOBSERVERADAPTER_H_
+
+#include "MantidAPI/AnalysisDataServiceObserver.h"
+#include <boost/python/wrapper.hpp>
+
+namespace Mantid {
+namespace PythonInterface {
+
+/**
+A wrapper class helping to export AnalysisDataServiceObserver to python.
+It provides access from the C++ side to methods defined in python
+on subclasses of AnalysisDataServiceObserver.
+This allows the virtual methods to be overriden by python subclasses.
+ */
+
+class DLLExport AnalysisDataServiceObserverAdapter
+    : public API::AnalysisDataServiceObserver {
+public:
+  explicit AnalysisDataServiceObserverAdapter(PyObject *self);
+  AnalysisDataServiceObserverAdapter(
+      const AnalysisDataServiceObserverAdapter &) = delete;
+  AnalysisDataServiceObserverAdapter &
+  operator=(const AnalysisDataServiceObserverAdapter &) = delete;
+
+  void anyChangeHandle() override;
+  void addHandle(const std::string &wsName, const Workspace_sptr &ws) override;
+  void replaceHandle(const std::string &wsName,
+                     const Workspace_sptr &ws) override;
+  void deleteHandle(const std::string &wsName,
+                    const Workspace_sptr &ws) override;
+  void clearHandle() override;
+  void renameHandle(const std::string &wsName,
+                    const std::string &newName) override;
+  void groupHandle(const std::string &wsName,
+                   const Workspace_sptr &ws) override;
+  void unGroupHandle(const std::string &wsName,
+                     const Workspace_sptr &ws) override;
+  void groupUpdateHandle(const std::string &wsName,
+                         const Workspace_sptr &ws) override;
+
+private:
+  /// Return the PyObject that owns this wrapper, i.e. self
+  inline PyObject *getSelf() const { return m_self; }
+  /// Value of "self" used by python to refer to an instance of this class
+  PyObject *m_self;
+};
+
+} // namespace PythonInterface
+} // namespace Mantid
+
+#endif /*MANTID_PYTHONINTERFACE_ANALYSISDATASERVICEOBSERVERADAPTER_H_*/
\ No newline at end of file
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/api/ComponentInfoPythonIterator.h b/Framework/PythonInterface/inc/MantidPythonInterface/api/ComponentInfoPythonIterator.h
new file mode 100644
index 0000000000000000000000000000000000000000..e1cc280bf4e2339b6ee7cb0b9d0af83ca739994c
--- /dev/null
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/api/ComponentInfoPythonIterator.h
@@ -0,0 +1,57 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_PYTHONINTERFACE_COMPONENTINFOPYTHONITERATOR_H_
+#define MANTID_PYTHONINTERFACE_COMPONENTINFOPYTHONITERATOR_H_
+
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include "MantidGeometry/Instrument/ComponentInfoItem.h"
+#include "MantidGeometry/Instrument/ComponentInfoIterator.h"
+
+#include <boost/python/iterator.hpp>
+
+using Mantid::Geometry::ComponentInfo;
+using Mantid::Geometry::ComponentInfoItem;
+using Mantid::Geometry::ComponentInfoIterator;
+using namespace boost::python;
+
+namespace Mantid {
+namespace PythonInterface {
+
+/** ComponentInfoPythonIterator
+
+ComponentInfoPythonIterator is used to expose ComponentInfoIterator to the
+Python side.
+*/
+
+class ComponentInfoPythonIterator {
+public:
+  explicit ComponentInfoPythonIterator(ComponentInfo &detectorInfo)
+      : m_begin(detectorInfo.begin()), m_end(detectorInfo.end()),
+        m_firstOrDone(true) {}
+
+  ComponentInfoItem<ComponentInfo> next() {
+    if (!m_firstOrDone)
+      ++m_begin;
+    else
+      m_firstOrDone = false;
+    if (m_begin == m_end) {
+      m_firstOrDone = true;
+      objects::stop_iteration_error();
+    }
+    return *m_begin;
+  }
+
+private:
+  ComponentInfoIterator<ComponentInfo> m_begin;
+  ComponentInfoIterator<ComponentInfo> m_end;
+  bool m_firstOrDone;
+};
+
+} // namespace PythonInterface
+} // namespace Mantid
+
+#endif /* MANTID_PYTHONINTERFACE_COMPONENTINFOPYTHONITERATOR_H_ */
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h b/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h
index ee194aaaa3ef3a5aa5332263cead2f8c3fe64e1a..42c10470115f524c45e8e5c5b70a513c7dafbdca 100644
--- a/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h
@@ -42,7 +42,7 @@ public:
       : m_begin(detectorInfo.begin()), m_end(detectorInfo.end()),
         m_firstOrDone(true) {}
 
-  const DetectorInfoItem<DetectorInfo> &next() {
+  DetectorInfoItem<DetectorInfo> next() {
     if (!m_firstOrDone)
       ++m_begin;
     else
diff --git a/Framework/PythonInterface/mantid/CMakeLists.txt b/Framework/PythonInterface/mantid/CMakeLists.txt
index 0c0d81339fc060ef6e6b21a08e8ce48591db4bd2..efe5100404975ab97f286070839ecf463b38d836 100644
--- a/Framework/PythonInterface/mantid/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/CMakeLists.txt
@@ -65,7 +65,6 @@ include ( ${CMAKE_CURRENT_SOURCE_DIR}/BundlePython.cmake )
 ###########################################################################
 
 # Pure Python files
-install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid )
+mtd_install_files( FILES ${PY_FILES} INSTALL_DIRS ${BIN_DIR}/mantid ${WORKBENCH_BIN_DIR}/mantid)
 # version.py that will overwrite the ones from the built target
-install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/${VERSION_PY}.install.py DESTINATION
-          ${BIN_DIR}/mantid RENAME ${VERSION_PY}.py )
+mtd_install_files( FILES ${CMAKE_CURRENT_BINARY_DIR}/${VERSION_PY}.install.py INSTALL_DIRS ${BIN_DIR}/mantid ${WORKBENCH_BIN_DIR}/mantid RENAME ${VERSION_PY}.py)
diff --git a/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt b/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt
index d75e5076b2f8770e5d87fa7c757d8aebf1a6553d..a50bc9d924700ee1d85c0b167461b33099728f8e 100644
--- a/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt
@@ -76,7 +76,7 @@ endif ()
 ###########################################################################
 # Installation settings
 ###########################################################################
-install ( TARGETS PythonCurveFittingModule ${SYSTEM_PACKAGE_TARGET} DESTINATION ${BIN_DIR}/mantid/_plugins )
+mtd_install_targets( TARGETS PythonCurveFittingModule INSTALL_DIRS ${BIN_DIR}/mantid/_plugins ${WORKBENCH_BIN_DIR}/mantid/_plugins )
 
 # Pure Python files
-install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid/_plugins )
+mtd_install_files( FILES ${PY_FILES} INSTALL_DIRS ${BIN_DIR}/mantid/_plugins ${WORKBENCH_BIN_DIR}/mantid/_plugins)
diff --git a/Framework/PythonInterface/mantid/api/CMakeLists.txt b/Framework/PythonInterface/mantid/api/CMakeLists.txt
index 0555fbc2c9f40f3c29ad3d37d03b077a572977c5..1158ba9e36554d939c067c406c435c0a95102f39 100644
--- a/Framework/PythonInterface/mantid/api/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/api/CMakeLists.txt
@@ -81,6 +81,7 @@ set ( EXPORT_FILES
   src/Exports/SpectrumInfoItem.cpp
   src/Exports/SpectrumInfoIterator.cpp
   src/Exports/SpectrumInfoPythonIterator.cpp
+  src/Exports/AnalysisDataServiceObserver.cpp
 )
 
 set ( MODULE_DEFINITION ${CMAKE_CURRENT_BINARY_DIR}/api.cpp )
@@ -99,6 +100,7 @@ set ( SRC_FILES
   src/PythonAlgorithm/DataProcessorAdapter.cpp
   src/CloneMatrixWorkspace.cpp
   src/ExtractWorkspace.cpp
+  src/Exports/AnalysisDataServiceObserverAdapter.cpp
 )
 
 set ( INC_FILES
@@ -109,12 +111,14 @@ set ( INC_FILES
   ${HEADER_DIR}/api/FitFunctions/IPeakFunctionAdapter.h
   ${HEADER_DIR}/api/PythonAlgorithm/AlgorithmAdapter.h
   ${HEADER_DIR}/api/PythonAlgorithm/DataProcessorAdapter.h
+  ${HEADER_DIR}/api/AnalysisDataServiceObserverAdapter.h
   ${HEADER_DIR}/api/BinaryOperations.h
   ${HEADER_DIR}/api/CloneMatrixWorkspace.h
   ${HEADER_DIR}/api/ExtractWorkspace.h
   ${HEADER_DIR}/api/WorkspacePropertyExporter.h
   ${HEADER_DIR}/api/SpectrumInfoPythonIterator.h
   ${HEADER_DIR}/api/DetectorInfoPythonIterator.h
+  ${HEADER_DIR}/api/ComponentInfoPythonIterator.h
 )
 
 set ( PY_FILES
@@ -172,7 +176,7 @@ endif ()
 ###########################################################################
 # Installation settings
 ###########################################################################
-install ( TARGETS PythonAPIModule ${SYSTEM_PACKAGE_TARGET} DESTINATION ${BIN_DIR}/mantid/api )
+mtd_install_targets( TARGETS PythonAPIModule INSTALL_DIRS ${BIN_DIR}/mantid/api ${WORKBENCH_BIN_DIR}/mantid/api )
 
 # Pure Python files
-install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid/api )
+mtd_install_files( FILES ${PY_FILES} INSTALL_DIRS ${BIN_DIR}/mantid/api ${WORKBENCH_BIN_DIR}/mantid/api)
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/AlgorithmObserver.cpp b/Framework/PythonInterface/mantid/api/src/Exports/AlgorithmObserver.cpp
index ac33395dcd6cf409acead24c46f1ce8aff625f71..3c70f0f155b9d874be80191f507db11a9f05ada4 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/AlgorithmObserver.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/AlgorithmObserver.cpp
@@ -9,7 +9,6 @@
 
 #include <boost/python/class.hpp>
 #include <boost/python/register_ptr_to_python.hpp>
-#include <iostream>
 
 using namespace Mantid::API;
 using namespace Mantid::PythonInterface;
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataService.cpp b/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataService.cpp
index 23213e94b6b6e0792f1d055be77f1737a44d3f35..bd12d58ac65f4f04f19695dad1f800c961871e71 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataService.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataService.cpp
@@ -77,5 +77,11 @@ void export_AnalysisDataService() {
       .def("retrieveWorkspaces", retrieveWorkspaces,
            AdsRetrieveWorkspacesOverloads(
                "Retrieve a list of workspaces by name",
-               (arg("self"), arg("names"), arg("unrollGroups") = false)));
+               (arg("self"), arg("names"), arg("unrollGroups") = false)))
+      .def("addToGroup", &AnalysisDataServiceImpl::addToGroup,
+           (arg("groupName"), arg("wsName")),
+           "Add a workspace in the ADS to a group in the ADS")
+      .def("removeFromGroup", &AnalysisDataServiceImpl::removeFromGroup,
+           (arg("groupName"), arg("wsName")),
+           "Remove a workspace from a group in the ADS");
 }
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataServiceObserver.cpp b/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataServiceObserver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8eb4ba7041187fd01d6973fd7a17ef62acdacad6
--- /dev/null
+++ b/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataServiceObserver.cpp
@@ -0,0 +1,55 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidAPI/AnalysisDataServiceObserver.h"
+#include "MantidPythonInterface/api/AnalysisDataServiceObserverAdapter.h"
+#include "MantidPythonInterface/kernel/GetPointer.h"
+
+#include <boost/python/bases.hpp>
+#include <boost/python/class.hpp>
+
+using namespace Mantid::API;
+using namespace Mantid::PythonInterface;
+using namespace boost::python;
+
+void export_AnalysisDataServiceObserver() {
+  boost::python::class_<AnalysisDataServiceObserver, bases<>,
+                        AnalysisDataServiceObserverAdapter, boost::noncopyable>(
+      "AnalysisDataServiceObserver",
+      "Observes AnalysisDataService notifications: all only")
+      .def("observeAll", &AnalysisDataServiceObserverAdapter::observeAll,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for any changes")
+      .def("observeAdd", &AnalysisDataServiceObserverAdapter::observeAdd,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for a workspace being added")
+      .def("observeReplace",
+           &AnalysisDataServiceObserverAdapter::observeReplace,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for a workspace being replaced")
+      .def("observeDelete", &AnalysisDataServiceObserverAdapter::observeDelete,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for a workspace being deleted")
+      .def("observeClear", &AnalysisDataServiceObserverAdapter::observeClear,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for it being cleared")
+      .def("observeRename", &AnalysisDataServiceObserverAdapter::observeRename,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for a workspace being renamed")
+      .def(
+          "observeGroup", &AnalysisDataServiceObserverAdapter::observeGroup,
+          (arg("self"), arg("on")),
+          "Observe AnalysisDataService for a group being added/made in the ADS")
+      .def("observeUnGroup",
+           &AnalysisDataServiceObserverAdapter::observeUnGroup,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for a group being removed from the ADS")
+      .def("observeGroupUpdate",
+           &AnalysisDataServiceObserverAdapter::observeGroupUpdate,
+           (arg("self"), arg("on")),
+           "Observe AnalysisDataService for a group being updated by being "
+           "added to or removed from");
+}
\ No newline at end of file
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataServiceObserverAdapter.cpp b/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataServiceObserverAdapter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..32e33c85472ef41ed0e592ec1a5252933f42b4b8
--- /dev/null
+++ b/Framework/PythonInterface/mantid/api/src/Exports/AnalysisDataServiceObserverAdapter.cpp
@@ -0,0 +1,98 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidPythonInterface/api/AnalysisDataServiceObserverAdapter.h"
+#include "MantidAPI/AnalysisDataServiceObserver.h"
+#include "MantidPythonInterface/core/CallMethod.h"
+
+namespace Mantid {
+namespace PythonInterface {
+
+AnalysisDataServiceObserverAdapter::AnalysisDataServiceObserverAdapter(
+    PyObject *self)
+    : API::AnalysisDataServiceObserver(), m_self(self) {}
+
+void AnalysisDataServiceObserverAdapter::anyChangeHandle() {
+  try {
+    return callMethod<void>(getSelf(), "anyChangeHandle");
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::addHandle(const std::string &wsName,
+                                                   const Workspace_sptr &ws) {
+  try {
+    return callMethod<void>(getSelf(), "addHandle", wsName, ws);
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::replaceHandle(
+    const std::string &wsName, const Workspace_sptr &ws) {
+  try {
+    return callMethod<void>(getSelf(), "replaceHandle", wsName, ws);
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::deleteHandle(
+    const std::string &wsName, const Workspace_sptr &ws) {
+  try {
+    return callMethod<void>(getSelf(), "deleteHandle", wsName, ws);
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::clearHandle() {
+  try {
+    return callMethod<void>(getSelf(), "clearHandle");
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::renameHandle(
+    const std::string &wsName, const std::string &newName) {
+  try {
+    return callMethod<void>(getSelf(), "renameHandle", wsName, newName);
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::groupHandle(const std::string &wsName,
+                                                     const Workspace_sptr &ws) {
+  try {
+    return callMethod<void>(getSelf(), "groupHandle", wsName, ws);
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::unGroupHandle(
+    const std::string &wsName, const Workspace_sptr &ws) {
+  try {
+    return callMethod<void>(getSelf(), "unGroupHandle", wsName, ws);
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+void AnalysisDataServiceObserverAdapter::groupUpdateHandle(
+    const std::string &wsName, const Workspace_sptr &ws) {
+  try {
+    return callMethod<void>(getSelf(), "groupUpdateHandle", wsName, ws);
+  } catch (UndefinedAttributeError &) {
+    return;
+  }
+}
+
+} // namespace PythonInterface
+} // namespace Mantid
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/FileFinder.cpp b/Framework/PythonInterface/mantid/api/src/Exports/FileFinder.cpp
index 6a7eef7aa6ca46ef5e063d3a4b70ac431ea77851..424e6fe1be22f9bd62107446790a8ffb2afee766 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/FileFinder.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/FileFinder.cpp
@@ -8,6 +8,7 @@
 #include "MantidKernel/WarningSuppressions.h"
 #include "MantidPythonInterface/core/ReleaseGlobalInterpreterLock.h"
 #include <boost/python/class.hpp>
+#include <boost/python/list.hpp>
 #include <boost/python/overloads.hpp>
 #include <boost/python/reference_existing_object.hpp>
 
@@ -30,18 +31,28 @@ GNU_DIAG_ON("unused-local-typedef")
 /**
  * Runs FileFinder.findRuns after releasing the python GIL.
  * @param self :: A reference to the calling object
- * @param hinstr :: A string containing the run number and possibly instrument
+ * @param hintstr :: A string containing the run number and possibly instrument
  * to search for
+ * @param exts_list :: A python list containing strings of file extensions to
+ * search
+ * @param useExtsOnly :: bool. If true, use exts_list only. If false, use
+ * combination of exts_list and facility_exts.
  */
 std::vector<std::string> runFinderProxy(FileFinderImpl &self,
-                                        std::string hinstr) {
+                                        std::string hintstr, list exts_list,
+                                        const bool useExtsOnly) {
+  // Convert python list to c++ vector
+  std::vector<std::string> exts;
+  for (int i = 0; i < len(exts_list); ++i)
+    exts.push_back(extract<std::string>(exts_list[i]));
+
   //   Before calling the function we need to release the GIL,
   //   drop the Python threadstate and reset anything installed
   //   via PyEval_SetTrace while we execute the C++ code -
   //   ReleaseGlobalInterpreter does this for us
   Mantid::PythonInterface::ReleaseGlobalInterpreterLock
       releaseGlobalInterpreterLock;
-  return self.findRuns(hinstr);
+  return self.findRuns(hintstr, exts, useExtsOnly);
 }
 
 void export_FileFinder() {
@@ -52,11 +63,17 @@ void export_FileFinder() {
                "Return a full path to the given file if it can be found within "
                "datasearch.directories paths. Directories can be ignored with "
                "ignoreDirs=True. An empty string is returned otherwise."))
-      .def("findRuns", &runFinderProxy, (arg("self"), arg("hintstr")),
+      .def("findRuns", &runFinderProxy,
+           (arg("self"), arg("hintstr"), arg("exts_list") = list(),
+            arg("useExtsOnly") = false),
            "Find a list of files file given a hint. "
            "The hint can be a comma separated list of run numbers and can also "
            "include ranges of runs, e.g. 123-135 or equivalently 123-35"
-           "If no instrument prefix is given then the current default is used.")
+           "If no instrument prefix is given then the current default is used."
+           "exts_list is an optional list containing strings of file "
+           "extensions to search."
+           "useExtsOnly is an optional bool. If it's true then don't use "
+           "facility exts.")
       .def("getCaseSensitive", &FileFinderImpl::getCaseSensitive, (arg("self")),
            "Option to get if file finder should be case sensitive.")
       .def("setCaseSensitive", &FileFinderImpl::setCaseSensitive,
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp
index 006e403de8002742763e91368ad0af6ac4eed4d9..c66f1ead66d595a1fa4be7003ce5080e6066d77e 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp
@@ -5,6 +5,7 @@
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidAPI/IEventList.h"
+#include "MantidPythonInterface/kernel/Converters/NDArrayToVector.h"
 #include "MantidPythonInterface/kernel/GetPointer.h"
 #include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h"
 #include <boost/python/class.hpp>
@@ -18,11 +19,16 @@ using Mantid::API::TOF;
 using Mantid::API::WEIGHTED;
 using Mantid::API::WEIGHTED_NOTIME;
 
-namespace Policies = Mantid::PythonInterface::Policies;
+using namespace Mantid::PythonInterface;
 using namespace boost::python;
 
 GET_POINTER_SPECIALIZATION(IEventList)
 
+namespace {
+void maskCondition(IEventList &self, const NDArray &data) {
+  self.maskCondition(Converters::NDArrayToVector<bool>(data)());
+}
+} // namespace
 /// return_value_policy for copied numpy array
 using return_clone_numpy = return_value_policy<Policies::VectorToNumpy>;
 
@@ -67,6 +73,8 @@ void export_IEventList() {
       .def("maskTof", &IEventList::maskTof, args("self", "tofMin", "tofMax"),
            "Mask out events that have a tof between tofMin and tofMax "
            "(inclusively)")
+      .def("maskCondition", &maskCondition, args("self", "mask"),
+           "Mask out events by the condition vector")
       .def("getTofs",
            (std::vector<double>(IEventList::*)(void) const) &
                IEventList::getTofs,
@@ -84,6 +92,10 @@ void export_IEventList() {
            "Get a vector of the weights of the events")
       .def("getPulseTimes", &IEventList::getPulseTimes, args("self"),
            "Get a vector of the pulse times of the events")
+      .def("getPulseTimeMax", &IEventList::getPulseTimeMax, args("self"),
+           "The maximum pulse time for the list of the events.")
+      .def("getPulseTimeMin", &IEventList::getPulseTimeMin, args("self"),
+           "The minimum pulse time for the list of the events.")
       .def("getTofMin", &IEventList::getTofMin, args("self"),
            "The minimum tof value for the list of the events.")
       .def("getTofMax", &IEventList::getTofMax, args("self"),
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp
index 291268be925844004be324f06627aa6881c14c69..411660fd56c4323b3b7462b3b81dcde1725b86a6 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp
@@ -45,6 +45,12 @@ void export_IEventWorkspace() {
       .def("getTofMax", &IEventWorkspace::getTofMax, args("self"),
            "Returns the maximum TOF value (in microseconds) held by the "
            ":class:`~mantid.api.Workspace`")
+      .def("getPulseTimeMin", &IEventWorkspace::getPulseTimeMin, args("self"),
+           "Returns the minimum pulse time held by the "
+           ":class:`~mantid.api.Workspace`")
+      .def("getPulseTimeMax", &IEventWorkspace::getPulseTimeMax, args("self"),
+           "Returns the maximum pulse time held by the "
+           ":class:`~mantid.api.Workspace`")
       .def("getEventList", &deprecatedGetEventList,
            return_internal_reference<>(), args("self", "workspace_index"),
            "Return the :class:`~mantid.api.IEventList` managing the events at "
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/ITableWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/ITableWorkspace.cpp
index d1a9f270e68717e259c0964afd9f10fea8b747ca..aebe266ed2d00f06c1defd73d351bf44e7b33544 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/ITableWorkspace.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/ITableWorkspace.cpp
@@ -12,6 +12,7 @@
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidKernel/V3D.h"
 #include "MantidPythonInterface/core/NDArray.h"
+#include "MantidPythonInterface/core/VersionCompat.h"
 #include "MantidPythonInterface/kernel/Converters/CloneToNumpy.h"
 #include "MantidPythonInterface/kernel/Converters/NDArrayToVector.h"
 #include "MantidPythonInterface/kernel/Converters/PySequenceToVector.h"
@@ -26,6 +27,7 @@
 #include <boost/python/dict.hpp>
 #include <boost/python/list.hpp>
 #include <boost/python/make_constructor.hpp>
+
 #include <cstring>
 #include <vector>
 
@@ -296,6 +298,25 @@ PyObject *row(ITableWorkspace &self, int row) {
   return result;
 }
 
+/**
+ * Return the C++ types for all columns
+ * @param self A reference to the TableWorkspace python object that we were
+ * called on
+ */
+boost::python::list columnTypes(ITableWorkspace &self) {
+  int numCols = static_cast<int>(self.columnCount());
+
+  boost::python::list types;
+
+  for (int col = 0; col < numCols; col++) {
+    const auto column = self.getColumn(col);
+    const auto &type = column->type();
+    types.append(type);
+  }
+
+  return types;
+}
+
 /**
  * Adds a new row in the table, where the items are given in a dictionary
  * object mapping {column name:value}. It must contain a key-value entry for
@@ -450,12 +471,16 @@ PyObject *cell(ITableWorkspace &self, const object &value, int row_or_col) {
  * column if value is an index
  */
 void setCell(ITableWorkspace &self, const object &col_or_row,
-             const int row_or_col, const object &value) {
+             const int row_or_col, const object &value,
+             const bool &notify_replace) {
   Mantid::API::Column_sptr column;
   int row(-1);
   getCellLoc(self, col_or_row, row_or_col, column, row);
   setValue(column, row, value);
-  self.modified();
+
+  if (notify_replace) {
+    self.modified();
+  }
 }
 } // namespace
 
@@ -629,6 +654,9 @@ void export_ITableWorkspace() {
       .def("row", &row, (arg("self"), arg("row")),
            "Return all values of a specific row as a dict.")
 
+      .def("columnTypes", &columnTypes, arg("self"),
+           "Return the types of the columns as a list")
+
       // FromSequence must come first since it takes an object parameter
       // Otherwise, FromDict will never be called as object accepts anything
       .def("addRow", &addRowFromSequence, (arg("self"), arg("row_items_seq")),
@@ -646,10 +674,11 @@ void export_ITableWorkspace() {
 
       .def("setCell", &setCell,
            (arg("self"), arg("row_or_column"), arg("column_or_row"),
-            arg("value")),
+            arg("value"), arg("notify_replace") = true),
            "Sets the value of a given cell. If the row_or_column argument is a "
            "number then it is interpreted as a row otherwise it "
-           "is interpreted as a column name.")
+           "is interpreted as a column name. If notify replace is false, then "
+           "the replace workspace event is not triggered.")
 
       .def("toDict", &toDict, (arg("self")),
            "Gets the values of this workspace as a dictionary. The keys of the "
diff --git a/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt b/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt
index be9fd123dd24e43432bde62d64619e7b1a7a4cfb..e559622eed48ec3d84c078dbb646534ff41ce25b 100644
--- a/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt
@@ -93,7 +93,7 @@ endif ()
 ###########################################################################
 # Installation settings
 ###########################################################################
-install ( TARGETS PythonDataObjectsModule ${SYSTEM_PACKAGE_TARGET} DESTINATION ${BIN_DIR}/mantid/dataobjects )
+mtd_install_targets( TARGETS PythonDataObjectsModule INSTALL_DIRS ${BIN_DIR}/mantid/dataobjects ${WORKBENCH_BIN_DIR}/mantid/dataobjects )
 
 # Pure Python files
-install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid/dataobjects )
+mtd_install_files( FILES ${PY_FILES} INSTALL_DIRS ${BIN_DIR}/mantid/dataobjects ${WORKBENCH_BIN_DIR}/mantid/dataobjects)
diff --git a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt
index b96ce5a042a002d7ad7447bf141b1334a2bd16c7..afb043288b4f5f15a53dd6c39e201b2aa6edd191 100644
--- a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt
@@ -41,9 +41,10 @@ set ( EXPORT_FILES
   src/Exports/ReflectionGenerator.cpp
   src/Exports/DetectorInfo.cpp
   src/Exports/DetectorInfoItem.cpp
-  src/Exports/DetectorInfoIterator.cpp
   src/Exports/DetectorInfoPythonIterator.cpp
   src/Exports/ComponentInfo.cpp
+  src/Exports/ComponentInfoItem.cpp
+  src/Exports/ComponentInfoPythonIterator.cpp
 )
 
 #############################################################################################
@@ -111,7 +112,7 @@ endif ()
 ###########################################################################
 # Installation settings
 ###########################################################################
-install ( TARGETS PythonGeometryModule ${SYSTEM_PACKAGE_TARGET} DESTINATION ${BIN_DIR}/mantid/geometry )
+mtd_install_targets( TARGETS PythonGeometryModule INSTALL_DIRS ${BIN_DIR}/mantid/geometry ${WORKBENCH_BIN_DIR}/mantid/geometry )
 
 # Pure Python files
-install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid/geometry )
+mtd_install_files( FILES ${PY_FILES} INSTALL_DIRS ${BIN_DIR}/mantid/geometry ${WORKBENCH_BIN_DIR}/mantid/geometry)
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp
index a9b068b3b2e427ea26ecb5857d3562d8d53fe3c4..9ecf5d49229f3a623be9caf3d091ffc866f6dd63 100644
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp
@@ -8,6 +8,7 @@
 #include "MantidGeometry/Objects/IObject.h"
 #include "MantidKernel/Quat.h"
 #include "MantidKernel/V3D.h"
+#include "MantidPythonInterface/api/ComponentInfoPythonIterator.h"
 #include "MantidPythonInterface/core/Converters/WrapWithNDArray.h"
 #include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h"
 
@@ -19,10 +20,17 @@
 using Mantid::Geometry::ComponentInfo;
 using Mantid::Kernel::Quat;
 using Mantid::Kernel::V3D;
+using Mantid::PythonInterface::ComponentInfoPythonIterator;
 using namespace Mantid::PythonInterface::Converters;
 using namespace Mantid::PythonInterface::Policies;
 using namespace boost::python;
 
+namespace {
+ComponentInfoPythonIterator make_pyiterator(ComponentInfo &componentInfo) {
+  return ComponentInfoPythonIterator(componentInfo);
+}
+} // namespace
+
 // Function pointers to help resolve ambiguity
 Mantid::Kernel::V3D (ComponentInfo::*position)(const size_t) const =
     &ComponentInfo::position;
@@ -40,6 +48,8 @@ void (ComponentInfo::*setRotation)(const size_t, const Mantid::Kernel::Quat &) =
 void export_ComponentInfo() {
   class_<ComponentInfo, boost::noncopyable>("ComponentInfo", no_init)
 
+      .def("__iter__", make_pyiterator)
+
       .def("__len__", &ComponentInfo::size, arg("self"),
            "Returns the number of components.")
 
@@ -138,5 +148,12 @@ void export_ComponentInfo() {
 
       .def("shape", &ComponentInfo::shape, (arg("self"), arg("index")),
            return_value_policy<reference_existing_object>(),
-           "Returns the shape of the component identified by 'index'.");
+           "Returns the shape of the component identified by 'index'.")
+
+      .def("indexOfAny", &ComponentInfo::indexOfAny, (arg("self"), arg("name")),
+           "Returns the index of any component matching name. Raises "
+           "ValueError if name not found")
+
+      .def("root", &ComponentInfo::root, arg("self"),
+           "Returns the index of the root component");
 }
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoItem.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoItem.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7e9feba6251bcaf2b671d0689524eed4d0147e36
--- /dev/null
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoItem.cpp
@@ -0,0 +1,50 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidGeometry/Instrument/ComponentInfoItem.h"
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include "MantidKernel/Quat.h"
+#include "MantidKernel/V3D.h"
+#include "MantidPythonInterface/core/Converters/WrapWithNDArray.h"
+#include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h"
+#include <boost/python/class.hpp>
+#include <boost/python/make_function.hpp>
+#include <boost/python/module.hpp>
+
+using Mantid::Geometry::ComponentInfo;
+using Mantid::Geometry::ComponentInfoItem;
+using Mantid::Kernel::V3D;
+using namespace boost::python;
+using namespace Mantid::PythonInterface::Converters;
+using namespace Mantid::PythonInterface::Policies;
+
+// Export DetectorInfoItem
+void export_ComponentInfoItem() {
+
+  // Export to Python
+  class_<ComponentInfoItem<ComponentInfo>>("ComponentInfoItem", no_init)
+      .add_property("isDetector", &ComponentInfoItem<ComponentInfo>::isDetector)
+      .add_property(
+          "componentsInSubtree",
+          make_function(&ComponentInfoItem<ComponentInfo>::componentsInSubtree,
+                        return_value_policy<VectorToNumpy>()))
+      .add_property(
+          "detectorsInSubtree",
+          make_function(&ComponentInfoItem<ComponentInfo>::detectorsInSubtree,
+                        return_value_policy<VectorToNumpy>()))
+      .add_property("position", &ComponentInfoItem<ComponentInfo>::position)
+      .add_property("rotation", &ComponentInfoItem<ComponentInfo>::rotation)
+      .add_property("parent", &ComponentInfoItem<ComponentInfo>::parent)
+      .add_property("hasParent", &ComponentInfoItem<ComponentInfo>::hasParent)
+      .add_property("scaleFactor",
+                    &ComponentInfoItem<ComponentInfo>::scaleFactor)
+      .add_property("name", &ComponentInfoItem<ComponentInfo>::name)
+      .add_property(
+          "children",
+          make_function(&ComponentInfoItem<ComponentInfo>::children,
+                        return_value_policy<VectorRefToNumpy<WrapReadOnly>>()))
+      .add_property("index", &ComponentInfoItem<ComponentInfo>::index);
+}
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoPythonIterator.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoPythonIterator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a07add6f07db2fe2d3be67a1ff3d89cdfe8735b5
--- /dev/null
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoPythonIterator.cpp
@@ -0,0 +1,28 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidPythonInterface/api/ComponentInfoPythonIterator.h"
+#include "MantidPythonInterface/core/VersionCompat.h"
+#include <boost/python/class.hpp>
+#include <boost/python/iterator.hpp>
+#include <boost/python/module.hpp>
+
+using Mantid::PythonInterface::ComponentInfoPythonIterator;
+using namespace boost::python;
+
+// Export ComponentInfoPythonIterator
+void export_ComponentInfoPythonIterator() {
+
+  // Export to Python
+  class_<ComponentInfoPythonIterator>("ComponentInfoPythonIterator", no_init)
+      .def("__iter__", objects::identity_function())
+#ifdef IS_PY3K
+      .def("__next__", &ComponentInfoPythonIterator::next)
+#else
+      .def("next", &ComponentInfoPythonIterator::next)
+#endif
+      ;
+}
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp
index f348ac26520887c9dfb1adfeeaa6fc775b4fba08..d8e57ebe670007cc40614fa290b53457a2a40c84 100644
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp
@@ -62,6 +62,8 @@ void export_DetectorInfo() {
   void (DetectorInfo::*setMasked)(const size_t, bool) =
       &DetectorInfo::setMasked;
 
+  double (DetectorInfo::*l2)(const size_t) const = &DetectorInfo::l2;
+
   // Export to Python
   class_<DetectorInfo, boost::noncopyable>("DetectorInfo", no_init)
 
@@ -105,5 +107,9 @@ void export_DetectorInfo() {
            "Returns the absolute rotation of the detector where the detector "
            "is identified by 'index'.")
       .def("detectorIDs", &DetectorInfo::detectorIDs, return_readonly_numpy(),
-           "Returns all detector ids sorted by detector index");
+           "Returns all detector ids sorted by detector index")
+      .def("l2", l2, (arg("self"), arg("index")),
+           "Returns the l2 scattering distance")
+      .def("l1", &DetectorInfo::l1, arg("self"),
+           "Returns the l1 scattering distance");
 }
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp
index 09445dbb303b113314cbe711cb0ce91842e914d3..e9b2a2d346105485f89134f4bb554b0ae5c5a4c9 100644
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp
@@ -27,6 +27,8 @@ void export_DetectorInfoItem() {
       .add_property("twoTheta", &DetectorInfoItem<DetectorInfo>::twoTheta)
       .add_property("position", &DetectorInfoItem<DetectorInfo>::position)
       .add_property("rotation", &DetectorInfoItem<DetectorInfo>::rotation)
+      .add_property("l2", &DetectorInfoItem<DetectorInfo>::l2)
+      .add_property("index", &DetectorInfoItem<DetectorInfo>::index)
       .def("setMasked", &DetectorInfoItem<DetectorInfo>::setMasked,
            (arg("self"), arg("masked")), "Set the mask flag for the detector");
 }
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoIterator.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoIterator.cpp
deleted file mode 100644
index 9f099aff92a1c60986ba11c48adce3fca83d8d2f..0000000000000000000000000000000000000000
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoIterator.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-//     NScD Oak Ridge National Laboratory, European Spallation Source
-//     & Institut Laue - Langevin
-// SPDX - License - Identifier: GPL - 3.0 +
-#include "MantidGeometry/Instrument/DetectorInfoIterator.h"
-#include "MantidGeometry/Instrument/DetectorInfo.h"
-
-#include <boost/python/class.hpp>
-#include <boost/python/module.hpp>
-
-using Mantid::Geometry::DetectorInfo;
-using Mantid::Geometry::DetectorInfoIterator;
-using namespace boost::python;
-
-// Export DetectorInfoIterator
-void export_DetectorInfoIterator() {
-
-  // Export to Python
-  class_<DetectorInfoIterator<DetectorInfo>>("DetectorInfoIterator", no_init);
-}
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoPythonIterator.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoPythonIterator.cpp
index 2f104addd520e52c1b02cd755003388571e59bf8..0c7db21d73591acce9aea5acdf57eb8a0651edef 100644
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoPythonIterator.cpp
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoPythonIterator.cpp
@@ -5,9 +5,8 @@
 //     & Institut Laue - Langevin
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidPythonInterface/api/DetectorInfoPythonIterator.h"
-
+#include "MantidPythonInterface/core/VersionCompat.h"
 #include <boost/python/class.hpp>
-#include <boost/python/copy_const_reference.hpp>
 #include <boost/python/iterator.hpp>
 #include <boost/python/module.hpp>
 
@@ -20,22 +19,10 @@ void export_DetectorInfoPythonIterator() {
   // Export to Python
   class_<DetectorInfoPythonIterator>("DetectorInfoPythonIterator", no_init)
       .def("__iter__", objects::identity_function())
-      .def(
-#if PY_VERSION_HEX >= 0x03000000
-          "__next__"
+#ifdef IS_PY3K
+      .def("__next__", &DetectorInfoPythonIterator::next)
 #else
-          "next"
+      .def("next", &DetectorInfoPythonIterator::next)
 #endif
-          ,
-          &DetectorInfoPythonIterator::next,
-          return_value_policy<copy_const_reference>());
-  /*
-   Return value policy for next is to copy the const reference. Copy by value is
-   essential for python 2.0 compatibility because items (DetectorInfoItem) will
-   outlive their iterators if declared as part of for loops. There is no good
-   way to deal with this other than to force a copy so that internals of the
-   item are not also corrupted. Future developers may wish to choose a separte
-   policy for python 3.0 where this is not a concern, and const ref returns
-   would be faster.
-  */
+      ;
 }
diff --git a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
index def81cd351727e8fa331d7a8592afd295cff8fe4..e5558d77b59c38867b63d4ab4fcbe743aab48b30 100644
--- a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
@@ -194,13 +194,16 @@ endif ()
 ###########################################################################
 # Installation settings
 ###########################################################################
-install ( TARGETS PythonKernelModule ${SYSTEM_PACKAGE_TARGET} DESTINATION
-          ${BIN_DIR}/mantid/kernel )
+mtd_install_targets( TARGETS PythonKernelModule INSTALL_DIRS ${BIN_DIR}/mantid/kernel ${WORKBENCH_BIN_DIR}/mantid/kernel )
 
 # Pure Python files
-install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid/kernel )
+mtd_install_files( FILES ${PY_FILES} INSTALL_DIRS ${BIN_DIR}/mantid/kernel ${WORKBENCH_BIN_DIR}/mantid/kernel)
+
 # packagesetup.py that will overwrite the ones from the built target
-install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGESETUP_PY}.install.py DESTINATION
-          ${BIN_DIR}/mantid/kernel RENAME ${PACKAGESETUP_PY}.py )
-install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/${MPISETUP_PY}.install.py DESTINATION
-          ${BIN_DIR}/mantid/kernel RENAME ${MPISETUP_PY}.py )
+mtd_install_files( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGESETUP_PY}.install.py 
+                   INSTALL_DIRS ${BIN_DIR}/mantid/kernel ${WORKBENCH_BIN_DIR}/mantid/kernel
+                   RENAME ${PACKAGESETUP_PY}.py )
+
+mtd_install_files(FILES ${CMAKE_CURRENT_BINARY_DIR}/${MPISETUP_PY}.install.py
+                  INSTALL_DIRS ${BIN_DIR}/mantid/kernel ${WORKBENCH_BIN_DIR}/mantid/kernel
+                  RENAME ${MPISETUP_PY}.py)
diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/RebinParamsValidator.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/RebinParamsValidator.cpp
index 68f11dad61bb266de730d6738ad320d620f5f086..ba8f8e430a79e58396ab5cc0d4b8a37bbf8a79eb 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Exports/RebinParamsValidator.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Exports/RebinParamsValidator.cpp
@@ -26,5 +26,7 @@ void export_RebinParamsValidator() {
       .def("__init__",
            make_constructor(
                &createRebinParamsValidator, default_call_policies(),
-               (arg("AllowEmpty") = false, arg("AllowRange") = false)));
+               (arg("AllowEmpty") = false, arg("AllowRange") = false)),
+           "Constructs a validator verifying that the given float array is "
+           "valid sequence of rebinning parameters.");
 }
diff --git a/Framework/PythonInterface/mantid/plots/CMakeLists.txt b/Framework/PythonInterface/mantid/plots/CMakeLists.txt
index 0554701e3828ad5e2fb02c16be247b436bbbd4c1..dee130a204683eb7806bcfa4c16f2336f12bf5fb 100644
--- a/Framework/PythonInterface/mantid/plots/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/plots/CMakeLists.txt
@@ -6,7 +6,6 @@ set ( PY_FILES
   utility.py
 )
 
-
 #############################################################################################
 # Copy over the pure Python files for the module
 #############################################################################################
@@ -28,4 +27,4 @@ add_custom_target ( PythonPlotsModule ALL DEPENDS ${PYTHON_INSTALL_FILES} )
 # Installation settings
 ###########################################################################
 # Pure Python files
-install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid/plots )
+mtd_install_files( FILES ${PY_FILES} INSTALL_DIRS ${WORKBENCH_BIN_DIR}/mantid/plots ${BIN_DIR}/mantid/plots )
diff --git a/Framework/PythonInterface/mantid/plots/__init__.py b/Framework/PythonInterface/mantid/plots/__init__.py
index b910bc6ff731d5aa7ff72a1532ce232f707230ba..aaadcf0a9e84d30f00c40561e385ab00184875b4 100644
--- a/Framework/PythonInterface/mantid/plots/__init__.py
+++ b/Framework/PythonInterface/mantid/plots/__init__.py
@@ -15,11 +15,16 @@ Functionality for unpacking mantid objects for plotting with matplotlib.
 # of the main package.
 from __future__ import (absolute_import, division, print_function)
 
-import mantid.kernel
-import mantid.plots.plotfunctions
-import mantid.plots.plotfunctions3D
+from collections import Iterable
+
+from mantid.kernel import logger
+from mantid.plots import helperfunctions, plotfunctions
+from mantid.plots import plotfunctions3D
+from matplotlib import cbook
 from matplotlib.axes import Axes
+from matplotlib.container import Container
 from matplotlib.projections import register_projection
+from matplotlib.colors import Colormap
 
 try:
     from mpl_toolkits.mplot3d.axes3d import Axes3D
@@ -42,9 +47,69 @@ except ImportError:
     from mpl_toolkits.mplot3d.axes3d import Axes3D
 
 
+def plot_decorator(func):
+    def wrapper(self, *args, **kwargs):
+        func_value = func(self, *args, **kwargs)
+        # Saves saving it on array objects
+        if helperfunctions.validate_args(*args, **kwargs):
+            # Fill out kwargs with the values of args
+            kwargs["workspaces"] = args[0].name()
+            kwargs["function"] = func.__name__
+            if "cmap" in kwargs and isinstance(kwargs["cmap"], Colormap):
+                kwargs["cmap"] = kwargs["cmap"].name
+            self.creation_args.append(kwargs)
+        return func_value
+    return wrapper
+
+
+class _WorkspaceArtists(object):
+    """Captures information regarding an artist that has been plotted
+    from a workspace. It allows for removal and replacement of said artists
+
+    """
+    def __init__(self, artists, data_replace_cb):
+        """
+        Initialize an instance
+        :param artists: A reference to a list of artists "attached" to a workspace
+        :param data_replace_cb: A reference to a callable with signature (artists, workspace) -> new_artists
+        """
+        self._set_artists(artists)
+        self._data_replace_cb = data_replace_cb
+
+    def remove(self, axes):
+        """
+        Remove the tracked artists from the given axes
+        :param axes: A reference to the axes instance the artists are attached to
+        """
+        # delete the artists from the axes
+        for artist in self._artists:
+            artist.remove()
+            # Remove doesn't catch removing the container for errorbars etc
+            if isinstance(artist, Container):
+                try:
+                    axes.containers.remove(artist)
+                except ValueError:
+                    pass
+
+        if (not axes.is_empty()) and axes.legend_ is not None:
+            axes.legend()
+
+    def replace_data(self, workspace):
+        """Replace or replot artists based on a new workspace
+        :param workspace: The new workspace containing the data
+        """
+        self._set_artists(self._data_replace_cb(self._artists, workspace))
+
+    def _set_artists(self, artists):
+        """Ensure the stored artists is an iterable"""
+        if isinstance(artists, Container) or not isinstance(artists, Iterable):
+            self._artists = [artists]
+        else:
+            self._artists = artists
+
 
 class MantidAxes(Axes):
-    '''
+    """
     This class defines the **mantid** projection for 2d plotting. One chooses
     this projection using::
 
@@ -60,14 +125,88 @@ class MantidAxes(Axes):
         ax = fig.add_subplot(111,projection='mantid')
 
     The mantid projection allows replacing the array objects with mantid workspaces.
-    '''
-
+    """
+    # Required by Axes base class
     name = 'mantid'
+
+    # Enumerators for plotting directions
     HORIZONTAL = BIN = 0
     VERTICAL = SPECTRUM = 1
 
+    # Store information for any workspaces attached to this axes instance
+    tracked_workspaces = None
+
+    def __init__(self, *args, **kwargs):
+        super(MantidAxes, self).__init__(*args, **kwargs)
+        self.tracked_workspaces = dict()
+        self.creation_args = []
+
+    def track_workspace_artist(self, workspace, artists, data_replace_cb=None):
+        """
+        Add the given workspace's name to the list of workspaces
+        displayed on this Axes instance
+        :param workspace: The name of the workspace. If empty then no tracking takes place
+        :param artists: A single artist or iterable of artists containing the data for the workspace
+        :param data_replace_cb: A function to call when the data is replaced to update
+        the artist (optional)
+        :returns: The artists variable as it was passed in.
+        """
+        name = workspace.name()
+        if name:
+            if data_replace_cb is None:
+                def data_replace_cb(_, __):
+                    logger.warning("Updating data on this plot type is not yet supported")
+            artist_info = self.tracked_workspaces.setdefault(name, [])
+            artist_info.append(_WorkspaceArtists(artists, data_replace_cb))
+
+        return artists
+
+    def remove_workspace_artists(self, workspace):
+        """
+        Remove the artists reference by this workspace (if any) and return True
+        if the axes is then empty
+        :param workspace: The name of the workspace
+        :return: True if the axes is empty, false if artists remain or this workspace is not associated here
+        """
+        try:
+            # pop to ensure we don't hold onto an artist reference
+            artist_info = self.tracked_workspaces.pop(workspace.name())
+        except KeyError:
+            return False
+
+        for workspace_artist in artist_info:
+            workspace_artist.remove(self)
+        return self.is_empty()
+
+    def replace_workspace_artists(self, workspace):
+        """
+        Replace the data of any artists relating to this workspace.
+        The axes are NOT redrawn
+        :param workspace: The workspace containing the new data
+        :return : True if data was replace, false otherwise
+        """
+        try:
+            artist_info = self.tracked_workspaces[workspace.name()]
+        except KeyError:
+            return False
+
+        for workspace_artist in artist_info:
+            workspace_artist.replace_data(workspace)
+        return True
+
+    def is_empty(self):
+        """
+        Checks the known artist containers to see if anything exists within them
+        :return: True if no artists exist, false otherwise
+        """
+        def _empty(container):
+            return len(container) == 0
+        return _empty(self.lines) and _empty(self.images) and _empty(self.collections)\
+               and _empty(self.containers)
+
+    @plot_decorator
     def plot(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.plot` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -83,16 +222,26 @@ class MantidAxes(Axes):
             ax.plot(x,y,'bo')                 #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.plot`.
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args, **kwargs):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.plot(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.plot`.
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+
+            def _data_update(artists, workspace):
+                # It's only possible to plot 1 line at a time from a workspace
+                x, y, _, __ = plotfunctions._plot_impl(self, workspace, args, kwargs)
+                artists[0].set_data(x, y)
+                self.relim()
+                self.autoscale()
+                return artists
+
+            return self.track_workspace_artist(args[0], plotfunctions.plot(self, *args, **kwargs), _data_update)
         else:
             return Axes.plot(self, *args, **kwargs)
 
+    @plot_decorator
     def scatter(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.scatter` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -108,16 +257,16 @@ class MantidAxes(Axes):
             ax.scatter(x,y,'bo')                 #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.scatter`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.scatter(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.scatter`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
         else:
             return Axes.scatter(self, *args, **kwargs)
 
+    @plot_decorator
     def errorbar(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.errorbar` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -133,16 +282,45 @@ class MantidAxes(Axes):
             ax.errorbar(x,y,yerr,'bo')            #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.errorbar`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.errorbar(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.errorbar`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+
+            def _data_update(artists, workspace):
+                # errorbar with workspaces can only return a single container
+                container_orig = artists[0]
+                # It is not possible to simply reset the error bars so
+                # we have to plot new lines but ensure we don't reorder them on the plot!
+                orig_idx = self.containers.index(container_orig)
+                container_orig.remove()
+                # The container does not remove itself from the containers list
+                # but protect this just in case matplotlib starts doing this
+                try:
+                    self.containers.remove(container_orig)
+                except ValueError:
+                    pass
+                # this gets pushed back onto the containers list
+                container_new = plotfunctions.errorbar(self, workspace, **kwargs)
+                self.containers.insert(orig_idx, container_new)
+                self.containers.pop()
+                # update line properties to match original
+                orig_flat, new_flat = cbook.flatten(container_orig), cbook.flatten(container_new)
+                for artist_orig, artist_new in zip(orig_flat, new_flat):
+                     artist_new.update_from(artist_orig)
+                # ax.relim does not support collections...
+                self._update_line_limits(container_new[0])
+                self.autoscale()
+                return container_new
+
+            return self.track_workspace_artist(args[0], plotfunctions.errorbar(self, *args, **kwargs),
+                                               _data_update)
         else:
             return Axes.errorbar(self, *args, **kwargs)
 
+    @plot_decorator
     def pcolor(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.pcolor` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -158,22 +336,19 @@ class MantidAxes(Axes):
             ax.pcolor(x,y,C)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolor`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.pcolor(self, *args, **kwargs)
-        else:
-            return Axes.pcolor(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.pcolor`
+        """
+        return self._pcolor_func('pcolor', *args, **kwargs)
 
+    @plot_decorator
     def pcolorfast(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.pcolorfast` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
         or :class:`mantid.api.IMDHistoWorkspace`. You can have something like::
 
-            import matplotlib.pyplot as plt
+            import matpolotlib.pyplot as plt
             from mantid import plots
 
             ...
@@ -183,16 +358,13 @@ class MantidAxes(Axes):
             ax.pcolorfast(x,y,C)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolorfast`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.pcolorfast(self, *args, **kwargs)
-        else:
-            return Axes.pcolorfast(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.pcolorfast`
+        """
+        return self._pcolor_func('pcolorfast', *args, **kwargs)
 
+    @plot_decorator
     def pcolormesh(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.pcolormesh` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -208,16 +380,13 @@ class MantidAxes(Axes):
             ax.pcolormesh(x,y,C)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolormesh`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.pcolormesh(self, *args, **kwargs)
-        else:
-            return Axes.pcolormesh(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.pcolormesh`
+        """
+        return self._pcolor_func('pcolormesh', *args, **kwargs)
 
+    @plot_decorator
     def imshow(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.imshow` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -233,16 +402,79 @@ class MantidAxes(Axes):
             ax.imshow(C)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.imshow`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.imshow(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.imshow`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+
+            def _update_data(artists, workspace):
+                return self._redraw_colorplot(plotfunctions.imshow,
+                                              artists, workspace, **kwargs)
+
+            return self.track_workspace_artist(args[0], plotfunctions.imshow(self, *args, **kwargs),
+                                               _update_data)
         else:
             return Axes.imshow(self, *args, **kwargs)
 
+    def _pcolor_func(self, name, *args, **kwargs):
+        """
+        Implementation of pcolor-style methods
+        :param name: The name of the method
+        :param args: The args passed from the user
+        :param kwargs: The kwargs passed from the use
+        :return: The return value of the pcolor* function
+        """
+        plotfunctions_func = getattr(plotfunctions, name)
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+
+            def _update_data(artists, workspace):
+                return self._redraw_colorplot(plotfunctions_func,
+                                              artists, workspace, **kwargs)
+            # We return the last mesh so the return type is a single artist like the standard Axes
+            artists = self.track_workspace_artist(args[0], plotfunctions_func(self, *args, **kwargs),
+                                                  _update_data)
+            try:
+                return artists[-1]
+            except TypeError:
+                return artists
+        else:
+            return getattr(Axes, name)(self, *args, **kwargs)
+
+    def _redraw_colorplot(self, colorfunc, artists_orig, workspace,
+                          **kwargs):
+        """
+        Redraw a pcolor* or imshow type plot bsaed on a new workspace
+        :param colorfunc: The Axes function to use to draw the new artist
+        :param artists_orig: A reference to an iterable of existing artists
+        :param workspace: A reference to the workspace object
+        :param kwargs: Any kwargs passed to the original call
+        """
+        for artist_orig in artists_orig:
+            artist_orig.remove()
+            if hasattr(artist_orig, 'colorbar_cid'):
+                artist_orig.callbacksSM.disconnect(artist_orig.colorbar_cid)
+        artists_new = colorfunc(self, workspace, **kwargs)
+        if not isinstance(artists_new, Iterable):
+            artists_new = [artists_new]
+        plotfunctions.update_colorplot_datalimits(self, artists_new)
+        # the type of plot can mutate back to single image from a multi collection
+        if len(artists_orig) == len(artists_new):
+            for artist_orig, artist_new in zip(artists_orig, artists_new):
+                if artist_orig.colorbar is not None:
+                    self._attach_colorbar(artist_new, artist_orig.colorbar)
+        else:
+            # pick up the colorbar from the first one we find
+            for artist_orig in artists_orig:
+                if artist_orig.colorbar is not None:
+                    self._attach_colorbar(artists_new[-1], artist_orig.colorbar)
+                    break
+            self.set_aspect('auto')
+        return artists_new
+
+    @plot_decorator
     def contour(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.contour` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -258,16 +490,17 @@ class MantidAxes(Axes):
             ax.contour(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.contour`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.contour(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.contour`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+            return self.track_workspace_artist(args[0], plotfunctions.contour(self, *args, **kwargs))
         else:
             return Axes.contour(self, *args, **kwargs)
 
+    @plot_decorator
     def contourf(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.contourf` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -283,16 +516,17 @@ class MantidAxes(Axes):
             ax.contourf(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.contourf`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.contourf(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.contourf`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+            return self.track_workspace_artist(args[0], plotfunctions.contourf(self, *args, **kwargs))
         else:
             return Axes.contourf(self, *args, **kwargs)
 
+    @plot_decorator
     def tripcolor(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.tripcolor` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -308,16 +542,17 @@ class MantidAxes(Axes):
             ax.tripcolor(x,y,C)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tripcolor`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.tripcolor(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.tripcolor`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+            return self.track_workspace_artist(args[0], plotfunctions.tripcolor(self, *args, **kwargs))
         else:
             return Axes.tripcolor(self, *args, **kwargs)
 
+    @plot_decorator
     def tricontour(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.tricontour` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -333,16 +568,17 @@ class MantidAxes(Axes):
             ax.tricontour(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tricontour`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.tricontour(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.tricontour`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+            return self.track_workspace_artist(args[0], plotfunctions.tricontour(self, *args, **kwargs))
         else:
             return Axes.tricontour(self, *args, **kwargs)
 
+    @plot_decorator
     def tricontourf(self, *args, **kwargs):
-        '''
+        """
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.tricontourf` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -358,17 +594,32 @@ class MantidAxes(Axes):
             ax.tricontourf(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tricontourf`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
-            return mantid.plots.plotfunctions.tricontourf(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions.tricontourf`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions')
+            return self.track_workspace_artist(args[0], plotfunctions.tricontourf(self, *args, **kwargs))
         else:
             return Axes.tricontourf(self, *args, **kwargs)
 
+    # ------------------ Private api --------------------------------------------------------
+
+    def _attach_colorbar(self, mappable, colorbar):
+        """
+        Attach the given colorbar to the mappable and update the clim values
+        :param mappable: An instance of a mappable
+        :param colorbar: An instance of a colorbar
+        """
+        cb = colorbar
+        cb.mappable = mappable
+        cb.set_clim(mappable.get_clim())
+        mappable.colorbar = cb
+        mappable.colorbar_cid = mappable.callbacksSM.connect('changed', cb.on_mappable_changed)
+        cb.update_normal(mappable)
+
 
 class MantidAxes3D(Axes3D):
-    '''
+    """
     This class defines the **mantid3d** projection for 3d plotting. One chooses
     this projection using::
 
@@ -384,12 +635,12 @@ class MantidAxes3D(Axes3D):
         ax = fig.add_subplot(111,projection='mantid3d')
 
     The mantid3d projection allows replacing the array objects with mantid workspaces.
-    '''
+    """
 
     name = 'mantid3d'
 
     def plot(self, *args, **kwargs):
-        '''
+        """
         If the **mantid3d** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes3D.plot` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -405,16 +656,16 @@ class MantidAxes3D(Axes3D):
             ax.plot(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.plot3D`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
-            return mantid.plots.plotfunctions3D.plot(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions3D.plot3D`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions3D')
+            return plotfunctions3D.plot(self, *args, **kwargs)
         else:
             return Axes3D.plot(self, *args, **kwargs)
 
     def scatter(self, *args, **kwargs):
-        '''
+        """
         If the **mantid3d** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes3D.scatter` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -430,16 +681,16 @@ class MantidAxes3D(Axes3D):
             ax.scatter(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.scatter`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
-            return mantid.plots.plotfunctions3D.scatter(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions3D.scatter`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions3D')
+            return plotfunctions3D.scatter(self, *args, **kwargs)
         else:
             return Axes3D.scatter(self, *args, **kwargs)
 
     def plot_wireframe(self, *args, **kwargs):
-        '''
+        """
         If the **mantid3d** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes3D.plot_wireframe` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -455,16 +706,16 @@ class MantidAxes3D(Axes3D):
             ax.plot_wireframe(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.wireframe`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
-            return mantid.plots.plotfunctions3D.plot_wireframe(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions3D.wireframe`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions3D')
+            return plotfunctions3D.plot_wireframe(self, *args, **kwargs)
         else:
             return Axes3D.plot_wireframe(self, *args, **kwargs)
 
     def plot_surface(self, *args, **kwargs):
-        '''
+        """
         If the **mantid3d** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes3D.plot_surface` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -480,16 +731,16 @@ class MantidAxes3D(Axes3D):
             ax.plot_surface(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.plot_surface`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
-            return mantid.plots.plotfunctions3D.plot_surface(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions3D.plot_surface`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions3D')
+            return plotfunctions3D.plot_surface(self, *args, **kwargs)
         else:
             return Axes3D.plot_surface(self, *args, **kwargs)
 
     def contour(self, *args, **kwargs):
-        '''
+        """
         If the **mantid3d** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes3D.contour` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -505,16 +756,16 @@ class MantidAxes3D(Axes3D):
             ax.contour(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.contour`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
-            return mantid.plots.plotfunctions3D.contour(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions3D.contour`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions3D')
+            return plotfunctions3D.contour(self, *args, **kwargs)
         else:
             return Axes3D.contour(self, *args, **kwargs)
 
     def contourf(self, *args, **kwargs):
-        '''
+        """
         If the **mantid3d** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes3D.contourf` for arrays,
         or it can be used to plot :class:`mantid.api.MatrixWorkspace`
@@ -530,11 +781,11 @@ class MantidAxes3D(Axes3D):
             ax.contourf(x,y,z)     #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.contourf`
-        '''
-        if mantid.plots.helperfunctions.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
-            return mantid.plots.plotfunctions3D.contourf(self, *args, **kwargs)
+        For keywords related to workspaces, see :func:`plotfunctions3D.contourf`
+        """
+        if helperfunctions.validate_args(*args):
+            logger.debug('using plotfunctions3D')
+            return plotfunctions3D.contourf(self, *args, **kwargs)
         else:
             return Axes3D.contourf(self, *args, **kwargs)
 
diff --git a/Framework/PythonInterface/mantid/plots/plotfunctions.py b/Framework/PythonInterface/mantid/plots/plotfunctions.py
index 0601aa6f494a700a77e091d2827d708b722c091f..447b448ef5d8a6455be22117e57c1ba1038c08c2 100644
--- a/Framework/PythonInterface/mantid/plots/plotfunctions.py
+++ b/Framework/PythonInterface/mantid/plots/plotfunctions.py
@@ -9,20 +9,30 @@
 #
 from __future__ import (absolute_import, division, print_function)
 
-import numpy
-from skimage.transform import resize
-import mantid.kernel
-import mantid.api
-from mantid.plots.helperfunctions import *
+from mantid.plots.utility import MantidAxType
+import collections
+import sys
+
 import matplotlib
+import matplotlib.collections as mcoll
 import matplotlib.colors
 import matplotlib.dates as mdates
 import matplotlib.image as mimage
+import numpy
+from skimage.transform import resize
+
+import mantid.api
+import mantid.kernel
+from mantid.plots.helperfunctions import get_axes_labels, get_bins, get_data_uneven_flag, get_distribution, \
+    get_matrix_2d_data, get_md_data1d, get_md_data2d_bin_bounds, get_md_data2d_bin_centers, get_normalization, \
+    get_sample_log, get_spectrum, get_uneven_data, get_wksp_index_dist_and_label
+
+# Used for initializing searches of max, min values
+_LARGEST, _SMALLEST = float(sys.maxsize), -sys.maxsize
 
 # ================================================
 # Private 2D Helper functions
 # ================================================
-from mantid.plots.utility import MantidAxType
 
 
 def _setLabels1D(axes, workspace):
@@ -43,7 +53,7 @@ def _setLabels2D(axes, workspace):
     axes.set_ylabel(labels[2])
 
 
-def _get_data_for_plot(axes, kwargs, workspace, with_dy=False, with_dx=False):
+def _get_data_for_plot(axes, workspace, kwargs, with_dy=False, with_dx=False):
     if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
         (normalization, kwargs) = get_normalization(workspace, **kwargs)
         (x, y, dy) = get_md_data1d(workspace, normalization)
@@ -66,6 +76,26 @@ def _get_data_for_plot(axes, kwargs, workspace, with_dy=False, with_dx=False):
 # Plot functions
 # ========================================================
 
+def _plot_impl(axes, workspace, args, kwargs):
+    """
+    Compute data and labels for plot. Used by workspace
+    replacement handlers to recompute data. See plot for
+    argument details
+    """
+    if 'LogName' in kwargs:
+        (x, y, FullTime, LogName, units, kwargs) = get_sample_log(workspace, **kwargs)
+        axes.set_ylabel('{0} ({1})'.format(LogName, units))
+        axes.set_xlabel('Time (s)')
+        if FullTime:
+            axes.xaxis_date()
+            axes.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S\n%b-%d'))
+            axes.set_xlabel('Time')
+        kwargs['linestyle'] = 'steps-post'
+    else:
+        x, y, _, _, kwargs = _get_data_for_plot(axes, workspace, kwargs)
+        _setLabels1D(axes, workspace)
+    return x, y, args, kwargs
+
 
 def plot(axes, workspace, *args, **kwargs):
     """
@@ -105,19 +135,7 @@ def plot(axes, workspace, *args, **kwargs):
     keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
     dimension
     """
-    if 'LogName' in kwargs:
-        (x, y, FullTime, LogName, units, kwargs) = get_sample_log(workspace, **kwargs)
-        axes.set_ylabel('{0} ({1})'.format(LogName, units))
-        axes.set_xlabel('Time (s)')
-        if FullTime:
-            axes.xaxis_date()
-            axes.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S\n%b-%d'))
-            axes.set_xlabel('Time')
-        kwargs['linestyle'] = 'steps-post'
-        return axes.plot(x, y, *args, **kwargs)
-
-    x, y, dy, dx, kwargs = _get_data_for_plot(axes, kwargs, workspace)
-    _setLabels1D(axes, workspace)
+    x, y, args, kwargs = _plot_impl(axes, workspace, args, kwargs)
     return axes.plot(x, y, *args, **kwargs)
 
 
@@ -148,7 +166,8 @@ def errorbar(axes, workspace, *args, **kwargs):
     keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
     dimension
     """
-    x, y, dy, dx, kwargs = _get_data_for_plot(axes, kwargs, workspace, with_dy=True, with_dx=True)
+    x, y, dy, dx, kwargs = _get_data_for_plot(axes, workspace, kwargs,
+                                              with_dy=True, with_dx=True)
     _setLabels1D(axes, workspace)
     return axes.errorbar(x, y, dy, dx, *args, **kwargs)
 
@@ -235,6 +254,7 @@ def contourf(axes, workspace, *args, **kwargs):
     else:
         (distribution, kwargs) = get_distribution(workspace, **kwargs)
         (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=False)
+
     _setLabels2D(axes, workspace)
     return axes.contourf(x, y, z, *args, **kwargs)
 
@@ -252,7 +272,7 @@ def _pcolorpieces(axes, workspace, distribution, *args, **kwargs):
     :param pcolortype: this keyword allows the plotting to be one of pcolormesh or
         pcolorfast if there is "mesh" or "fast" in the value of the keyword, or
         pcolor by default
-    Note: the return is the pcolor, pcolormesh, or pcolorfast of the last spectrum
+    :return: A list of the pcolor pieces created
     '''
     (x, y, z) = get_uneven_data(workspace, distribution)
     mini = numpy.min([numpy.min(i) for i in z])
@@ -278,11 +298,12 @@ def _pcolorpieces(axes, workspace, distribution, *args, **kwargs):
     else:
         pcolor = axes.pcolor
 
+    pieces = []
     for xi, yi, zi in zip(x, y, z):
         XX, YY = numpy.meshgrid(xi, yi, indexing='ij')
-        cm = pcolor(XX, YY, zi.reshape(-1, 1), **kwargs)
+        pieces.append(pcolor(XX, YY, zi.reshape(-1, 1), **kwargs))
 
-    return cm
+    return pieces
 
 
 def pcolor(axes, workspace, *args, **kwargs):
@@ -398,7 +419,7 @@ class ScalingAxesImage(mimage.AxesImage):
         self.dx = None
         self.dy = None
         self.unsampled_data = None
-        super(mimage.AxesImage, self).__init__(
+        super(ScalingAxesImage, self).__init__(
             ax,
             cmap=cmap,
             norm=norm,
@@ -412,16 +433,16 @@ class ScalingAxesImage(mimage.AxesImage):
 
     def set_data(self, A):
         dims = A.shape
-        max_dims = (3840,2160) # 4K resolution
+        max_dims = (3840, 2160)  # 4K resolution
         if dims[0] > max_dims[0] or dims[1] > max_dims[1]:
             new_dims = numpy.minimum(dims, max_dims)
-            if(_skimage_version()):
+            if (_skimage_version()):
                 self.unsampled_data = resize(A, new_dims, mode='constant', cval=numpy.nan, anti_aliasing=True)
             else:
                 self.unsampled_data = resize(A, new_dims, mode='constant', cval=numpy.nan)
         else:
             self.unsampled_data = A
-        super(mimage.AxesImage, self).set_data(A)
+        super(ScalingAxesImage, self).set_data(A)
 
     def draw(self, renderer):
         ax = self.axes
@@ -429,20 +450,22 @@ class ScalingAxesImage(mimage.AxesImage):
         we = ax.get_window_extent()
         dx = round(we.x1 - we.x0)
         dy = round(we.y1 - we.y0)
-        #decide if we should downsample
+        # decide if we should downsample
         dims = self.unsampled_data.shape
         if dx != self.dx or dy != self.dy:
             if dims[0] > dx or dims[1] > dy:
-                new_dims = numpy.minimum(dims,[dx,dy])
-                if(_skimage_version()):
-                    sampled_data = resize(self.unsampled_data, new_dims, mode='constant', cval=numpy.nan, anti_aliasing=True)
+                new_dims = numpy.minimum(dims, [dx, dy])
+                if (_skimage_version()):
+                    sampled_data = resize(self.unsampled_data, new_dims, mode='constant', cval=numpy.nan,
+                                          anti_aliasing=True)
                 else:
                     sampled_data = resize(self.unsampled_data, new_dims, mode='constant', cval=numpy.nan)
                 self.dx = dx
                 self.dy = dy
-                super(mimage.AxesImage, self).set_data(sampled_data)
+                super(ScalingAxesImage, self).set_data(sampled_data)
         return super(ScalingAxesImage,self).draw(renderer)
 
+
 def _imshow(axes, z, cmap=None, norm=None, aspect=None,
             interpolation=None, alpha=None, vmin=None, vmax=None,
             origin=None, extent=None, shape=None, filternorm=1,
@@ -545,7 +568,6 @@ def tripcolor(axes, workspace, *args, **kwargs):
         (distribution, kwargs) = get_distribution(workspace, **kwargs)
         (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=False)
     _setLabels2D(axes, workspace)
-
     return axes.tripcolor(x.ravel(), y.ravel(), z.ravel(), *args, **kwargs)
 
 
@@ -625,3 +647,46 @@ def tricontourf(axes, workspace, *args, **kwargs):
     y = y[condition]
     z = z[condition]
     return axes.tricontourf(x, y, z, *args, **kwargs)
+
+
+def update_colorplot_datalimits(axes, mappables):
+    """
+    For an colorplot (imshow, pcolor*) plots update the data limits on the axes
+    to circumvent bugs in matplotlib
+    :param mappables: An iterable of mappable for this axes
+    """
+    # ax.relim in matplotlib < 2.2 doesn't take into account of images
+    # and it doesn't support collections at all as of verison 3 so we'll take
+    # over
+    if not isinstance(mappables, collections.Iterable):
+        mappables = [mappables]
+    xmin_all, xmax_all, ymin_all, ymax_all = _LARGEST, _SMALLEST, _LARGEST, _SMALLEST
+    for mappable in mappables:
+        xmin, xmax, ymin, ymax = get_colorplot_extents(mappable)
+        xmin_all, xmax_all = min(xmin_all, xmin), max(xmax_all, xmax)
+        ymin_all, ymax_all = min(ymin_all, ymin), max(ymax_all, ymax)
+
+    axes.update_datalim(((xmin_all, ymin_all), (xmax_all, ymax_all)))
+    axes.autoscale()
+
+
+def get_colorplot_extents(mappable):
+    """
+    Return the extent of the given mappable
+    :param mappable: A 2D mappable object
+    :return: (left, right, bottom, top)
+    """
+    if isinstance(mappable, mimage.AxesImage):
+        xmin, xmax, ymin, ymax = mappable.get_extent()
+    elif isinstance(mappable, mcoll.QuadMesh):
+        # coordinates are vertices of the grid
+        coords = mappable._coordinates
+        xmin, ymin = coords[0][0]
+        xmax, ymax = coords[-1][-1]
+    elif isinstance(mappable, mcoll.PolyCollection):
+        xmin, ymin = mappable._paths[0].get_extents().min
+        xmax, ymax = mappable._paths[-1].get_extents().max
+    else:
+        raise ValueError("Unknown mappable type '{}'".format(type(mappable)))
+
+    return xmin, xmax, ymin, ymax
diff --git a/Framework/PythonInterface/mantid/py3compat/CMakeLists.txt b/Framework/PythonInterface/mantid/py3compat/CMakeLists.txt
index 4f578a4dfef26af4b12a8a9c121ed6fdfce1c2da..bedb11b0996100bf12613fcbb78f929e656e8828 100644
--- a/Framework/PythonInterface/mantid/py3compat/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/py3compat/CMakeLists.txt
@@ -1,6 +1,3 @@
-set (_module_name Py3Compat )
-string(TOLOWER _module_name _module_name_lower)
-
 set ( PY_FILES
   __init__.py
   enum.py
@@ -29,3 +26,4 @@ add_custom_target ( PythonPy3CompatModule ALL DEPENDS ${PYTHON_INSTALL_FILES} )
 ###########################################################################
 # Pure Python files
 install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR}/mantid/py3compat )
+install ( FILES ${PY_FILES} DESTINATION ${WORKBENCH_BIN_DIR}/mantid/py3compat )
diff --git a/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py b/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py
index 718e5a7e6cddc3bc9805f95bb20351c94c9e21c8..7c8f94638e9dfef1616fbe56abc3debef280794d 100644
--- a/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py
+++ b/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py
@@ -9,9 +9,9 @@ from __future__ import (absolute_import, division, print_function)
 from mantid.api import mtd, AlgorithmFactory, DistributedDataProcessorAlgorithm, ITableWorkspaceProperty, \
     MatrixWorkspaceProperty, MultipleFileProperty, PropertyMode
 from mantid.kernel import ConfigService, Direction
-from mantid.simpleapi import AlignAndFocusPowder, CompressEvents, ConvertUnits, CreateCacheFilename, \
-    DeleteWorkspace, DetermineChunking, Divide, EditInstrumentGeometry, FilterBadPulses, Load, \
-    LoadNexusProcessed, PDDetermineCharacterizations, Plus, RenameWorkspace, SaveNexusProcessed
+from mantid.simpleapi import AlignAndFocusPowder, CompressEvents, ConvertUnits, CopyLogs, CreateCacheFilename, \
+    DeleteWorkspace, DetermineChunking, Divide, EditInstrumentGeometry, FilterBadPulses, LoadNexusProcessed, \
+    PDDetermineCharacterizations, Plus, RemoveLogs, RenameWorkspace, SaveNexusProcessed
 import os
 
 EXTENSIONS_NXS = ["_event.nxs", ".nxs.h5"]
@@ -19,7 +19,7 @@ PROPS_FOR_INSTR = ["PrimaryFlightPath", "SpectrumIDs", "L2", "Polar", "Azimuthal
 CAL_FILE, GROUP_FILE = "CalFileName", "GroupFilename"
 CAL_WKSP, GRP_WKSP, MASK_WKSP = "CalibrationWorkspace", "GroupingWorkspace", "MaskWorkspace"
 PROPS_FOR_ALIGN = [CAL_FILE, GROUP_FILE,
-                   GRP_WKSP,CAL_WKSP, "OffsetsWorkspace",
+                   GRP_WKSP, CAL_WKSP, "OffsetsWorkspace",
                    MASK_WKSP, "MaskBinTable",
                    "Params", "ResampleX", "Dspacing", "DMin", "DMax",
                    "TMin", "TMax", "PreserveEvents",
@@ -63,7 +63,7 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
         return "Diffraction\\Reduction"
 
     def seeAlso(self):
-        return [ "AlignAndFocusPowder" ]
+        return ["AlignAndFocusPowder"]
 
     def name(self):
         return "AlignAndFocusPowderFromFiles"
@@ -124,11 +124,28 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
                 linearizedRuns.append(item)
         return linearizedRuns
 
+    def __createLoader(self, filename, wkspname, progstart=None, progstop=None):
+        # load a chunk - this is a bit crazy long because we need to get an output property from `Load` when it
+        # is run and the algorithm history doesn't exist until the parent algorithm (this) has finished
+        if progstart is None or progstop is None:
+            loader = self.createChildAlgorithm(self.__loaderName)
+        else:
+            loader = self.createChildAlgorithm(self.__loaderName,
+                                               startProgress=progstart, endProgress=progstop)
+        loader.setAlwaysStoreInADS(True)
+        loader.setLogging(True)
+        loader.initialize()
+        loader.setPropertyValue('Filename', filename)
+        loader.setPropertyValue('OutputWorkspace', wkspname)
+        return loader
+
     def __getAlignAndFocusArgs(self):
         args = {}
         for name in PROPS_FOR_ALIGN:
             prop = self.getProperty(name)
-            if name == 'PreserveEvents' or not prop.isDefault:
+            name_list = ['PreserveEvents', 'CompressTolerance',
+                         'CompressWallClockTolerance', 'CompressStartTime']
+            if name in name_list or not prop.isDefault:
                 if 'Workspace' in name:
                     args[name] = prop.valueAsStr
                 else:
@@ -159,18 +176,25 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
             if key not in self.kwargs:
                 self.kwargs[key] = instr + ext
 
-    def __determineCharacterizations(self, filename, wkspname, loadFile):
+    def __determineCharacterizations(self, filename, wkspname):
         useCharac = bool(self.charac is not None)
+        loadFile = not mtd.doesExist(wkspname)
 
         # input workspace is only needed to find a row in the characterizations table
         tempname = None
         if loadFile:
             if useCharac:
                 tempname = '__%s_temp' % wkspname
-                Load(Filename=filename, OutputWorkspace=tempname,
-                     MetaDataOnly=True)
+                # set the loader for this file
+                loader = self.__createLoader(filename, tempname)
+                loader.setProperty('MetaDataOnly', True)  # this is only supported by LoadEventNexus
+                loader.execute()
+
+                # get the underlying loader name if we used the generic one
+                if self.__loaderName == 'Load':
+                    self.__loaderName = loader.getPropertyValue('LoaderName')
         else:
-            tempname = wkspname # assume it is already loaded
+            tempname = wkspname  # assume it is already loaded
 
         # put together argument list
         args = dict(ReductionProperties=self.getProperty('ReductionProperties').valueAsStr)
@@ -179,7 +203,7 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
             if not prop.isDefault:
                 args[name] = prop.value
         if tempname is not None:
-            args['InputWorkspace']=tempname
+            args['InputWorkspace'] = tempname
         if useCharac:
             args['Characterizations'] = self.charac
 
@@ -209,12 +233,13 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
 
     def __processFile(self, filename, wkspname, unfocusname, file_prog_start, determineCharacterizations):
         chunks = determineChunking(filename, self.chunkSize)
-        numSteps = 6 # for better progress reporting - 6 steps per chunk
+        numSteps = 6  # for better progress reporting - 6 steps per chunk
         if unfocusname != '':
-            numSteps = 7 # one more for accumulating the unfocused workspace
+            numSteps = 7  # one more for accumulating the unfocused workspace
         self.log().information('Processing \'{}\' in {:d} chunks'.format(filename, len(chunks)))
         prog_per_chunk_step = self.prog_per_file * 1./(numSteps*float(len(chunks)))
         unfocusname_chunk = ''
+        canSkipLoadingLogs = False
 
         # inner loop is over chunks
         for (j, chunk) in enumerate(chunks):
@@ -223,11 +248,30 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
             if unfocusname != '':  # only create unfocus chunk if needed
                 unfocusname_chunk = '{}_c{:d}'.format(unfocusname, j)
 
-            Load(Filename=filename, OutputWorkspace=chunkname,
-                 startProgress=prog_start, endProgress=prog_start+prog_per_chunk_step,
-                 **chunk)
-            if determineCharacterizations:
-                self.__determineCharacterizations(filename, chunkname, False) # updates instance variable
+            # load a chunk - this is a bit crazy long because we need to get an output property from `Load` when it
+            # is run and the algorithm history doesn't exist until the parent algorithm (this) has finished
+            loader = self.__createLoader(filename, chunkname,
+                                         progstart=prog_start, progstop=prog_start + prog_per_chunk_step)
+            if canSkipLoadingLogs:
+                loader.setProperty('LoadLogs', False)
+            for key, value in chunk.items():
+                if isinstance(value, str):
+                    loader.setPropertyValue(key, value)
+                else:
+                    loader.setProperty(key, value)
+            loader.execute()
+
+            # copy the necessary logs onto the workspace
+            if canSkipLoadingLogs:
+                CopyLogs(InputWorkspace=wkspname, OutputWorkspace=chunkname, MergeStrategy='WipeExisting')
+
+            # get the underlying loader name if we used the generic one
+            if self.__loaderName == 'Load':
+                self.__loaderName = loader.getPropertyValue('LoaderName')
+            canSkipLoadingLogs = self.__loaderName == 'LoadEventNexus'
+
+            if determineCharacterizations and j == 0:
+                self.__determineCharacterizations(filename, chunkname)  # updates instance variable
                 determineCharacterizations = False
 
             prog_start += prog_per_chunk_step
@@ -250,7 +294,7 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
             AlignAndFocusPowder(InputWorkspace=chunkname, OutputWorkspace=chunkname, UnfocussedWorkspace=unfocusname_chunk,
                                 startProgress=prog_start, endProgress=prog_start+2.*prog_per_chunk_step,
                                 **self.kwargs)
-            prog_start += 2.*prog_per_chunk_step # AlignAndFocusPowder counts for two steps
+            prog_start += 2. * prog_per_chunk_step  # AlignAndFocusPowder counts for two steps
 
             if j == 0:
                 self.__updateAlignAndFocusArgs(chunkname)
@@ -258,19 +302,24 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
                 if unfocusname != '':
                     RenameWorkspace(InputWorkspace=unfocusname_chunk, OutputWorkspace=unfocusname)
             else:
+                RemoveLogs(Workspace=chunkname)  # accumulation has them already
                 Plus(LHSWorkspace=wkspname, RHSWorkspace=chunkname, OutputWorkspace=wkspname,
                      ClearRHSWorkspace=self.kwargs['PreserveEvents'],
                      startProgress=prog_start, endProgress=prog_start+prog_per_chunk_step)
                 DeleteWorkspace(Workspace=chunkname)
 
                 if unfocusname != '':
+                    RemoveLogs(Workspace=unfocusname_chunk)  # accumulation has them already
                     Plus(LHSWorkspace=unfocusname, RHSWorkspace=unfocusname_chunk, OutputWorkspace=unfocusname,
                          ClearRHSWorkspace=self.kwargs['PreserveEvents'],
                          startProgress=prog_start, endProgress=prog_start + prog_per_chunk_step)
                     DeleteWorkspace(Workspace=unfocusname_chunk)
 
-                if self.kwargs['PreserveEvents']:
-                    CompressEvents(InputWorkspace=wkspname, OutputWorkspace=wkspname)
+                if self.kwargs['PreserveEvents'] and self.kwargs['CompressTolerance'] > 0.:
+                    CompressEvents(InputWorkspace=wkspname, OutputWorkspace=wkspname,
+                                   WallClockTolerance=self.kwargs['CompressWallClockTolerance'],
+                                   Tolerance=self.kwargs['CompressTolerance'],
+                                   StartTime=self.kwargs['CompressStartTime'])
         # end of inner loop
 
     def PyExec(self):
@@ -296,7 +345,7 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
         else:
             self.log().warning('CacheDir is not specified - functionality disabled')
 
-        self.prog_per_file = 1./float(len(filenames)) # for better progress reporting
+        self.prog_per_file = 1./float(len(filenames))  # for better progress reporting
 
         # these are also passed into the child-algorithms
         self.kwargs = self.__getAlignAndFocusArgs()
@@ -306,14 +355,14 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
             # default name is based off of filename
             wkspname = os.path.split(filename)[-1].split('.')[0]
 
+            self.__loaderName = 'Load'  # reset to generic load with each file
             if useCaching:
-                self.__determineCharacterizations(filename,
-                                                  wkspname, True) # updates instance variable
+                self.__determineCharacterizations(filename, wkspname)  # updates instance variable
                 cachefile = self.__getCacheName(wkspname)
             else:
                 cachefile = None
 
-            wkspname += '_f%d' % i # add file number to be unique
+            wkspname += '_f%d' % i  # add file number to be unique
 
             # if the unfocussed data is requested, don't read it from disk
             # because of the extra complication of the unfocussed workspace
@@ -330,7 +379,7 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
                 if editinstrargs:
                     EditInstrumentGeometry(Workspace=wkspname, **editinstrargs)
             else:
-                self.__processFile(filename, wkspname, unfocusname_file, self.prog_per_file*float(i), not useCaching)
+                self.__processFile(filename, wkspname, unfocusname_file, self.prog_per_file * float(i), not useCaching)
 
                 # write out the cachefile for the main reduced data independent of whether
                 # the unfocussed workspace was requested
@@ -353,8 +402,11 @@ class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
                          ClearRHSWorkspace=self.kwargs['PreserveEvents'])
                     DeleteWorkspace(Workspace=unfocusname_file)
 
-                if self.kwargs['PreserveEvents']:
-                    CompressEvents(InputWorkspace=finalname, OutputWorkspace=finalname)
+                if self.kwargs['PreserveEvents'] and self.kwargs['CompressTolerance'] > 0.:
+                    CompressEvents(InputWorkspace=finalname, OutputWorkspace=finalname,
+                                   WallClockTolerance=self.kwargs['CompressWallClockTolerance'],
+                                   Tolerance=self.kwargs['CompressTolerance'],
+                                   StartTime=self.kwargs['CompressStartTime'])
                     # not compressing unfocussed workspace because it is in d-spacing
                     # and is likely to be from a different part of the instrument
 
diff --git a/Framework/PythonInterface/plugins/algorithms/BASISReduction.py b/Framework/PythonInterface/plugins/algorithms/BASISReduction.py
index 736b80da60d42cb0a556d022e84308aec822bd9f..0511f0a3e1f15889206599e628d9fcac872da7a9 100644
--- a/Framework/PythonInterface/plugins/algorithms/BASISReduction.py
+++ b/Framework/PythonInterface/plugins/algorithms/BASISReduction.py
@@ -4,7 +4,7 @@
 #     NScD Oak Ridge National Laboratory, European Spallation Source
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
-#pylint: disable=no-init
+# pylint: disable=no-init
 from __future__ import (absolute_import, division, print_function)
 
 import numpy as np
@@ -20,32 +20,34 @@ from mantid.kernel import (IntArrayProperty, StringListValidator,
 from mantid import config
 from os.path import join as pjoin
 
-TEMPERATURE_SENSOR = "SensorA"
+TEMPERATURE_SENSOR = 'SensorA'
 DEFAULT_RANGE = [6.24, 6.30]
-DEFAULT_MASK_GROUP_DIR = "/SNS/BSS/shared/autoreduce/new_masks_08_12_2015"
-DEFAULT_CONFIG_DIR = config["instrumentDefinition.directory"]
+DEFAULT_MASK_GROUP_DIR = '/SNS/BSS/shared/autoreduce/new_masks_08_12_2015'
+DEFAULT_CONFIG_DIR = config['instrumentDefinition.directory']
 
 # BASIS allows two possible reflections, with associated default properties
-#pylint: disable=line-too-long
-REFLECTIONS_DICT = {"silicon111": {"name": "silicon111",
-                                   "energy_bins": [-150, 0.4, 500],  # micro-eV
-                                   "q_bins": [0.3, 0.2, 1.9],  # inverse Angstroms
-                                   "mask_file": "BASIS_Mask_default_111.xml",
-                                   "parameter_file": "BASIS_silicon_111_Parameters.xml",
-                                   "default_energy": 2.0826,  # mili-eV
-                                   "vanadium_bins": [-0.0034, 0.068, 0.0034]  # mili-eV
+# pylint: disable=line-too-long
+REFLECTIONS_DICT = {'silicon111': {'name': 'silicon111',
+                                   'energy_bins': [-150, 0.4, 500],  # micro-eV
+                                   'q_bins': [0.3, 0.2, 1.9],  # inverse Angst
+                                   'banks': 'bank1,bank3,bank4',
+                                   'mask_file': 'BASIS_Mask_default_111.xml',
+                                   'parameter_file': 'BASIS_silicon_111_Parameters.xml',
+                                   'default_energy': 2.0826,  # meV
+                                   'vanadium_bins': [-0.0034, 0.068, 0.0034]
                                    },
-                    "silicon311": {"name": "silicon311",
-                                   "energy_bins": [-740, 1.6, 740],
-                                   "q_bins": [0.5, 0.2, 3.7],
-                                   "mask_file": "BASIS_Mask_default_311.xml",
-                                   "parameter_file": "BASIS_silicon_311_Parameters.xml",
-                                   "default_energy": 7.6368,  # mili-eV
-                                   "vanadium_bins": [-0.015, 0.030, 0.015]# mili-eV
+                    'silicon311': {'name': 'silicon311',
+                                   'energy_bins': [-740, 1.6, 740],
+                                   'q_bins': [0.5, 0.2, 3.7],
+                                   'banks': 'bank2',
+                                   'mask_file': 'BASIS_Mask_default_311.xml',
+                                   'parameter_file': 'BASIS_silicon_311_Parameters.xml',
+                                   'default_energy': 7.6368,  # meV
+                                   'vanadium_bins': [-0.015, 0.030, 0.015]
                                    }
                     }
 
-#pylint: disable=too-many-instance-attributes
+# pylint: disable=too-many-instance-attributes
 
 
 class BASISReduction(PythonAlgorithm):
@@ -114,15 +116,15 @@ class BASISReduction(PythonAlgorithm):
         self.declareProperty('MonitorNorm', True,
                              'Normalization with wavelength-dependent monitor counts')
         self.declareProperty('ExcludeTimeSegment', '',
-                             'Exclude a contigous time segment; '+
-                             'Examples: "71546:0-60" filter run 71546 from '+
-                             'start to 60 seconds, "71546:300-600", '+
+                             'Exclude a contigous time segment; ' +
+                             'Examples: "71546:0-60" filter run 71546 from ' +
+                             'start to 60 seconds, "71546:300-600", ' +
                              '"71546:120-end" from 120s to the end of the run')
-        grouping_type = ["None", "Low-Resolution", "By-Tube"]
-        self.declareProperty("GroupDetectors", "None",
+        grouping_type = ['None', 'Low-Resolution', 'By-Tube']
+        self.declareProperty('GroupDetectors', 'None',
                              StringListValidator(grouping_type),
-                             "Switch for grouping detectors")
-        self.declareProperty('NormalizeToFirst', False, 'Normalize spectra '+
+                             'Switch for grouping detectors')
+        self.declareProperty('NormalizeToFirst', False, 'Normalize spectra ' +
                              'to intensity of spectrum with lowest Q?')
 
         # Properties affected by the reflection selected
@@ -178,7 +180,7 @@ class BASISReduction(PythonAlgorithm):
         self.setPropertyGroup('NormWavelengthRange', titleDivideByVanadium)
 
         # Properties setting the saving of NSXPE file
-        title_nxspe= 'Save to NXSPE'
+        title_nxspe = 'Save to NXSPE'
         self.declareProperty('SaveNXSPE', False, direction=Direction.Input,
                              doc='Do we save to NXSPE format?')
         nxspe_enabled = EnabledWhenProperty('SaveNXSPE',
@@ -202,7 +204,7 @@ class BASISReduction(PythonAlgorithm):
                              doc='Output dynamic susceptibility (Xqw)')
         self.setPropertyGroup('OutputSusceptibility', titleAddionalOutput)
 
-    #pylint: disable=too-many-branches
+    # pylint: disable=too-many-branches
     def PyExec(self):
         config['default.facility'] = 'SNS'
         config['default.instrument'] = self._long_inst
@@ -229,15 +231,15 @@ class BASISReduction(PythonAlgorithm):
             self._nxspe_psi_angle_log = self.getProperty('PsiAngleLog').value
             self._nxspe_offset = self.getProperty('PsiOffset').value
 
-        datasearch = config["datasearch.searcharchive"]
-        if datasearch != "On":
-            config["datasearch.searcharchive"] = "On"
+        datasearch = config['datasearch.searcharchive']
+        if datasearch != 'On':
+            config['datasearch.searcharchive'] = 'On'
 
         # Apply default mask if not supplied by user
         self._overrideMask = bool(self._maskFile)
         if not self._overrideMask:
             config.appendDataSearchDir(DEFAULT_MASK_GROUP_DIR)
-            self._maskFile = self._reflection["mask_file"]
+            self._maskFile = self._reflection['mask_file']
 
         sapi.LoadMask(Instrument='BASIS',
                       OutputWorkspace='BASIS_MASK',
@@ -248,29 +250,29 @@ class BASISReduction(PythonAlgorithm):
         self._dMask = _dMask[1]
         sapi.DeleteWorkspace(_dMask[0])
 
-        ############################
-        ##  Process the Vanadium  ##
-        ############################
+        ##########################
+        #  Process the Vanadium  #
+        ##########################
 
-        norm_runs = self.getProperty("NormRunNumbers").value
+        norm_runs = self.getProperty('NormRunNumbers').value
         if self._doNorm and bool(norm_runs):
-            if ";" in norm_runs:
-                raise SyntaxError("Normalization does not support run groups")
-            self._normalizationType = self.getProperty("NormalizationType").value
-            self.log().information("Divide by Vanadium with normalization" +
+            if ';' in norm_runs:
+                raise SyntaxError('Normalization does not support run groups')
+            self._normalizationType = self.getProperty('NormalizationType').value
+            self.log().information('Divide by Vanadium with normalization' +
                                    self._normalizationType)
 
             # Following steps common to all types of Vanadium normalization
 
             # norm_runs encompasses a single set, thus _getRuns returns
             # a list of only one item
-            norm_set = self._getRuns(norm_runs, doIndiv=False)[0]
-            normWs = self._sum_and_calibrate(norm_set, extra_extension="_norm")
+            norm_set = self._get_runs(norm_runs, doIndiv=False)[0]
+            normWs = self._sum_and_calibrate(norm_set, extra_extension='_norm')
 
-            normRange = self.getProperty("NormWavelengthRange").value
+            normRange = self.getProperty('NormWavelengthRange').value
             bin_width = normRange[1] - normRange[0]
             # This rebin integrates counts onto a histogram of a single bin
-            if self._normalizationType == "by detector ID":
+            if self._normalizationType == 'by detector ID':
                 self._normRange = [normRange[0], bin_width, normRange[1]]
                 sapi.Rebin(InputWorkspace=normWs,
                            OutputWorkspace=normWs,
@@ -284,20 +286,20 @@ class BASISReduction(PythonAlgorithm):
                                             RangeUpper=normRange[1],
                                             OutputWorkspace='BASIS_NORM_MASK')
             # additional reduction steps when normalizing by Q slice
-            if self._normalizationType == "by Q slice":
+            if self._normalizationType == 'by Q slice':
                 self._normWs = self._group_and_SofQW(normWs, self._etBins,
                                                      isSample=False)
 
-        ##########################
-        ##  Process the sample  ##
-        ##########################
-        self._run_list = self._getRuns(self.getProperty("RunNumbers").value,
-                                       doIndiv=self._doIndiv)
+        ########################
+        #  Process the sample  #
+        ########################
+        self._run_list = self._get_runs(self.getProperty('RunNumbers').value,
+                                        doIndiv=self._doIndiv)
         for run_set in self._run_list:
             self._samWs = self._sum_and_calibrate(run_set)
             self._samWsRun = str(run_set[0])
             # Divide by Vanadium detector ID, if pertinent
-            if self._normalizationType == "by detector ID":
+            if self._normalizationType == 'by detector ID':
                 # Mask detectors with insufficient Vanadium signal before dividing
                 sapi.MaskDetectors(Workspace=self._samWs,
                                    MaskedWorkspace='BASIS_NORM_MASK')
@@ -310,7 +312,7 @@ class BASISReduction(PythonAlgorithm):
             if not self._debugMode:
                 sapi.DeleteWorkspace(self._samWs)  # delete events file
             # Divide by Vanadium Q slice, if pertinent
-            if self._normalizationType == "by Q slice":
+            if self._normalizationType == 'by Q slice':
                 sapi.Divide(LHSWorkspace=self._samSqwWs,
                             RHSWorkspace=self._normWs,
                             OutputWorkspace=self._samSqwWs)
@@ -333,49 +335,49 @@ class BASISReduction(PythonAlgorithm):
                            OutputWorkspace=self._samSqwWs)
             self.serialize_in_log(self._samSqwWs)  # store the call
             # Output Dave and Nexus files
-            extension = "_divided.dat" if self._doNorm else ".dat"
-            dave_grp_filename = self._makeRunName(self._samWsRun, False) +\
+            extension = '_divided.dat' if self._doNorm else '.dat'
+            dave_grp_filename = self._make_run_name(self._samWsRun, False) + \
                 extension
             sapi.SaveDaveGrp(Filename=dave_grp_filename,
                              InputWorkspace=self._samSqwWs,
                              ToMicroEV=True)
-            extension = "_divided_sqw.nxs" if self._doNorm else "_sqw.nxs"
-            processed_filename = self._makeRunName(self._samWsRun, False) +\
+            extension = '_divided_sqw.nxs' if self._doNorm else '_sqw.nxs'
+            processed_filename = self._make_run_name(self._samWsRun, False) + \
                 extension
             sapi.SaveNexus(Filename=processed_filename,
                            InputWorkspace=self._samSqwWs)
 
             # additional output
-            if self.getProperty("OutputSusceptibility").value:
+            if self.getProperty('OutputSusceptibility').value:
                 temperature = mtd[self._samSqwWs].getRun().\
                     getProperty(TEMPERATURE_SENSOR).getStatistics().mean
-                samXqsWs = self._samSqwWs.replace("sqw", "Xqw")
+                samXqsWs = self._samSqwWs.replace('sqw', 'Xqw')
                 sapi.ApplyDetailedBalance(InputWorkspace=self._samSqwWs,
                                           OutputWorkspace=samXqsWs,
                                           Temperature=str(temperature))
                 sapi.ConvertUnits(InputWorkspace=samXqsWs,
                                   OutputWorkspace=samXqsWs,
-                                  Target="DeltaE_inFrequency",
-                                  Emode="Indirect")
+                                  Target='DeltaE_inFrequency',
+                                  Emode='Indirect')
                 self.serialize_in_log(samXqsWs)
-                susceptibility_filename = processed_filename.replace("sqw", "Xqw")
+                susceptibility_filename = processed_filename.replace('sqw', 'Xqw')
                 sapi.SaveNexus(Filename=susceptibility_filename,
                                InputWorkspace=samXqsWs)
 
         if not self._debugMode:
-            sapi.DeleteWorkspace("BASIS_MASK")  # delete the mask
+            sapi.DeleteWorkspace('BASIS_MASK')  # delete the mask
             if self._doNorm and bool(norm_runs):
-                sapi.DeleteWorkspace("BASIS_NORM_MASK")  # delete vanadium mask
+                sapi.DeleteWorkspace('BASIS_NORM_MASK')  # delete vanadium mask
                 sapi.DeleteWorkspace(self._normWs)  # Delete vanadium S(Q)
-                if self._normalizationType == "by Q slice":
+                if self._normalizationType == 'by Q slice':
                     sapi.DeleteWorkspace(normWs)  # Delete vanadium events file
-            if self.getProperty("ExcludeTimeSegment").value:
+            if self.getProperty('ExcludeTimeSegment').value:
                 sapi.DeleteWorkspace('splitter')
                 [sapi.DeleteWorkspace(name) for name in
                  ('splitted_unfiltered', 'TOFCorrectWS') if
                  AnalysisDataService.doesExist(name)]
 
-    def _getRuns(self, rlist, doIndiv=True):
+    def _get_runs(self, rlist, doIndiv=True):
         """
         Create sets of run numbers for analysis. A semicolon indicates a
         separate group of runs to be processed together.
@@ -386,33 +388,33 @@ class BASISReduction(PythonAlgorithm):
          each string is a run number.
         """
         run_list = []
-        # ";" separates the runs into substrings. Each substring
+        # ';' separates the runs into substrings. Each substring
         #  represents a set of runs
         rlvals = rlist.split(';')
         for rlval in rlvals:
-            iap = IntArrayProperty("", rlval)  # split the substring
+            iap = IntArrayProperty('', rlval)  # split the substring
             if doIndiv:
                 run_list.extend([[x] for x in iap.value])
             else:
                 run_list.append(iap.value)
         return run_list
 
-    def _makeRunName(self, run, useShort=True):
+    def _make_run_name(self, run, useShort=True):
         """
         Make name like BSS_24234
         """
         if useShort:
-            return self._short_inst + "_" + str(run)
+            return self._short_inst + '_' + str(run)
         else:
-            return self._long_inst + "_" + str(run)
+            return self._long_inst + '_' + str(run)
 
-    def _makeRunFile(self, run):
+    def _make_run_file(self, run):
         """
         Make name like BSS_24234_event.nxs
         """
-        return "{0}_{1}_event.nxs".format(self._short_inst,str(run))
+        return '{0}_{1}_event.nxs'.format(self._short_inst, str(run))
 
-    def _sumRuns(self, run_set, sam_ws, mon_ws, extra_ext=None):
+    def _sum_runs(self, run_set, sam_ws, mon_ws, extra_ext=None):
         """
         Aggregate the set of runs
         @param run_set: list of run numbers
@@ -421,21 +423,16 @@ class BASISReduction(PythonAlgorithm):
         @param extra_ext: string to be added to the temporary workspaces
         """
         for run in run_set:
-            ws_name = self._makeRunName(run)
+            ws_name = self._make_run_name(run)
             if extra_ext is not None:
                 ws_name += extra_ext
-            mon_ws_name = ws_name + "_monitors"
-            run_file = self._makeRunFile(run)
-
-            # Faster loading for the 311 reflection
-            if self._reflection["name"] == "silicon311":
-                kwargs = {"BankName": "bank2"}  # 311 analyzers only in bank2
-            else:
-                kwargs = {}
+            mon_ws_name = ws_name + '_monitors'
+            run_file = self._make_run_file(run)
 
             sapi.LoadEventNexus(Filename=run_file,
-                                OutputWorkspace=ws_name, **kwargs)
-            if str(run)+':' in self.getProperty("ExcludeTimeSegment").value:
+                                OutputWorkspace=ws_name,
+                                BankName=self._reflection['banks'])
+            if str(run)+':' in self.getProperty('ExcludeTimeSegment').value:
                 self._filterEvents(str(run), ws_name)
 
             if self._MonNorm:
@@ -453,14 +450,14 @@ class BASISReduction(PythonAlgorithm):
                           OutputWorkspace=mon_ws)
                 sapi.DeleteWorkspace(mon_ws_name)
 
-    def _calibData(self, sam_ws, mon_ws):
+    def _calibrate_data(self, sam_ws, mon_ws):
         sapi.MaskDetectors(Workspace=sam_ws,
                            DetectorList=self._dMask)
         sapi.ModeratorTzeroLinear(InputWorkspace=sam_ws,
                                   OutputWorkspace=sam_ws)
         sapi.LoadParameterFile(Workspace=sam_ws,
                                Filename=pjoin(DEFAULT_CONFIG_DIR,
-                                              self._reflection["parameter_file"]))
+                                              self._reflection['parameter_file']))
         sapi.ConvertUnits(InputWorkspace=sam_ws,
                           OutputWorkspace=sam_ws,
                           Target='Wavelength',
@@ -471,7 +468,8 @@ class BASISReduction(PythonAlgorithm):
                                       OutputWorkspace=mon_ws)
             sapi.Rebin(InputWorkspace=mon_ws,
                        OutputWorkspace=mon_ws,
-                       Params='10')
+                       Params='10',  # 10 microseconds TOF bin width
+                       PreserveEvents=False)
             sapi.ConvertUnits(InputWorkspace=mon_ws,
                               OutputWorkspace=mon_ws,
                               Target='Wavelength')
@@ -489,18 +487,18 @@ class BASISReduction(PythonAlgorithm):
                         RHSWorkspace=mon_ws,
                         OutputWorkspace=sam_ws)
 
-    def _sum_and_calibrate(self, run_set, extra_extension=""):
+    def _sum_and_calibrate(self, run_set, extra_extension=''):
         """
         Aggregate the set of runs and calibrate
         @param run_set: list of run numbers
         @param extra_extension: string to be added to the workspace names
         @return: workspace name of the aggregated and calibrated data
         """
-        wsName = self._makeRunName(run_set[0])
+        wsName = self._make_run_name(run_set[0])
         wsName += extra_extension
-        wsName_mon = wsName + "_monitors"
-        self._sumRuns(run_set, wsName, wsName_mon, extra_extension)
-        self._calibData(wsName, wsName_mon)
+        wsName_mon = wsName + '_monitors'
+        self._sum_runs(run_set, wsName, wsName_mon, extra_extension)
+        self._calibrate_data(wsName, wsName_mon)
         if not self._debugMode:
             if self._MonNorm:
                 sapi.DeleteWorkspace(wsName_mon)  # delete monitors
@@ -523,11 +521,11 @@ class BASISReduction(PythonAlgorithm):
         sapi.Rebin(InputWorkspace=wsName,
                    OutputWorkspace=wsName,
                    Params=etRebins)
-        if self._groupDetOpt != "None":
-            if self._groupDetOpt == "Low-Resolution":
-                grp_file = "BASIS_Grouping_LR.xml"
+        if self._groupDetOpt != 'None':
+            if self._groupDetOpt == 'Low-Resolution':
+                grp_file = 'BASIS_Grouping_LR.xml'
             else:
-                grp_file = "BASIS_Grouping.xml"
+                grp_file = 'BASIS_Grouping.xml'
             # If mask override used, we need to add default grouping file
             # location to search paths
             if self._overrideMask:
@@ -535,7 +533,7 @@ class BASISReduction(PythonAlgorithm):
                 sapi.GroupDetectors(InputWorkspace=wsName,
                                     OutputWorkspace=wsName,
                                     MapFile=grp_file,
-                                    Behaviour="Sum")
+                                    Behaviour='Sum')
 
         # Output NXSPE file (must be done before transforming the
         # vertical axis to point data)
@@ -563,13 +561,13 @@ class BASISReduction(PythonAlgorithm):
         sapi.SofQW3(InputWorkspace=wsName,
                     QAxisBinning=self._qBins,
                     EMode='Indirect',
-                    EFixed=self._reflection["default_energy"],
+                    EFixed=self._reflection['default_energy'],
                     OutputWorkspace=wsSqwName)
         # Rebin the vanadium within the elastic line
         if not isSample:
             sapi.Rebin(InputWorkspace=wsSqwName,
                        OutputWorkspace=wsSqwName,
-                       Params=self._reflection["vanadium_bins"])
+                       Params=self._reflection['vanadium_bins'])
         return wsSqwName
 
     def _ScaleY(self, wsName):
@@ -583,7 +581,7 @@ class BASISReduction(PythonAlgorithm):
         sapi.Scale(InputWorkspace=wsName,
                    OutputWorkspace=wsName,
                    Factor=1./maximumYvalue,
-                   Operation="Multiply")
+                   Operation='Multiply')
 
     def generateSplitterWorkspace(self, fragment):
         r"""Create a table workspace with time intervals to keep
@@ -600,7 +598,7 @@ class BASISReduction(PythonAlgorithm):
         splitter = sapi.CreateEmptyTableWorkspace(OutputWorkspace='splitter')
         splitter.addColumn('double', 'start')
         splitter.addColumn('double', 'stop')
-        splitter.addColumn('str', 'target') #, 'str')
+        splitter.addColumn('str', 'target')
         if a == 0.0:
             splitter.addRow([b, inf, '0'])
         elif b == inf:
@@ -619,7 +617,7 @@ class BASISReduction(PythonAlgorithm):
         ws_name : str
             name of the workspace to filter
         """
-        for run_fragment in self.getProperty("ExcludeTimeSegment").value.split(';'):
+        for run_fragment in self.getProperty('ExcludeTimeSegment').value.split(';'):
             if run+':' in run_fragment:
                 self.generateSplitterWorkspace(run_fragment.split(':')[1])
                 sapi.FilterEvents(InputWorkspace=ws_name,
@@ -660,5 +658,6 @@ class BASISReduction(PythonAlgorithm):
         r = mtd[ws_name].mutableRun()
         r.addProperty('asString', json.dumps(self._as_json), True)
 
+
 # Register algorithm with Mantid.
 AlgorithmFactory.subscribe(BASISReduction)
diff --git a/Framework/PythonInterface/plugins/algorithms/CalculateEfficiencyCorrection.py b/Framework/PythonInterface/plugins/algorithms/CalculateEfficiencyCorrection.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fa64666e9c8a8d2c218378b15934d04c451b76c
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/CalculateEfficiencyCorrection.py
@@ -0,0 +1,253 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import absolute_import, division, print_function
+import numpy as np
+
+from mantid.simpleapi import \
+    CloneWorkspace, ConvertFromDistribution, ConvertToPointData, \
+    ConvertUnits, CreateWorkspace, Rebin, SetSampleMaterial
+from mantid.api import \
+    AlgorithmFactory, CommonBinsValidator, PropertyMode, PythonAlgorithm, \
+    MatrixWorkspaceProperty
+from mantid.kernel import \
+    Direction, FloatBoundedValidator, MaterialBuilder, \
+    StringListValidator, StringMandatoryValidator
+
+TABULATED_WAVELENGTH = 1.7982
+
+
+class CalculateEfficiencyCorrection(PythonAlgorithm):
+    _input_ws = None
+    _output_ws = None
+
+    def category(self):
+        return 'CorrectionFunctions\\EfficiencyCorrections'
+
+    def name(self):
+        return 'CalculateEfficiencyCorrection'
+
+    def summary(self):
+        return 'Calculate an efficiency correction using various inputs. Can be used to determine \
+                an incident spectrum after correcting a measured spectrum from beam monitors \
+                or vanadium measurements.'
+
+    def seeAlso(self):
+        return [ "He3TubeEfficiency", "CalculateSampleTransmission",
+                 "DetectorEfficiencyCor", "DetectorEfficiencyCorUser",
+                 "CalculateEfficiency", "ComputeCalibrationCoefVan" ]
+
+    def PyInit(self):
+        self.declareProperty(
+            MatrixWorkspaceProperty('InputWorkspace', '',
+                                    direction=Direction.Input,
+                                    optional=PropertyMode.Optional,
+                                    validator=CommonBinsValidator()),
+            doc='Input workspace with wavelength range to calculate for the correction.')
+
+        self.declareProperty(name='WavelengthRange', defaultValue='',
+                             doc='Wavelength range to calculate efficiency for.')
+
+        self.declareProperty(
+            MatrixWorkspaceProperty('OutputWorkspace', '',
+                                    direction=Direction.Output),
+            doc="Output workspace for the efficiency correction. \
+                 This can be applied by multiplying the OutputWorkspace \
+                 to the workspace that requires the correction.")
+
+        self.declareProperty(
+            name='ChemicalFormula', defaultValue='None',
+            validator=StringMandatoryValidator(),
+            doc='Sample chemical formula used to determine cross-section term.')
+
+        self.declareProperty(
+            name='DensityType', defaultValue = 'Mass Density',
+            validator=StringListValidator(['Mass Density', 'Number Density']),
+            doc = 'Use of Mass density (g/cm^3) or Number density (atoms/Angstrom^3)')
+
+        self.declareProperty(
+            name='Density',
+            defaultValue=0.0,
+            validator=FloatBoundedValidator(0.0),
+            doc='Mass density (g/cm^3) or Number density (atoms/Angstrom^3).')
+
+        self.declareProperty(
+            name='Thickness',
+            defaultValue=1.0,
+            validator=FloatBoundedValidator(0.0),
+            doc='Sample thickness (cm).')
+
+        self.declareProperty(
+            name='MeasuredEfficiency',
+            defaultValue=0.0,
+            validator=FloatBoundedValidator(0.0, 1.0),
+            doc="Directly input the efficiency measured at MeasuredEfficiencyWavelength. \
+                 This is used to determine a Density*Thickness term.")
+
+        self.declareProperty(
+            name='MeasuredEfficiencyWavelength',
+            defaultValue=1.7982,
+            validator=FloatBoundedValidator(0.0),
+            doc="The wavelength at which the MeasuredEfficiency was measured.")
+
+        self.declareProperty(
+            name='Alpha', defaultValue=0.0,
+            doc="Directly input the alpha term in exponential to multiply by the wavelength. \
+                 XSectionType has no effect if this is used.")
+
+        self.declareProperty(
+            name='XSectionType',
+            defaultValue="AttenuationXSection",
+            validator=StringListValidator(['AttenuationXSection', 'TotalXSection']),
+            doc = 'Use either the absorption  or total cross section in exponential term. \
+                   The absorption cross section is for monitor-type corrections and \
+                   the total cross section is for transmission-type corrections' )
+
+    def validateInputs(self):
+        issues = dict()
+
+        # Check the inputs for wavelength
+        isInWSDefault = self.getProperty('InputWorkspace').isDefault
+        isWlRangeDefault = self.getProperty('WavelengthRange').isDefault
+
+        if isInWSDefault and isWlRangeDefault:
+            issues['InputWorkspace'] = "Must select either InputWorkspace and WavelengthRange as input"
+            issues['WavelengthRange'] = "Must select either InputWorkspace and WavelengthRange as input"
+
+        if isInWSDefault and isWlRangeDefault:
+            issues['InputWorkspace'] = "Cannot select both InputWorkspace and WavelengthRange as input"
+            issues['WavelengthRange'] = "Cannot select both InputWorkspace and WavelengthRange as input"
+
+        # Check the different inputs for the exponential term
+        isDensityDefault = self.getProperty('Density').isDefault
+        isFormulaDefault = self.getProperty('ChemicalFormula').isDefault
+        isEffDefault     = self.getProperty('MeasuredEfficiency').isDefault
+        isAlphaDefault   = self.getProperty('Alpha').isDefault
+
+        if not isDensityDefault:
+            if isFormulaDefault:
+                issues['ChemicalFormula'] = "Must specify the ChemicalFormula with Density"
+
+        if not isEffDefault:
+            if isFormulaDefault:
+                issues['ChemicalFormula'] = "Must specify the ChemicalFormula with MeasuredEfficiency"
+
+        if not isEffDefault and not isDensityDefault:
+            issues['MeasuredEfficiency'] = "Cannot select both MeasuredEfficiency and Density as input"
+            issues['Density'] = "Cannot select both MeasuredEfficiency and Density as input"
+
+        if not isAlphaDefault and not isDensityDefault:
+            issues['Alpha'] = "Cannot select both Alpha and Density as input"
+            issues['Density'] = "Cannot select both Alpha and Density as input"
+
+        if not isEffDefault and not isAlphaDefault:
+            issues['MeasuredEfficiency'] = "Cannot select both MeasuredEfficiency and Alpha as input"
+            issues['Alpha'] = "Cannot select both MeasuredEfficiency and Alpha as input"
+
+        return issues
+
+    def _setup(self):
+        self._input_ws = self.getProperty('InputWorkspace').value
+        self._bin_params = self.getPropertyValue('WavelengthRange')
+        self._output_ws = self.getProperty('OutputWorkspace').valueAsStr
+        self._chemical_formula = self.getPropertyValue('ChemicalFormula')
+        self._density_type = self.getPropertyValue('DensityType')
+        self._density = self.getProperty('Density').value
+        self._thickness = self.getProperty('Thickness').value
+        self._efficiency = self.getProperty('MeasuredEfficiency').value
+        self._efficiency_wavelength = self.getProperty('MeasuredEfficiencyWavelength').value
+        self._alpha_absXS = self.getProperty('Alpha').value
+        self._alpha_scatXS = 0.0
+        self._xsection_type = self.getProperty('XSectionType').value
+
+    def PyExec(self):
+        self._setup()
+        if not self.getProperty("InputWorkspace").isDefault:
+            self._output_ws = CloneWorkspace(Inputworkspace=self._input_ws,
+                                             OutputWorkspace=self._output_ws,
+                                             StoreInADS=False)
+        else:
+            self._output_ws = CreateWorkspace(NSpec=1, DataX=[0], DataY=[0],
+                                              UnitX='Wavelength', Distribution=False,
+                                              StoreInADS=False)
+            self._output_ws = Rebin(InputWorkspace=self._output_ws,
+                                    Params=self._bin_params,
+                                    StoreInADS=False)
+
+        if self._output_ws.isDistribution():
+            ConvertFromDistribution(Workspace=self._output_ws,
+                                    StoreInADS=False)
+
+        self._output_ws = ConvertToPointData(InputWorkspace=self._output_ws,
+                                             StoreInADS=False)
+        self._output_ws = ConvertUnits(InputWorkspace=self._output_ws,
+                                       Target='Wavelength',
+                                       EMode='Elastic',
+                                       StoreInADS=False)
+
+        if self.getProperty('Alpha').isDefault:
+            SetSampleMaterial(
+                InputWorkspace=self._output_ws,
+                ChemicalFormula=self._chemical_formula,
+                SampleNumberDensity=self._density,
+                StoreInADS=False)
+            if self.getProperty('MeasuredEfficiency').isDefault:
+                self._calculate_area_density_from_density()
+            else:
+                self._calculate_area_density_from_efficiency()
+            self._calculate_alpha_absXS_term()
+            if self._xsection_type == "TotalXSection":
+                self._calculate_alpha_scatXS_term()
+
+        wavelengths = self._output_ws.readX(0)
+        efficiency = self._calculate_efficiency(wavelengths)
+        for histo in range(self._output_ws.getNumberHistograms()):
+            self._output_ws.setY(histo, efficiency)
+
+        self.setProperty('OutputWorkspace', self._output_ws)
+
+    def _calculate_area_density_from_efficiency(self):
+        """Calculates area density (atom/cm^2) using efficiency"""
+        material = self._output_ws.sample().getMaterial()
+        ref_absXS = material.absorbXSection()
+        xs_term = ref_absXS * self._efficiency_wavelength / TABULATED_WAVELENGTH
+        if self._xsection_type == "TotalXSection":
+            xs_term += material.totalScatterXSection()
+        self._area_density = - np.log( 1.0 - self._efficiency) / xs_term
+
+    def _calculate_area_density_from_density(self):
+        """Calculates area density (atom/cm^2) using number density and thickness."""
+        if self._density_type == 'Mass Density':
+            builder = MaterialBuilder().setFormula(self._chemical_formula)
+            mat = builder.setMassDensity(self._density).build()
+            self._density = mat.numberDensity
+        self._area_density = self._density * self._thickness
+
+    def _calculate_alpha_absXS_term(self):
+        """Calculates absorption XS alpha term = area_density * absXS / 1.7982"""
+        material = self._output_ws.sample().getMaterial()
+        absXS = material.absorbXSection()  / TABULATED_WAVELENGTH
+        self._alpha_absXS = self._area_density * absXS
+
+    def _calculate_alpha_scatXS_term(self):
+        """Calculates scattering XS alpha term = area_density * scatXS"""
+        material = self._output_ws.sample().getMaterial()
+        scatXS = material.totalScatterXSection()
+        self._alpha_scatXS = self._area_density * scatXS
+
+    def _calculate_efficiency(self, wavelength):
+        """
+        Calculates efficiency of a detector / monitor at a given wavelength.
+        If using just the absorption cross section, _alpha_scatXS is == 0.
+
+        @param wavelength Wavelength at which to calculate (in Angstroms)
+        @return efficiency
+        """
+        efficiency = 1. / (1.0 - np.exp(-(self._alpha_scatXS + self._alpha_absXS * wavelength)))
+        return efficiency
+
+
+AlgorithmFactory.subscribe(CalculateEfficiencyCorrection)
diff --git a/Framework/PythonInterface/plugins/algorithms/CalibrateRectangularDetectors.py b/Framework/PythonInterface/plugins/algorithms/CalibrateRectangularDetectors.py
index f8e2d32dbe5cd7b314f017ed9b44b9404eecbc16..95e057e0c84a0ddd908e7bdd5393d5d7832e380a 100644
--- a/Framework/PythonInterface/plugins/algorithms/CalibrateRectangularDetectors.py
+++ b/Framework/PythonInterface/plugins/algorithms/CalibrateRectangularDetectors.py
@@ -235,9 +235,9 @@ class CalibrateRectangularDetectors(PythonAlgorithm):
 
         #Find good peak for reference
         ymax = 0
+        midBin = int(mtd[wksp].blocksize()/2)
         for s in range(0,mtd[wksp].getNumberHistograms()):
             y_s = mtd[wksp].readY(s)
-            midBin = int(mtd[wksp].blocksize()/2)
             if y_s[midBin] > ymax:
                 refpixel = s
                 ymax = y_s[midBin]
@@ -265,9 +265,9 @@ class CalibrateRectangularDetectors(PythonAlgorithm):
                   Params=str(self._peakmin2)+","+str(abs(self._binning[1]))+","+str(self._peakmax2))
             #Find good peak for reference
             ymax = 0
+            midBin = int(mtd[wksp].blocksize()/2)
             for s in range(0,mtd[wksp].getNumberHistograms()):
                 y_s = mtd[wksp].readY(s)
-                midBin = int(mtd[wksp].blocksize()/2)
                 if y_s[midBin] > ymax:
                     refpixel = s
                     ymax = y_s[midBin]
@@ -296,9 +296,9 @@ class CalibrateRectangularDetectors(PythonAlgorithm):
                   Params=str(self._peakmin3)+","+str(abs(self._binning[1]))+","+str(self._peakmax3))
             #Find good peak for reference
             ymax = 0
+            midBin = mtd[wksp].blocksize()/2
             for s in range(0,mtd[wksp].getNumberHistograms()):
                 y_s = mtd[wksp].readY(s)
-                midBin = mtd[wksp].blocksize()/2
                 if y_s[midBin] > ymax:
                     refpixel = s
                     ymax = y_s[midBin]
diff --git a/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py b/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py
index 8feb07972e8432eb2637ac93376fb74e9d6e6be2..c2345beb41cbf5de17a165d44f1278b00b8d1f95 100644
--- a/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py
+++ b/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py
@@ -110,7 +110,7 @@ class CropWorkspaceForMDNorm(PythonAlgorithm):
                 raise RuntimeError("Could not compare old and new values for 'MDNorm_high' log. "+
                                    "Make sure the length of the old data is equal to the number of spectra")
         run_obj.addProperty('MDNorm_high', [xmax]*num_spectra, True)
-        run_obj.addProperty('MDNorm_spectra_index', numpy.arange(num_spectra).tolist(), True)
+        run_obj.addProperty('MDNorm_spectra_index', numpy.arange(num_spectra, dtype=float).tolist(), True)
         self.setProperty('OutputWorkspace', out_ws)
 
 # Register algorithm with Mantid.
diff --git a/Framework/PythonInterface/plugins/algorithms/EnggVanadiumCorrections.py b/Framework/PythonInterface/plugins/algorithms/EnggVanadiumCorrections.py
index a20c264c8842883846d0de7114646afa42c6619f..edc703ec6b077794bf2ba5eecb5a6f64cc52b091 100644
--- a/Framework/PythonInterface/plugins/algorithms/EnggVanadiumCorrections.py
+++ b/Framework/PythonInterface/plugins/algorithms/EnggVanadiumCorrections.py
@@ -147,8 +147,9 @@ class EnggVanadiumCorrections(PythonAlgorithm):
         @param van_integration_ws :: pre-calculated integral of every spectrum for the Vanadium data
         @param van_curves_ws :: pre-calculated per-bank curves from the Vanadium data
         """
+        blocksize = van_curves_ws.blocksize()
         for i in range(0, ws.getNumberHistograms()):
-            scale_factor = van_integration_ws.cell(i, 0) / van_curves_ws.blocksize()
+            scale_factor = van_integration_ws.cell(i, 0) / blocksize
             ws.setY(i, np.divide(ws.dataY(i), scale_factor))
 
     def _apply_pix_by_pix_correction(self, ws, van_curves_ws):
diff --git a/Framework/PythonInterface/plugins/algorithms/GetLiveInstrumentValue.py b/Framework/PythonInterface/plugins/algorithms/GetLiveInstrumentValue.py
index 5f63a0bc5555cbc0e4563f4c309e4b7dfa727b8e..a857a0c21e91b2aa9bfcd1f85f0b7c9c643c5907 100644
--- a/Framework/PythonInterface/plugins/algorithms/GetLiveInstrumentValue.py
+++ b/Framework/PythonInterface/plugins/algorithms/GetLiveInstrumentValue.py
@@ -58,14 +58,29 @@ class GetLiveInstrumentValue(DataProcessorAlgorithm):
         else:
             return ':CS:SB:'
 
-    def _get_live_value(self):
+    @staticmethod
+    def _caget(pvname, as_string=False):
+        """Retrieve an EPICS PV value"""
         try:
-            from epics import caget
+            from CaChannel import CaChannel, CaChannelException, ca
         except ImportError:
-            raise RuntimeError("PyEpics must be installed to use this algorithm. "
-                               "For details, see https://www.mantidproject.org/PyEpics_In_Mantid")
+            raise RuntimeError("CaChannel must be installed to use this algorithm. "
+                               "For details, see https://www.mantidproject.org/CaChannel_In_Mantid")
+        if as_string:
+            dbr_type = ca.DBR_STRING
+        else:
+            dbr_type = None
+        try:
+            chan = CaChannel(pvname)
+            chan.setTimeout(5.0)
+            chan.searchw()
+            return chan.getw(dbr_type)
+        except CaChannelException as e:
+            raise RuntimeError("Error reading EPICS PV \"{}\": {}".format(pvname, str(e)))
+
+    def _get_live_value(self):
         epicsName = self._prefix() + self._instrument + self._name_prefix() + self._propertyName
-        return caget(epicsName, as_string=True)
+        return self._caget(epicsName, as_string=True)
 
     def _set_output_value(self, value):
         if value is not None:
diff --git a/Framework/PythonInterface/plugins/algorithms/MagnetismReflectometryReduction.py b/Framework/PythonInterface/plugins/algorithms/MagnetismReflectometryReduction.py
index 8d163f4c9925d5d082d3899a33b8dbb256f50e6d..092ace358cb608665d98efd61dc0a0a86a7abc5c 100644
--- a/Framework/PythonInterface/plugins/algorithms/MagnetismReflectometryReduction.py
+++ b/Framework/PythonInterface/plugins/algorithms/MagnetismReflectometryReduction.py
@@ -90,6 +90,8 @@ class MagnetismReflectometryReduction(PythonAlgorithm):
                              "TOF/wavelength range to use with detector binning")
         self.declareProperty("UseWLTimeAxis", False,
                              doc="For const-Q, if true, wavelength will be used as the time axis, otherwise TOF is used")
+        self.declareProperty("ErrorWeightedBackground", True,
+                             doc="If true, use an error weighted average for the background estimate")
         self.declareProperty("CutTimeAxis", True,
                              doc="If true, the TOF/wavelength dimension will be cropped according to the TimeAxisRange property")
         self.declareProperty("RoundUpPixel", True, doc="If True, round up pixel position of the specular reflectivity")
@@ -102,6 +104,7 @@ class MagnetismReflectometryReduction(PythonAlgorithm):
         self.declareProperty("TimeAxisStep", 40.0,
                              doc="Binning step size for the time axis. TOF for detector binning, wavelength for constant Q")
         self.declareProperty("CropFirstAndLastPoints", True, doc="If true, we crop the first and last points")
+        self.declareProperty("CleanupBadData", True, doc="If true, we crop the points consistent with R=0")
         self.declareProperty("ConstQTrim", 0.5,
                              doc="With const-Q binning, cut Q bins with contributions fewer than ConstQTrim of WL bins")
         self.declareProperty("SampleLength", 10.0, doc="Length of the sample in mm")
@@ -483,8 +486,9 @@ class MagnetismReflectometryReduction(PythonAlgorithm):
             if low_q is not None and high_q is not None:
                 break
 
+        cleanup = self.getProperty("CleanupBadData").value
         crop = self.getProperty("CropFirstAndLastPoints").value
-        if low_q is not None and high_q is not None:
+        if cleanup and low_q is not None and high_q is not None:
             # Get rid of first and last Q points to avoid edge effects
             if crop:
                 low_q += 1
@@ -493,7 +497,7 @@ class MagnetismReflectometryReduction(PythonAlgorithm):
             q_rebin = CropWorkspace(InputWorkspace=q_rebin,
                                     OutputWorkspace=str(q_rebin),
                                     XMin=data_x[low_q], XMax=data_x[high_q])
-        else:
+        elif cleanup:
             logger.error("Data is all zeros. Check your TOF ranges.")
 
         # Clean up the workspace for backward compatibility
@@ -696,6 +700,7 @@ class MagnetismReflectometryReduction(PythonAlgorithm):
         """
         logger.warning("Processing %s" % str(workspace))
         use_wl_cut = self.getProperty("UseWLTimeAxis").value
+        error_weighted_bck = self.getProperty("ErrorWeightedBackground").value
         constant_q_binning = self.getProperty("ConstantQBinning").value
         tof_step = self.getProperty("TimeAxisStep").value
 
@@ -753,7 +758,7 @@ class MagnetismReflectometryReduction(PythonAlgorithm):
                              XPixelMax=int(background_range[1]),
                              YPixelMin=low_res_min,
                              YPixelMax=low_res_max,
-                             ErrorWeighting = True,
+                             ErrorWeighting = error_weighted_bck,
                              SumPixels=True, NormalizeSum=True)
 
             signal = RefRoi(InputWorkspace=workspace, IntegrateY=True,
diff --git a/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py b/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ab0361b4cae86e3c2e6443e5433ed6fb64d5f4a
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py
@@ -0,0 +1,76 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#pylint: disable=no-init
+
+from mantid.api import PythonAlgorithm, AlgorithmFactory, ITableWorkspaceProperty
+from mantid.kernel import Direction
+from mantid.simpleapi import *
+from mantid import mtd
+
+# Create an empty table workspace to be populated by a python script.
+
+
+class OptimizeCrystalPlacementByRun(PythonAlgorithm):
+
+    def summary(self):
+        return "Optimizes the sample position for each run in a peaks workspace."
+
+    def category(self):
+        return 'Crystal\\Corrections'
+
+    def seeAlso(self):
+        return [ "OptimizeCrystalPlacement" ]
+
+    def PyInit(self):
+        # Declare properties
+        self.declareProperty(ITableWorkspaceProperty("InputWorkspace", "", Direction.Input),
+                             "The name of the peaks workspace that will be optimized.")
+        self.declareProperty("Tolerance", 0.15, "Tolerance of indexing of peaks.")
+        self.declareProperty("OutputWorkspace", "",
+                             "The name of the peaks workspace that will be created.")
+
+    def PyExec(self):
+        ws = self.getProperty("InputWorkspace").value
+        ws_append = self.getProperty("OutputWorkspace").value
+        ws_group = 'ws_group'
+        tolerance = self.getProperty("Tolerance").value
+        if not ws.sample().hasOrientedLattice():
+            FindUBUsingIndexedPeaks(PeaksWorkspace=ws,Tolerance=tolerance)
+        num,err=IndexPeaks(PeaksWorkspace=ws,Tolerance=tolerance)
+        logger.notice('Initial Number indexed: %s error: %s\n'%(num, err))
+        stats = StatisticsOfTableWorkspace(InputWorkspace=ws)
+        stat_col = stats.column('Statistic')
+        minR = int(stats.column('RunNumber')[stat_col.index('Minimum')])
+        maxR = int(stats.column('RunNumber')[stat_col.index('Maximum')]) + 1
+        AnalysisDataService.remove(stats.getName())
+        group = []
+        for run in range(minR, maxR):
+            FilterPeaks(InputWorkspace=ws, OutputWorkspace=str(run), FilterVariable='RunNumber',
+                        FilterValue=run, Operator='=')
+            run = mtd[str(run)]
+            peaks = run.getNumberPeaks()
+            if peaks == 0:
+                AnalysisDataService.remove(str(run))
+            else:
+                group.append(str(run))
+        GroupWorkspaces(InputWorkspaces=group, OutputWorkspace=ws_group)
+        OptimizeCrystalPlacement(PeaksWorkspace=ws_group, ModifiedPeaksWorkspace=ws_group, AdjustSampleOffsets=True,
+                                 MaxSamplePositionChangeMeters=0.005,MaxIndexingError=tolerance)
+        RenameWorkspace(InputWorkspace=str(minR),OutputWorkspace=ws_append)
+        for run in range(minR+1, maxR):
+            if AnalysisDataService.doesExist(str(run)):
+                CombinePeaksWorkspaces(LHSWorkspace=ws_append, RHSWorkspace=str(run),OutputWorkspace=ws_append)
+                logger.notice('Optimized %s sample position: %s\n'%(str(run),mtd[str(run)].getPeak(0).getSamplePos()))
+                AnalysisDataService.remove( str(run))
+        num,err=IndexPeaks(PeaksWorkspace=ws_append,Tolerance=tolerance)
+        logger.notice('After Optimization Number indexed: %s error: %s\n'%(num, err))
+        AnalysisDataService.remove(ws_group)
+        self.setProperty("OutputWorkspace", ws_append)
+
+
+# Register algorithm with Mantid
+AlgorithmFactory.subscribe(OptimizeCrystalPlacementByRun)
diff --git a/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py b/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py
index 046b6cc654857d1f88f0a0d49589ff1dde1030b8..b3eb99ada058fb1d5ae6918a5e41d6793babc726 100644
--- a/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py
+++ b/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py
@@ -536,7 +536,8 @@ class SNSPowderReduction(DistributedDataProcessorAlgorithm):
                                               OutputWorkspace=self._charTable)
         # export the characterizations table
         charTable = results[0]
-        self.declareProperty(ITableWorkspaceProperty("CharacterizationsTable", self._charTable, Direction.Output))
+        if not self.existsProperty("CharacterizationsTable"):
+            self.declareProperty(ITableWorkspaceProperty("CharacterizationsTable", self._charTable, Direction.Output))
         self.setProperty("CharacterizationsTable", charTable)
 
         # get the focus positions from the properties
@@ -972,7 +973,8 @@ class SNSPowderReduction(DistributedDataProcessorAlgorithm):
                 self.log().warning(str(e))
 
             propertyName = "OutputWorkspace%s" % str(output_wksp_list[split_index])
-            self.declareProperty(WorkspaceProperty(propertyName, str(output_wksp_list[split_index]), Direction.Output))
+            if not self.existsProperty(propertyName):
+                self.declareProperty(WorkspaceProperty(propertyName, str(output_wksp_list[split_index]), Direction.Output))
             self.setProperty(propertyName, output_wksp_list[split_index])
             self._save(output_wksp_list[split_index], self._info, False, True)
             self.log().information("Done focussing data of %d." % (split_index))
diff --git a/Framework/PythonInterface/plugins/algorithms/StatisticsOfTableWorkspace.py b/Framework/PythonInterface/plugins/algorithms/StatisticsOfTableWorkspace.py
index f148fc24d9f8bac466243921ecbedbf96d11223f..6b9d25ad702e0473b2a9902914f87772647b6371 100644
--- a/Framework/PythonInterface/plugins/algorithms/StatisticsOfTableWorkspace.py
+++ b/Framework/PythonInterface/plugins/algorithms/StatisticsOfTableWorkspace.py
@@ -4,30 +4,33 @@
 #     NScD Oak Ridge National Laboratory, European Spallation Source
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
-#pylint: disable=no-init
+# pylint: disable=no-init
 
 from __future__ import (absolute_import, division, print_function)
-from mantid.api import PythonAlgorithm, AlgorithmFactory, ITableWorkspaceProperty
-from mantid.kernel import Direction, Stats
-import mantid.simpleapi as ms
-from mantid import mtd, logger
-import numpy as np
+
 import collections
+
+import numpy as np
 from six import iteritems
 
+import mantid.simpleapi as ms
+from mantid import logger, mtd
+from mantid.api import AlgorithmFactory, ITableWorkspaceProperty, PythonAlgorithm
+from mantid.kernel import Direction, IntArrayBoundedValidator, IntArrayProperty, Stats
+
 
 def _stats_to_dict(stats):
     """
-    Converts a Statstics object to an ordered dictionary.
+    Converts a Statistics object to an ordered dictionary.
     @param stats Statistics object to convertToWaterfall
     @return Dictionary of statistics
     """
     stat_dict = collections.OrderedDict()
-    stat_dict['standard_deviation'] = stats.standard_deviation
-    stat_dict['maximum'] = stats.maximum
-    stat_dict['minimum'] = stats.minimum
-    stat_dict['mean'] = stats.mean
-    stat_dict['median'] = stats.median
+    stat_dict['StandardDev'] = stats.standard_deviation
+    stat_dict['Maximum'] = stats.maximum
+    stat_dict['Minimum'] = stats.minimum
+    stat_dict['Mean'] = stats.mean
+    stat_dict['Median'] = stats.median
     return stat_dict
 
 
@@ -42,38 +45,55 @@ class StatisticsOfTableWorkspace(PythonAlgorithm):
     def PyInit(self):
         self.declareProperty(ITableWorkspaceProperty('InputWorkspace', '', Direction.Input),
                              doc='Input table workspace.')
+        validator = IntArrayBoundedValidator()
+        validator.setLower(0)
+        self.declareProperty(
+            IntArrayProperty('ColumnIndices', values=[], direction=Direction.Input, validator=validator),
+            'Comma separated list of column indices for which statistics will be separated')
         self.declareProperty(ITableWorkspaceProperty('OutputWorkspace', '', Direction.Output),
-                             doc='Output workspace contatining column statitics.')
+                             doc='Output workspace containing column statistics.')
 
     def PyExec(self):
         in_ws = mtd[self.getPropertyValue('InputWorkspace')]
+        indices_list = self.getPropertyValue('ColumnIndices')
         out_ws_name = self.getPropertyValue('OutputWorkspace')
+        column_names = in_ws.getColumnNames()
+
+        # If column indices are not provided, then default to _ALL_ columns
+        if len(indices_list) > 0:
+            indices_list = [int(x) for x in indices_list.split(',')]
+        else:
+            indices_list = range(len(column_names))
 
         out_ws = ms.CreateEmptyTableWorkspace(OutputWorkspace=out_ws_name)
 
-        out_ws.addColumn('str', 'statistic')
+        out_ws.addColumn('str', 'Statistic')
 
         stats = collections.OrderedDict([
-            ('standard_deviation', collections.OrderedDict()),
-            ('minimum', collections.OrderedDict()),
-            ('median', collections.OrderedDict()),
-            ('maximum', collections.OrderedDict()),
-            ('mean', collections.OrderedDict()),
+            ('StandardDev', collections.OrderedDict()),
+            ('Minimum', collections.OrderedDict()),
+            ('Median', collections.OrderedDict()),
+            ('Maximum', collections.OrderedDict()),
+            ('Mean', collections.OrderedDict()),
         ])
 
-        for name in in_ws.getColumnNames():
+        for index in indices_list:
+            column_name = column_names[index]
             try:
-                col_stats = _stats_to_dict(Stats.getStatistics(np.array([float(v) for v in in_ws.column(name)])))
-                for statname in stats:
-                    stats[statname][name] = col_stats[statname]
-                out_ws.addColumn('float', name)
-            except ValueError:
-                logger.notice('Column \'%s\' is not numerical, skipping' % name)
-
-        for name, stat in iteritems(stats):
-            stat1 = collections.OrderedDict(stat)
-            stat1['statistic'] = name
-            out_ws.addRow(stat1)
+                column_data = np.array([float(v) for v in in_ws.column(index)])
+                col_stats = _stats_to_dict(Stats.getStatistics(column_data))
+                for stat_name in stats:
+                    stats[stat_name][column_name] = col_stats[stat_name]
+                out_ws.addColumn('float', column_name)
+            except RuntimeError:
+                logger.notice('Column \'%s\' is not numerical, skipping' % column_name)
+            except:
+                logger.notice('Column \'%s\' is not numerical, skipping' % column_name)
+
+        for index, stat_name in iteritems(stats):
+            stat = collections.OrderedDict(stat_name)
+            stat['Statistic'] = index
+            out_ws.addRow(stat)
 
         self.setProperty('OutputWorkspace', out_ws)
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ElasticWindowMultiple.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ElasticWindowMultiple.py
index 6be6dade76b1dedf64841e3c40215d61348ae4ca..8be63a6b10db464f298aa36ee31904a6c8ccd61c 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ElasticWindowMultiple.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ElasticWindowMultiple.py
@@ -10,6 +10,8 @@ from mantid.simpleapi import AppendSpectra, CloneWorkspace, ElasticWindow, LoadL
 from mantid.kernel import *
 from mantid.api import *
 
+import numpy as np
+
 
 def _normalize_by_index(workspace, index):
     """
@@ -19,15 +21,28 @@ def _normalize_by_index(workspace, index):
     @param workspace    The workspace to normalize.
     @param index        The index of the y-value to normalize by.
     """
+    number_of_histograms = workspace.getNumberHistograms()
+
+    for idx in range(0, number_of_histograms):
+        y_values = workspace.readY(idx)
+        y_errors = workspace.readE(idx)
+
+        # Avoid divide by zero
+        if y_values[index] == 0.0:
+            scale = np.reciprocal(1.0e-8)
+        else:
+            scale = np.reciprocal(y_values[index])
+
+        # Normalise y values
+        y_values_normalised = scale * y_values
 
-    num_hist = workspace.getNumberHistograms()
+        # Propagate y errors: C = A / B ; dC = sqrt( (dA/B)^2 + (A*dB/B^2)^2 )
+        a = (y_errors*scale)
+        b = (y_values*y_errors[index]*(scale ** 2))
+        y_errors_propagated = np.sqrt(a ** 2 + b ** 2)
 
-    # Normalize each spectrum in the workspace
-    for idx in range(0, num_hist):
-        y_vals = workspace.readY(idx)
-        scale = 1.0 / y_vals[index]
-        y_vals_scaled = scale * y_vals
-        workspace.setY(idx, y_vals_scaled)
+        workspace.setY(idx, y_values_normalised)
+        workspace.setE(idx, y_errors_propagated)
 
 
 class ElasticWindowMultiple(DataProcessorAlgorithm):
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/PowderDiffILLDetEffCorr.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/PowderDiffILLDetEffCorr.py
index c9e052459d42cee91fb923e2d1c3d1d270db0cbe..492b82c2ed49bc93f33a3e4dc87a1832dc145bbd 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/PowderDiffILLDetEffCorr.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/PowderDiffILLDetEffCorr.py
@@ -538,17 +538,19 @@ class PowderDiffILLDetEffCorr(PythonAlgorithm):
             if self._out_response:
                 # take care of combined response
                 end = self._bin_offset
+                response = mtd[response_ws]
+                responseBlockSize = response.blocksize()
                 if det == self._pixel_range[1] - 1:
                     end = self._scan_points - self._bin_offset
                     for scan_point in range(0, self._bin_offset):
-                        index = mtd[response_ws].blocksize() - self._bin_offset + scan_point
-                        mtd[response_ws].dataY(0)[index] = mtd[ws].readY(0)[end + scan_point]
-                        mtd[response_ws].dataE(0)[index] = mtd[ws].readE(0)[end + scan_point]
+                        index = responseBlockSize - self._bin_offset + scan_point
+                        response.dataY(0)[index] = mtd[ws].readY(0)[end + scan_point]
+                        response.dataE(0)[index] = mtd[ws].readE(0)[end + scan_point]
 
                 for scan_point in range(0, end):
                     index = det * self._bin_offset + scan_point
-                    mtd[response_ws].dataY(0)[index] = mtd[ref_ws].readY(0)[scan_point]
-                    mtd[response_ws].dataE(0)[index] = mtd[ref_ws].readE(0)[scan_point]
+                    response.dataY(0)[index] = mtd[ref_ws].readY(0)[scan_point]
+                    response.dataE(0)[index] = mtd[ref_ws].readE(0)[scan_point]
 
             DeleteWorkspace(ws)
         # end of loop over pixels
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SavePlot1D.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SavePlot1D.py
index 7be56237c9cb0bd48c4561b738c79049126ec19a..146814c99eac19af174af8cd5a6479db2edd7482 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SavePlot1D.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SavePlot1D.py
@@ -155,8 +155,8 @@ class SavePlot1D(mantid.api.PythonAlgorithm):
                 (traces, xlabel, ylabel) = self.toScatterAndLabels(wksp, spectraNames)
                 for spectrum in traces:
                     fig.append_trace(spectrum, i+1, 1)
-                fig['layout']['xaxis%d' % (i+1)].update(title=xlabel)
-                fig['layout']['yaxis%d' % (i+1)].update(title=ylabel)
+                fig['layout']['xaxis%d' % (i+1)].update(title={'text':xlabel})
+                fig['layout']['yaxis%d' % (i+1)].update(title={'text':ylabel})
                 if len(spectraNames) > 0:  # remove the used spectra names
                     spectraNames = spectraNames[len(traces):]
             fig['layout'].update(margin={'r':0,'t':0})
@@ -164,9 +164,16 @@ class SavePlot1D(mantid.api.PythonAlgorithm):
             (traces, xlabel, ylabel) = self.toScatterAndLabels(self._wksp,
                                                                spectraNames)
 
-            layout = go.Layout(yaxis={'title': ylabel},
-                               xaxis={'title': xlabel},
-                               margin={'l':40,'r':0,'t':0,'b':40})
+            # plotly seems to change the way to set the axes labels
+            # randomly and within a version. Just give up and try both.
+            try:
+                layout = go.Layout(yaxis={'title': ylabel},
+                                   xaxis={'title': xlabel},
+                                   margin={'l': 40, 'r': 0, 't': 0, 'b': 40})
+            except RuntimeError:
+                layout = go.Layout(yaxis={'title': {'text': ylabel}},
+                                   xaxis={'title': {'text': xlabel}},
+                                   margin={'l': 40, 'r': 0, 't': 0, 'b': 40})
 
             fig = go.Figure(data=traces, layout=layout)
 
diff --git a/Framework/PythonInterface/test/python/mantid/api/AnalysisDataServiceObserverTest.py b/Framework/PythonInterface/test/python/mantid/api/AnalysisDataServiceObserverTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a7cba7cf4bd18ed4128ec0d0eede570249863db
--- /dev/null
+++ b/Framework/PythonInterface/test/python/mantid/api/AnalysisDataServiceObserverTest.py
@@ -0,0 +1,250 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import unittest
+import sys
+
+from mantid.api import AnalysisDataService as ADS, AnalysisDataServiceObserver
+from mantid.simpleapi import CreateSampleWorkspace, RenameWorkspace, GroupWorkspaces, UnGroupWorkspace, DeleteWorkspace
+
+if sys.version_info.major >= 3:
+    # Python 3 and above
+    from unittest import mock
+else:
+    # Python 2
+    import mock
+
+
+class FakeADSObserver(AnalysisDataServiceObserver):
+    # These methods are going to be mocked out but needs to be present to actually get the hook from the C++ side
+    def anyChangeHandle(self):
+        pass
+
+    def addHandle(self, wsName, ws):
+        pass
+
+    def replaceHandle(self, wsName, ws):
+        pass
+
+    def deleteHandle(self, wsName, ws):
+        pass
+
+    def clearHandle(self):
+        pass
+
+    def renameHandle(self, wsName, newName):
+        pass
+
+    def groupHandle(self, wsName, ws):
+        pass
+
+    def unGroupHandle(self, wsName, ws):
+        pass
+
+    def groupUpdateHandle(self, wsName, ws):
+        pass
+
+
+class AnalysisDataServiceObserverTest(unittest.TestCase):
+    def setUp(self):
+        self.fake_class = FakeADSObserver()
+        self.fake_class.anyChangeHandle = mock.MagicMock()
+
+    def tearDown(self):
+        self.fake_class.observeAll(False)
+        ADS.clear()
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_add(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws")
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, 1)
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_replace(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws")
+        expected_count = 1
+
+        # Will replace first workspace
+        CreateSampleWorkspace(OutputWorkspace="ws")
+        expected_count += 1
+
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, expected_count)
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_delete(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws")
+        expected_count = 1
+
+        # Will replace first workspace
+        DeleteWorkspace("ws")
+        expected_count += 1
+
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, expected_count)
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_clear(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws")
+        expected_count = 1
+
+        # Will replace first workspace
+        ADS.clear()
+        expected_count += 1
+
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, expected_count)
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_rename(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws")
+        expected_count = 1
+
+        # Will replace first workspace
+        RenameWorkspace(InputWorkspace="ws", OutputWorkspace="ws1")
+        # One for the rename
+        expected_count += 1
+        # One for replacing original named workspace
+        expected_count += 1
+
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, expected_count)
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_group_made(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        expected_count = 1
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        expected_count += 1
+
+        # Will replace first workspace
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+        # One for grouping the workspace
+        expected_count += 1
+        # One for adding it to the ADS
+        expected_count += 1
+
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, expected_count)
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_ungroup_performed(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        expected_count = 1
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        expected_count += 1
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+        # One for grouping the workspace
+        expected_count += 1
+        # One for adding it to the ADS
+        expected_count += 1
+
+        # Will replace first workspace
+        UnGroupWorkspace(InputWorkspace="NewGroup")
+        # One for ungrouping the workspace
+        expected_count += 1
+        # One for removing the grouped workspace object from the ADS
+        expected_count += 1
+
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, expected_count)
+
+    def test_observeAll_calls_anyChangeHandle_when_set_on_ads_group_updated(self):
+        self.fake_class.observeAll(True)
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        expected_count = 1
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        expected_count += 1
+        CreateSampleWorkspace(OutputWorkspace="ws3")
+        expected_count += 1
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+        # One for grouping the workspace
+        expected_count += 1
+        # One for adding it to the ADS
+        expected_count += 1
+
+        # Will update group
+        ADS.addToGroup("NewGroup", "ws3")
+        expected_count += 1
+
+        self.assertEqual(self.fake_class.anyChangeHandle.call_count, expected_count)
+
+    def test_observeAdd_calls_addHandle_when_set_on_ads_and_a_workspace_is_added(self):
+        self.fake_class.observeAdd(True)
+        self.fake_class.addHandle = mock.MagicMock()
+        CreateSampleWorkspace(OutputWorkspace="ws")
+        self.assertEqual(self.fake_class.addHandle.call_count, 1)
+
+    def test_observeReplace_calls_replaceHandle_when_set_on_ads_and_a_workspace_is_replaced(self):
+        CreateSampleWorkspace(OutputWorkspace="ws")
+
+        self.fake_class.observeReplace(True)
+        self.fake_class.replaceHandle = mock.MagicMock()
+        CreateSampleWorkspace(OutputWorkspace="ws")
+
+        self.assertEqual(self.fake_class.replaceHandle.call_count, 1)
+
+    def test_observeDelete_calls_deleteHandle_when_set_on_ads_and_a_workspace_is_deleted(self):
+        CreateSampleWorkspace(OutputWorkspace="ws")
+
+        self.fake_class.observeDelete(True)
+        self.fake_class.deleteHandle = mock.MagicMock()
+        ADS.remove("ws")
+
+        self.assertEqual(self.fake_class.deleteHandle.call_count, 1)
+
+    def test_observeClear_calls_clearHandle_when_set_on_ads_its_cleared(self):
+        CreateSampleWorkspace(OutputWorkspace="ws")
+
+        self.fake_class.observeClear(True)
+        self.fake_class.clearHandle = mock.MagicMock()
+        ADS.clear()
+
+        self.assertEqual(self.fake_class.clearHandle.call_count, 1)
+
+    def test_observeRename_calls_renameHandle_when_set_on_ads_and_a_workspace_is_renamed(self):
+        CreateSampleWorkspace(OutputWorkspace="ws")
+
+        self.fake_class.observeRename(True)
+        self.fake_class.renameHandle = mock.MagicMock()
+        RenameWorkspace(InputWorkspace="ws", OutputWorkspace="ws1")
+
+        self.assertEqual(self.fake_class.renameHandle.call_count, 1)
+
+    def test_observeGroup_calls_groupHandle_when_set_on_ads_and_a_group_workspace_is_made(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+
+        self.fake_class.observeGroup(True)
+        self.fake_class.groupHandle = mock.MagicMock()
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+
+        self.assertEqual(self.fake_class.groupHandle.call_count, 1)
+
+    def test_observeUnGroup_calls_unGroupHandle_when_set_on_ads_and_a_group_is_ungrouped(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+
+        self.fake_class.observeUnGroup(True)
+        self.fake_class.unGroupHandle = mock.MagicMock()
+        UnGroupWorkspace(InputWorkspace="NewGroup")
+
+        self.assertEqual(self.fake_class.unGroupHandle.call_count, 1)
+
+    def test_observeGroupUpdated_calls_groupUpdateHandle_when_set_on_ads_and_a_group_in_the_ads_is_updated(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        CreateSampleWorkspace(OutputWorkspace="ws3")
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+
+        self.fake_class.observeGroupUpdate(True)
+        self.fake_class.groupUpdateHandle = mock.MagicMock()
+        ADS.addToGroup("NewGroup", "ws3")
+
+        self.assertEqual(self.fake_class.groupUpdateHandle.call_count, 1)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/api/AnalysisDataServiceTest.py b/Framework/PythonInterface/test/python/mantid/api/AnalysisDataServiceTest.py
index e2dc256c8c2126e608e833eb8ecdae64645adb3c..801550ebf3a71d3c0f62f2162b395d187244a0b9 100644
--- a/Framework/PythonInterface/test/python/mantid/api/AnalysisDataServiceTest.py
+++ b/Framework/PythonInterface/test/python/mantid/api/AnalysisDataServiceTest.py
@@ -6,6 +6,7 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
 
+import six
 import unittest
 from testhelpers import run_algorithm
 from mantid.api import (AnalysisDataService, AnalysisDataServiceImpl,
@@ -20,7 +21,7 @@ class AnalysisDataServiceTest(unittest.TestCase):
         FrameworkManagerImpl.Instance()
 
     def tearDown(self):
-      AnalysisDataService.Instance().clear()
+        AnalysisDataService.Instance().clear()
 
     def test_len_returns_correct_value(self):
         self.assertEquals(len(AnalysisDataService), 0)
@@ -166,5 +167,34 @@ class AnalysisDataServiceTest(unittest.TestCase):
         for name in extra_names:
             mtd.remove(name)
 
+    def test_addToGroup_adds_workspace_to_group(self):
+        from mantid.simpleapi import CreateSampleWorkspace, GroupWorkspaces
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+        CreateSampleWorkspace(OutputWorkspace="ws3")
+
+        AnalysisDataService.addToGroup("NewGroup", "ws3")
+
+        group = mtd['NewGroup']
+
+        self.assertEquals(group.size(), 3)
+        six.assertCountEqual(self, group.getNames(), ["ws1", "ws2", "ws3"])
+
+    def test_removeFromGroup_removes_workspace_from_group(self):
+        from mantid.simpleapi import CreateSampleWorkspace, GroupWorkspaces
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        CreateSampleWorkspace(OutputWorkspace="ws3")
+        GroupWorkspaces(InputWorkspaces="ws1,ws2,ws3", OutputWorkspace="NewGroup")
+
+        AnalysisDataService.removeFromGroup("NewGroup", "ws3")
+
+        group = mtd['NewGroup']
+
+        self.assertEquals(group.size(), 2)
+        six.assertCountEqual(self, group.getNames(), ["ws1", "ws2"])
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/api/CMakeLists.txt b/Framework/PythonInterface/test/python/mantid/api/CMakeLists.txt
index 6a6816959dea5cd0d8bf275861851bb10d97ea22..5edd860be0586d0451f3ba5c0e168bf23e673a42 100644
--- a/Framework/PythonInterface/test/python/mantid/api/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/mantid/api/CMakeLists.txt
@@ -9,6 +9,7 @@ set ( TEST_PY_FILES
   AlgorithmManagerTest.py
   AlgorithmPropertyTest.py
   AnalysisDataServiceTest.py
+  AnalysisDataServiceObserverTest.py
   AxisTest.py
   CatalogManagerTest.py
   CompositeFunctionTest.py
diff --git a/Framework/PythonInterface/test/python/mantid/api/FileFinderTest.py b/Framework/PythonInterface/test/python/mantid/api/FileFinderTest.py
index a5ae031553c24907fefe7def6ea75cdc29146455..8a8d59dc738afb77dc487bb691027522d458701e 100644
--- a/Framework/PythonInterface/test/python/mantid/api/FileFinderTest.py
+++ b/Framework/PythonInterface/test/python/mantid/api/FileFinderTest.py
@@ -6,9 +6,11 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
 
+import os
 import unittest
+
 from mantid.api import FileFinder
-import os
+
 
 class FileFinderTest(unittest.TestCase):
 
@@ -24,4 +26,19 @@ class FileFinderTest(unittest.TestCase):
         # We can't be sure what the full path is in general but it should certainly exist!
         self.assertTrue(os.path.exists(runs[0]))
 
-if __name__ == '__main__': unittest.main()
\ No newline at end of file
+    def test_that_find_runs_accepts_a_list_of_string_and_a_bool(self):
+        try:
+            runs = FileFinder.findRuns("CNCS7860", useExtsOnly=True)
+            FileFinder.findRuns("CNCS7860", [".nxs", ".txt"], useExtsOnly=True)
+        except Exception as e:
+            if type(e).__name__ == "ArgumentError":
+                self.assertFalse(True, "Expected findRuns to accept a list of strings and a bool as input."
+                                       " {} error was raised with message {}".format(type(e).__name__, str(e)))
+        else:
+            # Confirm that it works as above
+            self.assertTrue(len(runs) == 1)
+            self.assertTrue(os.path.exists(runs[0]))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/api/ITableWorkspaceTest.py b/Framework/PythonInterface/test/python/mantid/api/ITableWorkspaceTest.py
index ea1baf299d492dd8e3164b7652dc55d5da16c7da..2d9fe0a81c3dc98253d52dc2b25425e0e159c05b 100644
--- a/Framework/PythonInterface/test/python/mantid/api/ITableWorkspaceTest.py
+++ b/Framework/PythonInterface/test/python/mantid/api/ITableWorkspaceTest.py
@@ -211,12 +211,25 @@ class ITableWorkspaceTest(unittest.TestCase):
         table.addRow([3, 4])
         self.assertEquals(table.rowCount(), 2)
 
+    def test_column_types(self):
+        table = WorkspaceFactory.createTable()
+        table.addColumn(type="int",name="index")
+        table.addColumn(type="str",name="value")
+        table.addColumn(type="V3D",name="position")
+
+        types = table.columnTypes()
+
+        self.assertEqual(types[0], "int")
+        self.assertEqual(types[1], "str")
+        self.assertEqual(types[2], "V3D")
+
+
     def test_convert_to_dict(self):
         from mantid.kernel import V3D
         expected_output = {
-            'index': [1, 2], 
-            'value': ['10', '100'], 
-            'position': [V3D(0, 0, 1), V3D(1, 0, 0)] 
+            'index': [1, 2],
+            'value': ['10', '100'],
+            'position': [V3D(0, 0, 1), V3D(1, 0, 0)]
         }
 
         table = WorkspaceFactory.createTable()
diff --git a/Framework/PythonInterface/test/python/mantid/dataobjects/EventListTest.py b/Framework/PythonInterface/test/python/mantid/dataobjects/EventListTest.py
index 60a87c5952b554916862134d9f0ba69430a2253f..c6b1420f8da23e1a754b0eccd512757f1317f829 100644
--- a/Framework/PythonInterface/test/python/mantid/dataobjects/EventListTest.py
+++ b/Framework/PythonInterface/test/python/mantid/dataobjects/EventListTest.py
@@ -61,5 +61,15 @@ class EventListTest(unittest.TestCase):
 
         self.assertEquals(left.integrate(-1.,31., True), -10.)
 
+    def test_mask_condition(self):
+        evl = self.createRandomEventList(20)
+
+        tof = evl.getTofs()
+        mask = (tof < 10)
+        evl.maskCondition(mask)
+
+        self.assertEquals(evl.getNumberEvents(), 10)
+        self.assertEquals(evl.getTofMax(), float(9.0))
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py b/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py
index a3de637b28bed02faa035813f5bb0a4fc450f8ce..6ac48ff2ca9019af362b1cf84c3b9d275cb93fbb 100644
--- a/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py
+++ b/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py
@@ -14,6 +14,7 @@ from mantid.kernel import V3D
 from mantid.kernel import Quat
 from mantid.geometry import CSGObject
 from mantid.simpleapi import *
+from itertools import islice
 
 class ComponentInfoTest(unittest.TestCase):
 
@@ -185,7 +186,16 @@ class ComponentInfoTest(unittest.TestCase):
         info = workspace.componentInfo()
         self.assertEquals(info.size(), 1)
 
-
+    def test_indexOfAny(self):
+        info = self._ws.componentInfo()
+        index = info.indexOfAny(info.name(info.root()))
+        # Root index and the discovered index should be the same
+        self.assertEquals(index, info.root())
+    
+    def test_indexOfAny_throws(self):
+        info = self._ws.componentInfo()
+        with self.assertRaises(ValueError):
+            info.indexOfAny('fictitious')
     """
     ----------------------------------------------------------------------------
     Extreme Tests
@@ -474,5 +484,66 @@ class ComponentInfoTest(unittest.TestCase):
         with self.assertRaises(TypeError):
             info.shape(11.32)
 
+    def test_basic_iterator(self):
+        info = self._ws.componentInfo()
+        expected_iterations = len(info) 
+        actual_iterations = len(list(iter(info)))
+        self.assertEquals(expected_iterations, actual_iterations)
+        it = iter(info)
+        self.assertEquals(next(it).index, 0)
+        self.assertEquals(next(it).index, 1)
+        
+    def test_isDetector_via_iterator(self):
+        comp_info = self._ws.componentInfo()
+        n_detectors = len(self._ws.detectorInfo())
+        it = iter(comp_info)
+        self.assertEquals(next(it).isDetector, True)
+        self.assertEquals(next(it).isDetector, True)
+        self.assertEquals(next(it).isDetector, False)
+        self.assertEquals(next(it).isDetector, False)
+
+    def test_position_via_iterator(self):
+        comp_info = self._ws.componentInfo()
+        source_pos = comp_info.sourcePosition()
+        it = iter(comp_info)
+        # basic check on first detector position
+        self.assertTrue(next(it).position.distance(source_pos) > 0)
+        
+    def test_children_via_iterator(self):
+        info = self._ws.componentInfo()
+        it = iter(info)
+        first_det = next(it) 
+        self.assertEquals(type(first_det.children), np.ndarray)
+        self.assertEquals(len(first_det.children), 0)
+        root = next(it)
+        for root in it:
+            continue
+        self.assertEquals(root.index, info.root()) # sanity check
+        self.assertTrue(np.array_equal(root.children, np.array([0,1,2,3,4], dtype='uint64')))
+
+    def test_detectorsInSubtree_via_iterator(self):
+        info = self._ws.componentInfo()
+        it = iter(info)
+        first_det = next(it) 
+        self.assertEquals(type(first_det.detectorsInSubtree), np.ndarray)
+        # For detectors, only contain own index
+        self.assertTrue(np.array_equal(first_det.detectorsInSubtree,np.array([0], dtype='uint64')))
+        root = next(it)
+        for root in it:
+            continue
+        self.assertTrue(np.array_equal(root.detectorsInSubtree, np.array([0,1], dtype='uint64')))
+
+    def test_componentsInSubtree_via_iterator(self):
+        info = self._ws.componentInfo()
+        it = iter(info)
+        first_det = next(it) 
+        self.assertEquals(type(first_det.detectorsInSubtree), np.ndarray)
+        # For detectors, only contain own index
+        self.assertTrue(np.array_equal(first_det.componentsInSubtree,np.array([0], dtype='uint64')))
+        root = next(it)
+        for root in it:
+            continue
+        # All component indices expected including self
+        self.assertTrue(np.array_equal(root.componentsInSubtree, np.array([0,1,2,3,4,5], dtype='uint64')))
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py b/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py
index a9ca56956b6369c20a6f475597f0fd513c4dd91c..2419c714d0e4a9b2bf33901030542b94d1759203 100644
--- a/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py
+++ b/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py
@@ -109,7 +109,19 @@ class DetectorInfoTest(unittest.TestCase):
         self.assertEquals(type(info.rotation(0)), Quat)
         self.assertEquals(type(info.rotation(1)), Quat)
 
-
+    def test_l2(self):
+        det_info = self._ws.detectorInfo()
+        sample_pos = self._ws.componentInfo().samplePosition()
+        l2_calc = det_info.position(0).distance(sample_pos)
+        self.assertEquals(det_info.l2(0), l2_calc)
+
+    def test_l1(self):
+        source_pos = self._ws.componentInfo().sourcePosition()
+        sample_pos = self._ws.componentInfo().samplePosition()
+        l1_calc = sample_pos.distance(source_pos)
+        det_info = self._ws.detectorInfo()
+        self.assertEquals(det_info.l1(), l1_calc)
+        
     """
     ---------------
     Iteration
@@ -121,6 +133,9 @@ class DetectorInfoTest(unittest.TestCase):
         expected_iterations = len(info) 
         actual_iterations = len(list(iter(info)))
         self.assertEquals(expected_iterations, actual_iterations)
+        it = iter(info)
+        self.assertEquals(next(it).index, 0)
+        self.assertEquals(next(it).index, 1)
 
     def test_iterator_for_monitors(self):
         info = self._ws.detectorInfo()
@@ -155,6 +170,10 @@ class DetectorInfoTest(unittest.TestCase):
                 self.assertTrue(pos.Y() > lastY)
             lastY = pos.Y()
 
+    def test_iterator_for_l2(self):
+        info = self._ws.detectorInfo()
+        for item in info:
+            self.assertTrue(item.l2 > 0)
 
     """
     ----------------------------------------------------------------------------
diff --git a/Framework/PythonInterface/test/python/mantid/plots/helperfunctionsTest.py b/Framework/PythonInterface/test/python/mantid/plots/helperfunctionsTest.py
index 355fa27b3cc98cbc8473733446a18a07d1f6e393..66fa97330243f041979e1f0755b0c23801655780 100644
--- a/Framework/PythonInterface/test/python/mantid/plots/helperfunctionsTest.py
+++ b/Framework/PythonInterface/test/python/mantid/plots/helperfunctionsTest.py
@@ -4,20 +4,60 @@
 #     NScD Oak Ridge National Laboratory, European Spallation Source
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
+#
 from __future__ import (absolute_import, division, print_function)
 
+import datetime
 import unittest
+
 import matplotlib
-matplotlib.use('AGG')
-import datetime
 import numpy as np
+from mock import Mock
+
 import mantid.api
 import mantid.plots.helperfunctions as funcs
 from mantid.kernel import config
-from mantid.simpleapi import CreateWorkspace, DeleteWorkspace, CreateMDHistoWorkspace,\
-                             ConjoinWorkspaces, AddTimeSeriesLog
+from mantid.plots.utility import MantidAxType
+from mantid.simpleapi import AddTimeSeriesLog, ConjoinWorkspaces, CreateMDHistoWorkspace, CreateSampleWorkspace, \
+    CreateSingleValuedWorkspace, CreateWorkspace, DeleteWorkspace
+
+matplotlib.use('AGG')
 
 
+def add_workspace_with_data(func):
+    def wrapper(self):
+        dataX = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
+        dataY = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+        dataE = dataY
+        dX = dataY
+
+        ws = CreateWorkspace(DataX=dataX, DataY=dataY, DataE=dataE, NSpec=4, UnitX="Wavelength", Dx=dX)
+        return func(self, ws)
+
+    return wrapper
+
+
+def add_md_workspace_with_data(dimensions=2):
+    def function_wrapper(func):
+        def wrapper(self):
+            if dimensions == 2:
+                S = range(0, 100)
+                ERR = range(0, 100)
+                mdws = CreateMDHistoWorkspace(Dimensionality=2, Extents='-3,3,-10,10', SignalInput=S, ErrorInput=ERR,
+                                              NumberOfBins='10,10', Names='Dim1,Dim2',
+                                              Units='MomentumTransfer,EnergyTransfer')
+            else:
+                S = range(0, 1000)
+                ERR = range(0, 1000)
+                mdws = CreateMDHistoWorkspace(Dimensionality=3, Extents='-3,3,-10,10,-20,20', SignalInput=S,
+                                              ErrorInput=ERR,
+                                              NumberOfBins='10,10,10', Names='Dim1,Dim2,Dim3',
+                                              Units='MomentumTransfer,EnergyTransfer,EnergyTransfer')
+            return func(self, mdws)
+
+        return wrapper
+
+    return function_wrapper
 
 
 class HelperFunctionsTest(unittest.TestCase):
@@ -263,15 +303,248 @@ class HelperFunctionsTest(unittest.TestCase):
         np.testing.assert_allclose(z[1], np.array([1, 2, 3, 4]))
 
     def test_get_sample_logs(self):
-        x, y, FullTime, LogName, units, kwargs = funcs.get_sample_log(self.ws2d_histo,LogName='my_log', FullTime=True)
-        self.assertEquals(x[0],datetime.datetime(2010,1,1,0,0,0))
-        self.assertEquals(x[1],datetime.datetime(2010,1,1,0,30,0))
-        self.assertEquals(x[2],datetime.datetime(2010,1,1,0,50,0))
-        np.testing.assert_allclose(y, np.array([100,15,100.2]))
+        x, y, FullTime, LogName, units, kwargs = funcs.get_sample_log(self.ws2d_histo, LogName='my_log', FullTime=True)
+        self.assertEquals(x[0], datetime.datetime(2010, 1, 1, 0, 0, 0))
+        self.assertEquals(x[1], datetime.datetime(2010, 1, 1, 0, 30, 0))
+        self.assertEquals(x[2], datetime.datetime(2010, 1, 1, 0, 50, 0))
+        np.testing.assert_allclose(y, np.array([100, 15, 100.2]))
         self.assertTrue(FullTime)
         self.assertEquals(LogName, 'my_log')
         self.assertEquals(units, '')
         self.assertEquals(kwargs, {})
 
+    def test_validate_args_success(self):
+        ws = CreateSampleWorkspace()
+        result = funcs.validate_args(ws)
+        self.assertEqual(True, result)
+
+    def test_get_distribution(self):
+        ws = CreateSampleWorkspace()
+        result = funcs.get_distribution(ws)
+        self.assertEqual((False, {}), result)
+
+    def test_get_distribution_from_kwargs(self):
+        ws = CreateSampleWorkspace()
+        result = funcs.get_distribution(ws, distribution=True)
+        self.assertEqual((True, {}), result)
+        result = funcs.get_distribution(ws, distribution=False)
+        self.assertEqual((False, {}), result)
+
+    def test_points_from_boundaries_raise_length_less_than_2(self):
+        arr = np.array([1])
+        self.assertRaises(ValueError, funcs.points_from_boundaries, arr)
+
+    def test_points_from_boundaries_raise_not_np_array(self):
+        arr = [1, 2, 3, 4]
+        self.assertRaises(AssertionError, funcs.points_from_boundaries, arr)
+
+    def test_dim2array(self):
+        class MockIMDDimension:
+            def __init__(self):
+                self.getMinimum = Mock(return_value=1)
+                self.getMaximum = Mock(return_value=10)
+                self.getNBins = Mock(return_value=3)
+
+        mock_dimension = MockIMDDimension()
+        result = funcs._dim2array(mock_dimension)
+        self.assertTrue(np.array_equal([1., 4., 7., 10.], result))
+
+    def test_get_wksp_index_and_spec_num_with_specNum_axis_spectrum(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace when traversing the SPECTRUM axis
+
+        This test provides a spectrum number and expects the workspace index to be correct
+        """
+        ws = CreateSampleWorkspace()
+        axis = MantidAxType.SPECTRUM
+        res_workspace_index, res_spectrum_number, res_kwargs = funcs._get_wksp_index_and_spec_num(ws, axis, specNum=3)
+        self.assertEqual(2, res_workspace_index)
+        self.assertEqual(3, res_spectrum_number)
+
+    def test_get_wksp_index_and_spec_num_with_specNum_axis_bin(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace when traversing the BIN axis
+
+        This test provides a spectrum number and expects the workspace index to be correct
+        """
+        ws = CreateSampleWorkspace()
+        axis = MantidAxType.BIN
+        res_workspace_index, res_spectrum_number, res_kwargs = funcs._get_wksp_index_and_spec_num(ws, axis, specNum=3)
+        self.assertEqual(2, res_workspace_index)
+        self.assertEqual(3, res_spectrum_number)
+
+    def test_get_wksp_index_and_spec_num_1_histogram_axis_spectrum(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace with 1 histogram,
+        when traversing the SPECTRUM axis
+        """
+        ws = CreateSingleValuedWorkspace()
+        axis = MantidAxType.SPECTRUM
+        res_workspace_index, res_spectrum_number, res_kwargs = funcs._get_wksp_index_and_spec_num(ws, axis)
+        self.assertEqual(0, res_workspace_index)
+        self.assertEqual(0, res_spectrum_number)
+
+    def test_get_wksp_index_and_spec_num_1_histogram_axis_bin(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace with 1 histogram,
+        when traversing the BIN axis
+        """
+        ws = CreateSingleValuedWorkspace()
+        axis = MantidAxType.BIN
+        res_workspace_index, res_spectrum_number, res_kwargs = funcs._get_wksp_index_and_spec_num(ws, axis)
+        self.assertEqual(0, res_workspace_index)
+        self.assertEqual(None, res_spectrum_number)
+
+    def test_get_wksp_index_and_spec_num_with_wkspIndex_axis_bin(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace, when traversing the BIN axis
+
+        This test provides a workspace index, and expects the spectrum number to be correct
+        """
+        ws = CreateSampleWorkspace()
+        axis = MantidAxType.BIN
+        res_workspace_index, res_spectrum_number, res_kwargs = funcs._get_wksp_index_and_spec_num(ws, axis, wkspIndex=5)
+        self.assertEqual(5, res_workspace_index)
+        self.assertEqual(None, res_spectrum_number)
+
+    def test_get_wksp_index_and_spec_num_with_wkspIndex_axis_spectrum(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace, when traversing the SPECTRUM axis
+
+        This test provides a workspace index, and expects the spectrum number to be correct
+        """
+        ws = CreateSampleWorkspace()
+        axis = MantidAxType.SPECTRUM
+        res_workspace_index, res_spectrum_number, res_kwargs = funcs._get_wksp_index_and_spec_num(ws, axis, wkspIndex=5)
+        self.assertEqual(5, res_workspace_index)
+        self.assertEqual(6, res_spectrum_number)
+
+    def test_get_wksp_index_and_spec_num_error_with_both(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace
+
+        This test checks that an error is shown when both a spectrum number and a workspace index are passed in
+        """
+        ws = CreateSampleWorkspace()
+        axis = MantidAxType.SPECTRUM  # doesn't matter for this test
+        self.assertRaises(RuntimeError, funcs._get_wksp_index_and_spec_num, ws, axis, wkspIndex=5, specNum=3)
+
+    def test_get_wksp_index_and_spec_num_error_with_none(self):
+        """
+        Test getting the WorkspaceIndex and Spectrum Number for a Workspace
+
+        This test checks that an error is shown when neither spectrum number nor workspace index is passed in
+        """
+        ws = CreateSampleWorkspace()
+        axis = MantidAxType.SPECTRUM  # doesn't matter for this test
+        self.assertRaises(RuntimeError, funcs._get_wksp_index_and_spec_num, ws, axis)
+
+    def test_get_wksp_index_dist_and_label_for_bins(self):
+        """
+        Tests that the workspace index, distribution and label are correctly retrieved.
+
+        The label changes depending on the axis.
+        """
+        ws = CreateSampleWorkspace()
+        axis = MantidAxType.BIN
+        res_workspace_index, res_distribution, res_kwargs = funcs.get_wksp_index_dist_and_label(ws, axis, wkspIndex=1)
+
+        self.assertEqual(1, res_workspace_index)
+        self.assertEqual(False, res_distribution)
+        self.assertEqual(res_kwargs['label'], 'ws: bin 1')
+
+        res_workspace_index, res_distribution, res_kwargs = funcs.get_wksp_index_dist_and_label(ws, axis, wkspIndex=0)
+
+        self.assertEqual(0, res_workspace_index)
+        self.assertEqual(False, res_distribution)
+        self.assertEqual(res_kwargs['label'], 'ws: bin 0')
+
+    @add_md_workspace_with_data
+    def test_get_md_data_no_error(self, mdws):
+        dim_arrays, data, err = funcs.get_md_data(mdws, normalization=None)
+        self.assertEqual(11, len(dim_arrays[0]))
+        self.assertEqual(-3, dim_arrays[0][0])
+        self.assertEqual(3, dim_arrays[0][-1])
+
+        self.assertEqual(11, len(dim_arrays[1]))
+        self.assertEqual(-10, dim_arrays[1][0])
+        self.assertEqual(10, dim_arrays[1][-1])
+
+        self.assertTrue(all(len(d) == 10 for d in data))
+        self.assertEqual(0.0, data[0][0])
+        self.assertEqual(99.0, data[-1][-1])
+
+        self.assertIsNone(err)
+
+    @add_md_workspace_with_data
+    def test_get_md_data_with_error(self, mdws):
+        dim_arrays, data, err = funcs.get_md_data(mdws, normalization=None, withError=True)
+        self.assertEqual(11, len(dim_arrays[0]))
+        self.assertEqual(-3, dim_arrays[0][0])
+        self.assertEqual(3, dim_arrays[0][-1])
+
+        self.assertEqual(11, len(dim_arrays[1]))
+        self.assertEqual(-10, dim_arrays[1][0])
+        self.assertEqual(10, dim_arrays[1][-1])
+
+        self.assertTrue(all(len(d) == 10 for d in data))
+        self.assertEqual(0.0, data[0][0])
+        self.assertEqual(99.0, data[-1][-1])
+
+        self.assertTrue(all(len(e) == 10 for e in err))
+        self.assertEqual(0.0, err[0][0])
+        self.assertEqual(99.0, err[-1][-1])
+
+    @add_workspace_with_data
+    def test_get_spectrum_no_dy_dx(self, ws):
+        x, y, dy, dx = funcs.get_spectrum(ws, 3, distribution=False, withDy=False, withDx=False)
+        self.assertTrue(np.array_equal([13.5, 14.5, 15.5], x))
+        self.assertTrue(np.array_equal([10.0, 11.0, 12.0], y))
+        self.assertIsNone(dy)
+        self.assertIsNone(dx)
+
+    @add_workspace_with_data
+    def test_get_spectrum_with_dy_dx(self, ws):
+        x, y, dy, dx = funcs.get_spectrum(ws, 3, distribution=False, withDy=True, withDx=True)
+
+        self.assertTrue(np.array_equal([13.5, 14.5, 15.5], x))
+        self.assertTrue(np.array_equal([10.0, 11.0, 12.0], y))
+        self.assertTrue(np.array_equal([10.0, 11.0, 12.0], dy))
+        self.assertTrue(np.array_equal([10.0, 11.0, 12.0], dx))
+
+    @add_workspace_with_data
+    def test_get_bins_no_dy(self, ws):
+        x, y, dy, dx = funcs.get_bins(ws, 1, withDy=False)
+        self.assertTrue(np.array_equal([0, 1, 2, 3], x))
+        self.assertTrue(np.array_equal([2.0, 5.0, 8.0, 11.0], y))
+        self.assertIsNone(dy)
+
+    @add_workspace_with_data
+    def test_get_bins_with_dy(self, ws):
+        x, y, dy, dx = funcs.get_bins(ws, 1, withDy=True)
+        self.assertTrue(np.array_equal([0, 1, 2, 3], x))
+        self.assertTrue(np.array_equal([2.0, 5.0, 8.0, 11.0], y))
+        self.assertTrue(np.array_equal([2.0, 5.0, 8.0, 11.0], dy))
+
+    @add_md_workspace_with_data()
+    def test_get_md_data2d_bin_bounds(self, mdws):
+        coord1, coord2, data = funcs.get_md_data2d_bin_bounds(mdws, False)
+        self.assertEqual(11, len(coord1))
+        self.assertEqual(-3, coord1[0])
+        self.assertEqual(3, coord1[-1])
+
+        self.assertEqual(11, len(coord2))
+        self.assertEqual(-10, coord2[0])
+        self.assertEqual(10, coord2[-1])
+
+        self.assertTrue(all(len(d) == 10 for d in data))
+        self.assertEqual(0.0, data[0][0])
+        self.assertEqual(99.0, data[-1][-1])
+
+    @add_md_workspace_with_data(dimensions=3)
+    def test_get_md_data2d_bin_bounds_raises_AssertionException_too_many_dims(self, mdws):
+        self.assertRaises(AssertionError, funcs.get_md_data2d_bin_bounds, mdws, False)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py b/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py
index f815504db44a0af3ee3ce1026b9581a8a6e45ccc..0002e5deac9c872114d402619b862eb9ac72955a 100644
--- a/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py
+++ b/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py
@@ -123,6 +123,26 @@ class PlotFunctionsTest(unittest.TestCase):
         funcs.pcolorfast(ax, self.ws2d_point_uneven, vmin=-1)
         funcs.imshow(ax, self.ws2d_histo)
 
+    def _do_update_colorplot_datalimits(self, color_func):
+        fig, ax = plt.subplots()
+        mesh = color_func(ax, self.ws2d_histo)
+        ax.set_xlim(-0.05, 0.05)
+        ax.set_ylim(-0.05, 0.05)
+        funcs.update_colorplot_datalimits(ax, mesh)
+        self.assertAlmostEqual(10.0, ax.get_xlim()[0])
+        self.assertAlmostEqual(30.0, ax.get_xlim()[1])
+        self.assertAlmostEqual(4.0, ax.get_ylim()[0])
+        self.assertAlmostEqual(8.0, ax.get_ylim()[1])
+
+    def test_update_colorplot_datalimits_for_pcolormesh(self):
+        self._do_update_colorplot_datalimits(funcs.pcolormesh)
+
+    def test_update_colorplot_datalimits_for_pcolor(self):
+        self._do_update_colorplot_datalimits(funcs.pcolor)
+
+    def test_update_colorplot_datalimits_for_imshow(self):
+        self._do_update_colorplot_datalimits(funcs.imshow)
+
     def test_1d_plots_with_unplottable_type_raises_attributeerror(self):
         table = CreateEmptyTableWorkspace()
         _, ax = plt.subplots()
diff --git a/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py b/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py
index 018bbed0675f8e9cd45734a9c87ed72ae2c72e17..041e807df189b8c734ac7385ab8fe28074bd80c3 100644
--- a/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py
+++ b/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py
@@ -9,10 +9,14 @@ from __future__ import (absolute_import, division, print_function)
 import matplotlib
 matplotlib.use('AGG')
 import matplotlib.pyplot as plt
+from matplotlib.container import ErrorbarContainer
 import numpy as np
 import unittest
 
-from mantid.simpleapi import CreateWorkspace, DeleteWorkspace
+from mantid.plots.plotfunctions import get_colorplot_extents
+from mantid.api import WorkspaceFactory
+from mantid.simpleapi import (AnalysisDataService, CreateWorkspace,
+                              CreateSampleWorkspace, DeleteWorkspace)
 
 
 class Plots__init__Test(unittest.TestCase):
@@ -35,11 +39,127 @@ class Plots__init__Test(unittest.TestCase):
     def tearDownClass(cls):
         DeleteWorkspace('ws2d_histo')
 
-    def test_1d_plots(self):
-        fig, ax = plt.subplots(subplot_kw={'projection': 'mantid'})
-        # ax.plot(self.ws2d_histo, 'rs', specNum=1)
-        ax.plot(self.ws2d_histo, specNum=2, linewidth=6)
-        ax.plot(np.arange(10), np.arange(10), 'bo-')
+    def setUp(self):
+        self.fig, self.ax = plt.subplots(subplot_kw={'projection': 'mantid'})
+
+    def tearDown(self):
+        plt.close('all')
+        self.fig, self.ax = None, None
+
+    def test_line2d_plots(self):
+        self.ax.plot(self.ws2d_histo, 'rs', specNum=2, linewidth=6)
+        self.assertEqual('r', self.ax.lines[-1].get_color())
+        self.ax.plot(np.arange(10), np.arange(10), 'bo-')
+
+    def test_errorbar_plots(self):
+        self.ax.errorbar(self.ws2d_histo, specNum=2, linewidth=6)
+        self.ax.errorbar(np.arange(10), np.arange(10), 0.1*np.ones((10,)), fmt='bo-')
+
+    def test_imshow(self):
+        self.ax.imshow(self.ws2d_histo)
+
+    def test_pcolor(self):
+        self.ax.pcolor(self.ws2d_histo)
+
+    def test_pcolorfast(self):
+        self.ax.pcolorfast(self.ws2d_histo)
+
+    def test_pcolormesh(self):
+        self.ax.pcolormesh(self.ws2d_histo)
+
+    def test_remove_workspace_artist_for_known_workspace_removes_plot(self):
+        self.ax.plot(self.ws2d_histo, specNum=2, linewidth=6)
+        is_empty = self.ax.remove_workspace_artists(self.ws2d_histo)
+        self.assertEqual(True, is_empty)
+        self.assertEqual(0, len(self.ax.lines))
+
+    def test_remove_workspace_artist_for_unknown_workspace_does_nothing(self):
+        self.ax.plot(self.ws2d_histo, specNum=2, linewidth=6)
+        unknown_ws = CreateSampleWorkspace()
+        self.ax.remove_workspace_artists(unknown_ws)
+        self.assertEqual(1, len(self.ax.lines))
+
+    def test_remove_workspace_artist_for_removes_only_specified_workspace(self):
+        second_ws = CreateSampleWorkspace()
+        line_ws2d_histo = self.ax.plot(self.ws2d_histo, specNum=2, linewidth=6)[0]
+        line_second_ws = self.ax.plot(second_ws, specNum=5)[0]
+        self.assertEqual(2, len(self.ax.lines))
+
+        self.ax.remove_workspace_artists(self.ws2d_histo)
+        self.assertEqual(1, len(self.ax.lines))
+        self.assertTrue(line_ws2d_histo not in self.ax.lines)
+        self.assertTrue(line_second_ws in self.ax.lines)
+        DeleteWorkspace(second_ws)
+
+    def test_replace_workspace_data_plot(self):
+        plot_data = CreateWorkspace(DataX=[10, 20, 30, 10, 20, 30, 10, 20, 30],
+                                    DataY=[3, 4, 5, 3, 4, 5],
+                                    DataE=[1, 2, 3, 4, 1, 1],
+                                    NSpec=3)
+        line_ws2d_histo = self.ax.plot(plot_data, specNum=2, color='r')[0]
+        plot_data = CreateWorkspace(DataX=[20, 30, 40, 20, 30, 40, 20, 30, 40],
+                                    DataY=[3, 4, 5, 3, 4, 5],
+                                    DataE=[1, 2, 3, 4, 1, 1],
+                                    NSpec=3)
+        self.ax.replace_workspace_artists(plot_data)
+        self.assertAlmostEqual(25, line_ws2d_histo.get_xdata()[0])
+        self.assertAlmostEqual(35, line_ws2d_histo.get_xdata()[-1])
+        self.assertEquals('r', line_ws2d_histo.get_color())
+        # try deleting
+        self.ax.remove_workspace_artists(plot_data)
+
+    def test_replace_workspace_data_errorbar(self):
+        eb_data = CreateWorkspace(DataX=[10, 20, 30, 10, 20, 30, 10, 20, 30],
+                                  DataY=[3, 4, 5, 3, 4, 5],
+                                  DataE=[1, 2, 3, 4, 1, 1],
+                                  NSpec=3)
+        self.ax.errorbar(eb_data, specNum=2, color='r')
+        eb_data = CreateWorkspace(DataX=[20, 30, 40, 20, 30, 40, 20, 30, 40],
+                                     DataY=[3, 4, 5, 3, 4, 5],
+                                     DataE=[.1, .2, .3, .4, .1, .1],
+                                     NSpec=3)
+        self.ax.replace_workspace_artists(eb_data)
+        self.assertEqual(1, len(self.ax.containers))
+        eb_container = self.ax.containers[0]
+        self.assertTrue(isinstance(eb_container, ErrorbarContainer))
+        self.assertAlmostEqual(25, eb_container[0].get_xdata()[0])
+        self.assertAlmostEqual(35, eb_container[0].get_xdata()[-1])
+        self.assertEquals('r', eb_container[0].get_color())
+        # try deleting
+        self.ax.remove_workspace_artists(eb_data)
+
+    def _do_image_replace_common_bins(self, color_func, artists):
+        im_data = CreateWorkspace(DataX=[10, 20, 30, 10, 20, 30, 10, 20, 30],
+                                  DataY=[3, 4, 5, 3, 4, 5],
+                                  DataE=[1, 2, 3, 4, 1, 1],
+                                  NSpec=3)
+        getattr(self.ax, color_func)(im_data)
+        im_data = CreateWorkspace(DataX=[20, 30, 40, 20, 30, 40, 20, 30, 40],
+                                  DataY=[3, 4, 5, 3, 4, 5],
+                                  DataE=[.1, .2, .3, .4, .1, .1],
+                                  NSpec=3, VerticalAxisValues=[2, 3, 4],
+                                  VerticalAxisUnit='DeltaE')
+        self.ax.replace_workspace_artists(im_data)
+        self.assertEquals(1, len(artists))
+        left, right, bottom, top = get_colorplot_extents(artists[0])
+        self.assertAlmostEqual(20., left)
+        self.assertAlmostEqual(40., right)
+        self.assertAlmostEqual(1.5, bottom)
+        self.assertAlmostEqual(4.5, top)
+        # try deleting
+        self.ax.remove_workspace_artists(im_data)
+
+    def test_replace_workspace_data_imshow(self):
+        self._do_image_replace_common_bins('imshow', self.ax.images)
+
+    def test_replace_workspace_data_pcolor(self):
+        self._do_image_replace_common_bins('pcolor', self.ax.collections)
+
+    def test_replace_workspace_data_pcolorfast(self):
+        self._do_image_replace_common_bins('pcolorfast', self.ax.collections)
+
+    def test_replace_workspace_data_pcolormesh(self):
+        self._do_image_replace_common_bins('pcolormesh', self.ax.collections)
 
     def test_3d_plots(self):
         fig = plt.figure()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
index b4bc9506769944334f4c5ee668de905f41278ac0..4603c39cde4eec159288be26df141e29c8fc97de 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
@@ -12,6 +12,7 @@ set ( TEST_PY_FILES
   AngularAutoCorrelationsTwoAxesTest.py
   ApplyDetectorScanEffCorrTest.py
   BinWidthAtXTest.py
+  CalculateEfficiencyCorrectionTest.py
   CalculateSampleTransmissionTest.py
   CheckForSampleLogsTest.py
   ConjoinSpectraTest.py
@@ -78,6 +79,7 @@ set ( TEST_PY_FILES
   MuonMaxEntTest.py
   NMoldyn4InterpolationTest.py
   NormaliseSpectraTest.py
+  OptimizeCrystalPlacementByRunTest.py
   ReflectometryReductionOneLiveDataTest.py
   ReflectometrySliceEventWorkspaceTest.py
   RetrieveRunInfoTest.py
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CalculateEfficiencyCorrectionTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/CalculateEfficiencyCorrectionTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..5254b3e668d3e61e904b15695992ea6c6e413743
--- /dev/null
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/CalculateEfficiencyCorrectionTest.py
@@ -0,0 +1,436 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+from mantid.simpleapi import \
+    CalculateEfficiencyCorrection, CloneWorkspace, ConvertToPointData, \
+    CreateSampleWorkspace, DeleteWorkspace, LoadAscii, Multiply
+from testhelpers import run_algorithm
+from mantid.api import AnalysisDataService
+
+
+class CalculateEfficiencyCorrectionTest(unittest.TestCase):
+
+    _input_wksp = "input_wksp"
+    _correction_wksp = "correction_wksp"
+    _output_wksp = "output_wksp"
+    _wavelengths="0.2,0.01,4.0"
+    _alpha = 0.693
+    _chemical_formula = "(He3)"
+    _number_density = 0.0002336682167635477
+    _mass_density = 0.0011702649036052439
+    _efficiency1_forAbsXS = 0.712390781371
+    _efficiency2_forAbsXS = { "Efficiency": 0.74110947758, "Wavelength": 1.95}
+    _efficiency1_forTotalXS = 0.712793729636
+    _efficiency2_forTotalXS = { "Efficiency": 0.741472190178, "Wavelength": 1.95}
+    _thickness = 1.0
+
+    def setUp(self):
+        '''
+        This file is the back-calculated spectrum of polyethylene moderator (ambient 300K)
+        prior to the efficiency correction described in Eq (3) of:
+          Mildner et al. "A cooled polyethylene moderator on a pulsed neutron source",
+          Nucl. Instr. Meth. 152, 1978, doi:/10.1016/0029-554X(78)90043-5
+
+        After the correction is applied, the workspace will replicated (b) in Fig. 2 of this article.
+
+        Similar results are shown in Fig 1.a for "ambient (300K)" in:
+          S. Howells, "On the choice of moderator for a liquids diffractometer on a pulsed neutron source",
+          Nucl. Instr. Meth. Phys. Res. 223, 1984, doi:10.1016/0167-5087(84)90256-4
+        '''
+        LoadAscii(OutputWorkspace=self._input_wksp,
+                  Filename="CalculateEfficiencyCorrection_milder_moderator_polyethlyene_300K.txt",
+                  Unit="Wavelength")
+        ConvertToPointData(InputWorkspace=self._input_wksp, OutputWorkspace=self._input_wksp)
+
+    def checkResults(self, eventCheck=False, xsection="AttenuationXSection"):
+        # Check results
+
+        Multiply(LHSWorkspace=self._input_wksp,
+                 RHSWorkspace=self._correction_wksp,
+                 OutputWorkspace=self._output_wksp)
+        output_wksp = AnalysisDataService.retrieve(self._output_wksp)
+
+        self.assertEqual(output_wksp.getAxis(0).getUnit().unitID(), 'Wavelength')
+        self.assertAlmostEqual(output_wksp.readX(0)[79], 0.995)
+        if eventCheck:
+            self.assertAlmostEqual(output_wksp.readY(0)[79], 62.22517501)
+        else:
+            if xsection == "AttenuationXSection":
+                self.assertAlmostEqual(output_wksp.readY(0)[79], 3250.28183501)
+            if xsection == "TotalXSection":
+                self.assertAlmostEqual(output_wksp.readY(0)[79], 3245.70148939)
+
+    # Result tests
+    def testCalculateEfficiencyCorrectionAlphaForEventWksp(self):
+        self._input_wksp = "input_wksp"
+        self._correction_wksp = "correction_wksp"
+        self._output_wksp = "output_wksp"
+
+        # Create an exponentially decaying function in wavelength to simulate
+        # measured sample
+        CreateSampleWorkspace(WorkspaceType="Event", Function="User Defined",
+                              UserDefinedFunction="name=ExpDecay, Height=100, Lifetime=4",
+                              Xmin=0.2, Xmax=4.0, BinWidth=0.01, XUnit="Wavelength",
+                              NumEvents=10000, NumBanks=1, BankPixelWidth=1,
+                              OutputWorkspace=self._input_wksp)
+
+        # Calculate the efficiency correction
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 Alpha=self._alpha,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+        ConvertToPointData(InputWorkspace=self._input_wksp, OutputWorkspace=self._input_wksp)
+        self.checkResults(eventCheck=True)
+
+    def testCalculateEfficiencyCorrectionAlphaForEventWkspInputWkspNotInADS(self):
+        self.cleanup()
+
+        # Create an exponentially decaying function in wavelength to simulate
+        # measured sample
+        tmp_wksp = CreateSampleWorkspace(WorkspaceType="Event", Function="User Defined",
+                                         UserDefinedFunction="name=ExpDecay, Height=100, Lifetime=4",
+                                         Xmin=0.2, Xmax=4.0, BinWidth=0.01, XUnit="Wavelength",
+                                         NumEvents=10000, NumBanks=1, BankPixelWidth=1,
+                                         OutputWorkspace=self._input_wksp, StoreInADS=False)
+
+        # Calculate the efficiency correction
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=tmp_wksp,
+                                 Alpha=self._alpha,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+        CloneWorkspace(InputWorkspace=tmp_wksp, OutputWorkspace=self._input_wksp)
+        ConvertToPointData(InputWorkspace=self._input_wksp, OutputWorkspace=self._input_wksp)
+        self.checkResults(eventCheck=True)
+
+    def testCalculateEfficiencyCorrectionAlpha(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 Alpha=self._alpha,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionAlphaWithWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 Alpha=self._alpha,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionMassDensityAndThickness(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 Density=self._mass_density,
+                                 Thickness=self._thickness,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionMassDensityAndThicknessWithWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 Density=self._mass_density,
+                                 Thickness=self._thickness,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionNumberDensityAndThickness(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 DensityType="Number Density",
+                                 Density=self._number_density,
+                                 Thickness=self._thickness,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionNumberDensityAndThicknessWithWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 DensityType="Number Density",
+                                 Density=self._number_density,
+                                 Thickness=self._thickness,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionEfficiency(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=self._efficiency1_forAbsXS,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionEfficiencyWithWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=self._efficiency1_forAbsXS,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionEfficiencyWithWavelength(self):
+        efficiency=self._efficiency2_forAbsXS['Efficiency']
+        wavelength=self._efficiency2_forAbsXS['Wavelength']
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=efficiency,
+                                 MeasuredEfficiencyWavelength=wavelength,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionEfficiencyWithWavelengthWithWaveRange(self):
+        efficiency=self._efficiency2_forAbsXS['Efficiency']
+        wavelength=self._efficiency2_forAbsXS['Wavelength']
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=efficiency,
+                                 MeasuredEfficiencyWavelength=wavelength,
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionAlphaWithTotalXS(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 Alpha=self._alpha,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionAlphaWithTotalXSWithWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 Alpha=self._alpha,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults()
+
+    def testCalculateEfficiencyCorrectionMassDensityAndThicknessWithTotalXS(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 Density=self._mass_density,
+                                 Thickness=self._thickness,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorrectionMassDensityAndThicknessWithTotalXSWithWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 Density=self._mass_density,
+                                 Thickness=self._thickness,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorrectionNumberDensityAndThicknessWithTotalXS(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 DensityType="Number Density",
+                                 Density=self._number_density,
+                                 Thickness=self._thickness,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorrectionNumberDensityAndThicknessWithTotalXSWithWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 DensityType="Number Density",
+                                 Density=self._number_density,
+                                 Thickness=self._thickness,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorrectionEfficiencyWithTotalXS(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=self._efficiency1_forTotalXS,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorrectionEfficiencyWithTotalXSWitWaveRange(self):
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=self._efficiency1_forTotalXS,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorrectionEfficiencyWithWavelengthWithTotalXS(self):
+        efficiency=self._efficiency2_forTotalXS['Efficiency']
+        wavelength=self._efficiency2_forTotalXS['Wavelength']
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 InputWorkspace=self._input_wksp,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=efficiency,
+                                 MeasuredEfficiencyWavelength=wavelength,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorrectionEfficiencyWithWavelengthWithTotalXSWithWaveRange(self):
+        efficiency=self._efficiency2_forTotalXS['Efficiency']
+        wavelength=self._efficiency2_forTotalXS['Wavelength']
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 ChemicalFormula=self._chemical_formula,
+                                 MeasuredEfficiency=efficiency,
+                                 MeasuredEfficiencyWavelength=wavelength,
+                                 XSectionType="TotalXSection",
+                                 OutputWorkspace=self._correction_wksp)
+        self.assertTrue(alg_test.isExecuted())
+
+        self.checkResults(xsection="TotalXSection")
+
+    def testCalculateEfficiencyCorretionStoreADSCheck(self):
+        self.cleanup()
+        alg_test = run_algorithm("CalculateEfficiencyCorrection",
+                                 WavelengthRange=self._wavelengths,
+                                 Alpha=self._alpha,
+                                 OutputWorkspace=self._output_wksp)
+        self.assertTrue(alg_test.isExecuted())
+        self.assertTrue(AnalysisDataService.doesExist(self._output_wksp))
+
+    # Invalid checks
+    def testCalculateEfficiencyCorretionInvalidStoreADSCheck(self):
+        self.cleanup()
+        corr_wksp = CalculateEfficiencyCorrection(
+                                 WavelengthRange=self._wavelengths,
+                                 Alpha=self._alpha,
+                                 StoreInADS=False)
+        self.assertFalse(AnalysisDataService.doesExist(corr_wksp.name()))
+
+    def testCalculateEfficiencyCorretionInvalidInput(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionInvalidTooManyInputs(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          WavelengthRange=self._wavelengths,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionInvalidDensity(self):
+        self.assertRaises(ValueError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          ChemicalFormula=self._chemical_formula,
+                          Density=-1.0,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionMassDensityWithoutChemicalFormula(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          Density=1.0,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionNumberDensityWithoutChemicalFormula(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          DensityType="Number Density",
+                          Density=1.0,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionEfficiencyWithoutChemicalFormula(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          MeasuredEfficiency=1.0,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionEfficiencyAndDensity(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          Density=1.0,
+                          MeasuredEfficiency=1.0,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionAlphaAndDensity(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          Density=1.0,
+                          Alpha=1.0,
+                          OutputWorkspace=self._output_wksp)
+
+    def testCalculateEfficiencyCorretionEfficiencyAndAlpha(self):
+        self.assertRaises(RuntimeError,
+                          CalculateEfficiencyCorrection,
+                          InputWorkspace=self._input_wksp,
+                          Alpha=1.0,
+                          MeasuredEfficiency=1.0,
+                          OutputWorkspace=self._output_wksp)
+
+    def cleanup(self):
+        if AnalysisDataService.doesExist(self._input_wksp):
+            DeleteWorkspace(self._input_wksp)
+        if AnalysisDataService.doesExist(self._output_wksp):
+            DeleteWorkspace(self._output_wksp)
+        if AnalysisDataService.doesExist(self._correction_wksp):
+            DeleteWorkspace(self._correction_wksp)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..9429be6ff88a77351cf3ce73fff45f42079f8e5a
--- /dev/null
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py
@@ -0,0 +1,32 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+from mantid.simpleapi import LoadIsawPeaks, FindUBUsingFFT, IndexPeaks, OptimizeCrystalPlacementByRun
+from mantid.api import mtd
+
+
+class OptimizeCrystalPlacementByRunTest(unittest.TestCase):
+    def test_simple(self):
+        ws=LoadIsawPeaks("calibrated.peaks")
+        FindUBUsingFFT(PeaksWorkspace=ws,MinD=2,MaxD=20,Tolerance=0.12)
+        IndexPeaks(PeaksWorkspace='ws',Tolerance=0.12)
+        wsd = OptimizeCrystalPlacementByRun(InputWorkspace=ws,OutputWorkspace='wsd',Tolerance=0.12)
+        result = mtd['wsd'].getPeak(0).getSamplePos()
+        self.assertAlmostEqual(result.getX(), -0.000678629)
+        self.assertAlmostEqual(result.getY(), -2.16033e-05)
+        self.assertAlmostEqual(result.getZ(), 0.00493278)
+        result = mtd['wsd'].getPeak(8).getSamplePos()
+        self.assertAlmostEqual(result.getX(), -0.0027929)
+        self.assertAlmostEqual(result.getY(), -0.00105681)
+        self.assertAlmostEqual(result.getZ(), 0.00497094)
+
+
+
+if __name__=="__main__":
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/StatisticsOfTableWorkspaceTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/StatisticsOfTableWorkspaceTest.py
index 4c7aca53c6fa3ac7dbdf420dd880076d8a175668..417f35cb590dc7c7f168c8d7800a00094f164fbd 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/StatisticsOfTableWorkspaceTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/StatisticsOfTableWorkspaceTest.py
@@ -34,14 +34,13 @@ class StatisticsOfTableWorkspaceTest(unittest.TestCase):
         self.assertEqual(stats.rowCount(), 5)
         self.assertEqual(stats.columnCount(), 3)
 
-        stat_col = stats.column('statistic')
-
-        self.assertAlmostEqual(stats.column('a')[stat_col.index('standard_deviation')], 1.11803400517)
-        self.assertAlmostEqual(stats.column('a')[stat_col.index('minimum')], 1.0)
-        self.assertAlmostEqual(stats.column('a')[stat_col.index('median')], 2.5)
-        self.assertAlmostEqual(stats.column('a')[stat_col.index('maximum')], 4.0)
-        self.assertAlmostEqual(stats.column('a')[stat_col.index('mean')], 2.5)
+        stat_col = stats.column('Statistic')
 
+        self.assertAlmostEqual(stats.column('a')[stat_col.index('StandardDev')], 1.11803400517)
+        self.assertAlmostEqual(stats.column('a')[stat_col.index('Minimum')], 1.0)
+        self.assertAlmostEqual(stats.column('a')[stat_col.index('Median')], 2.5)
+        self.assertAlmostEqual(stats.column('a')[stat_col.index('Maximum')], 4.0)
+        self.assertAlmostEqual(stats.column('a')[stat_col.index('Mean')], 2.5)
 
     def test_invalid_types(self):
         """
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/SavePlot1DTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/SavePlot1DTest.py
index a0f0393885efe24395411e4a61941f4eac56fffe..96b8cb6ea0e81aed42337933e707e49765ec290f 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/SavePlot1DTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/SavePlot1DTest.py
@@ -8,12 +8,29 @@ from __future__ import (absolute_import, division, print_function)
 
 import unittest, os
 from mantid import AnalysisDataServiceImpl, config, simpleapi
+try:
+    import plotly
+    havePlotly = True
+except ImportError:
+    havePlotly = False
+# check if matplotlib is available and a new enough version
+matplotlibissue = None  # indicates there are no issues
+try:
+    import matplotlib
+    from distutils.version import LooseVersion
+
+    if LooseVersion(matplotlib.__version__) < LooseVersion("1.2.0"):
+        matplotlibissue = 'Wrong version of matplotlib. Required >= 1.2.0'
+    matplotlib.use("agg")
+    import matplotlib.pyplot as plt
+except:
+    matplotlibissue = 'Problem importing matplotlib'
 
 
 class SavePlot1DTest(unittest.TestCase):
     def makeWs(self):
         simpleapi.CreateWorkspace(OutputWorkspace='test1', DataX='1,2,3,4,5,1,2,3,4,5', DataY='1,2,3,4,2,3,4,5',
-                                  DataE='1,2,3,4,2,3,4,5', NSpec='2', UnitX='TOF', Distribution='1', YUnitlabel="S(q)")
+                                  DataE='1,2,3,4,2,3,4,5', NSpec='2', UnitX='dSpacing', Distribution='1', YUnitlabel="S(q)")
         simpleapi.CreateWorkspace(OutputWorkspace='test2', DataX='1,2,3,4,5,1,2,3,4,5', DataY='1,2,3,4,2,3,4,5',
                                   DataE='1,2,3,4,2,3,4,5', NSpec='2',
                                   UnitX='Momentum', VerticalAxisUnit='TOF', VerticalAxisValues='1,2', Distribution='1',
@@ -29,22 +46,34 @@ class SavePlot1DTest(unittest.TestCase):
         if os.path.exists(self.plotfile):
             os.remove(self.plotfile)
 
-    def testPlot(self):
+    @unittest.skipIf(matplotlibissue is not None, matplotlibissue)
+    def testPlotSingle(self):
+        self.makeWs()
+        simpleapi.SavePlot1D('test1', self.plotfile)
+        self.assertGreater(os.path.getsize(self.plotfile), 1e4)
+        self.cleanup()
+
+    @unittest.skipIf(matplotlibissue is not None, matplotlibissue)
+    def testPlotGroup(self):
+        self.makeWs()
+        simpleapi.SavePlot1D('group', self.plotfile)
+        self.assertGreater(os.path.getsize(self.plotfile), 1e4)
+        self.cleanup()
+
+    @unittest.skipIf(not havePlotly, 'Do not have plotly installed')
+    def testPlotlySingle(self):
+        self.makeWs()
+        div = simpleapi.SavePlot1D(InputWorkspace='test1', OutputType='plotly')
+        self.cleanup()
+        self.assertTrue(len(div) > 0)  # confirm result is non-empty
+
+
+    @unittest.skipIf(not havePlotly, 'Do not have plotly installed')
+    def testPlotlyGroup(self):
         self.makeWs()
-        ok2run = ''
-        try:
-            import matplotlib
-            from distutils.version import LooseVersion
-            if LooseVersion(matplotlib.__version__) < LooseVersion("1.2.0"):
-                ok2run = 'Wrong version of matplotlib. Required >= 1.2.0'
-            matplotlib.use("agg")
-            import matplotlib.pyplot as plt
-        except:
-            ok2run = 'Problem importing matplotlib'
-        if ok2run == '':
-            simpleapi.SavePlot1D("group", self.plotfile)
-            self.assertGreater(os.path.getsize(self.plotfile), 1e4)
+        div = simpleapi.SavePlot1D(InputWorkspace='group', OutputType='plotly')
         self.cleanup()
+        self.assertTrue(len(div) > 0)  # confirm result is non-empty
 
 
 if __name__ == "__main__":
diff --git a/Framework/RemoteAlgorithms/CMakeLists.txt b/Framework/RemoteAlgorithms/CMakeLists.txt
index 7b2ad0fb0d4b281ffa26f5f0e137e1470de1efc2..c08fa5aaf902f31f890ffe80ea9a4a23fb63cde9 100644
--- a/Framework/RemoteAlgorithms/CMakeLists.txt
+++ b/Framework/RemoteAlgorithms/CMakeLists.txt
@@ -114,4 +114,4 @@ add_subdirectory ( test )  # Note: No tests yet for many remote algorithms...
 # Installation settings
 ###########################################################################
 
-install ( TARGETS RemoteAlgorithms ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS RemoteAlgorithms INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/RemoteJobManagers/CMakeLists.txt b/Framework/RemoteJobManagers/CMakeLists.txt
index 4bd444b58becd53ca3835a486b1f309afb2a763f..2b3f919059d502f6cc7dd5828982f2b234b28f7f 100644
--- a/Framework/RemoteJobManagers/CMakeLists.txt
+++ b/Framework/RemoteJobManagers/CMakeLists.txt
@@ -63,4 +63,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS RemoteJobManagers ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS RemoteJobManagers INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/SINQ/CMakeLists.txt b/Framework/SINQ/CMakeLists.txt
index 23141ba462e7d4b35098e20ec0ba7033aa94be7d..422514bd830c1b84a865143d794f3e6e24f5d133 100644
--- a/Framework/SINQ/CMakeLists.txt
+++ b/Framework/SINQ/CMakeLists.txt
@@ -170,4 +170,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS SINQ ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS SINQ INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/ScriptRepository/CMakeLists.txt b/Framework/ScriptRepository/CMakeLists.txt
index 30daa6f690d014962bd7963957004e99fec6c3f7..4497751414f4e8a8c9c10d1e74aa2097d88ec5a4 100644
--- a/Framework/ScriptRepository/CMakeLists.txt
+++ b/Framework/ScriptRepository/CMakeLists.txt
@@ -40,4 +40,4 @@ include_directories(inc)
 
 target_link_libraries(ScriptRepository LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${LIBS} ${JSONCPP_LIBRARIES})
 
-install (TARGETS ScriptRepository ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS ScriptRepository INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/TestHelpers/inc/MantidTestHelpers/IndirectFitDataCreationHelper.h b/Framework/TestHelpers/inc/MantidTestHelpers/IndirectFitDataCreationHelper.h
index ddce2d0ed52e0a33a216d7d83559289e143d6262..581c1f7f3d131c201f7916b552509ff0116db932 100644
--- a/Framework/TestHelpers/inc/MantidTestHelpers/IndirectFitDataCreationHelper.h
+++ b/Framework/TestHelpers/inc/MantidTestHelpers/IndirectFitDataCreationHelper.h
@@ -1,8 +1,15 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
 #ifndef MANTID_INDIRECTFITDATACREATIONHELPER_H_
 #define MANTID_INDIRECTFITDATACREATIONHELPER_H_
 
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/MatrixWorkspace_fwd.h"
+#include "MantidAPI/TextAxis.h"
 #include "MantidHistogramData/BinEdges.h"
 
 #include <string>
@@ -13,12 +20,23 @@
 
 namespace Mantid {
 namespace IndirectFitDataCreationHelper {
+/// Commonly used constant variables
+int const START_X_COLUMN(2);
+int const END_X_COLUMN(3);
+int const EXCLUDE_REGION_COLUMN(4);
 
 /// Functions used in the creation of workspaces
 Mantid::API::MatrixWorkspace_sptr createWorkspace(int const &numberOfSpectra);
 Mantid::API::MatrixWorkspace_sptr createInstrumentWorkspace(int const &xLength,
                                                             int const &yLength);
 Mantid::API::MatrixWorkspace_sptr
+createWorkspaceWithTextAxis(int const &numberOfSpectra,
+                            std::vector<std::string> const &labels);
+
+Mantid::API::TextAxis *getTextAxis(int const &numberOfSpectra,
+                                   std::vector<std::string> const &labels);
+
+Mantid::API::MatrixWorkspace_sptr
 setWorkspaceEFixed(Mantid::API::MatrixWorkspace_sptr workspace,
                    int const &xLength);
 Mantid::API::MatrixWorkspace_sptr
diff --git a/Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp b/Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp
index 83082b0c41ef340802cfdb8591cfd883832370ba..3aab288df166664217dfde39b323d81169fe7c2d 100644
--- a/Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp
+++ b/Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp
@@ -15,6 +15,26 @@ MatrixWorkspace_sptr createInstrumentWorkspace(int const &xLength,
       xLength, yLength - 1, false, false, true, "testInst");
 }
 
+MatrixWorkspace_sptr
+createWorkspaceWithTextAxis(int const &numberOfSpectra,
+                            std::vector<std::string> const &labels) {
+  if (static_cast<std::size_t>(numberOfSpectra) == labels.size()) {
+    auto workspace = createWorkspace(numberOfSpectra);
+    workspace->replaceAxis(1, getTextAxis(numberOfSpectra, labels));
+    return workspace;
+  } else
+    throw std::runtime_error(
+        "The number of spectra is not equal to the number of labels");
+}
+
+TextAxis *getTextAxis(int const &numberOfSpectra,
+                      std::vector<std::string> const &labels) {
+  auto axis = new TextAxis(numberOfSpectra);
+  for (auto index = 0; index < numberOfSpectra; ++index)
+    axis->setLabel(index, labels[index]);
+  return axis;
+}
+
 MatrixWorkspace_sptr setWorkspaceEFixed(MatrixWorkspace_sptr workspace,
                                         int const &xLength) {
   for (int i = 0; i < xLength; ++i)
diff --git a/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp b/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
index 9f2c70a1ae6b89a005a29de92197937849b8dd42..fb1490fe359c72de05d6736d7091c06573f1d1a6 100644
--- a/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
+++ b/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
@@ -1133,7 +1133,7 @@ createProcessedInelasticWS(const std::vector<double> &L2,
     for (size_t i = 0; i <= numBins; i++) {
       E_transfer.push_back(Emin + static_cast<double>(i) * dE);
     }
-    ws->mutableX(j) = E_transfer;
+    ws->mutableX(j) = std::move(E_transfer);
   }
 
   // set axis, correspondent to the X-values
diff --git a/Framework/Types/CMakeLists.txt b/Framework/Types/CMakeLists.txt
index c51faf4b997e774bd56a5d5611080cadb3883ad1..2cd3bcce5cfb1a9489e2398c4868ef3d1c50343d 100644
--- a/Framework/Types/CMakeLists.txt
+++ b/Framework/Types/CMakeLists.txt
@@ -41,7 +41,7 @@ generate_mantid_export_header ( Types )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS Types ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
+mtd_install_targets( TARGETS Types INSTALL_DIRS ${LIB_DIR} ${WORKBENCH_LIB_DIR})
 
 # Add the unit tests directory
 add_subdirectory ( test )
diff --git a/Framework/WorkflowAlgorithms/CMakeLists.txt b/Framework/WorkflowAlgorithms/CMakeLists.txt
index 49c85f2b98968c7dc872893dcf93ee590ee5714c..f5f612c8418c5c84980de92d54e50d8b533b916d 100644
--- a/Framework/WorkflowAlgorithms/CMakeLists.txt
+++ b/Framework/WorkflowAlgorithms/CMakeLists.txt
@@ -86,6 +86,7 @@ set ( INC_FILES
 )
 
 set ( TEST_FILES
+	AlignAndFocusPowderTest.h
 	ExtractQENSMembersTest.h
 	IMuonAsymmetryCalculatorTest.h
 	LoadEventAndCompressTest.h
@@ -141,4 +142,4 @@ add_subdirectory ( test )
 # Installation settings
 ###########################################################################
 
-install ( TARGETS WorkflowAlgorithms ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} )
+mtd_install_targets( TARGETS WorkflowAlgorithms INSTALL_DIRS ${PLUGINS_DIR} ${WORKBENCH_PLUGINS_DIR})
diff --git a/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp b/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp
index 412a6ec4cff4b07a311af7e4e55dd5e01c807cc2..bf042d3b297507fb62334dcf9b9fbe2be7b3f7e3 100644
--- a/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp
+++ b/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp
@@ -271,8 +271,12 @@ void AlignAndFocusPowder::exec() {
   m_inputW = getProperty("InputWorkspace");
   m_inputEW = boost::dynamic_pointer_cast<EventWorkspace>(m_inputW);
   m_instName = m_inputW->getInstrument()->getName();
-  m_instName =
-      Kernel::ConfigService::Instance().getInstrument(m_instName).shortName();
+  try {
+    m_instName =
+        Kernel::ConfigService::Instance().getInstrument(m_instName).shortName();
+  } catch (Exception::NotFoundError &) {
+    ; // not noteworthy
+  }
   std::string calFilename = getPropertyValue("CalFileName");
   std::string groupFilename = getPropertyValue("GroupFilename");
   m_calibrationWS = getProperty("CalibrationWorkspace");
@@ -379,7 +383,7 @@ void AlignAndFocusPowder::exec() {
   } else {
     // workspace2D
     if (m_outputW != m_inputW) {
-      m_outputW = WorkspaceFactory::Instance().create(m_inputW);
+      m_outputW = m_inputW->clone();
     }
   }
 
@@ -720,7 +724,6 @@ void AlignAndFocusPowder::exec() {
     API::IAlgorithm_sptr compressAlg = createChildAlgorithm("CompressEvents");
     compressAlg->setProperty("InputWorkspace", m_outputEW);
     compressAlg->setProperty("OutputWorkspace", m_outputEW);
-    compressAlg->setProperty("OutputWorkspace", m_outputEW);
     compressAlg->setProperty("Tolerance", tolerance);
     if (!isEmpty(wallClockTolerance)) {
       compressAlg->setProperty("WallClockTolerance", wallClockTolerance);
@@ -776,6 +779,13 @@ AlignAndFocusPowder::diffractionFocus(API::MatrixWorkspace_sptr ws) {
     return ws;
   }
 
+  if (m_maskWS) {
+    API::IAlgorithm_sptr maskAlg = createChildAlgorithm("MaskDetectors");
+    maskAlg->setProperty("Workspace", m_groupWS);
+    maskAlg->setProperty("MaskedWorkspace", m_maskWS);
+    maskAlg->executeAsChildAlg();
+  }
+
   g_log.information() << "running DiffractionFocussing started at "
                       << Types::Core::DateAndTime::getCurrentTime() << "\n";
 
diff --git a/Framework/WorkflowAlgorithms/test/AlignAndFocusPowderTest.h b/Framework/WorkflowAlgorithms/test/AlignAndFocusPowderTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..15c4eb136223823e8008ef5df90b33594c7548e7
--- /dev/null
+++ b/Framework/WorkflowAlgorithms/test/AlignAndFocusPowderTest.h
@@ -0,0 +1,820 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef ALIGNANDFOCUSPOWDERTEST_H_
+#define ALIGNANDFOCUSPOWDERTEST_H_
+
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/Axis.h"
+#include "MantidAlgorithms/AddSampleLog.h"
+#include "MantidAlgorithms/AddTimeSeriesLog.h"
+#include "MantidAlgorithms/ConvertUnits.h"
+#include "MantidAlgorithms/CreateGroupingWorkspace.h"
+#include "MantidAlgorithms/CreateSampleWorkspace.h"
+#include "MantidAlgorithms/ResampleX.h"
+#include "MantidDataHandling/LoadDiffCal.h"
+#include "MantidDataHandling/LoadNexus.h"
+#include "MantidDataHandling/MaskDetectors.h"
+#include "MantidDataHandling/MoveInstrumentComponent.h"
+#include "MantidDataHandling/RotateInstrumentComponent.h"
+#include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/GroupingWorkspace.h"
+#include "MantidWorkflowAlgorithms/AlignAndFocusPowder.h"
+
+using namespace Mantid::API;
+using namespace Mantid::Algorithms;
+using namespace Mantid::DataHandling;
+using namespace Mantid::DataObjects;
+using namespace Mantid::Kernel;
+using namespace Mantid::WorkflowAlgorithms;
+
+class AlignAndFocusPowderTest : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static AlignAndFocusPowderTest *createSuite() {
+    return new AlignAndFocusPowderTest();
+  }
+  static void destroySuite(AlignAndFocusPowderTest *suite) { delete suite; }
+
+  /* Test AlignAndFocusPowder basics */
+  void testTheBasics() {
+    AlignAndFocusPowder align_and_focus;
+    TS_ASSERT_EQUALS(align_and_focus.name(), "AlignAndFocusPowder");
+    TS_ASSERT_EQUALS(align_and_focus.version(), 1);
+  }
+
+  void testInit() {
+    AlignAndFocusPowder align_and_focus;
+    TS_ASSERT_THROWS_NOTHING(align_and_focus.initialize());
+    TS_ASSERT(align_and_focus.isInitialized());
+  }
+
+  void testException() {
+    AlignAndFocusPowder align_and_focus;
+    align_and_focus.initialize();
+    TS_ASSERT_THROWS(align_and_focus.execute(), std::runtime_error);
+  }
+
+  /* Test AlignAndFocusPowder for HRP38692 raw data */
+  void testHRP38692_useCalfile() { doTestHRP38692(true, false, false, false); }
+
+  void testHRP38692_useCalfile_useGroupfile() {
+    doTestHRP38692(true, false, true, false);
+  }
+
+  void testHRP38692_useCalfile_useGroupWorkspace() {
+    doTestHRP38692(true, false, false, true);
+  }
+
+  void testHRP38692_useCalWorkspace_useGroupfile() {
+    doTestHRP38692(false, true, true, false);
+  }
+
+  void testHRP38692_useCalWorkspace_useGroupWorkspace() {
+    doTestHRP38692(false, true, false, true);
+  }
+
+  /* Test AlignAndFocusPowder for Event Workspace*/
+  void testEventWksp_preserveEvents() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[80], 1609.2800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[80], 20);
+    TS_ASSERT_DELTA(m_outWS->x(0)[880], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[880], 587);
+  }
+
+  void testEventWksp_preserveEvents_useGroupAll() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = true;
+    m_useResamplex = true;
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[423], 1634.3791, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[423], 2702);
+    TS_ASSERT_DELTA(m_outWS->x(0)[970], 14719.8272, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[970], 149165);
+  }
+
+  void testEventWksp_doNotPreserveEvents() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = false;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[80], 1609.2800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[80], 20);
+    TS_ASSERT_DELTA(m_outWS->x(0)[880], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[880], 587);
+  }
+
+  void testEventWksp_doNotPreserveEvents_useGroupAll() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = false;
+    m_useGroupAll = true;
+    m_useResamplex = true;
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[423], 1634.3791, 0.0001);
+    TS_ASSERT_DELTA(m_outWS->y(0)[423], 2419.5680, 0.0001);
+    TS_ASSERT_DELTA(m_outWS->x(0)[970], 14719.8272, 0.0001);
+    TS_ASSERT_DELTA(m_outWS->y(0)[970], 148503.3853, 0.0001);
+  }
+
+  void testEventWksp_rebin_preserveEvents() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = false;
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Test the input
+    TS_ASSERT_DELTA(m_inWS->x(0)[170], 1628.3764, 0.0001);
+    TS_ASSERT_EQUALS(m_inWS->y(0)[170], 48);
+    TS_ASSERT_DELTA(m_inWS->x(0)[391], 14681.7696, 0.0001);
+    TS_ASSERT_EQUALS(m_inWS->y(0)[391], 2540);
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[1693], 1629.3502, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[1693], 6);
+    TS_ASSERT_DELTA(m_outWS->x(0)[3895], 14718.1436, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[3895], 612);
+  }
+
+  void testEventWksp_preserveEvents_dmin_dmax() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_dmin = createArgForNumberHistograms(0.5, m_inWS);
+    m_dmax = createArgForNumberHistograms(1.5, m_inWS);
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_dmin = "0";
+    m_dmax = "0";
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[116], 3270.3908, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[116], 37);
+    TS_ASSERT_DELTA(m_outWS->x(0)[732], 6540.7817, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[732], 25);
+  }
+
+  void testEventWksp_preserveEvents_tmin_tmax() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_tmin = "2000.0";
+    m_tmax = "10000.0";
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_tmin = "0";
+    m_tmax = "0";
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[149], 3270.7563, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[149], 51);
+    TS_ASSERT_DELTA(m_outWS->x(0)[982], 9814.5378, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[982], 138);
+  }
+
+  void testEventWksp_preserveEvents_lambdamin_lambdamax() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_lambdamin = "0.5";
+    m_lambdamax = "3.0";
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_lambdamin = "0";
+    m_lambdamax = "0";
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[181], 3262.2460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[181], 105);
+    TS_ASSERT_DELTA(m_outWS->x(0)[581], 9808.6460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[581], 290);
+    TS_ASSERT_DELTA(m_outWS->x(0)[880], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[880], 0);
+  }
+
+  void testEventWksp_preserveEvents_maskbins() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_maskBinTableWS = createMaskBinTable();
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_maskBinTableWS = nullptr;
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[181], 3262.2460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[181], 105);
+    TS_ASSERT_DELTA(m_outWS->x(0)[581], 9808.6460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[581], 290);
+    TS_ASSERT_DELTA(m_outWS->x(0)[880], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[880], 0);
+  }
+
+  void testEventWksp_preserveEvents_noCompressTolerance() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_compressTolerance = "0.0";
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_compressTolerance = "0";
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[181], 3262.2460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[181], 105);
+    TS_ASSERT_DELTA(m_outWS->x(0)[581], 9808.6460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[581], 290);
+    TS_ASSERT_DELTA(m_outWS->x(0)[880], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[880], 587);
+  }
+
+  void testEventWksp_preserveEvents_highCompressTolerance() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_compressTolerance = "5.0";
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_compressTolerance = "0";
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[181], 3262.2460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[181], 96);
+    TS_ASSERT_DELTA(m_outWS->x(0)[581], 9808.6460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[581], 427);
+    TS_ASSERT_DELTA(m_outWS->x(0)[880], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[880], 672);
+  }
+
+  void testEventWksp_preserveEvents_compressWallClockTolerance() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_compressWallClockTolerance = "50.0";
+    addPulseTimesForLogs();
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_compressWallClockTolerance = "0";
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[181], 3262.2460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[181], 105);
+    TS_ASSERT_DELTA(m_outWS->x(0)[581], 9808.6460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[581], 290);
+    TS_ASSERT_DELTA(m_outWS->x(0)[880], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[880], 587);
+  }
+
+  void testEventWksp_preserveEvents_removePromptPulse() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    m_removePromptPulse = true;
+    addFrequencyForLogs();
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_removePromptPulse = false;
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[181], 3262.2460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[181], 105);
+    TS_ASSERT_DELTA(m_outWS->x(0)[581], 9808.6460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[581], 0);
+  }
+
+  void testEventWksp_preserveEvents_compressStartTime() {
+    // Setup the event workspace
+    setUp_EventWorkspace();
+
+    // Set the inputs for doTestEventWksp
+    m_preserveEvents = true;
+    m_useGroupAll = false;
+    m_useResamplex = true;
+    // require both inside AlignAndFocusPowder
+    m_compressStartTime =
+        "2010-01-01T00:20:00"; // start time is "2010-01-01T00:00:00"
+    m_compressWallClockTolerance =
+        "50.0"; // require both inside AlignAndFocusPowder
+
+    // Run the main test function
+    doTestEventWksp();
+
+    // Reset inputs to default values
+    m_compressStartTime = "0";
+    m_compressWallClockTolerance = "0";
+
+    // Test the input
+    docheckEventInputWksp();
+
+    // Test the output
+    TS_ASSERT_DELTA(m_outWS->x(0)[181], 3262.2460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[181], 72);
+    TS_ASSERT_DELTA(m_outWS->x(0)[581], 9808.6460, 0.0001);
+    TS_ASSERT_EQUALS(m_outWS->y(0)[581], 197);
+  }
+
+  /** Setup for testing HRPD NeXus data */
+  void setUp_HRP38692() {
+
+    LoadNexus loader;
+    loader.initialize();
+    loader.setPropertyValue("Filename", "HRP38692a.nxs");
+    loader.setPropertyValue("OutputWorkspace", m_inputWS);
+    loader.execute();
+    TS_ASSERT_THROWS_NOTHING(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+  }
+
+  void doTestHRP38692(bool useCalfile, bool useCalWksp, bool useGroupfile,
+                      bool useGroupWksp) {
+
+    setUp_HRP38692();
+
+    AlignAndFocusPowder align_and_focus;
+    align_and_focus.initialize();
+
+    align_and_focus.setPropertyValue("InputWorkspace", m_inputWS);
+    align_and_focus.setPropertyValue("OutputWorkspace", m_outputWS);
+    align_and_focus.setProperty("ResampleX", 1000);
+    align_and_focus.setProperty("Dspacing", false);
+
+    TS_ASSERT_THROWS_NOTHING(align_and_focus.execute());
+    TS_ASSERT(align_and_focus.isExecuted());
+
+    std::string calfilename("hrpd_new_072_01.cal");
+    if (useCalfile)
+      align_and_focus.setPropertyValue("CalFilename", calfilename);
+    else if (useCalWksp) {
+      loadDiffCal(calfilename, false, true, true);
+      align_and_focus.setPropertyValue("GroupingWorkspace",
+                                       m_loadDiffWSName + "_group");
+      align_and_focus.setPropertyValue("CalibrationWorkspace",
+                                       m_loadDiffWSName + "_cal");
+      align_and_focus.setPropertyValue("MaskWorkspace",
+                                       m_loadDiffWSName + "_mask");
+    }
+
+    if (useGroupfile)
+      align_and_focus.setPropertyValue("GroupFilename", calfilename);
+    else if (useGroupWksp) {
+      loadDiffCal(calfilename, true, false, true);
+      align_and_focus.setPropertyValue("MaskWorkspace",
+                                       m_loadDiffWSName + "_mask");
+      align_and_focus.setPropertyValue("GroupingWorkspace",
+                                       m_loadDiffWSName + "_group");
+    }
+
+    TS_ASSERT_THROWS_NOTHING(align_and_focus.execute());
+    TS_ASSERT(align_and_focus.isExecuted());
+
+    m_inWS =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(m_inputWS);
+    m_outWS =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(m_outputWS);
+
+    TS_ASSERT_EQUALS(m_inWS->size(), 263857);
+    TS_ASSERT_EQUALS(m_inWS->blocksize(), 23987);
+
+    TS_ASSERT_EQUALS(m_outWS->getAxis(0)->unit()->unitID(), "TOF");
+    TS_ASSERT_EQUALS(m_outWS->size(), 1000);
+    TS_ASSERT_EQUALS(m_outWS->blocksize(), m_outWS->size());
+    TS_ASSERT_EQUALS(m_outWS->getNumberHistograms(), 1);
+
+    // Maximum of peak near TOF approx. equal to 22,000 (micro-seconds)
+    TS_ASSERT_DELTA(m_outWS->x(0)[333], 21990.0502, 0.0001);
+    TS_ASSERT_DELTA(m_outWS->y(0)[333], 770.2515, 0.0001);
+
+    // Maximum of peak near TOF approx. equal to 25,800 (micro-seconds)
+    TS_ASSERT_DELTA(m_outWS->x(0)[398], 25750.3113, 0.0001);
+    TS_ASSERT_DELTA(m_outWS->y(0)[398], 1522.3778, 0.0001);
+
+    // Maximum of peak near TOF approx. equal to 42,000 (micro-seconds)
+    TS_ASSERT_DELTA(m_outWS->x(0)[600], 42056.6091, 0.0001);
+    TS_ASSERT_DELTA(m_outWS->y(0)[600], 7283.29652, 0.0001);
+  }
+
+  /* Setup for event data */
+
+  void setUp_EventWorkspace() {
+    m_inputWS = "eventWS";
+
+    CreateSampleWorkspace createSampleAlg;
+    createSampleAlg.initialize();
+    createSampleAlg.setPropertyValue("WorkspaceType", "Event");
+    createSampleAlg.setPropertyValue("Function", "Powder Diffraction");
+    createSampleAlg.setProperty("XMin", m_xmin); // first frame
+    createSampleAlg.setProperty("XMax", m_xmax);
+    createSampleAlg.setProperty("BinWidth", 1.0);
+    createSampleAlg.setProperty("NumBanks", m_numBanks); // detIds = [100,200)
+    createSampleAlg.setProperty("BankPixelWidth", m_numPixels);
+    createSampleAlg.setProperty("NumEvents", m_numEvents);
+    createSampleAlg.setPropertyValue("OutputWorkspace", m_inputWS);
+    createSampleAlg.execute();
+
+    for (int i = 1; i <= m_numBanks; i++) {
+      std::string bank = "bank" + std::to_string(i);
+      RotateInstrumentComponent rotateInstr;
+      rotateInstr.initialize();
+      rotateInstr.setPropertyValue("Workspace", m_inputWS);
+      rotateInstr.setPropertyValue("ComponentName", bank);
+      rotateInstr.setProperty("Y", 1.);
+      rotateInstr.setProperty("Angle", 90.);
+      rotateInstr.execute();
+
+      MoveInstrumentComponent moveInstr;
+      moveInstr.initialize();
+      moveInstr.setPropertyValue("Workspace", m_inputWS);
+      moveInstr.setPropertyValue("ComponentName", bank);
+      moveInstr.setProperty("X", 5.);
+      moveInstr.setProperty("Y", -.1);
+      moveInstr.setProperty("Z", .1);
+      moveInstr.setProperty("RelativePosition", false);
+      moveInstr.execute();
+    }
+  }
+
+  void docheckEventInputWksp() {
+    TS_ASSERT_DELTA(m_inWS->x(0)[8], 1609.2800, 0.0001);
+    TS_ASSERT_EQUALS(m_inWS->y(0)[8], 97);
+    TS_ASSERT_DELTA(m_inWS->x(0)[18], 3245.8800, 0.0001);
+    TS_ASSERT_EQUALS(m_inWS->y(0)[18], 237);
+    TS_ASSERT_DELTA(m_inWS->x(0)[38], 6519.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_inWS->y(0)[38], 199);
+    TS_ASSERT_DELTA(m_inWS->x(0)[58], 9792.2800, 0.0001);
+    TS_ASSERT_EQUALS(m_inWS->y(0)[58], 772);
+    TS_ASSERT_DELTA(m_inWS->x(0)[88], 14702.0800, 0.0001);
+    TS_ASSERT_EQUALS(m_inWS->y(0)[88], 2162);
+  }
+
+  void doTestEventWksp() {
+    // Bin events using either ResampleX or Rebin
+    int inputHistoBins{100};
+    int numHistoBins{1000};
+    std::string input_params{"-0.01"};
+    std::string params{"-0.001"};
+    if (m_useResamplex) {
+      resamplex(inputHistoBins);
+    } else {
+      rebin(params);
+      m_inWS = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
+          m_inputWS);
+      numHistoBins = int(m_inWS->blocksize());
+
+      rebin(input_params);
+      m_inWS = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
+          m_inputWS);
+      inputHistoBins = int(m_inWS->blocksize());
+    }
+
+    // Initialize AlignAndFocusPowder
+    AlignAndFocusPowder align_and_focus;
+    align_and_focus.initialize();
+    align_and_focus.setPropertyValue("InputWorkspace", m_inputWS);
+    align_and_focus.setPropertyValue("OutputWorkspace", m_outputWS);
+    align_and_focus.setProperty("Dspacing", false);
+    align_and_focus.setProperty("PreserveEvents", m_preserveEvents);
+
+    // Use a Mask TableWorkspace created from createMaskBinTable
+    if (m_maskBinTableWS)
+      align_and_focus.setProperty("MaskBinTable", m_maskBinTableWS);
+
+    // Compress tolerance for events
+    if (m_compressTolerance != "0")
+      align_and_focus.setProperty("CompressTolerance", m_compressTolerance);
+
+    // Compression for the wall clock time; controls whether all pulses are
+    // compressed together
+    if (m_compressWallClockTolerance != "0")
+      align_and_focus.setProperty("CompressWallClockTolerance",
+                                  m_compressWallClockTolerance);
+
+    // Filtering for the start wall clock time; cuts off events before start
+    // time
+    if (m_compressStartTime != "0")
+      align_and_focus.setProperty("CompressStartTime", m_compressStartTime);
+
+    // Remove prompt pulse; will cutoff last 6 long-TOF peaks (freq is 200 Hz)
+    if (m_removePromptPulse)
+      align_and_focus.setProperty("RemovePromptPulseWidth", 1e4);
+
+    // Setup the binning type
+    if (m_useResamplex) {
+      align_and_focus.setProperty("ResampleX", numHistoBins);
+    } else {
+      align_and_focus.setProperty("Params", params);
+    }
+
+    // Crop each histogram using dSpacing
+    if (m_dmin != "0") {
+      align_and_focus.setProperty("Dspacing", true);
+      align_and_focus.setPropertyValue("DMin", m_dmin);
+    }
+    if (m_dmax != "0") {
+      align_and_focus.setProperty("Dspacing", true);
+      align_and_focus.setPropertyValue("DMax", m_dmax);
+    }
+
+    // Crop entire workspace by TOF
+    if (m_tmin != "0")
+      align_and_focus.setPropertyValue("TMin", m_tmin);
+    if (m_tmax != "0")
+      align_and_focus.setPropertyValue("TMax", m_tmax);
+
+    // Crop entire workspace by Wavelength
+    if (m_lambdamin != "0")
+      align_and_focus.setPropertyValue("CropWavelengthMin", m_lambdamin);
+    if (m_lambdamax != "0")
+      align_and_focus.setPropertyValue("CropWavelengthMax", m_lambdamax);
+
+    int numGroups{m_numBanks * m_numPixels * m_numPixels};
+    if (m_useGroupAll) {
+      groupAllBanks(m_inputWS);
+      auto group_wksp =
+          AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
+              m_groupWS);
+      align_and_focus.setProperty("GroupingWorkspace", group_wksp->getName());
+      numGroups = (int)group_wksp->blocksize();
+    }
+
+    TS_ASSERT_THROWS_NOTHING(align_and_focus.execute());
+    TS_ASSERT(align_and_focus.isExecuted());
+
+    m_inWS =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(m_inputWS);
+    m_outWS =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(m_outputWS);
+
+    TS_ASSERT_EQUALS(m_inWS->size(),
+                     m_numBanks * m_numPixels * m_numPixels * inputHistoBins);
+    TS_ASSERT_EQUALS(m_inWS->blocksize(), inputHistoBins);
+
+    TS_ASSERT_EQUALS(m_outWS->getAxis(0)->unit()->unitID(), "TOF");
+    TS_ASSERT_EQUALS(m_outWS->size(), numGroups * numHistoBins);
+    TS_ASSERT_EQUALS(m_outWS->blocksize(), numHistoBins);
+    TS_ASSERT_EQUALS(m_outWS->getNumberHistograms(), numGroups);
+  }
+
+  /* Utility functions */
+  void loadDiffCal(std::string calfilename, bool group, bool cal, bool mask) {
+    LoadDiffCal loadDiffAlg;
+    loadDiffAlg.initialize();
+    loadDiffAlg.setPropertyValue("Filename", calfilename);
+    loadDiffAlg.setPropertyValue("InstrumentName", "HRPD");
+    loadDiffAlg.setProperty("MakeGroupingWorkspace", group);
+    loadDiffAlg.setProperty("MakeCalWorkspace", cal);
+    loadDiffAlg.setProperty("MakeMaskWorkspace", mask);
+    loadDiffAlg.setPropertyValue("WorkspaceName", m_loadDiffWSName);
+    loadDiffAlg.execute();
+  }
+
+  void groupAllBanks(std::string m_inputWS) {
+    CreateGroupingWorkspace groupAlg;
+    groupAlg.initialize();
+    groupAlg.setPropertyValue("InputWorkspace", m_inputWS);
+    groupAlg.setPropertyValue("GroupDetectorsBy", "All");
+    groupAlg.setPropertyValue("OutputWorkspace", m_groupWS);
+    groupAlg.execute();
+  }
+
+  void rebin(std::string params, bool preserveEvents = true) {
+    Rebin rebin;
+    rebin.initialize();
+    rebin.setPropertyValue("InputWorkspace", m_inputWS);
+    rebin.setPropertyValue("OutputWorkspace", m_inputWS);
+    rebin.setPropertyValue("Params", params);
+    rebin.setProperty("PreserveEvents", preserveEvents);
+    rebin.execute();
+    rebin.isExecuted();
+  }
+
+  void resamplex(int numHistoBins, bool preserveEvents = true) {
+    ResampleX resamplexAlg;
+    resamplexAlg.initialize();
+    resamplexAlg.setPropertyValue("InputWorkspace", m_inputWS);
+    resamplexAlg.setPropertyValue("OutputWorkspace", m_inputWS);
+    resamplexAlg.setProperty("NumberBins", numHistoBins);
+    resamplexAlg.setProperty("PreserveEvents", preserveEvents);
+    resamplexAlg.execute();
+  }
+
+  std::string createArgForNumberHistograms(double val, MatrixWorkspace_sptr ws,
+                                           std::string delimiter = ",") {
+    std::vector<std::string> vec;
+    for (size_t i = 0; i < ws->getNumberHistograms(); i++)
+      vec.push_back(boost::lexical_cast<std::string>(val));
+    std::string joined = boost::algorithm::join(vec, delimiter + " ");
+    return joined;
+  }
+
+  ITableWorkspace_sptr createMaskBinTable() {
+    m_maskBinTableWS = WorkspaceFactory::Instance().createTable();
+    m_maskBinTableWS->addColumn("str", "SpectraList");
+    m_maskBinTableWS->addColumn("double", "XMin");
+    m_maskBinTableWS->addColumn("double", "XMax");
+    TableRow row1 = m_maskBinTableWS->appendRow();
+    row1 << "" << 0.0 << 2000.0;
+    TableRow row2 = m_maskBinTableWS->appendRow();
+    row2 << "" << 10000.0 << m_xmax + 1000.0;
+    return m_maskBinTableWS;
+  }
+
+  void addPulseTimesForLogs() {
+    AddTimeSeriesLog logAlg;
+    std::string time, minute;
+    std::string prefix{"2010-01-01T00:"};
+    for (int i = 0; i < 60; i++) {
+      minute =
+          std::string(2 - std::to_string(i).length(), '0') + std::to_string(i);
+      time = prefix + minute + "00";
+      logAlg.initialize();
+      logAlg.setPropertyValue("Workspace", m_inputWS);
+      logAlg.setPropertyValue("Name", "proton_charge");
+      logAlg.setPropertyValue("Time", time);
+      logAlg.setPropertyValue("Value", "100");
+    }
+    logAlg.execute();
+  }
+
+  void addFrequencyForLogs() {
+    AddSampleLog freqAlg;
+    freqAlg.initialize();
+    freqAlg.setPropertyValue("LogName", "Frequency");
+    freqAlg.setPropertyValue("LogText", "200.0");
+    freqAlg.setPropertyValue("LogUnit", "Hz");
+    freqAlg.setPropertyValue("LogType", "Number Series");
+    freqAlg.setPropertyValue("NumberType", "Double");
+    freqAlg.setPropertyValue("Workspace", m_inputWS);
+    freqAlg.execute();
+  }
+
+private:
+  std::string m_inputWS{"nexusWS"};
+  std::string m_outputWS{"align_and_focused"};
+  MatrixWorkspace_sptr m_inWS;
+  MatrixWorkspace_sptr m_outWS;
+  ITableWorkspace_sptr m_maskBinTableWS;
+
+  std::string m_loadDiffWSName{"AlignAndFocusPowderTest_diff"};
+  std::string m_groupWS{"AlignAndFocusPowderTest_groupWS"};
+  std::string m_maskBinTableWSName{"AlignAndFocusPowderTest_maskBinTable"};
+
+  int m_numEvents{10000};
+  int m_numBanks{1};
+  int m_numPixels{12};
+  double m_xmin{300.0};
+  double m_xmax{16666.0};
+
+  std::string m_dmin{"0"};
+  std::string m_dmax{"0"};
+  std::string m_tmin{"0"};
+  std::string m_tmax{"0"};
+  std::string m_lambdamin{"0"};
+  std::string m_lambdamax{"0"};
+  std::string m_compressTolerance{"0"};
+  std::string m_compressWallClockTolerance{"0"};
+  std::string m_compressStartTime{"0"};
+  bool m_removePromptPulse{false};
+  bool m_preserveEvents{true};
+  bool m_useGroupAll{true};
+  bool m_useResamplex{true};
+};
+
+#endif /*ALIGNANDFOCUSPOWDERTEST_H_*/
diff --git a/MantidPlot/FixMavericksBundle.cmake.in b/MantidPlot/FixMavericksBundle.cmake.in
index 572443874d5ad33431c424ca15ef99d8e3a2c56a..f7cba9a4574738d230747100e9e0f2f64e6ac309 100644
--- a/MantidPlot/FixMavericksBundle.cmake.in
+++ b/MantidPlot/FixMavericksBundle.cmake.in
@@ -1,5 +1,5 @@
 set ( bundle ${CMAKE_INSTALL_PREFIX}/MantidPlot.app )
-execute_process(COMMAND chmod +x make_package.rb WORKING_DIRECTORY ${bundle})
+execute_process(COMMAND chmod +x ./make_package.rb WORKING_DIRECTORY ${bundle})
 execute_process(COMMAND ./make_package.rb WORKING_DIRECTORY ${bundle} RESULT_VARIABLE install_name_tool_result)
 if(NOT install_name_tool_result EQUAL 0)
   message(FATAL_ERROR "Package script failed!!!\n")
diff --git a/MantidPlot/make_package.rb.in b/MantidPlot/make_package.rb.in
index c59b0561d220a28f391aa48bbd081b648b6db44a..68f20f05dab57055a579703841018c7b78b1aa92 100755
--- a/MantidPlot/make_package.rb.in
+++ b/MantidPlot/make_package.rb.in
@@ -47,6 +47,17 @@ def findQScintilla2(lib_dir)
     end
 end
 
+
+def findBoostPythonMT(lib_dir)
+    [lib_dir, "usr/local/opt"].each do |dir|
+        if File.file?(dir+"libboost_python-mt.dylib")
+            return "libboost_python-mt.dylib"
+        elsif File.file?(dir+"libboost_python27-mt.dylib")
+            return "libboost_python27-mt.dylib"
+        end
+    end
+end
+
 lib_dir = Pathname.new("/usr/local/lib")
 openssl_dir = Pathname.new("/usr/local/opt/openssl/lib")
 ParaView_dir = Pathname.new("@ParaView_DIR@")
@@ -54,7 +65,6 @@ ParaView_dir = Pathname.new("@ParaView_DIR@")
 #filenames with path for all shared libraries used by MantidPlot and its dependencies.
 library_filenames = ["libboost_regex-mt.dylib",
                      "libboost_date_time-mt.dylib",
-                     "libboost_python-mt.dylib",
                      "libboost_serialization-mt.dylib",
                      "libboost_filesystem-mt.dylib",
                      "libboost_system-mt.dylib",
@@ -116,6 +126,7 @@ if("@OPENMP_FOUND@" == "TRUE")
 end
 
 library_filenames << findQScintilla2(lib_dir)
+library_filenames << findBoostPythonMT(lib_dir)
 
 #This copies the libraries over, then changes permissions and the id from /usr/local/lib to @rpath
 library_filenames.each do |filename|
diff --git a/MantidPlot/src/ApplicationWindow.cpp b/MantidPlot/src/ApplicationWindow.cpp
index 4cb0fbb82362fa6d70866ae5f4ac133cb17402be..e52f2fbc8010174d8349784e20da5955dc164808 100644
--- a/MantidPlot/src/ApplicationWindow.cpp
+++ b/MantidPlot/src/ApplicationWindow.cpp
@@ -11647,7 +11647,11 @@ void ApplicationWindow::patchPaletteForLinux(QPalette &palette) const {
 
 bool ApplicationWindow::isUnityDesktop() const {
   return QString::fromLocal8Bit(qgetenv("XDG_SESSION_DESKTOP")) == "Unity" ||
-         QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")) == "Unity";
+         QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")) == "Unity" ||
+         QString::fromLocal8Bit(qgetenv("XDG_SESSION_DESKTOP")) ==
+             "ubuntu:GNOME" ||
+         QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")) ==
+             "ubuntu:GNOME";
 }
 
 void ApplicationWindow::setAppColors(const QColor &wc, const QColor &pc,
diff --git a/MantidPlot/src/Mantid/MantidSurfaceContourPlotGenerator.cpp b/MantidPlot/src/Mantid/MantidSurfaceContourPlotGenerator.cpp
index bdcd272f94f74ca0c8910e37ebc2c5cf1f1ef543..3a795cc574bf2714da90ddc8d2151b63ccc2a7d5 100644
--- a/MantidPlot/src/Mantid/MantidSurfaceContourPlotGenerator.cpp
+++ b/MantidPlot/src/Mantid/MantidSurfaceContourPlotGenerator.cpp
@@ -166,10 +166,11 @@ MantidSurfaceContourPlotGenerator::createWorkspaceForGroupPlot(
   // If it's a contour plot, make a histo workspace.
   const auto xMode = graphType == Type::Contour ? Histogram::XMode::BinEdges
                                                 : Histogram::XMode::Points;
-  const auto xSize = graphType == Type::Contour ? firstWS->blocksize() + 1
-                                                : firstWS->blocksize();
+  const auto firstBlocksize = firstWS->blocksize();
+  const auto xSize =
+      graphType == Type::Contour ? firstBlocksize + 1 : firstBlocksize;
   matrixWS = Mantid::API::WorkspaceFactory::Instance().create(
-      firstWS, nWorkspaces, xSize, firstWS->blocksize());
+      firstWS, nWorkspaces, xSize, firstBlocksize);
   matrixWS->setYUnitLabel(firstWS->YUnitLabel());
 
   // For each workspace in group, add data and log values
diff --git a/Testing/Data/SystemTest/WISH/input/11_4/WISH00019612.raw.md5 b/Testing/Data/SystemTest/WISH/input/11_4/WISH00019612.raw.md5
new file mode 100644
index 0000000000000000000000000000000000000000..8a7b42a5047089aa0a64a5840ec0637d3154f3b8
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/11_4/WISH00019612.raw.md5
@@ -0,0 +1 @@
+81e9d09c17c274f9c79252cdd73c0491
diff --git a/Testing/Data/SystemTest/WISH/input/11_4/WISH00019618.raw.md5 b/Testing/Data/SystemTest/WISH/input/11_4/WISH00019618.raw.md5
new file mode 100644
index 0000000000000000000000000000000000000000..1ca467917012ab64944245f925797eb060e96760
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/11_4/WISH00019618.raw.md5
@@ -0,0 +1 @@
+9b8888478637a478d9563fe7060a8881
diff --git a/Testing/Data/SystemTest/WISH/input/18_1/WISH00040503.raw.md5 b/Testing/Data/SystemTest/WISH/input/18_1/WISH00040503.raw.md5
new file mode 100644
index 0000000000000000000000000000000000000000..0d713758f24e30dbad440464a19c2127999b52a6
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/18_1/WISH00040503.raw.md5
@@ -0,0 +1 @@
+4fdb9e8db88934f2acb4d4dd900da0b1
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISH_cycle_10_3_noends_10to10.cal.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISH_cycle_10_3_noends_10to10.cal.md5
deleted file mode 100644
index 5c1b0c2e9dbdf35d782f2ff7e4d764d22c85ebb2..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISH_cycle_10_3_noends_10to10.cal.md5
+++ /dev/null
@@ -1 +0,0 @@
-055d17177f5593f65066a35d61a7db2c
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISH_cycle_15_4_noends_10to10_dodgytube_removed_feb2016.cal.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISH_cycle_15_4_noends_10to10_dodgytube_removed_feb2016.cal.md5
new file mode 100644
index 0000000000000000000000000000000000000000..57f6caec5e3c1e74b278ecebf54fee192483bb30
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/Cal/WISH_cycle_15_4_noends_10to10_dodgytube_removed_feb2016.cal.md5
@@ -0,0 +1 @@
+98d61de6108b0298c12f7339e5729a4c
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-10foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-10foc.nxs.md5
deleted file mode 100644
index 2db9ed15d7d29be90bf84538d333b56998b8136d..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-10foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-a6bb6c3c8413e25f2e55189476b0205f
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-1foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-1foc.nxs.md5
deleted file mode 100644
index 4292e9aa75eead93017c148a86a72d99577dfeb7..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-1foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-a3a3932fb3429932d5c4eb5ad6dd50db
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-2foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-2foc.nxs.md5
deleted file mode 100644
index 30de28390184d73e4db13ceedf56f569aa64ff6f..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-2foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-265af81599660d239a7643a999222d95
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-3foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-3foc.nxs.md5
deleted file mode 100644
index 9952fc37b622e3add2cb71bb43e276547fd7a610..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-3foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-dfa2b3fee890f6530ea1071c3910f293
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-4foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-4foc.nxs.md5
deleted file mode 100644
index e71c1d7d48170eb881c647fec5b30f2290656418..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-4foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-3bc719616f727879c2be9e57433a3fe6
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-5foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-5foc.nxs.md5
deleted file mode 100644
index 9ce1643b3bad368f02ee197863e606cd4f4ecac3..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-5foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-37c393acc606b627ff7f8712f4b0c27b
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-6foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-6foc.nxs.md5
deleted file mode 100644
index 308fa0541c339912432e0e2340eb474f6ad76a75..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-6foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-81a2766ecb6e31db395ba84e495adc52
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-7foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-7foc.nxs.md5
deleted file mode 100644
index 950aa568e3c5d4f73d542a3fa841a8e5314c0f4b..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-7foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-158392b86a834dd39c27fba9dcc7a006
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-8foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-8foc.nxs.md5
deleted file mode 100644
index f82dc507147ec666a9f5f32d92af6a3fa7f1e69c..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-8foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-ffbb89ea154b576d3e96f899c297ad88
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-9foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-9foc.nxs.md5
deleted file mode 100644
index d3baa82f99f9e540df5dd206bc652e1ae6b3184c..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/WISHvana41865-9foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-decc7353a7dc895fdfe7f298a046c6ea
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst19618-5foc-SF-S.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst19618-5foc-SF-S.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..a08dc42650091a1b60ee5eb2e10a71333b196f42
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst19618-5foc-SF-S.nxs.md5
@@ -0,0 +1 @@
+71831f8efeb8be3e0f5d3caa2e9eca89
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst19618-6foc-SF-S.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst19618-6foc-SF-S.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..a08dc42650091a1b60ee5eb2e10a71333b196f42
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst19618-6foc-SF-S.nxs.md5
@@ -0,0 +1 @@
+71831f8efeb8be3e0f5d3caa2e9eca89
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-10foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-10foc.nxs.md5
deleted file mode 100644
index e5364e50ed20ca7892db65932dc1ff1cbb97e28c..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-10foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-9b3f366c3f0597cdaf74c77fa3fd1c27
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-1foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-1foc.nxs.md5
deleted file mode 100644
index 8e9185681b28a6d1850f9c8a1df7409633a1ac85..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-1foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-f529a4e600134d3d116e65c33a078817
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-2foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-2foc.nxs.md5
deleted file mode 100644
index 696f25ffc7f85ce24a8c42102fd78c2c91574e7f..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-2foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-74ff004da8309bc26c251c69e24106a5
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-3foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-3foc.nxs.md5
deleted file mode 100644
index ab8659ea511243cf9014f57876cbb9ef3a3e20a5..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-3foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-e3ff38a26c672fde75b1ed7de99d19a6
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-4foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-4foc.nxs.md5
deleted file mode 100644
index cd6d92a8f2051698a16a7a624de63ad78dee8fa2..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-4foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-678059385510dc8d1e45b9565f2b7208
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-5foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-5foc.nxs.md5
deleted file mode 100644
index 53913d47efa9909651c730101ffd763236c2e406..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-5foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-a8f292edf6640bbb7c2eac34ff44f9bd
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-6foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-6foc.nxs.md5
deleted file mode 100644
index ae70766609d29061035e23a7df1311b3f2df3ef2..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-6foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-61fad722828e1620be2be59b6eddc66b
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-7foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-7foc.nxs.md5
deleted file mode 100644
index 7d6e36d5a90f551233842ec5379c2aabe28fd8ed..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-7foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-f45ee059905b380093748835d9cd90d0
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-8foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-8foc.nxs.md5
deleted file mode 100644
index cfbc1794ee484359353490ecb0446f1f676d1bcc..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-8foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-f7f9eb1001e7e7260b067f9ba901d949
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-9foc.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-9foc.nxs.md5
deleted file mode 100644
index 4eae207a8b259d775b5dc762f3be0b4b4650138a..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/Cal/emptyinst38581-9foc.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-3d8675ca48aa242e3c5a02b1f85624ae
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/vana19612-5foc-SF-SS.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/vana19612-5foc-SF-SS.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..cf5800e7b756d10574b57b78fe76ed36fb4b8518
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/Cal/vana19612-5foc-SF-SS.nxs.md5
@@ -0,0 +1 @@
+e8fea832144b67b4911cd00e5abb27e8
diff --git a/Testing/Data/SystemTest/WISH/input/Cal/vana19612-6foc-SF-SS.nxs.md5 b/Testing/Data/SystemTest/WISH/input/Cal/vana19612-6foc-SF-SS.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..d81d1572114947b481305f83bb2e5d71ae0aaf0b
--- /dev/null
+++ b/Testing/Data/SystemTest/WISH/input/Cal/vana19612-6foc-SF-SS.nxs.md5
@@ -0,0 +1 @@
+dafd63eef31cf573fcf643614ce5808d
diff --git a/Testing/Data/SystemTest/WISH/input/WISH00041870.nxs.md5 b/Testing/Data/SystemTest/WISH/input/WISH00041870.nxs.md5
deleted file mode 100644
index cf41a2b053048fc0c247daf42e65c2d92fb5498b..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/WISH/input/WISH00041870.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-b8ce8bb291d797f10fcc53b1faebd284
diff --git a/Testing/Data/UnitTest/CalculateEfficiencyCorrection_milder_moderator_polyethlyene_300K.txt.md5 b/Testing/Data/UnitTest/CalculateEfficiencyCorrection_milder_moderator_polyethlyene_300K.txt.md5
new file mode 100644
index 0000000000000000000000000000000000000000..14c2b9131a13e8f94490f67f077a2e1ac54752cb
--- /dev/null
+++ b/Testing/Data/UnitTest/CalculateEfficiencyCorrection_milder_moderator_polyethlyene_300K.txt.md5
@@ -0,0 +1 @@
+1d78382b08411d49a4189d8c68980fe1
diff --git a/Testing/Data/UnitTest/SI-4200-610.stl.md5 b/Testing/Data/UnitTest/SI-4200-610.stl.md5
new file mode 100644
index 0000000000000000000000000000000000000000..5993cad31adf12304b2936cf03d0d0f2617f288b
--- /dev/null
+++ b/Testing/Data/UnitTest/SI-4200-610.stl.md5
@@ -0,0 +1 @@
+af483b9877fd265539a2a0df6c4b5513
diff --git a/Testing/PerformanceTests/make_report.py b/Testing/PerformanceTests/make_report.py
index 99332b10bba0ceeb96ad9beb051ae99c90ad50e4..8c766e84b23dbce0b678bdf616037b00d7ba65a9 100755
--- a/Testing/PerformanceTests/make_report.py
+++ b/Testing/PerformanceTests/make_report.py
@@ -64,14 +64,18 @@ if __name__ == "__main__":
                         default=["./MantidSystemTests.db"],
                         help='Required: Path to the SQL database file(s).')
 
+    parser.add_argument('--plotting', dest='plotting',
+                        default="plotly",
+                        help='Plotting toolkit to generate the plots. Options=["plotly", "matplotlib"]')
+
     args = parser.parse_args()
 
-    # Import the manager definition
-    try:
+    if args.plotting == 'plotly':
         import analysis
-    except:
-        # plotly not available, use matplotlib fallback
+    elif args.plotting == 'matplotlib':
         import analysis_mpl as analysis
+    else:
+        raise RuntimeError("Unknown plotting toolkit '{}'".format(args.plotting))
 
     import sqlresults
 
diff --git a/Testing/SystemTests/scripts/InstallerTests.py b/Testing/SystemTests/scripts/InstallerTests.py
index 5822173ca4ca89d603f12b3b7c3e9d0af818cb0a..1e4a1547f3b0a21fc0c59e6f309ef141a1afaf7d 100644
--- a/Testing/SystemTests/scripts/InstallerTests.py
+++ b/Testing/SystemTests/scripts/InstallerTests.py
@@ -33,7 +33,7 @@ parser.add_argument('--archivesearch', dest='archivesearch', action='store_true'
 parser.add_argument('--exclude-in-pull-requests', dest="exclude_in_pr_builds",action="store_true",
                     help="Skip tests that are not run in pull request builds")
 log_levels = ['error', 'warning', 'notice', 'information', 'debug']
-parser.add_argument('-l', dest='log_level', metavar='level', default='notice',
+parser.add_argument('-l', dest='log_level', metavar='level', default='information',
                     choices=log_levels, help='Log level '+str(log_levels))
 options = parser.parse_args()
 
diff --git a/Testing/SystemTests/tests/analysis/GUIStartupTest.py b/Testing/SystemTests/tests/analysis/GUIStartupTest.py
index 8b0eea635a971f9a447d6b01ceebb6820cf165df..fb2e24e8e261ae735fc3448106a7fb60825b56cf 100644
--- a/Testing/SystemTests/tests/analysis/GUIStartupTest.py
+++ b/Testing/SystemTests/tests/analysis/GUIStartupTest.py
@@ -58,7 +58,7 @@ class GUIStartupTest(systemtesting.MantidSystemTest):
         # good startup
         p = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
         out, err = p.communicate()
-        self.assertEquals(out, b'Hello Mantid\n')
+        self.assertTrue(b'Hello Mantid\n' in out)
 
         # failing script
         with open(self.script, 'a') as f:
diff --git a/Testing/SystemTests/tests/analysis/ISISIndirectInelastic.py b/Testing/SystemTests/tests/analysis/ISISIndirectInelastic.py
index ef40423a6ca63f5221ee0b049ac794e4f232e579..3b915f32ce413a08b25cd02012f17e9b82cd3c46 100644
--- a/Testing/SystemTests/tests/analysis/ISISIndirectInelastic.py
+++ b/Testing/SystemTests/tests/analysis/ISISIndirectInelastic.py
@@ -85,6 +85,11 @@ import platform
 from six import with_metaclass
 
 
+def currentOSHasGSLv2():
+    """ Check whether the current OS should be running GSLv2 """
+    return platform.linux_distribution()[0].lower() == "ubuntu" or platform.mac_ver()[0] != ''
+
+
 class ISISIndirectInelasticBase(with_metaclass(ABCMeta, systemtesting.MantidSystemTest)):
     '''
     A common base class for the ISISIndirectInelastic* base classes.
@@ -857,7 +862,7 @@ class IRISIqtAndIqtFit(ISISIndirectInelasticIqtAndIqtFit):
         ref_files = ['II.IRISFury.nxs']
         # gsl v2 gives a slightly different result than v1
         # we could do with a better check than this
-        if platform.linux_distribution()[0] == "Ubuntu":
+        if currentOSHasGSLv2():
             ref_files += ['II.IRISFuryFitSeq_gslv2.nxs']
         else:
             ref_files += ['II.IRISFuryFitSeq_gslv1.nxs']
diff --git a/Testing/SystemTests/tests/analysis/ISIS_WISHPowderReductionTest.py b/Testing/SystemTests/tests/analysis/ISIS_WISHPowderReductionTest.py
index ad9bf484c4add564a0beef9d0349ed4516b950cc..7ae9255c1ee5eb1297d8ea9d07746c3ed49d9c1c 100644
--- a/Testing/SystemTests/tests/analysis/ISIS_WISHPowderReductionTest.py
+++ b/Testing/SystemTests/tests/analysis/ISIS_WISHPowderReductionTest.py
@@ -1,5 +1,5 @@
 from systemtesting import MantidSystemTest
-from wish.reduce import Wish_Run
+from wish.reduce import Wish
 
 from mantid import config
 import mantid.simpleapi as mantid
@@ -18,7 +18,6 @@ output_folder_name = "output"
 # Relative to input folder
 calibration_folder_name = "Cal"
 
-
 # Generate paths for the tests
 # This implies DIRS[0] is the system test data folder
 working_dir = os.path.join(DIRS[0], working_folder_name)
@@ -27,42 +26,109 @@ input_dir = os.path.join(working_dir, input_folder_name)
 output_dir = os.path.join(working_dir, output_folder_name)
 
 calibration_dir = os.path.join(input_dir, calibration_folder_name)
+# just test 5 and 6 to save time as process is the same for all other pairs
+panels = [5, 6]
+linked_panels = {
+    1: 10,
+    2: 9,
+    3: 8,
+    4: 7,
+    5: 6
+}
 
 
 class WISHPowderReductionTest(MantidSystemTest):
     # still missing required files check with ./systemtest -R PowderReduction --showskipped
     def requiredFiles(self):
-        input_files = ["WISHvana41865-1foc.nxs", "WISHvana41865-2foc.nxs", "WISHvana41865-3foc.nxs",
-                       "WISHvana41865-4foc.nxs", "WISHvana41865-5foc.nxs", "WISHvana41865-6foc.nxs",
-                       "WISHvana41865-7foc.nxs", "WISHvana41865-8foc.nxs", "WISHvana41865-9foc.nxs",
-                       "WISHvana41865-10foc.nxs", "emptyinst38581-1foc.nxs", "emptyinst38581-2foc.nxs",
-                       "emptyinst38581-3foc.nxs", "emptyinst38581-4foc.nxs", "emptyinst38581-5foc.nxs",
-                       "emptyinst38581-6foc.nxs", "emptyinst38581-7foc.nxs", "emptyinst38581-8foc.nxs",
-                       "emptyinst38581-9foc.nxs", "emptyinst38581-10foc.nxs"]
+        input_files = ["vana19612-{}foc-SF-SS.nxs".format(panel) for panel in panels]
+
+        input_files = [os.path.join(calibration_dir, files) for files in input_files]
+        return input_files
+
+    def cleanup(self):
+        if os.path.isdir(output_dir):
+            shutil.rmtree(output_dir)
+
+    def runTest(self):
+        os.makedirs(output_dir)
+        wish_test = Wish(calibration_dir, output_dir, True, input_dir + "/", False)
+        runs = [40503]
+
+        wish_test.reduce(runs, panels)
+        self.clearWorkspaces()
+
+    def validate(self):
+        self.tolerence = 1.e-8
+        validation_files = []
+        for panel in [x for x in panels if x < 6]:
+            validation_files = validation_files + \
+                               ["w40503-{0}_{1}foc".format(panel, linked_panels.get(panel)),
+                                "WISH40503-{0}_{1}no_absorb_raw.nxs".format(panel, linked_panels.get(panel))]
+        return validation_files
+
+    def clearWorkspaces(self):
+        deletews = ["w40503-{}foc".format(panel) for panel in panels]
+        for ws in deletews:
+            mantid.DeleteWorkspace(ws)
+            mantid.DeleteWorkspace(ws + "-d")
+
+
+class WISHPowderReductionNoAbsorptionTest(MantidSystemTest):
+    # still missing required files check with ./systemtest -R PowderReduction --showskipped
+    def requiredFiles(self):
+        input_files = ["vana19612-{}foc-SF-SS.nxs".format(panel) for panel in panels]
 
         input_files = [os.path.join(calibration_dir, files) for files in input_files]
         return input_files
 
     def cleanup(self):
-        shutil.rmtree(output_dir)
+        if os.path.isdir(output_dir):
+            shutil.rmtree(output_dir)
 
     def runTest(self):
         os.makedirs(output_dir)
-        Wish_Run("__main__", calibration_dir+"/", input_dir, output_dir, True)
+        wish_test = Wish(calibration_dir, output_dir, True, input_dir + "/")
+        runs = [40503]
+
+        wish_test.reduce(runs, panels)
         self.clearWorkspaces()
 
     def validate(self):
-        return "w41870-2_9foc", "WISH41870-2_9raw.nxs", \
-               "w41870-3_8foc", "WISH41870-3_8raw.nxs", \
-               "w41870-4_7foc", "WISH41870-4_7raw.nxs", \
-               "w41870-5_6foc", "WISH41870-5_6raw.nxs"
+        self.tolerence = 1.e-8
+        validation_files = []
+        for panel in [x for x in panels if x < 6]:
+            validation_files = validation_files + ["w40503-{0}_{1}foc".format(panel, linked_panels.get(panel)),
+                                                   "WISH40503-{0}_{1}raw.nxs".format(panel, linked_panels.get(panel))]
+        return validation_files
 
     def clearWorkspaces(self):
-        deletews = ["w41870-" + str(i) + "foc" for i in range(1, 11)]
+        deletews = ["w40503-{}foc".format(panel) for panel in panels]
         for ws in deletews:
             mantid.DeleteWorkspace(ws)
             mantid.DeleteWorkspace(ws + "-d")
 
-    # Skip test when on builds as extremely slow, run only as reversion test for wish script
-    def skipTests(self):
-        return True
+
+class WISHPowderReductionCreateVanadiumTest(MantidSystemTest):
+    # still missing required files check with ./systemtest -R PowderReduction --showskipped
+    def requiredFiles(self):
+        input_files = ["emptyinst19618-{}foc-SF-S.nxs".format(panel) for panel in panels]
+
+        input_files = [os.path.join(calibration_dir, files) for files in input_files]
+        return input_files
+
+    def cleanup(self):
+        if os.path.isdir(output_dir):
+            shutil.rmtree(output_dir)
+
+    def runTest(self):
+        os.makedirs(output_dir)
+        wish_test = Wish(calibration_dir, output_dir, True, input_dir + "/")
+        wish_test.create_vanadium_run(19612, 19618, panels)
+
+    def validate(self):
+        self.tolerence = 1.e-8
+        validation_files = []
+        for panel in [x for x in panels if x < 6]:
+            validation_files = validation_files + ["w19612-{}foc".format(panel),
+                                                   "vana19612-{}foc-SF-SS.nxs".format(panel)]
+        return validation_files
diff --git a/Testing/SystemTests/tests/analysis/MDNormCORELLITest.py b/Testing/SystemTests/tests/analysis/MDNormCORELLITest.py
new file mode 100644
index 0000000000000000000000000000000000000000..75a87d23378d948d7ccd2b04b0f7f7519fca30aa
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/MDNormCORELLITest.py
@@ -0,0 +1,69 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#pylint: disable=no-init
+
+"""
+System test for MDNorm
+"""
+from mantid.simpleapi import *
+import systemtesting
+
+
+class MDNormCORELLITest(systemtesting.MantidSystemTest):
+    def requiredFiles(self):
+        return ["CORELLI_29782.nxs","CORELLI_29792.nxs",
+                "SingleCrystalDiffuseReduction_SA.nxs",
+                "SingleCrystalDiffuseReduction_Flux.nxs",
+                "SingleCrystalDiffuseReduction_UB.mat"]
+
+    def runTest(self):
+        Load(Filename='CORELLI_29782.nxs', OutputWorkspace='data')
+        Load(Filename='SingleCrystalDiffuseReduction_SA.nxs', OutputWorkspace='SolidAngle')
+        Load(Filename='SingleCrystalDiffuseReduction_Flux.nxs', OutputWorkspace= 'Flux')
+        MaskDetectors(Workspace='data', MaskedWorkspace='SolidAngle')
+        ConvertUnits(InputWorkspace='data',OutputWorkspace='data',Target='Momentum')
+        CropWorkspaceForMDNorm(InputWorkspace='data',
+                               XMin=2.5,
+                               XMax=10,
+                               OutputWorkspace='data')
+        LoadIsawUB(InputWorkspace='data',Filename='SingleCrystalDiffuseReduction_UB.mat')
+        SetGoniometer(Workspace='data',Axis0='BL9:Mot:Sample:Axis1,0,1,0,1')
+        min_vals,max_vals=ConvertToMDMinMaxGlobal(InputWorkspace='data',
+                                                  QDimensions='Q3D',
+                                                  dEAnalysisMode='Elastic',
+                                                  Q3DFrames='Q')
+        ConvertToMD(InputWorkspace='data',
+                    QDimensions='Q3D',
+                    dEAnalysisMode='Elastic',
+                    Q3DFrames='Q_sample',
+                    OutputWorkspace='md',
+                    MinValues=min_vals,
+                    MaxValues=max_vals)
+        RecalculateTrajectoriesExtents(InputWorkspace= 'md', OutputWorkspace='md')
+        DeleteWorkspace('data')
+
+        MDNorm(InputWorkspace='md',
+               SolidAngleWorkspace='SolidAngle',
+               FluxWorkspace='Flux',
+               QDimension0='1,1,0',
+               QDimension1='1,-1,0',
+               QDimension2='0,0,1',
+               Dimension0Name='QDimension0',
+               Dimension0Binning='-10.0,0.1,10.0',
+               Dimension1Name='QDimension1',
+               Dimension1Binning='-10.0,0.1,10.0',
+               Dimension2Name='QDimension2',
+               Dimension2Binning='-0.1,0.1',
+               SymmetryOperations='P 31 2 1',
+               OutputWorkspace='result',
+               OutputDataWorkspace='dataMD',
+               OutputNormalizationWorkspace='normMD')
+        DeleteWorkspace('md')
+
+    def validate(self):
+        self.tolerance = 1e-7
+        return 'result','MDNormCORELLI.nxs'
diff --git a/Testing/SystemTests/tests/analysis/MDNormHYSPECTest.py b/Testing/SystemTests/tests/analysis/MDNormHYSPECTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..95a3dfafa6e38428bad0a59207653558f55ef2e1
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/MDNormHYSPECTest.py
@@ -0,0 +1,97 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#pylint: disable=no-init
+
+"""
+System test for MDNorm
+"""
+
+from mantid.simpleapi import *
+import systemtesting
+
+
+class MDNormHYSPECTest(systemtesting.MantidSystemTest):
+
+    tolerance=1e-8
+
+    def requiredMemoryMB(self):
+        return 5000
+
+    def requiredFiles(self):
+        return ['HYS_13656_event.nxs','HYS_13657_event.nxs','HYS_13658_event.nxs']
+
+    def runTest(self):
+        config.setFacility('SNS')
+        Load(Filename='HYS_13656-13658',OutputWorkspace='sum')
+        SetGoniometer(Workspace='sum', Axis0='s1,0,1,0,1')
+        GenerateEventsFilter(InputWorkspace='sum',
+                             OutputWorkspace='splboth',
+                             InformationWorkspace='info',
+                             UnitOfTime='Nanoseconds',
+                             LogName='s1',
+                             MaximumLogValue=90,
+                             LogValueInterval=1)
+        FilterEvents(InputWorkspace='sum',
+                     SplitterWorkspace='splboth',
+                     InformationWorkspace='info',
+                     FilterByPulseTime=True,
+                     GroupWorkspaces=True,
+                     OutputWorkspaceIndexedFrom1=True,
+                     OutputWorkspaceBaseName='split')
+        DeleteWorkspace('sum')
+        DeleteWorkspace('splboth')
+        DeleteWorkspace('info')
+        DgsReduction(SampleInputWorkspace='split',
+                     SampleInputMonitorWorkspace='split_1',
+                     IncidentEnergyGuess=50,
+                     SofPhiEIsDistribution=False,
+                     TimeIndepBackgroundSub=True,
+                     TibTofRangeStart=10400,
+                     TibTofRangeEnd=12400,
+                     OutputWorkspace='reduced')
+        SetUB(Workspace='reduced',
+              a=5.823,
+              b=6.475,
+              c=3.186,
+              u='0,1,0',
+              v='0,0,1')
+        CropWorkspaceForMDNorm(InputWorkspace='reduced',
+                               XMin=-25,
+                               XMax=49,
+                               OutputWorkspace='reduced')
+        ConvertToMD(InputWorkspace='reduced',
+                    QDimensions='Q3D',
+                    Q3DFrames='Q_sample',
+                    OutputWorkspace='md',
+                    MinValues='-11,-11,-11,-25',
+                    MaxValues='11,11,11,49')
+        DeleteWorkspace("split")
+        DeleteWorkspace("reduced")
+        DeleteWorkspace("PreprocessedDetectorsWS")
+        DeleteWorkspace("TOFCorrectWS")
+        MergeMD(InputWorkspaces='md', OutputWorkspace='merged')
+        DeleteWorkspace("md")
+        MDNorm(InputWorkspace='merged',
+               Dimension0Name='QDimension1',
+               Dimension0Binning='-5,0.05,5',
+               Dimension1Name='QDimension2',
+               Dimension1Binning='-5,0.05,5',
+               Dimension2Name='DeltaE',
+               Dimension2Binning='-2,2',
+               Dimension3Name='QDimension0',
+               Dimension3Binning='-0.5,0.5',
+               SymmetryOperations='x,y,z;x,-y,z;x,y,-z;x,-y,-z',
+               OutputWorkspace='result',
+               OutputDataWorkspace='dataMD',
+               OutputNormalizationWorkspace='normMD')
+        DeleteWorkspace("dataMD")
+        DeleteWorkspace("normMD")
+        DeleteWorkspace("merged")
+
+    def validate(self):
+        self.tolerance = 1e-8
+        return 'result','MDNormHYSPEC.nxs'
diff --git a/Testing/SystemTests/tests/analysis/reference/II.AnalysisElWinMulti.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/II.AnalysisElWinMulti.nxs.md5
index 871ffa7d1915166aee2e35ae476e716cddffb251..2b3c7b399f4764d938447be7e2d727dcfb5db487 100644
--- a/Testing/SystemTests/tests/analysis/reference/II.AnalysisElWinMulti.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/II.AnalysisElWinMulti.nxs.md5
@@ -1 +1 @@
-c411c711cfef6fea15184c45d733a480
\ No newline at end of file
+bd3274a0fe71c8d634f7cecd4d90479e
diff --git a/Testing/SystemTests/tests/analysis/reference/II.IRISMoments.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/II.IRISMoments.nxs.md5
index 870024467e73790d3cf578ce13fa1e900a33ca4a..a9857f36b9e5022067e49ec3004d659683329c33 100644
--- a/Testing/SystemTests/tests/analysis/reference/II.IRISMoments.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/II.IRISMoments.nxs.md5
@@ -1 +1 @@
-d2185756bd31c3fce4bee9f4dc0bc441
+5019e21091a7b56a76b92b9f98cc59ec
diff --git a/Testing/SystemTests/tests/analysis/reference/II.OSIRISMoments.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/II.OSIRISMoments.nxs.md5
index 8bd1cff7ee995f771198fd4e5a5b861457a4292f..863af15f45b1492b2ffafa6f756b6fe7d7d0c84d 100644
--- a/Testing/SystemTests/tests/analysis/reference/II.OSIRISMoments.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/II.OSIRISMoments.nxs.md5
@@ -1 +1 @@
-a1a495cd3351cd56ed06c7c7844817bb
+c23e62c08f2ced8496ce229b071be418
diff --git a/Testing/SystemTests/tests/analysis/reference/MDNormCORELLI.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/MDNormCORELLI.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..6c886bd720b24b1bd664921ed836419efb9a82cf
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/MDNormCORELLI.nxs.md5
@@ -0,0 +1 @@
+a81889c498787ce6ed71228fd43fc776
diff --git a/Testing/SystemTests/tests/analysis/reference/MDNormHYSPEC.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/MDNormHYSPEC.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..11d3591f43d75321ddcfc51a7db9b15294470d84
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/MDNormHYSPEC.nxs.md5
@@ -0,0 +1 @@
+c2610ae5c15704573e63d30dfe0c2827
diff --git a/Testing/SystemTests/tests/analysis/reference/PG3_9829_sum_reference.gsa.md5 b/Testing/SystemTests/tests/analysis/reference/PG3_9829_sum_reference.gsa.md5
index d5ea4e051aa8223bae162d5464e41160bf20dfca..e3db4c237afddfb25a0e78a62f185f1803b3d35a 100644
--- a/Testing/SystemTests/tests/analysis/reference/PG3_9829_sum_reference.gsa.md5
+++ b/Testing/SystemTests/tests/analysis/reference/PG3_9829_sum_reference.gsa.md5
@@ -1 +1 @@
-13f233609f78d30c6cbdb228a8c1b1b3
+0a6f1170aa0baca89f766337fe50b687
diff --git a/Testing/SystemTests/tests/analysis/reference/WISH40503-5_6no_absorb_raw.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/WISH40503-5_6no_absorb_raw.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..48be88f06db245a22550ac088d1dd566857297d0
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/WISH40503-5_6no_absorb_raw.nxs.md5
@@ -0,0 +1 @@
+6d48e72de164d0bea0d2f33d954ae1ef
diff --git a/Testing/SystemTests/tests/analysis/reference/WISH40503-5_6raw.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/WISH40503-5_6raw.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..f011cfd731f0c5ff2394a70957d4b34a01dcb1a0
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/WISH40503-5_6raw.nxs.md5
@@ -0,0 +1 @@
+c9c36283bd351afc7296b81594b64f05
diff --git a/Testing/SystemTests/tests/analysis/reference/WISH41870-1_10raw.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/WISH41870-1_10raw.nxs.md5
deleted file mode 100644
index b35e98a748bf0a0efcf682c585fc529e532261af..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/reference/WISH41870-1_10raw.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-62b46e6a09c66c34fd65cca545783c2b
diff --git a/Testing/SystemTests/tests/analysis/reference/WISH41870-2_9raw.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/WISH41870-2_9raw.nxs.md5
deleted file mode 100644
index 43cfcbfd6a2f5c2e0ee978a832c3d5d92ef43a96..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/reference/WISH41870-2_9raw.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-75b95e5e550ab45932c92525f7285209
diff --git a/Testing/SystemTests/tests/analysis/reference/WISH41870-3_8raw.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/WISH41870-3_8raw.nxs.md5
deleted file mode 100644
index fe1f174409616056aff2fba331dd6bcd0461d3d0..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/reference/WISH41870-3_8raw.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-cb91390e3ed1428eda044c75c776afb6
diff --git a/Testing/SystemTests/tests/analysis/reference/WISH41870-4_7raw.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/WISH41870-4_7raw.nxs.md5
deleted file mode 100644
index 0045522dc678b8ef92214b190692de4eca889013..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/reference/WISH41870-4_7raw.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-31d0b9bea9965eae70fbf0d2c93ab6f1
diff --git a/Testing/SystemTests/tests/analysis/reference/WISH41870-5_6raw.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/WISH41870-5_6raw.nxs.md5
deleted file mode 100644
index 42fa6b456e67c67dd9320478c976822b1d640d53..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/reference/WISH41870-5_6raw.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-c4515cceb5b4d58f0bcfded962d2a189
diff --git a/Testing/SystemTests/tests/analysis/reference/vana19612-5foc-SF-SS.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/vana19612-5foc-SF-SS.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..cf5800e7b756d10574b57b78fe76ed36fb4b8518
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/vana19612-5foc-SF-SS.nxs.md5
@@ -0,0 +1 @@
+e8fea832144b67b4911cd00e5abb27e8
diff --git a/Testing/SystemTests/tests/analysis/reference/vana19612-6foc-SF-SS.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/vana19612-6foc-SF-SS.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..d81d1572114947b481305f83bb2e5d71ae0aaf0b
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/vana19612-6foc-SF-SS.nxs.md5
@@ -0,0 +1 @@
+dafd63eef31cf573fcf643614ce5808d
diff --git a/buildconfig/CMake/Bootstrap.cmake b/buildconfig/CMake/Bootstrap.cmake
index 1272f8f8ee299019e1906b73bd1fcb9368a6a7c0..7f0d1195b812d95221c2476e424def5ed590183c 100644
--- a/buildconfig/CMake/Bootstrap.cmake
+++ b/buildconfig/CMake/Bootstrap.cmake
@@ -10,7 +10,7 @@ if( MSVC )
   include ( ExternalProject )
   set( EXTERNAL_ROOT ${PROJECT_SOURCE_DIR}/external CACHE PATH "Location to clone third party dependencies to" )
   set( THIRD_PARTY_GIT_URL "https://github.com/mantidproject/thirdparty-msvc2015.git" )
-  set ( THIRD_PARTY_GIT_SHA1 80b72f1c5ad30675c03967ead71c8223fbe50d4f )
+  set ( THIRD_PARTY_GIT_SHA1 22bf373a4055d85b6793a34f6c823719d482a229 )
   set ( THIRD_PARTY_DIR ${EXTERNAL_ROOT}/src/ThirdParty )
   # Generates a script to do the clone/update in tmp
   set ( _project_name ThirdParty )
diff --git a/buildconfig/CMake/DarwinSetup.cmake b/buildconfig/CMake/DarwinSetup.cmake
index 4906de643c4915b444dee907c8f637bf69f2efcd..4107b939183e5c62b8470ae1fb832e7b20ad230e 100644
--- a/buildconfig/CMake/DarwinSetup.cmake
+++ b/buildconfig/CMake/DarwinSetup.cmake
@@ -121,7 +121,7 @@ set ( CPACK_PACKAGE_EXECUTABLES MantidPlot )
 set ( INBUNDLE MantidPlot.app/ )
 
 # Copy the launcher script to the correct location
-configure_file ( ${CMAKE_MODULE_PATH}/Packaging/osx/Mantid_osx_launcher 
+configure_file ( ${CMAKE_MODULE_PATH}/Packaging/osx/Mantid_osx_launcher.in
                  ${CMAKE_BINARY_DIR}/bin/MantidPlot.app/Contents/MacOS/Mantid_osx_launcher COPYONLY )
 
 # We know exactly where this has to be on Darwin, but separate whether we have
@@ -139,6 +139,11 @@ set ( BIN_DIR MantidPlot.app/Contents/MacOS )
 set ( LIB_DIR MantidPlot.app/Contents/MacOS )
 # This is the root of the plugins directory
 set ( PLUGINS_DIR MantidPlot.app/plugins )
+
+set ( WORKBENCH_BIN_DIR MantidWorkbench.app/Contents/MacOS )
+set ( WORKBENCH_LIB_DIR MantidWorkbench.app/Contents/MacOS )
+set ( WORKBENCH_PLUGINS_DIR MantidWorkbench.app/plugins )
+
 # Separate directory of plugins to be discovered by the ParaView framework
 # These cannot be mixed with our other plugins. Further sub-directories
 # based on the Qt version will also be created by the installation targets
@@ -158,6 +163,15 @@ string(SUBSTRING "${PYQT4_SYMLINK_Qtso}" 0 ${STOPPOS} PYQT4_SYMLINK)
 set( PYQT4_PYTHONPATH ${PYQT4_PATH}/${PYQT4_SYMLINK} )
 string(REGEX REPLACE "/$" "" PYQT4_PYTHONPATH "${PYQT4_PYTHONPATH}")
 
+if (NOT PYQT5_PATH)
+  set ( PYQT5_PATH /usr/local/lib/python${PY_VER}/site-packages/PyQt5 )
+endif(NOT PYQT5_PATH)
+execute_process(COMMAND readlink ${PYQT5_PATH}/Qt.so OUTPUT_VARIABLE PYQT5_SYMLINK_Qtso)
+string(FIND "${PYQT5_SYMLINK_Qtso}" "Qt.so" STOPPOS)
+string(SUBSTRING "${PYQT5_SYMLINK_Qtso}" 0 ${STOPPOS} PYQT5_SYMLINK)
+set( PYQT5_PYTHONPATH ${PYQT5_PATH}/${PYQT5_SYMLINK} )
+string(REGEX REPLACE "/$" "" PYQT5_PYTHONPATH "${PYQT5_PYTHONPATH}")
+
 if (NOT SITEPACKAGES_PATH)
   set ( SITEPACKAGES_PATH /usr/local/lib/python${PY_VER}/site-packages )
 endif(NOT SITEPACKAGES_PATH)
@@ -170,6 +184,7 @@ string(REGEX REPLACE "/$" "" SITEPACKAGES "${SITEPACKAGES}")
 # Python packages
 
 install ( PROGRAMS ${SITEPACKAGES}/sip.so DESTINATION ${BIN_DIR} )
+install ( PROGRAMS ${SITEPACKAGES}/sip.so DESTINATION ${WORKBENCH_BIN_DIR} )
 
 # Explicitly specify which PyQt libraries we want because just taking the whole
 #directory will swell the install kit unnecessarily.
@@ -190,12 +205,35 @@ endif ()
 
 install ( DIRECTORY ${PYQT4_PYTHONPATH}/uic DESTINATION ${BIN_DIR}/PyQt4 )
 
+# Explicitly specify which PyQt libraries we want because just taking the whole
+#directory will swell the install kit unnecessarily.
+install ( FILES ${PYQT5_PYTHONPATH}/Qt.so
+                ${PYQT5_PYTHONPATH}/QtCore.so
+                ${PYQT5_PYTHONPATH}/QtGui.so
+                ${PYQT5_PYTHONPATH}/QtOpenGL.so
+                ${PYQT5_PYTHONPATH}/QtSql.so
+                ${PYQT5_PYTHONPATH}/QtSvg.so
+                ${PYQT5_PYTHONPATH}/QtXml.so
+                ${PYQT5_PYTHONPATH}/QtWidgets.so
+                ${PYQT5_PYTHONPATH}/QtPrintSupport.so
+                ${PYQT5_PYTHONPATH}/__init__.py
+                DESTINATION ${WORKBENCH_BIN_DIR}/PyQt5 )
+# Newer PyQt versions have a new internal library that we need to take
+if ( EXISTS ${PYQT5_PYTHONPATH}/_qt.so )
+  install ( FILES ${PYQT5_PYTHONPATH}/_qt.so
+      DESTINATION ${WORKBENCH_BIN_DIR}/PyQt5 )
+endif ()
+
+install ( DIRECTORY ${PYQT5_PYTHONPATH}/uic DESTINATION ${WORKBENCH_BIN_DIR}/PyQt5 )
+
+
 install ( FILES ${CMAKE_SOURCE_DIR}/images/MantidPlot.icns
           DESTINATION MantidPlot.app/Contents/Resources/ )
 # Add launcher script for mantid python
 install ( PROGRAMS ${CMAKE_BINARY_DIR}/mantidpython_osx_install
           DESTINATION MantidPlot.app/Contents/MacOS/
           RENAME mantidpython )
+
 # Add launcher application for a Mantid IPython console
 install ( PROGRAMS ${CMAKE_MODULE_PATH}/Packaging/osx/MantidPython_osx_launcher
           DESTINATION MantidPython\ \(optional\).app/Contents/MacOS/
@@ -205,6 +243,18 @@ install ( FILES ${CMAKE_MODULE_PATH}/Packaging/osx/mantidpython_Info.plist
           RENAME Info.plist )
 install ( FILES ${CMAKE_SOURCE_DIR}/images/MantidPython.icns
           DESTINATION MantidPython\ \(optional\).app/Contents/Resources/ )
+
+# Add launcher application for a Mantid Workbench
+install ( PROGRAMS ${CMAKE_MODULE_PATH}/Packaging/osx/MantidWorkbench_osx_launcher
+          DESTINATION MantidWorkbench.app/Contents/MacOS/
+          RENAME MantidWorkbench )
+install ( FILES ${CMAKE_MODULE_PATH}/Packaging/osx/mantidworkbench_Info.plist
+          DESTINATION MantidWorkbench.app/Contents/
+          RENAME Info.plist )
+      install ( FILES ${CMAKE_SOURCE_DIR}/images/MantidWorkbench.icns
+          DESTINATION MantidWorkbench.app/Contents/Resources/ 
+          RENAME MantidWorkbench.icns )
+
 # Add launcher application for Mantid IPython notebooks
 install ( PROGRAMS ${CMAKE_MODULE_PATH}/Packaging/osx/MantidNotebook_osx_launcher
           DESTINATION MantidNotebook\ \(optional\).app/Contents/MacOS/
diff --git a/buildconfig/CMake/FindGSL.cmake b/buildconfig/CMake/FindGSL.cmake
deleted file mode 100644
index eb16af1497aead87f31776e75f5d98bc6eb52a39..0000000000000000000000000000000000000000
--- a/buildconfig/CMake/FindGSL.cmake
+++ /dev/null
@@ -1,161 +0,0 @@
-# Script found on KDE-edu list
-# TODO replace this with OpenCog SIAI copyrighted version.
-# 
-# Look for the header file
-# Try to find gnu scientific library GSL
-# See 
-# http://www.gnu.org/software/gsl/  and 
-# http://gnuwin32.sourceforge.net/packages/gsl.htm
-#
-# Once run this will define: 
-# 
-# GSL_FOUND       = system has GSL lib
-#
-# GSL_LIBRARIES   = full path to the libraries
-#    on Unix/Linux with additional linker flags from "gsl-config --libs"
-# 
-# CMAKE_GSL_CXX_FLAGS  = Unix compiler flags for GSL, essentially "`gsl-config --cxxflags`"
-#
-# GSL_INCLUDE_DIR      = where to find headers 
-#
-# GSL_LINK_DIRECTORIES = link directories, useful for rpath on Unix
-# GSL_EXE_LINKER_FLAGS = rpath on Unix
-#
-# Felix Woelk 07/2004
-# Jan Woetzel
-#
-# www.mip.informatik.uni-kiel.de
-# --------------------------------
-
-IF( WIN32 OR APPLE )  # We get things out of Third_Party on the Mac
-  # JW tested with gsl-1.8, Windows XP, MSVS 7.1
-  SET(GSL_POSSIBLE_ROOT_DIRS
-    ${GSL_ROOT_DIR}
-    $ENV{GSL_ROOT_DIR}
-    ${GSL_DIR}
-    ${GSL_HOME}    
-    $ENV{GSL_DIR}
-    $ENV{GSL_HOME}
-    $ENV{EXTRA}
-    )
-  FIND_PATH(GSL_INCLUDE_DIR
-    NAMES gsl/gsl_cdf.h gsl/gsl_randist.h
-    PATHS ${GSL_POSSIBLE_ROOT_DIRS}
-    PATH_SUFFIXES include
-    DOC "GSL header include dir"
-    )
-  
-  FIND_LIBRARY(GSL_GSL_LIBRARY
-    NAMES gsl libgsl
-    PATHS  ${GSL_POSSIBLE_ROOT_DIRS}
-    PATH_SUFFIXES lib
-    DOC "GSL library dir" )  
-  
-  FIND_LIBRARY(GSL_GSL_LIBRARY_DEBUG
-    NAMES gsl_d libgsl_d
-    PATHS  ${GSL_POSSIBLE_ROOT_DIRS}
-    PATH_SUFFIXES lib
-    DOC "GSL library dir" )  
-  
-  FIND_LIBRARY(GSL_GSLCBLAS_LIBRARY
-    NAMES gslcblas libgslcblas cblas
-    PATHS  ${GSL_POSSIBLE_ROOT_DIRS}
-    PATH_SUFFIXES lib
-    DOC "GSL cblas library dir" )
-  
-  FIND_LIBRARY(GSL_GSLCBLAS_LIBRARY_DEBUG
-    NAMES gslcblas_d libgslcblas_d cblas_d
-    PATHS  ${GSL_POSSIBLE_ROOT_DIRS}
-    PATH_SUFFIXES lib
-    DOC "GSL cblas library dir" )
-  
-  IF ( GSL_GSL_LIBRARY_DEBUG AND GSL_GSLCBLAS_LIBRARY_DEBUG )
-    SET(GSL_LIBRARIES optimized ${GSL_GSL_LIBRARY} optimized ${GSL_GSLCBLAS_LIBRARY} debug ${GSL_GSL_LIBRARY_DEBUG} debug ${GSL_GSLCBLAS_LIBRARY_DEBUG})
-  ELSE()
-    SET(GSL_LIBRARIES ${GSL_GSL_LIBRARY} ${GSL_GSLCBLAS_LIBRARY} )
-  ENDIF()
-
-  mark_as_advanced ( GSL_INCLUDE_DIR GSL_GSL_LIBRARY GSL_GSL_LIBRARY_DEBUG
-                     GSL_GSLCBLAS_LIBRARY GSL_GSLCBLAS_LIBRARY_DEBUG
-  )
-
-ELSE()
-  
-  IF(UNIX) 
-    SET(GSL_CONFIG_PREFER_PATH 
-      "$ENV{GSL_DIR}/bin"
-      "$ENV{GSL_DIR}"
-      "$ENV{GSL_HOME}/bin" 
-      "$ENV{GSL_HOME}" 
-      CACHE STRING "preferred path to GSL (gsl-config)")
-    FIND_PROGRAM(GSL_CONFIG gsl-config
-      ${GSL_CONFIG_PREFER_PATH}
-      /usr/bin/
-      )
-    # MESSAGE("DBG GSL_CONFIG ${GSL_CONFIG}")
-    
-    IF (GSL_CONFIG) 
-      # set CXXFLAGS to be fed into CXX_FLAGS by the user:
-      SET(GSL_CXX_FLAGS "`${GSL_CONFIG} --cflags`")
-      
-      # set INCLUDE_DIRS to prefix+include
-      EXEC_PROGRAM(${GSL_CONFIG}
-        ARGS --prefix
-        OUTPUT_VARIABLE GSL_PREFIX)
-      SET(GSL_INCLUDE_DIR ${GSL_PREFIX}/include CACHE STRING INTERNAL)
-
-      # set link libraries and link flags
-      
-      # extract link dirs for rpath  
-      EXEC_PROGRAM(${GSL_CONFIG}
-        ARGS --libs
-        OUTPUT_VARIABLE GSL_CONFIG_LIBS )
-
-      SET( GSL_LIBRARIES ${GSL_CONFIG_LIBS} )
-
-      # split off the link dirs (for rpath)
-      # use regular expression to match wildcard equivalent "-L*<endchar>"
-      # with <endchar> is a space or a semicolon
-      STRING(REGEX MATCHALL "[-][L]([^ ;])+" 
-        GSL_LINK_DIRECTORIES_WITH_PREFIX 
-        "${GSL_CONFIG_LIBS}" )
-      #      MESSAGE("DBG  GSL_LINK_DIRECTORIES_WITH_PREFIX=${GSL_LINK_DIRECTORIES_WITH_PREFIX}")
-
-      # remove prefix -L because we need the pure directory for LINK_DIRECTORIES
-      
-      IF (GSL_LINK_DIRECTORIES_WITH_PREFIX)
-        STRING(REGEX REPLACE "[-][L]" "" GSL_LINK_DIRECTORIES ${GSL_LINK_DIRECTORIES_WITH_PREFIX} )
-      ENDIF (GSL_LINK_DIRECTORIES_WITH_PREFIX)
-      SET(GSL_EXE_LINKER_FLAGS "-Wl,-rpath,${GSL_LINK_DIRECTORIES}" CACHE STRING INTERNAL)
-      #      MESSAGE("DBG  GSL_LINK_DIRECTORIES=${GSL_LINK_DIRECTORIES}")
-      #      MESSAGE("DBG  GSL_EXE_LINKER_FLAGS=${GSL_EXE_LINKER_FLAGS}")
-
-      #      ADD_DEFINITIONS("-DHAVE_GSL")
-      #      SET(GSL_DEFINITIONS "-DHAVE_GSL")
-      MARK_AS_ADVANCED(
-        GSL_CXX_FLAGS
-        GSL_INCLUDE_DIR
-        GSL_LIBRARIES
-        GSL_LINK_DIRECTORIES
-        GSL_DEFINITIONS
-      )
-      #MESSAGE(STATUS "Using GSL from ${GSL_PREFIX}")
-      include ( FindPackageHandleStandardArgs )
-      find_package_handle_standard_args( GSL DEFAULT_MSG GSL_INCLUDE_DIR )
-      
-    ELSE(GSL_CONFIG)
-      MESSAGE("FindGSL.cmake: gsl-config not found. Please set it manually. GSL_CONFIG=${GSL_CONFIG}")
-    ENDIF(GSL_CONFIG)
-
-    mark_as_advanced ( GSL_CONFIG GSL_CONFIG_PREFER_PATH GSL_EXE_LINKER_FLAGS )
-
-  ENDIF(UNIX)
-ENDIF()
-
-
-IF(GSL_LIBRARIES)
-  IF(GSL_INCLUDE_DIR OR GSL_CXX_FLAGS)
-    SET(GSL_FOUND 1)
-  ENDIF(GSL_INCLUDE_DIR OR GSL_CXX_FLAGS)
-ENDIF(GSL_LIBRARIES)
-
diff --git a/buildconfig/CMake/FindPyUnitTest.cmake b/buildconfig/CMake/FindPyUnitTest.cmake
index 5ac987e2e6291f92d6adc0e70198daeafd859e0e..20725240b3ff1abab4c30d6d6a92e710db61e006 100644
--- a/buildconfig/CMake/FindPyUnitTest.cmake
+++ b/buildconfig/CMake/FindPyUnitTest.cmake
@@ -24,7 +24,13 @@ function ( PYUNITTEST_ADD_TEST _test_src_dir _testname_prefix )
   if ( WIN32 )
     set ( _test_runner ${_test_runner}.bat )
   endif ()
-  set ( _test_runner_module ${CMAKE_SOURCE_DIR}/Framework/PythonInterface/test/testhelpers/testrunner.py )
+
+  if ( NOT PYUNITTEST_RUNNER )
+    set ( _test_runner_module ${CMAKE_SOURCE_DIR}/Framework/PythonInterface/test/testhelpers/testrunner.py )
+  else ()
+    set ( _test_runner_module ${PYUNITTEST_RUNNER} )
+  endif()
+
 
   # Environment
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
diff --git a/buildconfig/CMake/LinuxPackageScripts.cmake b/buildconfig/CMake/LinuxPackageScripts.cmake
index 7c48de4f85345676a1ec33872bf06c6ecc4b994e..07706ddb051526b41321cb33122ee0f3135e4076 100644
--- a/buildconfig/CMake/LinuxPackageScripts.cmake
+++ b/buildconfig/CMake/LinuxPackageScripts.cmake
@@ -15,8 +15,12 @@
 set ( BIN_DIR bin )
 set ( ETC_DIR etc )
 set ( LIB_DIR lib )
-# This is the root of the plugins directory
 set ( PLUGINS_DIR plugins )
+
+set ( WORKBENCH_BIN_DIR ${BIN_DIR} )
+set ( WORKBENCH_LIB_DIR ${LIB_DIR} )
+set ( WORKBENCH_PLUGINS_DIR ${PLUGINS_DIR} )
+
 # Separate directory of plugins to be discovered by the ParaView framework
 # These cannot be mixed with our other plugins. Further sub-directories
 # based on the Qt version will also be created by the installation targets
@@ -252,7 +256,7 @@ if (ENABLE_MANTIDPLOT)
             DESTINATION ${BIN_DIR} RENAME launch_mantidplot.sh )
 endif ()
 if (PACKAGE_WORKBENCH) # will eventually switch to ENABLE_WORKBENCH
-  set ( MANTIDWORKBENCH_EXEC workbench ) # what the actual thing is called
+  set ( MANTIDWORKBENCH_EXEC workbench-script ) # what the actual thing is called
   configure_file ( ${CMAKE_MODULE_PATH}/Packaging/launch_mantidworkbench.sh.in
                    ${CMAKE_CURRENT_BINARY_DIR}/launch_mantidworkbench.sh.install @ONLY )
   install ( PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/launch_mantidworkbench.sh.install
diff --git a/buildconfig/CMake/MSVCSetup.cmake b/buildconfig/CMake/MSVCSetup.cmake
index d6944bde37b86424cc5a3d43665a9584f4fcd2c0..269a35f4256f896f947c4c4d151187d2d6bc7c4f 100644
--- a/buildconfig/CMake/MSVCSetup.cmake
+++ b/buildconfig/CMake/MSVCSetup.cmake
@@ -102,6 +102,8 @@ else ()
 endif()
 
 configure_file ( ${WINDOWS_BUILDCONFIG}/command-prompt.bat.in ${PROJECT_BINARY_DIR}/command-prompt.bat @ONLY )
+configure_file ( ${WINDOWS_BUILDCONFIG}/pycharm.env.in ${PROJECT_BINARY_DIR}/pycharm.env @ONLY )
+
 # The IDE may not be installed as we could be just using the build tools
 if ( EXISTS ${MSVC_IDE_LOCATION}/devenv.exe )
     configure_file ( ${WINDOWS_BUILDCONFIG}/visual-studio.bat.in ${PROJECT_BINARY_DIR}/visual-studio.bat @ONLY )
@@ -150,6 +152,11 @@ set ( BIN_DIR bin )
 set ( LIB_DIR ${BIN_DIR} )
 # This is the root of the plugins directory
 set ( PLUGINS_DIR plugins )
+
+set ( WORKBENCH_BIN_DIR ${BIN_DIR} )
+set ( WORKBENCH_LIB_DIR ${LIB_DIR} )
+set ( WORKBENCH_PLUGINS_DIR ${PLUGINS_DIR} )
+
 # Separate directory of plugins to be discovered by the ParaView framework
 # These cannot be mixed with our other plugins. Further sub-directories
 # based on the Qt version will also be created by the installation targets
diff --git a/buildconfig/CMake/Packaging/launch_mantidplot.sh.in b/buildconfig/CMake/Packaging/launch_mantidplot.sh.in
index 0ba17b9ad15420e63018f7fd27d534876159d876..e1a6c9d5c14e01d36af2312c7a33a7e6b8da0525 100644
--- a/buildconfig/CMake/Packaging/launch_mantidplot.sh.in
+++ b/buildconfig/CMake/Packaging/launch_mantidplot.sh.in
@@ -13,9 +13,16 @@ INSTALLDIR=$(dirname $INSTALLDIR) # root install directory
 
 @VIRTUAL_GL_WRAPPER@
 
+# Define where python libraries are. ParaView paths are added by MantidPlot itself
+LOCAL_PYTHONPATH=@LOCAL_PYPATH@
+if [ -n "${PYTHONPATH}" ]; then
+    LOCAL_PYTHONPATH=${LOCAL_PYTHONPATH}:${PYTHONPATH}
+fi
+
 @GDB_DEFINITIONS@
 
 # Launch
 LD_PRELOAD=${LOCAL_PRELOAD} TCMALLOC_RELEASE_RATE=${TCM_RELEASE} \
     TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=${TCM_REPORT} \
-    @WRAPPER_PREFIX@$VGLRUN $GDB $INSTALLDIR/bin/@MANTIDPLOT_EXEC@ $*@WRAPPER_POSTFIX@ || @PYTHON_EXECUTABLE@ @SCRIPTSDIR@/@ERROR_CMD@
+    PYTHONPATH=${LOCAL_PYTHONPATH} \
+    @WRAPPER_PREFIX@$VGLRUN $GDB $INSTALLDIR/bin/@MANTIDPLOT_EXEC@ "$@"@WRAPPER_POSTFIX@ || @PYTHON_EXECUTABLE@ @SCRIPTSDIR@/@ERROR_CMD@
diff --git a/buildconfig/CMake/Packaging/launch_mantidworkbench.sh.in b/buildconfig/CMake/Packaging/launch_mantidworkbench.sh.in
index 476e533daac4a1a18b2615862db746548e937faf..ffe62a59252757c331116a79f06f5638f6b9c5a2 100644
--- a/buildconfig/CMake/Packaging/launch_mantidworkbench.sh.in
+++ b/buildconfig/CMake/Packaging/launch_mantidworkbench.sh.in
@@ -24,4 +24,4 @@ fi
 LD_PRELOAD=${LOCAL_PRELOAD} TCMALLOC_RELEASE_RATE=${TCM_RELEASE} \
     TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=${TCM_REPORT} \
     PYTHONPATH=${LOCAL_PYTHONPATH} \
-    @WRAPPER_PREFIX@$VGLRUN $GDB @PYTHON_EXECUTABLE@ $INSTALLDIR/bin/@MANTIDWORKBENCH_EXEC@ $*@WRAPPER_POSTFIX@ || @PYTHON_EXECUTABLE@ @SCRIPTSDIR@/@ERROR_CMD@
+    @WRAPPER_PREFIX@$VGLRUN $GDB @PYTHON_EXECUTABLE@ $INSTALLDIR/bin/@MANTIDWORKBENCH_EXEC@ "$@"@WRAPPER_POSTFIX@ || @PYTHON_EXECUTABLE@ @SCRIPTSDIR@/@ERROR_CMD@
diff --git a/buildconfig/CMake/Packaging/mantidpython.in b/buildconfig/CMake/Packaging/mantidpython.in
index c96304ffa464f047d88ec95a74f6dde1d20230bd..99011c8f4eacf221ff3b661e8b94e9d61df61c6c 100755
--- a/buildconfig/CMake/Packaging/mantidpython.in
+++ b/buildconfig/CMake/Packaging/mantidpython.in
@@ -34,18 +34,19 @@ fi
 
 if [ -n "$1" ] && [ "$1" = "--classic" ]; then
     shift
-    set -- @WRAPPER_PREFIX@@PYTHON_EXECUTABLE@ $*@WRAPPER_POSTFIX@
+    set -- @WRAPPER_PREFIX@@PYTHON_EXECUTABLE@ "$@"@WRAPPER_POSTFIX@
 elif [ -n "$1" ] && [ -n "$2" ] && [ "$1" = "-n" ]; then
     ranks=$2
     shift 2
-    set -- mpirun -n $ranks @WRAPPER_PREFIX@@PYTHON_EXECUTABLE@ $*@WRAPPER_POSTFIX@
+    set -- mpirun -n $ranks @WRAPPER_PREFIX@@PYTHON_EXECUTABLE@ "$@"@WRAPPER_POSTFIX@
 else
     IPYTHON_STARTUP="import IPython;IPython.start_ipython()"
-    set -- @WRAPPER_PREFIX@@PYTHON_EXECUTABLE@ -c "${IPYTHON_STARTUP}" $*@WRAPPER_POSTFIX@
+    set -- @WRAPPER_PREFIX@@PYTHON_EXECUTABLE@ -c "${IPYTHON_STARTUP}" "$@"@WRAPPER_POSTFIX@
 fi
 
 LD_PRELOAD=${LOCAL_PRELOAD} TCMALLOC_RELEASE_RATE=${TCM_RELEASE} \
     TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=${TCM_REPORT} \
+    @MTD_PATH_DEFINITION@ \
     PYTHONPATH=${LOCAL_PYTHONPATH} \
     QT_API=${LOCAL_QT_API} \
     LD_LIBRARY_PATH=${LOCAL_LDPATH} \
diff --git a/buildconfig/CMake/Packaging/osx/MantidWorkbench_osx_launcher b/buildconfig/CMake/Packaging/osx/MantidWorkbench_osx_launcher
new file mode 100644
index 0000000000000000000000000000000000000000..4e7dd2cd6a509e121a63b18f1176653412ce30b2
--- /dev/null
+++ b/buildconfig/CMake/Packaging/osx/MantidWorkbench_osx_launcher
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+INSTALLDIR=$(cd "$(dirname "$0")"; pwd)
+PLUGIN_DIR=$INSTALLDIR/../PlugIns
+cd $INSTALLDIR
+QT_PLUGIN_PATH=$PLUGIN_DIR ./workbench-script $* || /usr/bin/python ../../scripts/ErrorReporter/error_dialog_app.py --exitcode=$? --directory=$INSTALLDIR --qtdir=$PLUGIN_DIR
diff --git a/buildconfig/CMake/Packaging/osx/Mantid_osx_launcher b/buildconfig/CMake/Packaging/osx/Mantid_osx_launcher.in
similarity index 71%
rename from buildconfig/CMake/Packaging/osx/Mantid_osx_launcher
rename to buildconfig/CMake/Packaging/osx/Mantid_osx_launcher.in
index 985067c5c32221b4e5830eb863c610bb68966a26..de20a831c4345aac0149cb9d54b73dcecc98a3ed 100755
--- a/buildconfig/CMake/Packaging/osx/Mantid_osx_launcher
+++ b/buildconfig/CMake/Packaging/osx/Mantid_osx_launcher.in
@@ -2,4 +2,4 @@
 
 INSTALLDIR=$(cd "$(dirname "$0")"; pwd)
 cd $INSTALLDIR
-./MantidPlot || /usr/bin/python ../../scripts/ErrorReporter/error_dialog_app.py --exitcode=$1 --directory=$INSTALLDIR --qtdir=$INSTALLDIR/../PlugIns
+./MantidPlot || /usr/bin/python ../../scripts/ErrorReporter/error_dialog_app.py --exitcode=$? --directory=$INSTALLDIR --qtdir=$INSTALLDIR/../PlugIns
diff --git a/buildconfig/CMake/Packaging/osx/mantidworkbench_Info.plist b/buildconfig/CMake/Packaging/osx/mantidworkbench_Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..7802a29feeac99fd5ac6d8e66005ec4fa3e79459
--- /dev/null
+++ b/buildconfig/CMake/Packaging/osx/mantidworkbench_Info.plist
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict>
+    <key>CFBundlePackageType</key>              <string>APPL</string>
+    <key>CFBundleInfoDictionaryVersion</key>    <string>6.0</string>
+    <key>CFBundleName</key>                     <string>MantidWorkbench</string>
+    <key>CFBundleExecutable</key>               <string>MantidWorkbench</string>
+    <key>CFBundleIconFile</key>                 <string>MantidWorkbench.icns</string>
+    <key>CFBundleIdentifier</key>               <string>appified.$USER.MantidWorkbench</string>
+    <key>CFBundleVersion</key>                  <string>0.1</string>
+    <key>CFBundleGetInfoString</key>            <string>0.1 launch the Mantid Workbench</string>
+    <key>CFBundleShortVersionString</key>       <string>0.1</string>
+</dict></plist>
+
diff --git a/buildconfig/CMake/PythonPackageTargetFunctions.cmake b/buildconfig/CMake/PythonPackageTargetFunctions.cmake
index f363007fa0ffd2a9c318bc7a0b4bfbdc91320921..542c35dfe122eb94e4cd49682299291c2d1bfd22 100644
--- a/buildconfig/CMake/PythonPackageTargetFunctions.cmake
+++ b/buildconfig/CMake/PythonPackageTargetFunctions.cmake
@@ -72,17 +72,18 @@ CustomInstallLib = patch_setuptools_command('install_lib')
     install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} ${_setup_py} install -O1 --single-version-externally-managed --root=${_setup_py_build_root}/install --install-scripts=bin --install-lib=lib WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})")
 
     # Specify the installation directory based on OS
-    if ( WIN32 )
+    if ( WIN32 OR APPLE)
       # The / after lib tells cmake to copy over the _CONTENTS_ of the lib directory
       # placing the installed files inside the DESTINATION folder. This copies the
       # installed Python package inside the bin directory of Mantid's installation
       set ( _package_source_directory ${_setup_py_build_root}/install/lib/ )
-      set ( _package_install_destination bin )
+      set ( _package_install_destination ${WORKBENCH_BIN_DIR} )
     else ()
       # NOTE the lack of slash at the end - this means the _whole_ lib directory will be moved
       set ( _package_source_directory ${_setup_py_build_root}/install/lib )
       set ( _package_install_destination . )
     endif ()
+
     # Registers the "installed" components with CMake so it will carry them over
     install(DIRECTORY ${_package_source_directory}
             DESTINATION ${_package_install_destination}
@@ -93,8 +94,10 @@ CustomInstallLib = patch_setuptools_command('install_lib')
         # On UNIX systems install the workbench executable directly.
         # The Windows case is handled with a custom startup script installed in WindowsNSIS
         if ( NOT WIN32 )
-          install(PROGRAMS ${_setup_py_build_root}/install/bin/${_executable_name} DESTINATION bin)
+            install(PROGRAMS ${_setup_py_build_root}/install/bin/${pkg_name}
+                DESTINATION ${WORKBENCH_BIN_DIR}
+                RENAME workbench-script)
         endif()
-    endif()
   endif()
+endif()
 endfunction ()
diff --git a/buildconfig/CMake/QtTargetFunctions.cmake b/buildconfig/CMake/QtTargetFunctions.cmake
index 056ad2ce5073e6bf65d0d733f34ff3bbe6b8bdfd..c4d874e7e2b37c164a0f56dbf65bed3eb450f257 100644
--- a/buildconfig/CMake/QtTargetFunctions.cmake
+++ b/buildconfig/CMake/QtTargetFunctions.cmake
@@ -93,11 +93,10 @@ endfunction()
 function (mtd_add_qt_target)
   set (options LIBRARY EXECUTABLE NO_SUFFIX EXCLUDE_FROM_ALL)
   set (oneValueArgs
-    TARGET_NAME OUTPUT_NAME QT_VERSION OUTPUT_DIR_BASE OUTPUT_SUBDIR
-    INSTALL_DIR INSTALL_DIR_BASE PRECOMPILED)
+    TARGET_NAME OUTPUT_NAME QT_VERSION OUTPUT_DIR_BASE OUTPUT_SUBDIR PRECOMPILED)
   set (multiValueArgs SRC UI MOC
     NOMOC RES DEFS QT4_DEFS QT5_DEFS INCLUDE_DIRS SYSTEM_INCLUDE_DIRS LINK_LIBS
-    QT4_LINK_LIBS QT5_LINK_LIBS MTD_QT_LINK_LIBS OSX_INSTALL_RPATH LINUX_INSTALL_RPATH)
+    QT4_LINK_LIBS QT5_LINK_LIBS MTD_QT_LINK_LIBS OSX_INSTALL_RPATH LINUX_INSTALL_RPATH INSTALL_DIR INSTALL_DIR_BASE)
   cmake_parse_arguments (PARSED "${options}" "${oneValueArgs}"
                          "${multiValueArgs}" ${ARGN})
   if (PARSED_UNPARSED_ARGUMENTS)
@@ -211,20 +210,31 @@ function (mtd_add_qt_target)
   if ( PARSED_EXCLUDE_FROM_ALL )
     set_target_properties ( ${_target} PROPERTIES EXCLUDE_FROM_ALL TRUE )
   else ()
+
     if (PARSED_INSTALL_DIR AND PARSED_INSTALL_DIR_BASE)
       message ( FATAL "Cannot provide both INSTALL_DIR and PARSED_INSTALL_DIR_BASE options" )
     endif()
 
     if (PARSED_INSTALL_DIR)
-      set ( _install_dir ${PARSED_INSTALL_DIR} )
+      list(REMOVE_DUPLICATES PARSED_INSTALL_DIR)
+      set ( _install_dirs ${PARSED_INSTALL_DIR} )
     elseif (PARSED_INSTALL_DIR_BASE)
-      _append_qt_suffix (AS_DIR VERSION ${PARSED_QT_VERSION} OUTPUT_VARIABLE _install_dir
-                         ${PARSED_INSTALL_DIR_BASE})
+      list(REMOVE_DUPLICATES PARSED_INSTALL_DIR_BASE)
+
+      set (_install_dirs)
+      foreach( _dir ${PARSED_INSTALL_DIR_BASE} )
+          _append_qt_suffix (AS_DIR VERSION ${PARSED_QT_VERSION} OUTPUT_VARIABLE _dir
+                             ${PARSED_INSTALL_DIR_BASE})
+          list( APPEND _install_dirs ${_dir})
+      endforeach ()
     else()
-      set ( _install_dir "" )
+      set ( _install_dirs "" )
       message ( FATAL_ERROR "Target: ${_target} is configured to build but has no install destination" )
     endif()
-    mtd_install_qt_library ( ${PARSED_QT_VERSION} ${_target} "${SYSTEM_PACKAGE_TARGET}" ${_install_dir} )
+
+    foreach( _dir ${_install_dirs})
+        mtd_install_qt_library ( ${PARSED_QT_VERSION} ${_target} "${SYSTEM_PACKAGE_TARGET}" ${_dir} )
+    endforeach ()
   endif ()
 
   # Group into folder for VS
diff --git a/buildconfig/CMake/TargetFunctions.cmake b/buildconfig/CMake/TargetFunctions.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..98881263cbe985482136c01ecd37f387a6fcfc22
--- /dev/null
+++ b/buildconfig/CMake/TargetFunctions.cmake
@@ -0,0 +1,46 @@
+function (mtd_install_targets)
+  set (options)
+  set (oneValueArgs TARGETS)
+  set (multiValueArgs INSTALL_DIRS)
+  cmake_parse_arguments (PARSED "${options}" "${oneValueArgs}"
+                         "${multiValueArgs}" ${ARGN})
+
+    if (NOT PARSED_INSTALL_DIRS)
+        message(FATAL_ERROR "Empty argument INSTALL_DIRS")
+        return()
+    endif()
+    if (NOT PARSED_TARGETS)
+        message(FATAL_ERROR "Empty argument TARGETS")
+        return()
+    endif()
+
+    list(REMOVE_DUPLICATES PARSED_INSTALL_DIRS)
+    set ( _install_dirs ${PARSED_INSTALL_DIRS} )
+
+    foreach( _dir ${_install_dirs})
+        install ( TARGETS ${PARSED_TARGETS} ${SYSTEM_PACKAGE_TARGET} DESTINATION ${_dir} )
+    endforeach ()
+endfunction()
+
+function (mtd_install_files)
+  set (options)
+  set (oneValueArgs RENAME)
+  set (multiValueArgs FILES INSTALL_DIRS)
+  cmake_parse_arguments (PARSED "${options}" "${oneValueArgs}"
+                         "${multiValueArgs}" ${ARGN})
+
+    if (NOT PARSED_INSTALL_DIRS)
+        message(FATAL_ERROR "Empty argument INSTALL_DIRS")
+        return()
+    endif()
+    if (NOT PARSED_FILES)
+        message(FATAL_ERROR "Empty argument FILES")
+        return()
+    endif()
+     list(REMOVE_DUPLICATES PARSED_INSTALL_DIRS)
+
+     foreach( _dir ${PARSED_INSTALL_DIRS})
+         install ( FILES ${PARSED_FILES} DESTINATION ${_dir} RENAME ${PARSED_RENAME} )
+     endforeach ()
+endfunction()
+
diff --git a/buildconfig/CMake/WindowsNSIS.cmake b/buildconfig/CMake/WindowsNSIS.cmake
index ce43ee33addeee0b2eb717ff5e69458278832480..ceacfc6d6730610b5bd3b3dde6ff4eec11fd7626 100644
--- a/buildconfig/CMake/WindowsNSIS.cmake
+++ b/buildconfig/CMake/WindowsNSIS.cmake
@@ -166,7 +166,7 @@ if ( ENABLE_WORKBENCH AND PACKAGE_WORKBENCH )
     execute_process(COMMAND powershell.exe -version 2.0 -noprofile -windowstyle hidden -ExecutionPolicy Bypass ${THIRD_PARTY_DIR}/bin/ps2exe.ps1 -inputFile ${CMAKE_CURRENT_SOURCE_DIR}/buildconfig/CMake/Packaging/${_workbench_base_name}.ps1 -outputFile ${CMAKE_CURRENT_BINARY_DIR}/${_workbench_executable_install_name} -x64 -runtime2 -noconsole RESULT_VARIABLE _workbench_powershell_return_code OUTPUT_VARIABLE _workbench_powershell_output)
 
     # If the EXE generation failed then display an error and stop the CMAKE generation
-    if ( _workbench_powershell_return_code GREATER 0 )
+    if ( NOT _workbench_powershell_return_code EQUAL 0 )
       message(STATUS ${_workbench_powershell_output})
       message(FATAL_ERROR "Generating the Workbench executable encountered an error.")
     endif ()
diff --git a/buildconfig/Jenkins/buildscript b/buildconfig/Jenkins/buildscript
index 2bdc3fb258f0a12518c68ad58f82da60eb3ac3a9..60cd839dc019799c52ea8cc76019aee533037e66 100755
--- a/buildconfig/Jenkins/buildscript
+++ b/buildconfig/Jenkins/buildscript
@@ -251,7 +251,7 @@ if [[ ${DO_BUILD_PKG} == true ]]; then
   PACKAGINGVARS="-DPACKAGE_DOCS=ON"
   # Set some variables relating to the linux packages
   if [[ "${ON_MACOS}" == true ]]; then
-    PACKAGINGVARS="${PACKAGINGVARS} -DPACKAGE_WORKBENCH=OFF -DCPACK_PACKAGE_SUFFIX="
+    PACKAGINGVARS="${PACKAGINGVARS} -DPACKAGE_WORKBENCH=ON -DCPACK_PACKAGE_SUFFIX="
   else
     PACKAGINGVARS="${PACKAGINGVARS} -DPACKAGE_WORKBENCH=ON"
     # Use different suffix for linux builds if parameter is not defined
@@ -363,6 +363,16 @@ fi
 # Prevent race conditions when creating the user config directory
 userconfig_dir=$HOME/.mantid
 rm -fr $userconfig_dir
+# Remove GUI qsettings files
+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
+fi
+rm -f ~/.config/mantidproject/mantidworkbench.ini
+
 mkdir -p $userconfig_dir
 # use a fixed number of openmp threads to avoid overloading the system
 userprops_file=$userconfig_dir/Mantid.user.properties
diff --git a/buildconfig/Jenkins/buildscript.bat b/buildconfig/Jenkins/buildscript.bat
index 892b6c5b31d20b829b13dbbf1709240b671514b2..402a0db2907f86067cc4be73b1fc70a85aff3231 100755
--- a/buildconfig/Jenkins/buildscript.bat
+++ b/buildconfig/Jenkins/buildscript.bat
@@ -201,9 +201,10 @@ if ERRORLEVEL 1 exit /B %ERRORLEVEL%
 :: This prevents race conditions when creating the user config directory
 set USERPROPS=bin\%BUILD_CONFIG%\Mantid.user.properties
 del %USERPROPS%
-set CONFIGDIR=%APPDATA%\mantidproject\mantid
+set CONFIGDIR=%APPDATA%\mantidproject
 rmdir /S /Q %CONFIGDIR%
-mkdir %CONFIGDIR%
+:: Create the directory to avoid any race conditions
+mkdir %CONFIGDIR%\mantid
 :: use a fixed number of openmp threads to avoid overloading the system
 echo MultiThreaded.MaxCores=2 > %USERPROPS%
 
diff --git a/buildconfig/Jenkins/systemtests b/buildconfig/Jenkins/systemtests
index fe04694e0c2436bc9593f4188a4afb29beae0213..4fdda64ac74231a227b5113648b9b31eb2ff2496 100755
--- a/buildconfig/Jenkins/systemtests
+++ b/buildconfig/Jenkins/systemtests
@@ -50,6 +50,11 @@ fi
 [ -d $WORKSPACE/build ] || mkdir $WORKSPACE/build
 cd $WORKSPACE/build
 
+# Remove (possibly) stale files
+#   build/ExternalData/**: data files will change over time and removing
+#                          the links helps keep it fresh
+rm -rf $WORKSPACE/build/ExternalData
+
 ###############################################################################
 # CMake configuration if it has not already been configured.
 # We use the special flag that only creates the targets for the data
@@ -69,10 +74,22 @@ ${CMAKE_EXE} --build . -- SystemTestData
 ###############################################################################
 # Run the tests
 ###############################################################################
-# Remove any Mantid.user.properties file
-userprops=~/.mantid/Mantid.user.properties
-rm -f $userprops
+# Remove any user settings
+userconfig_dir=$HOME/.mantid
+rm -fr $userconfig_dir
+# Remove GUI qsettings files
+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
+fi
+rm -f ~/.config/mantidproject/mantidworkbench.ini
+
 # Turn off any auto updating on startup
+mkdir -p $userconfig_dir
+userprops=$userconfig_dir/Mantid.user.properties
 echo "UpdateInstrumentDefinitions.OnStartup = 0" > $userprops
 echo "usagereports.enabled = 0" >> $userprops
 echo "CheckMantidVersion.OnStartup = 0" >> $userprops
diff --git a/buildconfig/Jenkins/systemtests.bat b/buildconfig/Jenkins/systemtests.bat
index 02c2fd894afe31157b2b62b15ac4aa4659ea31bb..1034ccc99c090427eb684375e04a07417871121b 100755
--- a/buildconfig/Jenkins/systemtests.bat
+++ b/buildconfig/Jenkins/systemtests.bat
@@ -34,6 +34,11 @@ if NOT DEFINED MANTID_DATA_STORE (
 md %WORKSPACE%\build
 cd %WORKSPACE%\build
 
+:: Remove (possibly) stale files
+::   build/ExternalData/**: data files will change over time and removing
+::                          the links helps keep it fresh
+rmdir /S /Q %WORKSPACE%\build\ExternalData
+
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: CMake configuration if it has not already been configured.
 :: We use the special flag that only creates the targets for the data
@@ -62,6 +67,12 @@ if ERRORLEVEL 1 exit /b %ERRORLEVEL%
 set USERPROPS_RELEASE=C:\MantidInstall\bin\Mantid.user.properties
 set USERPROPS_NIGHTLY=C:\MantidNightlyInstall\bin\Mantid.user.properties
 del /Q %USERPROPS_RELEASE% %USERPROPS_NIGHTLY%
+:: Remove user settings
+set CONFIGDIR=%APPDATA%\mantidproject
+rmdir /S /Q %CONFIGDIR%
+:: Create the directory to avoid any race conditions
+mkdir %CONFIGDIR%\mantid
+
 :: Turn off any auto updating on startup
 echo UpdateInstrumentDefinitions.OnStartup = 0 > %USERPROPS_RELEASE%
 echo usagereports.enabled = 0 >> %USERPROPS_RELEASE%
diff --git a/buildconfig/windows/pycharm.env.in b/buildconfig/windows/pycharm.env.in
new file mode 100644
index 0000000000000000000000000000000000000000..0c4ff94018fe1dae01de96f7c4ce9b73797f6921
--- /dev/null
+++ b/buildconfig/windows/pycharm.env.in
@@ -0,0 +1,7 @@
+THIRD_PARTY_DIR=@THIRD_PARTY_DIR@
+QT4_BIN=${THIRD_PARTY_DIR}\lib\qt4\bin;${THIRD_PARTY_DIR}\lib\qt4\lib
+QT5_BIN=${THIRD_PARTY_DIR}\lib\qt5\bin;${THIRD_PARTY_DIR}\lib\qt5\lib
+QT_QPA_PLATFORM_PLUGIN_PATH=${THIRD_PARTY_DIR}\lib\qt5\plugins
+PYTHONHOME=${THIRD_PARTY_DIR}\lib\python2.7
+MISC_BIN=${THIRD_PARTY_DIR}\bin;${THIRD_PARTY_DIR}\bin\mingw
+PATH=${MISC_BIN};${PYTHONHOME};${QT5_BIN};${QT4_BIN};${PATH}
\ No newline at end of file
diff --git a/dev-docs/source/GettingStartedWithPyCharm.rst b/dev-docs/source/GettingStartedWithPyCharm.rst
index 6169cefea8ec0bf1891d458682a6502e4b0a96cb..d646592528161edda3b45e93c33b169d380a0d3d 100644
--- a/dev-docs/source/GettingStartedWithPyCharm.rst
+++ b/dev-docs/source/GettingStartedWithPyCharm.rst
@@ -10,11 +10,11 @@ This tutorial assumes you are familiar with the process of building Mantid (with
 .. contents::
   :local:
 
+.. _setting-up-pycharm-on-windows:
+
 Setting up PyCharm on Windows
 #############################
 
-PyCharm should be opened using ``pycharm.bat`` which can be found in the build directory (this sets some additional environment variables compared with simply opening PyCharm directly).
-
 1. Once PyCharm is open, set up the project. Go to ``File->Open`` and select the root directory in which both your source and build directories reside.
 
    Go to ``File->Settings``, then under ``Project`` you will set two sub-menus ``Project Interpreter`` and ``Project Structure``. The interpreter defines the python executable that will be used to run your code, and the structure menu allows you to decide which folders within the project to include and index.
@@ -29,14 +29,20 @@ PyCharm should be opened using ``pycharm.bat`` which can be found in the build d
 
 3. In the ``Project Structure`` sub menu you should see your root directory with the source/build directories both visible (if not, add them). The folder structure should be present in the centre of the window allowing you to mark folders orange (excluded) or blue (source). Source directories will be searched for python code.
 
-   Within the source directory add the following to your sources;
+   Within the source directory add the following to your sources:
 
    .. code-block:: sh
 
        <Mantid Source Directory>/scripts
+       <Mantid Source Directory>/qt/applications/workbench
+       <Mantid Source Directory>/qt/widgets
+       <Mantid Source Directory>/qt/python
        <Mantid Source Directory>/external/src/ThirdParty/lib
 
-   The first folder can be replaced with the folder that contains your code, if you aren't writing code in ``scripts/``. In the Mantid build directory add the following as source folders;
+
+   The first folder can be replaced with the folder that contains your code, if you aren't writing code in ``scripts/``.
+
+   Additionally, in the Mantid build directory add the following as source folders:
 
    .. code-block:: sh
 
@@ -44,32 +50,74 @@ PyCharm should be opened using ``pycharm.bat`` which can be found in the build d
 
    here we are setting up PyCharm for the Debug build, you would use ``/bin/Release`` instead if you are building mantid in release mode.
 
+4. The environment needs to be set up before running the configuration. Follow the instructions below to use either the EnvFile plugin (recommended) or manual path setup.
+
 NOTE : In some cases, imports in the code will still be highlighted red when they come from folders within the ``script/`` folder, or from other folders entirely. To fix this simply add the relevant folder that contains the module you are importing in the same fashion as step 3 above.
 
-Running Files in the Debugger
------------------------------
+.. _running-file-debug-with-envfile-extension:
+
+Running Files in the Debugger with EnvFile extension
+####################################################
 
 Running python code from within PyCharm which depends on the python API, or PyQt for example requires one extra step. Because the source root labelling from the previous section only affects PyCharm searching and not the run configuration, before running the file we must set up the run configuration correctly.
 
-As an example, create a new file in ``<Mantid Source Directory>/scripts/`` called ``test.py``. Copy into it the Python code below.
+4. Install the EnvFile plugin by Borys Pierov. The plugin can be installed in multiple ways:
+
+   a) Open Settings(CTRL + SHIFT + S), to go Plugins and search for ``EnvFile``. Install and restart PyCharm.
+   b) Go to the plugin's `webpage <https://plugins.jetbrains.com/plugin/7861-envfile>`_, download and install it.
+
+5. To edit the configurations go to Run->Run... and select Edit Configurations. Notice that there is now a ``EnvFile`` tab under the configuration's name.
+   - Note that you have to do that for each configuration, or you can change the template configuration, and all configuration that use that template will have the EnvFile setup.
+6. Open the ``EnvFile`` tab, check ``Enable EnvFile`` and ``Substitute Environmental Variables (...)`` - this allows setting up the third-party paths dynamically.
+7. Click the ``+`` (plus) on the right side, select the ``pycharm.env`` file in the root of the **build** directory.
+
+For running the Workbench continue onto :ref:`Workbench`, and follow the instructions to set up the *Script Path* and *Working Directory*.
+
+Advantages of this approach:
+
+- You can have multiple instances of PyCharm running with environment configuration for separate repositories. This is otherwise not possible, as all PyCharm instances seem to share a parent process and environment. (as is the case of 11/01/2019, it might change in the future)
+- This makes possible switching projects for multiple repositories via the File > Open Recent ... menu, as when the new project is opened its environment won't be poluted with environment variables from the last one.
+
+  - This can cause errors when the external dependencies aren't quite the same between all the repositories, as some packages might be missing, or be different versions.
+
+Disadvantages:
+
+- Additional setup for each configuration necessary. Thankfully, if the template is edited to have the correct ``EnvFile`` setup, all copies of it will have it too. Copying an already existing configuration also copies the ``EnvFile`` setup.
+
+
+Running Files in the Debugger without EnvFile extension
+#######################################################
+
 
-4. To edit the configurations go to ``Run->Run...`` and select ``Edit Configurations``. This should open up a sub window. Hit the green ``+`` in the top left to create a new configuration and name it. In order to tell PyCharm where to look for python modules and libraries we need to add some folders to the ``PATH`` environment variable. Click on the ``...`` next to the *Environment Variables* box, and hit the ``+`` icon. In the Name column enter "PATH", in the value column enter the following;
+This can be done in two ways:
+
+- Open PyCharm using ``pycharm.bat`` which can be found in the build directory (this sets some additional environment variables compared with simply opening PyCharm directly).
+
+  - This is preferred if you only have 1 repository with which PyCharm is used. If you need to use PyCharm on multiple repositories, it is recommended that you use the EnvFile extension.
+
+- To edit the configurations go to ``Run->Run...`` and select ``Edit Configurations``. This should open up a sub window. Hit the green ``+`` in the top left to create a new configuration and name it. In order to tell PyCharm where to look for python modules and libraries we need to add some folders to the ``PATH`` environment variable. Click on the ``...`` next to the *Environment Variables* box, and hit the ``+`` icon. In the Name column enter "PATH", in the value column enter the following;
 
    .. code-block:: sh
 
        <Mantid Build Directory>\bin\Debug;
        <Mantid Source Directory>\external\src\ThirdParty\bin;
+       <Mantid Source Directory>\external\src\ThirdParty\bin\mingw;
+       <Mantid Source Directory>\external\src\ThirdParty\lib\python2.7;
+       <Mantid Source Directory>\external\src\ThirdParty\lib\qt5\plugins;
        <Mantid Source Directory>\external\src\ThirdParty\lib\qt4\bin;
        <Mantid Source Directory>\external\src\ThirdParty\lib\qt5\bin;
+       <Mantid Source Directory>\external\src\ThirdParty\lib\qt4\lib;
+       <Mantid Source Directory>\external\src\ThirdParty\lib\qt5\lib;
        %PATH%
 
-   The semi-colon delimited list of paths should end in ``;%PATH%`` so that we prepend to the existing list of paths rather than overwriting them. The last two lines will allow imports of PyQt4 and PyQt5 modules.
+The semi-colon delimited list of paths should end in ``;%PATH%`` so that we prepend to the existing list of paths rather than overwriting them.
 
 You should now be able to run and debug the scripts using the newly created configuration, by adding the full path of the file in the ``Script path`` box at the top of the configuration window.
 
+As an example, create a new file in ``<Mantid Source Directory>/scripts/`` called ``test.py``. Copy into it the Python code below.
 
 Testing using PyQt
-------------------
+##################
 
 To test that the above instructions have worked, you can simply create a new Python file with the following content (for PyQt5)
 
@@ -95,14 +143,48 @@ To test that the above instructions have worked, you can simply create a new Pyt
         ui.show()
         sys.exit(app.exec_())
 
+
+Local Debugging of Unit Tests with PyCharm
+##########################################
+
+This **does not** require a PyCharm Professional license for debugging, but requires additional setup for running unit tests.
+
+1. Go to your Run/Debug Configurations.
+2. Open Templates > Python tests > Unittests configuration.
+3. Set the working directory to ``<Mantid Build Dir>/bin/Debug``, for a Debug build, or ``<Mantid Build Dir>/bin/Release`` for a Release build.
+4. Add the EnvFile to the Unittests configuration, instructions in :ref:`running-file-debug-with-envfile-extension`.
+5. You should now be able to click the Run/Debug icons next to each unit test method or class to run/debug them.
+
+
+Remote Debugging of Unit Tests with PyCharm
+#############################
+
+This requires a PyCharm Professional license for the Remote Debugging feature.
+
+This approach can be used to debug unit tests. However, as the required package ``pydevd`` is not shipped with Mantid, we need to manually add it at runtime. This can be done by appending a directory that contains the installed ``pydevd`` package on the ``PYTHONPATH``. The following code does so at runtime::
+
+    PYTHON_ROOT="<Change this to point to a Python installation that has pydevd installed>"
+    # PYTHON_ROOT="c:/users/qbr77747/apps/miniconda3"
+    import os
+    import sys
+    sys.path.append(os.path.join(PYTHON_ROOT, "lib/site-packages"))
+    import pydevd
+    pydevd.settrace('localhost', port=44444, stdoutToServer=True, stderrToServer=True)
+
+
+A Remote Debugging configration needs to be setup to use the ``44444`` port (can be changed, but it needs to be reflected in the code), and running before the tests are run!
+
+The ``pydevd`` package does not have to be installed on Python 2. As of 12/11/2018 installing ``pydevd`` on a separate installation with Python 3.7, and adding the code above successfully connects.
+
+
 Setting up PyCharm on Linux
 ###########################
 
 On Linux the instructions are identical to Windows except that :
 
-- In step 1, the file is ``pycharm.sh`` rather than ``pycharm.bat``
 - In step 2, use the native python interpreter (``/usr/bin/python2.7/python.exe``) rather than from ``<Mantid Source Directory>/external/src/ThirdParty/lib/python2.7/python.exe``
-- In step 4, add ``<Mantid Build Directory>/bin;`` to the ``PATH`` environment variable in the new configuration (rather than ``<Mantid Build Directory>/bin/Debug;``), and remove the other three file paths.
+- In step 3, add ``<Mantid Build Directory>/bin;`` to the ``PATH`` environment variable in the new configuration (rather than ``<Mantid Build Directory>/bin/Debug;``), and remove the other three file paths.
+- In step 4, if you are not using the EnvFile extension, then the file is ``pycharm.sh`` rather than ``pycharm.bat``
 
 Useful Plugins
 ##############
diff --git a/dev-docs/source/Standards/AdditionalPythonCode.rst b/dev-docs/source/Standards/AdditionalPythonCode.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c984cbf5fb52ff05cdf0992f8df932fb98398776
--- /dev/null
+++ b/dev-docs/source/Standards/AdditionalPythonCode.rst
@@ -0,0 +1,57 @@
+.. _AdditionalPythonCode:
+
+======================
+Additional Python code
+======================
+
+.. contents::
+  :local:
+
+Overview
+########
+
+Python code that is not part of 'core' Mantid, e.g. is not an algorithm, not
+related to the Python exports (``mantid.*`` modules) nor an integral part of
+``workbench`` should go to either the ``scripts`` directory or ``qt``
+directory.
+
+``scripts`` is a place to put non-GUI modules and scripts such as pure Python 
+reduction frameworks or technique specific plotting modules which are 
+desirable to be included in an official release. The code in ``scripts`` can 
+be included in the automatic unit testing machinery making it possible to 
+avoid breakages due to changes in Mantid's core parts. Although some reduction 
+interface code lives in ``scripts``, adding more GUI related code there is 
+discouraged. 
+
+Python interfaces should go to the ``qt`` directory.
+
+The ``scripts`` directory
+#########################
+
+Python code should be written as proper `modules 
+<https://docs.python.org/3/tutorial/modules.html>`_. For example, the code of 
+a module called ``spam`` should go into ``scripts/spam`` and contain a file 
+named ``__init__.py`` at minimum. Then the module can be imported into Mantid 
+simply by ``import spam``.
+
+Documentation
+-------------
+
+Documentation can be added to Mantid's Python API docs in ``docs/source/api`` 
+and linked to ``docs/source/api/index.rst``. Note, that it is possible to 
+import Python's docstrings in ``.rst`` files using directives such as ``.. 
+automodule::`` or ``.. autoclass::``, see `here 
+<http://www.sphinx-doc.org/es/stable/ext/autodoc.html>`_.
+
+Unit testing
+------------
+
+Unit tests for each module should be added to ``scripts/test/modulename``. The 
+tests follow the :ref:`standard Mantid unit testing practices 
+<UnitTestGoodPractice>`.
+
+The ``qt`` directory
+####################
+
+There are no official rules for ``qt`` yet. Follow the structure of already
+existing code there.
diff --git a/dev-docs/source/Standards/CPPStandards.rst b/dev-docs/source/Standards/CPPStandards.rst
index 89a81e6236a1b36414f0812b61c0ad6789c411b5..7c4b19d8e181b3ab7fdb788307ba4f886b40c3d3 100644
--- a/dev-docs/source/Standards/CPPStandards.rst
+++ b/dev-docs/source/Standards/CPPStandards.rst
@@ -280,20 +280,16 @@ Expressions and Statements
       with side effects as the right-operand of ``&&`` or ``||``. Any such
       instances must be commented in detail to alert other developers to
       the fact that the function is not always called.
-4. ``for`` and ``while`` loops should not use ``break`` and
-   ``continue`` where they can be avoided. Where they are required,
-   comments should draw attention to them as an alternative exit point
-   from the loop.
-5. A ``for`` loop should only have one control variable, and should
+4. A ``for`` loop should only have one control variable, and should
    not modify it in the body.
-6. ``switch`` statements must include a ``default`` clause, even if
+5. ``switch`` statements must include a ``default`` clause, even if
    only to catch errors.
-7. Each ``case`` of a ``switch`` statement must either end with a
+6. Each ``case`` of a ``switch`` statement must either end with a
    ``break``/``return``, or contain a clear comment to alert other
    developers to the fact that execution will fall through to the next
    case. Multiple ``case`` labels (with no code between them) are,
    however, permitted for the same block of code.
-8. ``goto`` must be avoided. When there is a need to break out of two
+7. ``goto`` must be avoided. When there is a need to break out of two
    or more nested loops in one go, the loops should be moved to a
    separate function where 'return' can be used instead.
 
diff --git a/dev-docs/source/Standards/index.rst b/dev-docs/source/Standards/index.rst
index 20709a3f984ff07e6e82d93b1a044750509481ac..55bf344979fc1c54fa4f06e0e559bf37e9d6d0b9 100644
--- a/dev-docs/source/Standards/index.rst
+++ b/dev-docs/source/Standards/index.rst
@@ -42,3 +42,4 @@ Guidelines
    :maxdepth: 1
 
    Libraries
+   AdditionalPythonCode
diff --git a/dev-docs/source/Testing/IndirectInelastic/IndirectInelasticAcceptanceTests.rst b/dev-docs/source/Testing/IndirectInelastic/IndirectInelasticAcceptanceTests.rst
index 3871e7e6832560c73df40965418ecf1b774c027e..cd985643dd1c227a548ed9e9059df2589b889d4d 100644
--- a/dev-docs/source/Testing/IndirectInelastic/IndirectInelasticAcceptanceTests.rst
+++ b/dev-docs/source/Testing/IndirectInelastic/IndirectInelasticAcceptanceTests.rst
@@ -92,7 +92,7 @@ Data analysis Elwin
 #. Click ``Run``
 #. This should result in three new workspaces again, this time with file ranges as their name
 #. In the main GUI right-click on ``irs26174-26176_graphite002_red_elwin_eq2`` and choose ``Plot Spectrum``, choose ``Plot All``
-#. This should plot two lines of :math:`A^{-2}` vs :math:`Q`
+#. This should plot two lines of A^2 vs Q
 #. Right-click on the ``irs26176_graphite002_elwin_eq`` workspace and ``Save Nexus``; save to a location of your choice; you will use this file in the next test
 
 Data analysis MSD
@@ -193,5 +193,3 @@ Data analysis I(Q, T) Fit
 #. Select Lifetime from the ``Plot Output`` drop-down
 #. Click ``Plot Result`` this should open a new plot with the lifetimes plotted
   
-
-
diff --git a/dev-docs/source/Testing/VSI/VSITesting.rst b/dev-docs/source/Testing/VSI/VSITesting.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2063beb03abe647bf09e81c4e022901bc258c68c
--- /dev/null
+++ b/dev-docs/source/Testing/VSI/VSITesting.rst
@@ -0,0 +1,61 @@
+.. _vsi_testing:
+
+VSI Testing
+=============
+
+.. contents::
+   :local:
+
+
+*Preparation*
+
+- Get the scripts from the Mantid repository `this directory <https://github.com/mantidproject/mantid/tree/master/scripts/Vates>`_.
+- Make sure that you have the Mantid system test data. You can do this by building a SystemTestData target of Mantid.
+- Put the system test data directory in your Mantid user directories
+
+
+**Time required 20-30 minutes**
+
+--------------
+
+#. Load the script SXD_NaCl.py in MantidPlot. Run it.
+#. This should load a workspace called QLab. Right-click this workspace and choose 'Show Vates simple interface'.
+#. This should open the VSI window. Like the picture below. Check that all tabs are present.
+
+.. figure:: ../../images/vsi.png
+   :alt: alternate text
+   :align: right
+   
+#. Click the various tabs making sure that the interface does not crash. 
+#. Make sure all tabs have a tool tip when you hover over them.
+#. Go into three slice mode (the star shaped tab at the top).
+#. Play with the maximum and minimum parameters at the top of the plot, make sure the colours change in the plot.
+#. Hover on each of the upper two plots and the lower left plot. While hovering over a plot use the roll bar (or equivalent), the slices in the other plots should update. The lower right plot should show all three slices. Check that the text on the plots is legible and updates appropriately.
+#. Switch to multi-slice view (just to the left of three slice view).
+
+   #. In the properties tab (left of the plot) select the Display menu.
+   #.  Choose all options for Representation, make sure they work.
+   #. Change the Styling and Lighting sliders, make sure the picture updates.
+   #. Next to Data Axes Grid click edit, this should open a new dialog of settings for the plot.
+   #. Change some of the properties in this dialog and click apply, the plot should update accordingly.
+   #. Make sure that the sliders for the plot work. These are the triangles on the scales above and to the left of the plot. Slide them and move through the plot.
+   #. On the slider scale Shift+Right-click should add another slice
+   #. Select the View menu.
+   #. Turn on/off the orientation axes visibility, check that the little axes icon on the plot turns on and off.
+   #. Check and uncheck the parallel camera box, make sure the view switches between perspective on and off.
+
+#. Switch to standard view (left of multi-slice)
+
+   #. In the Rebin drop-down (above the plot) select Fast Rebin and click Slice - a new MDScaleWorkspace should appear in the list
+   #. In the Properties tab select Properties and change the X/Y/Z scaling factors, click Apply. The plot should distort accordingly. 
+   #. In the properties tab click Delete and the plot alterations should be removed from the plot.
+   #. Repeat the previous three steps for the different Rebin options.  
+
+#. Close the VSI interface and then reopen as before. You are now in scatter plot mode.
+
+   #. Under Properties, change the Number of Points and Top Percentile settings and see that the plot changes.
+   #. Drag the peaks_qLab workspace from the main GUI onto VSI. This should show the peak position in the plot as points.
+   #. Drag the peaks_qLab_integrated workspace from the main GUI onto VSI. This should show the peak positions in the plot as spheres.
+
+#. Close the VSI GUI.
+
diff --git a/dev-docs/source/Testing/index.rst b/dev-docs/source/Testing/index.rst
index 8b5446cd991f14a97fe028c5f40199b194155336..679a911759d4e1d92204f3127a8ff49acc8a798d 100644
--- a/dev-docs/source/Testing/index.rst
+++ b/dev-docs/source/Testing/index.rst
@@ -24,4 +24,5 @@ creation is outlined in :ref:`issue_tracking`.
    IndirectInelastic/IndirectInelasticAcceptanceTests
    ErrorReporter-ProjectRecovery/ErrorReporterTesting
    ErrorReporter-ProjectRecovery/ProjectRecoveryTesting
+   VSI/VSITesting
 
diff --git a/dev-docs/source/Workbench.rst b/dev-docs/source/Workbench.rst
index 520fc5578aceca560cb45ec0b33c6517efbc113a..e5de7a44fa6d2301bd70a08549b773192ac16c48 100644
--- a/dev-docs/source/Workbench.rst
+++ b/dev-docs/source/Workbench.rst
@@ -30,7 +30,7 @@ using the cmake flag ``PACKAGE_WORKBENCH=ON``.
 
 Developing and debugging with PyCharm on Windows
 ################################################
-The first thing that needs to be done is creating the PyCharm project and configuring the project settings. Please follow the instructions at :ref:`GettingStartedWithPyCharm`.
+The first thing that needs to be done is creating the PyCharm project and configuring the project settings. Please follow the instructions at :ref:`setting-up-pycharm-on-windows`.
 
 After the project settings have been configured, a Run/Debug configuration needs to be created. To edit the configurations go to ``Run->Run...`` and select ``Edit Configurations``. Select ``Templates->Python``, and hit the green ``+`` in the top left.
 The necessary changes to the configuration are:
@@ -90,23 +90,3 @@ For example to run the MatrixWorkspaceDisplay:
 2. Set the Script Path to ``<Mantid Source Dir>/qt/python/mantidqt/widgets/matrixworkspaceviewer``. This package has a ``__main__.py`` file which makes it runnable.
 3. You might have to CHANGE back the working directory to ``<Mantid Build Dir>/bin/Debug``.
 4. Click OK, then running the configration should start the MatrixWorkspaceDisplay.
-
-Remote Debugging with PyCharm
------------------------------
-
-This requires a PyCharm Professional license for the Remote Debugging feature.
-
-This approach can be used to debug unit tests. However, as the required package ``pydevd`` is not shipped with Mantid, we need to manually add it at runtime. This can be done by appending a directory that contains the installed ``pydevd`` package on the ``PYTHONPATH``. The following code does so at runtime::
-
-    PYTHON_ROOT="<Change this to point to a Python installation that has pydevd installed>"
-    PYTHON_ROOT="c:/users/qbr77747/apps/miniconda3"
-    import os
-    import sys
-    sys.path.append(os.path.join(PYTHON_ROOT, "lib/site-packages"))
-    import pydevd
-    pydevd.settrace('localhost', port=44444, stdoutToServer=True, stderrToServer=True)
-
-
-A Remote Debugging configration needs to be setup to use the ``44444`` port (can be changed, but it needs to be reflected in the code), and running before the tests are run!
-
-The ``pydevd`` package does not have to be installed on Python 2. As of 12/11/2018 installing ``pydevd`` on a separate installation with Python 3.7, and adding the code above successfully connects.
\ No newline at end of file
diff --git a/dev-docs/source/images/vsi.png b/dev-docs/source/images/vsi.png
new file mode 100644
index 0000000000000000000000000000000000000000..28685e6c7c23cca0890ccb37de18b9d17dbbd182
Binary files /dev/null and b/dev-docs/source/images/vsi.png differ
diff --git a/docs/source/algorithms/AbsorptionCorrection-v1.rst b/docs/source/algorithms/AbsorptionCorrection-v1.rst
index 7cce5394108f305c9f80272d802b9ffcd95dbf1d..472811bea3ffdda3404c9fdd91e9da4f0d664562 100644
--- a/docs/source/algorithms/AbsorptionCorrection-v1.rst
+++ b/docs/source/algorithms/AbsorptionCorrection-v1.rst
@@ -112,3 +112,4 @@ Output:
 .. categories::
 
 .. sourcelink::
+   :filename: AnyShapeAbsorption
diff --git a/docs/source/algorithms/CalculateEfficiencyCorrection-v1.rst b/docs/source/algorithms/CalculateEfficiencyCorrection-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..bc31d255f76abcb02733251c35172e84a8ccf572
--- /dev/null
+++ b/docs/source/algorithms/CalculateEfficiencyCorrection-v1.rst
@@ -0,0 +1,342 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+This algorithm calculates the detection efficiency correction, :math:`\epsilon`, defined by:
+
+.. math::
+    :label: efficiency
+
+    \epsilon &= 1-e^{-\rho T \sigma (\lambda)} \\
+             &= 1-e^{-\rho_{A} \sigma (\lambda)} \\
+             &= 1-e^{-\alpha \lambda}
+
+and output correction is given as:
+
+.. math::
+    :label: efficiency_correction
+
+    \frac{1}{\epsilon} = \frac{1}{1-e^{-\alpha \lambda}}
+
+where
+
+- :math:`\rho` is the number density in atoms/:math:`\AA^3`
+- :math:`T` is a sample thickness given in cm
+- :math:`\lambda_{ref}` = 1.7982 :math:`\AA`,
+- :math:`\sigma (\lambda)` is the wavelength-dependent cross-section which is either:
+
+    - :math:`\sigma (\lambda) = \sigma_a (\lambda_{ref}) \left( \frac{\lambda}{\lambda_{ref}} \right)` for ``XSectionType`` == ``AttenuationXSection`` where :math:`\sigma_a` is the absorption cross-section in units of barns 
+    - or :math:`\sigma (\lambda) = \sigma_s + \sigma_a (\lambda_{ref}) \left( \frac{\lambda}{\lambda_{ref}} \right)` for ``XSectionType`` == ``TotalXSection`` where :math:`\sigma_s` is the total scattering cross-section in units of barns
+
+- :math:`\rho_{A}` is the area density (:math:`\rho_{A}=\rho * T`) in units of atoms*cm/:math:`\AA^3`,
+- :math:`\alpha = \rho_{A} * \frac{\sigma (\lambda_{ref})}{\lambda_{ref}} = \rho * T * \frac{\sigma (\lambda_{ref})}{\lambda_{ref}}` in units of 1/:math:`\AA`.
+- :math:`\lambda` is in units of :math:`\AA`. 
+
+NOTE: :math:`1 \AA^2 = 10^{8}` barns and :math:`1 \AA = 10^{-8}` cm.
+
+The optional inputs into the algorithm are to input either:
+  1. The ``Alpha`` parameter
+  2. The ``Density`` and ``ChemicalFormula`` to calculate the :math:`\sigma(\lambda_{ref})` term.
+  3. The ``MeasuredEfficiency``, an optional ``MeasuredEfficiencyWavelength``, and ``ChemicalFormula`` to calculate the :math:`\sigma(\lambda_{ref})` term.
+
+The ``MeasuredEfficiency`` is the :math:`\epsilon` term measured at a specific wavelength, :math:`\lambda_{\epsilon}`, which is specified by ``MeasuredEfficiencyWavelength``. This helps
+if the efficiency has been directly measured experimentally at a given wavelength. This will calculate the
+:math:`\rho * T` term, where it will be either:
+
+- :math:`\rho * T = - ln(1-\epsilon) \frac{1}{ \frac{\lambda_{\epsilon} \sigma (\lambda_{ref})}{\lambda_{ref}}}` for ``XSectionType`` == ``AttenuationXSection``
+- :math:`\rho * T = - ln(1-\epsilon) \frac{1}{ \sigma_s + \frac{\lambda_{\epsilon} \sigma (\lambda_{ref})}{\lambda_{ref}}}` for ``XSectionType`` == ``TotalXSection``
+
+For the ``XSectionType``, if the efficiency correction is applied to a beam monitor to determine the incident spectrum, then the ``AttenuationXSection`` option should be used. This is due to the fact that scatter events do not lead to neutrons that will be in the incident beam. If the efficiency correction is to be used similar to a transmission measurement for an actual sample measurement, such as in :ref:`algm-CalculateSampleTransmission-v1`, then the ``TotalXSection`` should be used to include both types of events.
+
+Usage
+-----
+**Example - Basics of running CalculateEfficiencyCorrection with Alpha.**
+
+.. testcode:: ExBasicCalcualteEfficiencyCorrectionWithAlpha
+
+    # Create an exponentially decaying function in wavelength to simulate measured sample
+    input_wksp = CreateSampleWorkspace(WorkspaceType="Event", Function="User Defined",
+                                       UserDefinedFunction="name=ExpDecay, Height=100, Lifetime=4",
+                                       Xmin=0.2, Xmax=4.0, BinWidth=0.01, XUnit="Wavelength",
+                                       NumEvents=10000, NumBanks=1, BankPixelWidth=1)
+
+    # Calculate the efficiency correction
+    corr_wksp = CalculateEfficiencyCorrection(InputWorkspace=input_wksp, Alpha=0.5)
+    corr_wksp_with_wave_range = CalculateEfficiencyCorrection(WavelengthRange="0.2,0.01,4.0", Alpha=0.5)
+
+    # Apply the efficiency correction to the measured spectrum
+    input_wksp = ConvertToPointData(InputWorkspace=input_wksp)
+    output_wksp = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp)
+    output_wksp_with_wave_range = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp_with_wave_range)
+
+    print('Input workspace: {}'.format(input_wksp.readY(0)[:5]))
+    print('Correction workspace: {}'.format(corr_wksp.readY(0)[:5]))
+    print('Output workspace: {}'.format(output_wksp.readY(0)[:5]))
+    print('Output workspace using WavelengthRange: {}'.format(output_wksp_with_wave_range.readY(0)[:5]))
+
+Ouptut:
+
+.. testoutput:: ExBasicCalcualteEfficiencyCorrectionWithAlpha
+
+    Input workspace: [ 38.  38.  38.  38.  38.]
+    Correction workspace: [ 10.26463773   9.81128219   9.39826191   9.02042771   8.67347109]
+    Output workspace: [ 390.05623383  372.82872321  357.13395265  342.77625306  329.59190131]
+    Output workspace using WavelengthRange: [ 390.05623383  372.82872321  357.13395265  342.77625306  329.59190131]
+
+**Example - Basics of running CalculateEfficiencyCorrection with Density and ChemicalFormula.**
+
+.. testcode:: ExBasicCalcualteEfficiencyCorrectionWithDensity
+
+    # Create an exponentially decaying function in wavelength to simulate measured sample
+    input_wksp = CreateSampleWorkspace(WorkspaceType="Event", Function="User Defined",
+                                       UserDefinedFunction="name=ExpDecay, Height=100, Lifetime=4",
+                                       Xmin=0.2, Xmax=4.0, BinWidth=0.01, XUnit="Wavelength",
+                                       NumEvents=10000, NumBanks=1, BankPixelWidth=1)
+
+    # Calculate the efficiency correction
+    corr_wksp = CalculateEfficiencyCorrection(InputWorkspace=input_wksp,
+                                              Density=6.11,
+                                              ChemicalFormula="V")
+    corr_wksp_with_wave_range = CalculateEfficiencyCorrection(WavelengthRange="0.2,0.01,4.0",
+                                                              Density=6.11,
+                                                              ChemicalFormula="V")
+
+    # Apply the efficiency correction to the measured spectrum
+    input_wksp = ConvertToPointData(InputWorkspace=input_wksp)
+    output_wksp = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp)
+    output_wksp_with_wave_range = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp_with_wave_range)
+
+    print('Input workspace: {}'.format(input_wksp.readY(0)[:5]))
+    print('Correction workspace: {}'.format(corr_wksp.readY(0)[:5]))
+    print('Output workspace: {}'.format(output_wksp.readY(0)[:5]))
+    print('Output workspace using WavelengthRange: {}'.format(output_wksp_with_wave_range.readY(0)[:5]))
+
+Ouptut:
+
+.. testoutput:: ExBasicCalcualteEfficiencyCorrectionWithDensity
+
+    Input workspace: [ 38.  38.  38.  38.  38.]
+    Correction workspace: [ 24.40910309  23.29738394  22.28449939  21.35783225  20.50682528]
+    Output workspace: [ 927.54591732  885.30058981  846.81097679  811.59762534  779.25936055]
+    Output workspace using WavelengthRange: [ 927.54591732  885.30058981  846.81097679  811.59762534  779.25936055]
+
+**Example - Basics of running CalculateEfficiencyCorrection with MeasuredEfficiency and ChemicalFormula.**
+
+.. testcode:: ExBasicCalcualteEfficiencyCorrectionWithEfficiency
+
+    # Create an exponentially decaying function in wavelength to simulate measured sample
+    input_wksp = CreateSampleWorkspace(WorkspaceType="Event", Function="User Defined",
+                                       UserDefinedFunction="name=ExpDecay, Height=100, Lifetime=4",
+                                       Xmin=0.2, Xmax=4.0, BinWidth=0.01, XUnit="Wavelength",
+                                       NumEvents=10000, NumBanks=1, BankPixelWidth=1)
+
+    # Calculate the efficiency correction
+    corr_wksp = CalculateEfficiencyCorrection(InputWorkspace=input_wksp,
+                                              MeasuredEfficiency=1e-2,
+                                              ChemicalFormula="(He3)")
+
+    corr_wksp_with_wave_range = CalculateEfficiencyCorrection(WavelengthRange="0.2,0.01,4.0",
+                                                              MeasuredEfficiency=1e-2,
+                                                              ChemicalFormula="(He3)")
+
+
+    # Apply the efficiency correction to the measured spectrum
+    input_wksp = ConvertToPointData(InputWorkspace=input_wksp)
+    output_wksp = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp)
+    output_wksp_with_wave_range = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp_with_wave_range)
+
+    print('Input workspace: {}'.format(input_wksp.readY(0)[:5]))
+    print('Correction workspace: {}'.format(corr_wksp.readY(0)[:5]))
+    print('Output workspace: {}'.format(output_wksp.readY(0)[:5]))
+    print('Output workspace using WavelengthRange: {}'.format(output_wksp_with_wave_range.readY(0)[:5]))
+
+Ouptut:
+
+.. testoutput:: ExBasicCalcualteEfficiencyCorrectionWithEfficiency
+
+    Input workspace: [ 38.  38.  38.  38.  38.]
+    Correction workspace: [ 873.27762699  832.68332786  795.69741128  761.85923269  730.78335476]
+    Output workspace: [ 33184.54982567  31641.9664586   30236.50162877  28950.65084207
+      27769.76748099]
+    Output workspace using WavelengthRange: [ 33184.54982567  31641.9664586   30236.50162877  28950.65084207
+      27769.76748099]
+
+**Example - Basics of running CalculateEfficiencyCorrection with MeasuredEfficiency and ChemicalFormula using the total cross section.**
+
+.. testcode:: ExBasicCalcualteEfficiencyCorrectionWithEfficiency
+
+    # Create an exponentially decaying function in wavelength to simulate measured sample
+    input_wksp = CreateSampleWorkspace(WorkspaceType="Event", Function="User Defined",
+                                       UserDefinedFunction="name=ExpDecay, Height=100, Lifetime=4",
+                                       Xmin=0.2, Xmax=4.0, BinWidth=0.01, XUnit="Wavelength",
+                                       NumEvents=10000, NumBanks=1, BankPixelWidth=1)
+
+    # Calculate the efficiency correction
+    corr_wksp = CalculateEfficiencyCorrection(InputWorkspace=input_wksp,
+                                              MeasuredEfficiency=1e-2,
+                                              ChemicalFormula="(He3)",
+                                              XSectionType="TotalXSection")
+
+    corr_wksp_with_wave_range = CalculateEfficiencyCorrection(WavelengthRange="0.2,0.01,4.0",
+                                                              MeasuredEfficiency=1e-2,
+                                                              ChemicalFormula="(He3)",
+                                                              XSectionType="TotalXSection")
+
+
+    # Apply the efficiency correction to the measured spectrum
+    input_wksp = ConvertToPointData(InputWorkspace=input_wksp)
+    output_wksp = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp)
+    output_wksp_with_wave_range = Multiply(LHSWorkspace=input_wksp, RHSWorkspace=corr_wksp_with_wave_range)
+
+    print('Input workspace: {}'.format(input_wksp.readY(0)[:5]))
+    print('Correction workspace: {}'.format(corr_wksp.readY(0)[:5]))
+    print('Output workspace: {}'.format(output_wksp.readY(0)[:5]))
+    print('Output workspace using WavelengthRange: {}'.format(output_wksp_with_wave_range.readY(0)[:5]))
+
+Ouptut:
+
+.. testoutput:: ExBasicCalcualteEfficiencyCorrectionWithEfficiency
+
+    Input workspace: [ 38.  38.  38.  38.  38.]
+    Correction workspace: [ 865.7208838   825.85320701  789.49774383  756.20995361  725.61727932]
+    Output workspace: [ 32897.39358441  31382.42186624  30000.91426562  28735.97823706
+      27573.45661411]
+    Output workspace using WavelengthRange: [ 32897.39358441  31382.42186624  30000.91426562  28735.97823706
+      27573.45661411]
+
+The transmission of a sample can be measured as :math:`e^{-\rho T \sigma_t (\lambda)}` where :math:`\sigma_t (\lambda) = \sigma_s + \sigma_a (\lambda)` is the total cross-section. This can be calculatd directly by the :ref:`algm-CalculateSampleTransmission-v1` algorithm. Yet, we can also back out the transmission with the ``CalculateEfficiencyCorrection`` algorithm. The example below shows how:
+
+**Example - Transmission using the CalculateEfficiencyCorrection and CalculateSampleTransmission comparison.**
+
+.. testcode:: ExTransmissionCalcualteEfficiencyCorrection
+
+    ws = CalculateSampleTransmission(WavelengthRange='2.0, 0.1, 10.0',  
+                                     ChemicalFormula='H2-O')  
+    print('Transmission: {} ...'.format(ws.readY(0)[:3]))  
+     
+    corr_wksp = CalculateEfficiencyCorrection(WavelengthRange="2.0, 0.1, 10.0", 
+                                              Density=0.1, 
+                                              Thickness=0.1, 
+                                              ChemicalFormula="H2-O", 
+                                              XSectionType="TotalXSection") 
+    dataX = corr_wksp.readX(0) 
+    dataY = np.ones(len(corr_wksp.readX(0))) 
+    ones = CreateWorkspace(dataX, dataY, UnitX="Wavelength") 
+    efficiency = Divide(LHSWorkspace=ones, RHSWorkspace=corr_wksp) # 1 + -1 * transmission
+    negative_trans = Minus(LHSWorkspace=efficiency, RHSWorkspace=ones) # -1 * transmission
+    transmission = Multiply(LHSWOrkspace=negative_trans, RHSWorkspace=-1.*ones) 
+    print('Transmission using efficiency correction: {} ...'.format(transmission.readY(0)[:3]))        
+
+Output:
+
+.. testoutput:: ExTransmissionCalcualteEfficiencyCorrection
+
+    Transmission: [ 0.94506317  0.94505148  0.94503979] ...
+    Transmission using efficiency correction: [ 0.9450632   0.94505151  0.94503982] ...
+
+The discrepancies are due to the differenc in :math:`\lambda_{ref}` = 1.7982 :math:`\AA` in ``CalculateEfficiencyCorrection``, consistent with `ReferenceLambda <https://github.com/mantidproject/mantid/blob/32ed0b2cbbe4fbfb230570d5a53032f6101743de/Framework/Kernel/src/NeutronAtom.cpp#L23>`_ where :ref:`algm-CalculateSampleTransmission-v1`  uses :math:`\lambda_{ref}` = 1.798 :math:`\AA`.
+
+**Example - Running CalculateEfficiencyCorrection for incident spectrum.**
+
+To model the incident spectrum of polyethylene moderators, the following function is used to
+join the exponential decay of the epithermal flux  to the Maxwellian distribution of the thermal flux [1]_:
+
+.. math::
+    :label: incident_spectrum
+
+    \phi(\lambda) = \phi_{max} \frac{\lambda_T^4}{\lambda^5} \mathrm{e}^{-(\lambda_T / \lambda)^2} + \phi_{epi} \frac{\Delta(\lambda_T / \lambda)}{\lambda^{1+2\alpha}}
+
+To determine this incident spectrum experimentally, one must make a measurement either via using a sample measurement such as vanadium [1]_ or using beam monitors. [2]_ [3]_ In either case, an efficiency correction must be applied to the measured spectrum to obtain the actual incident spectrum. This incident spectrum is a crucial part of calculating Placzek recoil sample corrections. [4]_
+
+From Eq. :eq:`incident_spectrum`, the parameters vary based on the moderator material. For a polyethlyene moderator at a temperature of 300K, the following parameters have been used to accurately model the incident spectrum. [1]_ The parameter labels, variables used in the following code example, and values for the parameters are given in the table below:
+
++--------------------+-------------+-----------------------------+
+| Parameter          | Variables   | Polyethlyene 300K (ambient) |
++====================+=============+=============================+
+| :math:`\phi_{max}` | ``phiMax``  | 6324                        |
++--------------------+-------------+-----------------------------+
+| :math:`\phi_{epi}` | ``phiEpi``  | 786                         |
++--------------------+-------------+-----------------------------+
+| :math:`\alpha`     | ``alpha``   | 0.099                       |
++--------------------+-------------+-----------------------------+
+| :math:`\lambda_1`  | ``lambda1`` | 0.67143                     |
++--------------------+-------------+-----------------------------+
+| :math:`\lambda_2`  | ``lambda2`` | 0.06075                     |
++--------------------+-------------+-----------------------------+
+| :math:`\lambda_T`  | ``lambdaT`` | 1.58 :math:`\AA`            |
++--------------------+-------------+-----------------------------+
+
+To first back out the measured spectrum of Milder et al. [1]_, the incident spectrum for polyethylene at 300K using Eq. :eq:`incident_spectrum` is obtained, then the efficiency correction is calculated, and then the incident spectrum is divided by this correction to back out what was originally measured. Then, the correction is applied by multiplying it by the measured spectrum to get back to the corrected incident spectrum to demonstrate how this is regularly apply this to a measured spectrum:
+
+.. testcode:: ExIncidentSpectrum
+
+    # Create the workspace to hold the already corrected incident spectrum
+    incident_wksp_name = 'incident_spectrum_wksp'
+    binning = "%s,%s,%s" % (0.2,0.01,4.0)
+    incident_wksp = CreateWorkspace(OutputWorkspace=incident_wksp_name,
+                                    NSpec=1, DataX=[0], DataY=[0],
+                                    UnitX='Wavelength',
+                                    VerticalAxisUnit='Text',
+                                    VerticalAxisValues='IncidentSpectrum')
+    incident_wksp = Rebin(InputWorkspace=incident_wksp, Params=binning)
+    incident_wksp = ConvertToPointData(InputWorkspace=incident_wksp)
+
+    # Spectrum function given in Milder et al. Eq (5)
+    def incidentSpectrum(wavelengths, phiMax, phiEpi, alpha, lambda1, lambda2, lamdaT):
+        deltaTerm =  1. / (1. + np.exp((wavelengths - lambda1) / lambda2))
+        term1 = phiMax * (lambdaT**4. / wavelengths**5.) * np.exp(-(lambdaT / wavelengths)**2.)
+        term2 = phiEpi * deltaTerm / (wavelengths**(1 + 2 * alpha))
+        return term1 + term2
+
+    # Variables for polyethlyene moderator at 300K
+    phiMax  = 6324
+    phiEpi  = 786
+    alpha   = 0.099
+    lambda1 = 0.67143
+    lambda2 = 0.06075
+    lambdaT = 1.58
+
+    # Add the incident spectrum to the workspace
+    corrected_spectrum = incidentSpectrum(incident_wksp.readX(0),
+                                          phiMax, phiEpi, alpha,
+                                          lambda1, lambda2, lambdaT)
+    incident_wksp.setY(0, corrected_spectrum)
+
+    # Calculate the efficiency correction for Alpha=0.693 and back calculate measured spectrum
+    eff_wksp = CalculateEfficiencyCorrection(InputWorkspace=incident_wksp, Alpha=0.693)
+    measured_wksp = Divide(LHSWorkspace=incident_wksp, RHSWorkspace=eff_wksp)
+
+    # Re-applying the correction to the measured data (how to normally use it)
+    eff2_wksp = CalculateEfficiencyCorrection(InputWorkspace=measured_wksp, Alpha=0.693)
+    recorrected_wksp = Multiply(LHSWorkspace=measured_wksp, RHSWorkspace=eff2_wksp)
+
+    print('Measured incident spectrum: {}'.format(measured_wksp.readY(0)[:5]))
+    print('Corrected incident spectrum: {}'.format(incident_wksp.readY(0)[:5]))
+    print('Re-corrected incident spectrum: {}'.format(recorrected_wksp.readY(0)[:5]))
+
+Output:
+
+.. testoutput:: ExIncidentSpectrum
+
+   Measured incident spectrum: [ 694.61415533  685.71520053  677.21326605  669.0696332   661.25022644]
+   Corrected incident spectrum: [ 5244.9385468   4953.63834159  4690.60136547  4451.98728342  4234.6092648 ]
+   Re-corrected incident spectrum: [ 5244.9385468   4953.63834159  4690.60136547  4451.98728342  4234.6092648 ]
+
+References
+------------
+
+.. [1] D. F. R. Mildner, B. C. Boland, R. N. Sinclair, C. G. Windsor, L. J. Bunce, and J. H. Clarke (1977) *A Cooled Polyethylene Moderator on a Pulsed Neutron Source*, Nuclear Instruments and Methods 152 437-446 `doi: 10.1016/0029-554X(78)90043-5 <https://doi.org/10.1016/0029-554X(78)90043-5>`__
+.. [2] J. P. Hodges, J. D. Jorgensen, S. Short, D. N. Argyiou, and J. W. Richardson, Jr.  *Incident Spectrum Determination for Time-of-Flight Neutron Powder Diffraction Data Analysis* ICANS 14th Meeting of the International Collaboration on Advanced Neutron Sources 813-822 `link to paper <http://www.neutronresearch.com/parch/1998/01/199801008130.pdf>`__
+.. [3] F. Issa, A. Khaplanov, R. Hall-Wilton, I. Llamas, M. Dalseth Ricktor, S. R. Brattheim, and H. Perrey (2017) *Characterization of Thermal Neutron Beam Monitors* Physical Review Accelerators and Beams 20 092801 `doi: 10.1103/PhysRevAccelBeams.20.092801 <https://doi.org/10.1103/PhysRevAccelBeams.20.092801>`__
+.. [4] W. S. Howells (1983) *On the Choice of Moderator for Liquids Diffractometer on a Pulsed Neutron Source*, Nuclear Instruments and Methods in Physics Research 223 141-146 `doi: 10.1016/0167-5087(84)90256-4 <https://doi.org/10.1016/0167-5087(84)90256-4>`__
+
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/CreateSampleShape-v1.rst b/docs/source/algorithms/CreateSampleShape-v1.rst
index d4de5dbd5bf095c3e6a624a6dbf0607e8d0ed7b9..e533f43f24fd21c2c7582cf01b0f6b7b1b074e08 100644
--- a/docs/source/algorithms/CreateSampleShape-v1.rst
+++ b/docs/source/algorithms/CreateSampleShape-v1.rst
@@ -13,10 +13,12 @@ Creates a shape object that defines the sample and sets the sample for
 the given workspace. Shapes are defined using XML descriptions that can
 be found :ref:`here <HowToDefineGeometricShape>`.
 
+.. note:: It is recommended that you use :ref:`SetSample <algm-SetSample>` instead.
+
 Usage
 -----
 
-**Example - A Sphere**  
+**Example - A Sphere**
 
 .. testcode:: Sphere
 
@@ -28,7 +30,7 @@ Usage
       </sphere>'''
     CreateSampleShape(ws,shape_xml)
 
-**Example - A ball with a cylinder carved out of the middle**  
+**Example - A ball with a cylinder carved out of the middle**
 
 .. testcode:: BallwithHole
 
diff --git a/docs/source/algorithms/EstimateResolutionDiffraction-v1.rst b/docs/source/algorithms/EstimateResolutionDiffraction-v1.rst
index df4b51d778d521b95eec4d79cd343f2aab5524c2..4072fc364b89a7e4e982bf53fac9147b2bace63b 100644
--- a/docs/source/algorithms/EstimateResolutionDiffraction-v1.rst
+++ b/docs/source/algorithms/EstimateResolutionDiffraction-v1.rst
@@ -47,7 +47,7 @@ functions where ``_tof`` is the time-of-flight term, ``_length`` is
 the path length term, and ``_angle`` is the angular term. Note that
 the total resolution is these terms added in quadriture.
 
-Note that :math:`\frac{\Delta d}{d} = \frac{\Delta Q}{Q}`.
+Note that :math:`\frac{\Delta d}{d} = \frac{\Delta Q}{Q}`. When fitting peaks in time-of-flight the resolution is :math:`\frac{\Delta T}{T} = \frac{\Delta d}{d}`.
 
 Factor Sheet
 ------------
diff --git a/docs/source/algorithms/FilterByLogValue-v1.rst b/docs/source/algorithms/FilterByLogValue-v1.rst
index 6579b1d95d58e25908e113772d61d9159450c7be..8e5a6508db1fb9a3ba1b8d49c74049e1ddaf3614 100644
--- a/docs/source/algorithms/FilterByLogValue-v1.rst
+++ b/docs/source/algorithms/FilterByLogValue-v1.rst
@@ -38,6 +38,11 @@ will be included also. If a log has a single point in time, then that
 log value is assumed to be constant for all time and if it falls within
 the range, then all events will be kept.
 
+.. warning::
+
+   :ref:`FilterByLogValue <algm-FilterByLogValue>` is not suitable for
+   fast log filtering.
+
 PulseFilter (e.g. for Veto Pulses)
 ##################################
 
@@ -63,7 +68,7 @@ rejected. For example, this call will filter out veto pulses:
 .. testsetup:: VetoPulseTime
 
    ws=CreateSampleWorkspace("Event")
-   AddTimeSeriesLog(ws, Name="veto_pulse_time", Time="2010-01-01T00:00:00", Value=1) 
+   AddTimeSeriesLog(ws, Name="veto_pulse_time", Time="2010-01-01T00:00:00", Value=1)
    AddTimeSeriesLog(ws, Name="veto_pulse_time", Time="2010-01-01T00:10:00", Value=0)
    AddTimeSeriesLog(ws, Name="veto_pulse_time", Time="2010-01-01T00:20:00", Value=1)
    AddTimeSeriesLog(ws, Name="veto_pulse_time", Time="2010-01-01T00:30:00", Value=0)
@@ -77,20 +82,20 @@ rejected. For example, this call will filter out veto pulses:
 Comparing with other event filtering algorithms
 ###############################################
 
-Wiki page :ref:`EventFiltering` has a detailed
-introduction on event filtering in MantidPlot.
+The :ref:`EventFiltering` page has a detailed introduction on event
+filtering in mantid.
 
 
 Usage
 -----
 
-**Example - Filtering by a simple time series Log**  
+**Example - Filtering by a simple time series Log**
 
 .. testcode:: FilterByLogValue
 
    ws = CreateSampleWorkspace("Event",BankPixelWidth=1)
 
-   AddTimeSeriesLog(ws, Name="proton_charge", Time="2010-01-01T00:00:00", Value=100) 
+   AddTimeSeriesLog(ws, Name="proton_charge", Time="2010-01-01T00:00:00", Value=100)
    AddTimeSeriesLog(ws, Name="proton_charge", Time="2010-01-01T00:10:00", Value=100)
    AddTimeSeriesLog(ws, Name="proton_charge", Time="2010-01-01T00:20:00", Value=100)
    AddTimeSeriesLog(ws, Name="proton_charge", Time="2010-01-01T00:30:00", Value=100)
diff --git a/docs/source/algorithms/FilterEvents-v1.rst b/docs/source/algorithms/FilterEvents-v1.rst
index b45de531098b1640271f410a12797f88f29a7f4a..7063dcb65b05b1757d3ea993b6613e7474117a38 100644
--- a/docs/source/algorithms/FilterEvents-v1.rst
+++ b/docs/source/algorithms/FilterEvents-v1.rst
@@ -9,147 +9,120 @@
 Description
 -----------
 
-This algorithm filters events from an :ref:`EventWorkspace` to one or
-multiple :ref:`EventWorkspaces <EventWorkspace>` according to an input
-:ref:`SplittersWorkspace` containing a series of splitters (i.e.,
-:ref:`splitting intervals <SplittingInterval>`).
-
-Inputs
-######
-
-FilterEvents takes 2 mandatory input Workspaces and 1 optional
-Workspace.  One of mandatory workspace is the :ref:`EventWorkspace`
-where the events are filtered from.  The other mandatory workspace is
-workspace containing splitters.  It can be a MatrixWorkspace, a TableWorkspace or
-a :ref:`SplittersWorkspace <SplittersWorkspace>`.
-
-The optional workspace is a :ref:`TableWorkspace <Table Workspaces>`
-for information of splitters.
-
-Workspace containing splitters
-==============================
-
-This algorithm accepts three types of workspace that contains event splitters.
-- TableWorkspace: a general TableWorkspace with at three columns
-- MatrixWorkspace: a 1-spectrum MatrixWorkspace
-- SplittersWorkspace: an extended TableWorkspace with restrict definition on start and stop time.
-
-Event splitter
-++++++++++++++
-
-An event splitter contains three items, start time, stop time and splitting target (index).
-All the events belonged to the same splitting target will be saved to a same output EventWorkspace.
-
-Unit of input splitters
-+++++++++++++++++++++++
-
-- MatrixWorkspace:  the unit must be second.
-- TableWorkspace: the unit must be second.
-- SplittersWorkspace: by the definition of SplittersWorkspace, the unit has to be nanosecond.
-
-
-How to generate input workspace containing splitters
-++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-There are two ways to generate
-
-Algorithm :ref:`GenerateEventsFilter <algm-GenerateEventsFilter>`
-creates both the :ref:`SplittersWorkspace <SplittersWorkspace>` and
-splitter information workspace.
-
-
-Splitters in relative time or absolute time
-+++++++++++++++++++++++++++++++++++++++++++
-
-As the SplittersWorkspace is in format of :ref:`MatrixWorkspace
-<MatrixWorkspace>`, its time, i.e., the value in X vector, can be
-relative time.
-
-Property ``RelativeTime`` flags that the splitters' time is relative.
-Property ``FilterStartTime`` specifies the starting time of the filter.
-Or the shift of time of the splitters.
-If it is not specified, then the algorithm will search for sample log ``run_start``.
-
-Outputs
-#######
-
-The output will be one or multiple workspaces according to the number of
-index in splitters. The output workspace name is the combination of
-parameter OutputWorkspaceBaseName and the index in splitter.
-
-Calibration File
-################
-
-The calibration, or say correction, from the detector to sample must be
-consider in fast log. Thus a calibration file is required. The math is
-
-``TOF_calibrated = TOF_raw * correction(detector ID).``
-
-The calibration is in column data format.
-
-A reasonable approximation of the correction is
-
-``correction(detector_ID) = L1/(L1+L2(detector_ID))``
+This algorithm filters events from a single :ref:`EventWorkspace` to
+one or multiple :ref:`EventWorkspaces <EventWorkspace>` according to
+the ``SplittersWorkspace`` property. The :ref:`EventFiltering` concept
+page has a detailed introduction to event filtering.
+
+Specifying the splitting strategy
+---------------------------------
+
+The ``SplittersWorkspace`` describes much of the information for
+splitting the ``InputWorkspace`` into the various output
+workspaces. It can have one of three types
+
++--------------------------------------------------------------+-------------+----------+
+| workspace class                                              | units       | rel/abs  |
++==============================================================+=============+==========+
+| :ref:`MatrixWorkspace <MatrixWorkspace>`                     | seconds     | either   |
++--------------------------------------------------------------+-------------+----------+
+| :class:`SplittersWorkspace <mantid.api.ISplittersWorkspace>` | nanoseconds | absolute |
++--------------------------------------------------------------+-------------+----------+
+| :ref:`TableWorkspace <Table Workspaces>`                     | seconds     | either   |
++--------------------------------------------------------------+-------------+----------+
+
+Whether the values in :ref:`MatrixWorkspace <MatrixWorkspace>` and
+:ref:`TableWorkspace <Table Workspaces>` is treated as relative or
+absolute time is dependent on the value of ``RelativeTime``. In the
+case of ``RelativeTime=True``, the time is relative to the start of
+the run (in the ``ws.run()['run_start']``) or, if specified, the
+``FilterStartTime``. In the case of ``RelativeTime=False``, the times
+are relative to the :class:`GPS epoch <mantid.kernel.DateAndTime>`.
+
+Both :ref:`TableWorkspace <Table Workspaces>` and
+:class:`SplittersWorkspace <mantid.api.ISplittersWorkspace>` have 3
+colums, ``start``, ``stop``, and ``target`` which should be a float,
+float, and string. The :ref:`event filtering <EventFiltering>` concept
+page has details on creating the :ref:`TableWorkspace <Table
+Workspaces>` by hand.
+
+If the ``SplittersWorkspace`` is a :ref:`MatrixWorkspace
+<MatrixWorkspace>`, it must have a single spectrum with the x-value is
+the time boundaries and the y-value is the workspace group index.
+
+The optional ``InformationWorkspace`` is a :ref:`TableWorkspace <Table
+Workspaces>` for information of splitters.
 
 Unfiltered Events
-#################
+-----------------
 
 Some events are not inside any splitters. They are put to a workspace
-name ended with '\_unfiltered'.
-
-If input property 'OutputWorkspaceIndexedFrom1' is set to True, then
-this workspace shall not be outputted.
+name ended with ``_unfiltered``. If
+``OutputWorkspaceIndexedFrom1=True``, then this workspace will not be
+created.
 
 Using FilterEvents with fast-changing logs
-##########################################
+------------------------------------------
+
+There are a few parameters to consider when the log filtering is
+expected to produce a large splitter table. An example of such a case
+would be a data file for which the events need to be split according
+to a log with two or more states changing in the kHz range. To reduce
+the filtering time, one may do the following:
+
+- Make sure the ``SplitterWorkspace`` input is a :ref:`MatrixWorkspace
+  <MatrixWorkspace>`. Such a workspace can be produced by using the
+  ``FastLog = True`` option when calling :ref:`GenerateEventsFilter
+  <algm-GenerateEventsFilter>`.
+- Choose the logs to split. Filtering the logs can take a substantial
+  amount of time. To save time, you may want to split only the logs
+  you will need for analysis. To do so, set ``ExcludeSpecifiedLogs =
+  False`` and list the logs you need in
+  ``TimeSeriesPropertyLogs``. For example, if we only need to know the
+  accumulated proton charge for each filtered workspace, we would set
+  ``TimeSeriesPropertyLogs = proton_charge``.
+
+Correcting time neutron was at the sample
+#########################################
+
+When filtering fast logs, the time to filter by is the time that the
+neutron was at the sample. This can be specified using the
+``CorrectionToSample`` parameter. Either the user specifies the
+correction parameter for every pixel, or one is calculated. The
+correction parameters are applied as
+
+.. math::
+
+   TOF_{sample} = TOF_{detector} * scale[detectorID] + shift[detectorID]
+
+and stored in the ``OutputTOFCorrectionWorkspace``.
+
+* ``CorrectionToSample="None"`` applies no correction
+* ``CorrectionToSample="Elastic"`` applies :math:`shift = 0` with
+  :math:`scale = L1/(L1+L2)` for detectors and :math:`scale = L1/L_{monitor}`
+  for monitors
+* ``CorrectionToSample="Direct"`` applies :math:`scale = 0` and
+  :math:`shift = L1 / \sqrt{2 E_{fix} / m_n}`.  The value supplied in
+  ``IncidentEnergy`` will override the value found in the workspace's
+  value of ``Ei``.
+* ``CorrectionToSample="Indirect"`` applies :math:`scale = 1` and
+  :math:`shift = -1 * L2 / \sqrt{2 E_{fix} / m_n}` for detectors. For
+  monitors, uses the same corrections as ``Elastic``.
+
+* ``CorrectionToSample="Customized"`` applies the correction supplied
+  in the ``DetectorTOFCorrectionWorkspace``.
 
-There are a few parameters to consider when the log filtering is expected to produce a large
-splitter table. An example of such a case would be a data file for which the events need to be split
-according to a log with two or more states changing in the kHz range. To reduce the filtering time,
-one may do the following:
-
-- Make sure the ``SplitterWorkspace`` input is a :ref:`MatrixWorkspace <MatrixWorkspace>`. Such a workspace can be produced by using the ``FastLog = True`` option when calling :ref:`GenerateEventsFilter <algm-GenerateEventsFilter>`.
-- Choose the logs to split. Filtering the logs can take a substantial amount of time. To save time, you may want to split only the logs you will need for analysis. To do so, set ``ExcludeSpecifiedLogs = False`` and list the logs you need in ``TimeSeriesPropertyLogs``. For example, if we only need to know the accumulated proton charge for each filtered workspace, we would set ``TimeSeriesPropertyLogs = proton_charge``.
 
 Difference from FilterByLogValue
-################################
-
-In FilterByLogValue(), EventList.splitByTime() is used.
-
-In FilterEvents, if FilterByPulse is selected true,
-EventList.SplitByTime is called; otherwise, EventList.SplitByFullTime()
-is called instead.
-
-The difference between splitByTime and splitByFullTime is that
-splitByTime filters events by pulse time, and splitByFullTime considers
-both pulse time and TOF.
-
-Therefore, FilterByLogValue is not suitable for fast log filtering.
-
-Comparing with other event filtering algorithms
-###############################################
-
-Wiki page :ref:`EventFiltering` has a detailed introduction on event
-filtering in MantidPlot.
-
-
-Developer's Note
-----------------
-
-Splitters given by TableWorkspace
-#################################
-
-- The ``start/stop time`` is converted to ``m_vecSplitterTime``.
-- The splitting target (in string) is mapped to a set of continuous integers that are stored in ``m_vecSplitterGroup``.
-  - The mapping will be recorded in ``m_targetIndexMap`` and ``m_wsGroupIndexTargetMap``.
-  - Class variable ``m_maxTargetIndex`` is set up to record the highest target group/index,i.e., the max value of ``m_vecSplitterGroup``.
-
-
-Undefined splitting target
-##########################
-
-Indexed as ``0`` in m_vecSplitterGroup.
-
+--------------------------------
+
+In :ref:`FilterByLogValue <algm-FilterByLogValue>`,
+``EventList.splitByTime()`` is used. In FilterEvents, it only uses
+this when ``FilterByPulse=True``. Otherwise,
+``EventList.splitByFullTime()`` is used. The difference between
+``splitByTime`` and ``splitByFullTime`` is that ``splitByTime``
+filters events by pulse time, and ``splitByFullTime`` considers both
+pulse time and TOF.
 
 Usage
 -----
diff --git a/docs/source/algorithms/GetLiveInstrumentValue-v1.rst b/docs/source/algorithms/GetLiveInstrumentValue-v1.rst
index c93da0976e444e678da66162509a1938db9136a0..78ab36dc2c9cc0c65af512a258140bb1ab435f44 100644
--- a/docs/source/algorithms/GetLiveInstrumentValue-v1.rst
+++ b/docs/source/algorithms/GetLiveInstrumentValue-v1.rst
@@ -12,7 +12,7 @@ GetLiveInstrumentValue
 
 This algorithm gets live values such as run title or theta from an instrument.
 
-Values are accessed via EPICS, so EPICS support must be installed in Mantid for this algorithm to work. This is included by default on Windows but see the instructions `here <https://www.mantidproject.org/PyEpics_In_Mantid>`_ for other platforms.
+CaChannel must be installed for this algorithm to work. See the instructions `here <https://www.mantidproject.org/CaChannel_In_Mantid>`_.
 
 The instrument must also be on IBEX or have additional processes installed to supply the EPICS values. If it does not, you will get an error that the requested value could not be found.
 
diff --git a/docs/source/algorithms/LoadSampleEnvironment-v1.rst b/docs/source/algorithms/LoadSampleEnvironment-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..74d6ba9237259bcc08de7989201f2fcd5884a06e
--- /dev/null
+++ b/docs/source/algorithms/LoadSampleEnvironment-v1.rst
@@ -0,0 +1,25 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+Loads an environment into the sample of a workspace, either replacing the current environment, or adding to it. you may also set a material for the environment to be loaded, this follows the same inputs as :ref:`algm-SetSampleMaterial`.
+
+The following types of input file are supported:
+
+* ``*.stl`` stereolithography `https://en.wikipedia.org/wiki/STL_(file_format) <https://en.wikipedia.org/wiki/STL_(file_format)>`_
+  This is a file format consisting of a list of faces specified by their vertex coordinates.
+  The file may be in ASCII or Binary format, and all the faces must be triangular. 
+  The normals are ignored, but the vertices must be in the order required by the standard 
+  (counter-clockwise when viewed from outside).
+
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/MDNorm-v1.rst b/docs/source/algorithms/MDNorm-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..eab6ac6f6a278363d32958b93662a445d79c3715
--- /dev/null
+++ b/docs/source/algorithms/MDNorm-v1.rst
@@ -0,0 +1,190 @@
+
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+Starting from events in the momentum transfer space , this algorithm puts the data on a regular grid
+in either reciprocal space of the sample (if `RLU` is selected) or in the goniometer frame, 
+then it normalizes to get the scattering cross section.
+For diffraction data, the output workspace contains the differential cross section :math:`\frac{d\sigma}{d\Omega}`, while
+for direct geometry inelastic data one obtains the double differential cross section  :math:`\frac{d^2 \sigma}{dE d\Omega}`.
+One can choose any orientation for the momentum axes (to get the first axis to be `[-H,H,0]`, set `QDimension0` to `-1,1,0`.
+
+**Note:** In order to calculate the trajectories, the algorithm relies on finding information about detector
+trajectories stored in the workspace. The algorithm :ref:`CropWorkspaceForMDNorm <algm-CropWorkspaceForMDNorm>` must
+be run *before* converting to multidimensional workspace. Optionally, in aaddition, one can recalculate the extents of the trajectories 
+using the :ref:`RecalculateTrajectoriesExtents  <algm-RecalculateTrajectoriesExtents>` algorithm after convering to the 
+multidimensional workspace.
+
+The solid angle workspace is a :ref:`MatrixWorkspace <MatrixWorkspace>` that contains the solid angle/efficiency of the detectors.
+One can just integrate a vanadium file between some appropriate limits. For diffraction measurements, the flux workspace 
+is a mandatory input. It is a :ref:`MatrixWorkspace <MatrixWorkspace>`
+that contains the indefinite integral of the incident flux. It contains one or more spectra, each of them corresponding to 
+detectors that have the same energy response. The algorithm :ref:`MDNormSCDPreprocessIncoherent
+<algm-MDNormSCDPreprocessIncoherent>` can be used to process Vanadium
+data for the Solid Angle and Flux workspaces.
+
+The input workspace is binned using the :ref:`BinMD <algm-BinMD>` algorithm, to account for the selected momentum
+dimensions. For each dimension to be binned, we specify a name (for example `Dimension0Name='QDimension0'`). For any momentum dimension
+the name is one of `QDimension0`, `QDimension1`, or `QDimension2` along the axes specified in the algorithm. All three momentum 
+dimensions must be present in the parameter list of the algorithm. Any other dimension name, such as `DeltaE`, is optional, 
+and must be identical to a dimension in the input workspace. If a dimension name is present in the input workspace but not within the 
+parameters of this algorithm, the corresponding data will be integrated. The semnification of binning parameters is as following: 
+
++----------------------------------+-------------------------------------------------------+
+| Format                           |                                                       |
++==================================+=======================================================+
+|  minimum, stepsize, maximum      | The dimension in the output workspace will extend     |
+|                                  | from 'minimum' to 'maximum' with bins of width        |
+|                                  | 'stepsize'.                                           |
++----------------------------------+-------------------------------------------------------+
+|  minimum, maximum                | A single bin will be created between 'minimum' and    |
+|                                  | 'maximum'.                                            |
++----------------------------------+-------------------------------------------------------+
+|  stepsize                        | The 'minimum' and 'maximum' are set to the dimension  |
+|                                  | limits; the workspace is not cut in this dimension.   |
++----------------------------------+-------------------------------------------------------+
+
+The binned workspace is stored in the `OutputDataWorkspace`.
+
+Trajectories of each detector in reciprocal space are omputed, and the normalization is calculated between intersections with each
+MDBox. A brief introduction to the multi-dimensional data normalization can be found :ref:`here <MDNorm>`. The 
+`OutputNormalizationWorkspace` contains the denominator of equations (2) or (3). In the :ref:`normalization document <MDNorm>`.
+
+The `OutputWorkspace` contains the ratio of the `OutputDataWorkspace` and `OutputNormalizationWorkspace`.
+
+One can accumulate multiple inputs. The correct way to do it is to add the counts together, add the normalizations
+together, then divide. For user convenience, one can provide these accumulation workspaces as `TemporaryDataWorkspace`
+and `TemporaryNormalizationWorkspace`.
+
+There are symmetrization options for the data. To achieve this option, one can use the `SymmetryOperations` parameter. It can accept
+a space group name, a point group name, or a list of symmetry operations. More information about symmetry operations can be found
+:ref:`here <Symmetry groups>` and :ref:`here <Point and space groups>`
+
+
+**Example - MDNorm**
+
+For diffraction measurements a sample code is found below:
+
+.. code-block:: python
+
+   Load(Filename='CORELLI_29782.nxs', OutputWorkspace='data')
+   Load(Filename='SingleCrystalDiffuseReduction_SA.nxs', OutputWorkspace='SolidAngle')
+   Load(Filename='SingleCrystalDiffuseReduction_Flux.nxs', OutputWorkspace= 'Flux')
+   MaskDetectors(Workspace='data', MaskedWorkspace='SolidAngle')
+   ConvertUnits(InputWorkspace='data',OutputWorkspace='data',Target='Momentum')
+   CropWorkspaceForMDNorm(InputWorkspace='data', 
+                          XMin=2.5, 
+                          XMax=10, 
+                          OutputWorkspace='data')
+   LoadIsawUB(InputWorkspace='data',Filename='SingleCrystalDiffuseReduction_UB.mat')
+   SetGoniometer(Workspace='data',Axis0='BL9:Mot:Sample:Axis1,0,1,0,1')
+   min_vals,max_vals=ConvertToMDMinMaxGlobal(InputWorkspace='data',
+                                             QDimensions='Q3D',
+                                             dEAnalysisMode='Elastic',
+                                             Q3DFrames='Q')
+   ConvertToMD(InputWorkspace='data', 
+               QDimensions='Q3D', 
+               dEAnalysisMode='Elastic',
+               Q3DFrames='Q_sample', 
+               OutputWorkspace='md', 
+               MinValues=min_vals, 
+               MaxValues=max_vals)   
+   RecalculateTrajectoriesExtents(InputWorkspace= 'md', OutputWorkspace='md')
+   
+   MDNorm(InputWorkspace='md', 
+          SolidAngleWorkspace='SolidAngle',
+          FluxWorkspace='Flux',
+          QDimension0='1,1,0',
+          QDimension1='1,-1,0',
+          QDimension2='0,0,1',
+          Dimension0Name='QDimension0', 
+          Dimension0Binning='-10.0,0.1,10.0', 
+          Dimension1Name='QDimension1', 
+          Dimension1Binning='-10.0,0.1,10.0', 
+          Dimension2Name='QDimension2', 
+          Dimension2Binning='-0.1,0.1',
+          SymmetryOperations='P 31 2 1',
+          OutputWorkspace='result', 
+          OutputDataWorkspace='dataMD', 
+          OutputNormalizationWorkspace='normMD')
+
+The output would look like:
+
+.. figure:: /images/MDNorm_elastic_sym.png
+
+
+Here is a sample code for inelastic data:
+
+.. code-block:: python
+
+   Load(Filename='HYS_13656-13658',OutputWorkspace='sum')
+   SetGoniometer(Workspace='sum', Axis0='s1,0,1,0,1')
+   GenerateEventsFilter(InputWorkspace='sum', 
+                        OutputWorkspace='splboth', 
+                        InformationWorkspace='info', 
+                        UnitOfTime='Nanoseconds', 
+                        LogName='s1', 
+                        MaximumLogValue=90, 
+                        LogValueInterval=1)
+   FilterEvents(InputWorkspace='sum', 
+                SplitterWorkspace='splboth',  
+                InformationWorkspace='info', 
+                FilterByPulseTime=True, 
+                GroupWorkspaces=True,
+                OutputWorkspaceIndexedFrom1=True,   
+                OutputWorkspaceBaseName='split')
+   DgsReduction(SampleInputWorkspace='split',
+                SampleInputMonitorWorkspace='split_1', 
+                IncidentEnergyGuess=50, 
+                SofPhiEIsDistribution=False, 
+                TimeIndepBackgroundSub=True, 
+                TibTofRangeStart=10400, 
+                TibTofRangeEnd=12400, 
+                OutputWorkspace='reduced')
+   SetUB(Workspace='reduced', 
+         a=5.823, 
+         b=6.475, 
+         c=3.186, 
+         u='0,1,0', 
+         v='0,0,1')
+   CropWorkspaceForMDNorm(InputWorkspace='reduced', 
+                          XMin=-25, 
+                          XMax=49, 
+                          OutputWorkspace='reduced')
+   ConvertToMD(InputWorkspace='reduced', 
+               QDimensions='Q3D', 
+               Q3DFrames='Q_sample', 
+               OutputWorkspace='md', 
+               MinValues='-11,-11,-11,-25', 
+               MaxValues='11,11,11,49')
+   MergeMD(InputWorkspaces='md', OutputWorkspace='merged')
+   MDNorm(InputWorkspace='merged', 
+          Dimension0Name='QDimension1', 
+          Dimension0Binning='-5,0.05,5', 
+          Dimension1Name='QDimension2', 
+          Dimension1Binning='-5,0.05,5', 
+          Dimension2Name='DeltaE', 
+          Dimension2Binning='-2,2', 
+          Dimension3Name='QDimension0', 
+          Dimension3Binning='-0.5,0.5', 
+          SymmetryOperations='x,y,z;x,-y,z;x,y,-z;x,-y,-z',
+          OutputWorkspace='result', 
+          OutputDataWorkspace='dataMD',
+          OutputNormalizationWorkspace='normMD')
+
+and the corresponding output:
+
+.. figure:: /images/MDNorm_inelastic_sym.png
+
+.. categories::
+
+.. sourcelink::
+
diff --git a/docs/source/algorithms/OptimizeCrystalPlacement-v1.rst b/docs/source/algorithms/OptimizeCrystalPlacement-v1.rst
index d4527d6fdbfba4fe4c41c28e33c416768f54787e..7b48ff9752ce23a87403270148a40a1d299a2b02 100644
--- a/docs/source/algorithms/OptimizeCrystalPlacement-v1.rst
+++ b/docs/source/algorithms/OptimizeCrystalPlacement-v1.rst
@@ -54,16 +54,16 @@ Usage
 .. testcode:: ExOptimizeCrystalPlacement
 
     ws=LoadIsawPeaks("TOPAZ_3007.peaks")
-    LoadIsawUB(ws,"ls5637.mat")
+    LoadIsawUB(ws,"TOPAZ_3007.mat")
     wsd = OptimizeCrystalPlacement(ws)
-    (wsPeakOut,fitInfoTable,chi2overDoF,nPeaks,nParams,nIndexed,covrianceInfoTable) = OptimizeCrystalPlacement(ws)
+    (wsPeakOut,fitInfoTable,chi2overDoF,nPeaks,nParams,nIndexed,covrianceInfoTable) = OptimizeCrystalPlacement(ws,AdjustSampleOffsets=True)
     print("Chi2: {:.4f}".format(chi2overDoF))
 
 Output:
 
 .. testoutput:: ExOptimizeCrystalPlacement
 
-    Chi2: 0.0203
+    Chi2: 0.0003
 
 .. categories::
 
diff --git a/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst b/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..758e4c699cbc0ffd59b44ee0c1d41b3f669e54a1
--- /dev/null
+++ b/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst
@@ -0,0 +1,45 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm basically optimizes h,k, and l offsets from an integer by
+varying the parameters sample positions for each run in the peaks workspace.
+
+The crystal orientation matrix, :ref:`UB matrix <Lattice>`, from the
+PeaksWorkspace should index all the runs "very well". Otherwise iterations
+that slowly build a :ref:`UB matrix <Lattice>` with corrected sample
+orientations may be needed.
+
+
+Usage
+-----
+
+**Example:**
+
+.. testcode:: ExOptimizeCrystalPlacementByRun
+
+   ws=LoadIsawPeaks("calibrated.peaks")
+   FindUBUsingFFT(PeaksWorkspace=ws,MinD=2,MaxD=20,Tolerance=0.12)
+   IndexPeaks(PeaksWorkspace='ws',Tolerance=0.12)
+   wsd = OptimizeCrystalPlacementByRun(InputWorkspace=ws,OutputWorkspace='wsd',Tolerance=0.12)
+   print('Optimized %s sample position: %s'%(mtd['wsd'].getPeak(0).getRunNumber(),mtd['wsd'].getPeak(0).getSamplePos()))
+   print('Optimized %s sample position: %s'%(mtd['wsd'].getPeak(8).getRunNumber(),mtd['wsd'].getPeak(8).getSamplePos()))
+
+Output:
+
+.. testoutput:: ExOptimizeCrystalPlacementByRun
+
+   Optimized 71907 sample position: [-0.000678629,-2.16033e-05,0.00493278]
+   Optimized 72007 sample position: [-0.0027929,-0.00105681,0.00497094]
+
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/SetCrystalLocation-v1.rst b/docs/source/algorithms/SetCrystalLocation-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..114fb8d9e3297278a3e242ce56eaf29bd0867a1b
--- /dev/null
+++ b/docs/source/algorithms/SetCrystalLocation-v1.rst
@@ -0,0 +1,36 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm changes the sample location for an events workspace.  If the InputWorkspace and OutputWorkspace are the same, the position is simply changed.  If the InputWorkspace and OutputWorkspace are different, the InputWorkspace is cloned then the clone's position is changed.  The former is faster, especially for large workspaces. 
+
+Usage
+-----
+
+**Example:**
+
+.. testcode:: ExSetCrystalLocation
+
+  events = Load('BSS_11841_event.nxs')
+  sample = mtd['events'].getInstrument().getSample()
+  print('Sample position before SetCrystalLocation: {}'.format(sample.getPos()))
+  SetCrystalLocation(InputWorkspace=events, OutputWorkspace=events, NewX=0.1, NewY=0.1, NewZ=0.1)
+  print('Sample position after SetCrystalLocation: {}'.format(sample.getPos()))
+
+Output:
+
+.. testoutput:: ExSetCrystalLocation
+
+  Sample position before SetCrystalLocation: [0,0,0]
+  Sample position after SetCrystalLocation: [0.1,0.1,0.1]
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/SetSample-v1.rst b/docs/source/algorithms/SetSample-v1.rst
index ca36b085e2965ba238a672e63cf0ce0eeb73bf61..b30e5b18137c4c23dbe00de3b0664345e84114e4 100644
--- a/docs/source/algorithms/SetSample-v1.rst
+++ b/docs/source/algorithms/SetSample-v1.rst
@@ -22,6 +22,10 @@ The 3 arguments to this algorithm ``Environment``, ``Geometry`` and
 dictionaries specifying multiple parameters that relate to the
 respective argument.
 
+.. note:: Contrary to the :ref:`xml forms of defining the geometry
+          <HowToDefineGeometricShape>` which are in metres,
+          :py:obj:`dict` versions are in centimetres.
+
 Environment
 ###########
 
diff --git a/docs/source/algorithms/SetSampleMaterial-v1.rst b/docs/source/algorithms/SetSampleMaterial-v1.rst
index af4760a9cb00987720ecc38ef96cf7fc48686085..7dddeb7af0c40d7ca212721013404068a5a684fb 100644
--- a/docs/source/algorithms/SetSampleMaterial-v1.rst
+++ b/docs/source/algorithms/SetSampleMaterial-v1.rst
@@ -9,110 +9,15 @@
 Description
 -----------
 
-Sets the neutrons information in the sample. You can either enter
-details about the chemical formula or atomic number, or you can provide
-specific values for the attenuation and scattering cross sections and
-the sample number density. If you decide to provide specific values you
-must give values for all three (attenuation and scattering cross
-sections and the sample number density), and any formula information
-will be ignored. If you miss any of the three specific values then the
-other will be ignored.
-
-Neutron scattering lengths and cross sections of the elements and their
-isotopes have been taken from
-`NIST <http://www.ncnr.nist.gov/resources/n-lengths/list.html>`__.
-
-Chemical Composition with Examples
-##################################
-- ``H2 O`` - Isotopically averaged Hydrogen
-- ``(H2)2 O`` - Heavy water
-- ``D2 O`` - Another way to specify heavy water
-
-Enter a composition as a molecular formula of elements or isotopes.
-For example, basic elements might be ``H``, ``Fe`` or ``Si``, etc.
-A molecular formula of elements might be ``H4-N2-C3``, which
-corresponds to a molecule with 4 Hydrogen atoms, 2 Nitrogen atoms and
-3 Carbon atoms.  Each element in a molecular formula is followed by
-the number of the atoms for that element, specified **without a hyphen**,
-because each element is separated from other elements using a hyphen.
-
-The number of atoms can be integer or float, but must start with a
-digit, e.g. 0.6 is fine but .6 is not. This can be used to set elemental ratios
-within a chemical composition. For example 95.1% Vanadium 4.9% Niobium can be
-expressed as ``V0.951 Nb0.049``. *Warning: Using this representation will
-calculate all properties except for SampleNumberDensity which must be
-set manually if required*
-
-Isotopes may also be included in a :py:obj:`material
-<mantid.kernel.Material>` composition, and can be specified alone (as
-in ``Li7``), or in a molecular formula (as in ``(Li7)2-C-H4-N-Cl6``).
-Note, however, that No Spaces or Hyphens are allowed in an isotope
-symbol specification. Also Note that for isotopes specified in a
-molecular expression, the isotope must be enclosed by parenthesis,
-except for two special cases, ``D`` and ``T``, which stand for ``H2``
-and ``H3``, respectively.
-
-Cross Section Calculations
-##########################
-
-Each of the cross sections (:math:`\sigma`) are calculated according to
-
-.. math:: \sigma = \frac{1}{N_{atoms}}\sum_{i}\sigma_{i}n_{i}
-
-where :math:`N_{atoms} = \sum_{i}n_{i}`. A concrete example for the total
-cross section of ``D2 O``
-
-.. math:: \sigma = \frac{1}{2+1}\left( 7.64*2 + 4.232*1\right) = 6.504\ barns
-
-Number Density
-##############
-
-The number density is defined as
-
-.. math:: \rho_n = \frac{N_{atoms}ZParameter}{UnitCellVolume}
-
-It can can be generated in one of three ways:
-
-1. Specifying it directly with ``SampleNumberDensity``.
-2. Specifying the ``ZParameter`` and the ``UnitCellVolume`` (or letting
-   the algorithm calculate it from the OrientedLattice on the
-   ``InputWorkspace``).
-3. Specifying the mass density. In this case the number density is calculated as
-
-.. math:: \rho_n = \frac{N_{atoms} \rho_m N_A}{M_r}
-
-where :math:`\rho_m` is the mass density, :math:`N_A` is the Avogadro constant, and :math:`M_r` the relative molecular mass.
-
-Linear Absorption Coefficients
-##############################
-
-.. math:: \mu_s = \rho_n \frac{1}{N_{atoms}}\sum_{i}s_{i}n_{i} \text{ units of 1/cm}
-.. math:: s = \sigma_{total scattering}
-.. math:: \mu_a = \rho_n \frac{1}{N_{atoms}}\sum_{i}a_{i}n_{i} \text{ units of 1/cm}
-.. math:: a = \sigma_{absorption} (\lambda=1.8)
-
-A detailed version of this is found in [2].
-
-Normalized Laue
-###############
-
-The low-:math:`Q` limit of :math:`S(Q)` is :math:`-L` where :math:`L` is called the normalized Laue term
-
-.. math:: bAverage = <b_{coh}> = \frac{1}{N_{atoms}}\sum_{i}b_{coh,i}
-.. math:: bSquaredAverage = <b_{tot}^2> = \frac{1}{N_{atoms}}\sum_{i}b_{tot,i}^2
-.. math:: L = \frac{<b_{tot}^2>-<b_{coh}>^2}{<b_{coh}>^2}
-
-References
-----------
-
-The data used in this algorithm comes from the following paper.
-
-#. Varley F. Sears, *Neutron scattering lengths and cross sections*, Neutron News **3:3** (1992) 26
-   `doi: 10.1080/10448639208218770 <http://dx.doi.org/10.1080/10448639208218770>`_
-#. J. A. K. Howard, O. Johnson, A. J. Schultz and A. M. Stringer, *Determination of the neutron
-   absorption cross section for hydrogen as a function of wavelength with a pulsed neutron
-   source*, J. Appl. Cryst. (1987). 20, 120-122
-   `doi: 10.1107/S0021889887087028 <http://dx.doi.org/10.1107/S0021889887087028>`_
+Sets the neutrons information in the sample. You can either enter details 
+about the chemical formula or atomic number, or you can provide specific 
+values for the attenuation and scattering cross sections and the sample number 
+density. If you provide a chemical formula or atomic number, the cross 
+sections will be calculated from a database. If you decide to provide specific 
+cross sections, they will override the tabulated ones. For details of how the 
+various quantities are calculated, refer to the :ref:`Materials` concept page.
+
+.. note:: It is recommended that you use :ref:`SetSample <algm-SetSample>` instead.
 
 .. categories::
 
diff --git a/docs/source/algorithms/StatisticsOfTableWorkspace-v1.rst b/docs/source/algorithms/StatisticsOfTableWorkspace-v1.rst
index c53256425cb77e93e359f54406064e57e344bf98..70c3c34a4b1ffca081795ca28bc76386a78e7217 100644
--- a/docs/source/algorithms/StatisticsOfTableWorkspace-v1.rst
+++ b/docs/source/algorithms/StatisticsOfTableWorkspace-v1.rst
@@ -30,7 +30,7 @@ Usage
     stats = StatisticsOfTableWorkspace(ws)
 
     for idx in range(stats.rowCount()):
-        stat_name = stats.column('statistic')[idx]
+        stat_name = stats.column('Statistic')[idx]
         stat_value = stats.column('a')[idx]
         print('%s of column \'a\' is %.3f' % (stat_name, stat_value))
 
@@ -38,11 +38,11 @@ Output:
 
 .. testoutput:: ExStatisticsOfTableWorkspace
 
-    standard_deviation of column 'a' is 1.118
-    minimum of column 'a' is 1.000
-    median of column 'a' is 2.500
-    maximum of column 'a' is 4.000
-    mean of column 'a' is 2.500
+    StandardDev of column 'a' is 1.118
+    Minimum of column 'a' is 1.000
+    Median of column 'a' is 2.500
+    Maximum of column 'a' is 4.000
+    Mean of column 'a' is 2.500
 
 .. categories::
 
diff --git a/docs/source/algorithms/WeightedMean-v1.rst b/docs/source/algorithms/WeightedMean-v1.rst
index ac5a7caa55b9c8472a7d4ab487c2b0e9d82d344e..c67687674c0048643817a118eefe939bbdad9201 100644
--- a/docs/source/algorithms/WeightedMean-v1.rst
+++ b/docs/source/algorithms/WeightedMean-v1.rst
@@ -9,9 +9,17 @@
 Description
 -----------
 
-The algorithm calculates the weighted mean of two workspaces. This is useful when working with distributions rather than histograms, particularly when counting statistics are poor and it is possible that the value of one data set is statistically insignificant but differs greatly from the other. In such a case simply calculating the average of the two data sets would produce a spurious result. This algorithm will eventually be modified to take a list of workspaces as an input.
+The algorithm calculates the weighted mean of two workspaces. This is useful when working with distributions rather than histograms, particularly when counting statistics are poor and it is possible that the value of one data set is statistically insignificant but differs greatly from the other. In such a case simply calculating the average of the two data sets would produce a spurious result.
+If each input workspace and the standard deviation are labelled :math:`w_i` and :math:`sigma_i`, respectively, and there
+are *N* workspaces then the weighted mean is computed as:
 
-:math:`y=\frac{\sum\frac{x_i}{\sigma^{2}_i}}{\sum\frac{1}{\sigma^{2}_i}}`
+.. math::
+
+   m = \frac{\sum_{i=0}^{N-1}\frac{w_i}{\sigma^{2}_i}}{\sum_{i=0}^{N-1}\frac{1}{\sigma^{2}_i}}
+
+where *m* is the output workspace. The *x* values are copied from the first input workspace.
+
+The input workspaces must be compatible with respect to size, units, and whether they are distributions or not.
 
 Usage
 -----
diff --git a/docs/source/api/python/mantid/kernel/RebinParamsValidator.rst b/docs/source/api/python/mantid/kernel/RebinParamsValidator.rst
new file mode 100644
index 0000000000000000000000000000000000000000..16eac58b20a3993b72e182ea4c773cc824f0af51
--- /dev/null
+++ b/docs/source/api/python/mantid/kernel/RebinParamsValidator.rst
@@ -0,0 +1,15 @@
+======================
+ RebinParamsValidator
+======================
+
+This is a Python binding to the C++ class Mantid::Kernel::RebinParamsValidator.
+
+*bases:* :py:obj:`mantid.kernel.IValidator`
+
+.. module:`mantid.kernel`
+
+.. autoclass:: mantid.kernel.RebinParamsValidator 
+    :members:
+    :undoc-members:
+    :inherited-members:
+
diff --git a/docs/source/concepts/ComponentInfo.rst b/docs/source/concepts/ComponentInfo.rst
index 80ce4b8f77f7d0230210e6eb3f9afe5fae3778a6..5aa5257c45e70e8cbddc993c29df10203163e9a6 100644
--- a/docs/source/concepts/ComponentInfo.rst
+++ b/docs/source/concepts/ComponentInfo.rst
@@ -9,7 +9,26 @@ ComponentInfo
 
 Introduction
 ------------
-``ComponentInfo`` provides faster and simpler access to instrument/beamline geometry as required by Mantid :ref:`Algorithms <Algorithm>` than was possible using :ref:`Instrument`. ``ComponentInfo`` and :ref:`DetectorInfo` are designed as full replacements to :ref:`Instrument`. At the time of writing, `ComponentInfo` is incomplete.
+``ComponentInfo`` provides faster and simpler access to instrument/beamline geometry as required by Mantid :ref:`Algorithms <Algorithm>` than was possible using :ref:`Instrument`. ``ComponentInfo`` and :ref:`DetectorInfo` are designed as full replacements to :ref:`Instrument`. 
 
-Until complete, :ref:`Instrument Access Layers <InstrumentAccessLayers>` provides the best details on how `ComponentInfo` can be used in it's current state of implementation.
+:ref:`Instrument Access Layers <InstrumentAccessLayers>` provides details on how `DetectorInfo` interacts with other geometry access layers.
+
+Python Interface
+----------------
+
+Examples of using ``ComponentInfo`` in python
+
+Print indices of detectors in "bank1" that are masked
+
+.. code-block:: python 
+
+  from mantid.simpleapi import CreateSampleWorkspace
+
+  ws = CreateSampleWorkspace()
+  comp_info = ws.componentInfo()
+  det_info = ws.detectorInfo()
+  bank_index compinfo.indexOfAny('bank1')
+  for det_index in compinfo.detectorsInSubtree(bank_index):
+      if det_info.isMasked(int(det_index)):
+          print('Masked detector of bank1, ', det_index)
 
diff --git a/docs/source/concepts/DetectorInfo.rst b/docs/source/concepts/DetectorInfo.rst
index 9af68a98968c52cbe8d17ea303e5fa9c72ada3f9..a7e6783b04c445f992e6565ddc7c6f74a62cd3fb 100644
--- a/docs/source/concepts/DetectorInfo.rst
+++ b/docs/source/concepts/DetectorInfo.rst
@@ -9,6 +9,37 @@ DetectorInfo
 
 Introduction
 ------------
-``DetectorInfo`` provides faster and simpler access to instrument/beamline detector geometry and metadata as required by Mantid :ref:`Algorithms <Algorithm>` than was possible using :ref:`Instrument`. ``DetectorInfo`` and :ref:`ComponentInfo` are designed as full replacements to :ref:`Instrument`. At the time of writing, `DetectorInfo` is incomplete.
+``DetectorInfo`` provides faster and simpler access to instrument/beamline detector geometry and metadata as required by Mantid :ref:`Algorithms <Algorithm>` than was possible using :ref:`Instrument`. ``DetectorInfo`` and :ref:`ComponentInfo` are designed as full replacements to :ref:`Instrument`. 
 
-Until complete, :ref:`Instrument Access Layers <InstrumentAccessLayers>` provides the best details on how `DetectorInfo` can be used in it's current state of implementation.
+:ref:`Instrument Access Layers <InstrumentAccessLayers>` provides details on how `DetectorInfo` interacts with other geometry access layers.
+
+Python Interface
+----------------
+
+Examples of using ``DetectorInfo`` in python
+
+Mask detectors at some distance from the source
+
+.. code-block:: python 
+
+  from mantid.simpleapi import CreateSampleWorkspace
+
+  # Test workspace with instrument
+  ws = CreateSampleWorkspace()
+  det_info = ws.detectorInfo();
+  for item in det_info:
+      if not item.isMonitor and item.l2 > 2.0:
+          item.setMasked(True)
+
+Print detectors with scattering angle
+
+.. code-block:: python 
+
+  from mantid.simpleapi import CreateSampleWorkspace
+
+  # Test workspace with instrument
+  ws = CreateSampleWorkspace()
+  det_info = ws.detectorInfo()
+  for item in det_info:
+      if item.l2 > 0.01:
+         print(item.index) 
diff --git a/docs/source/concepts/EventFiltering.rst b/docs/source/concepts/EventFiltering.rst
index d86f072e1dbd1578c81e99ca95cd0bd21b04c47a..c77137bbefa98cf7fbd1e2437740ac4bec866820 100644
--- a/docs/source/concepts/EventFiltering.rst
+++ b/docs/source/concepts/EventFiltering.rst
@@ -7,86 +7,117 @@ Event Filtering
 .. contents::
    :local:
 
-In MantidPlot, there are a few algorithms working with event
-filtering.  These algorithms are :ref:`algm-FilterByTime`,
-:ref:`algm-FilterByLogValue`, :ref:`algm-FilterEvents`, and
-:ref:`algm-GenerateEventsFilter`.
+In mantid, there are a variety of ways to filter events that are in an
+:ref:`EventWorkspace`. They are :ref:`FilterByTime
+<algm-FilterByTime>` and :ref:`FilterByLogValue
+<algm-FilterByLogValue>` which will create a filter and apply it in a
+single step. The other way to filter events is to use
+:ref:`FilterEvents <algm-FilterEvents>` which allows for a variety of
+workspaces to specify how an :ref:`EventWorkspace` is split. This
+document focuses on how the create these workspaces and will largely
+ignore :ref:`FilterByTime <algm-FilterByTime>` and
+:ref:`FilterByLogValue <algm-FilterByLogValue>`.
 
 How to generate event filters
 =============================
 
-Generating filters explicitly
------------------------------
-
-:ref:`algm-FilterEvents` reads and parses a
-:class:`mantid.api.ISplittersWorkspace` object to generate a list of
-:ref:`SplittingIntervals <SplittingInterval>`, which are used to split
-neutron events to specified output workspaces according to the times
-that they arrive detectors.
-
-There can be two approaches to create a
-:class:`mantid.api.ISplittersWorkspace`.
-
-* :ref:`algm-GenerateEventsFilter` generate event filters by either by
-  time or log value.  The output filters are stored in a
-  :ref:`SplittersWorkspace`, which is taken as an input property of
-  :ref:`algm-FilterEvents`.
-
-* Users can create a :class:`mantid.api.ISplittersWorkspace` from scrach from Python
-  script, because :class:`mantid.api.ISplittersWorkspace` inherits from
-  :ref:`TableWorkspace <Table Workspaces>`.
-
-Generating inexplicit filters
------------------------------
-
-:ref:`algm-FilterByTime` and :ref:`algm-FilterByLogValue` generate event filters during execution.
-
-* :ref:`algm-FilterByTime` generates a set of :ref:`SplittingInterval`
-  according to user-specified setup for time splicing;
-
-* :ref:`algm-FilterByLogValue` generates a set of
-  :ref:`SplittingInterval` according to the value of a specific sample
-  log.
-
-:ref:`algm-GenerateEventsFilter` and :ref:`algm-FilterEvents` vs :ref:`algm-FilterByTime` and :ref:`algm-FilterByLogValue`
---------------------------------------------------------------------------------------------------------------------------
-
-* If :ref:`algm-GenerateEventsFilter` and :ref:`algm-FilterEvents` are
-  set up correctly, they can have the same functionality as
-  :ref:`algm-FilterByTime` and :ref:`algm-FilterByLogValue`.
-
-* :ref:`algm-FilterEvents` is able to filter neutron events by either
-  their pulse times or their absolute times.  An neutron event's
-  absolute time is the summation of its pulse time and TOF.
-
-* :ref:`algm-FilterByLogValue` and :ref:`algm-FilterByTime` can only
-  split neutron events by their pulse time.
-
-Types of events filters
-=======================
-
-Filtering by :ref:`SplittingInterval`
--------------------------------------
-
-:ref:`SplittingInterval` is an individual class to indicate an
-independent time splitter.  Any event can be filtered by a
-:ref:`SplittingInterval` object.
-
-:ref:`SplittersWorkspace` is a :ref:`TableWorkspace <Table
-Workspaces>` that stors a set of :ref:`SplittingInterval`.
-
-Filtering by duplicate entries/booleans
----------------------------------------
-
-Duplicate entries in a :ref:`TimeSeriesProperty` and boolean type of
-:ref:`TimeSeriesProperty` are used in MantidPlot too to serve as time
-splitters.
-
-These two are applied in the MantidPlot log viewing functionality and
-unfortunately intrudes into :ref:`TimeSeriesProperty`.
-
-As time splitters are better to be isolated from logs, which are
-recorded in :ref:`TimeSeriesProperty`, it is not
-recommended to set up event filters by this approach.
+Implicit filters
+----------------
+
+:ref:`algm-FilterByTime` and :ref:`algm-FilterByLogValue` internally
+generate event filters during execution that are not exposed to the
+user. These algorithms can only split the neutron events by pulse
+time and do not provide the equivalent of a ``FastLog=True`` option.
+
+Explicit filters
+----------------
+
+:ref:`algm-FilterEvents` takes either a :class:`SplittersWorkspace
+<mantid.api.ISplittersWorkspace>`, :ref:`TableWorkspace <Table
+Workspaces>`, or :ref:`MatrixWorkspace <MatrixWorkspace>` as the
+``SplittersWorkspace``. The events are split into output workspaces
+according to the times that they arrive detectors.
+
+:ref:`GenerateEventsFilter <algm-GenerateEventsFilter>` will create a
+:class:`SplittersWorkspace <mantid.api.ISplittersWorkspace>` based on
+its various options. This result can be supplied as the
+``SplittersWorkspace`` input property of ref:`algm-FilterEvents`. It
+will also generate an ``InformationWorkspace`` which can be passed
+along to :ref:`GenerateEventsFilter <algm-GenerateEventsFilter>`.
+Depending on the parameters in :ref:`GenerateEventsFilter
+<algm-GenerateEventsFilter>`, the events will be filtered based on
+their pulse times or their absolute times.  An neutron event's
+absolute time is the summation of its pulse time and TOF.
+
+Custom event filters
+====================
+
+Sometimes one wants to filter events based on arbitrary conditions. In
+this case, one needs to go beyond what existing algorithms can do. For
+this, one must generate their own splitters workspace. The workspace
+is generally 3 columns, with the first two being start and stop times
+and the third being the workspace index to put the events into. For
+filtering with time relative to the start of the run, the first two
+columns are ``float``. To specify the times as absolute, in the case
+of filtering files that will be summed together, the first two columns
+should be ``int64``. For both of the examples below, the filter
+workspaces are created using the following function:
+
+.. code-block:: python
+
+   def create_table_workspace(table_ws_name, column_def_list):
+      CreateEmptyTableWorkspace(OutputWorkspace=table_ws_name)
+      table_ws = mtd[table_ws_name]
+      for col_tup in column_def_list:
+          data_type = col_tup[0]
+          col_name = col_tup[1]
+          table_ws.addColumn(data_type, col_name)
+
+      return table_ws
+
+Relative time
+-------------
+
+The easiest way to generate a custom event filter is to make one
+relative to the start time of the run or relative to a specified
+epoch. As the times in the table are seconds, a table can be created
+and used
+
+.. code-block:: python
+
+   filter_rel = create_table_workspace('custom_relative', [('float', 'start'), ('float', 'stop'), ('str', 'target')])
+   filter_rel.addRow((0,9500, '0'))
+   filter_rel.addRow((9500,19000, '1'))
+   FilterEvents(InputWorkspace='ws', SplitterWorkspace=filter_rel,
+                GroupWorkspaces=True, OutputWorkspaceBaseName='relative', RelativeTime=True)
+
+This will generate an event filter relative to the start of the
+run. Specifying the ``FilterStartTime`` in :ref:`FilterEvents
+<algm-FilterEvents>`, one can specify a different time that filtering
+will be relative to.
+
+Absolute time
+-------------
+
+If instead a custom filter is to be created with absolute time, the
+time must be processed somewhat to go into the table workspace. Much of the
+
+.. code-block:: python
+
+   abs_times = [datetime64('2014-12-12T09:11:22.538096666'), datetime64('2014-12-12T11:45:00'), datetime64('2014-12-12T14:14:00')]
+   # convert to time relative to GPS epoch
+   abs_times = [time - datetime64('1990-01-01T00:00') for time in abs_times]
+   # convert to number of seconds
+   abs_times = [float(time / timedelta64(1, 's')) for time in abs_times]
+
+   filter_abs = create_table_workspace('custom_absolute', [('float', 'start'), ('float', 'stop'), ('str', 'target')])
+   filter_abs.addRow((abs_times[0], abs_times[1], '0'))
+   filter_abs.addRow((abs_times[1], abs_times[2], '1'))
+   FilterEvents(InputWorkspace='PG3_21638', SplitterWorkspace=filter_abs,
+                GroupWorkspaces=True, OutputWorkspaceBaseName='absolute', RelativeTime=False)
+
+Be warned that specifying ``RelativeTime=True`` with a table full of
+absolute times will almost certainly generate output workspaces
+without any events in them.
 
 .. categories:: Concepts
diff --git a/docs/source/concepts/FitFunctionsInPython.rst b/docs/source/concepts/FitFunctionsInPython.rst
index af1f53d3fe63fb4bd48b2e6daa661137673f10b7..6ba7ef2b30e8bff89806910a1582ded4f996ceca 100644
--- a/docs/source/concepts/FitFunctionsInPython.rst
+++ b/docs/source/concepts/FitFunctionsInPython.rst
@@ -204,6 +204,21 @@ Also one can put parameters into the function when evaluating.
 
 This enables one to fit the functions with ``scipy.optimize.curve_fit``.  
 
+Errors
+------
+
+The errors assoicated with a given parameter can be accessed using the ``getError`` method.
+``getError`` takes either the parameter name or index as input. For example to get the error
+on ``A1`` in the above polynomial, the code is:
+
+.. code:: python
+
+    # Find the parameter error by index
+    error_A1 = p.getError(1)
+    # Find the parameter error by name
+    error_A1 = p.getError('A1')
+
+
 Plotting
 --------
 Functions may be plotted by calling the ``plot`` method of the function.
diff --git a/docs/source/concepts/InstrumentAccessLayers.rst b/docs/source/concepts/InstrumentAccessLayers.rst
index 65543664651ed6df60ae362c3e608b141c585e92..7161ca559b38b64f7c4f8b0704ced739cd6f4eac 100644
--- a/docs/source/concepts/InstrumentAccessLayers.rst
+++ b/docs/source/concepts/InstrumentAccessLayers.rst
@@ -37,12 +37,20 @@ There is also a near-complete implementation of the "real" ``DetectorInfo`` clas
 
 ``ExperimentInfo`` now also provides a method ``mutableDetectorInfo()`` so that non-const access to the DetectorInfo is possible for purposes of writing detector related information such as positions or rotations. 
 
+The python interface to ``DetectorInfo`` has matured, and includes widespread immutable access via iterators. The iterators can also be used to set masking flags.
+
+See :ref:`DetectorInfo <DetectorInfo>` for more information.
+
 ComponentInfo
 ______________
 ``ComponentInfo`` can be obtatined from a call to ``ExperimentInfo::componentInfo()`` (usually this method would be called on ``MatrixWorkspace``). Much like ``DetectorInfo``, the ``ComponentInfo`` yielded from this method call is a wrapper, which contains shape and index information, that cannot yet be moved in to the real ``Beamline::ComponentInfo``. However, replacing existing usage of ``IComponent`` and ``IObjComponent`` wherever possible with ``ComponentInfo`` across the framework will represent a major step forwards.
 
 For writing to the component tree. You can extract a non-const ``ComponentInfo`` via ``ExperimentInfo::mutableComponentInfo``.
 
+The python interface to ``ComponentInfo`` has matured, and now provides equal, if not better support than the ``Instrument`` API for navigating the high-level instrument. Iterator support has also been provided for ``ComponentInfo``.
+
+See :ref:`ComponentInfo <ComponentInfo>` for more information.
+
 Changes for Rollout
 -------------------
 
diff --git a/docs/source/concepts/Materials.rst b/docs/source/concepts/Materials.rst
new file mode 100644
index 0000000000000000000000000000000000000000..58ea2cfc42990d5049bbee41878eefaca8816426
--- /dev/null
+++ b/docs/source/concepts/Materials.rst
@@ -0,0 +1,104 @@
+.. _Materials:
+
+Materials
+=========
+
+.. contents::
+
+Neutron scattering lengths and cross sections of the elements and their
+isotopes have been taken from
+`NIST <http://www.ncnr.nist.gov/resources/n-lengths/list.html>`__.
+
+Chemical Composition with Examples
+##################################
+- ``H2 O`` - Isotopically averaged Hydrogen
+- ``(H2)2 O`` - Heavy water
+- ``D2 O`` - Another way to specify heavy water
+
+Enter a composition as a molecular formula of elements or isotopes.
+For example, basic elements might be ``H``, ``Fe`` or ``Si``, etc.
+A molecular formula of elements might be ``H4-N2-C3``, which
+corresponds to a molecule with 4 Hydrogen atoms, 2 Nitrogen atoms and
+3 Carbon atoms.  Each element in a molecular formula is followed by
+the number of the atoms for that element, specified **without a hyphen**,
+because each element is separated from other elements using a hyphen.
+
+The number of atoms can be integer or float, but must start with a
+digit, e.g. 0.6 is fine but .6 is not. This can be used to set elemental ratios
+within a chemical composition. For example 95.1% Vanadium 4.9% Niobium can be
+expressed as ``V0.951 Nb0.049``. *Warning: Using this representation will
+calculate all properties except for SampleNumberDensity which must be
+set manually if required*
+
+Isotopes may also be included in a :py:obj:`material
+<mantid.kernel.Material>` composition, and can be specified alone (as
+in ``(Li7)``), or in a molecular formula (as in ``(Li7)2-C-H4-N-Cl6``).
+Note, however, that No Spaces or Hyphens are allowed in an isotope
+symbol specification. Also Note that for isotopes specified in a
+molecular expression, the isotope must be enclosed by parenthesis,
+except for two special cases, ``D`` and ``T``, which stand for ``H2``
+and ``H3``, respectively.
+
+Cross Section Calculations
+##########################
+
+Each of the cross sections (:math:`\sigma`) are calculated according to
+
+.. math:: \sigma = \frac{1}{N_{atoms}}\sum_{i}\sigma_{i}n_{i}
+
+where :math:`N_{atoms} = \sum_{i}n_{i}`. A concrete example for the total
+cross section of ``D2 O``
+
+.. math:: \sigma = \frac{1}{2+1}\left( 7.64*2 + 4.232*1\right) = 6.504\ barns
+
+Number Density
+##############
+
+The number density is defined as
+
+.. math:: \rho_n = \frac{N_{atoms}ZParameter}{UnitCellVolume}
+
+It can can be generated in one of three ways:
+
+1. Specifying it directly with ``SampleNumberDensity``.
+2. Specifying the ``ZParameter`` and the ``UnitCellVolume`` (or letting
+   the algorithm calculate it from the OrientedLattice on the
+   ``InputWorkspace``).
+3. Specifying the mass density. In this case the number density is calculated as
+
+.. math:: \rho_n = \frac{N_{atoms} \rho_m N_A}{M_r}
+
+where :math:`\rho_m` is the mass density, :math:`N_A` is the Avogadro constant, and :math:`M_r` the relative molecular mass.
+
+Linear Absorption Coefficients
+##############################
+
+.. math:: \mu_s = \rho_n \frac{1}{N_{atoms}}\sum_{i}s_{i}n_{i} \text{ units of 1/cm}
+.. math:: s = \sigma_{total scattering}
+.. math:: \mu_a = \rho_n \frac{1}{N_{atoms}}\sum_{i}a_{i}n_{i} \text{ units of 1/cm}
+.. math:: a = \sigma_{absorption} (\lambda=1.8)
+
+A detailed version of this is found in [2].
+
+Normalized Laue
+###############
+
+The low-:math:`Q` limit of :math:`S(Q)` is :math:`-L` where :math:`L` is called the normalized Laue term
+
+.. math:: bAverage = <b_{coh}> = \frac{1}{N_{atoms}}\sum_{i}b_{coh,i}
+.. math:: bSquaredAverage = <b_{tot}^2> = \frac{1}{N_{atoms}}\sum_{i}b_{tot,i}^2
+.. math:: L = \frac{<b_{tot}^2>-<b_{coh}>^2}{<b_{coh}>^2}
+
+References
+----------
+
+The data used in this algorithm comes from the following paper.
+
+#. Varley F. Sears, *Neutron scattering lengths and cross sections*, Neutron News **3:3** (1992) 26
+   `doi: 10.1080/10448639208218770 <http://dx.doi.org/10.1080/10448639208218770>`_
+#. J. A. K. Howard, O. Johnson, A. J. Schultz and A. M. Stringer, *Determination of the neutron
+   absorption cross section for hydrogen as a function of wavelength with a pulsed neutron
+   source*, J. Appl. Cryst. (1987). 20, 120-122
+   `doi: 10.1107/S0021889887087028 <http://dx.doi.org/10.1107/S0021889887087028>`_
+
+.. categories:: Concepts
diff --git a/docs/source/concepts/SampleEnvironment.rst b/docs/source/concepts/SampleEnvironment.rst
index 48ed51a372bce2dfc35ff964ac12cf82a8943e59..8cc1f99d7fbe1c6cafbdb6f5a55a755a2b2dfd00 100644
--- a/docs/source/concepts/SampleEnvironment.rst
+++ b/docs/source/concepts/SampleEnvironment.rst
@@ -16,9 +16,11 @@ Specification
 
 A sample environment is defined by:
 
-- one or more available containers, each with a defined geometry & composition
-- optional additional components that will be in the beam, each with their own
-  geometry and composition.
+- one or more available containers, each with a defined :ref:`geometry
+  <HowToDefineGeometricShape>` and :ref:`composition <Materials>`
+- optional additional components that will be in the beam, each with
+  their own :ref:`geometry <HowToDefineGeometricShape>` and
+  :ref:`composition <Materials>`
 
 At a minimum a sample environment is expected to define a container with both its
 geometry and composition.
@@ -71,17 +73,17 @@ is used to reference the material when defining a container or component.
 The other attributes define the properties of the material. The allowed attributes
 map to the arguments of a similar name on the :ref:`SetSampleMaterial <algm-SetSampleMaterial>` algorithm
 
-- formula
-- atomicnumber
-- massnumber
-- numberdensity
-- zparameter
-- unitcellvol
-- massdensity
-- totalscatterxsec
-- cohscatterxsec
-- incohscatterxsec
-- absorptionxsec
+- ``formula``
+- ``atomicnumber``
+- ``massnumber``
+- ``numberdensity``
+- ``zparameter``
+- ``unitcellvol``
+- ``massdensity``
+- ``totalscatterxsec``
+- ``cohscatterxsec``
+- ``incohscatterxsec``
+- ``absorptionxsec``
 
 Non-container Components
 ------------------------
diff --git a/docs/source/images/MDNorm_elastic_sym.png b/docs/source/images/MDNorm_elastic_sym.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec23051a0e48bef1a3d8dd0cdfb457c07bac797c
Binary files /dev/null and b/docs/source/images/MDNorm_elastic_sym.png differ
diff --git a/docs/source/images/MDNorm_inelastic_sym.png b/docs/source/images/MDNorm_inelastic_sym.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d730042c4f67c05765631d62de8ed695c0291b3
Binary files /dev/null and b/docs/source/images/MDNorm_inelastic_sym.png differ
diff --git a/docs/source/interfaces/ISIS Reflectometry.rst b/docs/source/interfaces/ISIS Reflectometry.rst
index b20f2fab93624916583a67a5741bca4a33b2cfb2..253daa9ee02676155185311cd56b1329512123ac 100644
--- a/docs/source/interfaces/ISIS Reflectometry.rst	
+++ b/docs/source/interfaces/ISIS Reflectometry.rst	
@@ -497,7 +497,7 @@ Interface to re-link it.
 
 Live data monitoring has the following requirements:
 
-- EPICS support must be installed in Mantid. This is included by default on Windows but see the instructions `here <https://www.mantidproject.org/PyEpics_In_Mantid>`_ for other platforms.
+- CaChannel must be installed in Mantid. See the instructions `here <https://www.mantidproject.org/CaChannel_In_Mantid>`_.
 - The instrument must be on IBEX or have additional processes installed to supply the EPICS values. If it does not, you will get an error that live values could not be found for `Theta` and the slits.
 
 
diff --git a/docs/source/interfaces/Indirect Data Analysis.rst b/docs/source/interfaces/Indirect Data Analysis.rst
index 93183a1838f8d8be13a8a1dc7b213d02dba30fde..a7b41e296a60b9f3dce3bb2b22033f06ca34ef4a 100644
--- a/docs/source/interfaces/Indirect Data Analysis.rst	
+++ b/docs/source/interfaces/Indirect Data Analysis.rst	
@@ -1,4 +1,4 @@
-Indirect Data Analysis
+Indirect Data Analysis
 ======================
 
 .. contents:: Table of Contents
@@ -69,6 +69,12 @@ Input File
   Specify a range of input files that are either reduced (*_red.nxs*) or
   :math:`S(Q, \omega)`.
 
+Group Input
+  Provides an option to group or ungroup the input data.
+
+Load History
+  If unchecked the input workspace will be loaded without it's history.
+
 Integration Range
   The energy range over which to integrate the values.
 
@@ -93,8 +99,10 @@ SE log value
   specified value in the instrument parameters file, and in the absence of such
   specification, defaults to "last value")
 
-Plot Result
-  If enabled will plot the result as a spectra plot.
+Plot Spectrum
+  If enabled it will plot the spectrum represented by the workspace index in the 
+  neighbouring spin box. This workspace index is the index of the spectrum within the 
+  workspace selected in the combobox.
 
 Save Result
   If enabled the result will be saved as a NeXus file in the default save
@@ -178,11 +186,16 @@ Save Result
   directory.
   
 Tiled Plot
-  Produces a tiled plot of the output workspaces generated.
+  Produces a tiled plot of spectra included within the range for the output workspaces 
+  generated. There is a maximum of 18 spectra allowed for a tiled plot. 
 
 Monte Carlo Error Calculation - Number Of Iterations
-  The number of iterations to perform in the Monte Carlo routine for error
-  calculation in I(Q,t)
+  The number of iterations to perform in the Monte Carlo routine for error calculation 
+  in I(Q,t). 
+
+Monte Carlo Error Calculation - Calculate Errors
+  The calculation of errors using a Monte Carlo implementation can be skipped by ticking 
+  the Calculate Errors checkbox.
 
 A note on Binning
 ~~~~~~~~~~~~~~~~~
@@ -271,11 +284,11 @@ Options
 
 Sample
   Either a reduced file (*_red.nxs*) or workspace (*_red*) or an :math:`S(Q,
-  \omega)` file (*_sqw.nxs*) or workspace (*_sqw*).
+  \omega)` file (*_sqw.nxs*, *_sqw.dave*) or workspace (*_sqw*).
 
 Resolution
   Either a resolution file (_res.nxs) or workspace (_res) or an :math:`S(Q,
-  \omega)` file (*_sqw.nxs*) or workspace (*_sqw*).
+  \omega)` file (*_sqw.nxs*, *_sqw.dave*) or workspace (*_sqw*).
 
 Use Delta Function
   Found under 'Custom Function Groups'. Enables use of a delta function.
@@ -424,10 +437,11 @@ The 'Plot Guess' check-box can be used to enable/disable the guess curve in the
 Output
 ~~~~~~
 
-The results of the fit may be plot and saved under the 'Output' section of the fitting interfaces.
+The results of the fit may be plotted and saved under the 'Output' section of the fitting interfaces.
 
-Next to the 'Plot Output' label, you can select a parameter to plot and then click 'Plot' to plot it across the
-fit spectra (if multiple data-sets have been used, a separate plot will be produced for each data-set).
+Next to the 'Plot Output' label, you can select a parameter to plot and then click 'Plot' to plot it with error 
+bars across the fit spectra (if multiple data-sets have been used, a separate plot will be produced for each data-set). 
+The 'Plot Output' options will be disabled after a fit if there is only one data point for the parameters.
 
 Clicking the 'Save Result' button will save the result of the fit to your default save location.
 
@@ -481,9 +495,9 @@ input workspace, using the fitted values from the previous spectrum as input
 values for fitting the next. This is done by means of the
 :ref:`IqtFitSequential <algm-IqtFitSequential>` algorithm.
 
-A sequential fit is run by clicking the Run button at the bottom of the tab, a
-single fit can be done using the Fit Single Spectrum button underneath the
-preview plot.
+A sequential fit is run by clicking the Run button seen just above the output 
+options, a single fit can be done using the Fit Single Spectrum button underneath 
+the preview plot.
 
 Spectrum Selection
 ~~~~~~~~~~~~~~~~~~
diff --git a/docs/source/release/v3.14.0/diffraction.rst b/docs/source/release/v3.14.0/diffraction.rst
index ba61cc82b62e7a67f4a6db354a82645ff150940a..131bbab7513af585788e1eb4ffb4d5e87798788c 100644
--- a/docs/source/release/v3.14.0/diffraction.rst
+++ b/docs/source/release/v3.14.0/diffraction.rst
@@ -20,15 +20,22 @@ Improvements
 - :ref:`SNAPReduce <algm-SNAPReduce>` now has progress bar and all output workspaces have history
 - :ref:`SNAPReduce <algm-SNAPReduce>` has been completely refactored. It now uses :ref:`AlignAndFocusPowderFromFiles <algm-AlignAndFocusPowderFromFiles>` for a large part of its functionality. It has progress bar and all output workspaces have history. It is also more memory efficient by reducing the number of temporary workspaces created.
 - :ref:`AlignAndFocusPowder <algm-AlignAndFocusPowder>` and :ref:`AlignAndFocusPowderFromFiles <algm-AlignAndFocusPowderFromFiles>` now support outputting the unfocussed data and weighted events (with time). This allows for event filtering **after** processing the data.
+- :ref:`AlignAndFocusPowderFromFiles <algm-AlignAndFocusPowderFromFiles>` has a significant performance improvement when used with chunking
 - :ref:`LoadWAND <algm-LoadWAND>` has grouping option added and loads faster
 - Mask workspace option added to :ref:`WANDPowderReduction <algm-WANDPowderReduction>`
 - :ref:`Le Bail concept page <Le Bail Fit>` moved from mediawiki
 - Rework of :ref:`powder diffraction calibration <Powder Diffraction Calibration>` documentation
+- New TOPAZ instrument geometry for 2019 run cycle
 
 
 Single Crystal Diffraction
 --------------------------
 
+New Algorithms
+##############
+
+- The new algorithm :ref:`MDNorm <algm-MDNorm>` can be used to calculate cross section for single crystal diffraction measurements.
+
 Improvements
 ############
 
@@ -40,6 +47,9 @@ Improvements
 - :ref:`SaveIsawPeaks <algm-SaveIsawPeaks>` now has option to renumber peaks sequentially.
 - SCD Event Data Reduction Diffraction Interface now has option to create MD HKL workspace.
 - :ref:`IntegratePeaksUsingClusters <algm-IntegratePeaksUsingClusters>` will now treat NaN's as background.
+- SCD Event Data Reduction Diffraction Interface now adds goniometer for CORELLI and used proton charge as monitor count if no monitors are in input file.
+- :ref:`SetCrystalLocation <algm-SetCrystalLocation>` is a new algorithm to set the sample location in events workspaces.
+- :ref:`OptimizeCrystalPlacementByRun <algm-OptimizeCrystalPlacementByRun>` is new algorithm to update the sample position for each run in a peaks workspace.
 
 Bugfixes
 ########
@@ -50,7 +60,7 @@ Bugfixes
 - :ref:`SaveIsawPeaks <algm-SaveIsawPeaks>` does not have duplicate peak numbers when saving PeaksWorkspaces with more than one RunNumber.
 - :ref:`LoadIsawPeaks <algm-LoadIsawPeaks>` now loads the calibration from the peaks file correctly.
 
-- :ref:`OptimizeCrystalPlacement <algm-OptimizeCrystalPlacement>` now updates the sample location used by peaks.  Previously, the sample was effectively left unmoved.
+- :ref:`OptimizeCrystalPlacement <algm-OptimizeCrystalPlacement>` now updates the sample location used by peaks.  Previously, the sample was effectively left unmoved. Default for indexing tolerance was lowered to 0.15 and can now be called more than once without error.
 
 Powder Diffraction
 ------------------
diff --git a/docs/source/release/v3.14.0/direct_inelastic.rst b/docs/source/release/v3.14.0/direct_inelastic.rst
index cf3e1b1093888213f396f16a745dbdc6c8f2e175..d20ffa1249b2deadc7e9912699252eddd465b8a2 100644
--- a/docs/source/release/v3.14.0/direct_inelastic.rst
+++ b/docs/source/release/v3.14.0/direct_inelastic.rst
@@ -18,6 +18,7 @@ New Algorithms
 
 - Added a new algorithm to ILL's reduction workflow: :ref:`DirectILLTubeBackground <algm-DirectILLTubeBackground>` which can be used to calculate the time-independent backgrounds for instruments with PSD detectors such as IN5.
 - The new algorithm :ref:`SofTwoThetaTOF <algm-SofTwoThetaTOF>` can be used to convert a workspace from (spectrum number, TOF) units to (:math:`2\theta`, TOF) averaging the intensities over constant scattering angles.
+- The new algorithm :ref:`MDNorm <algm-MDNorm>` can be used to calculate cross section for single crystal direct inelastic measurements.
 
 Improvements
 ############
diff --git a/docs/source/release/v3.14.0/framework.rst b/docs/source/release/v3.14.0/framework.rst
index ee2aa82106053100193f08e01264d740062e8783..682418f412c2a702268f910e8554a5fc047a6023 100644
--- a/docs/source/release/v3.14.0/framework.rst
+++ b/docs/source/release/v3.14.0/framework.rst
@@ -38,7 +38,7 @@ Archive Searching
 SNS / ONCat
 ###########
 
-- SNS file searching has been moved to `ONCAT <https://oncat.ornl.gov/>`_
+- SNS file searching has been moved to `ONCAT <https://oncat.ornl.gov/>`_. Due to auto-updating of the ``Facilities.xml``, this was done by directing ``SNSDataSearch`` and ``ORNLDataSearch`` to both use ONCAT.
 - For HFIR instruments that write out raw files with run numbers, we have enabled functionality that allows for the searching of file locations by making calls to ONCat.  To use this, make sure that the "Search Data Archive" option is checked in your "Manage User Directories" settings.  The ``FileFinder`` and algorithms such as :ref:`Load <algm-Load>`  will then accept inputs such as "``HB2C_143210``".
 
 ISIS / ICat
@@ -63,7 +63,9 @@ New Algorithms
 - :ref:`MatchSpectra <algm-MatchSpectra>` is an algorithm that calculates factors to match all spectra to a reference spectrum.
 - :ref:`MaskBinsIf <algm-MaskBinsIf>` is an algorithm to mask bins according to criteria specified as a muparser expression.
 - :ref:`MaskNonOverlappingBins <algm-MaskNonOverlappingBins>` masks the bins that do not overlap with another workspace.
+- :ref:`LoadSampleEnvironment <algm-LoadSampleEnvironment>` loads or adds to a sample environment from a .stl file, as well as allowing setting the material of the environment to load.
 - :ref:`ParallaxCorrection <algm-ParallaxCorrection>` will perform a geometric correction for the so-called parallax effect in tube based SANS detectors.
+- :ref:`CalculateEfficiencyCorrection <algm-CalculateEfficiencyCorrection>` will calculate a detection efficiency correction with multiple and flexible inputs for calculation.
 
 Improvements
 ############
@@ -79,11 +81,14 @@ Improvements
 - :ref:`LoadSampleShape <algm-LoadSampleShape-v1>` now supports loading from binary .stl files.
 - :ref:`MaskDetectorsIf <algm-MaskDetectorsIf>` now supports masking a workspace in addition to writing the masking information to a calfile.
 - :ref:`ApplyDetectorScanEffCorr <algm-ApplyDetectorScanEffCorr>` will properly propagate the masked bins in the calibration map to the output workspace.
-- :ref:`LoadSampleShape <algm-LoadSampleShape-v1>` now supports loading from binary .stl files.
 - :ref:`LoadNexusLogs <algm-LoadNexusLogs-v1>` now will load files that have 1D arrays for each time value in the logs, but will not load this data.
 - :ref:`GroupDetectors <algm-GroupDetectors>` now takes masked bins correctly into account when processing histogram workspaces.
 - :ref:`SaveNexusProcessed <algm-SaveNexusProcessed>` and :ref:`LoadNexusProcessed <algm-LoadNexusProcessed>` can now save and load a ``MaskWorkspace``.
 - :ref:`FitPeaks <algm-FitPeaks>` can output parameters' uncertainty (fitting error) in an optional workspace.
+- The documentation in :ref:`EventFiltering` and :ref:`FilterEvents <algm-FilterEvents>` have been extensively rewritten to aid in understanding what the code does.
+- All of the numerical integration based absorption corrections which use :ref:`AbsorptionCorrection <algm-AbsorptionCorrection>` will generate an exception when they fail to generate a gauge volume. Previously, they would silently generate a correction workspace that was all not-a-number (``NAN``).
+- Various clarifications and additional links in the geometry and material documentation pages
+- :ref:`SetSample <algm-SetSample>` and :ref:`SetSampleMaterial <algm-SetSampleMaterial>` now accept materials without ``ChemicalFormula`` or ``AtomicNumber``. In this case, all cross sections and ``SampleNumberDensity`` have to be given.
 
 Bugfixes
 ########
@@ -92,7 +97,7 @@ Bugfixes
 - Bugfix in :ref:`ConvertToMatrixWorkspace <algm-ConvertToMatrixWorkspace>` with ``Workspace2D`` as the ``InputWorkspace`` not being cloned to the ``OutputWorkspace``. Added support for ragged workspaces.
 - :ref:`SolidAngle <algm-SolidAngle-v1>` Now properly accounts for a given StartWorkspaceIndex.
 - :ref:`FilterEvents <algm-FilterEvents-v1>` output workspaces now contain the goniometer.
-- Fixed an issue where if a workspace's history wouldn't update for some algorithms
+- Fixed an issue where a workspace's history wouldn't update for some algorithms
 - Fixed a ``std::bad_cast`` error in :ref:`algm-LoadLiveData` when the data size changes.
 - :ref:`Fit <algm-Fit>` now applies the ties in correct order independently on the order they are set. If any circular dependencies are found Fit will give an error.
 - Fixed a rare bug in :ref:`MaskDetectors <algm-MaskDetectors>` where a workspace could become invalidated in Python if it was a ``MaskWorkspace``.
@@ -100,8 +105,12 @@ Bugfixes
 - The output workspace now keeps the units of the input workspace for all sample log entries of algorithms :ref:`MergeRuns <algm-MergeRuns>` and :ref:`ConjoinXRuns <algm-ConjoinXRuns>`.
 - History for algorithms that took groups sometimes would get incorrect history causing history to be incomplete, so now full group history is saved for all items belonging to the group.
 - Fixed a bug in `SetGoniometer <algm-SetGoniometer>` where it would use the mean log value rather than the time series average value for goniometer angles.
+- Fixed a bug in `AlignAndFocusPowderFromFiles <algm-AlignAndFocusPowderFromFiles>` for using the passed on CompressTolerance and CompressWallClockTolerance in the child `CompressEvents <algm-CompressEvents>` algorithm instead of just in the child `AlignAndFocusPowder <algm-AlignAndFocusPowder>` algorithm.
 - `ConvertToMD <algm-ConvertToMD>` now uses the time-average value for logs when using them as ``OtherDimensions``
 - The input validator is fixed in :ref:`MostLikelyMean <algm-MostLikelyMean>` avoiding a segmentation fault.
+- Fixed a bug in `AlignAndFocusPowder <algm-AlignAndFocusPowder>` where a histogram input workspace did not clone propertly to the output workspace and properly masking a grouping workspace passed to `DiffractionFocussing <algm-DiffractionFocussing>`. Also adds initial unit tests for `AlignAndFocusPowder <algm-AlignAndFocusPowder>`.
+- Fixed a bug in :ref:`ExtractSpectra <algm-ExtractSpectra>` which was causing a wrong last value in the output's vertical axis if the axis type was ``BinEdgeAxis``.
+- Fixed an issue in :ref:`Rebin2D <algm-Rebin2D>` where `NaN` values would result if there were zero-area bins in the input workspace.
 
 Python
 ------
@@ -118,8 +127,8 @@ New
  * :class:`mantid.geometry.DetectorInfo`
  * :class:`mantid.api.SpectrumInfo`
 
-- :class:`mantid.geometry.ComponentInfo` is exposed to allow the user to access geometric information about the components which are part of a beamline.
-- :class:`mantid.geometry.DetectorInfo` offers the user the ability to access geometric information about the detector(s) which are part of a beamline. ``DetectorInfo`` has also been given an iterator to allow users to write more Pythonic loops rather than normal index based loops.
+- :class:`mantid.geometry.ComponentInfo` is exposed to allow the user to access geometric information about the components which are part of a beamline. Iterator support is also provided via python.
+- :class:`mantid.geometry.DetectorInfo` offers the user the ability to access geometric information about the detector(s) which are part of a beamline. ``DetectorInfo`` has also been given a python iterator.
 - :class:`mantid.api.SpectrumInfo` allows the user to access information about the spectra being used in a beamline. ``SpectrumInfo`` has also been given an iterator to allow users to write more Pythonic loops rather than normal index based loops. In addition to this ``SpectrumDefinition`` objects can also be accessed via a :class:`mantid.api.SpectrumInfo` object. The ``SpectrumDefinition`` object can be used to obtain information about the spectrum to detector mapping and provides a definition of what a spectrum comprises, i.e. indices of all detectors that contribute to the data stored in the spectrum.
 - Added new :ref:`unit <Unit Factory>` called ``Temperature`` which has units of Kelvin.
 - Importing ``mantid`` no longer initializes the ``FrameworkManager``. This allows separate classes to be imported without requiring a long delay in waiting for the framework to start. Amongst other things this allows the application name to be set correctly:
@@ -130,6 +139,7 @@ New
    UsageService.setApplicationName('myapp')
    FrameworkManager.Instance()
 
+- `FileFinder.findRuns` now optionally accepts a list of file extensions to search, called **exts**, and an boolean flag **useExtsOnly**. If this flag is True, FileFinder will search for the passed in extensions ONLY. If it is False, it will search for passed in extensions and then facility extensions.
 
 Improvements
 ############
@@ -137,7 +147,8 @@ Improvements
 - :ref:`ChudleyElliot <func-ChudleyElliot>` includes hbar in the definition
 - :ref:`Functions <FitFunctionsInPython>` may now have their constraint penalties for fitting set in python using ``function.setConstraintPenaltyFactor("parameterName", double)``.
 - :py:obj:`mantid.kernel.Logger` now handles unicode in python2
-
+- :py:meth:`mantid.api.ITableWorkspace.columnTypes` now returns human readable strings for non-primitive column types.
+- It is now possible to build custom materials with :class:`mantid.kernel.MaterialBuilder` without setting a formula or atomic number. In this case, all cross sections and number density have to be given.
 
 Bugfixes
 ########
diff --git a/docs/source/release/v3.14.0/indirect_inelastic.rst b/docs/source/release/v3.14.0/indirect_inelastic.rst
index df1cd2d56ce6bb9a19f7367781c6f01e3cc8534a..9c7e5d813e105d2e374ece2f43e1577216f2692b 100644
--- a/docs/source/release/v3.14.0/indirect_inelastic.rst
+++ b/docs/source/release/v3.14.0/indirect_inelastic.rst
@@ -35,7 +35,7 @@ Improvements
 - When the InelasticDiffSphere, InelasticDiffRotDiscreteCircle, ElasticDiffSphere or ElasticDiffRotDiscreteCircle
   Fit Types are selected in the ConvFit Tab, the Q values are retrieved from the workspaces, preventing a crash
   when plotting a guess.
-- The Plot buttons in MSDFit, I(Q,t)Fit, ConvFit and F(Q)Fit are disabled after a Run when the result workspace only 
+- The Plot buttons in MSDFit, I(Q,t)Fit, ConvFit and F(Q)Fit are disabled after a Run when the result workspace only
   has one data point to plot.
 - There is now an option to choose which output parameter to plot in MSDFit.
 - An option to skip the calculation of Monte Carlo Errors on the I(Q,t) Tab has been added.
@@ -46,6 +46,14 @@ Improvements
 - The WorkspaceIndex and Q value in the FitPropertyBrowser are now updated when the Plot Spectrum number is changed.
   This improvement can be seen in ConvFit when functions which depend on Q value are selected.
 - Fit and Fit Sequential in the Fit combobox above the FitPropertyBrowser are now disabled while fitting is taking place.
+- The option to choose which workspace index to Plot Spectrum for and from which output workspace is now given in Elwin.
+- ConvFit now allows the loading of Dave ASCII files which end with '_sqw.dave'.
+- The results of a fit in MSDFit, IqtFit, ConvFit and F(Q)Fit are now plotted with error bars.
+- The AddWorkspace windows (opened from the Multiple Input tab) now stay open after adding a workspace to the data table. This 
+  is found on the MSDFit, I(Q,t)Fit, ConvFit and F(Q)Fit interfaces.
+- It is now possible to load a Nexus file without it's history on the Elwin interface by unchecking the Load History checkbox.
+- It is now possible to undock the mini-plots on the MSDFit, IqtFit, ConvFit and F(Q)Fit interfaces.
+
 
 Bugfixes
 ########
@@ -62,8 +70,12 @@ Bugfixes
 - A bug where fixed parameters don't remain fixed when using the FABADA minimizer in ConvFit has been corrected.
 - The expression for the Fit type Yi in MSDFit was incorrect and has now been corrected.
 - The x-axis labels in the output plots for MSDFit are now correct.
-- An unexpected error is now prevented when clicking Plot Guess from the Display combo box in ConvFit without first loading 
+- An unexpected error is now prevented when clicking Plot Guess from the Display combo box in ConvFit without first loading
   a reduced file.
+- The output workspace ending with _Results now contains workspaces with corrected names which detail the fit functions used.
+- Selecting multiple data using the All Spectra checkbox without first selected a sample file used to cause an unexpected error.
+  This is now prevented. Meaningful error messages are also displayed when a sample or resolution file are not selected.
+- In the Elwin interface, the errors are now propagated correctly through to the workspace with extension _elt.
 
 
 Data Corrections Interface
diff --git a/docs/source/release/v3.14.0/sans.rst b/docs/source/release/v3.14.0/sans.rst
index e4cdb5b9cae07921cfaead1b54a13cea5410973f..69c45556542595112881b9cdedd671c2858c1d10 100644
--- a/docs/source/release/v3.14.0/sans.rst
+++ b/docs/source/release/v3.14.0/sans.rst
@@ -31,6 +31,9 @@ Improved
 * Added a "process all" and "process selected" button to the batch table in place of "process" button.
 * Added a load button to load selected workspaces without processing.
 * Added save_can option to output unsubtracted can and sample workspaces.
+* Can separate items in variable q binning with commas or spaces. E.g. L/Q 0.0, 0.02 0.3 0.05, 0.8
+* Can export table as a csv, which can be re-loaded as a batch file.
+* File path to batch file will be added to your directories automatically upon loading
 
 Bug fixes
 #########
@@ -40,7 +43,9 @@ Bug fixes
 * The GUI no longer hangs whilst searching the archive for files.
 * Updated the options and units displayed in wavelength and momentum range combo boxes.
 * Fixed a bug which crashed the beam centre finder if a phi mask was set.
-* Removed option to process in non-compatibility mode to avoid calculation issues
+* Removed option to process in non-compatibility mode to avoid calculation issues.
+* GUI can correctly read user files with variable step sizes, in /LOG and /LIN modes.
+* Fixed occasional crash when entering data into table.
 
 Improvements
 ############
diff --git a/docs/source/release/v3.14.0/ui.rst b/docs/source/release/v3.14.0/ui.rst
index 144282194067b0940c97c670cc64ad88b20bdf90..258c564fa847c354fc0a8661b27d4035eff13e49 100644
--- a/docs/source/release/v3.14.0/ui.rst
+++ b/docs/source/release/v3.14.0/ui.rst
@@ -64,6 +64,7 @@ Bugfixes
 - Project recovery will now successfully recover live data, it will however be unable to recover any data that was up at the time, but will start the live data streams again from scratch.
 - If an empty group workspace is present in the ADS it will no longer crash the save thread of project recovery and instead will delete it from the ADS and ignore it.
 - A bug has been fixed in Indirect I(Q,t) interface when analyzing IN16B reduced data.
+- :ref:`SavePlot1D <algm-SavePlot1D>` has been updated to follow changes to the plotly api.
 
 MantidPlot
 ----------
@@ -80,5 +81,32 @@ BugFixes
 
 - Fixed issue where an open set of data from ITableWorkspace wouldn't update if the data was changed via python
 - Fixed an issue where MantidPlot would crash when renaming workspaces.
+- Fixed issue with filenames containing spaces that are passed to Mantid when launched from the command line
+
+MantidWorkbench
+---------------
+
+Changes
+#######
+- Colorfill plots with uniform bin widths were made more responsive by resampling to 4K resolution and using :func:`~mantid.plots.MantidAxes.imshow`.
+
+BugFixes
+########
+
+Instrument View
+---------------
+
+Changes
+#######
+ - The miniplot on the pick tab of the instrument view now shows the HKL values for peaks when viewing a summed collection of detectors.
+
+
+Logging
+-------
+
+Changes
+#######
+
+ - Increased the log level from information to notice when creating an instrument geometry.
 
 :ref:`Release 3.14.0 <v3.14.0>`
diff --git a/images/MantidWorkbench.icns b/images/MantidWorkbench.icns
new file mode 100644
index 0000000000000000000000000000000000000000..64c52ae83245ee9de9e51b1c051520f3e8c34047
Binary files /dev/null and b/images/MantidWorkbench.icns differ
diff --git a/installers/MacInstaller/CMakeDMGSetup.scpt b/installers/MacInstaller/CMakeDMGSetup.scpt
index 37a2e9dde79f2a68c31733b110eaf0267bf9fb00..01b1b6c23649329780a57cb5bf0d40767e39cc72 100644
--- a/installers/MacInstaller/CMakeDMGSetup.scpt
+++ b/installers/MacInstaller/CMakeDMGSetup.scpt
@@ -38,11 +38,12 @@ on run argv
         set sidebar width to 0
         set statusbar visible to false
         set toolbar visible to false
-        set the bounds to { 400, 000, 900, 517 }
+        set the bounds to { 400, 000, 900, 710 }
         set position of item "MantidPlot.app" to { 110, 220 }
         set position of item "Applications" to { 380, 220 }
         set position of item "MantidPython (optional)" to { 380, 400 }
         set position of item "MantidNotebook (optional)" to { 110, 400 }
+        set position of item "MantidWorkbench" to { 110, 580 }
       end tell
       update without registering applications
       delay 5
diff --git a/instrument/D22_Parameters.xml b/instrument/D22_Parameters.xml
index 1e06905a671c9172b4b599b03340229c01d66666..b2bc05852637c405b732b91f10e773bbfe6a3ec5 100644
--- a/instrument/D22_Parameters.xml
+++ b/instrument/D22_Parameters.xml
@@ -21,7 +21,7 @@
     <component-link name="detector">
 
     <parameter name="parallax" type="string">
-		  <value val="1+0.14*exp(-4*log(2.)*((t-0.588)/0.414)^2)"/>
+		  <value val="1+0.14*exp(-4*ln(2.)*((t-0.588)/0.414)^2)"/>
 		</parameter>
 
     <parameter name="direction" type="string">
diff --git a/instrument/D22lr_Parameters.xml b/instrument/D22lr_Parameters.xml
index 79ef430cdd8cc93a351e32e4fde7bdd620e32aa0..4c91ab7050f99f8dc7a2d044118c78ac724f99ba 100644
--- a/instrument/D22lr_Parameters.xml
+++ b/instrument/D22lr_Parameters.xml
@@ -20,7 +20,7 @@
     <component-link name="detector">
 
     <parameter name="parallax" type="string">
-		  <value val="1+0.14*exp(-4*log(2.)*((abs(t)-0.588)/0.414)^2)"/>
+		  <value val="1+0.14*exp(-4*ln(2.)*((abs(t)-0.588)/0.414)^2)"/>
 		</parameter>
 
     <parameter name="direction" type="string">
diff --git a/instrument/Facilities.xml b/instrument/Facilities.xml
index d97a9656c7a3d7030ea37143a19ca76dbdca0cc0..bf7f57180884cc1f94eb37c15773169f7c383941 100644
--- a/instrument/Facilities.xml
+++ b/instrument/Facilities.xml
@@ -442,7 +442,7 @@
 <facility name="SNS" delimiter="_" FileExtensions=".nxs.h5,_event.nxs,.nxs,.dat,_runinfo.xml,_histo.nxs">
 
    <archive>
-      <archiveSearch plugin="ORNLDataSearch" />
+      <archiveSearch plugin="SNSDataSearch" />
    </archive>
 
    <computeResource name="Fermi">
diff --git a/instrument/MERLIN_Definition_2017_02.xml b/instrument/MERLIN_Definition_2017_02.xml
index 062b2d9b1923f5805d38768d5ba07af50ff5e7da..3ce0f1b0f2c0446d87c26b19a38660027b8ddec6 100644
--- a/instrument/MERLIN_Definition_2017_02.xml
+++ b/instrument/MERLIN_Definition_2017_02.xml
@@ -5,8 +5,8 @@
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd"
  name="MERLIN" valid-from   ="2017-09-12 11:00:01"
-               valid-to     ="2100-01-31 23:59:59"
-               last-modified="2017-09-11 14:00:05">
+               valid-to     ="2018-12-03 17:18:00"
+               last-modified="2019-01-03 13:48:00">
 
   <defaults>
     <length unit="meter"/>
diff --git a/instrument/MERLIN_Definition_2018_03.xml b/instrument/MERLIN_Definition_2018_03.xml
new file mode 100644
index 0000000000000000000000000000000000000000..55a5ed81c14261170c5b417c45d1c42e911e16ba
--- /dev/null
+++ b/instrument/MERLIN_Definition_2018_03.xml
@@ -0,0 +1,882 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- For help on the notation used to specify an Instrument Definition File 
+     see http://www.mantidproject.org/IDF -->
+<instrument xmlns="http://www.mantidproject.org/IDF/1.0" 
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd"
+ name="MERLIN" valid-from   ="2018-12-03 17:18:01"
+               valid-to     ="2100-01-31 23:59:59"
+               last-modified="2019-01-03 13:48:00">
+
+  <defaults>
+    <length unit="meter"/>
+    <angle unit="degree"/>
+    <reference-frame>
+      <!-- The z-axis is set parallel to and in the direction of the beam. the 
+           y-axis points up and the coordinate system is right handed. -->
+      <along-beam axis="z"/>
+      <pointing-up axis="y"/>
+      <handedness val="right"/>
+    </reference-frame>
+    <default-view view="cylindrical_y"/>
+  </defaults>
+
+  
+  <!-- DESCRIPTION OF INSTRUMENT IN WORDS: 
+  
+    The data for Merlin was obtained from Robert Bewley.
+    
+    2012-05-17 - added names to tubes
+    2013-11-14 - use locations tag in tube definitions
+    2017-09-11 - change to 512 detectors per tube
+  -->
+
+  
+  <!--  SOURCE AND SAMPLE POSITION -->
+  
+  <component type="undulator">
+    <location z="-11.8" />
+  </component>
+  <type name="undulator" is="Source"> </type>
+  
+  <component type="sample-position">
+    <location />
+  </component>
+  <type name="sample-position" is="SamplePos"></type>
+  
+  <!-- Aperture -->
+  <component type="aperture" name="aperture">
+    <location z="-10.1"/>
+  </component>
+ 
+  <type name="aperture">
+  <cuboid id="aperture0">
+    <left-front-bottom-point x="0.047" y="-0.047" z="-0.0001"  />
+    <left-front-top-point  x="0.047" y="-0.047" z="0.0001"  />
+    <left-back-bottom-point  x="-0.047" y="-0.047" z="-0.0001"  />
+    <right-front-bottom-point  x="0.047" y="0.047" z="-0.0001"  />
+  </cuboid>
+  <algebra val="aperture0" />
+  </type>
+ 
+ <!-- Chopper position -->
+ <component type="chopper-position">
+    <location z="-1.8"/>
+    <!--description is="The component provides sample-choper distance, used
+    to estimate chopper delay time and Tobyfit resolution calculations."/-->
+    <parameter name="initial_phase">    
+        <value val="-490000."/>
+        <description is="The initial rotation phase of the disk used to caluclate the time
+        for neutrons arriving at the chopper according to the formula time = delay + initial_phase/Speed"/>    
+    </parameter>
+    <parameter name="ChopperDelayLog" type="string">
+        <value val="Chopper_delay"/>    
+    </parameter>
+    <parameter name="ChopperSpeedLog" type="string">
+        <value val="Chopper_Speed"/>    
+    </parameter>
+    <parameter name="FilterBaseLog" type="string">
+        <value val="good_uah_log"/>    
+    </parameter>
+    <!-- if the log above should be used as it is or 
+    one should calculate its derivative -->
+    <parameter name="filter_with_derivative" type="bool">
+        <value val="True"/>    
+    </parameter>    
+
+  </component>
+  <type name="chopper-position" is="ChopperPos"></type>  
+  
+
+  <!-- DETECTORS -->
+  
+  <component type="door1" idlist="door1">
+    <location />
+  </component>
+  
+  <!-- Uncomment to include door 6. If this is done you MUST also
+       uncomment the <idlist name="door6"> at the bottom of this file
+       -->
+  <component type="door2" idlist="door2">
+    <location />
+  </component>
+  
+  <component type="door3" idlist="door3">
+    <location />
+  </component>
+  
+  <component type="door4" idlist="door4">
+    <location />
+  </component>
+  
+  <component type="door5" idlist="door5">
+    <location />
+  </component>
+  
+  <component type="door6" idlist="door6">
+    <location />
+  </component>
+  
+  <component type="door7" idlist="door7">
+    <location />
+  </component>
+  
+  <component type="door8" idlist="door8">
+    <location />
+  </component>
+  
+  <component type="door9" idlist="door9">
+    <location />
+  </component>
+
+  <component type="monitors" idlist="monitors">
+    <location />
+  </component>
+  
+  <type name="monitors">
+    <component type="monitor">
+      <location r="3.25800" t="180.0" p="0.0" />
+      <!-- old position was 1.50400-->
+      <location r="1.60800" t="180.0" p="0.0" />
+      <location r="1.60800" t="180.0" p="0.0" />
+      <location r="1.60800" t="180.0" p="0.0" />
+      <location r="1.60800" t="180.0" p="0.0" /> 
+      <location r="4.24700" t="0.000" p="0.0" />
+      <location r="4.24700" t="0.000" p="0.0" />
+      <location r="4.24700" t="0.000" p="0.0" />
+      <location r="4.24700" t="0.000" p="0.0" />
+    </component>
+  </type>
+
+  <type name="door1">
+    <component type="standard-tube">
+      <location r="2.5" t="-45.01000" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-44.37977" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-43.74954" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-43.11931" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-42.48908" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-41.85885" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-41.22862" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-40.59839" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="-39.97000" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-39.33977" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-38.70954" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-38.07931" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-37.44908" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-36.81885" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-36.18862" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-35.55839" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="-34.93000" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-34.29977" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-33.66954" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-33.03931" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-32.40908" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-31.77885" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-31.14863" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-30.51840" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="-29.89001" name="tube_4_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-29.25978" name="tube_4_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-28.62955" name="tube_4_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-27.99932" name="tube_4_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-27.36909" name="tube_4_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-26.73886" name="tube_4_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-26.10863" name="tube_4_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-25.47840" name="tube_4_8"> <facing x="0" y="0" z="0"/> </location>      
+    </component>      
+  </type>
+  
+  <type name="door2">
+    <component type="standard-tube">
+      <location r="2.5" t="-23.57801" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-22.94778" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-22.31755" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-21.68732" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-21.05709" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-20.42686" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-19.79664" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-19.16641" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="-18.53802" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-17.90779" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-17.27756" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-16.64733" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-16.01710" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-15.38687" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-14.75664" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-14.12642" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location> 
+                                      
+      <location r="2.5" t="-13.49803" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-12.86780" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-12.23757" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-11.60734" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-10.97712" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-10.34689" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-9.716660" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-9.086440" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="-8.458050" name="tube_4_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-7.827830" name="tube_4_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-7.197600" name="tube_4_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-6.567380" name="tube_4_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-5.937160" name="tube_4_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-5.306940" name="tube_4_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-4.676720" name="tube_4_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="-4.046510" name="tube_4_8"> <facing x="0" y="0" z="0"/> </location> 
+    </component>
+  </type>
+  
+  <type name="door3">
+    <component type="straight-through-beam-tube" >
+      <location r="2.50937" t="5.41821" p="113.91585" name="tube_1_1" > </location>
+      <location r="2.50937" t="5.19467" p="107.56693" name="tube_1_2" > </location>   
+      <location r="2.50937" t="5.04030" p="100.73910" name="tube_1_3" > </location>
+      <location r="2.50937" t="4.96155" p="93.58839" name="tube_1_4"> </location>   
+      <location r="2.50937" t="4.96203" p="86.32413" name="tube_1_5"> </location>
+      <location r="2.50937" t="5.04172" p="79.17615" name="tube_1_6"> </location>   
+      <location r="2.50937" t="5.19697" p="72.35330" name="tube_1_7"> </location>
+      <location r="2.50937" t="5.42130" p="66.01085" name="tube_1_8"> </location>   
+
+      <location r="2.88977" t="30.17630" p="-93.79165" name="tube_2_1" > </location>
+      <location r="2.88977" t="30.14056" p="-92.70872" name="tube_2_2"> </location>   
+      <location r="2.88977" t="30.11674" p="-91.62351" name="tube_2_3"> </location>
+      <location r="2.88977" t="30.10486" p="-90.53695" name="tube_2_4"> </location>   
+      <location r="2.88977" t="30.10493" p="-89.44993" name="tube_2_5"> </location>
+      <location r="2.88977" t="30.11696" p="-88.36337" name="tube_2_6"> </location>   
+      <location r="2.88977" t="30.14092" p="-87.27819" name="tube_2_7"> </location>
+      <location r="2.88977" t="30.17680" p="-86.19529" name="tube_2_8"> </location>         
+    </component>
+    <component type="standard-tube"> 
+      <location r="2.5" t="2.838180 " name="tube_3_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="3.468370 " name="tube_3_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="4.098580 " name="tube_3_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="4.728790 " name="tube_3_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="5.359010 " name="tube_3_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="5.989230 " name="tube_3_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="6.619450 " name="tube_3_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="7.249670 " name="tube_3_8" > <facing x="0" y="0" z="0"/> </location>      
+      <location r="2.5" t="7.878060 " name="tube_4_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="8.508280 " name="tube_4_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="9.138510 " name="tube_4_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="9.768730 " name="tube_4_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="10.398960" name="tube_4_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="11.029190" name="tube_4_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="11.659410" name="tube_4_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="12.289640" name="tube_4_8"> <facing x="0" y="0" z="0"/> </location>    
+      <location r="2.5" t="12.918030" name="tube_5_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="13.548260" name="tube_5_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="14.178490" name="tube_5_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="14.808710" name="tube_5_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="15.438940" name="tube_5_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="16.069170" name="tube_5_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="16.699400" name="tube_5_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="17.329630" name="tube_5_8"> <facing x="0" y="0" z="0"/> </location> 
+    </component>
+  </type>
+  
+  <type name="door4">
+    <component type="standard-tube">
+      <location r="2.5" t="19.163020" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="19.793250" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="20.423470" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="21.053700" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="21.683930" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="22.314160" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="22.944390" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="23.574620" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="24.204010" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="24.834240" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="25.464470" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="26.094700" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="26.724930" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="27.355160" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="27.985390" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="28.615620" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location> 
+                                      
+      <location r="2.5" t="29.247010" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="29.877240" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="30.507470" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="31.137700" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="31.767920" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="32.398150" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="33.028380" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="33.658610" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location> 
+                                      
+      <location r="2.5" t="34.288000" name="tube_4_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="34.918230" name="tube_4_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="35.548460" name="tube_4_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="36.178690" name="tube_4_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="36.808920" name="tube_4_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="37.439150" name="tube_4_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="38.069380" name="tube_4_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="38.699610" name="tube_4_8"> <facing x="0" y="0" z="0"/> </location>      
+    </component>  
+  </type>
+  
+  <type name="door5">
+    <component type="standard-tube">
+      <location r="2.5" t="40.587000" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="41.217230" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="41.847460" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="42.477690" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="43.107920" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="43.738150" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="44.368380" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="44.998610" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="45.629000" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="46.259230" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="46.889460" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="47.519690" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="48.149920" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="48.780150" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="49.410380" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="50.040610" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location>  
+                                      
+      <location r="2.5" t="50.671000" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="51.301230" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="51.931460" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="52.561690" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="53.191920" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="53.822150" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="54.452380" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="55.082610" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="55.713000" name="tube_4_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="56.343230" name="tube_4_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="56.973460" name="tube_4_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="57.603690" name="tube_4_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="58.233920" name="tube_4_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="58.864150" name="tube_4_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="59.494380" name="tube_4_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="60.124610" name="tube_4_8"> <facing x="0" y="0" z="0"/> </location>      
+    </component>  
+  </type>
+  
+  <type name="door6">
+    <component type="standard-tube">
+      <location r="2.5" t="61.817990" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="62.448220" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="63.078450" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="63.708680" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="64.338910" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="64.969140" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="65.599370" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="66.229600" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="66.859990" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="67.490220" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="68.120450" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="68.750680" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="69.380910" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="70.011140" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="70.641370" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="71.271600" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location> 
+                                      
+      <location r="2.5" t="71.901990" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="72.532220" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="73.162450" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="73.792680" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="74.422910" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="75.053140" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="75.683370" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="76.313600" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="76.943990" name="tube_4_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="77.574220" name="tube_4_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="78.204450" name="tube_4_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="78.834680" name="tube_4_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="79.464910" name="tube_4_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="80.095140" name="tube_4_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="80.725370" name="tube_4_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="81.355600" name="tube_4_8"> <facing x="0" y="0" z="0"/> </location>      
+    </component>  
+  </type>
+  
+  <type name="door7">
+    <component type="standard-tube">
+      <location r="2.5" t="83.278990" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="83.909220" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="84.539450" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="85.169680" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="85.799910" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="86.430140" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="87.060370" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="87.690600" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="88.320990" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="88.951220" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="89.581450" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="90.211680" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="90.841910" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="91.472140" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="92.102370" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="92.732600" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="93.362990" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="93.993220" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="94.623450" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="95.253680" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="95.883910" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="96.514140" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="97.144370" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="97.774600" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="98.404990" name="tube_4_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="99.035220" name="tube_4_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="99.665450" name="tube_4_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="100.29568" name="tube_4_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="100.92591" name="tube_4_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="101.55614" name="tube_4_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="102.18637" name="tube_4_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="102.81660" name="tube_4_8"> <facing x="0" y="0" z="0"/> </location>      
+    </component>  
+  </type>
+  
+  <type name="door8">
+    <component type="standard-tube">
+      <location r="2.5" t="104.71099" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="105.34122" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="105.97145" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="106.60168" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="107.23191" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="107.86214" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="108.49237" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="109.12260" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="109.75299" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="110.38322" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="111.01345" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="111.64368" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="112.27391" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="112.90414" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="113.53437" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="114.16460" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="114.79499" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="115.42522" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="116.05545" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="116.68568" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="117.31591" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="117.94614" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="118.57636" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="119.20659" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location>                                        
+    </component>  
+  </type>
+  
+  <type name="door9">
+    <component type="standard-tube">
+      <location r="2.5" t="121.19498" name="tube_1_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="121.82521" name="tube_1_2" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="122.45544" name="tube_1_3" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="123.08567" name="tube_1_4" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="123.71590" name="tube_1_5" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="124.34613" name="tube_1_6" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="124.97636" name="tube_1_7" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="125.60659" name="tube_1_8" > <facing x="0" y="0" z="0"/> </location>
+                                                   
+      <location r="2.5" t="126.23698" name="tube_2_1" > <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="126.86721" name="tube_2_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="127.49744" name="tube_2_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="128.12767" name="tube_2_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="128.75790" name="tube_2_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="129.38813" name="tube_2_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="130.01836" name="tube_2_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="130.64859" name="tube_2_8"> <facing x="0" y="0" z="0"/> </location>
+                                      
+      <location r="2.5" t="131.27898" name="tube_3_1"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="131.90921" name="tube_3_2"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="132.53944" name="tube_3_3"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="133.16967" name="tube_3_4"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="133.79990" name="tube_3_5"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="134.43013" name="tube_3_6"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="135.06036" name="tube_3_7"> <facing x="0" y="0" z="0"/> </location>
+      <location r="2.5" t="135.69059" name="tube_3_8"> <facing x="0" y="0" z="0"/> </location>                                        
+    </component>  
+  </type>
+  
+     
+  <type name="standard-tube" outline="yes">
+    <component type="standard-pixel">
+      <locations y="-1.4635693359375" y-end="1.46356933593751" n-elements="512"/>
+    </component>
+  </type>
+        
+  <type name="straight-through-beam-tube" outline="yes">
+    <component type="straight-through-beam-pixel">
+      <locations y="0" y-end="1.22879882812501" n-elements="512" />
+    </component>
+  </type>
+  
+  
+  <type name="standard-pixel" is="detector">    
+    <cylinder id="shape">
+      <centre-of-bottom-base x="0.0" y="-0.0028614" z="0.0" />
+      <axis x="0.0" y="1.0" z="0.0" /> 
+      <radius val="0.0127" />
+      <height val="0.00572" />
+    </cylinder>
+    <algebra val="shape" />
+  </type>
+
+  <type name="straight-through-beam-pixel" is="detector">    
+    <cylinder id="shape">
+      <centre-of-bottom-base x="0.0" y="-0.001201172" z="0.0" />
+      <axis x="0.0" y="1.0" z="0.0" /> 
+      <radius val="0.0127" />
+      <height val="0.0024024" />
+    </cylinder>
+    <algebra val="shape" />
+  </type>
+
+  <type name="monitor" is="monitor">
+    <cylinder id="some-shape">
+      <centre-of-bottom-base r="0.0" t="0.0" p="0.0" />
+      <axis x="0.0" y="0.0" z="1.0" /> 
+      <radius val="0.01" />
+      <height val="0.03" />
+    </cylinder> 
+    <algebra val="some-shape" />    
+  </type>
+
+  
+  <!-- DETECTOR ID LISTS -->
+  
+  <idlist idname="monitors">
+    <id val="11" />
+    <id start="21" end="24" />
+    <id start="31" end="34" />    
+  </idlist>
+  
+  <idlist idname="door1">
+    <id start="1110001" end="1110512" />
+    <id start="1120001" end="1120512" />
+    <id start="1130001" end="1130512" />
+    <id start="1140001" end="1140512" />
+    <id start="1150001" end="1150512" />
+    <id start="1160001" end="1160512" />
+    <id start="1170001" end="1170512" />
+    <id start="1180001" end="1180512" />   
+    <id start="1210001" end="1210512" />
+    <id start="1220001" end="1220512" />
+    <id start="1230001" end="1230512" />
+    <id start="1240001" end="1240512" />
+    <id start="1250001" end="1250512" />
+    <id start="1260001" end="1260512" />
+    <id start="1270001" end="1270512" />
+    <id start="1280001" end="1280512" />    
+    <id start="1310001" end="1310512" />
+    <id start="1320001" end="1320512" />
+    <id start="1330001" end="1330512" />
+    <id start="1340001" end="1340512" />
+    <id start="1350001" end="1350512" />
+    <id start="1360001" end="1360512" />
+    <id start="1370001" end="1370512" />
+    <id start="1380001" end="1380512" />    
+    <id start="1410001" end="1410512" />
+    <id start="1420001" end="1420512" />
+    <id start="1430001" end="1430512" />
+    <id start="1440001" end="1440512" />
+    <id start="1450001" end="1450512" />
+    <id start="1460001" end="1460512" />
+    <id start="1470001" end="1470512" />
+    <id start="1480001" end="1480512" />    
+  </idlist>
+  
+  <idlist idname="door2">
+    <id start="2110001" end="2110512" />
+    <id start="2120001" end="2120512" />
+    <id start="2130001" end="2130512" />
+    <id start="2140001" end="2140512" />
+    <id start="2150001" end="2150512" />
+    <id start="2160001" end="2160512" />
+    <id start="2170001" end="2170512" />
+    <id start="2180001" end="2180512" />   
+    <id start="2210001" end="2210512" />
+    <id start="2220001" end="2220512" />
+    <id start="2230001" end="2230512" />
+    <id start="2240001" end="2240512" />
+    <id start="2250001" end="2250512" />
+    <id start="2260001" end="2260512" />
+    <id start="2270001" end="2270512" />
+    <id start="2280001" end="2280512" />   
+    <id start="2310001" end="2310512" />
+    <id start="2320001" end="2320512" />
+    <id start="2330001" end="2330512" />
+    <id start="2340001" end="2340512" />
+    <id start="2350001" end="2350512" />
+    <id start="2360001" end="2360512" />
+    <id start="2370001" end="2370512" />
+    <id start="2380001" end="2380512" />    
+    <id start="2410001" end="2410512" />
+    <id start="2420001" end="2420512" />
+    <id start="2430001" end="2430512" />
+    <id start="2440001" end="2440512" />
+    <id start="2450001" end="2450512" />
+    <id start="2460001" end="2460512" />
+    <id start="2470001" end="2470512" />
+    <id start="2480001" end="2480512" />    
+  </idlist>  
+  
+  <idlist idname="door3">
+    <id start="3110001" end="3110512" />
+    <id start="3120001" end="3120512" />
+    <id start="3130001" end="3130512" />
+    <id start="3140001" end="3140512" />
+    <id start="3150001" end="3150512" />
+    <id start="3160001" end="3160512" />
+    <id start="3170001" end="3170512" />
+    <id start="3180001" end="3180512" />   
+    <id start="3210001" end="3210512" />
+    <id start="3220001" end="3220512" />
+    <id start="3230001" end="3230512" />
+    <id start="3240001" end="3240512" />
+    <id start="3250001" end="3250512" />
+    <id start="3260001" end="3260512" />
+    <id start="3270001" end="3270512" />
+    <id start="3280001" end="3280512" />   
+    <id start="3310001" end="3310512" />
+    <id start="3320001" end="3320512" />
+    <id start="3330001" end="3330512" />
+    <id start="3340001" end="3340512" />
+    <id start="3350001" end="3350512" />
+    <id start="3360001" end="3360512" />
+    <id start="3370001" end="3370512" />
+    <id start="3380001" end="3380512" />    
+    <id start="3410001" end="3410512" />
+    <id start="3420001" end="3420512" />
+    <id start="3430001" end="3430512" />
+    <id start="3440001" end="3440512" />
+    <id start="3450001" end="3450512" />
+    <id start="3460001" end="3460512" />
+    <id start="3470001" end="3470512" />
+    <id start="3480001" end="3480512" />    
+    <id start="3510001" end="3510512" />
+    <id start="3520001" end="3520512" />
+    <id start="3530001" end="3530512" />
+    <id start="3540001" end="3540512" />
+    <id start="3550001" end="3550512" />
+    <id start="3560001" end="3560512" />
+    <id start="3570001" end="3570512" />
+    <id start="3580001" end="3580512" />     
+  </idlist>    
+
+  <idlist idname="door4">
+    <id start="4110001" end="4110512" />
+    <id start="4120001" end="4120512" />
+    <id start="4130001" end="4130512" />
+    <id start="4140001" end="4140512" />
+    <id start="4150001" end="4150512" />
+    <id start="4160001" end="4160512" />
+    <id start="4170001" end="4170512" />
+    <id start="4180001" end="4180512" />   
+    <id start="4210001" end="4210512" />
+    <id start="4220001" end="4220512" />
+    <id start="4230001" end="4230512" />
+    <id start="4240001" end="4240512" />
+    <id start="4250001" end="4250512" />
+    <id start="4260001" end="4260512" />
+    <id start="4270001" end="4270512" />
+    <id start="4280001" end="4280512" />   
+    <id start="4310001" end="4310512" />
+    <id start="4320001" end="4320512" />
+    <id start="4330001" end="4330512" />
+    <id start="4340001" end="4340512" />
+    <id start="4350001" end="4350512" />
+    <id start="4360001" end="4360512" />
+    <id start="4370001" end="4370512" />
+    <id start="4380001" end="4380512" />    
+    <id start="4410001" end="4410512" />
+    <id start="4420001" end="4420512" />
+    <id start="4430001" end="4430512" />
+    <id start="4440001" end="4440512" />
+    <id start="4450001" end="4450512" />
+    <id start="4460001" end="4460512" />
+    <id start="4470001" end="4470512" />
+    <id start="4480001" end="4480512" />    
+  </idlist>  
+
+  <idlist idname="door5">
+    <id start="5110001" end="5110512" />
+    <id start="5120001" end="5120512" />
+    <id start="5130001" end="5130512" />
+    <id start="5140001" end="5140512" />
+    <id start="5150001" end="5150512" />
+    <id start="5160001" end="5160512" />
+    <id start="5170001" end="5170512" />
+    <id start="5180001" end="5180512" />   
+    <id start="5210001" end="5210512" />
+    <id start="5220001" end="5220512" />
+    <id start="5230001" end="5230512" />
+    <id start="5240001" end="5240512" />
+    <id start="5250001" end="5250512" />
+    <id start="5260001" end="5260512" />
+    <id start="5270001" end="5270512" />
+    <id start="5280001" end="5280512" />   
+    <id start="5310001" end="5310512" />
+    <id start="5320001" end="5320512" />
+    <id start="5330001" end="5330512" />
+    <id start="5340001" end="5340512" />
+    <id start="5350001" end="5350512" />
+    <id start="5360001" end="5360512" />
+    <id start="5370001" end="5370512" />
+    <id start="5380001" end="5380512" />    
+    <id start="5410001" end="5410512" />
+    <id start="5420001" end="5420512" />
+    <id start="5430001" end="5430512" />
+    <id start="5440001" end="5440512" />
+    <id start="5450001" end="5450512" />
+    <id start="5460001" end="5460512" />
+    <id start="5470001" end="5470512" />
+    <id start="5480001" end="5480512" />    
+  </idlist>    
+
+  <idlist idname="door6">
+    <id start="6110001" end="6110512" />
+    <id start="6120001" end="6120512" />
+    <id start="6130001" end="6130512" />
+    <id start="6140001" end="6140512" />
+    <id start="6150001" end="6150512" />
+    <id start="6160001" end="6160512" />
+    <id start="6170001" end="6170512" />
+    <id start="6180001" end="6180512" />   
+    <id start="6210001" end="6210512" />
+    <id start="6220001" end="6220512" />
+    <id start="6230001" end="6230512" />
+    <id start="6240001" end="6240512" />
+    <id start="6250001" end="6250512" />
+    <id start="6260001" end="6260512" />
+    <id start="6270001" end="6270512" />
+    <id start="6280001" end="6280512" />   
+    <id start="6310001" end="6310512" />
+    <id start="6320001" end="6320512" />
+    <id start="6330001" end="6330512" />
+    <id start="6340001" end="6340512" />
+    <id start="6350001" end="6350512" />
+    <id start="6360001" end="6360512" />
+    <id start="6370001" end="6370512" />
+    <id start="6380001" end="6380512" />    
+    <id start="6410001" end="6410512" />
+    <id start="6420001" end="6420512" />
+    <id start="6430001" end="6430512" />
+    <id start="6440001" end="6440512" />
+    <id start="6450001" end="6450512" />
+    <id start="6460001" end="6460512" />
+    <id start="6470001" end="6470512" />
+    <id start="6480001" end="6480512" />    
+  </idlist>   
+  
+  <idlist idname="door7">
+    <id start="7110001" end="7110512" />
+    <id start="7120001" end="7120512" />
+    <id start="7130001" end="7130512" />
+    <id start="7140001" end="7140512" />
+    <id start="7150001" end="7150512" />
+    <id start="7160001" end="7160512" />
+    <id start="7170001" end="7170512" />
+    <id start="7180001" end="7180512" />   
+    <id start="7210001" end="7210512" />
+    <id start="7220001" end="7220512" />
+    <id start="7230001" end="7230512" />
+    <id start="7240001" end="7240512" />
+    <id start="7250001" end="7250512" />
+    <id start="7260001" end="7260512" />
+    <id start="7270001" end="7270512" />
+    <id start="7280001" end="7280512" />   
+    <id start="7310001" end="7310512" />
+    <id start="7320001" end="7320512" />
+    <id start="7330001" end="7330512" />
+    <id start="7340001" end="7340512" />
+    <id start="7350001" end="7350512" />
+    <id start="7360001" end="7360512" />
+    <id start="7370001" end="7370512" />
+    <id start="7380001" end="7380512" />    
+    <id start="7410001" end="7410512" />
+    <id start="7420001" end="7420512" />
+    <id start="7430001" end="7430512" />
+    <id start="7440001" end="7440512" />
+    <id start="7450001" end="7450512" />
+    <id start="7460001" end="7460512" />
+    <id start="7470001" end="7470512" />
+    <id start="7480001" end="7480512" />    
+  </idlist>     
+  
+  <idlist idname="door8">
+    <id start="8110001" end="8110512" />
+    <id start="8120001" end="8120512" />
+    <id start="8130001" end="8130512" />
+    <id start="8140001" end="8140512" />
+    <id start="8150001" end="8150512" />
+    <id start="8160001" end="8160512" />
+    <id start="8170001" end="8170512" />
+    <id start="8180001" end="8180512" />   
+    <id start="8210001" end="8210512" />
+    <id start="8220001" end="8220512" />
+    <id start="8230001" end="8230512" />
+    <id start="8240001" end="8240512" />
+    <id start="8250001" end="8250512" />
+    <id start="8260001" end="8260512" />
+    <id start="8270001" end="8270512" />
+    <id start="8280001" end="8280512" />   
+    <id start="8310001" end="8310512" />
+    <id start="8320001" end="8320512" />
+    <id start="8330001" end="8330512" />
+    <id start="8340001" end="8340512" />
+    <id start="8350001" end="8350512" />
+    <id start="8360001" end="8360512" />
+    <id start="8370001" end="8370512" />
+    <id start="8380001" end="8380512" />    
+  </idlist>    
+  
+  <idlist idname="door9">
+    <id start="9110001" end="9110512" />
+    <id start="9120001" end="9120512" />
+    <id start="9130001" end="9130512" />
+    <id start="9140001" end="9140512" />
+    <id start="9150001" end="9150512" />
+    <id start="9160001" end="9160512" />
+    <id start="9170001" end="9170512" />
+    <id start="9180001" end="9180512" />   
+    <id start="9210001" end="9210512" />
+    <id start="9220001" end="9220512" />
+    <id start="9230001" end="9230512" />
+    <id start="9240001" end="9240512" />
+    <id start="9250001" end="9250512" />
+    <id start="9260001" end="9260512" />
+    <id start="9270001" end="9270512" />
+    <id start="9280001" end="9280512" />   
+    <id start="9310001" end="9310512" />
+    <id start="9320001" end="9320512" />
+    <id start="9330001" end="9330512" />
+    <id start="9340001" end="9340512" />
+    <id start="9350001" end="9350512" />
+    <id start="9360001" end="9360512" />
+    <id start="9370001" end="9370512" />
+    <id start="9380001" end="9380512" />    
+  </idlist>   
+
+  <!-- DETECTOR PARAMETERS -->
+  <component-link name="monitors">
+   <parameter name="DelayTime">
+      <value units="microseconds" val="0"/>
+   </parameter>
+  </component-link>
+
+  <!-- Set the same across the reset of the instrument -->
+  <component-link name = "MERLIN">
+    <parameter name="TubePressure">
+      <value units="atm" val="10.0"/>
+    </parameter>
+    <parameter name="TubeThickness">
+      <value units="metre" val="0.0008"/>
+    </parameter>
+    <parameter name="DelayTime">
+      <value units="microseconds" val="-5.3"/>
+    </parameter>
+  </component-link>
+
+  
+</instrument>
diff --git a/instrument/MERLIN_Parameters_2018_03.xml b/instrument/MERLIN_Parameters_2018_03.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8ae55cbec86913160257fc38f556d76d6d3b2cf9
--- /dev/null
+++ b/instrument/MERLIN_Parameters_2018_03.xml
@@ -0,0 +1,478 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<parameter-file instrument = "MERLIN" valid-from = "2018-12-03 17:18:01">
+
+<component-link name = "MERLIN">
+
+<!-- files properties : -->
+
+<!-- Specify that the detector positions should be taken from the data file (Not used ) -->
+<parameter name="det-pos-source" type="string">
+  <value val="datafile"/>
+</parameter>
+<!-- The file which defines proper (calibrated) detector positions 
+     if None - it is undefined -->
+<parameter name="det_cal_file" type="string">
+   <value val="det_corr_172.dat"/>
+   <description is = "The file which defines proper (calibrated) detector positions.
+                      If property set to None - this file is undefined."/>
+</parameter>
+<!-- The file which defines detectors to spectra mapping; File extension has to be specified here but filepath not
+     if None - one2one map is used  -->
+<parameter name="map_file" type="string">
+   <value val="None"/>
+</parameter>
+
+<!-- Preferred extension of the data files obtained from DAE, e.g. what to prefer if two 
+     files with the same name and different extension are found
+ -->
+<parameter name="data_file_ext" type="string">
+   <value val=".nxs"/>
+</parameter>
+<!-- The name of the hard mask file to use together with diag masking 4to1_124mod.msk-->
+<parameter name="hard_mask_file" type="string">
+    <value val="None"/>
+</parameter>
+
+<!-- The map file used when calculating absolute units conversion integrals 
+     This map usually groups together large areas of detectors to obtain proper vanadium statistics  -->
+<parameter name="monovan_mapfile" type="string">
+   <value val="mono_van_map.map"/>
+</parameter>
+
+
+
+
+<!-- RunNumber to use for diag instead of the input run number if none - use input run 
+     if yes, will probably change from command line-->
+<parameter name="mask_run" type="string">
+   <value val="None"/>
+</parameter>
+
+<!-- Energy conversion mode direct/indirect/elastic (the one the reducer understands) -->
+<parameter name="deltaE-mode" type="string">
+  <value val="direct"/>
+</parameter>
+
+<!-- normalise_method normalization. To compare different runs with different length one uses normalization by some flux-dependent parameter
+     Available values are: none (-largely meaningless), monitor-1,monitor-2, current 
+     the acceptable values are defined in DirectEnergyConversion initialization routine as recognized by direct energy conversion normalize method
+     these three are currently disabled/unknown:  uamph peak-->
+<parameter name="normalise_method" type="string">
+    <value val="monitor-1"/>
+</parameter>
+
+<!-- Monitor used to estimate total current on the sample while normalizing by monitor 1.  -->
+<parameter name="norm-mon1-spec"   >
+  <value val="69633"/>
+</parameter>
+
+<!-- Time interval used for integration to estimate current on the sample 
+    This interval is usually taken around the monitor peak-->
+<parameter name="norm-mon1-min">
+  <value val="1000"/>
+</parameter>
+<parameter name="norm-mon1-max">
+  <value val="2000"/>
+</parameter>
+<parameter name="norm_mon_integration_range"  type="string">
+  <value val="norm-mon1-min:norm-mon1-max"/>
+</parameter>
+
+<!-- Monitor after chopper used to estimate total current on the sample, if normalization is done on monitor 2  -->
+<parameter name="norm-mon2-spec"  type="int">
+    <value val="69636"/>
+</parameter>
+<!-- Relative energy range (wrt. to the incident energy) in which monitor 2 signal is present and the integration to 
+     calculate monitor-2 current occurs   -->
+<parameter name="mon2_norm_energy_range" type = "string">
+    <value val="0.8,1.2"/>
+</parameter>
+
+<!-- Usually one wants to avoid loading monitors together with data except in special cases. One of this cases is 
+     MARI which monitors numbers are in the beginning of the spectra. If one loads them separately, 
+     spectra numbers change and ISIS hard masks (msk) prepared for workspace with monitors become invalid. 
+     This parameter is actual until Mantid Masking change.
+  -->
+<parameter name="load_monitors_with_workspace"  type="bool">
+<value val="False"/>
+</parameter>
+
+<!-- -->
+<parameter name="ei-mon1-spec"  type="string">
+  <value val="69636"/>
+  <description is="First spectra number (monitor's spectra number) to use when measuring incident energy,
+     or comma separated list of such numbers.
+     Should be spectra with well defined energy peak or monitor's spectra.
+     If list of numbers is provided, the final spectrum is the sum of the spectra with the numbers provided.
+     It is important to have the detectors for these spectra to be located at the same distance from the source,
+     as the position of the combined detector will be defined by the position of the first spectra's detector."/>
+</parameter>
+<!-- -->
+<parameter name="ei-mon2-spec"  type="string">
+  <value val="69638,69639,69640,69641"/>
+  <description is="Second spectra number (monitor's spectra number) to use when measuring incident energy,
+     or comma separated list of such numbers.
+     Should be spectra with well defined energy peak or monitor's spectra.
+     If list of numbers is provided, the final spectrum is the sum of the spectra with the numbers provided.
+     It is important to have the detectors for these spectra to be located at the same distance from the source,
+     as the position of the combined detector will be defined by the position of the first spectra's detector."/>
+</parameter>
+<!-- -->
+<parameter name="ei_mon_spectra"  type="string">
+  <value val="ei-mon1-spec:ei-mon2-spec"/>
+  <description is="List of spectra, used to calculate incident energy of beam in inelastic experiments by GetEi algorithm.
+  It is complex property, dependent on two other properties, namely ei-mon1-spec and ei-mon2-spec, which define spectrum or
+  spectra lists for each of two monitors, used in GetEi calculations"/>
+</parameter>
+<!-- -->
+<parameter name="spectra_to_monitors_list"  type="string">
+  <value val="None"/>
+  <description is="If you use some detectors as monitors and work in event mode,
+    one needs to specify the comma separated list of these detectors as the value of this property
+    to copy detectors spectra to monitors spectra collected in histogram mode.
+    If no such monitors are used, None (or text string 'None' in IDF) has to be specified as the value.
+    This is also necessary in Histogram mode if you want to use some detectors as monitors and 
+    load_monitors_with_workspace is set to False necessary if monitors are measured with different time channels"/>
+</parameter>
+
+<!-- -->
+<parameter name="multirep_tof_specta_list"  type="string">
+    <value val="1,129"/>
+    <description is="List of two spectra corresponding to the detectors which are closest and furthest from the sample.
+    These detectors locations are used to identify TOF range, contributing into each energy range
+     in multirep mode"/>
+</parameter>
+
+
+<!-- by default getEi looks for peaks within (1+-0.1)*TOF_GUES range where TOF_GUES is evaluated on the basis of ei guess.
+    If the peaks are very narrow, or there are a lot of them very close to each other, 
+    this value can be reduced. For example, such situation happens on MARI for very high incident energies Ei=1800.
+ -->
+<parameter name="ei_mon_peak_search_range">
+   <value val="0.1"/>
+</parameter>
+
+
+<parameter name="scale-factor">
+  <value val="1.7016e8"/>
+</parameter>
+
+<parameter name="wb-scale-factor">
+  <value val="1000"/>
+</parameter>
+
+<!-- Remove the count rate seen in the regions of the histograms defined as the background regions -->
+<parameter name="check_background"  type="bool">
+   <value val="False"/>
+   <description is="If True, remove the count rate seen in the regions of the
+     histograms defined as the background regions. 
+     The background region is defined by background_range property."/>
+</parameter>
+<parameter name="nullify_negative_signal"  type="bool">
+  <value val="True"/>
+  <description is="If True and background removed, nullify the negative signal, which
+     may occur at some detectors at some moments of time due to flat background removal and modify errors accordingly.
+     If False, leave signals and errors unchanged. "/>
+</parameter>
+
+
+<!--  detector_van_range- integration in E(mev) for detector(white beam) vanadium data [20,100] -->
+<parameter name="wb-integr-min">
+  <value val="20"/>
+</parameter>
+<parameter name="wb-integr-max">
+  <value val="55"/>
+</parameter>
+<parameter name="wb_integr_range"   type="string">
+    <value val="wb-integr-min:wb-integr-max"/>
+</parameter>
+
+
+<!-- integration range for background tests  (in TOF) - composite property 
+  Used in test to reject high background (FlatBackground) and in High Background tests integration in Diagnostics
+  if diag_background_test_range is not set -->
+<parameter name="bkgd-range-min"> 
+  <value val="12000"/>
+</parameter>
+<parameter name="bkgd-range-max">
+  <value val="18000"/>
+</parameter>
+<parameter name="background_range"  type="string">
+      <value val="bkgd-range-min:bkgd-range-max"/>
+</parameter>
+
+<!-- ******************************** DIAGNOSTICS DEFAILTS **************************************** -->
+
+<!-- Perform diag by bank. These are the spectrum numbers -->
+<parameter name="diag_spectra" type="string">
+  <value val="None"/>
+</parameter>
+
+<!-- Absolute lo threshold for vanadium diag (tiny) -->
+<parameter name="diag_tiny">
+  <value val="1e-10"/>
+</parameter>
+
+<!-- Absolute hi threshold for vanadium diag (large) -->
+<parameter name="diag_huge">
+  <value val="1e10"/>
+</parameter>
+
+<!-- Setting diag to reject zero backgrounds; If true then zeroes in (vanadium) data are masked as fail  -->
+<parameter name="diag_samp_zero"  type="bool">
+  <value val="False"/>
+</parameter>
+
+<!-- Fraction of median to consider counting low for the white beam diag (diag_van_median_rate_limit_hi sv_lo)-->
+<parameter name="diag_samp_lo">
+  <value val="0.0"/>
+</parameter>
+<!-- Fraction of median to consider counting high for the white beam diag (sv_hi)-->
+<parameter name="diag_samp_hi">
+  <value val="1.5"/>
+</parameter>
+
+<!-- Error criterion as a multiple of error bar for background (sv_sig) 
+  i.e. to fail the test, the magnitude of the
+  difference with respect to the median value must also exceed this number of error bars (default=3.3)
+-->  
+<parameter name="diag_samp_sig">
+  <value val="3.3"/>
+</parameter>
+
+<!-- Lower bound defining outliers as fraction of median value (v_out_lo)-->
+<parameter name="diag_van_out_lo">
+  <value val="0.01"/>
+</parameter>
+
+<!-- Upper bound defining outliers as fraction of median value (v_out_hi) -->
+<parameter name="diag_van_out_hi">
+  <value val="100."/>
+</parameter>
+
+<!-- Fraction of median to consider counting low for the white beam diag (vv_lo)  vanlo=0.1  -->
+<parameter name="diag_van_lo">
+  <value val="0.1"/>
+</parameter>
+
+<!-- Fraction of median to consider counting high for the white beam diag (vv_hi) vanhi=1.5 -->
+<parameter name="diag_van_hi">
+  <value val="1.5"/>
+</parameter>
+
+<!-- Error criterion as a multiple of error bar     van_sig  "
+    i.e. to fail the test, the magnitude of the difference with respect to the median value must also exceed this number of error bars (default=0.0)
+-->
+<parameter name="diag_van_sig">
+  <value val="0.0"/>
+</parameter>
+
+<!-- Variation for ratio test with second white beam -->
+<parameter name="diag_variation">
+  <value val="1.1"/>
+</parameter>
+<!-- The range used in diagnostics and rejecting high background.
+  If none, the diag background range uses background ranges from background_range. Has to be directly set otherwise -->
+<parameter name="diag_background_test_range"  type="string" >
+    <value val="None"/>
+</parameter>
+
+<!--  -->
+<!--  Bleeding corrections   -->
+
+<!--  the number of pixels ignored within the bleed test diagnostic -->
+<parameter name="bleed_pixels">
+    <value val="80"/>
+</parameter>
+<!--  the maximum frame rate allowed in a tube -->
+<parameter name="bleed_maxrate">
+    <value val="0.01"/>
+</parameter>
+<!-- True if the bleed tests should be run use_bleeding-->
+<parameter name="diag_bleed_test"  type="bool">
+    <value val="False"/>
+</parameter>
+
+<!-- **************************************** DIAGNOSTICS DEFAILTS END ****************************************    -->
+
+
+<!-- **************************************** ABSOLUTE UNITS CORRECTION DEFAULTS ********************************  -->
+<!-- Absolute units conversion average -->
+<parameter name="monovan_lo_bound">
+  <value val="0.01"/>
+</parameter>
+
+<parameter name="monovan_hi_bound">
+  <value val="100"/>
+</parameter>
+
+<!-- This property is the part of the composite definition for abs_units_van_range: 
+ It specifies the relative to incident energy lower integration limit for monochromatic vanadium in the mono-vanadium integration -->
+<parameter name="monovan_lo_frac">
+  <value val="-0.6"/>
+</parameter>
+
+<!-- This property is the part of the composite definition for abs_units_van_range:
+ It specifies the the lower limit of energy range in the monochromatic-vanadium integration 
+ Used only if abs_units_van_range is set to val="monovan_lo_value,monovan_hi_value"-->
+<parameter name="monovan_lo_value">
+  <value val="-40."/>
+</parameter>
+
+<!-- This property is the part of the composite definition for abs_units_van_range
+ It specifies the relative to incident energy higher integration limit for monochromatic vanadium in the mono-vanadium integration -->
+<parameter name="monovan_hi_frac">
+  <value val="0.6"/>
+</parameter>
+<!-- This property is the part of the composite definition for abs_units_van_range
+ It specifies the the higher limit of energy range in the monochromatic-vanadium integration 
+ Used only if abs_units_van_range is set to val="monovan_lo_value,monovan_hi_value"-->
+<parameter name="monovan_hi_value">
+  <value val="40."/>
+</parameter>
+
+<!-- energy range for integration calculating absolute units correction vanadium data. 
+     if None, range is calculated from monovan_hi_frac/monovan_lo_frac 
+     - providing the fractions of the incident energy      
+     if one wants to specify the energy values here it has to be defined in the form:
+    <value val="monovan_lo_value,monovan_hi_value"/>      -->    
+<parameter name="abs_units_van_range"  type="string">
+  <value val="None"/>
+</parameter>
+<!-- Sample mass used in absolute units normalization and should usually be changed by user-->
+<parameter name="sample_mass">
+  <value val="1"/>
+</parameter>
+<!-- Sample rmm used in absolute units normalization should usually be changed by user -->
+<parameter name="sample_rmm">
+  <value val="1"/>
+</parameter>
+<!-- Vanadium mass used in absolute units normalization and is usually instrument specific (changes rarely) -->
+<parameter name="vanadium-mass">
+  <value val="7.85"/>
+</parameter>
+<!-- if this value set to true, modo-vanadium run is not analysed and masks obtained for arbitrary units are used for mono-vanadium -->
+<parameter name="use_sam_msk_on_monovan" type = "bool">
+  <value val="False"/>
+</parameter>
+
+<!-- if this value is provided (not None) it is string representation of the number used instead of calculating mono-vanadium based normalization factor 
+   one does not need to provide mono-vanadium run if this value is provided as it will be used instead
+  -->
+<parameter name="mono_correction_factor" type="string">
+  <value val="None"/>
+</parameter>
+  
+<!-- **************************************** ABSOLUTE UNITS CORRECTION DEFAULTS END ****************************  -->
+
+<!-- if defined to true, fix incident energy to this value and do not calculate the energy from the run -->
+<parameter name="fixei"  type="bool">
+    <value val="False"/>
+</parameter>
+
+<!--  ****************************************  Workflow control **************************** -->
+
+<!-- This parameter controls the format of output data written by reducer. 
+    Three values are currently supported, namely .spe, .nxspe, and nexus (Mantid workspace) (.nxs)
+     Three possible values for this are defined in DirectEnergyConversion init routine as recognized by save method 
+     If None is there, no internal script saving occurs and one needs to use external save operations -->  
+<parameter name="save_format" type="string">
+   <value val="None"/>
+</parameter>
+
+<!-- If one wants to sum runs. By default no, as in addition to the key word one has to provide list of input run-numbers
+     but presence of this key here allows to propagate this key-word to the reducer   -->
+<parameter name="sum_runs"  type="bool">
+   <value val="False"/>
+</parameter>
+
+<!-- # Run Detector Efficiency Correction -->
+<parameter name="apply_detector_eff"  type="bool">
+   <value val="True"/>
+</parameter>
+<!-- # Multiply result by ki/kf value -->
+<parameter name="apply_kikf_correction"  type="bool">
+   <value val="True"/>
+</parameter>
+
+<!-- The if true, use only hard mask file specified above and do not run diagnostics procedures -->
+<parameter name="use_hard_mask_only" type="bool">
+    <value val="False"/>
+</parameter>
+
+<!-- Parameter specifies if one wants to run diagnostics (which include applying hard mask file) or not-->
+<parameter name="run_diagnostics" type="bool">
+    <value val="True"/>
+</parameter>
+
+
+<!-- If this parameter is set to true, dgreduce will try to load mask from the mask file
+     correspondent to the run if finds it and uses this mask as hard mask only (does not run diagnostics). 
+     If such file has not been found, it will run diagnostics and save the masks into mask file for reuse 
+     Hard Mask file, provided separately or as additional hard mask file is ignored in this case -->
+<parameter name="save_and_reuse_masks"  type="bool">
+   <value val="False"/>
+</parameter>
+
+<!-- The semicolon separated list of possible log names, containing information on crystal rotation.
+     First found log will be used together with motor_offset to identify crystal rotation 
+     (psi in Horace) -->
+<parameter name="motor_log_names"  type="string">
+   <value val="CCR_ROT;wccr"/>
+</parameter>
+<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue()  -->
+<parameter name="motor_offset">
+   <value val="None"/>
+</parameter>
+
+
+<!-- List of the words which can be used as a command line arguments to define reducer keywords
+     the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, 
+     the reducer keywords are the leftmost values of the keyword assignments below
+     Each keyword met in command line or file above are converted into reducer keyword as below
+-->  
+<parameter name="synonims" type="string">
+   <value val="normalise_method=norm_method;
+       fix_ei=fixei;
+       sum_runs=sum;
+       wb_integr_range=detector_van_range;
+       motor_log_names = motor_name;
+       van_mass=vanadium-mass;
+       check_background = background;
+
+       mon1_norm_spec=norm-mon1-spec;
+       mon2_norm_spec=norm-mon2-spec;
+       scale_factor=scale-factor;
+       wb_scale_factor=wb-scale-factor;
+       monovan_integr_range=abs_units_van_range;
+       monovan_lo_value = monovan-integr-min;
+       monovan_hi_value = monovan-integr-max;
+       bkgd_range = background_range;
+       background_test_range = diag_background_test_range;
+       hard_mask_file=hard_mask;
+       van_out_lo = diag_van_median_rate_limit_lo=diag_van_out_lo;
+       van_out_hi = diag_van_median_rate_limit_hi=diag_van_out_hi;
+       van_lo     = diag_van_median_sigma_lo=diag_van_lo;
+       van_hi     = diag_van_median_sigma_hi=diag_van_hi;
+       van_sig    = diag_van_median_sigma=diag_van_sig;
+       tiny       = diag_tiny;
+       huge       = diag_huge=large;
+       samp_zero  = diag_remove_zero=diag_samp_zero;
+       samp_lo    = diag_samp_median_sigma_lo=diag_samp_lo;
+       samp_hi    = diag_samp_median_sigma_hi=diag_samp_hi;
+       samp_sig   = diag_samp_median_sigma=diag_samp_sig;
+       variation  = diag_variation;
+       bleed_test = bleed = diag_bleed_test;
+       bleed_maxrate= diag_bleed_maxrate;
+       bleed_pixels  = diag_bleed_pixels;
+       deltaE_mode =  deltaE-mode;
+       ei-mon1-spec=ei_mon1_spec;       
+       ei-mon2-spec = test_ei2_mon_spectra=ei_mon2_spec;
+       ei_mon_spectra=test_mon_spectra_composite"
+    />
+</parameter>
+ 
+  <!-- -->
+</component-link>
+
+</parameter-file>
diff --git a/instrument/TOPAZ_Definition_2018-09-01.xml b/instrument/TOPAZ_Definition_2018-09-01.xml
index e790df3775239427ea583dff582be4c2d90601b4..17a08c47da55f5c5ace78c05ee4e60f891cf4f4f 100644
--- a/instrument/TOPAZ_Definition_2018-09-01.xml
+++ b/instrument/TOPAZ_Definition_2018-09-01.xml
@@ -5,7 +5,7 @@
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd"
  name="TOPAZ" valid-from   ="2018-09-01 00:00:00"
-              valid-to     ="2100-01-31 23:59:59"
+              valid-to     ="2018-12-31 23:59:59"
                      last-modified="2018-09-04 16:30:00">
 
   <!--Created by Vickie Lynch-->
diff --git a/instrument/TOPAZ_Definition_2019-01-01.xml b/instrument/TOPAZ_Definition_2019-01-01.xml
new file mode 100644
index 0000000000000000000000000000000000000000..31dae1617294526d556aac339bef9243659f387c
--- /dev/null
+++ b/instrument/TOPAZ_Definition_2019-01-01.xml
@@ -0,0 +1,233 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- For help on the notation used to specify an Instrument Definition File 
+     see http://www.mantidproject.org/IDF -->
+<instrument xmlns="http://www.mantidproject.org/IDF/1.0" 
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd"
+ name="TOPAZ" valid-from   ="2019-01-01 00:00:00"
+              valid-to     ="2100-01-31 23:59:59"
+                     last-modified="2019-01-17 11:30:00">
+
+  <!--Created by Vickie Lynch-->
+  <!--Modified by Vickie Lynch using the TOPAZ.py script from the Translation Service calibration/geometry/ code. -->
+  <!--DEFAULTS-->
+  <defaults>
+    <length unit="metre"/>
+    <angle unit="degree"/>
+    <reference-frame>
+      <along-beam axis="z"/>
+      <pointing-up axis="y"/>
+      <handedness val="right"/>
+    </reference-frame>
+    <default-view view="spherical_y"/>
+  </defaults>
+
+  <!--SOURCE-->
+  <component type="moderator">
+    <location z="-18.0"/>
+  </component>
+  <type name="moderator" is="Source"/>
+
+  <!--SAMPLE-->
+  <component type="sample-position">
+    <location y="0.0" x="0.0" z="0.0"/>
+  </component>
+  <type name="sample-position" is="SamplePos"/>
+
+  <!--MONITORS-->
+  <component type="monitors" idlist="monitors">
+    <location/>
+  </component>
+  <type name="monitors">
+    <component type="monitor">
+      <location z="-2.488" name="monitor1"/>
+    </component>
+    <component type="monitor">
+      <location z="1.049" name="monitor2"/>
+    </component>
+  </type>
+
+
+<!-- XML Code automatically generated on 2019-01-17 11:30:01.182950 for the Mantid instrument definition file -->
+<component type="panel" idstart="851968" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.455000" t="105.192621" p="-33.305992" name="bank13" rot="-99.919712" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="53.154399">
+    <rot val="41.466968" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1048576" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.455000" t="133.320872" p="-133.248573" name="bank16" rot="8.080272" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="53.154399">
+    <rot val="41.466968" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1114112" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.455000" t="105.192621" p="-146.694008" name="bank17" rot="44.079783" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="53.154399">
+    <rot val="41.466968" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1179648" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.455000" t="74.807731" p="-146.694071" name="bank18" rot="80.079867" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="53.154399">
+    <rot val="41.466968" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1245184" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.455000" t="46.678985" p="-133.248429" name="bank19" rot="116.080009" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="53.154399">
+    <rot val="41.466968" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1310720" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="23.905622" p="-42.859145" name="bank20" rot="-177.410228" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1441792" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="90.000202" p="-16.000018" name="bank22" rot="-105.410002" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1703936" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="124.403098" p="-160.483843" name="bank26" rot="38.590066" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1769472" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="90.000202" p="-163.999982" name="bank27" rot="74.589577" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1835008" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="55.596651" p="-160.483782" name="bank28" rot="110.589776" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="1900544" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="23.905622" p="-137.140855" name="bank29" rot="146.589803" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="2162688" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.395000" t="108.000253" p="0.000000" name="bank33" rot="-71.999747" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="45.000000">
+    <rot val="0.000000" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="2359296" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.395000" t="143.999764" p="180.000000" name="bank36" rot="36.000236" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="45.000000">
+    <rot val="0.000000" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="2424832" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.395000" t="108.000253" p="180.000000" name="bank37" rot="71.999747" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="45.000000">
+    <rot val="0.000000" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="2490368" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.395000" t="72.000168" p="180.000000" name="bank38" rot="107.999832" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="45.000000">
+    <rot val="0.000000" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="2555904" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.395000" t="36.000027" p="180.000000" name="bank39" rot="143.999973" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="45.000000">
+    <rot val="0.000000" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="3014656" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="124.403098" p="160.483843" name="bank46" rot="69.410491" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="-22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="3080192" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="90.000202" p="163.999982" name="bank47" rot="105.410002" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="-22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="3145728" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="55.596651" p="160.483782" name="bank48" rot="141.410201" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="-22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<component type="panel" idstart="3211264" idfillbyfirst="y" idstepbyrow="256">
+<location r="0.425000" t="23.905622" p="137.140855" name="bank49" rot="177.410228" axis-x="0" axis-y="1" axis-z="0">
+  <rot val="47.178655">
+    <rot val="-22.073524" axis-x="0" axis-y="1" axis-z="0" />
+  </rot>
+</location>
+</component> 
+<!-- List of all the bank names:
+     bank13,bank16,bank17,bank18,bank19,bank20,bank22,bank26,bank27,bank28,bank29,bank33,bank36,bank37,bank38,bank39,bank46,bank47,bank48,bank49
+-->
+
+
+<!-- NOTE: This detector is the same as the SNAP detector -->
+<!-- Rectangular Detector Panel -->
+<type name="panel" is="rectangular_detector" type="pixel"
+    xpixels="256" xstart="-0.078795" xstep="+0.000618"
+    ypixels="256" ystart="-0.078795" ystep="+0.000618" >
+  <properties/>
+</type>
+
+  <!-- Pixel for Detectors-->
+  <type is="detector" name="pixel">
+    <cuboid id="pixel-shape">
+      <left-front-bottom-point y="-0.000309" x="-0.000309" z="0.0"/>
+      <left-front-top-point y="0.000309" x="-0.000309" z="0.0"/>
+      <left-back-bottom-point y="-0.000309" x="-0.000309" z="-0.0001"/>
+      <right-front-bottom-point y="-0.000309" x="0.000309" z="0.0"/>
+    </cuboid>
+    <algebra val="pixel-shape"/>
+  </type>
+
+  <!-- Shape for Monitors-->
+  <!-- TODO: Update to real shape -->
+  <type is="monitor" name="monitor">
+    <cylinder id="some-shape">
+      <centre-of-bottom-base p="0.0" r="0.0" t="0.0"/>
+      <axis y="0.0" x="0.0" z="1.0"/>
+      <radius val="0.01"/>
+      <height val="0.03"/>
+    </cylinder>
+    <algebra val="some-shape"/>
+  </type>
+
+  <!--MONITOR IDs-->
+  <idlist idname="monitors">
+    <id val="-1"/>
+    <id val="-2"/>
+  </idlist>
+</instrument>
diff --git a/qt/applications/workbench/CMakeLists.txt b/qt/applications/workbench/CMakeLists.txt
index 323ecee823ca5e7bbac587a5b51876e5066d7032..f3829339d18f2d734ac418b5bdc420d43db1ebed 100644
--- a/qt/applications/workbench/CMakeLists.txt
+++ b/qt/applications/workbench/CMakeLists.txt
@@ -20,8 +20,7 @@ set ( TEST_FILES
   workbench/config/test/test_user.py
   workbench/test/test_import.py
 
-  workbench/plotting/test/test_functions.py
-  workbench/plotting/test/test_figuretype.py
+  workbench/plotting/test/test_figuremanager.py
   workbench/plotting/test/test_globalfiguremanager.py
 
   workbench/widgets/plotselector/test/test_plotselector_model.py
@@ -29,9 +28,44 @@ set ( TEST_FILES
   workbench/widgets/plotselector/test/test_plotselector_view.py
 )
 
+set ( WORKBENCH_TEST_PY_FILES
+  workbench/test/test_fit_interactive_tool.py
+)
+
 set ( PYUNITTEST_RUN_SERIAL ON )
 set ( PYUNITTEST_QT_API pyqt5)
 pyunittest_add_test ( ${CMAKE_CURRENT_SOURCE_DIR}
   workbench ${TEST_FILES}
 )
-# Not installed yet...
+
+if ( CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR MATCHES "Xcode" )
+  set ( PYUNITTEST_RUNNER ${CMAKE_BINARY_DIR}/bin/$<CONFIG>/workbench-script.pyw -x)
+else()
+  set ( PYUNITTEST_RUNNER ${CMAKE_BINARY_DIR}/bin/workbench -x)
+endif()
+
+if (NOT APPLE)
+    # Tests fail/crash on OSX for unknown reasons. Skipping by the usual way
+    # via @unittest.skipIf also doesn't work.
+    pyunittest_add_test ( ${CMAKE_CURRENT_SOURCE_DIR}
+      workbench_gui ${WORKBENCH_TEST_PY_FILES}
+    )
+endif()
+
+# Install MantidWorkbench for OSX
+if ( APPLE )
+
+    configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/make_package.rb.in
+        ${CMAKE_CURRENT_BINARY_DIR}/make_package.rb
+        @ONLY )
+    install ( FILES  ${CMAKE_CURRENT_BINARY_DIR}/make_package.rb DESTINATION MantidWorkbench.app/ )
+    install ( CODE "
+        set ( bundle \${CMAKE_INSTALL_PREFIX}/MantidWorkbench.app )
+        execute_process(COMMAND chmod +x ./make_package.rb WORKING_DIRECTORY \${bundle})
+        execute_process(COMMAND ./make_package.rb WORKING_DIRECTORY \${bundle} RESULT_VARIABLE install_name_tool_result)
+        if(NOT install_name_tool_result EQUAL 0)
+          message(FATAL_ERROR \"Package script failed!!!\n\")
+        endif()
+    ")
+
+endif()
diff --git a/qt/applications/workbench/make_package.rb.in b/qt/applications/workbench/make_package.rb.in
new file mode 100755
index 0000000000000000000000000000000000000000..700ad18a5916795ffba2d9e8e68e9e9f38b0c573
--- /dev/null
+++ b/qt/applications/workbench/make_package.rb.in
@@ -0,0 +1,376 @@
+#!/usr/bin/env ruby
+#This loop changes the linking from /usr/local/lib to @rpath
+
+print `pwd`
+require 'pathname'
+
+def copyFile(file)
+  p "copying file #{file}"
+  output = system("cp #{file} Contents/MacOS/")
+  if output != true
+    exit 1
+  end
+end
+
+def copyOptionalFile(file)
+  if File.exist?(file)
+    p "copying optional file #{file}"
+    output = system("cp #{file} Contents/MacOS/")
+  end
+end
+
+def addPythonLibrary(input, output)
+  p "copying directory #{input} to #{output}"
+  output = system("rsync -a -L --exclude=.pyc #{input} #{output}")
+  if output != true
+    exit 1
+  end
+end
+
+def addPythonLibrariesInDirectory(input, output)
+  p "copying directory #{input} to #{output}"
+  output = system("rsync -a -L --exclude=.pyc #{input}/* #{output}")
+  if output != true
+    exit 1
+  end
+end
+
+# Find the qscintilla2 library. This is different depending
+# on what library you have installed from homebrew.
+def findQScintilla2(lib_dir)
+    pathname = lib_dir+"libqscintilla2_qt5.dylib"
+    if File.file?(pathname)
+        return pathname
+    else
+        p 'Could not file the libqscintilla2 library'
+        exit 1
+    end
+end
+
+def findBoostPythonMT(lib_dir)
+    [lib_dir, "usr/local/opt"].each do |dir|
+        if File.file?(dir+"libboost_python-mt.dylib")
+            return "libboost_python-mt.dylib"
+        elsif File.file?(dir+"libboost_python27-mt.dylib")
+            return "libboost_python27-mt.dylib"
+        end
+    end
+end
+
+lib_dir = Pathname.new("/usr/local/lib")
+openssl_dir = Pathname.new("/usr/local/opt/openssl/lib")
+ParaView_dir = Pathname.new("@ParaView_DIR@")
+
+#filenames with path for all shared libraries used by MantidPlot and its dependencies.
+library_filenames = ["libboost_regex-mt.dylib",
+                     "libboost_date_time-mt.dylib",
+                     "libboost_serialization-mt.dylib",
+                     "libboost_filesystem-mt.dylib",
+                     "libboost_system-mt.dylib",
+                     "libgsl.dylib",
+                     "libgslcblas.dylib",
+                     "libjsoncpp.dylib",
+                     "libmuparser.dylib",
+                     "libNeXus.dylib",
+                     "libNeXusCPP.dylib",
+                     "libPocoFoundation.dylib",
+                     "libPocoUtil.dylib",
+                     "libPocoXML.dylib",
+                     "libPocoNet.dylib",
+                     "libPocoCrypto.dylib",
+                     "libPocoNetSSL.dylib",
+                     "libTKernel.dylib",
+                     "libTKBO.dylib",
+                     "libTKernel.dylib",
+                     "libTKShHealing.dylib",
+                     "libTKPrim.dylib",
+                     "libTKMesh.dylib",
+                     "libTKBRep.dylib",
+                     "libTKGeomAlgo.dylib",
+                     "libTKTopAlgo.dylib",
+                     "libTKMath.dylib",
+                     "libTKG2d.dylib",
+                     "libTKG3d.dylib",
+                     "libTKGeomBase.dylib",
+                     "libmxml.dylib",
+                     "libhdf5.dylib",
+                     "libhdf5_hl.dylib",
+                     "libhdf5_cpp.dylib",
+                     "libhdf5_hl_cpp.dylib",
+                     "libmfhdf.dylib",
+                     "libdf.dylib",
+                     "libsz.dylib",
+                     "libjpeg.dylib",
+                     "libssl.dylib",
+                     "libcrypto.dylib",
+                     "libtbb.dylib",
+                     "libtbbmalloc.dylib",
+                     "libtbbmalloc_proxy.dylib",
+                     "librdkafka.dylib",
+                     "librdkafka++.dylib"]
+
+poco_version = "@POCO_VERSION@".split(".").map(&:to_i)
+if(poco_version[0] > 1 || (poco_version[0] == 1 && poco_version[1] >= 6))
+  library_filenames << "libPocoJSON.dylib"
+end
+
+if("@OPENMP_FOUND@" == "TRUE")
+  if not File.file?("/usr/lib/libomp.dylib")
+      # assume we're using system llvm
+      library_filenames << "libc++.dylib"
+  end
+  library_filenames << "libomp.dylib"
+end
+
+qscintilla2_lib = findQScintilla2("/usr/local/opt/qscintilla2/lib/")
+library_filenames << qscintilla2_lib
+library_filenames << findBoostPythonMT(lib_dir)
+
+#This copies the libraries over, then changes permissions and the id from /usr/local/lib to @rpath
+library_filenames.each do |filename|
+  if filename.include? "libssl.dylib"
+    copyFile(openssl_dir+filename)
+  elsif  filename.include? "libcrypto.dylib"
+    copyFile(openssl_dir+filename)
+  elsif filename.include? "libomp.dylib" or filename.include? "libc++.dylib"
+      if File.file?(lib_dir+"libomp.dylib")
+        # using system llvm
+        copyFile(lib_dir+filename)
+      elsif
+        # using homebrew llvm
+        copyFile("/usr/local/opt/llvm/lib/"+filename)
+      end
+  elsif File.file?(filename)
+    copyFile(filename)
+    filename = File.basename(filename)
+  else
+    copyFile(lib_dir+filename)
+  end
+  `chmod +w Contents/MacOS/#{filename}`
+  `install_name_tool -id @rpath/#{filename} Contents/MacOS/#{filename}`
+end
+
+# use install_tool_name to add an @rpath for the libraries
+Dir["Contents/MacOS/**/*.so"].each do |library|
+  dependencies = `otool -L #{library}`
+  print library, "\n"
+  `install_name_tool -add_rpath @loader_path/../MacOS #{library} > /dev/null 2>&1`
+end
+
+#use install_name_tool to change dependencies from /usr/local to libraries in the package.
+search_patterns = ["**/*.dylib","**/*.so"]
+search_patterns.each do |pattern|
+  Dir[pattern].each do |library|
+    dependencies = `otool -L #{library}`
+    dependencies.split("\n").each do |dependency|
+      currentname = dependency.strip.split(" ")
+      name_split_on_slash = currentname[0].strip.split("/")
+      name_split_on_period = name_split_on_slash[-1].split(".")
+      prefix = name_split_on_period[0]+"."
+      library_filenames.each do |filename|
+        basename = File.basename(filename,"dylib")
+        if prefix == basename
+          `install_name_tool -change #{currentname[0]} @rpath/#{basename+"dylib"} #{library}`
+        end
+      end
+    end
+  end
+end
+
+#We'll use macdeployqt to fix qt dependencies.
+Qt_Executables = "-executable=Contents/MacOS/#{File.basename(qscintilla2_lib)} "
+Qt_Executables << "-executable=Contents/MacOS/mantidqt/_commonqt5.so "
+Qt_Executables << "-executable=Contents/MacOS/mantidqt/widgets/instrumentview/_instrumentviewqt5.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtCore.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtGui.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtOpenGl.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtSql.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtSvg.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtWidgets.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtXml.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/QtPrintSupport.so "
+Qt_Executables << "-executable=Contents/MacOS/PyQt5/Qt.so "
+
+`/usr/local/opt/qt5/bin/macdeployqt ../MantidWorkbench.app #{Qt_Executables} > /dev/null 2>&1`
+
+if Dir.exist?("Contents/PlugIns")
+  #Fix remaining QT-related linking issues.
+  Dir["Contents/PlugIns/**/*.dylib"].each do |library|
+    basename =  File.basename(library)
+    `chmod +w #{library}`
+    `install_name_tool -id @rpath/#{basename} #{library}`
+  end
+else
+  p "Contents/PlugIns not created by macdeployqt."
+  exit 1
+end
+
+#Homebrew recently switched the linking from /usr/local/lib/* to /usr/local/opt/qt/lib/*
+#This attempts to determine the correct path to pass to install_name_tool.
+def find_linking_directories(patterns, lib_name, lib_path)
+    linking_dir = []
+    Dir[patterns[0]].each do |library|
+        dependencies = `otool -L #{library}`
+        dependencies.split("\n").each do |dependency|
+            if dependency.match(lib_name)
+                if dependency.match(lib_path)
+                    linking_dir << lib_path
+                else
+                    linking_dir << '/usr/local/lib'
+                end
+            end
+        end
+    end
+    return linking_dir
+end
+
+#change id of all libraries that match patterns
+def change_id(patterns, lib_path)
+    patterns.each do |pattern|
+        Dir[pattern].each do |library|
+            basename =  File.basename(library)
+            `chmod +w #{lib_path}/#{basename}`
+            `install_name_tool -id @rpath/#{basename} #{lib_path}/#{basename}`
+        end
+    end
+end
+
+pyqt5_patterns = ["**/PyQt5/*.so"]
+change_id(pyqt5_patterns, "Contents/MacOS/PyQt5/")
+Qt_linking_dir = find_linking_directories(pyqt5_patterns, 'Qt.*.framework/Versions/\d/Qt.*',"/usr/local/opt/qt@5/lib")
+
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtCore.so`
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtWidgets.so`
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtGui.so`
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtOpenGl.so`
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtSql.so`
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtSvg.so`
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtXml.so`
+`install_name_tool -change #{Qt_linking_dir[0]}/QtCore.framework/Versions/5/Qt @loader_path/../../Frameworks/QtCore.framework/Versions/5/Qt Contents/MacOS/PyQt5/QtPrintSupport.so`
+
+# We only need one copy of openssl
+`install_name_tool -change @loader_path/../../../libssl.1.0.0.dylib @loader_path/../../../../MacOS/libssl.dylib Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork`
+`install_name_tool -change @loader_path/../../../libcrypto.1.0.0.dylib @loader_path/../../../../MacOS/libcrypto.dylib Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork`
+# `rm Contents/Frameworks/libssl.1.0.0.dylib`
+# `rm Contents/Frameworks/libcrypto.1.0.0.dylib`
+
+#change id of all libraries that match patterns
+def change_id(patterns, lib_path)
+    patterns.each do |pattern|
+        Dir[pattern].each do |library|
+            basename =  File.basename(library)
+            `chmod +w #{lib_path}/#{basename}`
+            `install_name_tool -id @rpath/#{basename} #{lib_path}/#{basename}`
+        end
+    end
+end
+
+#Homebrew recently switched the linking from /usr/local/lib/* to /usr/local/opt/qt/lib/*
+#This attempts to determine the correct path to pass to install_name_tool.
+def find_linking_directories(patterns, lib_name, lib_path)
+    linking_dir = []
+    Dir[patterns[0]].each do |library|
+        dependencies = `otool -L #{library}`
+        dependencies.split("\n").each do |dependency|
+            if dependency.match(lib_name)
+                if dependency.match(lib_path)
+                    linking_dir << lib_path
+                else
+                    linking_dir << '/usr/local/lib'
+                end
+            end
+        end
+    end
+    return linking_dir
+end
+
+
+#Copy over python libraries not included with OSX.
+#currently missing epics
+system_python_extras = "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python"
+pip_site_packages = "/Library/Python/2.7/site-packages"
+directories = ["sphinx","sphinx_bootstrap_theme","IPython","zmq","pygments","backports", "qtawesome", "qtpy",
+               "certifi","tornado","markupsafe","matplotlib","mpl_toolkits", "jinja2","jsonschema","functools32",
+               "ptyprocess","CifFile","yaml","requests","networkx","PIL","dateutil","pytz",
+               "pywt","skimage"]
+directories.each do |directory|
+  module_dir = "#{pip_site_packages}/#{directory}"
+  if !File.exist?(module_dir)
+    module_dir = "#{system_python_extras}/#{directory}"
+    if !File.exist?(module_dir)
+      p "Cannot find python module #{directory} to copy into bundle"
+      exit 1
+    end
+  end
+  addPythonLibrary(module_dir,"Contents/MacOS/")
+end
+
+# Some versions of mpltool_kits are missing their __init__ file
+mpltoolkit_init = "Contents/MacOS/mpl_toolkits/__init__.py"
+if !File.exist?(mpltoolkit_init)
+  p "Creating missing #{mpltoolkit_init}"
+  `touch #{mpltoolkit_init}`
+end
+
+
+# ---------------------------------------------
+# H5Py section
+# ---------------------------------------------
+h5py_path = "/usr/local/lib/python2.7/site-packages"
+h5py_directories = ["h5py"]
+h5py_directories.each do |directory|
+  addPythonLibrary("#{h5py_path}/#{directory}","Contents/MacOS/")
+end
+
+h5py_patterns = ["**/h5py/*.so"]
+change_id(h5py_patterns, "Contents/MacOS/h5py/")
+
+h5py_linking_dir = find_linking_directories(h5py_patterns, 'lib/libhdf5', '/usr/local/opt/hdf5/lib')
+
+h5py_patterns.each do |pattern|
+  Dir[pattern].each do |library|
+    basename =  File.basename(library)
+    dependencies = `otool -L #{library}`
+    dependencies.split("\n").each do |dependency|
+        currentname = dependency.strip.split(" ")
+        filename = currentname[0]
+        if filename.include? "libhdf5"
+          name_split_on_slash = filename.strip.split("/")
+          filename_no_dir = name_split_on_slash[-1]
+          parts = filename_no_dir.strip.split(".")
+          loader_filename = parts[0] + "." + parts[2]
+          `install_name_tool -add_rpath @loader_path/../ Contents/MacOS/h5py/#{basename} > /dev/null 2>&1`
+          `install_name_tool -change #{h5py_linking_dir[0]}/#{filename_no_dir} @rpath/#{loader_filename} Contents/MacOS/h5py/#{basename}`
+       end
+    end
+  end
+end
+
+files = ["gnureadline.so","cycler.py","readline.py","pyparsing.py","mistune.py"]
+files.each do |file|
+  copyFile("#{pip_site_packages}/#{file}")
+end
+
+# mistune.so isn't present in v0.7
+copyOptionalFile("#{pip_site_packages}/mistune.so")
+
+`mkdir Contents/MacOS/bin`
+`cp /usr/local/bin/ipython@PYTHON_VERSION_MAJOR@ Contents/MacOS/bin/`
+
+#Lastly check for any libraries in the package linking against homebrew libraries.
+search_patterns.each do |pattern|
+  Dir[pattern].each do |library|
+    dependencies = `otool -L #{library}`
+    dependencies.split("\n").each do |dependency|
+      if dependency.include? "/usr/local/"
+        p "issue with library: #{library} linked against: #{dependency}"
+        exit 1
+      end
+      if dependency.include? "@loader_path/libTK"
+        p "issue with library: #{library} linked against: #{dependency}"
+        p "Is an OpenCascade library missing?"
+        exit 1
+      end
+    end
+  end
+end
diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py
index 4022774d702953148e9a21622b3091707ec4d710..dc34a443255c62a81ed62552efae41301849ec67 100644
--- a/qt/applications/workbench/workbench/app/mainwindow.py
+++ b/qt/applications/workbench/workbench/app/mainwindow.py
@@ -43,7 +43,7 @@ requirements.check_qt()
 # -----------------------------------------------------------------------------
 # Qt
 # -----------------------------------------------------------------------------
-from qtpy.QtCore import (QEventLoop, Qt, QCoreApplication, QPoint, QSize, QSettings)  # noqa
+from qtpy.QtCore import (QEventLoop, Qt, QCoreApplication, QPoint, QSize)  # noqa
 from qtpy.QtGui import (QColor, QGuiApplication, QIcon, QPixmap)  # noqa
 from qtpy.QtWidgets import (QApplication, QDesktopWidget, QFileDialog,
                             QMainWindow, QSplashScreen)  # noqa
@@ -52,11 +52,13 @@ from mantidqt.widgets.manageuserdirectories import ManageUserDirectories  # noqa
 from mantidqt.widgets.codeeditor.execution import PythonCodeExecution  # noqa
 from mantidqt.utils.qt import (add_actions, create_action, plugins,
                                widget_updates_disabled)  # noqa
+from mantidqt.project.project import Project  # noqa
 
 # Pre-application setup
 plugins.setup_library_paths()
 
 from workbench.config import APPNAME, CONF, ORG_DOMAIN, ORGANIZATION  # noqa
+from workbench.plotting.globalfiguremanager import GlobalFigureManager  # noqa
 
 
 # -----------------------------------------------------------------------------
@@ -137,6 +139,7 @@ class MainWindow(QMainWindow):
 
         # -- instance attributes --
         self.setWindowTitle("Mantid Workbench")
+        self.setObjectName("Mantid Workbench")
 
         # widgets
         self.messagedisplay = None
@@ -164,6 +167,9 @@ class MainWindow(QMainWindow):
         # Layout
         self.setDockOptions(self.DOCKOPTIONS)
 
+        # Project
+        self.project = None
+
     def setup(self):
         # menus must be done first so they can be filled by the
         # plugins in register_plugin
@@ -207,6 +213,9 @@ class MainWindow(QMainWindow):
         self.workspacewidget.register_plugin()
         self.widgets.append(self.workspacewidget)
 
+        # Set up the project object
+        self.project = Project(GlobalFigureManager)
+
         # uses default configuration as necessary
         self.readSettings(CONF)
 
@@ -238,26 +247,29 @@ class MainWindow(QMainWindow):
     def create_actions(self):
         # --- general application menu options --
         # file menu
-        action_open = create_action(self, "Open",
+        action_open = create_action(self, "Open Script",
                                     on_triggered=self.open_file,
                                     shortcut="Ctrl+O",
-                                    shortcut_context=Qt.ApplicationShortcut,
-                                    icon_name="fa.folder-open")
-        action_save = create_action(self, "Save",
-                                    on_triggered=self.save_file,
-                                    shortcut="Ctrl+S",
-                                    shortcut_context=Qt.ApplicationShortcut,
-                                    icon_name="fa.save")
+                                    shortcut_context=Qt.ApplicationShortcut)
+        action_load_project = create_action(self, "Open Project",
+                                            on_triggered=self.load_project)
+        action_save_script = create_action(self, "Save Script",
+                                           on_triggered=self.save_script,
+                                           shortcut="Ctrl+S",
+                                           shortcut_context=Qt.ApplicationShortcut)
+        action_save_project = create_action(self, "Save Project",
+                                            on_triggered=self.save_project)
+        action_save_project_as = create_action(self, "Save Project as...",
+                                               on_triggered=self.save_project_as)
+
         action_manage_directories = create_action(self, "Manage User Directories",
-                                                  on_triggered=self.open_manage_directories,
-                                                  icon_name="fa.folder")
+                                                  on_triggered=self.open_manage_directories)
 
         action_quit = create_action(self, "&Quit", on_triggered=self.close,
                                     shortcut="Ctrl+Q",
-                                    shortcut_context=Qt.ApplicationShortcut,
-                                    icon_name="fa.power-off")
-        self.file_menu_actions = [action_open, action_save, action_manage_directories, None, action_quit]
-
+                                    shortcut_context=Qt.ApplicationShortcut)
+        self.file_menu_actions = [action_open, action_load_project, None, action_save_script, action_save_project,
+                                  action_save_project_as, None, action_manage_directories, None, action_quit]
         # view menu
         action_restore_default = create_action(self, "Restore Default Layout",
                                                on_triggered=self.prep_window_for_reset,
@@ -401,6 +413,14 @@ class MainWindow(QMainWindow):
 
     # ----------------------- Events ---------------------------------
     def closeEvent(self, event):
+        # Check whether or not to save project
+        if not self.project.saved:
+            # Offer save
+            if self.project.offer_save(self):
+                # Cancel has been clicked
+                event.ignore()
+                return
+
         # Close editors
         if self.editor.app_closing():
             self.writeSettings(CONF)  # write current window information to global settings object
@@ -428,10 +448,18 @@ class MainWindow(QMainWindow):
             return
         self.editor.open_file_in_new_tab(filepath)
 
-    def save_file(self):
-        # todo: how should this interact with project saving and workspaces when they are implemented?
+    def save_script(self):
         self.editor.save_current_file()
 
+    def save_project(self):
+        self.project.save()
+
+    def save_project_as(self):
+        self.project.save_as()
+
+    def load_project(self):
+        self.project.load()
+
     def open_manage_directories(self):
         ManageUserDirectories(self).exec_()
 
diff --git a/qt/applications/workbench/workbench/plotting/backend_workbench.py b/qt/applications/workbench/workbench/plotting/backend_workbench.py
index b04b475d6f0cd68a2717848f43f4c2fecc9c69b0..96a4f720c503f0c8fcb2f5c4c510c03406d7487e 100644
--- a/qt/applications/workbench/workbench/plotting/backend_workbench.py
+++ b/qt/applications/workbench/workbench/plotting/backend_workbench.py
@@ -19,19 +19,15 @@ from __future__ import (absolute_import, division, print_function,
 # std imports
 import importlib
 
-
+# local imports
+from matplotlib.backends.backend_qt5agg import (draw_if_interactive as draw_if_interactive_impl,
+                                                show as show_impl)  # noqa
 # 3rd party imports
 # Put these first so that the correct Qt version is selected by qtpy
 from qtpy import QT_VERSION
 
-# local imports
-from workbench.plotting.figuremanager import (backend_version,  # noqa
-    draw_if_interactive as draw_if_interactive_impl,
-    new_figure_manager as new_figure_manager_impl,
-    new_figure_manager_given_figure as new_figure_manager_given_figure_impl,
-    show as show_impl,
-    QAppThreadCall
-)
+from workbench.plotting.figuremanager import (QAppThreadCall, new_figure_manager as new_figure_manager_impl,
+                                              new_figure_manager_given_figure as new_figure_manager_given_figure_impl)
 
 # Import the *real* matplotlib backend for the canvas
 mpl_qtagg_backend = importlib.import_module('matplotlib.backends.backend_qt{}agg'.format(QT_VERSION[0]))
@@ -40,7 +36,6 @@ try:
 except KeyError:
     raise ImportError("Unknown form of matplotlib Qt backend.")
 
-
 # -----------------------------------------------------------------------------
 # Backend implementation
 # -----------------------------------------------------------------------------
diff --git a/qt/applications/workbench/workbench/plotting/figuremanager.py b/qt/applications/workbench/workbench/plotting/figuremanager.py
index 4fb09a4c3f4b33417f1ce7f42c15038a72ff5a2d..c83ae048ff43161a71611241b4d46506323aaaad 100644
--- a/qt/applications/workbench/workbench/plotting/figuremanager.py
+++ b/qt/applications/workbench/workbench/plotting/figuremanager.py
@@ -8,21 +8,104 @@
 #
 #
 """Provides our custom figure manager to wrap the canvas, window and our custom toolbar"""
+import sys
+from functools import wraps
 
 # 3rdparty imports
 import matplotlib
-from matplotlib.backend_bases import FigureManagerBase
-from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg, backend_version, draw_if_interactive, show)  # noqa
 from matplotlib._pylab_helpers import Gcf
-from qtpy.QtCore import Qt, QObject
+from matplotlib.backend_bases import FigureManagerBase
+from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg)  # noqa
+from qtpy.QtCore import QObject, Qt
 from qtpy.QtWidgets import QApplication, QLabel
 from six import text_type
 
 # local imports
+from mantid.api import AnalysisDataServiceObserver
+from mantid.plots import MantidAxes
+from mantidqt.plotting.figuretype import FigureType, figure_type
+from mantidqt.widgets.fitpropertybrowser import FitPropertyBrowser
 from workbench.plotting.figurewindow import FigureWindow
 from workbench.plotting.propertiesdialog import LabelEditor, XAxisEditor, YAxisEditor
-from workbench.plotting.toolbar import WorkbenchNavigationToolbar
 from workbench.plotting.qappthreadcall import QAppThreadCall
+from workbench.plotting.toolbar import WorkbenchNavigationToolbar
+
+
+def _catch_exceptions(func):
+    """
+    Catch all exceptions in method and print a traceback to stderr
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        try:
+            func(*args, **kwargs)
+        except Exception:
+            sys.stderr.write("Error occurred in handler:\n")
+            import traceback
+            traceback.print_exc()
+
+    return wrapper
+
+
+class FigureManagerADSObserver(AnalysisDataServiceObserver):
+
+    def __init__(self, manager):
+        super(FigureManagerADSObserver, self).__init__()
+        self.window = manager.window
+        self.canvas = manager.canvas
+
+        self.observeClear(True)
+        self.observeDelete(True)
+        self.observeReplace(True)
+
+    @_catch_exceptions
+    def clearHandle(self):
+        """
+        Called when the ADS is deleted all of its workspaces
+        """
+        self.window.close()
+
+    @_catch_exceptions
+    def deleteHandle(self, _, workspace):
+        """
+        Called when the ADS has deleted a workspace. Checks the
+        attached axes for any hold a plot from this workspace. If removing
+        this leaves empty axes then the parent window is triggered for
+        closer
+        :param _: The name of the workspace. Unused
+        :param workspace: A pointer to the workspace
+        """
+        # Find the axes with this workspace reference
+        all_axes = self.canvas.figure.axes
+        if not all_axes:
+            return
+        empty_axes = True
+        for ax in all_axes:
+            if isinstance(ax, MantidAxes):
+                empty_axes = empty_axes & ax.remove_workspace_artists(workspace)
+        if empty_axes:
+            self.window.close()
+        else:
+            self.canvas.draw_idle()
+
+    @_catch_exceptions
+    def replaceHandle(self, _, workspace):
+        """
+        Called when the ADS has replaced a workspace with one of the same name.
+        If this workspace is attached to this figure then its data is updated
+        :param _: The name of the workspace. Unused
+        :param workspace: A reference to the new workspace
+        """
+        redraw = False
+        for ax in self.canvas.figure.axes:
+            if isinstance(ax, MantidAxes):
+                redraw_this = ax.replace_workspace_artists(workspace)
+            else:
+                continue
+            redraw = redraw | redraw_this
+        if redraw:
+            self.canvas.draw_idle()
 
 
 class FigureManagerWorkbench(FigureManagerBase, QObject):
@@ -50,8 +133,6 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
         self.show = QAppThreadCall(self._show_orig)
         self._window_activated_orig = self._window_activated
         self._window_activated = QAppThreadCall(self._window_activated_orig)
-        self._widgetclosed_orig = self._widgetclosed
-        self._widgetclosed = QAppThreadCall(self._widgetclosed_orig)
         self.set_window_title_orig = self.set_window_title
         self.set_window_title = QAppThreadCall(self.set_window_title_orig)
         self.fig_visibility_changed_orig = self.fig_visibility_changed
@@ -60,7 +141,7 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
         self.window = FigureWindow(canvas)
         self.window.activated.connect(self._window_activated)
         self.window.closing.connect(canvas.close_event)
-        self.window.closing.connect(self._widgetclosed)
+        self.window.closing.connect(self.destroy)
         self.window.visibility_changed.connect(self.fig_visibility_changed)
 
         self.window.setWindowTitle("Figure %d" % num)
@@ -87,6 +168,8 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
             self.window.addToolBar(self.toolbar)
             self.toolbar.message.connect(self.statusbar_label.setText)
             self.toolbar.sig_grid_toggle_triggered.connect(self.grid_toggle)
+            self.toolbar.sig_toggle_fit_triggered.connect(self.fit_toggle)
+            self.toolbar.setFloatable(False)
             tbs_height = self.toolbar.sizeHint().height()
         else:
             tbs_height = 0
@@ -99,7 +182,11 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
         height = cs.height() + self._status_and_tool_height
         self.window.resize(cs.width(), height)
 
+        self.fit_browser = FitPropertyBrowser(canvas)
+        self.fit_browser.closing.connect(self.handle_fit_browser_close)
         self.window.setCentralWidget(canvas)
+        self.window.addDockWidget(Qt.LeftDockWidgetArea, self.fit_browser)
+        self.fit_browser.hide()
 
         if matplotlib.is_interactive():
             self.window.show()
@@ -109,11 +196,13 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
             # This will be called whenever the current axes is changed
             if self.toolbar is not None:
                 self.toolbar.update()
+
         canvas.figure.add_axobserver(notify_axes_change)
 
         # Register canvas observers
         self._cids = []
         self._cids.append(self.canvas.mpl_connect('button_press_event', self.on_button_press))
+        self._ads_observer = FigureManagerADSObserver(self)
 
         self.window.raise_()
 
@@ -126,22 +215,8 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
     def _window_activated(self):
         Gcf.set_active(self)
 
-    def _widgetclosed(self):
-        if self.window._destroying:
-            return
-        self.window._destroying = True
-        for id in self._cids:
-            self.canvas.mpl_disconnect(id)
-        try:
-            Gcf.destroy(self.num)
-        except AttributeError:
-            pass
-            # It seems that when the python session is killed,
-            # Gcf can get destroyed before the Gcf.destroy
-            # line is run, leading to a useless AttributeError.
-
     def _get_toolbar(self, canvas, parent):
-            return WorkbenchNavigationToolbar(canvas, parent, False)
+        return WorkbenchNavigationToolbar(canvas, parent, False)
 
     def resize(self, width, height):
         'set the canvas size in pixels'
@@ -164,19 +239,36 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
 
         # Hack to ensure the canvas is up to date
         self.canvas.draw_idle()
+        if figure_type(self.canvas.figure) != FigureType.Line:
+            action = self.toolbar._actions['toggle_fit']
+            action.setEnabled(False)
+            action.setVisible(False)
 
     def destroy(self, *args):
         # check for qApp first, as PySide deletes it in its atexit handler
         if QApplication.instance() is None:
             return
+
         if self.window._destroying:
             return
         self.window._destroying = True
-        self.window.destroyed.connect(self._widgetclosed)
+
         if self.toolbar:
             self.toolbar.destroy()
+        self._ads_observer.observeAll(False)
+        del self._ads_observer
+        for id in self._cids:
+            self.canvas.mpl_disconnect(id)
         self.window.close()
 
+        try:
+            Gcf.destroy(self.num)
+        except AttributeError:
+            pass
+            # It seems that when the python session is killed,
+            # Gcf can get destroyed before the Gcf.destroy
+            # line is run, leading to a useless AttributeError.
+
     def grid_toggle(self):
         """
         Toggle grid lines on/off
@@ -187,6 +279,21 @@ class FigureManagerWorkbench(FigureManagerBase, QObject):
             ax.grid()
         canvas.draw_idle()
 
+    def fit_toggle(self):
+        """
+        Toggle fit browser and tool on/off
+        """
+        if self.fit_browser.isVisible():
+            self.fit_browser.hide()
+        else:
+            self.fit_browser.show()
+
+    def handle_fit_browser_close(self):
+        """
+        Respond to a signal that user closed self.fit_browser by clicking the [x] button.
+        """
+        self.toolbar.trigger_fit_toggle_action()
+
     def hold(self):
         """
         Mark this figure as held
@@ -269,12 +376,13 @@ def new_figure_manager_given_figure(num, figure):
 if __name__ == '__main__':
     # testing code
     import numpy as np
+
     qapp = QApplication([' '])
     qapp.setAttribute(Qt.AA_UseHighDpiPixmaps)
     if hasattr(Qt, 'AA_EnableHighDpiScaling'):
         qapp.setAttribute(Qt.AA_EnableHighDpiScaling, True)
 
-    x = np.linspace(0, 10*np.pi, 1000)
+    x = np.linspace(0, 10 * np.pi, 1000)
     cx, sx = np.cos(x), np.sin(x)
     fig_mgr_1 = new_figure_manager(1)
     fig1 = fig_mgr_1.canvas.figure
diff --git a/qt/applications/workbench/workbench/plotting/figurewindow.py b/qt/applications/workbench/workbench/plotting/figurewindow.py
index 77d32374295be504bab50ea29a16018fb08b85ce..85033c47c56b110bb369a5f05de30d4d64e1ea1c 100644
--- a/qt/applications/workbench/workbench/plotting/figurewindow.py
+++ b/qt/applications/workbench/workbench/plotting/figurewindow.py
@@ -18,7 +18,7 @@ from qtpy.QtCore import QEvent, Signal
 from qtpy.QtWidgets import QMainWindow
 
 # local imports
-from .figuretype import figure_type, FigureType
+from mantidqt.plotting.figuretype import figure_type, FigureType
 
 
 class FigureWindow(QMainWindow):
@@ -88,7 +88,7 @@ class FigureWindow(QMainWindow):
         if len(names) == 0:
             return
         # local import to avoid circular import with FigureManager
-        from workbench.plotting.functions import pcolormesh_from_names, plot_from_names
+        from mantidqt.plotting.functions import pcolormesh_from_names, plot_from_names
 
         fig = self._canvas.figure
         fig_type = figure_type(fig)
diff --git a/qt/applications/workbench/workbench/plotting/qappthreadcall.py b/qt/applications/workbench/workbench/plotting/qappthreadcall.py
index 5eae7efdfbbde71a877a30e14e5fd14be5a3ed02..c70d9de82564f1652fd73d35cb15d1287ffc0538 100644
--- a/qt/applications/workbench/workbench/plotting/qappthreadcall.py
+++ b/qt/applications/workbench/workbench/plotting/qappthreadcall.py
@@ -24,10 +24,11 @@ class QAppThreadCall(QObject):
     """
 
     def __init__(self, callee):
-        self.qApp = QApplication.instance()
-
         QObject.__init__(self)
-        self.moveToThread(self.qApp.thread())
+        qapp = QApplication.instance()
+        if qapp is not None:
+            self.moveToThread(qapp.thread())
+            self.qApp = qapp
         self.callee = callee
         # Help should then give the correct doc
         self.__call__.__func__.__doc__ = callee.__doc__
diff --git a/qt/applications/workbench/workbench/plotting/test/test_figuremanager.py b/qt/applications/workbench/workbench/plotting/test/test_figuremanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..00753f43d5abf7eb1eb69d7fc20fc7aff652c55e
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/test/test_figuremanager.py
@@ -0,0 +1,24 @@
+import unittest
+try:
+    from unittest import MagicMock, patch
+except ImportError:
+    from mock import MagicMock, patch
+
+from mantidqt.utils.qt.test import GuiTest
+
+from workbench.plotting.figuremanager import FigureCanvasQTAgg, FigureManagerWorkbench
+
+
+class FigureManagerWorkbenchTest(GuiTest):
+
+    @patch("workbench.plotting.qappthreadcall.QAppThreadCall")
+    def test_construction(self, mock_qappthread):
+        mock_qappthread.return_value = mock_qappthread
+        fig = MagicMock()
+        canvas = FigureCanvasQTAgg(fig)
+        fig_mgr = FigureManagerWorkbench(canvas, 1)
+        self.assertTrue(fig_mgr is not None)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/qt/applications/workbench/workbench/plotting/test/test_globalfiguremanager.py b/qt/applications/workbench/workbench/plotting/test/test_globalfiguremanager.py
index c44da2c9b6551166409dbe881c05fa0a7d5c6ffe..f8f36b6e324845f206314d58f2937795b9d8ad20 100644
--- a/qt/applications/workbench/workbench/plotting/test/test_globalfiguremanager.py
+++ b/qt/applications/workbench/workbench/plotting/test/test_globalfiguremanager.py
@@ -1,6 +1,8 @@
 import unittest
-
-from mock import Mock, call, patch
+try:
+    from unittest import Mock, call, patch
+except ImportError:
+    from mock import Mock, call, patch
 
 from workbench.plotting.globalfiguremanager import FigureAction, GlobalFigureManager, GlobalFigureManagerObserver
 from workbench.plotting.observabledictionary import DictionaryAction
@@ -301,3 +303,7 @@ class TestGlobalFigureManager(unittest.TestCase):
 
     def assertNotCalled(self, mock):
         self.assertEqual(0, mock.call_count)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/qt/applications/workbench/workbench/plotting/toolbar.py b/qt/applications/workbench/workbench/plotting/toolbar.py
index dc99531de3bf956e3410dc708bac1736eda00387..ff89f5b5b09cf9205074887cd87f94cd2bffa82c 100644
--- a/qt/applications/workbench/workbench/plotting/toolbar.py
+++ b/qt/applications/workbench/workbench/plotting/toolbar.py
@@ -26,6 +26,7 @@ class WorkbenchNavigationToolbar(NavigationToolbar2QT):
     sig_grid_toggle_triggered = QtCore.Signal()
     sig_active_triggered = QtCore.Signal()
     sig_hold_triggered = QtCore.Signal()
+    sig_toggle_fit_triggered = QtCore.Signal()
 
     toolitems = (
         ('Home', 'Reset original view', 'fa.home', 'home', None),
@@ -36,7 +37,9 @@ class WorkbenchNavigationToolbar(NavigationToolbar2QT):
         ('Save', 'Save the figure', 'fa.save', 'save_figure', None),
         ('Print','Print the figure', 'fa.print', 'print_figure', None),
         (None, None, None, None, None),
-        ('Customize', 'Configure plot options', 'fa.cog', 'edit_parameters', None)
+        ('Customize', 'Configure plot options', 'fa.cog', 'edit_parameters', None),
+        (None, None, None, None, None),
+        ('Fit', 'Toggle fit browser on/off', None, 'toggle_fit', False),
     )
 
     def _init_toolbar(self):
@@ -79,6 +82,12 @@ class WorkbenchNavigationToolbar(NavigationToolbar2QT):
     def toggle_grid(self):
         self.sig_grid_toggle_triggered.emit()
 
+    def toggle_fit(self):
+        self.sig_toggle_fit_triggered.emit()
+
+    def trigger_fit_toggle_action(self):
+        self._actions['toggle_fit'].trigger()
+
     def print_figure(self):
         printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
         printer.setOrientation(QtPrintSupport.QPrinter.Landscape)
diff --git a/qt/applications/workbench/workbench/plugins/editor.py b/qt/applications/workbench/workbench/plugins/editor.py
index ee320b3611e2fa5b36aae6229418225f264008b5..5dd6bcd5612a43fe196c22f08d2b84065ff2891a 100644
--- a/qt/applications/workbench/workbench/plugins/editor.py
+++ b/qt/applications/workbench/workbench/plugins/editor.py
@@ -10,15 +10,15 @@
 from __future__ import (absolute_import, unicode_literals)
 
 # system imports
+from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QVBoxLayout
 
 # third-party library imports
 from mantidqt.utils.qt import add_actions, create_action
 from mantidqt.widgets.codeeditor.multifileinterpreter import MultiPythonFileInterpreter
-from qtpy.QtCore import Qt
-from qtpy.QtWidgets import QVBoxLayout
-
 # local package imports
 from workbench.plugins.base import PluginWidget
+
 # from mantidqt.utils.qt import toQSettings when readSettings/writeSettings are implemented
 
 
@@ -53,7 +53,7 @@ class MultiFileEditor(PluginWidget):
         # attributes
         self.run_action = create_action(self, "Run",
                                         on_triggered=self.editors.execute_current,
-                                        shortcut="Ctrl+Return",
+                                        shortcut=("Ctrl+Return", "Ctrl+Enter"),
                                         shortcut_context=Qt.ApplicationShortcut)
         self.abort_action = create_action(self, "Abort",
                                           on_triggered=self.editors.abort_current)
diff --git a/qt/applications/workbench/workbench/plugins/workspacewidget.py b/qt/applications/workbench/workbench/plugins/workspacewidget.py
index 89d5a9038663d0ad29ee78c0e7c39aa8187bd6bb..0f80d5a968f1e908dae1446cfb2c76d711029ad4 100644
--- a/qt/applications/workbench/workbench/plugins/workspacewidget.py
+++ b/qt/applications/workbench/workbench/plugins/workspacewidget.py
@@ -12,17 +12,20 @@ from __future__ import (absolute_import, unicode_literals)
 # system imports
 from functools import partial
 
+import matplotlib.pyplot
+from qtpy.QtWidgets import QMessageBox, QVBoxLayout
+
 # third-party library imports
 from mantid.api import AnalysisDataService
+from mantid.kernel import logger
+from mantidqt.widgets.instrumentview.presenter import InstrumentViewPresenter
 from mantidqt.widgets.matrixworkspacedisplay.presenter import MatrixWorkspaceDisplay
 from mantidqt.widgets.samplelogs.presenter import SampleLogs
-from mantidqt.widgets.instrumentview.presenter import InstrumentViewPresenter
+from mantidqt.widgets.tableworkspacedisplay.presenter import TableWorkspaceDisplay
 from mantidqt.widgets.workspacewidget.workspacetreewidget import WorkspaceTreeWidget
-from qtpy.QtWidgets import QMessageBox, QVBoxLayout
-
+from mantidqt.plotting.functions import can_overplot, pcolormesh, plot, plot_from_names
 # local package imports
 from workbench.plugins.base import PluginWidget
-from workbench.plotting.functions import can_overplot, pcolormesh, plot_from_names, plot
 
 
 class WorkspaceWidget(PluginWidget):
@@ -121,10 +124,20 @@ class WorkspaceWidget(PluginWidget):
 
     def _do_show_data(self, names):
         for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True):
-            # the plot function is being injected in the presenter
-            # this is done so that the plotting library is mockable in testing
-            presenter = MatrixWorkspaceDisplay(ws, plot=plot, parent=self)
-            presenter.view.show()
+            try:
+                MatrixWorkspaceDisplay.supports(ws)
+                # the plot function is being injected in the presenter
+                # this is done so that the plotting library is mockable in testing
+                presenter = MatrixWorkspaceDisplay(ws, plot=plot, parent=self)
+                presenter.view.show()
+            except ValueError:
+                try:
+                    TableWorkspaceDisplay.supports(ws)
+                    presenter = TableWorkspaceDisplay(ws, plot=matplotlib.pyplot, parent=self)
+                    presenter.view.show()
+                except ValueError:
+                    logger.error(
+                        "Could not open workspace: {0} with neither MatrixWorkspaceDisplay nor TableWorkspaceDisplay.")
 
     def _action_double_click_workspace(self, name):
         self._do_show_data([name])
diff --git a/qt/applications/workbench/workbench/test/test_fit_interactive_tool.py b/qt/applications/workbench/workbench/test/test_fit_interactive_tool.py
new file mode 100644
index 0000000000000000000000000000000000000000..c36cfa1f4c08bfdb4577921b296be903873b9a7c
--- /dev/null
+++ b/qt/applications/workbench/workbench/test/test_fit_interactive_tool.py
@@ -0,0 +1,198 @@
+from __future__ import print_function
+import unittest
+
+from qtpy.QtCore import QPoint
+from qtpy.QtGui import QCursor
+
+from mantid.simpleapi import *
+from mantidqt.utils.qt.test.gui_window_test import *
+
+from mantidqt.plotting.functions import plot, pcolormesh
+from workbench.plotting.globalfiguremanager import GlobalFigureManager
+from workbench.test.workbenchtests import runTests
+
+
+def on_darwin():
+    return sys.platform == 'darwin'
+
+
+@unittest.skipIf(on_darwin(), "Couldn't make it work for a mac")
+class TestFitPropertyBrowser(WorkbenchGuiTest):
+
+    def start(self):
+        if 'ws' not in mtd:
+            ws = Load(r'irs26176_graphite002_conv_1LFixF_s0_to_9_Result.nxs', OutputWorkspace='ws')
+        else:
+            ws = mtd['ws']
+        plot([ws], [1])
+        manager = GlobalFigureManager.get_active()
+        self.w = manager.window
+        trigger_action(find_action_with_text(self.w, 'Fit'))
+        yield 0.1
+        self.fit_browser = manager.fit_browser
+
+    def move_marker(self, canvas, marker, pos, dx, try_other_way_if_failed):
+        tr = self.fit_browser.tool.ax.get_xaxis_transform()
+        x0 = tr.transform((0, 0))[0]
+        dx_pxl = tr.transform((dx, 0))[0] - x0
+        pos.setX(marker.get_x_in_pixels())
+        new_pos = pos + QPoint(dx_pxl, 0)
+        yield drag_mouse(canvas, pos, new_pos)
+        pos1 = canvas.mapFromGlobal(QCursor.pos())
+        if try_other_way_if_failed and pos1 != new_pos:
+            new_x = marker.x + dx
+            marker.on_click(pos.x())
+            marker.mouse_move(new_x)
+            yield 0.1
+            marker.stop()
+
+    def move_start_x(self, canvas, pos, dx, try_other_way_if_failed=True):
+        return self.move_marker(canvas, self.fit_browser.tool.fit_start_x, pos, dx,
+                                try_other_way_if_failed=try_other_way_if_failed)
+
+    def move_end_x(self, canvas, pos, dx, try_other_way_if_failed=True):
+        return self.move_marker(canvas, self.fit_browser.tool.fit_end_x, pos, dx,
+                                try_other_way_if_failed=try_other_way_if_failed)
+
+    def test_fit_range(self):
+        yield self.start()
+        start_x = self.fit_browser.startX()
+        end_x = self.fit_browser.endX()
+        self.assertGreater(end_x, start_x)
+        self.assertGreater(start_x, 0.3)
+        self.assertGreater(2.0, end_x)
+        pos = self.w._canvas.geometry().center()
+        canvas = self.w.childAt(pos)
+        yield self.move_start_x(canvas, pos, 0.5)
+        self.assertAlmostEqual(self.fit_browser.startX(), start_x + 0.5, 1)
+        yield self.move_end_x(canvas, pos, -0.25)
+        self.assertAlmostEqual(self.fit_browser.endX(), end_x - 0.25, 1)
+
+    def test_fit_range_start_moved_too_far(self):
+        yield self.start()
+        start_x = self.fit_browser.startX()
+        end_x = self.fit_browser.endX()
+        self.assertGreater(end_x, start_x)
+        self.assertGreater(start_x, 0.3)
+        self.assertGreater(2.0, end_x)
+        pos = self.w._canvas.geometry().center()
+        canvas = self.w.childAt(pos)
+        yield self.move_end_x(canvas, pos, -0.5)
+        new_end_x = self.fit_browser.endX()
+        self.assertAlmostEqual(new_end_x, end_x - 0.5, 1)
+        yield self.move_start_x(canvas, pos, 1.0)
+        self.assertAlmostEqual(self.fit_browser.startX(), new_end_x)
+
+    def test_fit_range_end_moved_too_far(self):
+        yield self.start()
+        start_x = self.fit_browser.startX()
+        end_x = self.fit_browser.endX()
+        self.assertGreater(end_x, start_x)
+        self.assertGreater(start_x, 0.3)
+        self.assertGreater(2.0, end_x)
+        pos = self.w._canvas.geometry().center()
+        canvas = self.w.childAt(pos)
+        yield self.move_start_x(canvas, pos, 0.5)
+        new_start_x = self.fit_browser.startX()
+        self.assertAlmostEqual(new_start_x, start_x + 0.5, 1)
+        yield self.move_end_x(canvas, pos, -1.0)
+        self.assertAlmostEqual(self.fit_browser.endX(), new_start_x)
+
+    def test_fit_range_moved_start_outside(self):
+        yield self.start()
+        start_x_pxl = self.fit_browser.tool.fit_start_x.get_x_in_pixels()
+        pos = self.w._canvas.geometry().center()
+        canvas = self.w.childAt(pos)
+        yield self.move_start_x(canvas, pos, -2.0, try_other_way_if_failed=False)
+        self.assertTrue(abs(start_x_pxl - self.fit_browser.tool.fit_start_x.get_x_in_pixels()) < 5)
+
+    def test_fit_range_moved_end_outside(self):
+        yield self.start()
+        end_x_pxl = self.fit_browser.tool.fit_end_x.get_x_in_pixels()
+        pos = self.w._canvas.geometry().center()
+        canvas = self.w.childAt(pos)
+        yield self.move_end_x(canvas, pos, 2.0, try_other_way_if_failed=False)
+        self.assertTrue(abs(end_x_pxl - self.fit_browser.tool.fit_end_x.get_x_in_pixels()) < 5)
+
+    def test_fit_range_set_start(self):
+        yield self.start()
+        self.fit_browser.setStartX(0.7)
+        self.assertAlmostEqual(self.fit_browser.tool.fit_start_x.x, 0.7)
+
+    def test_fit_range_set_start_outside(self):
+        yield self.start()
+        self.fit_browser.setStartX(0.1)
+        self.assertAlmostEqual(self.fit_browser.tool.fit_start_x.x, 0.1)
+
+    def test_fit_range_set_start_outside_right(self):
+        yield self.start()
+        self.fit_browser.setStartX(2.0)
+        self.assertAlmostEqual(self.fit_browser.tool.fit_start_x.x, self.fit_browser.endX())
+
+    def test_fit_range_set_end(self):
+        yield self.start()
+        self.fit_browser.setEndX(1.0)
+        self.assertAlmostEqual(self.fit_browser.tool.fit_end_x.x, 1.0)
+
+    def test_fit_range_set_end_outside(self):
+        yield self.start()
+        self.fit_browser.setEndX(2.0)
+        self.assertAlmostEqual(self.fit_browser.tool.fit_end_x.x, 2.0)
+
+    def test_fit_range_set_end_outside_left(self):
+        yield self.start()
+        self.fit_browser.setEndX(0.3)
+        self.assertAlmostEqual(self.fit_browser.tool.fit_end_x.x, self.fit_browser.startX())
+
+    def test_fit(self):
+        yield self.start()
+        self.fit_browser.loadFunction('name=LinearBackground')
+        self.fit_browser.fit()
+        yield self.wait_for_true(lambda: len(self.fit_browser.fit_result_lines) == 2)
+        # self.assertEqual(self.fit_browser.getFittingFunction(), "name=LinearBackground,A0=4.74354,A1=-0.442138")
+        self.assertEqual(len(self.fit_browser.fit_result_lines), 2)
+        del mtd['ws_Workspace']
+        self.fit_browser.fit()
+        yield self.wait_for_true(lambda: len(self.fit_browser.fit_result_lines) == 2)
+        self.assertEqual(len(self.fit_browser.fit_result_lines), 2)
+
+    def start_emu(self):
+        res1 = Load(r'emu00006473.nxs', OutputWorkspace='ws1')
+        Load(r'emu00006475.nxs', OutputWorkspace='ws2')
+        plot([res1[0]], [3, 5, 7])
+        manager = GlobalFigureManager.get_active()
+        self.w = manager.window
+        trigger_action(find_action_with_text(self.w, 'Fit'))
+        yield 0.1
+        self.fit_browser = manager.fit_browser
+
+    def test_ws_index(self):
+        yield self.start_emu()
+        self.assertEqual(self.fit_browser.getWorkspaceNames(), ['ws1'])
+        self.assertEqual(self.fit_browser.workspaceIndex(), 2)
+        self.fit_browser.setWorkspaceIndex(3)
+        yield self.wait_for_true(lambda: self.fit_browser.workspaceIndex() == 4)
+        self.assertEqual(self.fit_browser.workspaceIndex(), 4)
+        self.fit_browser.setWorkspaceIndex(3)
+        yield self.wait_for_true(lambda: self.fit_browser.workspaceIndex() == 2)
+        self.assertEqual(self.fit_browser.workspaceIndex(), 2)
+        self.fit_browser.setWorkspaceIndex(10)
+        yield self.wait_for_true(lambda: self.fit_browser.workspaceIndex() == 6)
+        self.assertEqual(self.fit_browser.workspaceIndex(), 6)
+        self.fit_browser.setWorkspaceIndex(0)
+        yield self.wait_for_true(lambda: self.fit_browser.workspaceIndex() == 2)
+        self.assertEqual(self.fit_browser.workspaceIndex(), 2)
+
+    def test_hidden_fit_for_images(self):
+        if 'ws1' in mtd:
+            ws1 = mtd['ws1']
+        else:
+            ws1 = Load(r'emu00006473.nxs', OutputWorkspace='ws1')[0]
+        pcolormesh([ws1])
+        manager = GlobalFigureManager.get_active()
+        action = manager.toolbar._actions['toggle_fit']
+        self.assertFalse(action.isVisible())
+        self.assertFalse(action.isEnabled())
+
+
+runTests(TestFitPropertyBrowser)
diff --git a/qt/applications/workbench/workbench/test/workbenchtests.py b/qt/applications/workbench/workbench/test/workbenchtests.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3d2f846b87dd9aedc0e0c1150fd1918f7b5bc2b
--- /dev/null
+++ b/qt/applications/workbench/workbench/test/workbenchtests.py
@@ -0,0 +1,51 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+"""
+Utility functions for running python test scripts
+inside Workbench.
+
+Public methods:
+    runTests(): to run Workbench unit tests
+
+"""
+from __future__ import (absolute_import, division, print_function)
+import sys
+import os
+import unittest
+from qtpy.QtCore import QCoreApplication
+from workbench.plotting.qappthreadcall import QAppThreadCall
+
+
+def runTests(classname):
+    """ Run the test suite in the class.
+    Uses the XML runner if the MANTID_SOURCE environment variable was set.
+    """
+    # Custom code to create and run this single test suite
+    suite = QAppThreadCall(unittest.TestSuite)()
+    QAppThreadCall(suite.addTest)(unittest.makeSuite(classname))
+    # Get the XML runner if the environment variable was set
+    src = os.getenv('MANTID_SOURCE')
+    if src is None:
+        runner = QAppThreadCall(unittest.TextTestRunner)()
+    else:
+        sys.path.append(os.path.join(src, "Testing", "Tools", "unittest-xml-reporting", "src"))
+        import xmlrunner
+        runner = QAppThreadCall(xmlrunner.XMLTestRunner)(output='Testing')
+
+    # Run using either runner
+    res = QAppThreadCall(runner.run)(suite)
+
+    # Process some events that ensure MantidPlot closes properly.
+    QCoreApplication.processEvents()
+    QCoreApplication.processEvents()
+    QCoreApplication.processEvents()
+
+    # Close Mantid and set exit code
+    if not res.wasSuccessful():
+        sys.exit(1)
+    else:
+        sys.exit(0)
diff --git a/qt/python/CMakeLists.txt b/qt/python/CMakeLists.txt
index 68e6e4ef408e8f17119fbff8b417137ddae2a7a0..d69051578fee39b12c72d85e46abe3f57eaa8926 100644
--- a/qt/python/CMakeLists.txt
+++ b/qt/python/CMakeLists.txt
@@ -69,6 +69,17 @@ endif ()
     mantidqt/dialogs/test/test_algorithm_dialog.py
     mantidqt/dialogs/test/test_spectraselectiondialog.py
 
+    mantidqt/plotting/test/test_figuretype.py
+    mantidqt/plotting/test/test_functions.py
+
+    mantidqt/project/test/test_plotssaver.py
+    mantidqt/project/test/test_plotsloader.py
+    mantidqt/project/test/test_project.py
+    mantidqt/project/test/test_projectloader.py
+    mantidqt/project/test/test_projectsaver.py
+    mantidqt/project/test/test_workspaceloader.py
+    mantidqt/project/test/test_workspacesaver.py
+
     mantidqt/utils/test/test_async.py
     mantidqt/utils/test/test_modal_tester.py
     mantidqt/utils/test/test_qt_utils.py
@@ -84,13 +95,25 @@ endif ()
 
     mantidqt/widgets/test/test_jupyterconsole.py
     mantidqt/widgets/test/test_messagedisplay.py
+    mantidqt/widgets/test/test_fitpropertybrowser.py
 
     mantidqt/widgets/samplelogs/test/test_samplelogs_model.py
     mantidqt/widgets/samplelogs/test/test_samplelogs_presenter.py
 
+    mantidqt/widgets/common/test/test_workspacedisplay_ads_observer.py
+    mantidqt/widgets/common/test/test_observing_presenter.py
+    mantidqt/widgets/common/test/test_observing_view.py
+
     mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_model.py
     mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_presenter.py
-    mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_tableviewmodel.py
+    mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_table_view_model.py
+
+    mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_error_column.py
+    mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py
+    mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_workbench_table_widget_item.py
+
+    mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_model.py
+    mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py
   )
 
   # Tests
diff --git a/qt/python/mantidqt/CMakeLists.txt b/qt/python/mantidqt/CMakeLists.txt
index 8b2ac266947ceac944c79338e0460175b5292ff4..830f2506f096871319abdd46babf38d0a78f57dd 100644
--- a/qt/python/mantidqt/CMakeLists.txt
+++ b/qt/python/mantidqt/CMakeLists.txt
@@ -27,6 +27,7 @@ mtd_add_sip_module (
   PYQT_VERSION 4
   INCLUDE_DIRS
     ${PYTHON_INCLUDE_PATH}
+    ${CMAKE_SOURCE_DIR}/Framework/PythonInterface/core/inc
   LINK_LIBS
     ${common_link_libs}
     MantidQtWidgetsCommonQt4
@@ -51,19 +52,22 @@ mtd_add_sip_module (
   PYQT_VERSION 5
   INCLUDE_DIRS
     ${PYTHON_INCLUDE_PATH}
+    ${CMAKE_SOURCE_DIR}/Framework/PythonInterface/core/inc
   LINK_LIBS
     ${common_link_libs}
     MantidQtWidgetsCommonQt5
     Qt5::Core
     Qt5::Gui
-    Qt5::Widgets
+Qt5::Widgets
     Qt5::Qscintilla
     ${Boost_LIBRARIES}
     API
   INSTALL_DIR
-    ${LIB_DIR}/mantidqt
+    ${WORKBENCH_LIB_DIR}/mantidqt
   LINUX_INSTALL_RPATH
     "\$ORIGIN/.."
+  OSX_INSTALL_RPATH
+    "@loader_path/.." 
   FOLDER Qt5
 )
 
diff --git a/qt/python/mantidqt/_common.sip b/qt/python/mantidqt/_common.sip
index 531d92aabb371f323406e5870a3fabd9853c60d9..4bf55659c6d1a01e35ea3174ebe99a3aeb106324 100644
--- a/qt/python/mantidqt/_common.sip
+++ b/qt/python/mantidqt/_common.sip
@@ -1,10 +1,14 @@
 %ModuleCode
 #include "MantidQtWidgets/Common/Message.h"
+#include "MantidQtWidgets/Common/WorkspaceObserver.h"
+#include "MantidPythonInterface/core/VersionCompat.h"
 // Allows suppression of namespaces within the module
 using namespace MantidQt::MantidWidgets;
 %End
 
 %InitialisationCode
+qRegisterMetaType<std::string>("StdString");
+qRegisterMetaType<Mantid::API::Workspace_sptr>("Workspace");
 qRegisterMetaType<Message>("Message");
 %End
 
@@ -256,3 +260,36 @@ class ManageUserDirectories : QDialog {
 public:
   ManageUserDirectories(QWidget *parent /TransferThis/ = nullptr);
 };
+
+// ---------------------------------
+// Fit Property Browser
+// ---------------------------------
+class FitPropertyBrowser : public QDockWidget {
+%TypeHeaderCode
+#include "MantidQtWidgets/Common/FitPropertyBrowser.h"
+%End
+public:
+    FitPropertyBrowser(QWidget *parent = nullptr, QObject *mantidui = nullptr);
+    void init();
+    SIP_PYOBJECT getFittingFunction() const;
+%MethodCode
+    auto f = sipCpp->getFittingFunction();
+    sipRes = FROM_CSTRING(f->asString().c_str());
+%End
+    int sizeOfFunctionsGroup() const;
+    void loadFunction(QString funStr);
+    double startX() const;
+    void setStartX(double start);
+    double endX() const;
+    void setEndX(double end);
+    void setXRange(double start, double end);
+    int workspaceIndex() const;
+    void setWorkspaceIndex(int i);
+    QStringList getWorkspaceNames();
+    void fit();
+    void addAllowedSpectra(const QString &wsName, const QList<int> &wsIndices);
+signals:
+    void startXChanged(double);
+    void endXChanged(double);
+    void fittingDone(const QString &);
+};
diff --git a/qt/python/mantidqt/io.py b/qt/python/mantidqt/io.py
index c4736787b2419f0b4f65bf94975dea17250d6515..26ca99d3d6ef867ae77d13e97b45a2f4b7a98f2b 100644
--- a/qt/python/mantidqt/io.py
+++ b/qt/python/mantidqt/io.py
@@ -8,9 +8,10 @@
 #
 
 import os.path
-from qtpy.QtWidgets import QFileDialog
+from qtpy.QtWidgets import QFileDialog, QDialog
 from qtpy.QtCore import QDir
 
+# For storing a persistent directory of where the last file was saved to.
 _LAST_SAVE_DIRECTORY = None
 
 
@@ -24,7 +25,7 @@ def open_a_file_dialog(parent=None,  default_suffix=None, directory=None, file_f
     :param file_filter: String; The filter name and file type e.g. "Python files (*.py)"
     :param accept_mode: enum AcceptMode; Defines the AcceptMode of the dialog, check QFileDialog Class for details
     :param file_mode: enum FileMode; Defines the FileMode of the dialog, check QFileDialog Class for details
-    :return: String; The filename that was selected, it is possible to return a directory so look out for that.
+    :return: String; The filename that was selected, it is possible to return a directory so look out for that
     """
     global _LAST_SAVE_DIRECTORY
     dialog = QFileDialog(parent)
@@ -55,10 +56,12 @@ def open_a_file_dialog(parent=None,  default_suffix=None, directory=None, file_f
     dialog.fileSelected.connect(_set_last_save)
 
     # Wait for dialog to finish before allowing continuation of code
-    dialog.exec_()
+    if dialog.exec_() == QDialog.Rejected:
+        return None
 
     filename = _LAST_SAVE_DIRECTORY
-    # Make sure that the _LAST_SAVE_DIRECTORY is set
+
+    # Make sure that the _LAST_SAVE_DIRECTORY is set as a directory
     if _LAST_SAVE_DIRECTORY is not None and not os.path.isdir(_LAST_SAVE_DIRECTORY):
         # Remove the file for last directory
         _LAST_SAVE_DIRECTORY = os.path.dirname(os.path.abspath(_LAST_SAVE_DIRECTORY))
@@ -68,7 +71,7 @@ def open_a_file_dialog(parent=None,  default_suffix=None, directory=None, file_f
 
 def _set_last_save(filename):
     """
-    Uses the global _LOCAL_SAVE_DIRECTORY to store output from connected signal
+    Uses the global _LAST_SAVE_DIRECTORY to store output from connected signal
     :param filename: String; Value to set _LAST_SAVE_DIRECTORY
     """
     global _LAST_SAVE_DIRECTORY
diff --git a/qt/python/mantidqt/plotting/__init__.py b/qt/python/mantidqt/plotting/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa481f76cef7127fd81c5757e6dec34d652833dc
--- /dev/null
+++ b/qt/python/mantidqt/plotting/__init__.py
@@ -0,0 +1,8 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
diff --git a/qt/applications/workbench/workbench/plotting/figuretype.py b/qt/python/mantidqt/plotting/figuretype.py
similarity index 100%
rename from qt/applications/workbench/workbench/plotting/figuretype.py
rename to qt/python/mantidqt/plotting/figuretype.py
diff --git a/qt/applications/workbench/workbench/plotting/functions.py b/qt/python/mantidqt/plotting/functions.py
similarity index 97%
rename from qt/applications/workbench/workbench/plotting/functions.py
rename to qt/python/mantidqt/plotting/functions.py
index f62784dba10b936a7933c82909a59a2d9762c5fe..59c2b1dcb41bc9997056aec4e1d2b0b1334b6f8a 100644
--- a/qt/applications/workbench/workbench/plotting/functions.py
+++ b/qt/python/mantidqt/plotting/functions.py
@@ -14,23 +14,21 @@ our custom window.
 # std imports
 import collections
 import math
+import numpy as np
 
 # 3rd party imports
-from mantid.api import AnalysisDataService, MatrixWorkspace
-from mantid.kernel import Logger
-import matplotlib.pyplot as plt
-
 try:
     from matplotlib.cm import viridis as DEFAULT_CMAP
 except ImportError:
     from matplotlib.cm import jet as DEFAULT_CMAP
 from mantid.py3compat import is_text_string
-from mantidqt.dialogs.spectraselectordialog import get_spectra_selection
 from matplotlib.gridspec import GridSpec
-import numpy as np
 
 # local imports
-from .figuretype import figure_type, FigureType
+from mantid.api import AnalysisDataService, MatrixWorkspace
+from mantid.kernel import Logger
+from mantidqt.plotting.figuretype import figure_type, FigureType
+from mantidqt.dialogs.spectraselectordialog import get_spectra_selection
 
 # -----------------------------------------------------------------------------
 # Constants
@@ -72,6 +70,7 @@ def current_figure_or_none():
 
     :return: An active figure or None
     """
+    import matplotlib.pyplot as plt
     if len(plt.get_fignums()) > 0:
         return plt.gcf()
     else:
@@ -144,6 +143,7 @@ def plot(workspaces, spectrum_nums=None, wksp_indices=None, errors=False,
     :param plot_kwargs: Arguments that will be passed onto the plot function
     :return: The figure containing the plots
     """
+    import matplotlib.pyplot as plt
     if plot_kwargs is None:
         plot_kwargs = {}
     _validate_plot_inputs(workspaces, spectrum_nums, wksp_indices)
@@ -202,12 +202,12 @@ def use_imshow(ws):
 
     x = ws.dataX(0)
     difference = np.diff(x)
-    if not np.all(np.isclose(difference, difference[0])):
+    if not np.all(np.isclose(difference[:-1], difference[0])):
         return False
 
     y = ws.getAxis(1).extractValues()
     difference = np.diff(y)
-    if not np.all(np.isclose(difference, difference[0])):
+    if not np.all(np.isclose(difference[:-1], difference[0])):
         return False
 
     return True
@@ -236,7 +236,7 @@ def pcolormesh(workspaces, fig=None):
             ws = workspaces[subplot_idx]
             ax.set_title(ws.name())
             if use_imshow(ws):
-                pcm = ax.imshow(ws, cmap=DEFAULT_CMAP, aspect='auto')
+                pcm = ax.imshow(ws, cmap=DEFAULT_CMAP, aspect='auto', origin='lower')
             else:
                 pcm = ax.pcolormesh(ws, cmap=DEFAULT_CMAP)
             for lbl in ax.get_xticklabels():
@@ -343,6 +343,7 @@ def _create_subplots(nplots, fig=None):
     :param fig: An optional figure. It is cleared before plotting the new contents
     :return: A 2-tuple of (fig, axes)
     """
+    import matplotlib.pyplot as plt
     square_side_len = int(math.ceil(math.sqrt(nplots)))
     nrows, ncols = square_side_len, square_side_len
     if square_side_len * square_side_len != nplots:
diff --git a/qt/python/mantidqt/plotting/test/__init__.py b/qt/python/mantidqt/plotting/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa481f76cef7127fd81c5757e6dec34d652833dc
--- /dev/null
+++ b/qt/python/mantidqt/plotting/test/__init__.py
@@ -0,0 +1,8 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
diff --git a/qt/applications/workbench/workbench/plotting/test/test_figuretype.py b/qt/python/mantidqt/plotting/test/test_figuretype.py
similarity index 93%
rename from qt/applications/workbench/workbench/plotting/test/test_figuretype.py
rename to qt/python/mantidqt/plotting/test/test_figuretype.py
index 7b190507b91c8e89649450f24468323bcb98073a..07735e3f835bad20154a76152df55ac9e9a528c5 100644
--- a/qt/applications/workbench/workbench/plotting/test/test_figuretype.py
+++ b/qt/python/mantidqt/plotting/test/test_figuretype.py
@@ -18,7 +18,7 @@ matplotlib.use('AGG')  # noqa
 import matplotlib.pyplot as plt
 
 # local imports
-from workbench.plotting.figuretype import figure_type, FigureType
+from mantidqt.plotting.figuretype import figure_type, FigureType
 
 
 class FigureTypeTest(TestCase):
@@ -42,7 +42,7 @@ class FigureTypeTest(TestCase):
 
     def test_image_plot_returns_image(self):
         ax = plt.subplot(111)
-        ax.imshow([[1],[1]])
+        ax.imshow([[1], [1]])
         self.assertEqual(FigureType.Image, figure_type(ax.figure))
 
 
diff --git a/qt/applications/workbench/workbench/plotting/test/test_functions.py b/qt/python/mantidqt/plotting/test/test_functions.py
similarity index 92%
rename from qt/applications/workbench/workbench/plotting/test/test_functions.py
rename to qt/python/mantidqt/plotting/test/test_functions.py
index 7a48f18b1940db48562da75d6ae57f8788b0107a..2411879d3cac581f20b0307bc0ccbbe66d007750 100644
--- a/qt/applications/workbench/workbench/plotting/test/test_functions.py
+++ b/qt/python/mantidqt/plotting/test/test_functions.py
@@ -27,13 +27,12 @@ from mantidqt.dialogs.spectraselectordialog import SpectraSelection
 import numpy as np
 
 # local imports
-from workbench.plotting.functions import (can_overplot, current_figure_or_none, figure_title,
-                                          plot, plot_from_names, pcolormesh_from_names)
+from mantidqt.plotting.functions import (can_overplot, current_figure_or_none, figure_title,
+                                         plot, plot_from_names, pcolormesh_from_names)
 
 
 # Avoid importing the whole of mantid for a single mock of the workspace class
 class FakeWorkspace(object):
-
     def __init__(self, name):
         self._name = name
 
@@ -86,8 +85,8 @@ class FunctionsTest(TestCase):
         with self.assertRaises(AssertionError):
             figure_title([], 5)
 
-    @mock.patch('workbench.plotting.functions.get_spectra_selection')
-    @mock.patch('workbench.plotting.functions.plot')
+    @mock.patch('mantidqt.plotting.functions.get_spectra_selection')
+    @mock.patch('mantidqt.plotting.functions.plot')
     def test_plot_from_names_calls_plot(self, get_spectra_selection_mock, plot_mock):
         ws_name = 'test_plot_from_names_calls_plot-1'
         AnalysisDataService.Instance().addOrReplace(ws_name, self._test_ws)
@@ -98,12 +97,12 @@ class FunctionsTest(TestCase):
 
         self.assertEqual(1, plot_mock.call_count)
 
-    @mock.patch('workbench.plotting.functions.get_spectra_selection')
+    @mock.patch('mantidqt.plotting.functions.get_spectra_selection')
     def test_plot_from_names_produces_single_line_plot_for_valid_name(self, get_spectra_selection_mock):
         self._do_plot_from_names_test(get_spectra_selection_mock, expected_labels=["spec 1"], wksp_indices=[0],
                                       errors=False, overplot=False)
 
-    @mock.patch('workbench.plotting.functions.get_spectra_selection')
+    @mock.patch('mantidqt.plotting.functions.get_spectra_selection')
     def test_plot_from_names_produces_single_error_plot_for_valid_name(self, get_spectra_selection_mock):
         fig = self._do_plot_from_names_test(get_spectra_selection_mock,
                                             # matplotlib does not set labels on the lines for error plots
@@ -111,14 +110,14 @@ class FunctionsTest(TestCase):
                                             wksp_indices=[0], errors=True, overplot=False)
         self.assertEqual(1, len(fig.gca().containers))
 
-    @mock.patch('workbench.plotting.functions.get_spectra_selection')
+    @mock.patch('mantidqt.plotting.functions.get_spectra_selection')
     def test_plot_from_names_produces_overplot_for_valid_name(self, get_spectra_selection_mock):
         # make first plot
         plot([self._test_ws], wksp_indices=[0])
         self._do_plot_from_names_test(get_spectra_selection_mock, expected_labels=["spec 1", "spec 2"],
                                       wksp_indices=[1], errors=False, overplot=True)
 
-    @mock.patch('workbench.plotting.functions.get_spectra_selection')
+    @mock.patch('mantidqt.plotting.functions.get_spectra_selection')
     def test_plot_from_names_within_existing_figure(self, get_spectra_selection_mock):
         # make existing plot
         fig = plot([self._test_ws], wksp_indices=[0])
@@ -126,7 +125,7 @@ class FunctionsTest(TestCase):
                                       wksp_indices=[1], errors=False, overplot=False,
                                       target_fig=fig)
 
-    @mock.patch('workbench.plotting.functions.pcolormesh')
+    @mock.patch('mantidqt.plotting.functions.pcolormesh')
     def test_pcolormesh_from_names_calls_pcolormesh(self, pcolormesh_mock):
         ws_name = 'test_pcolormesh_from_names_calls_pcolormesh-1'
         AnalysisDataService.Instance().addOrReplace(ws_name, self._test_ws)
diff --git a/qt/python/mantidqt/project/__init__.py b/qt/python/mantidqt/project/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..044ed3c205730d7114d5b66b2a4a733d211106c0
--- /dev/null
+++ b/qt/python/mantidqt/project/__init__.py
@@ -0,0 +1,8 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
diff --git a/qt/python/mantidqt/project/plotsloader.py b/qt/python/mantidqt/project/plotsloader.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b11afbff92e475940f9f2070a3aa99d581aa5e2
--- /dev/null
+++ b/qt/python/mantidqt/project/plotsloader.py
@@ -0,0 +1,324 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import copy
+
+from matplotlib import ticker, text, axis  # noqa
+import matplotlib.colors
+import matplotlib.axes
+import matplotlib.cm as cm
+
+from mantid import logger
+from mantid.api import AnalysisDataService as ADS
+from mantid import plots  # noqa
+from mantidqt.plotting.functions import pcolormesh
+
+# Constants set in workbench.plotting.functions but would cause backwards reliability
+SUBPLOT_WSPACE = 0.5
+SUBPLOT_HSPACE = 0.5
+
+
+class PlotsLoader(object):
+    def __init__(self):
+        self.color_bar_remade = False
+
+    def load_plots(self, plots_list):
+        if plots_list is None:
+            return
+
+        for plot_ in plots_list:
+            try:
+                self.make_fig(plot_)
+            except BaseException as e:
+                # Catch all errors in here so it can fail silently-ish
+                if isinstance(e, KeyboardInterrupt):
+                    raise KeyboardInterrupt(str(e))
+                logger.warning("A plot was unable to be loaded from the save file. Error: " + str(e))
+
+    def make_fig(self, plot_dict, create_plot=True):
+        """
+        This method currently only considers single matplotlib.axes.Axes based figures as that is the most common case
+        :param plot_dict: dictionary; A dictionary of various items intended to recreate a figure
+        :param create_plot: Bool; whether or not to make the plot, or to return the figure.
+        :return: matplotlib.figure; Only returns if create_plot=False
+        """
+        import matplotlib.pyplot as plt
+        # Grab creation arguments
+        creation_args = plot_dict["creationArguments"]
+
+        # Make a copy so it can be applied to the axes, of the plot once created.
+        creation_args_copy = copy.deepcopy(creation_args[0])
+
+        # Populate workspace names
+        workspace_name = creation_args[0][0].pop('workspaces')
+        workspace = ADS.retrieve(workspace_name)
+
+        # Make initial plot
+        fig, ax = plt.subplots(subplot_kw={'projection': 'mantid'})
+
+        # Make sure that the axes gets it's creation_args as loading doesn't add them
+        ax.creation_args = creation_args_copy
+
+        self.plot_func(workspace, ax, fig, creation_args[0][0])
+
+        # If an overplot is necessary plot onto the same figure
+        self.plot_extra_lines(creation_args=creation_args, ax=ax)
+
+        # Update the fig
+        fig._label = plot_dict["label"]
+        fig.canvas.set_window_title(plot_dict["label"])
+        self.restore_figure_data(fig=fig, dic=plot_dict)
+
+        # If the function should create plot then create else return
+        if create_plot:
+            fig.show()
+        else:
+            return fig
+
+    def plot_func(self, workspace, axes, fig,creation_arg):
+        """
+        Plot's the graph from the given workspace, axes and creation_args. then returns the function used to create it.
+        :param workspace: mantid.Workspace; Workspace to create the graph from
+        :param axes: matplotlib.Axes; Axes to create the graph
+        :param fig: matplotlib.Figure; Figure to add the colormesh to
+        :param creation_arg: The creation arguments that have been used to create the details of the
+        :return: String; The function used to create the plot
+        """
+        # Remove the function kwarg and if it's not found set to "plot
+        function_to_call = creation_arg.pop("function")
+
+        # Handle recreating the cmap objects
+        if "cmap" in creation_arg:
+            creation_arg["cmap"] = getattr(matplotlib.cm, creation_arg["cmap"])
+
+        function_dict = {"plot": axes.plot, "scatter": axes.scatter, "errorbar": axes.errorbar,
+                         "pcolor": axes.pcolor, "pcolorfast": axes.pcolorfast,"pcolormesh": pcolormesh,
+                         "imshow": pcolormesh, "contour": axes.contour, "contourf": axes.contourf,
+                         "tripcolor": axes.tripcolor, "tricontour": axes.tricontour, "tricontourf": axes.tricontourf}
+
+        func = function_dict[function_to_call]
+        # Plotting is done via an Axes object unless a colorbar needs to be added
+        if function_to_call in ["imshow", "pcolormesh"]:
+            func([workspace], fig)
+            self.color_bar_remade = True
+        else:
+            func(workspace, **creation_arg)
+
+    def plot_extra_lines(self, creation_args, ax):
+        """
+        This method currently only considers single matplotlib.axes.Axes based figures as that is the most common case,
+        to make it more than that the lines creation_args[0] needs to be rewrote to handle multiple axes args.
+        :param creation_args:
+        :param ax:
+        :return:
+        """
+        # If an overplot is necessary plot onto the same figure
+        if len(creation_args[0]) > 1:
+            for ii in range(1, len(creation_args[0])):
+                workspace_name = creation_args[0][ii].pop('workspaces')
+                workspace = ADS.retrieve(workspace_name)
+                self.plot_func(workspace, ax, ax.figure, creation_args[0][ii])
+
+    def restore_figure_data(self, fig, dic):
+        self.restore_fig_properties(fig, dic["properties"])
+        axes_list = dic["axes"]
+        for index, ax in enumerate(fig.axes):
+            try:
+                self.restore_fig_axes(ax, axes_list[index])
+            except IndexError as e:
+                if not self.color_bar_remade:
+                    raise IndexError(e)
+
+    @staticmethod
+    def restore_fig_properties(fig, dic):
+        fig.set_figheight(dic["figHeight"])
+        fig.set_figwidth(dic["figWidth"])
+        fig.set_dpi(dic["dpi"])
+
+    def restore_fig_axes(self, ax, dic):
+        # Restore axis properties
+        properties = dic["properties"]
+        self.update_properties(ax, properties)
+
+        # Set the titles
+        if dic["xAxisTitle"] is not None:
+            ax.set_xlabel(dic["xAxisTitle"])
+        if dic["yAxisTitle"] is not None:
+            ax.set_ylabel(dic["yAxisTitle"])
+        if dic["title"] is not None:
+            ax.set_title(dic["title"])
+
+        # Update the lines
+        line_list = dic["lines"]
+        for line in line_list:
+            self.update_lines(ax, line)
+
+        # Update/set text
+        text_list = dic["texts"]
+        for text_ in text_list:
+            self.create_text_from_dict(ax, text_)
+
+        # Update artists that are text
+        for artist in dic["textFromArtists"]:
+            self.create_text_from_dict(ax, artist)
+
+        # Update Legend
+        legend = ax.get_legend()
+        if legend is not None:
+            self.update_legend(ax, dic["legend"])
+        else:
+            ax.legend()
+            self.update_legend(ax, dic["legend"])
+
+        # Update colorbar if present
+        if self.color_bar_remade and dic["colorbar"]["exists"]:
+            if len(ax.images) > 0:
+                image = ax.images[0]
+            elif len(ax.collections) > 0:
+                image = ax.images[0]
+            else:
+                raise RuntimeError("self.color_bar_remade set to True whilst no colorbar found")
+
+            self.update_colorbar_from_dict(image, dic["colorbar"])
+
+    @staticmethod
+    def create_text_from_dict(ax, dic):
+        style_dic = dic["style"]
+        ax.text(x=dic["position"][0],
+                y=dic["position"][1],
+                s=dic["text"],
+                fontdict={u'alpha': style_dic["alpha"],
+                          u'color': style_dic["color"],
+                          u'rotation': style_dic["rotation"],
+                          u'fontsize': style_dic["textSize"],
+                          u'zorder': style_dic["zOrder"],
+                          u'usetex': dic["useTeX"],
+                          u'horizontalalignment': style_dic["hAlign"],
+                          u'verticalalignment': style_dic["vAlign"]})
+
+    @staticmethod
+    def update_lines(ax, line):
+        # Find the line based on label
+        current_line = ax.lines[line["lineIndex"]]
+
+        current_line.set_label(line["label"])
+        current_line.set_alpha(line["alpha"])
+        current_line.set_color(line["color"])
+        current_line.set_linestyle(line["lineStyle"])
+        current_line.set_linewidth(line["lineWidth"])
+
+        marker_style = line["markerStyle"]
+        current_line.set_markerfacecolor(marker_style["faceColor"])
+        current_line.set_markeredgecolor(marker_style["edgeColor"])
+        current_line.set_markeredgewidth(marker_style["edgeWidth"])
+        current_line.set_marker(marker_style["markerType"])
+        current_line.set_markersize(marker_style["markerSize"])
+        current_line.set_zorder(marker_style["zOrder"])
+
+        errorbar_style = line["errorbars"]
+        if errorbar_style["exists"]:
+            current_line.set_dash_capstyle(errorbar_style["dashCapStyle"])
+            current_line.set_dash_joinstyle(errorbar_style["dashJoinStyle"])
+            current_line.set_solid_capstyle(errorbar_style["solidCapStyle"])
+            current_line.set_solid_joinstyle(errorbar_style["solidJoinStyle"])
+
+    @staticmethod
+    def update_legend(ax, legend):
+        if not legend["exists"]:
+            ax.get_legend().remove()
+            return
+        ax.legend().set_visible(legend["visible"])
+
+    def update_properties(self, ax, properties):
+        ax.set_position(properties["bounds"])
+        ax.set_navigate(properties["dynamic"])
+        ax.axison = properties["axisOn"]
+        ax.set_frame_on(properties["frameOn"])
+        ax.set_visible(properties["visible"])
+
+        # Update X Axis
+        self.update_axis(ax.xaxis, properties["xAxisProperties"])
+
+        # Update Y Axis
+        self.update_axis(ax.yaxis, properties["yAxisProperties"])
+
+        ax.set_xscale(properties["xAxisScale"])
+        ax.set_yscale(properties["yAxisScale"])
+        ax.set_xlim(properties["xLim"])
+        ax.set_ylim(properties["yLim"])
+
+    def update_axis(self, axis_, properties):
+        if isinstance(axis_, matplotlib.axis.XAxis):
+            if properties["position"] is "top":
+                axis_.tick_top()
+            else:
+                axis_.tick_bottom()
+
+        if isinstance(axis_, matplotlib.axis.YAxis):
+            if properties["position"] is "right":
+                axis_.tick_right()
+            else:
+                axis_.tick_left()
+
+        labels = axis_.get_ticklabels()
+        if properties["fontSize"] is not "":
+            for label in labels:
+                label.set_fontsize(properties["fontSize"])
+
+        axis_.set_visible(properties["visible"])
+
+        # Set axis tick data
+        self.update_axis_ticks(axis_, properties)
+
+        # Set grid data
+        self.update_grid_style(axis_, properties)
+
+    @staticmethod
+    def update_grid_style(axis_, properties):
+        grid_dict = properties["gridStyle"]
+        grid_lines = axis_.get_gridlines()
+        if grid_dict["gridOn"]:
+            axis_._gridOnMajor = True
+            for grid_line in grid_lines:
+                grid_line.set_alpha(grid_dict["alpha"])
+                grid_line.set_color(grid_dict["color"])
+
+    @staticmethod
+    def update_axis_ticks(axis_, properties):
+        # Update Major and Minor Locator
+        if properties["majorTickLocator"] is "FixedLocator":
+            axis_.set_major_locator(ticker.FixedLocator(properties["majorTickLocatorValues"]))
+
+        if properties["minorTickLocator"] is "FixedLocator":
+            axis_.set_minor_locator(ticker.FixedLocator(properties["minorTickLocatorValues"]))
+
+        # Update Major and Minor Formatter
+        if properties["majorTickFormatter"] is "FixedFormatter":
+            axis_.set_major_formatter(ticker.FixedFormatter(properties["majorTickFormat"]))
+
+        if properties["minorTickFormatter"] is "FixedFormatter":
+            axis_.set_major_formatter(ticker.FixedLocator(properties["minorTickFormat"]))
+
+    @staticmethod
+    def update_colorbar_from_dict(image, dic):
+        # colorbar = image.colorbar
+        image.set_clim(*sorted([dic["min"], dic["max"]]))
+        image.set_label(dic["label"])
+        image.set_cmap(cm.get_cmap(dic["cmap"]))
+        image.set_interpolation(dic["interpolation"])
+        #Try and make the cmap line up but sometimes it wont
+        try:
+            image.axes.set_cmap(cm.get_cmap(dic["cmap"]))
+        except AttributeError as e:
+            logger.debug("PlotsLoader - The Image accessed did not have an axes with the ability to set the cmap: "
+                         + str(e))
+
+        # Redraw
+        image.axes.figure.canvas.draw()
diff --git a/qt/python/mantidqt/project/plotssaver.py b/qt/python/mantidqt/project/plotssaver.py
new file mode 100644
index 0000000000000000000000000000000000000000..632945087bf0dea9664b653b7ee7b75b2163a2a2
--- /dev/null
+++ b/qt/python/mantidqt/project/plotssaver.py
@@ -0,0 +1,259 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+from matplotlib import ticker
+import matplotlib.axis
+from matplotlib.image import AxesImage
+
+from mantid import logger
+
+try:
+    from matplotlib.colors import to_hex
+except ImportError:
+    from matplotlib.colors import colorConverter, rgb2hex
+
+    def to_hex(color):
+        return rgb2hex(colorConverter.to_rgb(color))
+
+
+class PlotsSaver(object):
+    def __init__(self):
+        self.figure_creation_args = {}
+
+    def save_plots(self, plot_dict):
+        # if arguement is none return empty dictionary
+        if plot_dict is None:
+            return []
+
+        plot_list = []
+        for index in plot_dict:
+            try:
+                plot_list.append(self.get_dict_from_fig(plot_dict[index].canvas.figure))
+            except BaseException as e:
+                # Catch all errors in here so it can fail silently-ish, if this is happening on all plots make sure you
+                # have built your project.
+                if isinstance(e, KeyboardInterrupt):
+                    raise KeyboardInterrupt
+                logger.warning("A plot was unable to be saved")
+        return plot_list
+
+    def get_dict_from_fig(self, fig):
+        axes_list = []
+        create_list = []
+        for ax in fig.axes:
+            try:
+                create_list.append(ax.creation_args)
+                self.figure_creation_args = ax.creation_args
+            except AttributeError:
+                logger.debug("Axis had a axis without creation_args - Common with colorfill")
+                continue
+            axes_list.append(self.get_dict_for_axes(ax))
+
+        fig_dict = {"creationArguments": create_list,
+                    "axes": axes_list,
+                    "label": fig._label,
+                    "properties": self.get_dict_from_fig_properties(fig)}
+        return fig_dict
+
+    @staticmethod
+    def get_dict_for_axes_colorbar(ax):
+        image = None
+        cb_dict = {}
+
+        # If an image is present (from imshow)
+        if len(ax.images) > 0 and isinstance(ax.images[0], AxesImage):
+            image = ax.images[0]
+        # If an image is present from pcolor/pcolormesh
+        elif len(ax.collections) > 0 and isinstance(ax.collections[0], AxesImage):
+            image = ax.collections[0]
+        else:
+            cb_dict["exists"] = False
+            return cb_dict
+
+        cb_dict["exists"] = True
+        cb_dict["max"] = image.norm.vmax
+        cb_dict["min"] = image.norm.vmin
+        cb_dict["interpolation"] = image._interpolation
+        cb_dict["cmap"] = image.cmap.name
+        cb_dict["label"] = image._label
+
+        return cb_dict
+
+    def get_dict_for_axes(self, ax):
+        ax_dict = {"properties": self.get_dict_from_axes_properties(ax),
+                   "title": ax.get_title(),
+                   "xAxisTitle": ax.get_xlabel(),
+                   "yAxisTitle": ax.get_ylabel(),
+                   "colorbar": self.get_dict_for_axes_colorbar(ax)}
+
+        # Get lines from the axes and store it's data
+        lines_list = []
+        for index, line in enumerate(ax.lines):
+            lines_list.append(self.get_dict_from_line(line, index))
+        ax_dict["lines"] = lines_list
+
+        texts_list = []
+        for text in ax.texts:
+            texts_list.append(self.get_dict_from_text(text))
+        ax_dict["texts"] = texts_list
+
+        # Potentially need to handle artists that are Text
+        artist_text_dict = {}
+        for artist in ax.artists:
+            if isinstance(artist, matplotlib.text.Text):
+                artist_text_dict = self.get_dict_from_text(artist)
+        ax_dict["textFromArtists"] = artist_text_dict
+
+        legend_dict = {}
+        legend = ax.get_legend()
+        if legend is not None:
+            legend_dict["visible"] = legend.get_visible()
+            legend_dict["exists"] = True
+        else:
+            legend_dict["exists"] = False
+        ax_dict["legend"] = legend_dict
+
+        return ax_dict
+
+    def get_dict_from_axes_properties(self, ax):
+        return {"bounds": ax.get_position().bounds,
+                "dynamic": ax.get_navigate(),
+                "axisOn": ax.axison,
+                "frameOn": ax.get_frame_on(),
+                "visible": ax.get_visible(),
+                "xAxisProperties": self.get_dict_from_axis_properties(ax.xaxis),
+                "yAxisProperties": self.get_dict_from_axis_properties(ax.yaxis),
+                "xAxisScale": ax.xaxis.get_scale(),
+                "xLim": ax.get_xlim(),
+                "yAxisScale": ax.yaxis.get_scale(),
+                "yLim": ax.get_ylim()}
+
+    def get_dict_from_axis_properties(self, ax):
+        prop_dict = {"majorTickLocator": type(ax.get_major_locator()).__name__,
+                     "minorTickLocator": type(ax.get_minor_locator()).__name__,
+                     "majorTickFormatter": type(ax.get_major_formatter()).__name__,
+                     "minorTickFormatter": type(ax.get_minor_formatter()).__name__,
+                     "gridStyle": self.get_dict_for_grid_style(ax),
+                     "visible": ax.get_visible()}
+        label1On = ax._major_tick_kw.get('label1On', True)
+
+        if isinstance(ax, matplotlib.axis.XAxis):
+            if label1On:
+                prop_dict["position"] = "Bottom"
+            else:
+                prop_dict["position"] = "Top"
+        elif isinstance(ax, matplotlib.axis.YAxis):
+            if label1On:
+                prop_dict["position"] = "Left"
+            else:
+                prop_dict["position"] = "Right"
+        else:
+            raise ValueError("Value passed is not a valid axis")
+
+        if isinstance(ax.get_major_locator(), ticker.FixedLocator):
+            prop_dict["majorTickLocatorValues"] = list(ax.get_major_locator())
+        else:
+            prop_dict["majorTickLocatorValues"] = None
+
+        if isinstance(ax.get_minor_locator(), ticker.FixedLocator):
+            prop_dict["minorTickLocatorValues"] = list(ax.get_minor_locator())
+        else:
+            prop_dict["minorTickLocatorValues"] = None
+
+        formatter = ax.get_major_formatter()
+        if isinstance(formatter, ticker.FixedFormatter):
+            prop_dict["majorTickFormat"] = list(formatter.seq)
+        else:
+            prop_dict["majorTickFormat"] = None
+
+        formatter = ax.get_minor_formatter()
+        if isinstance(formatter, ticker.FixedFormatter):
+            prop_dict["minorTickFormat"] = list(formatter.seq)
+        else:
+            prop_dict["minorTickFormat"] = None
+
+        labels = ax.get_ticklabels()
+        if labels:
+            prop_dict["fontSize"] = labels[0].get_fontsize()
+        else:
+            prop_dict["fontSize"] = ""
+
+        return prop_dict
+
+    @staticmethod
+    def get_dict_for_grid_style(ax):
+        grid_style = {}
+        gridlines = ax.get_gridlines()
+        if ax._gridOnMajor and len(gridlines) > 0:
+            grid_style["color"] = to_hex(gridlines[0].get_color())
+            grid_style["alpha"] = gridlines[0].get_alpha()
+            grid_style["gridOn"] = True
+        else:
+            grid_style["gridOn"] = False
+        return grid_style
+
+    def get_dict_from_line(self, line, index=0):
+        line_dict = {"lineIndex": index,
+                     "label": line.get_label(),
+                     "alpha": line.get_alpha(),
+                     "color": to_hex(line.get_color()),
+                     "lineWidth": line.get_linewidth(),
+                     "lineStyle": line.get_linestyle(),
+                     "markerStyle": self.get_dict_from_marker_style(line),
+                     "errorbars": self.get_dict_for_errorbars(line)}
+        if line_dict["alpha"] is None:
+            line_dict["alpha"] = 1
+        return line_dict
+
+    def get_dict_for_errorbars(self, line):
+        if self.figure_creation_args[0]["function"] == "errorbar":
+            return {"exists": True,
+                    "dashCapStyle": line.get_dash_capstyle(),
+                    "dashJoinStyle": line.get_dash_joinstyle(),
+                    "solidCapStyle": line.get_solid_capstyle(),
+                    "solidJoinStyle": line.get_solid_joinstyle()}
+        else:
+            return {"exists": False}
+
+    @staticmethod
+    def get_dict_from_marker_style(line):
+        style_dict = {"faceColor": to_hex(line.get_markerfacecolor()),
+                      "edgeColor": to_hex(line.get_markeredgecolor()),
+                      "edgeWidth": line.get_markeredgewidth(),
+                      "markerType": line.get_marker(),
+                      "markerSize": line.get_markersize(),
+                      "zOrder": line.get_zorder()}
+        return style_dict
+
+    def get_dict_from_text(self, text):
+        text_dict = {"text": text.get_text()}
+        if text_dict["text"]:
+            # text_dict["transform"] = text.get_transform()
+            text_dict["position"] = text.get_position()
+            text_dict["useTeX"] = text.get_usetex()
+            text_dict["style"] = self.get_dict_from_text_style(text)
+        return text_dict
+
+    @staticmethod
+    def get_dict_from_text_style(text):
+        style_dict = {"alpha": text.get_alpha(),
+                      "textSize": text.get_size(),
+                      "color": to_hex(text.get_color()),
+                      "hAlign": text.get_horizontalalignment(),
+                      "vAlign": text.get_verticalalignment(),
+                      "rotation": text.get_rotation(),
+                      "zOrder": text.get_zorder()}
+        if style_dict["alpha"] is None:
+            style_dict["alpha"] = 1
+        return style_dict
+
+    @staticmethod
+    def get_dict_from_fig_properties(fig):
+        return {"figWidth": fig.get_figwidth(), "figHeight": fig.get_figheight(), "dpi": fig.dpi}
diff --git a/qt/python/mantidqt/project/project.py b/qt/python/mantidqt/project/project.py
new file mode 100644
index 0000000000000000000000000000000000000000..f93b92f6d7792da6329856e999aaec841f2f365f
--- /dev/null
+++ b/qt/python/mantidqt/project/project.py
@@ -0,0 +1,174 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import os
+from qtpy.QtWidgets import QFileDialog, QMessageBox
+from qtpy.QtGui import QIcon  # noqa
+
+from mantid.api import AnalysisDataService, AnalysisDataServiceObserver
+from mantidqt.io import open_a_file_dialog
+from mantidqt.project.projectloader import ProjectLoader
+from mantidqt.project.projectsaver import ProjectSaver
+
+
+class Project(AnalysisDataServiceObserver):
+    def __init__(self, globalfiguremanager_instance):
+        super(Project, self).__init__()
+        # Has the project been saved, to Access this call .saved
+        self.__saved = True
+
+        # Last save locations
+        self.last_project_location = None
+
+        self.observeAll(True)
+
+        self.project_file_ext = ".mtdproj"
+
+        self.plot_gfm = globalfiguremanager_instance
+        self.plot_gfm.add_observer(self)
+
+    def __get_saved(self):
+        return self.__saved
+
+    saved = property(__get_saved)
+
+    def save(self):
+        """
+        The function that is called if the save button is clicked on the mainwindow
+        :return: None; if the user cancels
+        """
+        if self.last_project_location is None:
+            return self.save_as()
+        else:
+            # Offer an are you sure? overwriting GUI
+            answer = self._offer_overwriting_gui()
+
+            if answer == QMessageBox.Yes:
+                # Actually save
+                self._save()
+            # Else do nothing
+
+    def save_as(self):
+        """
+        The function that is called if the save as... button is clicked on the mainwindow
+        :return: None; if the user cancels.
+        """
+        path = self._save_file_dialog()
+        if path is None:
+            # Cancel close dialogs
+            return
+
+        overwriting = False
+        # If the selected path is a project directory ask if overwrite is required?
+        if os.path.exists(os.path.join(path, (os.path.basename(path) + self.project_file_ext))):
+            answer = self._offer_overwriting_gui()
+            if answer == QMessageBox.No:
+                return
+            elif answer == QMessageBox.Yes:
+                overwriting = True
+
+        if not overwriting and os.path.exists(path) and os.listdir(path) != []:
+            QMessageBox.warning(None, "Empty directory or project required!",
+                                "Please choose either an new directory or an already saved project", QMessageBox.Ok)
+            return
+
+        # todo: get a list of workspaces but to be implemented on GUI implementation
+        self.last_project_location = path
+        self._save()
+
+    @staticmethod
+    def _offer_overwriting_gui():
+        """
+        Offers up a overwriting QMessageBox giving the option to overwrite a project, and returns the reply.
+        :return: QMessaageBox.Yes or QMessageBox.No; The value is the value selected by the user.
+        """
+        return QMessageBox.question(None, "Overwrite project?",
+                                    "Would you like to overwrite the selected project?",
+                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
+
+    @staticmethod
+    def _save_file_dialog():
+        return open_a_file_dialog(accept_mode=QFileDialog.AcceptSave, file_mode=QFileDialog.Directory)
+
+    def _save(self):
+        workspaces_to_save = AnalysisDataService.getObjectNames()
+        plots_to_save = self.plot_gfm.figs
+        project_saver = ProjectSaver(self.project_file_ext)
+        project_saver.save_project(directory=self.last_project_location, workspace_to_save=workspaces_to_save,
+                                   plots_to_save=plots_to_save)
+        self.__saved = True
+
+    def load(self):
+        """
+        The event that is called when open project is clicked on the main window
+        :return: None; if the user cancelled.
+        """
+        file_name = self._load_file_dialog()
+        if file_name is None:
+            # Cancel close dialogs
+            return
+
+        # Sanity check
+        _, file_ext = os.path.splitext(file_name)
+
+        if file_ext != ".mtdproj":
+            QMessageBox.warning(None, "Wrong file type!", "Please select a valid project file", QMessageBox.Ok)
+
+        directory = os.path.dirname(file_name)
+
+        project_loader = ProjectLoader(self.project_file_ext)
+        project_loader.load_project(directory)
+        self.last_project_location = directory
+        self.__saved = True
+
+    def _load_file_dialog(self):
+        return open_a_file_dialog(accept_mode=QFileDialog.AcceptOpen, file_mode=QFileDialog.ExistingFile,
+                                  file_filter="Project files ( *" + self.project_file_ext + ")")
+
+    def offer_save(self, parent):
+        """
+        :param parent: QWidget; Parent of the QMessageBox that is popped up
+        :return: Bool; Returns false if no save needed/save complete. Returns True if need to cancel closing. However
+                        will return None if self.__saved is false
+        """
+        # If the current project is saved then return and don't do anything
+        if self.__saved:
+            return
+
+        result = self._offer_save_message_box(parent)
+
+        if result == QMessageBox.Yes:
+            self.save()
+        elif result == QMessageBox.Cancel:
+            return True
+        # if yes or no return false
+        return False
+
+    @staticmethod
+    def _offer_save_message_box(parent):
+        return QMessageBox.question(parent, 'Unsaved Project', "The project is currently unsaved would you like to "
+                                    "save before closing?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
+                                    QMessageBox.Yes)
+
+    def modified_project(self):
+        self.__saved = False
+
+    def anyChangeHandle(self):
+        """
+        The method that will be triggered if any of the changes in the ADS have occurred, that are checked for using the
+        AnalysisDataServiceObserver class' observeAll method
+        """
+        self.modified_project()
+
+    def notify(self, *args):
+        """
+        The method that will trigger when a plot is added, destroyed, or changed in the global figure manager.
+        """
+        self.modified_project()
diff --git a/qt/python/mantidqt/project/projectloader.py b/qt/python/mantidqt/project/projectloader.py
new file mode 100644
index 0000000000000000000000000000000000000000..67f420be40de961e603a6931c84860ec206b98b8
--- /dev/null
+++ b/qt/python/mantidqt/project/projectloader.py
@@ -0,0 +1,81 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import json
+import os
+
+from mantidqt.project.workspaceloader import WorkspaceLoader
+from mantidqt.project.plotsloader import PlotsLoader
+from mantid import AnalysisDataService as ADS, logger
+
+
+def _confirm_all_workspaces_loaded(workspaces_to_confirm):
+    if workspaces_to_confirm is None:
+        return True
+
+    current_workspaces = ADS.getObjectNames()
+    for ws in workspaces_to_confirm:
+        if ws not in current_workspaces:
+            logger.warning("Project Loader was unable to load back all of project workspaces")
+            return False
+    return True
+
+
+class ProjectLoader(object):
+    def __init__(self, project_file_ext):
+        self.project_reader = ProjectReader(project_file_ext)
+        self.workspace_loader = WorkspaceLoader()
+        self.plot_loader = PlotsLoader()
+        self.project_file_ext = project_file_ext
+
+    def load_project(self, directory):
+        """
+        Will load the project in the given directory
+        :param directory: String or string castable object; the directory of the project
+        :return: Bool; True if all workspace loaded successfully, False if not loaded successfully.
+        """
+        # It can be expected that if at this point it is NoneType that it's an error
+        if directory is None:
+            return
+
+        # Read project
+        self.project_reader.read_project(directory)
+
+        # Load in the workspaces
+        self.workspace_loader.load_workspaces(directory=directory,
+                                              workspaces_to_load=self.project_reader.workspace_names)
+        workspace_success = _confirm_all_workspaces_loaded(workspaces_to_confirm=self.project_reader.workspace_names)
+
+        if workspace_success:
+            # Load plots
+            self.plot_loader.load_plots(self.project_reader.plot_lists)
+
+        return workspace_success
+
+
+class ProjectReader(object):
+    def __init__(self, project_file_ext):
+        self.workspace_names = None
+        self.plot_lists = None
+        self.project_file_ext = project_file_ext
+
+    def read_project(self, directory):
+        """
+        :param directory: String or string castable object; the directory of the project
+        Will read the project file in from the directory that is given.
+        try:
+        """
+        try:
+            with open(os.path.join(directory, (os.path.basename(directory) + self.project_file_ext))) as f:
+                json_data = json.load(f)
+                self.workspace_names = json_data["workspaces"]
+                self.plot_lists = json_data["plots"]
+        except Exception:
+            logger.warning("JSON project file unable to be loaded/read")
diff --git a/qt/python/mantidqt/project/projectsaver.py b/qt/python/mantidqt/project/projectsaver.py
new file mode 100644
index 0000000000000000000000000000000000000000..63ab9ac6f69f1dc4a5e54f6c40c0f637be5fbaea
--- /dev/null
+++ b/qt/python/mantidqt/project/projectsaver.py
@@ -0,0 +1,78 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+from json import dump
+import os
+
+from mantidqt.project.workspacesaver import WorkspaceSaver
+from mantidqt.project.plotssaver import PlotsSaver
+from mantid import logger
+
+
+class ProjectSaver(object):
+    def __init__(self, project_file_ext):
+        self.project_file_ext = project_file_ext
+
+    def save_project(self, directory, workspace_to_save=None, plots_to_save=None):
+        """
+        The method that will actually save the project and call relevant savers for workspaces, plots, interfaces etc.
+        :param directory: String; The directory of the
+        :param workspace_to_save: List; of Strings that will have workspace names in it, if None will save all
+        :param plots_to_save: List; of matplotlib.figure objects to save to the project file.
+        :return: None; If the method cannot be completed.
+        """
+        # Check if the directory doesn't exist
+        if directory is None:
+            logger.warning("Can not save to empty directory")
+            return
+
+        # Check this isn't saving a blank project file
+        if workspace_to_save is None and plots_to_save is None:
+            logger.warning("Can not save an empty project")
+            return
+
+        # Save workspaces to that location
+        workspace_saver = WorkspaceSaver(directory=directory)
+        workspace_saver.save_workspaces(workspaces_to_save=workspace_to_save)
+
+        # Generate plots
+        plots_to_save_list = PlotsSaver().save_plots(plots_to_save)
+
+        # Pass dicts to Project Writer
+        writer = ProjectWriter(workspace_names=workspace_saver.get_output_list(),
+                               plots_to_save=plots_to_save_list,
+                               save_location=directory,
+                               project_file_ext=self.project_file_ext)
+        writer.write_out()
+
+
+class ProjectWriter(object):
+    def __init__(self, save_location, workspace_names, project_file_ext, plots_to_save):
+        self.workspace_names = workspace_names
+        self.directory = save_location
+        self.project_file_ext = project_file_ext
+        self.plots_to_save = plots_to_save
+
+    def write_out(self):
+        """
+        Write out the project file that contains workspace names, interfaces information, plot preferences etc.
+        """
+        # Get the JSON string versions
+        to_save_dict = {"workspaces": self.workspace_names, "plots": self.plots_to_save}
+
+        # Open file and save the string to it alongside the workspace_names
+        if not os.path.isdir(self.directory):
+            os.makedirs(self.directory)
+        file_path = os.path.join(self.directory, (os.path.basename(self.directory) + self.project_file_ext))
+        try:
+            with open(file_path, "w+") as f:
+                dump(obj=to_save_dict, fp=f)
+        except Exception:
+            logger.warning("JSON project file unable to be opened/written to")
diff --git a/qt/python/mantidqt/project/test/__init__.py b/qt/python/mantidqt/project/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..044ed3c205730d7114d5b66b2a4a733d211106c0
--- /dev/null
+++ b/qt/python/mantidqt/project/test/__init__.py
@@ -0,0 +1,8 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
diff --git a/qt/python/mantidqt/project/test/test_plotsloader.py b/qt/python/mantidqt/project/test/test_plotsloader.py
new file mode 100644
index 0000000000000000000000000000000000000000..df4dd5fc0f70367daf080448fe1e98b1c63bdbec
--- /dev/null
+++ b/qt/python/mantidqt/project/test/test_plotsloader.py
@@ -0,0 +1,135 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import matplotlib
+matplotlib.use('AGG')
+
+import sys  # noqa
+import unittest  # noqa
+import matplotlib.pyplot as plt  # noqa
+import matplotlib.figure  # noqa
+import matplotlib.text  # noqa
+
+from mantidqt.project.plotsloader import PlotsLoader  # noqa
+import mantid.plots.plotfunctions  # noqa
+from mantid.api import AnalysisDataService as ADS  # noqa
+from mantid.dataobjects import Workspace2D  # noqa
+
+if sys.version_info.major >= 3:
+    # Python 3 and above
+    from unittest import mock
+else:
+    # Python 2
+    import mock
+
+
+def pass_func():
+    pass
+
+
+class PlotsLoaderTest(unittest.TestCase):
+    def setUp(self):
+        self.plots_loader = PlotsLoader()
+        plt.plot = mock.MagicMock()
+        mantid.plots.plotfunctions.plot = mock.MagicMock()
+        self.dictionary = {u'legend': {u'exists': False}, u'lines': [],
+                           u'properties': {u'axisOn': True, u'bounds': (0.0, 0.0, 0.0, 0.0), u'dynamic': True,
+                                           u'frameOn': True, u'visible': True,
+                                           u'xAxisProperties': {u'fontSize': 10.0,
+                                                                u'gridStyle': {u'gridOn': False},
+                                                                u'majorTickFormat': None,
+                                                                u'majorTickFormatter': 'ScalarFormatter',
+                                                                u'majorTickLocator': 'AutoLocator',
+                                                                u'majorTickLocatorValues': None,
+                                                                u'minorTickFormat': None,
+                                                                u'minorTickFormatter': 'NullFormatter',
+                                                                u'minorTickLocator': 'NullLocator',
+                                                                u'minorTickLocatorValues': None,
+                                                                u'position': u'Bottom',
+                                                                u'visible': True},
+                                           u'xAxisScale': u'linear', u'xLim': (0.0, 1.0),
+                                           u'yAxisProperties': {u'fontSize': 10.0,
+                                                                u'gridStyle': {u'gridOn': False},
+                                                                u'majorTickFormat': None,
+                                                                u'majorTickFormatter': 'ScalarFormatter',
+                                                                u'majorTickLocator': 'AutoLocator',
+                                                                u'majorTickLocatorValues': None,
+                                                                u'minorTickFormat': None,
+                                                                u'minorTickFormatter': 'NullFormatter',
+                                                                u'minorTickLocator': 'NullLocator',
+                                                                u'minorTickLocatorValues': None,
+                                                                u'position': u'Left',
+                                                                u'visible': True},
+                                           u'yAxisScale': u'linear', u'yLim': (0.0, 1.0)},
+                           u'textFromArtists': {}, u'texts': [], u'title': u'', u'xAxisTitle': u'', u'yAxisTitle': u''}
+
+    def test_load_plots_does_the_right_calls(self):
+        self.plots_loader.make_fig = mock.MagicMock()
+        self.plots_loader.load_plots(["plot1", "plot2"])
+
+        self.assertEqual(self.plots_loader.make_fig.call_count, 2)
+
+    @mock.patch("matplotlib.figure.Figure.show")
+    def test_make_fig_makes_the_right_calls(self, pass_func):
+        ws = Workspace2D()
+        ADS.add("ws", ws)
+        plot_dict = {"label": "plot", "creationArguments": [[{"workspaces": "ws", "wkspIndex": 0}, {}, {}]]}
+        self.plots_loader.plot_extra_lines = mock.MagicMock()
+        self.plots_loader.plot_func = mock.MagicMock()
+        self.plots_loader.restore_figure_data = mock.MagicMock()
+
+        self.plots_loader.make_fig(plot_dict)
+
+        self.assertEqual(self.plots_loader.plot_func.call_count, 1)
+        self.assertEqual(self.plots_loader.plot_extra_lines.call_count, 1)
+        self.assertEqual(self.plots_loader.restore_figure_data.call_count, 1)
+
+    def test_restore_fig_properties(self):
+        matplotlib.figure.Figure.set_figheight = mock.MagicMock()
+        matplotlib.figure.Figure.set_figwidth = mock.MagicMock()
+        matplotlib.figure.Figure.set_dpi = mock.MagicMock()
+        self.plots_loader.restore_fig_properties(matplotlib.figure.Figure(), {"figHeight": 1, "figWidth": 1, "dpi": 1})
+
+        self.assertEqual(matplotlib.figure.Figure.set_figheight.call_count, 1)
+        self.assertEqual(matplotlib.figure.Figure.set_figwidth.call_count, 1)
+        self.assertEqual(matplotlib.figure.Figure.set_dpi.call_count, 1)
+
+    def test_restore_fig_axes(self):
+        self.plots_loader.update_properties = mock.MagicMock()
+        self.plots_loader.update_lines = mock.MagicMock()
+        self.plots_loader.create_text_from_dict = mock.MagicMock()
+        self.plots_loader.update_legend = mock.MagicMock()
+
+        fig = matplotlib.figure.Figure()
+        self.plots_loader.restore_fig_axes(matplotlib.axes.Axes(fig=fig, rect=[0, 0, 0, 0]), self.dictionary)
+
+        self.assertEqual(self.plots_loader.update_properties.call_count, 1)
+        self.assertEqual(self.plots_loader.update_lines.call_count, 0)
+        self.assertEqual(self.plots_loader.create_text_from_dict.call_count, 0)
+        self.assertEqual(self.plots_loader.update_legend.call_count, 1)
+
+    def test_create_text_from_dict(self):
+        fig = matplotlib.figure.Figure()
+        ax = matplotlib.axes.Axes(fig=fig, rect=[0, 0, 0, 0])
+        ax.text = mock.MagicMock()
+
+        self.plots_loader.create_text_from_dict(ax=ax, dic={"text": "text", "position": (1, 1), "useTeX": 1,
+                                                            "style": {"alpha": 1, "textSize": 1, "color": 1,
+                                                                      "hAlign": 1, "vAlign": 1, "rotation": 1,
+                                                                      "zOrder": 1}})
+        self.assertEqual(ax.text.call_count, 1)
+        ax.text.assert_called_once_with(fontdict={u'zorder': 1, u'fontsize': 1, u'color': 1, u'alpha': 1,
+                                                  u'rotation': 1, u'verticalalignment': 1, u'usetex': 1,
+                                                  u'horizontalalignment': 1}, s=u'text', x=1, y=1)
+
+    @mock.patch("matplotlib.figure.Figure.show")
+    def test_load_plot_from_dict(self, pass_func):
+        # The fact this runs is the test
+        self.plots_loader.load_plots([self.dictionary])
diff --git a/qt/python/mantidqt/project/test/test_plotssaver.py b/qt/python/mantidqt/project/test/test_plotssaver.py
new file mode 100644
index 0000000000000000000000000000000000000000..f162dc6dc935dbab4ac72d628b57823da6736e87
--- /dev/null
+++ b/qt/python/mantidqt/project/test/test_plotssaver.py
@@ -0,0 +1,172 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import unittest
+
+from mantid.api import AnalysisDataService as ADS
+from mantidqt.project.plotssaver import PlotsSaver
+from mantidqt.project.plotsloader import PlotsLoader
+from mantid.simpleapi import CreateSampleWorkspace
+
+import matplotlib
+matplotlib.use('AGG')
+
+
+class PlotsSaverTest(unittest.TestCase):
+    def setUp(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        self.plots_loader = PlotsLoader()
+
+        # Make a figure with a given input with all these values already set
+        self.loader_plot_dict = {u'axes': [{u'colorbar': {u'exists': False},
+                                            u'legend': {u'exists': False}, u'lines': [{u'alpha': 1,
+                                                                                       u'color': u'#1f77b4',
+                                                                                       u'label': u'ws1: spec 2',
+                                                                                       u'lineIndex': 0,
+                                                                                       u'lineStyle': u'-',
+                                                                                       u'lineWidth': 1.5,
+                                                                                       u'markerStyle': {
+                                                                                           u'edgeColor': u'#1f77b4',
+                                                                                           u'edgeWidth': 1.0,
+                                                                                           u'faceColor': u'#1f77b4',
+                                                                                           u'markerSize': 6.0,
+                                                                                           u'markerType': u'None',
+                                                                                           u'zOrder': 2},
+                                                                                       u'errorbars': {
+                                                                                           u'exists': False
+                                                                                       }}],
+                                 u'properties': {u'axisOn': True, u'bounds': (0.0, 0.0, 0.0, 0.0), u'dynamic': True,
+                                                 u'frameOn': True, u'visible': True,
+                                                 u'xAxisProperties': {u'fontSize': 10.0,
+                                                                      u'gridStyle': {u'gridOn': False},
+                                                                      u'majorTickFormat': None,
+                                                                      u'majorTickFormatter': u'ScalarFormatter',
+                                                                      u'majorTickLocator': u'AutoLocator',
+                                                                      u'majorTickLocatorValues': None,
+                                                                      u'minorTickFormat': None,
+                                                                      u'minorTickFormatter': u'NullFormatter',
+                                                                      u'minorTickLocator': u'NullLocator',
+                                                                      u'minorTickLocatorValues': None,
+                                                                      u'position': u'Bottom',
+                                                                      u'visible': True},
+                                                 u'xAxisScale': u'linear', u'xLim': (0.0, 1.0),
+                                                 u'yAxisProperties': {u'fontSize': 10.0,
+                                                                      u'gridStyle': {u'gridOn': False},
+                                                                      u'majorTickFormat': None,
+                                                                      u'majorTickFormatter': u'ScalarFormatter',
+                                                                      u'majorTickLocator': u'AutoLocator',
+                                                                      u'majorTickLocatorValues': None,
+                                                                      u'minorTickFormat': None,
+                                                                      u'minorTickFormatter': u'NullFormatter',
+                                                                      u'minorTickLocator': u'NullLocator',
+                                                                      u'minorTickLocatorValues': None,
+                                                                      u'position': u'Left',
+                                                                      u'visible': True},
+                                                 u'yAxisScale': u'linear', u'yLim': (0.0, 1.0)},
+                                            u'textFromArtists': {}, u'texts': [{u'position': (0, 0),
+                                                                                u'style': {u'alpha': 1,
+                                                                                           u'color': u'#000000',
+                                                                                           u'hAlign': u'left',
+                                                                                           u'rotation': 0.0,
+                                                                                           u'textSize': 10.0,
+                                                                                           u'vAlign': u'baseline',
+                                                                                           u'zOrder': 3},
+                                                                                u'text': u'text', u'useTeX': False}],
+                                            u'title': u'', u'xAxisTitle': u'',
+                                            u'yAxisTitle': u''}],
+                                 u'creationArguments': [[{u"workspaces": u"ws1", u"specNum": 2, u"function": u"plot"}]],
+                                 u'label': u'',
+                                 u'properties': {u'dpi': 100.0, u'figHeight': 4.8, u'figWidth': 6.4}}
+        self.fig = self.plots_loader.make_fig(self.loader_plot_dict, create_plot=False)
+
+        self.plot_saver = PlotsSaver()
+
+    def tearDown(self):
+        ADS.clear()
+
+    def test_save_plots(self):
+        plot_dict = {}
+        return_value = self.plot_saver.save_plots(plot_dict)
+
+        self.assertEqual(return_value, [])
+
+    def test_get_dict_from_fig(self):
+        self.fig.axes[0].creation_args = [{u"specNum": 2, "function": "plot"}]
+        return_value = self.plot_saver.get_dict_from_fig(self.fig)
+
+        self.loader_plot_dict[u'creationArguments'] = [[{u"specNum": 2, "function": "plot"}]]
+
+        self.maxDiff = None
+        self.assertDictEqual(return_value, self.loader_plot_dict)
+
+    def test_get_dict_from_axes(self):
+        self.plot_saver.figure_creation_args = [{"function": "plot"}]
+        return_value = self.plot_saver.get_dict_for_axes(self.fig.axes[0])
+
+        expected_value = self.loader_plot_dict["axes"][0]
+
+        self.maxDiff = None
+        self.assertDictEqual(return_value, expected_value)
+
+    def test_get_dict_from_axes_properties(self):
+        return_value = self.plot_saver.get_dict_from_axes_properties(self.fig.axes[0])
+
+        expected_value = self.loader_plot_dict["axes"][0]["properties"]
+
+        self.maxDiff = None
+        self.assertDictEqual(return_value, expected_value)
+
+    def test_get_dict_from_axis_properties(self):
+
+        return_value = self.plot_saver.get_dict_from_axis_properties(self.fig.axes[0].xaxis)
+
+        expected_value = self.loader_plot_dict["axes"][0]["properties"]["xAxisProperties"]
+
+        self.assertDictEqual(return_value, expected_value)
+
+    def test_get_dict_for_grid_style(self):
+        return_value = self.plot_saver.get_dict_for_grid_style(self.fig.axes[0].xaxis)
+
+        expected_value = self.loader_plot_dict["axes"][0]["properties"]["xAxisProperties"]["gridStyle"]
+
+        self.assertDictEqual(return_value, expected_value)
+
+    def test_get_dict_from_line(self):
+        self.plot_saver.figure_creation_args = [{"function": "plot"}]
+        line = self.fig.axes[0].lines[0]
+        return_value = self.plot_saver.get_dict_from_line(line, 0)
+
+        expected_value = self.loader_plot_dict["axes"][0]["lines"][0]
+
+        self.assertDictEqual(return_value, expected_value)
+
+    def test_get_dict_from_marker_style(self):
+        line = self.fig.axes[0].lines[0]
+        return_value = self.plot_saver.get_dict_from_marker_style(line)
+
+        expected_value = self.loader_plot_dict["axes"][0]["lines"][0]["markerStyle"]
+
+        self.assertDictEqual(return_value, expected_value)
+
+    def test_get_dict_from_text_style(self):
+        text = self.fig.axes[0].texts[0]
+        return_value = self.plot_saver.get_dict_from_text(text)
+
+        expected_value = self.loader_plot_dict["axes"][0]["texts"][0]
+
+        self.maxDiff = None
+        self.assertDictEqual(return_value, expected_value)
+
+    def test_get_dict_from_fig_properties(self):
+        return_value = self.plot_saver.get_dict_from_fig_properties(self.fig)
+
+        expected_value = {u'dpi': 100.0, u'figHeight': 4.8, u'figWidth': 6.4}
+
+        self.assertDictEqual(return_value, expected_value)
diff --git a/qt/python/mantidqt/project/test/test_project.py b/qt/python/mantidqt/project/test/test_project.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1198a6e85be09c5d1563dc3c849126ea690f9a9
--- /dev/null
+++ b/qt/python/mantidqt/project/test/test_project.py
@@ -0,0 +1,167 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import unittest
+import sys
+import tempfile
+import os
+
+from qtpy.QtWidgets import QMessageBox
+
+from mantid.simpleapi import CreateSampleWorkspace, GroupWorkspaces, RenameWorkspace, UnGroupWorkspace
+from mantid.api import AnalysisDataService as ADS
+from mantidqt.project.project import Project
+
+if sys.version_info.major >= 3:
+    # Python 3 and above
+    from unittest import mock
+else:
+    # Python 2
+    import mock
+
+
+class FakeGlobalFigureManager(object):
+    def add_observer(self, *unused):
+        pass
+
+
+class ProjectTest(unittest.TestCase):
+    def setUp(self):
+        self.fgfm = FakeGlobalFigureManager()
+        self.fgfm.figs = []
+        self.project = Project(self.fgfm)
+
+    def tearDown(self):
+        ADS.clear()
+
+    def test_save_calls_save_as_when_last_location_is_not_none(self):
+        self.project.save_as = mock.MagicMock()
+        self.project.save()
+        self.assertEqual(self.project.save_as.call_count, 1)
+
+    def test_save_does_not_call_save_as_when_last_location_is_not_none(self):
+        self.project.save_as = mock.MagicMock()
+        self.project.last_project_location = "1"
+        self.assertEqual(self.project.save_as.call_count, 0)
+
+    def test_save_saves_project_successfully(self):
+        working_directory = tempfile.mkdtemp()
+        self.project.last_project_location = working_directory
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        self.project._offer_overwriting_gui = mock.MagicMock(return_value=QMessageBox.Yes)
+
+        self.project.save()
+
+        self.assertTrue(os.path.isdir(working_directory))
+        file_list = os.listdir(working_directory)
+        self.assertTrue(os.path.basename(working_directory) + ".mtdproj" in file_list)
+        self.assertTrue("ws1.nxs" in file_list)
+        self.assertEqual(self.project._offer_overwriting_gui.call_count, 1)
+
+    def test_save_as_saves_project_successfully(self):
+        working_directory = tempfile.mkdtemp()
+        self.project._save_file_dialog = mock.MagicMock(return_value=working_directory)
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+
+        self.project.save_as()
+
+        self.assertEqual(self.project._save_file_dialog.call_count, 1)
+        self.assertTrue(os.path.isdir(working_directory))
+        file_list = os.listdir(working_directory)
+        self.assertTrue(os.path.basename(working_directory) + ".mtdproj" in file_list)
+        self.assertTrue("ws1.nxs" in file_list)
+
+    def test_load_calls_loads_successfully(self):
+        working_directory = tempfile.mkdtemp()
+        return_value_for_load = os.path.join(working_directory, os.path.basename(working_directory) + ".mtdproj")
+        self.project._save_file_dialog = mock.MagicMock(return_value=working_directory)
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        self.project.save_as()
+
+        self.assertEqual(self.project._save_file_dialog.call_count, 1)
+        ADS.clear()
+
+        self.project._load_file_dialog = mock.MagicMock(return_value=return_value_for_load)
+        self.project.load()
+        self.assertEqual(self.project._load_file_dialog.call_count, 1)
+        self.assertEqual(["ws1"], ADS.getObjectNames())
+
+    def test_offer_save_does_nothing_if_saved_is_true(self):
+        self.assertEqual(self.project.offer_save(None), None)
+
+    def test_offer_save_does_something_if_saved_is_false(self):
+        self.project._offer_save_message_box = mock.MagicMock(return_value=QMessageBox.Yes)
+        self.project.save = mock.MagicMock()
+
+        # Add something to the ads so __saved is set to false
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+
+        self.assertEqual(self.project.offer_save(None), False)
+        self.assertEqual(self.project.save.call_count, 1)
+        self.assertEqual(self.project._offer_save_message_box.call_count, 1)
+
+    def test_adding_to_ads_calls_any_change_handle(self):
+        self.project.anyChangeHandle = mock.MagicMock()
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+
+        self.assertEqual(1, self.project.anyChangeHandle.call_count)
+
+    def test_removing_from_ads_calls_any_change_handle(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+
+        self.project.anyChangeHandle = mock.MagicMock()
+        ADS.remove("ws1")
+
+        self.assertEqual(1, self.project.anyChangeHandle.call_count)
+
+    def test_grouping_in_ads_calls_any_change_handle(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+
+        self.project.anyChangeHandle = mock.MagicMock()
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+
+        # Called twice because group is made and then added to the ADS
+        self.assertEqual(2, self.project.anyChangeHandle.call_count)
+
+    def test_renaming_in_ads_calls_any_change_handle(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+
+        self.project.anyChangeHandle = mock.MagicMock()
+        RenameWorkspace(InputWorkspace="ws1", OutputWorkspace="ws2")
+
+        # Called twice because first workspace is removed and second is added
+        self.assertEqual(2, self.project.anyChangeHandle.call_count)
+
+    def test_ungrouping_in_ads_calls_any_change_handle(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+
+        self.project.anyChangeHandle = mock.MagicMock()
+        UnGroupWorkspace(InputWorkspace="NewGroup")
+
+        # 1 for removing old group and 1 for something else but 2 seems right
+        self.assertEqual(2, self.project.anyChangeHandle.call_count)
+
+    def test_group_updated_in_ads_calls_any_change_handle(self):
+        CreateSampleWorkspace(OutputWorkspace="ws1")
+        CreateSampleWorkspace(OutputWorkspace="ws2")
+        GroupWorkspaces(InputWorkspaces="ws1,ws2", OutputWorkspace="NewGroup")
+        CreateSampleWorkspace(OutputWorkspace="ws3")
+
+        self.project.anyChangeHandle = mock.MagicMock()
+        ADS.addToGroup("NewGroup", "ws3")
+
+        self.assertEqual(1, self.project.anyChangeHandle.call_count)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/qt/python/mantidqt/project/test/test_projectloader.py b/qt/python/mantidqt/project/test/test_projectloader.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f33fb1227f118f352fbea77880bcbdbaf994354
--- /dev/null
+++ b/qt/python/mantidqt/project/test/test_projectloader.py
@@ -0,0 +1,78 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+
+import unittest
+
+import matplotlib
+matplotlib.use('AGG')
+
+from os.path import isdir  # noqa
+from shutil import rmtree  # noqa
+import tempfile  # noqa
+
+from mantid.api import AnalysisDataService as ADS  # noqa
+from mantid.simpleapi import CreateSampleWorkspace  # noqa
+from mantidqt.project import projectloader, projectsaver  # noqa
+
+
+project_file_ext = ".mtdproj"
+working_directory = tempfile.mkdtemp()
+
+
+class ProjectLoaderTest(unittest.TestCase):
+    def setUp(self):
+        ws1_name = "ws1"
+        ADS.addOrReplace(ws1_name, CreateSampleWorkspace(OutputWorkspace=ws1_name))
+        project_saver = projectsaver.ProjectSaver(project_file_ext)
+        project_saver.save_project(workspace_to_save=[ws1_name], directory=working_directory)
+
+    def tearDown(self):
+        ADS.clear()
+        if isdir(working_directory):
+            rmtree(working_directory)
+
+    def test_project_loading_when_directory_is_none(self):
+        # Tests that error handling of a value being none receives a None back.
+        project_loader = projectloader.ProjectLoader(project_file_ext)
+
+        self.assertEqual(project_loader.load_project(None), None)
+
+    def test_project_loading(self):
+        project_loader = projectloader.ProjectLoader(project_file_ext)
+
+        self.assertTrue(project_loader.load_project(working_directory))
+
+        self.assertEqual(ADS.getObjectNames(), ["ws1"])
+
+    def test_confirm_all_workspaces_loaded(self):
+        ws1_name = "ws1"
+        ADS.addOrReplace(ws1_name, CreateSampleWorkspace(OutputWorkspace=ws1_name))
+        self.assertTrue(projectloader._confirm_all_workspaces_loaded(workspaces_to_confirm=[ws1_name]))
+
+
+class ProjectReaderTest(unittest.TestCase):
+    def setUp(self):
+        ws1_name = "ws1"
+        ADS.addOrReplace(ws1_name, CreateSampleWorkspace(OutputWorkspace=ws1_name))
+        project_saver = projectsaver.ProjectSaver(project_file_ext)
+        project_saver.save_project(workspace_to_save=[ws1_name], directory=working_directory)
+
+    def tearDown(self):
+        ADS.clear()
+        if isdir(working_directory):
+            rmtree(working_directory)
+
+    def test_project_reading(self):
+        project_reader = projectloader.ProjectReader(project_file_ext)
+        project_reader.read_project(working_directory)
+        self.assertEqual(["ws1"], project_reader.workspace_names)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/qt/python/mantidqt/project/test/test_projectsaver.py b/qt/python/mantidqt/project/test/test_projectsaver.py
new file mode 100644
index 0000000000000000000000000000000000000000..08341871d9d7499bea7e2d5d56e852945c519df4
--- /dev/null
+++ b/qt/python/mantidqt/project/test/test_projectsaver.py
@@ -0,0 +1,221 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+import unittest
+import tempfile
+
+import os
+import json
+from shutil import rmtree
+
+import matplotlib.backend_bases
+import matplotlib.figure
+
+from mantid.api import AnalysisDataService as ADS
+from mantid.simpleapi import CreateSampleWorkspace
+from mantidqt.project import projectsaver
+
+
+project_file_ext = ".mtdproj"
+working_directory = tempfile.mkdtemp()
+
+
+class ProjectSaverTest(unittest.TestCase):
+    def tearDown(self):
+        ADS.clear()
+
+    def setUp(self):
+        # In case it was hard killed and is still present
+        if os.path.isdir(working_directory):
+            rmtree(working_directory)
+
+    def test_only_one_workspace_saving(self):
+        ws1_name = "ws1"
+        ADS.addOrReplace(ws1_name, CreateSampleWorkspace(OutputWorkspace=ws1_name))
+        project_saver = projectsaver.ProjectSaver(project_file_ext)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        workspaces_string = "\"workspaces\": [\"ws1\"]"
+        plots_string = "\"plots\": []"
+
+        project_saver.save_project(workspace_to_save=[ws1_name], directory=working_directory)
+
+        # Check project file is saved correctly
+        f = open(file_name, "r")
+        file_string = f.read()
+        self.assertTrue(workspaces_string in file_string)
+        self.assertTrue(plots_string in file_string)
+
+        # Check workspace is saved
+        list_of_files = os.listdir(working_directory)
+        self.assertEqual(len(list_of_files), 2)
+        self.assertTrue(os.path.basename(working_directory) + project_file_ext in list_of_files)
+        self.assertTrue(ws1_name + ".nxs" in list_of_files)
+
+    def test_only_multiple_workspaces_saving(self):
+        ws1_name = "ws1"
+        ws2_name = "ws2"
+        ws3_name = "ws3"
+        ws4_name = "ws4"
+        ws5_name = "ws5"
+        CreateSampleWorkspace(OutputWorkspace=ws1_name)
+        CreateSampleWorkspace(OutputWorkspace=ws2_name)
+        CreateSampleWorkspace(OutputWorkspace=ws3_name)
+        CreateSampleWorkspace(OutputWorkspace=ws4_name)
+        CreateSampleWorkspace(OutputWorkspace=ws5_name)
+        project_saver = projectsaver.ProjectSaver(project_file_ext)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        workspaces_string = "\"workspaces\": [\"ws1\", \"ws2\", \"ws3\", \"ws4\", \"ws5\"]"
+        plots_string = "\"plots\": []"
+
+        project_saver.save_project(workspace_to_save=[ws1_name, ws2_name, ws3_name, ws4_name, ws5_name],
+                                   directory=working_directory)
+
+        # Check project file is saved correctly
+        f = open(file_name, "r")
+        file_string = f.read()
+        self.assertTrue(workspaces_string in file_string)
+        self.assertTrue(plots_string in file_string)
+
+        # Check workspace is saved
+        list_of_files = os.listdir(working_directory)
+        self.assertEqual(len(list_of_files), 6)
+        self.assertTrue(os.path.basename(working_directory) + project_file_ext in list_of_files)
+        self.assertTrue(ws1_name + ".nxs" in list_of_files)
+        self.assertTrue(ws2_name + ".nxs" in list_of_files)
+        self.assertTrue(ws3_name + ".nxs" in list_of_files)
+        self.assertTrue(ws4_name + ".nxs" in list_of_files)
+        self.assertTrue(ws5_name + ".nxs" in list_of_files)
+
+    def test_only_saving_one_workspace_when_multiple_are_present_in_the_ADS(self):
+        ws1_name = "ws1"
+        ws2_name = "ws2"
+        ws3_name = "ws3"
+        CreateSampleWorkspace(OutputWorkspace=ws1_name)
+        CreateSampleWorkspace(OutputWorkspace=ws2_name)
+        CreateSampleWorkspace(OutputWorkspace=ws3_name)
+        project_saver = projectsaver.ProjectSaver(project_file_ext)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        workspaces_string = "\"workspaces\": [\"ws1\"]"
+        plots_string = "\"plots\": []"
+
+        project_saver.save_project(workspace_to_save=[ws1_name], directory=working_directory)
+
+        # Check project file is saved correctly
+        f = open(file_name, "r")
+        file_string = f.read()
+        self.assertTrue(workspaces_string in file_string)
+        self.assertTrue(plots_string in file_string)
+
+        # Check workspace is saved
+        list_of_files = os.listdir(working_directory)
+        self.assertEqual(len(list_of_files), 2)
+        self.assertTrue(os.path.basename(working_directory) + project_file_ext in list_of_files)
+        self.assertTrue(ws1_name + ".nxs" in list_of_files)
+
+    def test_saving_plots_when_plots_are_passed(self):
+        fig = matplotlib.figure.Figure(dpi=100, figsize=(6.4, 4.8))
+        fig_manager = matplotlib.backend_bases.FigureManagerBase(matplotlib.backend_bases.FigureCanvasBase(fig), 1)
+        matplotlib.axes.Axes(fig=fig, rect=[0, 0, 0, 0])
+
+        project_saver = projectsaver.ProjectSaver(project_file_ext)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        project_saver.save_project(directory=working_directory,
+                                   plots_to_save={1: fig_manager})
+
+        plots_dict = {u"creationArguments": [], u"axes": [], u"label": u"", u"properties": {u"figWidth": 6.4,
+                                                                                            u"figHeight": 4.8,
+                                                                                            u"dpi": 100.0}}
+
+        f = open(file_name, "r")
+        file_dict = json.load(f)
+        self.assertDictEqual(plots_dict, file_dict["plots"][0])
+
+
+class ProjectWriterTest(unittest.TestCase):
+    def tearDown(self):
+        ADS.clear()
+
+    def setUp(self):
+        # In case it was hard killed and is still present
+        if os.path.isdir(working_directory):
+            rmtree(working_directory)
+
+    def test_write_out_empty_workspaces(self):
+        workspace_list = []
+        plots_to_save = []
+        project_writer = projectsaver.ProjectWriter(save_location=working_directory, workspace_names=workspace_list,
+                                                    project_file_ext=project_file_ext, plots_to_save=plots_to_save)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        workspaces_string = "\"workspaces\": []"
+        plots_string = "\"plots\": []"
+
+        project_writer.write_out()
+
+        f = open(file_name, "r")
+        file_string = f.read()
+        self.assertTrue(workspaces_string in file_string)
+        self.assertTrue(plots_string in file_string)
+
+    def test_write_out_on_just_workspaces(self):
+        plots_to_save = []
+        workspace_list = ["ws1", "ws2", "ws3", "ws4"]
+        project_writer = projectsaver.ProjectWriter(save_location=working_directory, workspace_names=workspace_list,
+                                                    project_file_ext=project_file_ext, plots_to_save=plots_to_save)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        workspaces_string = "\"workspaces\": [\"ws1\", \"ws2\", \"ws3\", \"ws4\"]"
+        plots_string = "\"plots\": []"
+
+        project_writer.write_out()
+        f = open(file_name, "r")
+        file_string = f.read()
+        self.assertTrue(workspaces_string in file_string)
+        self.assertTrue(plots_string in file_string)
+
+    def test_write_out_on_just_plots(self):
+        plots_to_save = [{"plots1": {"plot-information": "axes data"}}]
+        workspace_list = []
+        project_writer = projectsaver.ProjectWriter(save_location=working_directory, workspace_names=workspace_list,
+                                                    project_file_ext=project_file_ext, plots_to_save=plots_to_save)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        workspaces_string = "\"workspaces\": []"
+        plots_string = "\"plots\": [{\"plots1\": {\"plot-information\": \"axes data\"}}]"
+
+        project_writer.write_out()
+
+        f = open(file_name, "r")
+        file_string = f.read()
+        self.assertTrue(workspaces_string in file_string)
+        self.assertTrue(plots_string in file_string)
+
+    def test_write_out_on_both_workspaces_and_plots(self):
+        plots_to_save = [{"plots1": {"plot-information": "axes data"}}]
+        workspace_list = ["ws1", "ws2", "ws3", "ws4"]
+        project_writer = projectsaver.ProjectWriter(save_location=working_directory, workspace_names=workspace_list,
+                                                    project_file_ext=project_file_ext, plots_to_save=plots_to_save)
+        file_name = working_directory + "/" + os.path.basename(working_directory) + project_file_ext
+
+        workspaces_string = "\"workspaces\": [\"ws1\", \"ws2\", \"ws3\", \"ws4\"]"
+        plots_string = "\"plots\": [{\"plots1\": {\"plot-information\": \"axes data\"}}]"
+
+        project_writer.write_out()
+
+        f = open(file_name, "r")
+        file_string = f.read()
+        self.assertTrue(workspaces_string in file_string)
+        self.assertTrue(plots_string in file_string)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/qt/python/mantidqt/project/test/test_workspaceloader.py b/qt/python/mantidqt/project/test/test_workspaceloader.py
new file mode 100644
index 0000000000000000000000000000000000000000..54a46c2e588586615a411fe3a35366dc337d784a
--- /dev/null
+++ b/qt/python/mantidqt/project/test/test_workspaceloader.py
@@ -0,0 +1,42 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+
+import unittest
+
+from os.path import isdir
+from shutil import rmtree
+import tempfile
+
+from mantid.api import AnalysisDataService as ADS
+from mantid.simpleapi import CreateSampleWorkspace
+from mantidqt.project import projectsaver, workspaceloader
+
+
+class WorkspaceLoaderTest(unittest.TestCase):
+    def setUp(self):
+        self.working_directory = tempfile.mkdtemp()
+        self.ws1_name = "ws1"
+        self.project_ext = ".mtdproj"
+        ADS.addOrReplace(self.ws1_name, CreateSampleWorkspace(OutputWorkspace=self.ws1_name))
+        project_saver = projectsaver.ProjectSaver(self.project_ext)
+        project_saver.save_project(workspace_to_save=[self.ws1_name], directory=self.working_directory)
+
+    def tearDown(self):
+        ADS.clear()
+        if isdir(self.working_directory):
+            rmtree(self.working_directory)
+
+    def test_workspace_loading(self):
+        workspace_loader = workspaceloader.WorkspaceLoader()
+        workspace_loader.load_workspaces(self.working_directory, workspaces_to_load=[self.ws1_name])
+        self.assertEqual(ADS.getObjectNames(), [self.ws1_name])
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/qt/python/mantidqt/project/test/test_workspacesaver.py b/qt/python/mantidqt/project/test/test_workspacesaver.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b837776518cbfc90a5e48f305f0e0a2599ac9c0
--- /dev/null
+++ b/qt/python/mantidqt/project/test/test_workspacesaver.py
@@ -0,0 +1,84 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+
+import unittest
+
+from os import listdir
+from os.path import isdir
+from shutil import rmtree
+import tempfile
+
+from mantid.api import AnalysisDataService as ADS, IMDEventWorkspace  # noqa
+from mantid.dataobjects import MDHistoWorkspace, MaskWorkspace  # noqa
+from mantidqt.project import workspacesaver
+from mantid.simpleapi import (CreateSampleWorkspace, CreateMDHistoWorkspace, LoadMD, LoadMask, MaskDetectors,  # noqa
+                              ExtractMask)  # noqa
+
+
+class WorkspaceSaverTest(unittest.TestCase):
+    def setUp(self):
+        self.working_directory = tempfile.mkdtemp()
+
+    def tearDown(self):
+        ADS.clear()
+        if isdir(self.working_directory):
+            rmtree(self.working_directory)
+
+    def test_saving_single_workspace(self):
+        ws_saver = workspacesaver.WorkspaceSaver(self.working_directory)
+        ws1 = CreateSampleWorkspace()
+        ws1_name = "ws1"
+
+        ADS.addOrReplace(ws1_name, ws1)
+        ws_saver.save_workspaces([ws1_name])
+
+        list_of_files = listdir(self.working_directory)
+        self.assertEqual(len(list_of_files), 1)
+        self.assertTrue(ws1_name + ".nxs" in list_of_files)
+
+    def test_saving_multiple_workspaces(self):
+        ws_saver = workspacesaver.WorkspaceSaver(self.working_directory)
+        ws1 = CreateSampleWorkspace()
+        ws1_name = "ws1"
+        ws2 = CreateSampleWorkspace()
+        ws2_name = "ws2"
+
+        ADS.addOrReplace(ws1_name, ws1)
+        ADS.addOrReplace(ws2_name, ws2)
+        ws_saver.save_workspaces([ws1_name, ws2_name])
+
+        list_of_files = listdir(self.working_directory)
+        self.assertEqual(len(list_of_files), 2)
+        self.assertTrue(ws2_name + ".nxs" in list_of_files)
+        self.assertTrue(ws1_name + ".nxs" in list_of_files)
+
+    def test_when_MDWorkspace_is_in_ADS(self):
+        ws_saver = workspacesaver.WorkspaceSaver(self.working_directory)
+        ws1 = CreateMDHistoWorkspace(SignalInput='1,2,3,4,5,6,7,8,9', ErrorInput='1,1,1,1,1,1,1,1,1',
+                                     Dimensionality='2', Extents='-1,1,-1,1', NumberOfBins='3,3', Names='A,B',
+                                     Units='U,T')
+        ws1_name = "ws1"
+
+        ADS.addOrReplace(ws1_name, ws1)
+        ws_saver.save_workspaces([ws1_name])
+
+        list_of_files = listdir(self.working_directory)
+        self.assertEqual(len(list_of_files), 1)
+        self.assertTrue(ws1_name + ".nxs" in list_of_files)
+        self._load_MDWorkspace_and_test_it(ws1_name)
+
+    def _load_MDWorkspace_and_test_it(self, save_name):
+        filename = self.working_directory + '/' + save_name + ".nxs"
+        ws = LoadMD(Filename=filename)
+        ws_is_a_mdworkspace = isinstance(ws, IMDEventWorkspace) or isinstance(ws, MDHistoWorkspace)
+        self.assertEqual(ws_is_a_mdworkspace, True)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/qt/python/mantidqt/project/workspaceloader.py b/qt/python/mantidqt/project/workspaceloader.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3aeef043f15e122b5b0033a69c81347a6f95abf
--- /dev/null
+++ b/qt/python/mantidqt/project/workspaceloader.py
@@ -0,0 +1,33 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+from os import path
+
+from mantid import logger
+
+
+class WorkspaceLoader(object):
+    @staticmethod
+    def load_workspaces(directory, workspaces_to_load):
+        """
+        The method that is called to load in workspaces. From the given directory and the workspace names provided.
+        :param directory: String or string castable object; The project directory
+        :param workspaces_to_load: List of Strings; of the workspaces to load
+        """
+
+        if workspaces_to_load is None:
+            return
+
+        from mantid.simpleapi import Load  # noqa
+        for workspace in workspaces_to_load:
+            try:
+                Load(path.join(directory, (workspace + ".nxs")), OutputWorkspace=workspace)
+            except Exception:
+                logger.warning("Couldn't load file in project: " + workspace + ".nxs")
diff --git a/qt/python/mantidqt/project/workspacesaver.py b/qt/python/mantidqt/project/workspacesaver.py
new file mode 100644
index 0000000000000000000000000000000000000000..2be98f7ecd55d0c51c8ea8d27aa7c41c9a1c4ef8
--- /dev/null
+++ b/qt/python/mantidqt/project/workspacesaver.py
@@ -0,0 +1,63 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import os.path
+
+from mantid.api import AnalysisDataService as ADS, IMDEventWorkspace
+from mantid.dataobjects import MDHistoWorkspace
+from mantid import logger
+
+
+class WorkspaceSaver(object):
+    def __init__(self, directory):
+        """
+
+        :param directory:
+        """
+        self.directory = directory
+        self.output_list = []
+
+    def save_workspaces(self, workspaces_to_save=None):
+        """
+        Use the private method _get_workspaces_to_save to get a list of workspaces that are present in the ADS to save
+        to the directory that was passed at object creation time, it will also add each of them to the output_list
+        private instance variable on the WorkspaceSaver class.
+        :param workspaces_to_save: List of Strings; The workspaces that are to be saved to the project.
+        """
+
+        # Handle getting here and nothing has been given passed
+        if workspaces_to_save is None:
+            return
+
+        for workspace_name in workspaces_to_save:
+            # Get the workspace from the ADS
+            workspace = ADS.retrieve(workspace_name)
+            place_to_save_workspace = os.path.join(self.directory, workspace_name)
+
+            from mantid.simpleapi import SaveMD, SaveNexusProcessed
+
+            try:
+                if isinstance(workspace, MDHistoWorkspace) or isinstance(workspace, IMDEventWorkspace):
+                    # Save normally using SaveMD
+                    SaveMD(InputWorkspace=workspace_name, Filename=place_to_save_workspace + ".nxs")
+                else:
+                    # Save normally using SaveNexusProcessed
+                    SaveNexusProcessed(InputWorkspace=workspace_name, Filename=place_to_save_workspace + ".nxs")
+            except Exception:
+                logger.warning("Couldn't save workspace in project: " + workspace)
+
+            self.output_list.append(workspace_name)
+
+    def get_output_list(self):
+        """
+        Get the output_list
+        :return: List; String list of the workspaces that were saved
+        """
+        return self.output_list
diff --git a/qt/python/mantidqt/utils/asynchronous.py b/qt/python/mantidqt/utils/asynchronous.py
index 8ad6c59d0183d5fe98c3a7da27512d4a4cc09424..5d104be19e8ac0c34cf8573d4ca64829ae872509 100644
--- a/qt/python/mantidqt/utils/asynchronous.py
+++ b/qt/python/mantidqt/utils/asynchronous.py
@@ -110,6 +110,7 @@ class AsyncTaskResult(object):
 
     def __init__(self, elapsed_time):
         self.elapsed_time = elapsed_time
+        self.timestamp = time.ctime()
 
 
 class AsyncTaskSuccess(AsyncTaskResult):
diff --git a/qt/python/mantidqt/utils/qt/__init__.py b/qt/python/mantidqt/utils/qt/__init__.py
index dd491474b05b355d6c10ebae4b16cf2abfc6220d..9c1da5d4ae3db5f37ad8363827e29b57335b78f9 100644
--- a/qt/python/mantidqt/utils/qt/__init__.py
+++ b/qt/python/mantidqt/utils/qt/__init__.py
@@ -12,16 +12,16 @@
 from __future__ import absolute_import
 
 # stdlib modules
+import os.path as osp
 from contextlib import contextmanager
 from importlib import import_module
-import os.path as osp
 
 # 3rd-party modules
 from qtpy import QT_VERSION
-from qtpy.uic import loadUi, loadUiType
+from qtpy.QtGui import QKeySequence
 from qtpy.QtWidgets import QAction, QMenu
+from qtpy.uic import loadUi, loadUiType
 
-# local modules
 from ...icons import get_icon
 
 LIB_SUFFIX = 'qt' + QT_VERSION[0]
@@ -130,7 +130,12 @@ def create_action(parent, text, on_triggered=None, shortcut=None,
     if on_triggered is not None:
         action.triggered.connect(on_triggered)
     if shortcut is not None:
-        action.setShortcut(shortcut)
+        if isinstance(shortcut, tuple) or isinstance(shortcut, list):
+            qshortcuts = [QKeySequence(s) for s in shortcut]
+            action.setShortcuts(qshortcuts)
+        else:
+            action.setShortcut(shortcut)
+
         if shortcut_context is not None:
             action.setShortcutContext(shortcut_context)
     if icon_name is not None:
diff --git a/qt/python/mantidqt/utils/qt/test/__init__.py b/qt/python/mantidqt/utils/qt/test/__init__.py
index a4bb007c63352603aa13af818bdfb0d6ae73f7bc..7b87c75b12b99c51173a877e13518de4aea501f3 100644
--- a/qt/python/mantidqt/utils/qt/test/__init__.py
+++ b/qt/python/mantidqt/utils/qt/test/__init__.py
@@ -15,13 +15,10 @@ from __future__ import absolute_import
 from unittest import TestCase
 
 from qtpy.QtCore import Qt
-from qtpy.QtWidgets import QApplication
 
-from mantidqt.utils.qt.plugins import setup_library_paths
+from .application import get_application
 from .modal_tester import ModalTester
-
-# Hold on to QAPP reference to avoid garbage collection
-_QAPP = None
+from .gui_window_test import GuiWindowTest
 
 
 class GuiTest(TestCase):
@@ -33,10 +30,7 @@ class GuiTest(TestCase):
         """Prepare for test execution.
         Ensure that a (single copy of) QApplication has been created
         """
-        global _QAPP
-        if _QAPP is None:
-            setup_library_paths()
-            _QAPP = QApplication([cls.__name__])
+        get_application(cls.__name__)
 
 
 def select_item_in_tree(tree, item_label):
diff --git a/qt/python/mantidqt/utils/qt/test/application.py b/qt/python/mantidqt/utils/qt/test/application.py
new file mode 100644
index 0000000000000000000000000000000000000000..931ea74d5626b02856901f59dc6c627a3c433bc6
--- /dev/null
+++ b/qt/python/mantidqt/utils/qt/test/application.py
@@ -0,0 +1,30 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+# flake8: noqa
+from __future__ import absolute_import
+from qtpy.QtWidgets import QApplication
+
+from mantidqt.utils.qt.plugins import setup_library_paths
+
+# Hold on to QAPP reference to avoid garbage collection
+_QAPP = None
+
+
+def get_application(name=''):
+    """
+    Initialise and return the global application object
+    :param name: Optional application name
+    :return: Global appliction object
+    """
+    global _QAPP
+    if _QAPP is None:
+        setup_library_paths()
+        _QAPP = QApplication([name])
+    return _QAPP
diff --git a/qt/python/mantidqt/utils/qt/test/gui_test_runner.py b/qt/python/mantidqt/utils/qt/test/gui_test_runner.py
new file mode 100644
index 0000000000000000000000000000000000000000..47de8a408874ee40924a75c86c497df16358f101
--- /dev/null
+++ b/qt/python/mantidqt/utils/qt/test/gui_test_runner.py
@@ -0,0 +1,216 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import absolute_import, print_function
+
+import inspect
+import six
+import traceback
+
+from qtpy.QtCore import QTimer, QMetaObject, Qt
+from qtpy.QtWidgets import QWidget
+
+from mantidqt.utils.qt.test.application import get_application
+
+
+def split_qualified_name(qualified_name):
+    parts = qualified_name.split('.')
+    if len(parts) < 2:
+        raise RuntimeError('Qualified name must include name of the module in which it is defined,'
+                           ' found: {0}'.format(qualified_name))
+    module_name = '.'.join(parts[:-1])
+    name = parts[-1]
+    return module_name, name
+
+
+def create_widget(widget_path):
+    """
+    Imports a widget from a module in mantidqt
+    :param widget_path: A qualified name of a widget, ie mantidqt.mywidget.MyWidget
+    :return: The widget's class.
+    """
+    module_name, widget_name = split_qualified_name(widget_path)
+    m = __import__(module_name, fromlist=[widget_name])
+    widget_generator = getattr(m, widget_name)
+    return widget_generator()
+
+
+class ScriptRunner(object):
+    """
+    Runs a script that interacts with a widget (tests it).
+    If the script is a python generator then after each iteration controls returns
+    to the QApplication's event loop.
+    Generator scripts can yield a positive number. It is treated as the number of seconds
+    before the next iteration is called. During the wait time the event loop is running.
+    """
+    def __init__(self, script, widget=None, close_on_finish=True, pause=0, is_cli=False):
+        """
+        Initialise a runner.
+        :param script: The script to run.
+        :param widget: The widget to test.
+        :param close_on_finish: If true close the widget after the script has finished.
+        :param is_cli: If true the script is to be run from a command line tool. Exceptions are
+            treated slightly differently in this case.
+        """
+        app = get_application()
+        self.script = script
+        self.widget = widget
+        self.close_on_finish = close_on_finish
+        self.pause = pause
+        self.is_cli = is_cli
+        self.error = None
+        self.script_iter = [None]
+        self.pause_timer = QTimer(app)
+        self.pause_timer.setSingleShot(True)
+        self.script_timer = QTimer(app)
+
+    def run(self):
+        ret = run_script(self.script, self.widget)
+        if isinstance(ret, Exception):
+            raise ret
+        self.script_iter = [iter(ret) if inspect.isgenerator(ret) else None]
+        if self.pause != 0:
+            self.script_timer.setInterval(self.pause * 1000)
+        # Zero-timeout timer runs script_runner() between Qt events
+        self.script_timer.timeout.connect(self, Qt.QueuedConnection)
+        QMetaObject.invokeMethod(self.script_timer, 'start', Qt.QueuedConnection)
+
+    def __call__(self):
+        app = get_application()
+        if not self.pause_timer.isActive():
+            try:
+                script_iter = self.script_iter[-1]
+                if script_iter is None:
+                    if self.close_on_finish:
+                        app.closeAllWindows()
+                        app.exit()
+                    return
+                # Run test script until the next 'yield'
+                try:
+                    ret = next(script_iter)
+                except ValueError:
+                    return
+                while ret is not None:
+                    if inspect.isgenerator(ret):
+                        self.script_iter.append(ret)
+                        ret = None
+                    elif isinstance(ret, six.integer_types) or isinstance(ret, float):
+                        # Start non-blocking pause in seconds
+                        self.pause_timer.start(int(ret * 1000))
+                        ret = None
+                    else:
+                        ret = ret()
+            except StopIteration:
+                if len(self.script_iter) > 1:
+                    self.script_iter.pop()
+                else:
+                    self.script_iter = [None]
+                    self.script_timer.stop()
+                    if self.close_on_finish:
+                        app.closeAllWindows()
+                        app.exit(0)
+            except Exception as e:
+                self.script_iter = [None]
+                traceback.print_exc()
+                if self.close_on_finish:
+                    app.exit(1)
+                self.error = e
+
+
+def open_in_window(widget_or_name, script, attach_debugger=True, pause=0,
+                   close_on_finish=False, is_cli=False, in_workbench=False):
+
+    """
+    Displays a widget in a window.
+    :param widget_or_name: A widget to display.
+            1. If a string it's a qualified name of a widget, eg mantidqt.mywidget.MyWidget
+            2. If a callable it must take no arguments and return a QWidget
+            3. If a QWidget it's used directly.
+    :param script: A script to run after the widget is open.
+            1. If a string it's a qualified name of a test function that can be run after the
+            widget is created.
+            2. If a callable it's used directly.
+
+        The test function must have the signature:
+
+            def test(widget):
+                ...
+
+        where argument widget is an instance of the tested widget.
+        The test function can yield from time to time after which the widget can update itself.
+        This will make the test non-blocking and changes can be viewed as the script runs.
+        If the test yields an number it is interpreted as the number of seconds to wait
+        until the next step.
+        The test can yield a generator. In this case it will be iterated over until it stops
+        and the iterations of the main script continue.
+    :param attach_debugger: If true pause to let the user to attache a debugger before starting
+        application.
+    :param pause: A number of seconds to wait between the iterations.
+    :param close_on_finish: An option to close the widget after the script finishes.
+    :param is_cli: If true the script is to be run from a command line tool. Exceptions are
+        treated slightly differently in this case.
+    :param in_workbench: Set to True if the script will be run inside the workbench application.
+    """
+    if attach_debugger:
+        raw_input('Please attach the Debugger now if required. Press any key to continue')
+    app = get_application()
+    if widget_or_name is not None:
+        widget_name = 'Widget to test'
+        if isinstance(widget_or_name, six.string_types):
+            widget = create_widget(widget_or_name)
+            widget_name = widget_or_name
+        elif isinstance(widget_or_name, QWidget):
+            widget = widget_or_name
+        else:
+            widget = widget_or_name()
+        if hasattr(widget, 'setWindowTitle'):
+            widget.setWindowTitle(widget_name)
+        if widget is not None:
+            widget.show()
+    else:
+        widget = None
+
+    script_runner = None
+    if script is not None:
+        try:
+            script_runner = ScriptRunner(script, widget, close_on_finish=close_on_finish, is_cli=is_cli, pause=pause)
+            script_runner.run()
+        except Exception as e:
+            if not is_cli:
+                raise e
+
+    if not in_workbench:
+        ret = app.exec_()
+        if not is_cli and script_runner is not None and script_runner.error is not None:
+            raise script_runner.error
+    else:
+        ret = 0
+    return ret
+
+
+def run_script(script_or_name, widget):
+    """
+    Run a script passing the widget as an argument.
+    :param script_or_name: A callable, class or a name.
+    :param widget: A widget to interact with.
+    :return: Output of the script or an Exception object.
+    """
+    if isinstance(script_or_name, six.string_types):
+        module_name, fun_name = split_qualified_name(script_or_name)
+        m = __import__(module_name, fromlist=[fun_name])
+        fun = getattr(m, fun_name)
+    else:
+        fun = script_or_name
+    try:
+        if inspect.isclass(fun):
+            fun = fun()
+        return fun(widget)
+    except Exception as e:
+        traceback.print_exc()
+        return e
diff --git a/qt/python/mantidqt/utils/qt/test/gui_window_test.py b/qt/python/mantidqt/utils/qt/test/gui_window_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..613da5434c4b187d7e070d7c82233033c2bf87e9
--- /dev/null
+++ b/qt/python/mantidqt/utils/qt/test/gui_window_test.py
@@ -0,0 +1,180 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import print_function
+import inspect
+from unittest import TestCase
+
+from mantidqt.utils.qt.test.gui_test_runner import open_in_window
+from qtpy.QtWidgets import QPushButton, QMenu, QAction, QApplication
+from qtpy.QtCore import Qt, QMetaObject, QTime
+from qtpy.QtTest import QTest
+
+
+def trigger_action(action):
+    QMetaObject.invokeMethod(action, 'trigger', Qt.QueuedConnection)
+
+
+def find_action_with_text(widget, text):
+    for action in widget.findChildren(QAction):
+        if action.text() == text:
+            return action
+    raise RuntimeError("Couldn't find action with text \"{}\"".format(text))
+
+
+def drag_mouse(widget, from_pos, to_pos):
+    QTest.mousePress(widget, Qt.LeftButton, Qt.NoModifier, from_pos)
+    yield
+    QTest.mouseMove(widget, from_pos)
+    yield 0.1
+    QTest.mouseMove(widget, to_pos)
+    yield 0.1
+    QTest.mouseRelease(widget, Qt.LeftButton, Qt.NoModifier, to_pos)
+    yield 0.1
+
+
+class GuiTestBase(object):
+
+    def _call_test_method(self, w):
+        self.widget = w
+        if hasattr(self, self.call_method):
+            return getattr(self, self.call_method)()
+        if self.call_method != 'call':
+            raise RuntimeError("Test class has no method {}".format(self.call_method))
+
+    def wait_for(self, var_name):
+        var = getattr(self, var_name)
+        while var is None:
+            yield
+            var = getattr(self, var_name)
+
+    def wait_for_true(self, fun, timeout_sec=1.0):
+        t = QTime()
+        t.start()
+        timeout_millisec = int(timeout_sec * 1000)
+        while not fun():
+            if t.elapsed() > timeout_millisec:
+                raise StopIteration()
+            yield
+
+    def wait_for_modal(self):
+        return self.wait_for_true(self.get_active_modal_widget)
+
+    def wait_for_popup(self):
+        return self.wait_for_true(self.get_active_popup_widget)
+
+    def run_test(self, method='call', pause=0, close_on_finish=True, attach_debugger=False):
+        self.call_method = method
+        open_in_window(self.create_widget, self._call_test_method, attach_debugger=attach_debugger, pause=pause,
+                       close_on_finish=close_on_finish)
+
+    def get_child(self, child_class, name):
+        children = self.widget.findChildren(child_class, name)
+        if len(children) == 0:
+            raise RuntimeError("Widget doesn't have children of type {0} with name {1}.".format(child_class.__name__,
+                                                                                                name))
+        return children[0]
+
+    @staticmethod
+    def get_active_modal_widget():
+        return QApplication.activeModalWidget()
+
+    @staticmethod
+    def get_active_popup_widget():
+        return QApplication.activePopupWidget()
+
+    def get_menu(self, name):
+        return self.get_child(QMenu, name)
+
+    def get_action(self, name, get_menu=False):
+        action = self.get_child(QAction, name)
+        if not get_menu:
+            return action
+        menus = action.associatedWidgets()
+        if len(menus) == 0:
+            raise RuntimeError("QAction {} isn't associated with any menu".format(name))
+        return action, menus[0]
+
+    def trigger_action(self, name):
+        action, menu = self.get_action(name, get_menu=True)
+        if not menu.isVisible():
+            raise RuntimeError("Action {} isn't visible.".format(name))
+        QMetaObject.invokeMethod(action, 'trigger', Qt.QueuedConnection)
+        menu.close()
+
+    def hover_action(self, name):
+        action, menu = self.get_action(name, get_menu=True)
+        if not menu.isVisible():
+            raise RuntimeError("Action {} isn't visible.".format(name))
+        QMetaObject.invokeMethod(action, 'hover', Qt.QueuedConnection)
+
+    def get_button(self, name):
+        return self.get_child(QPushButton, name)
+
+    def click_button(self, name):
+        button = self.get_button(name)
+        QMetaObject.invokeMethod(button, 'click', Qt.QueuedConnection)
+
+
+def is_test_method(value):
+    if not (inspect.ismethod(value) or inspect.isfunction(value)):
+        return False
+    return value.__name__.startswith('test_')
+
+
+class GuiWindowTest(TestCase, GuiTestBase):
+
+    @classmethod
+    def make_test_wrapper(cls, wrapped_name):
+        def wrapper(self):
+            self.run_test(method=wrapped_name)
+        return wrapper
+
+    @classmethod
+    def setUpClass(cls):
+        cls.test_methods = []
+        cls.widget = None
+        for test in inspect.getmembers(cls, is_test_method):
+            name = test[0]
+            wrapped_name = '_' + name
+            setattr(cls, wrapped_name, test[1])
+            setattr(cls, name, cls.make_test_wrapper(wrapped_name))
+
+
+class MultiTestRunner(object):
+
+    def __init__(self, methods):
+        self.methods = methods
+
+    def __call__(self, w):
+        for method in self.methods:
+            yield method
+
+
+class WorkbenchGuiTest(GuiWindowTest):
+
+    @classmethod
+    def make_test_wrapper(cls, wrapped_name):
+        def wrapper(self):
+            if len(self.test_methods) == 0:
+                self.widget = self.create_widget()
+            self.test_methods.append(getattr(self, wrapped_name))
+        return wrapper
+
+    @classmethod
+    def tearDownClass(cls):
+        runner = MultiTestRunner(cls.test_methods)
+        open_in_window(cls.widget, runner, close_on_finish=True, attach_debugger=False, in_workbench=True)
+
+    def create_widget(self):
+        qapp = QApplication.instance()
+        for w in qapp.allWidgets():
+            if w.objectName() == "Mantid Workbench":
+                return w
+        return qapp.activeWindow()
diff --git a/qt/python/mantidqt/utils/qt/test/modal_tester.py b/qt/python/mantidqt/utils/qt/test/modal_tester.py
index bb54fd235afb776daafe7fb0a49eec05e89b92a7..ba6412453b9cb947fa244814789bb6f72bbc7963 100644
--- a/qt/python/mantidqt/utils/qt/test/modal_tester.py
+++ b/qt/python/mantidqt/utils/qt/test/modal_tester.py
@@ -67,7 +67,10 @@ class ModalTester(object):
             traceback.print_exc()
             self._qapp.exit(0)
         if self.widget is not None:
-            self.widget.exec_()
+            if hasattr(self.widget, 'exec_'):
+                self.widget.exec_()
+            else:
+                self.widget.show()
 
     def _idle(self):
         """
diff --git a/qt/python/mantidqt/utils/qt/test/run_test_app.py b/qt/python/mantidqt/utils/qt/test/run_test_app.py
index 33c18c121c8d0cb7b20edf899871f8da8673314a..b42f3f2e5d1af509c16c660cc0705ee807011fb2 100644
--- a/qt/python/mantidqt/utils/qt/test/run_test_app.py
+++ b/qt/python/mantidqt/utils/qt/test/run_test_app.py
@@ -24,111 +24,16 @@ Optional --script argument expects a name of a function which takes a widget as
 If specified the script is imported and run after the widget is created.
 
 """
-from __future__ import absolute_import, print_function
-
+import argparse
 import sys
-import traceback
-
-from qtpy.QtCore import QTimer
-from qtpy.QtWidgets import QApplication
-
-from mantidqt.utils.qt.plugins import setup_library_paths
-
-
-def split_qualified_name(qualified_name):
-    parts = qualified_name.split('.')
-    if len(parts) < 2:
-        raise RuntimeError('Qualified name must include name of the module in which it is defined,'
-                           ' found: {0}'.format(qualified_name))
-    module_name = '.'.join(parts[:-1])
-    name = parts[-1]
-    return module_name, name
-
-
-def create_widget(widget_path):
-    """
-    Imports a widget from a module in mantidqt
-    :param widget_path: A qualified name of a widget, ie mantidqt.mywidget.MyWidget
-    :return: The widget's class.
-    """
-    module_name, widget_name = split_qualified_name(widget_path)
-    m = __import__(module_name, fromlist=[widget_name])
-    widget_generator = getattr(m, widget_name)
-    return widget_generator()
-
-
-def open_in_window(widget_name, script):
-    """
-    Displays a widget in a window.
-    :param widget_name:  A qualified name of a widget, ie mantidqt.mywidget.MyWidget
-    :param script: A qualified name of a test function that can be run after the
-        widget is created. The test function must have the signature:
-
-            def test(widget):
-                ...
-
-        where argument widget is an instance of the tested widget.
-        The test function can yield from time to time after which the widget can update itself.
-        This will make the test non-blocking and changes can be viewed as the script runs.
-        If the test yields an integer it is interpreted as the number of seconds to wait
-        until the next step.
-    """
-    raw_input('Please attach the Debugger now if required. Press any key to continue')
-    setup_library_paths()
-    app = QApplication([""])
-    w = create_widget(widget_name)
-    w.setWindowTitle(widget_name)
-    w.show()
-
-    if script is not None:
-        try:
-            # If script is a generator script_iter allows non-blocking
-            # test execution
-            script_iter = iter(run_script(script, w))
-            pause_timer = QTimer()
-            pause_timer.setSingleShot(True)
-
-            def idle():
-                if not pause_timer.isActive():
-                    try:
-                        # Run test script until the next 'yield'
-                        pause_sec = script_iter.next()
-                        if pause_sec is not None:
-                            # Start non-blocking pause in seconds
-                            pause_timer.start(int(pause_sec * 1000))
-                    except StopIteration:
-                        pass
-                    except:
-                        traceback.print_exc()
-            timer = QTimer()
-            # Zero-timeout timer runs idle() between Qt events
-            timer.timeout.connect(idle)
-            timer.start()
-        except:
-            pass
-
-    sys.exit(app.exec_())
-
-
-def run_script(script_name, widget):
-    module_name, fun_name = split_qualified_name(script_name)
-    m = __import__(module_name, fromlist=[fun_name])
-    fun = getattr(m, fun_name)
-    try:
-        return fun(widget)
-    except:
-        traceback.print_exc()
-
-
-if __name__ == "__main__":
-    import argparse
-
-    parser = argparse.ArgumentParser()
-    parser.add_argument("widget", help="A qualified name of a widget to open for testing. The name must contain the "
-                                       "python module where the widget is defined, eg mypackage.mymodule.MyWidget")
-    parser.add_argument("--script", help="A qualified name of a python function to run to test the widget."
-                                         " The function must take a single argument - the widget."
-                                         " The name must contain the python module where the function is defined,"
-                                         " eg somepackage.somemodule.test_my_widget")
-    args = parser.parse_args()
-    open_in_window(args.widget, args.script)
+from gui_test_runner import open_in_window
+
+parser = argparse.ArgumentParser()
+parser.add_argument("widget", help="A qualified name of a widget to open for testing. The name must contain the "
+                                   "python module where the widget is defined, eg mypackage.mymodule.MyWidget")
+parser.add_argument("--script", help="A qualified name of a python function to run to test the widget."
+                                     " The function must take a single argument - the widget."
+                                     " The name must contain the python module where the function is defined,"
+                                     " eg somepackage.somemodule.test_my_widget")
+args = parser.parse_args()
+sys.exit(open_in_window(args.widget, args.script, is_cli=True))
diff --git a/qt/python/mantidqt/utils/test/test_qt_utils.py b/qt/python/mantidqt/utils/test/test_qt_utils.py
index b526580ec8a3d3fdded6201f9c4978b214f51e63..87ae0646f52cb54ffbe5c98c445880b1cd232bda 100644
--- a/qt/python/mantidqt/utils/test/test_qt_utils.py
+++ b/qt/python/mantidqt/utils/test/test_qt_utils.py
@@ -14,8 +14,10 @@ import unittest
 
 from qtpy.QtCore import QObject, Qt, Slot
 from qtpy.QtWidgets import QAction, QMenu, QToolBar
+
 try:
     from qtpy.QtCore import SIGNAL
+
     NEW_STYLE_SIGNAL = False
 except ImportError:
     NEW_STYLE_SIGNAL = True
@@ -29,6 +31,7 @@ class CreateActionTest(GuiTest):
     def test_parent_and_name_only_required(self):
         class Parent(QObject):
             pass
+
         parent = Parent()
         action = create_action(parent, "Test Action")
         self.assertTrue(isinstance(action, QAction))
@@ -44,6 +47,7 @@ class CreateActionTest(GuiTest):
             @Slot()
             def test_slot(self):
                 pass
+
         recv = Receiver()
         action = create_action(None, "Test Action", on_triggered=recv.test_slot)
         if NEW_STYLE_SIGNAL:
@@ -55,6 +59,12 @@ class CreateActionTest(GuiTest):
         action = create_action(None, "Test Action", shortcut="Ctrl+S")
         self.assertEqual("Ctrl+S", action.shortcut())
 
+    def test_multiple_shortcuts_are_set_if_given(self):
+        expected_shortcuts = ("Ctrl+S", "Ctrl+W")
+        action = create_action(None, "Test Action", shortcut=expected_shortcuts)
+        for expected, actual in zip(expected_shortcuts, action.shortcuts()):
+            self.assertEqual(expected, actual.toString())
+
     def test_shortcut_context_used_if_shortcut_given(self):
         action = create_action(None, "Test Action", shortcut="Ctrl+S",
                                shortcut_context=Qt.ApplicationShortcut)
diff --git a/qt/python/mantidqt/utils/test/test_writetosignal.py b/qt/python/mantidqt/utils/test/test_writetosignal.py
index 57d52fa701d12aae26006f3d79d1c3fabc2f680a..86044469e66a9becb792a1ed288acc62d2a700df 100644
--- a/qt/python/mantidqt/utils/test/test_writetosignal.py
+++ b/qt/python/mantidqt/utils/test/test_writetosignal.py
@@ -9,14 +9,12 @@
 #
 from __future__ import (absolute_import)
 
-# std imports
 import unittest
-import sys
 
-# 3rdparty
+import sys
+from mock import patch
 from qtpy.QtCore import QCoreApplication, QObject
 
-# local imports
 from mantidqt.utils.qt.test import GuiTest
 from mantidqt.utils.writetosignal import WriteToSignal
 
@@ -29,15 +27,35 @@ class Receiver(QObject):
 
 
 class WriteToSignalTest(GuiTest):
+    @classmethod
+    def setUpClass(cls):
+        if not hasattr(sys.stdout, "fileno"):
+            # if not present in the test stdout, then add it as an
+            # attribute so that mock can replace it later.
+            sys.stdout.fileno = None
+
+    def test_run_with_output_present(self):
+        with patch("sys.stdout.fileno", return_value=10) as mock_fileno:
+            writer = WriteToSignal(sys.stdout)
+            mock_fileno.assert_called_once_with()
+            self.assertEqual(writer._original_out, sys.stdout)
+
+    def test_run_without_output_present(self):
+        with patch("sys.stdout.fileno", return_value=-1) as mock_fileno:
+            writer = WriteToSignal(sys.stdout)
+            mock_fileno.assert_called_once_with()
+            self.assertEqual(writer._original_out, None)
 
     def test_connected_receiver_receives_text(self):
-        recv = Receiver()
-        writer = WriteToSignal(sys.stdout)
-        writer.sig_write_received.connect(recv.capture_text)
-        txt = "I expect to see this"
-        writer.write(txt)
-        QCoreApplication.processEvents()
-        self.assertEqual(txt, recv.captured_txt)
+        with patch("sys.stdout.fileno", return_value=1) as mock_fileno:
+            recv = Receiver()
+            writer = WriteToSignal(sys.stdout)
+            writer.sig_write_received.connect(recv.capture_text)
+            txt = "I expect to see this"
+            writer.write(txt)
+            QCoreApplication.processEvents()
+            self.assertEqual(txt, recv.captured_txt)
+            mock_fileno.assert_called_once_with()
 
 
 if __name__ == "__main__":
diff --git a/qt/python/mantidqt/utils/writetosignal.py b/qt/python/mantidqt/utils/writetosignal.py
index 35988bc9c6629c8310dbc797d610b2e0885d2a85..3cca96e46952daac4b7b2aab8d8f971c0378e773 100644
--- a/qt/python/mantidqt/utils/writetosignal.py
+++ b/qt/python/mantidqt/utils/writetosignal.py
@@ -9,9 +9,6 @@
 #
 from __future__ import (absolute_import)
 
-# std imports
-
-# 3rdparty imports
 from qtpy.QtCore import QObject, Signal
 
 
@@ -20,12 +17,17 @@ class WriteToSignal(QObject):
     used to transform write requests to
     Qt-signals. Mainly used to communicate
     stdout/stderr across threads"""
-    def __init__(self, original_out):
-        QObject.__init__(self)
-        self.__original_out = original_out
 
     sig_write_received = Signal(str)
 
+    def __init__(self, original_out):
+        QObject.__init__(self)
+        # If the file descriptor of the stream is < 0 then we are running in a no-external-console mode
+        if original_out.fileno() < 0:
+            self._original_out = None
+        else:
+            self._original_out = original_out
+
     def closed(self):
         return False
 
@@ -36,7 +38,13 @@ class WriteToSignal(QObject):
         return False
 
     def write(self, txt):
-        # write to the console
-        self.__original_out.write(txt)
-        # emit the signal which will write to logging
+        if self._original_out:
+            try:
+                self._original_out.write(txt)
+            except IOError as e:
+                self.sig_write_received.emit("Error: Unable to write to the console of the process.\n"
+                                             "This error is not related to your script's execution.\n"
+                                             "Original error: {}\n\n".format(str(e)))
+
+        # always write to the message log
         self.sig_write_received.emit(txt)
diff --git a/qt/python/mantidqt/widgets/algorithmprogress/dialog_presenter.py b/qt/python/mantidqt/widgets/algorithmprogress/dialog_presenter.py
index c85910564a35eefd3a21962a9bc449de2841b60b..8ec1f7309fd8ec4495f9d219303cb7f6697e85b1 100644
--- a/qt/python/mantidqt/widgets/algorithmprogress/dialog_presenter.py
+++ b/qt/python/mantidqt/widgets/algorithmprogress/dialog_presenter.py
@@ -13,9 +13,13 @@ class AlgorithmProgressDialogPresenter(AlgorithmProgressPresenterBase):
     """
     Presents progress reports on algorithms.
     """
+
     def __init__(self, view, model):
         super(AlgorithmProgressDialogPresenter, self).__init__()
-        view.close_button.clicked.connect(self.close)
+        # connect the close button to the closeEvent of the window
+        # so that pressing the X button, and pressing `Close` go through
+        # the same routine, and properly call the presenter's close()
+        view.close_button.clicked.connect(view.close)
         self.view = view
         self.model = model
         self.model.add_presenter(self)
@@ -60,7 +64,7 @@ class AlgorithmProgressDialogPresenter(AlgorithmProgressPresenterBase):
         """
         self.model.remove_presenter(self)
         self.progress_bars.clear()
-        self.view.close()
+        self.view.parent().clear_dialog()
 
     def cancel_algorithm(self, algorithm_id):
         """
diff --git a/qt/python/mantidqt/widgets/algorithmprogress/dialog_widget.py b/qt/python/mantidqt/widgets/algorithmprogress/dialog_widget.py
index 54af4137b151d504f1c313a278d87cd5815d5b6d..fcfc7beb315d567686d4d7e0352ef822b7044252 100644
--- a/qt/python/mantidqt/widgets/algorithmprogress/dialog_widget.py
+++ b/qt/python/mantidqt/widgets/algorithmprogress/dialog_widget.py
@@ -19,6 +19,7 @@ class CancelButton(QPushButton):
     """
     Button that cancels an algorithm
     """
+
     def __init__(self, presenter, algorithm_id):
         """
         Init an instance
@@ -38,6 +39,7 @@ class AlgorithmMonitorDialog(QDialog):
     """
     Displays progress of all running algorithms.
     """
+
     def __init__(self, parent, model):
         super(AlgorithmMonitorDialog, self).__init__(parent)
         self.tree = QTreeWidget(self)
@@ -67,6 +69,17 @@ class AlgorithmMonitorDialog(QDialog):
         self.presenter = AlgorithmProgressDialogPresenter(self, model)
         self.presenter.update_gui()
 
+    def closeEvent(self, event):
+        """
+        Funnel the closeEvent, triggered when the user presses X,
+        through the same routine as the `Close` button
+        :param args:
+        :return:
+        """
+        self.presenter.close()
+        self.deleteLater()
+        event.accept()
+
     def update(self, data):
         """
         Update the gui elements.
diff --git a/qt/python/mantidqt/widgets/algorithmprogress/model.py b/qt/python/mantidqt/widgets/algorithmprogress/model.py
index f4d20e35b7df65735543352973ec8aadd6e92ac1..5a17cf390e3f0f640444a7cde20aab29fd76e519 100644
--- a/qt/python/mantidqt/widgets/algorithmprogress/model.py
+++ b/qt/python/mantidqt/widgets/algorithmprogress/model.py
@@ -124,6 +124,6 @@ class AlgorithmProgressModel(AlgorithmObserver):
         for observer in self.progress_observers:
             properties = []
             for prop in observer.properties():
-                properties.append([prop.name, str(prop.value), 'Default' if prop.isDefault else ''])
+                properties.append([prop.name, str(prop.valueAsStr), 'Default' if prop.isDefault else ''])
             algorithm_data.append((observer.name(), observer.algorithm, properties))
         return algorithm_data
diff --git a/qt/python/mantidqt/widgets/algorithmprogress/widget.py b/qt/python/mantidqt/widgets/algorithmprogress/widget.py
index 78ddfbdc7013e653a27c63e65d2a5d319d8a750b..c6d74b6ad247bb134c5e21cb2b0e1d05b5e8fcad 100644
--- a/qt/python/mantidqt/widgets/algorithmprogress/widget.py
+++ b/qt/python/mantidqt/widgets/algorithmprogress/widget.py
@@ -17,6 +17,7 @@ class AlgorithmProgressWidget(QWidget):
     """
     Widget consisting of a progress bar and a button.
     """
+
     def __init__(self, parent=None):
         super(AlgorithmProgressWidget, self).__init__(parent)
         self.progress_bar = None
@@ -27,6 +28,7 @@ class AlgorithmProgressWidget(QWidget):
         self.layout.addWidget(self.details_button)
         self.setLayout(self.layout)
         self.presenter = AlgorithmProgressPresenter(self)
+        self.algorithm_monitor_dialog = None
 
     def show_progress_bar(self):
         if self.progress_bar is None:
@@ -43,5 +45,11 @@ class AlgorithmProgressWidget(QWidget):
             self.progress_bar = None
 
     def show_dialog(self):
-        dialog = AlgorithmMonitorDialog(self, self.presenter.model)
-        dialog.show()
+        if not self.algorithm_monitor_dialog:
+            self.algorithm_monitor_dialog = AlgorithmMonitorDialog(self, self.presenter.model)
+            self.algorithm_monitor_dialog.show()
+        else:
+            self.algorithm_monitor_dialog.setFocus()
+
+    def clear_dialog(self):
+        self.algorithm_monitor_dialog = None
diff --git a/qt/python/mantidqt/widgets/codeeditor/interpreter.py b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
index 98a8b559f921c9520ae821cbeb5f61ba8a7ec979..67c7b229c20d0b25da618de61335b626a10d28a7 100644
--- a/qt/python/mantidqt/widgets/codeeditor/interpreter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
@@ -27,7 +27,7 @@ from mantidqt.io import open_a_file_dialog
 
 # Status messages
 IDLE_STATUS_MSG = "Status: Idle."
-LAST_JOB_MSG_TEMPLATE = "Last job completed {} in {:.3f}s"
+LAST_JOB_MSG_TEMPLATE = "Last job completed {} at {} in {:.3f}s"
 RUNNING_STATUS_MSG = "Status: Running"
 
 # Editor
@@ -217,10 +217,10 @@ class PythonFileInterpreterPresenter(QObject):
     def req_execute_async(self):
         if self.is_executing:
             return
-        self.is_executing = True
         code_str, self._code_start_offset = self._get_code_for_execution()
         if not code_str:
             return
+        self.is_executing = True
         self.view.set_editor_readonly(True)
         self.view.set_status_message(RUNNING_STATUS_MSG)
         return self.model.execute_async(code_str, self.view.filename)
@@ -237,7 +237,7 @@ class PythonFileInterpreterPresenter(QObject):
 
     def _on_exec_success(self, task_result):
         self.view.editor.updateCompletionAPI(self.model.generate_calltips())
-        self._finish(success=True, elapsed_time=task_result.elapsed_time)
+        self._finish(success=True, task_result=task_result)
 
     def _on_exec_error(self, task_error):
         exc_type, exc_value, exc_stack = task_error.exc_type, task_error.exc_value, \
@@ -249,21 +249,21 @@ class PythonFileInterpreterPresenter(QObject):
             lineno = exc_stack[-1][1] + self._code_start_offset
         else:
             lineno = -1
-        sys.stderr.write(self._error_formatter.format(exc_type, exc_value, exc_stack) + '\n')
+        sys.stderr.write(self._error_formatter.format(exc_type, exc_value, exc_stack) + os.linesep)
         self.view.editor.updateProgressMarker(lineno, True)
-        self._finish(success=False, elapsed_time=task_error.elapsed_time)
+        self._finish(success=False, task_result=task_error)
 
-    def _finish(self, success, elapsed_time):
+    def _finish(self, success, task_result):
         status = 'successfully' if success else 'with errors'
-        self.view.set_status_message(self._create_status_msg(status,
-                                                             elapsed_time))
+        status_message = self._create_status_msg(status, task_result.timestamp,
+                                                 task_result.elapsed_time)
+        self.view.set_status_message(status_message)
         self.view.set_editor_readonly(False)
         self.is_executing = False
 
-    def _create_status_msg(self, status, elapsed_time):
+    def _create_status_msg(self, status, timestamp, elapsed_time):
         return IDLE_STATUS_MSG + ' ' + \
-               LAST_JOB_MSG_TEMPLATE.format(status,
-                                            elapsed_time)
+               LAST_JOB_MSG_TEMPLATE.format(status, timestamp, elapsed_time)
 
     def _on_progress_update(self, lineno):
         """Update progress on the view taking into account if a selection of code is
diff --git a/scripts/wish/__init__.py b/qt/python/mantidqt/widgets/common/__init__.py
similarity index 100%
rename from scripts/wish/__init__.py
rename to qt/python/mantidqt/widgets/common/__init__.py
diff --git a/qt/python/mantidqt/widgets/common/observing_presenter.py b/qt/python/mantidqt/widgets/common/observing_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..b71c0e8a3893d5cea023b295647e365a1eca5588
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/observing_presenter.py
@@ -0,0 +1,49 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+
+from __future__ import (absolute_import, division, print_function)
+
+
+class ObservingPresenter(object):
+    """
+    This class provides some common functions for classes that need to be observable.
+    It is not a GUI class, and if used, should be inherited by the presenter.
+    It is designed to be used with a view that inherits `ObservingView`.
+
+    The presenter that is inheriting this must initialise a model and a view,
+    that implement the methods required in the functions below.
+    """
+
+    def clear_observer(self):
+        """
+        If the observer is not cleared here then the C++ object is never freed,
+        and observers keep getting created, and triggering on ADS events.
+
+        This method is called from the view's close_later to ensure that
+        observer are deleted both when the workspace is deleted and when
+        the view window is closed manually (via the X)
+        """
+        self.ads_observer = None
+
+    def close(self, workspace_name):
+        if self.model.workspace_equals(workspace_name):
+            # if the observer is not cleared here then the C++ object is never freed,
+            # and observers keep getting created, and triggering on ADS events
+            self.ads_observer = None
+            self.view.emit_close()
+
+    def force_close(self):
+        self.ads_observer = None
+        self.view.emit_close()
+
+    def replace_workspace(self, workspace_name, workspace):
+        raise NotImplementedError("This method must be overridden.")
+
+    def rename_workspace(self, old_name, new_name):
+        if self.model.workspace_equals(old_name):
+            self.view.emit_rename(new_name)
diff --git a/qt/python/mantidqt/widgets/common/observing_view.py b/qt/python/mantidqt/widgets/common/observing_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b549c6a8b41bb9bf5912e9226b5c2bf3c792299
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/observing_view.py
@@ -0,0 +1,54 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+
+from __future__ import (absolute_import, division, print_function)
+
+
+class ObservingView(object):
+    """
+    This class provides some common functions needed across views observing the ADS.
+    It runs the close_signal so that the view is closed from the GUI thread,
+    and ensures that the closeEvent will clear the observer to prevent a memory leak.
+
+    It is designed to be used with a presenter that inherits `ObservingPresenter`.
+
+    If this class is inherited a `close_signal` and a `rename_signal` must be declared.
+
+    It is not possible to do that here, as this is not a QObject, however it was
+    attempted to do the declaration here, but the signals don't seem to work
+    through inheritance.
+
+    This class shouldn't inherit from a QObject/QWidget, otherwise the closeEvent
+    doesn't replace the closeEvent of the view that is inheriting this. This also
+    makes it easily testable, as the GUI library isn't pulled in.
+    """
+
+    TITLE_STRING = "{} - Mantid"
+
+    def emit_close(self):
+        """
+        Emits a close signal to the main GUI thread that triggers the built-in close method.
+
+        This function can be called from outside the main GUI thread. It is currently
+        used to close the window when the relevant workspace is deleted.
+
+        When the signal is emitted, the execution returns to the GUI thread, and thus
+        GUI code can be executed.
+        """
+        self.close_signal.emit()
+
+    def closeEvent(self, event):
+        # This clear prevents a leak when the window is closed from X by the user
+        self.presenter.clear_observer()
+        event.accept()
+
+    def emit_rename(self, new_name):
+        self.rename_signal.emit(new_name)
+
+    def _rename(self, new_name):
+        self.setWindowTitle(self.TITLE_STRING.format(new_name))
diff --git a/qt/python/mantidqt/widgets/common/table_copying.py b/qt/python/mantidqt/widgets/common/table_copying.py
new file mode 100644
index 0000000000000000000000000000000000000000..8eb2f0587e6f3584749672ed736b87b910da8a38
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/table_copying.py
@@ -0,0 +1,156 @@
+from __future__ import (absolute_import, division, print_function)
+
+from qtpy import QtGui
+from qtpy.QtCore import QPoint
+from qtpy.QtGui import QCursor, QFont, QFontMetrics
+from qtpy.QtWidgets import (QMessageBox, QToolTip)
+
+NO_SELECTION_MESSAGE = "No selection"
+COPY_SUCCESSFUL_MESSAGE = "Copy Successful"
+
+"""
+This module contains the common copying functionality between
+the MatrixWorkspaceDisplay and the TableWorkspaceDisplay.
+"""
+
+
+def copy_spectrum_values(table, ws_read):
+    """
+    Copies the values selected by the user to the system's clipboard
+
+    :param table: Table from which the selection will be read
+    :param ws_read: The workspace read function, that is used to access the data directly
+    """
+    selection_model = table.selectionModel()
+    if not selection_model.hasSelection():
+        show_no_selection_to_copy_toast()
+        return
+    selected_rows = selection_model.selectedRows()  # type: list
+    row_data = []
+
+    for index in selected_rows:
+        row = index.row()
+        data = "\t".join(map(str, ws_read(row)))
+
+        row_data.append(data)
+
+    copy_to_clipboard("\n".join(row_data))
+    show_successful_copy_toast()
+
+
+def copy_bin_values(table, ws_read, num_rows):
+    """
+    Copies the values selected by the user to the system's clipboard
+
+    :param table: Table from which the selection will be read
+    :param ws_read: The workspace read function, that is used to access the data directly
+    :param num_rows: The number of rows in the column
+    """
+    selection_model = table.selectionModel()
+    if not selection_model.hasSelection():
+        show_no_selection_to_copy_toast()
+        return
+    selected_columns = selection_model.selectedColumns()  # type: list
+
+    # Qt gives back a QModelIndex, we need to extract the column from it
+    column_data = []
+    for index in selected_columns:
+        column = index.column()
+        data = [str(ws_read(row)[column]) for row in range(num_rows)]
+        column_data.append(data)
+
+    all_string_rows = []
+    for i in range(num_rows):
+        # Appends ONE value from each COLUMN, this is because the final string is being built vertically
+        # the noqa disables a 'data' variable redefined warning
+        all_string_rows.append("\t".join([data[i] for data in column_data]))  # noqa: F812
+
+    # Finally all rows are joined together with a new line at the end of each row
+    final_string = "\n".join(all_string_rows)
+    copy_to_clipboard(final_string)
+    show_successful_copy_toast()
+
+
+def copy_cells(table):
+    """
+    :type table: QTableView
+    :param table: The table from which the data will be copied.
+    :return:
+    """
+    selectionModel = table.selectionModel()
+    if not selectionModel.hasSelection():
+        show_no_selection_to_copy_toast()
+        return
+
+    selection = selectionModel.selection()
+    selectionRange = selection.first()
+
+    top = selectionRange.top()
+    bottom = selectionRange.bottom()
+    left = selectionRange.left()
+    right = selectionRange.right()
+
+    data = []
+    index = selectionModel.currentIndex()
+    for i in range(top, bottom + 1):
+        for j in range(left, right):
+            data.append(str(index.sibling(i, j).data()))
+            data.append("\t")
+        data.append(str(index.sibling(i, right).data()))
+        data.append("\n")
+
+    # strip the string to remove the trailing new line
+    copy_to_clipboard("".join(data).strip())
+    show_successful_copy_toast()
+
+
+def keypress_copy(table, view, ws_read, num_rows):
+    selectionModel = table.selectionModel()
+    if not selectionModel.hasSelection():
+        show_no_selection_to_copy_toast()
+        return
+
+    if len(selectionModel.selectedRows()) > 0:
+        copy_spectrum_values(table, ws_read)
+    elif len(selectionModel.selectedColumns()) > 0:
+        copy_bin_values(table, ws_read, num_rows)
+    else:
+        copy_cells(table)
+
+
+def show_mouse_toast(message):
+    # Creates a text with empty space to get the height of the rendered text - this is used
+    # to provide the same offset for the tooltip, scaled relative to the current resolution and zoom.
+    font_metrics = QFontMetrics(QFont(" "))
+    # The height itself is divided by 2 just to reduce the offset so that the tooltip is
+    # reasonably position relative to the cursor
+    QToolTip.showText(QCursor.pos() + QPoint(font_metrics.height() / 2, 0), message)
+
+
+def copy_to_clipboard(data):
+    """
+    Uses the QGuiApplication to copy to the system clipboard.
+
+    :type data: str
+    :param data: The data that will be copied to the clipboard
+    :return:
+    """
+    cb = QtGui.QGuiApplication.clipboard()
+    cb.setText(data, mode=cb.Clipboard)
+
+
+def ask_confirmation(self, message, title="Mantid Workbench"):
+    """
+    :param message:
+    :return:
+    """
+    reply = QMessageBox.question(self, title, message, QMessageBox.Yes, QMessageBox.No)
+    return True if reply == QMessageBox.Yes else False
+
+
+def show_no_selection_to_copy_toast():
+    show_mouse_toast(NO_SELECTION_MESSAGE)
+
+
+def show_successful_copy_toast():
+    show_mouse_toast(COPY_SUCCESSFUL_MESSAGE)
diff --git a/qt/python/mantidqt/widgets/common/test/__init__.py b/qt/python/mantidqt/widgets/common/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/qt/python/mantidqt/widgets/common/test/test_observing_presenter.py b/qt/python/mantidqt/widgets/common/test/test_observing_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..16aaf163512f939ab90158274d523e1201ecf755
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/test/test_observing_presenter.py
@@ -0,0 +1,63 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from mantidqt.widgets.common.test_mocks.mock_observing import MockObservingPresenter
+
+
+def with_presenter(workspaces_are_equal=True):
+    def func_wrapper(func):
+        def wrapper(self, *args, **kwargs):
+            presenter = MockObservingPresenter(workspaces_are_equal)
+            return func(self, presenter, *args, **kwargs)
+
+        return wrapper
+
+    return func_wrapper
+
+
+class ObservingPresenterTest(unittest.TestCase):
+    @with_presenter()
+    def test_close(self, presenter):
+        mock_name = "dawn"
+        presenter.close(mock_name)
+        presenter.model.workspace_equals.assert_called_once_with(mock_name)
+        presenter.view.close_signal.emit.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_presenter(workspaces_are_equal=False)
+    def test_not_closing_with_invalid_name(self, presenter):
+        mock_name = "dawn"
+        presenter.close(mock_name)
+        presenter.model.workspace_equals.assert_called_once_with(mock_name)
+        self.assertEqual(0, presenter.view.close_signal.emit.call_count)
+        self.assertIsNotNone(presenter.ads_observer)
+
+    @with_presenter(workspaces_are_equal=False)
+    def test_force_close(self, presenter):
+        presenter.force_close()
+        presenter.view.close_signal.emit.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_presenter(workspaces_are_equal=False)
+    def test_replace_workspace_not_implemented(self, presenter):
+        self.assertRaises(NotImplementedError, presenter.replace_workspace, "", "")
+
+    @with_presenter()
+    def test_rename_workspace(self, presenter):
+        new_name = "xax"
+        presenter.rename_workspace("", new_name)
+        presenter.view.rename_signal.emit.assert_called_once_with(new_name)
+
+    @with_presenter(workspaces_are_equal=False)
+    def test_not_renaming_workspace_with_invalid_name(self, presenter):
+        new_name = "xax"
+        presenter.rename_workspace("", new_name)
+        self.assertEqual(0, presenter.view.rename_signal.emit.call_count)
diff --git a/qt/python/mantidqt/widgets/common/test/test_observing_view.py b/qt/python/mantidqt/widgets/common/test/test_observing_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..58efc1447b5517c2fe6b2077907f5a5d08e0beb0
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/test/test_observing_view.py
@@ -0,0 +1,39 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from mantidqt.widgets.common.test_mocks.mock_observing import MockObservingView
+from mantidqt.widgets.common.test_mocks.mock_qt import MockQtEvent
+
+
+class ObservingViewTest(unittest.TestCase):
+
+    def setUp(self):
+        self.view = MockObservingView(None)
+
+    def test_emit_close(self):
+        self.view.emit_close()
+        self.view.close_signal.emit.assert_called_once_with()
+
+    def test_closeEvent(self):
+        mock_event = MockQtEvent()
+        self.view.closeEvent(mock_event)
+        self.view.presenter.clear_observer.assert_called_once_with()
+        mock_event.accept.assert_called_once_with()
+
+    def test_rename(self):
+        new_name = "123edsad"
+        self.view.emit_rename(new_name)
+        self.view.rename_signal.emit.assert_called_once_with(new_name)
+
+    def test_rename_action(self):
+        new_name = "123edsad"
+        self.view._rename(new_name)
+        self.view.setWindowTitle.assert_called_once_with(self.view.TITLE_STRING.format(new_name))
diff --git a/qt/python/mantidqt/widgets/common/test/test_workspacedisplay_ads_observer.py b/qt/python/mantidqt/widgets/common/test/test_workspacedisplay_ads_observer.py
new file mode 100644
index 0000000000000000000000000000000000000000..989537a345c445d950df253e683e20660e44d2fa
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/test/test_workspacedisplay_ads_observer.py
@@ -0,0 +1,45 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from mock import Mock
+
+from mantidqt.widgets.common.workspacedisplay_ads_observer import WorkspaceDisplayADSObserver
+
+
+class MockWorkspaceDisplay:
+    def __init__(self):
+        self.close = Mock()
+        self.force_close = Mock()
+        self.replace_workspace = Mock()
+
+
+class WorkspaceDisplayADSObserverTest(unittest.TestCase):
+    def test_clearHandle(self):
+        mock_wsd = MockWorkspaceDisplay()
+        observer = WorkspaceDisplayADSObserver(mock_wsd)
+        observer.clearHandle()
+        mock_wsd.force_close.assert_called_once_with()
+
+    def test_deleteHandle(self):
+        mock_wsd = MockWorkspaceDisplay()
+        observer = WorkspaceDisplayADSObserver(mock_wsd)
+        expected_name = "adad"
+        observer.deleteHandle(expected_name, None)
+        mock_wsd.close.assert_called_once_with(expected_name)
+
+    def test_replaceHandle(self):
+        mock_wsd = MockWorkspaceDisplay()
+        observer = WorkspaceDisplayADSObserver(mock_wsd)
+
+        expected_name = "a"
+        expected_parameter = 444555.158
+        observer.replaceHandle(expected_name, expected_parameter)
+        mock_wsd.replace_workspace.assert_called_once_with(expected_name, expected_parameter)
diff --git a/qt/python/mantidqt/widgets/common/test_mocks/__init__.py b/qt/python/mantidqt/widgets/common/test_mocks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2542dc4ea67a9fb17d7f18f017e39f900655e23d
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/test_mocks/__init__.py
@@ -0,0 +1,7 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py b/qt/python/mantidqt/widgets/common/test_mocks/mock_mantid.py
similarity index 61%
rename from qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py
rename to qt/python/mantidqt/widgets/common/test_mocks/mock_mantid.py
index 30cc8e48c8d10b13489984c2edb7137a233bacd2..d7ece59a1304594e160156ab7c96146f7e6fed30 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/matrixworkspacedisplay_common.py
+++ b/qt/python/mantidqt/widgets/common/test_mocks/mock_mantid.py
@@ -5,44 +5,14 @@
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
 #  This file is part of the mantid workbench.
-#
-#
 from __future__ import (absolute_import, division, print_function)
 
 from mock import Mock
 
-from mantidqt.widgets.matrixworkspacedisplay.table_view_model import MatrixWorkspaceTableViewModel, \
-    MatrixWorkspaceTableViewModelType
-
 AXIS_INDEX_FOR_HORIZONTAL = 0
 AXIS_INDEX_FOR_VERTICAL = 1
 
 
-def setup_common_for_test_data():
-    """
-    Common configuration of variables and mocking for testing
-    MatrixWorkspaceDisplayTableViewModel's data and headerData functions
-    """
-    # Create some mock data for the mock workspace
-    row = 2
-    column = 2
-    # make a workspace with 0s
-    mock_data = [0] * 10
-    # set one of them to be not 0
-    mock_data[column] = 999
-    model_type = MatrixWorkspaceTableViewModelType.x
-    # pass onto the MockWorkspace so that it returns it when read from the TableViewModel
-    ws = MockWorkspace(read_return=mock_data)
-    ws.hasMaskedBins = Mock(return_value=True)
-    ws.maskedBinsIndices = Mock(return_value=[column])
-    model = MatrixWorkspaceTableViewModel(ws, model_type)
-    # The model retrieves the spectrumInfo object, and our MockWorkspace has already given it
-    # the MockSpectrumInfo, so all that needs to be done here is to set up the correct method Mocks
-    model.ws_spectrum_info.hasDetectors = Mock(return_value=True)
-    index = MockQModelIndex(row, column)
-    return ws, model, row, index
-
-
 class MockMantidSymbol:
     TEST_ASCII = "MANTID_ASCII_SYMBOL"
     TEST_UTF8 = "MANTID_UTF8_SYMBOL"
@@ -71,22 +41,6 @@ class MockMantidAxis:
         self.getUnit = Mock(return_value=self.mock_unit)
 
 
-class MockQModelIndexSibling:
-    TEST_SIBLING_DATA = "MANTID_TEST_SIBLING_DATA"
-
-    def __init__(self):
-        self.data = Mock(return_value=self.TEST_SIBLING_DATA)
-
-
-class MockQModelIndex:
-
-    def __init__(self, row, column):
-        self.row = Mock(return_value=row)
-        self.column = Mock(return_value=column)
-        self.mock_sibling = MockQModelIndexSibling()
-        self.sibling = Mock(return_value=self.mock_sibling)
-
-
 class MockSpectrumInfo:
     def __init__(self):
         self.hasDetectors = None
@@ -104,6 +58,10 @@ class MockSpectrum:
 class MockWorkspace:
     TEST_NAME = "THISISAtestWORKSPACE"
 
+    NUMBER_OF_ROWS_AND_COLUMNS = 5
+    COLS = NUMBER_OF_ROWS_AND_COLUMNS
+    ROWS = COLS
+
     @staticmethod
     def _return_MockSpectrumInfo():
         return MockSpectrumInfo()
@@ -125,10 +83,30 @@ class MockWorkspace:
         self.maskedBinsIndices = None
         self.isCommonBins = Mock(return_value=True)
 
+        self.column_types = ["int", "float", "string", "v3d", "bool"]
+        self.columnTypes = Mock(return_value=self.column_types)
+
         self.mock_spectrum = MockSpectrum()
         self.getSpectrum = Mock(return_value=self.mock_spectrum)
 
         self.mock_axis = MockMantidAxis()
         self.getAxis = Mock(return_value=self.mock_axis)
 
-        self.name = None
+        self.setCell = Mock()
+
+        self.name = Mock(return_value=self.TEST_NAME)
+
+        self._column_names = []
+        for i in range(self.COLS):
+            self._column_names.append("col{0}".format(i))
+
+        self.getColumnNames = Mock(return_value=self._column_names)
+        self.column_count = self.COLS
+        self.columnCount = Mock(return_value=self.column_count)
+
+        self.row_count = self.ROWS
+        self.rowCount = Mock(return_value=self.row_count)
+
+        self.column = Mock(return_value=[1] * self.row_count)
+
+        self.emit_repaint = Mock()
diff --git a/qt/python/mantidqt/widgets/common/test_mocks/mock_matrixworkspacedisplay.py b/qt/python/mantidqt/widgets/common/test_mocks/mock_matrixworkspacedisplay.py
new file mode 100644
index 0000000000000000000000000000000000000000..229b29adb6d66c51a9b50ee91becdb7fd3e26ebc
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/test_mocks/mock_matrixworkspacedisplay.py
@@ -0,0 +1,32 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+from __future__ import (absolute_import, division, print_function)
+
+from mock import Mock
+
+from mantidqt.widgets.common.test_mocks.mock_qt import MockQTab, MockQTableView
+
+
+class MockMatrixWorkspaceDisplayView:
+
+    def __init__(self):
+        self.set_context_menu_actions = Mock()
+        self.table_x = MockQTableView()
+        self.table_y = MockQTableView()
+        self.table_e = MockQTableView()
+        self.set_model = Mock()
+        self.ask_confirmation = None
+        self.emit_close = Mock()
+        self.mock_tab = MockQTab()
+        self.get_active_tab = Mock(return_value=self.mock_tab)
+
+
+class MockMatrixWorkspaceDisplayModel:
+    def __init__(self):
+        self.get_spectrum_plot_label = Mock()
+        self.get_bin_plot_label = Mock()
diff --git a/qt/python/mantidqt/widgets/common/test_mocks/mock_observing.py b/qt/python/mantidqt/widgets/common/test_mocks/mock_observing.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9cbadba3b24e861d21cd8b9d47b26c00ce1ff10
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/test_mocks/mock_observing.py
@@ -0,0 +1,31 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+from __future__ import (absolute_import, division, print_function)
+
+from mock import Mock
+
+from mantidqt.widgets.common.observing_presenter import ObservingPresenter
+from mantidqt.widgets.common.observing_view import ObservingView
+from mantidqt.widgets.common.test_mocks.mock_qt import MockQtSignal
+
+
+class MockObservingView(ObservingView):
+    def __init__(self, _):
+        self.close_signal = MockQtSignal()
+        self.rename_signal = MockQtSignal()
+        self.presenter = Mock()
+        self.presenter.clear_observer = Mock()
+        self.setWindowTitle = Mock()
+
+
+class MockObservingPresenter(ObservingPresenter):
+    def __init__(self, workspaces_are_equal=True):
+        self.ads_observer = Mock()
+        self.view = MockObservingView(None)
+        self.model = Mock()
+        self.model.workspace_equals = Mock(return_value=workspaces_are_equal)
diff --git a/qt/python/mantidqt/widgets/common/test_mocks/mock_plotlib.py b/qt/python/mantidqt/widgets/common/test_mocks/mock_plotlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..92f11f5d0735dde705034f23c508225a6153ad18
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/test_mocks/mock_plotlib.py
@@ -0,0 +1,58 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+
+"""
+The aim of this module is to provide a swap-in object for
+the matplotlib.pyplot module, which has the same interface.
+Every function is instantiated as a mock, which allows to record
+calls to the plotting functions, without executing any real
+matplotlib code that requires a running GUI application.
+
+The mocking follows the real matplotlib structure so that it can be easily
+swapped in when necessary. There are two ways to do that - either
+by injecting the plotting dependency in the constructor or
+by using mock.patch to replace the 'matplotlib.pyplot'.
+
+Note that not all matplotlib.pyplot functions have been added,
+only the ones that have been necessary so far. If another function
+needs to be mocked it can be freely added in the relevant class below
+and it should not break any existing tests.
+"""
+from __future__ import (absolute_import, division, print_function)
+
+
+from mock import Mock
+
+
+class MockAx:
+    def __init__(self):
+        self.plot = Mock()
+        self.scatter = Mock()
+        self.errorbar = Mock()
+        self.legend = Mock()
+        self.set_xlabel = Mock()
+        self.set_ylabel = Mock()
+
+
+class MockCanvas:
+    def __init__(self):
+        self.set_window_title = Mock()
+
+
+class MockFig:
+    def __init__(self):
+        self.show = Mock()
+        self.mock_canvas = MockCanvas()
+        self.canvas = Mock(return_value=self.mock_canvas)
+
+
+class MockPlotLib:
+    def __init__(self):
+        self.mock_ax = MockAx()
+        self.mock_fig = MockFig()
+        self.subplots = Mock(return_value=[self.mock_fig, self.mock_ax])
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py b/qt/python/mantidqt/widgets/common/test_mocks/mock_qt.py
similarity index 66%
rename from qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py
rename to qt/python/mantidqt/widgets/common/test_mocks/mock_qt.py
index 4b67e28ccaecd7eb454a28738204067ad12ab743..a678044aafde251cde646b7990da69c746a80dd5 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/mock_matrixworkspacedisplay.py
+++ b/qt/python/mantidqt/widgets/common/test_mocks/mock_qt.py
@@ -1,12 +1,12 @@
 # Mantid Repository : https://github.com/mantidproject/mantid
 #
-# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
 #     NScD Oak Ridge National Laboratory, European Spallation Source
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
 #  This file is part of the mantid workbench.
-#
-#
+from __future__ import (absolute_import, division, print_function)
+
 from mock import Mock
 
 from mantidqt.widgets.matrixworkspacedisplay.table_view_model import MatrixWorkspaceTableViewModelType
@@ -32,8 +32,8 @@ class MockQItemRange(object):
 
 
 class MockQSelectionModel:
-    def __init__(self):
-        self.hasSelection = Mock()
+    def __init__(self, has_selection=True):
+        self.hasSelection = Mock(return_value=has_selection)
         self.selectedRows = None
         self.selectedColumns = None
         self.currentIndex = None
@@ -63,19 +63,38 @@ class MockQTableView:
         self.selectionModel = Mock(return_value=self.mock_selection_model)
 
 
-class MockMatrixWorkspaceDisplayView:
+class MockViewport:
+    def __init__(self):
+        self.update = Mock()
+
+
+class MockQTab:
+    def __init__(self):
+        self.mock_viewport = MockViewport()
+        self.viewport = Mock(return_value=self.mock_viewport)
+
+
+class MockQModelIndexSibling:
+    TEST_SIBLING_DATA = "MANTID_TEST_SIBLING_DATA"
+
+    def __init__(self):
+        self.data = Mock(return_value=self.TEST_SIBLING_DATA)
+
+
+class MockQModelIndex:
+
+    def __init__(self, row, column):
+        self.row = Mock(return_value=row)
+        self.column = Mock(return_value=column)
+        self.mock_sibling = MockQModelIndexSibling()
+        self.sibling = Mock(return_value=self.mock_sibling)
+
+
+class MockQtEvent:
     def __init__(self):
-        self.set_context_menu_actions = Mock()
-        self.table_x = MockQTableView()
-        self.table_y = MockQTableView()
-        self.table_e = MockQTableView()
-        self.set_model = Mock()
-        self.copy_to_clipboard = Mock()
-        self.show_mouse_toast = Mock()
-        self.ask_confirmation = None
+        self.accept = Mock()
 
 
-class MockMatrixWorkspaceDisplayModel:
+class MockQtSignal:
     def __init__(self):
-        self.get_spectrum_plot_label = Mock()
-        self.get_bin_plot_label = Mock()
+        self.emit = Mock()
diff --git a/qt/python/mantidqt/widgets/common/workspacedisplay_ads_observer.py b/qt/python/mantidqt/widgets/common/workspacedisplay_ads_observer.py
new file mode 100644
index 0000000000000000000000000000000000000000..2bdc70b294189e2f8e180f22ba8f01453b18ca72
--- /dev/null
+++ b/qt/python/mantidqt/widgets/common/workspacedisplay_ads_observer.py
@@ -0,0 +1,76 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+
+from __future__ import (absolute_import, division, print_function)
+
+import sys
+from functools import wraps
+
+from mantid.api import AnalysisDataServiceObserver
+
+
+def _catch_exceptions(func):
+    """
+    Catch all exceptions in method and print a traceback to stderr
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        try:
+            func(*args, **kwargs)
+        except Exception:
+            sys.stderr.write("Error occurred in handler:\n")
+            import traceback
+            traceback.print_exc()
+
+    return wrapper
+
+
+class WorkspaceDisplayADSObserver(AnalysisDataServiceObserver):
+
+    def __init__(self, workspace_display, observe_replace=True):
+        super(WorkspaceDisplayADSObserver, self).__init__()
+        self.window = workspace_display
+
+        self.observeClear(True)
+        self.observeDelete(True)
+        self.observeReplace(observe_replace)
+        self.observeRename(True)
+
+    @_catch_exceptions
+    def clearHandle(self):
+        """
+        Called when the ADS is deleted all of its workspaces
+        """
+        self.window.force_close()
+
+    @_catch_exceptions
+    def deleteHandle(self, name, _):
+        """
+        Called when the ADS has deleted a workspace. Checks the
+        attached axes for any hold a plot from this workspace. If removing
+        this leaves empty axes then the parent window is triggered for
+        closer
+        :param _: The name of the workspace. Unused
+        :param __: A pointer to the workspace. Unused
+        """
+        self.window.close(name)
+
+    @_catch_exceptions
+    def replaceHandle(self, name, workspace):
+        """
+        Called when the ADS has replaced a workspace with one of the same name.
+        If this workspace is attached to this figure then its data is updated
+        :param _: The name of the workspace. Unused
+        :param workspace: A reference to the new workspace
+        """
+        self.window.replace_workspace(name, workspace)
+
+    @_catch_exceptions
+    def renameHandle(self, old_name, new_name):
+        self.window.rename_workspace(old_name, new_name)
diff --git a/qt/python/mantidqt/widgets/fitpropertybrowser.py b/qt/python/mantidqt/widgets/fitpropertybrowser.py
new file mode 100644
index 0000000000000000000000000000000000000000..633ff11b078adf98c40c6313543e8dd6fd6bf361
--- /dev/null
+++ b/qt/python/mantidqt/widgets/fitpropertybrowser.py
@@ -0,0 +1,228 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+#
+from __future__ import (print_function, absolute_import, unicode_literals)
+
+import re
+import numpy as np
+
+from matplotlib.path import Path
+from matplotlib.patches import PathPatch
+from qtpy.QtCore import QObject, Signal, Qt
+from qtpy.QtGui import QCursor
+from qtpy.QtWidgets import QApplication
+
+from mantid.simpleapi import mtd
+from mantidqt.utils.qt import import_qt
+
+
+BaseBrowser = import_qt('.._common', 'mantidqt.widgets', 'FitPropertyBrowser')
+
+
+class FitPropertyBrowserBase(BaseBrowser):
+
+    def __init__(self, parent=None):
+        super(FitPropertyBrowserBase, self).__init__(parent)
+        self.init()
+
+
+class FitPropertyBrowser(FitPropertyBrowserBase):
+
+    closing = Signal()
+
+    def __init__(self, canvas, parent=None):
+        super(FitPropertyBrowser, self).__init__(parent)
+        self.init()
+        self.canvas = canvas
+        self.tool = None
+        self.fit_result_lines = []
+        self.startXChanged.connect(self.move_start_x)
+        self.endXChanged.connect(self.move_end_x)
+        self.fittingDone.connect(self.fitting_done)
+
+    def closeEvent(self, event):
+        self.closing.emit()
+        BaseBrowser.closeEvent(self, event)
+
+    def show(self):
+        allowed_spectra = {}
+        pattern = re.compile('(.+?): spec (\d+)')
+        for label in [lin.get_label() for lin in self.get_lines()]:
+            a_match = re.match(pattern, label)
+            name, spec = a_match.group(1), int(a_match.group(2))
+            spec_list = allowed_spectra.get(name, [])
+            spec_list.append(spec)
+            allowed_spectra[name] = spec_list
+        for name, spec_list in allowed_spectra.items():
+            self.addAllowedSpectra(name, spec_list)
+        self.tool = FitInteractiveTool(self.canvas)
+        self.tool.fit_start_x_moved.connect(self.setStartX)
+        self.tool.fit_end_x_moved.connect(self.setEndX)
+        self.setXRange(self.tool.fit_start_x.x, self.tool.fit_end_x.x)
+        super(FitPropertyBrowser, self).show()
+
+    def hide(self):
+        if self.tool is not None:
+            self.tool.fit_start_x_moved.disconnect()
+            self.tool.fit_end_x_moved.disconnect()
+            self.tool.disconnect()
+        super(FitPropertyBrowser, self).hide()
+
+    def move_start_x(self, xd):
+        if self.tool is not None:
+            self.tool.move_start_x(xd)
+
+    def move_end_x(self, xd):
+        if self.tool is not None:
+            self.tool.move_end_x(xd)
+
+    def clear_fit_result_lines(self):
+        for lin in self.fit_result_lines:
+            try:
+                lin.remove()
+            except ValueError:
+                # workspace replacement could invalidate these references
+                pass
+        self.fit_result_lines = []
+
+    def get_lines(self):
+        return self.canvas.figure.get_axes()[0].get_lines()
+
+    def fitting_done(self, name):
+        from mantidqt.plotting.functions import plot
+        name += '_Workspace'
+        ws = mtd[name]
+        self.clear_fit_result_lines()
+        plot([ws], wksp_indices=[1, 2], fig=self.canvas.figure, overplot=True)
+        name += ':'
+        for lin in self.get_lines():
+            if lin.get_label().startswith(name):
+                self.fit_result_lines.append(lin)
+
+
+class VerticalMarker(QObject):
+
+    moved = Signal(float)
+
+    def __init__(self, canvas, x, color):
+        super(VerticalMarker, self).__init__()
+        self.x = x
+        self.ax = canvas.figure.get_axes()[0]
+        y0, y1 = self.ax.get_ylim()
+        path = Path([(x, y0), (x, y1)], [Path.MOVETO, Path.LINETO])
+        self.patch = PathPatch(path, facecolor='None', edgecolor=color, picker=5, linewidth=2.0, animated=True)
+        self.ax.add_patch(self.patch)
+        self.is_moving = False
+
+    def remove(self):
+        self.patch.remove()
+
+    def redraw(self):
+        y0, y1 = self.ax.get_ylim()
+        vertices = self.patch.get_path().vertices
+        vertices[0] = self.x, y0
+        vertices[1] = self.x, y1
+        self.ax.draw_artist(self.patch)
+
+    def get_x_in_pixels(self):
+        x_pixels, _ = self.patch.get_transform().transform((self.x, 0))
+        return x_pixels
+
+    def is_above(self, x):
+        return np.abs(self.get_x_in_pixels() - x) < 3
+
+    def on_click(self, x):
+        if self.is_above(x):
+            self.is_moving = True
+
+    def stop(self):
+        self.is_moving = False
+
+    def mouse_move(self, xd):
+        if self.is_moving and xd is not None:
+            self.x = xd
+            self.moved.emit(xd)
+
+    def should_override_cursor(self, x):
+        return self.is_moving or self.is_above(x)
+
+
+class FitInteractiveTool(QObject):
+
+    fit_start_x_moved = Signal(float)
+    fit_end_x_moved = Signal(float)
+
+    def __init__(self, canvas):
+        super(FitInteractiveTool, self).__init__()
+        self.canvas = canvas
+        ax = canvas.figure.get_axes()[0]
+        self.ax = ax
+        xlim = ax.get_xlim()
+        dx = (xlim[1] - xlim[0]) / 20.
+        start_x = xlim[0] + dx
+        end_x = xlim[1] - dx
+        self.fit_start_x = VerticalMarker(canvas, start_x, 'green')
+        self.fit_end_x = VerticalMarker(canvas, end_x, 'green')
+
+        self.fit_start_x.moved.connect(self.fit_start_x_moved)
+        self.fit_end_x.moved.connect(self.fit_end_x_moved)
+
+        self._cids = []
+        self._cids.append(canvas.mpl_connect('draw_event', self.draw_callback))
+        self._cids.append(canvas.mpl_connect('motion_notify_event', self.motion_notify_callback))
+        self._cids.append(canvas.mpl_connect('button_press_event', self.on_click))
+        self._cids.append(canvas.mpl_connect('button_release_event', self.on_release))
+
+        self.is_cursor_overridden = False
+
+    def disconnect(self):
+        for cid in self._cids:
+            self.canvas.mpl_disconnect(cid)
+        self.fit_start_x.remove()
+        self.fit_end_x.remove()
+
+    def draw_callback(self, event):
+        if self.fit_start_x.x > self.fit_end_x.x:
+            x = self.fit_start_x.x
+            self.fit_start_x.x = self.fit_end_x.x
+            self.fit_end_x.x = x
+        self.fit_start_x.redraw()
+        self.fit_end_x.redraw()
+
+    def motion_notify_callback(self, event):
+        x = event.x
+        if x is not None and (self.fit_start_x.should_override_cursor(x) or self.fit_end_x.should_override_cursor(x)):
+            if not self.is_cursor_overridden:
+                QApplication.setOverrideCursor(QCursor(Qt.SizeHorCursor))
+            self.is_cursor_overridden = True
+        else:
+            QApplication.restoreOverrideCursor()
+            self.is_cursor_overridden = False
+        self.fit_start_x.mouse_move(event.xdata)
+        self.fit_end_x.mouse_move(event.xdata)
+        self.canvas.draw()
+
+    def on_click(self, event):
+        if event.button == 1:
+            self.fit_start_x.on_click(event.x)
+            self.fit_end_x.on_click(event.x)
+
+    def on_release(self, event):
+        self.fit_start_x.stop()
+        self.fit_end_x.stop()
+
+    def move_start_x(self, xd):
+        if xd is not None:
+            self.fit_start_x.x = xd
+            self.canvas.draw()
+
+    def move_end_x(self, xd):
+        if xd is not None:
+            self.fit_end_x.x = xd
+            self.canvas.draw()
diff --git a/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt b/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt
index 31f02d565474207434366a88f79f3c02a952f1c2..e472f2d9f9833de7ecc0838cc9a0e60a064eabca 100644
--- a/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt
+++ b/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt
@@ -25,8 +25,10 @@ mtd_add_sip_module (
     Qt5::OpenGL
     Qt5::Widgets
   INSTALL_DIR
-    ${LIB_DIR}/mantidqt/widgets/instrumentview
+    ${WORKBENCH_LIB_DIR}/mantidqt/widgets/instrumentview
   LINUX_INSTALL_RPATH
     "\$ORIGIN/../../.."
+  OSX_INSTALL_RPATH
+    "@loader_path/../../.." 
   FOLDER Qt5
 )
diff --git a/qt/python/mantidqt/widgets/instrumentview/presenter.py b/qt/python/mantidqt/widgets/instrumentview/presenter.py
index 03f44680fbf9ac660f156e610f5be17efb428726..fb96adb88f1f61c90d602bbf4effecd748e51f33 100644
--- a/qt/python/mantidqt/widgets/instrumentview/presenter.py
+++ b/qt/python/mantidqt/widgets/instrumentview/presenter.py
@@ -13,15 +13,53 @@ Contains the presenter for displaying the InstrumentWidget
 from __future__ import (absolute_import, unicode_literals)
 
 # local imports
+from mantidqt.widgets.common.observing_presenter import ObservingPresenter
+from mantidqt.widgets.common.workspacedisplay_ads_observer import WorkspaceDisplayADSObserver
 from .view import InstrumentView
 
 
-class InstrumentViewPresenter(object):
+class InstrumentViewPresenter(ObservingPresenter):
     """
     Presenter holding the view widget for the InstrumentView.
     It has no model as its an old widget written in C++ with out MVP
     """
+
     view = None
 
-    def __init__(self, ws, parent=None):
-        self.view = InstrumentView(self, ws.name(), parent)
+    def __init__(self, ws, parent=None, ads_observer=None):
+        super(InstrumentViewPresenter, self).__init__()
+        self.ws_name = ws.name()
+        self.view = InstrumentView(self, self.ws_name, parent)
+
+        if ads_observer:
+            self.ads_observer = ads_observer
+        else:
+            self.ads_observer = WorkspaceDisplayADSObserver(self, observe_replace=False)
+
+    def close(self, workspace_name):
+        """
+        This closes the external window of the Instrument view.
+
+        The C++ InstrumentWidget handles all events to the workspace itself,
+        if the workspace is deleted then the widget closes itself.
+
+        The InstrumentWidget is also wrapped in a QWidget made from Python,
+        and that needs to be closed from here, otherwise we are left with an empty window,
+        when the InstrumentWidget closes itself on workspace deletion.
+
+        :param workspace_name: Used to check if it is the current workspace of the instrument view.
+                               If it is - then close the instrument view,
+                               but if it isn't - it does nothing
+        """
+        if self.ws_name == workspace_name:
+            self.clear_observer()
+            self.view.emit_close()
+
+    def replace_workspace(self, workspace_name, workspace):
+        # replace is handled by the InstrumentWidget inside C++
+        # this method is also unused, but is added to conform to the interface
+        pass
+
+    def rename_workspace(self, old_name, new_name):
+        # rename is handled by the InstrumentWidget inside C++
+        pass
diff --git a/qt/python/mantidqt/widgets/instrumentview/view.py b/qt/python/mantidqt/widgets/instrumentview/view.py
index 7abfce3a63fce6506d2b3109e731516d4f0606ae..22c457d9782d72ea13c7004352857887185f76b3 100644
--- a/qt/python/mantidqt/widgets/instrumentview/view.py
+++ b/qt/python/mantidqt/widgets/instrumentview/view.py
@@ -13,36 +13,48 @@ Contains the Python wrapper class for the C++ instrument widget
 from __future__ import (absolute_import, unicode_literals)
 
 # 3rdparty imports
-from qtpy.QtCore import Qt
+from qtpy.QtCore import Qt, Signal, Slot
 from qtpy.QtWidgets import QVBoxLayout, QWidget
 
 # local imports
 from mantidqt.utils.qt import import_qt
-
-
 # import widget class from C++ wrappers
+from mantidqt.widgets.common.observing_view import ObservingView
+
 InstrumentWidget = import_qt('._instrumentview', 'mantidqt.widgets.instrumentview',
                              'InstrumentWidget')
 
 
-class InstrumentView(QWidget):
+class InstrumentView(QWidget, ObservingView):
     """
     Defines a Window wrapper for the instrument widget. Sets
     the Qt.Window flag and window title. Holds a reference
     to the presenter and keeps it alive for the duration that
     the window is open
     """
-    _presenter = None
+    presenter = None
     _widget = None
 
+    close_signal = Signal()
+
     def __init__(self, presenter, name, parent=None):
         super(InstrumentView, self).__init__(parent)
-        self._presenter = presenter
+
+        self.widget = InstrumentWidget(name)
+
+        self.presenter = presenter
 
         self.setWindowTitle(name)
         self.setWindowFlags(Qt.Window)
 
         layout = QVBoxLayout()
         layout.setContentsMargins(0, 0, 0, 0)
-        layout.addWidget(InstrumentWidget(name))
+
+        layout.addWidget(self.widget)
         self.setLayout(layout)
+
+        self.close_signal.connect(self._run_close)
+
+    @Slot()
+    def _run_close(self):
+        self.close()
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/__main__.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/__main__.py
index b9c1d81bf38c198e3d6cd90b94106edd0cdbdae6..da33fe2595ed7c8bea171a5942977f1c93de90eb 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/__main__.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/__main__.py
@@ -18,7 +18,7 @@ from qtpy.QtWidgets import QApplication  # noqa: F402
 
 from mantid.simpleapi import Load  # noqa: F402
 from mantidqt.widgets.matrixworkspacedisplay.presenter import MatrixWorkspaceDisplay  # noqa: F402
-from workbench.plotting.functions import plot  # noqa: F402
+from mantidqt.plotting.functions import plot  # noqa: F402
 
 app = QApplication([])
 LOQ74044 = Load("LOQ74044.nxs")
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/model.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/model.py
index 1f16359607aa526dfbeff66ef5e85f67fb4ece02..b8b8353768d945544ae3f82fb9b7b954b4a6dc9b 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/model.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/model.py
@@ -22,10 +22,23 @@ class MatrixWorkspaceDisplayModel(object):
 
     ALLOWED_WORKSPACE_TYPES = [MatrixWorkspace, Workspace2D, EventWorkspace]
 
-    def __init__(self, ws):
-        if not any(isinstance(ws, allowed_type) for allowed_type in self.ALLOWED_WORKSPACE_TYPES):
+    @classmethod
+    def supports(cls, ws):
+        """
+        Checks that the provided workspace is supported by this display.
+        :param ws: Workspace to be checked for support
+        :raises ValueError: if the workspace is not supported
+        """
+        if not any(isinstance(ws, allowed_type) for allowed_type in cls.ALLOWED_WORKSPACE_TYPES):
             raise ValueError("The workspace type is not supported: {0}".format(ws))
 
+    def __init__(self, ws):
+        """
+        Initialise the model with the workspace
+        :param ws: Workspace to be used for providing data
+        :raises ValueError: if the workspace is not supported
+        """
+        self.supports(ws)
         self._ws = ws
 
     def get_name(self):
@@ -35,3 +48,6 @@ class MatrixWorkspaceDisplayModel(object):
         return (MatrixWorkspaceTableViewModel(self._ws, MatrixWorkspaceTableViewModelType.x),
                 MatrixWorkspaceTableViewModel(self._ws, MatrixWorkspaceTableViewModelType.y),
                 MatrixWorkspaceTableViewModel(self._ws, MatrixWorkspaceTableViewModelType.e))
+
+    def workspace_equals(self, workspace_name):
+        return workspace_name == self._ws.name()
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/presenter.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/presenter.py
index 7e6457a13f2cd146edb53bddece003bcde5a3210..91ff6860f2d001c8ee0c409271596735d0e18e82 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/presenter.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/presenter.py
@@ -10,18 +10,33 @@
 from __future__ import absolute_import, division, print_function
 
 from mantid.plots.utility import MantidAxType
+from mantidqt.widgets.common.observing_presenter import ObservingPresenter
+from mantidqt.widgets.common.table_copying import copy_bin_values, copy_cells, copy_spectrum_values, \
+    show_no_selection_to_copy_toast
+from mantidqt.widgets.common.workspacedisplay_ads_observer import WorkspaceDisplayADSObserver
 from mantidqt.widgets.matrixworkspacedisplay.table_view_model import MatrixWorkspaceTableViewModelType
 from .model import MatrixWorkspaceDisplayModel
 from .view import MatrixWorkspaceDisplayView
 
 
-class MatrixWorkspaceDisplay(object):
+class MatrixWorkspaceDisplay(ObservingPresenter):
     NO_SELECTION_MESSAGE = "No selection"
     COPY_SUCCESSFUL_MESSAGE = "Copy Successful"
     A_LOT_OF_THINGS_TO_PLOT_MESSAGE = "You selected {} spectra to plot. Are you sure you want to plot that many?"
     NUM_SELECTED_FOR_CONFIRMATION = 10
 
-    def __init__(self, ws, plot=None, parent=None, model=None, view=None):
+    def __init__(self, ws, plot=None, parent=None, model=None, view=None, ads_observer=None):
+        """
+        Creates a display for the provided workspace.
+
+        :param ws: Workspace to be displayed
+        :param plot: Plotting function that will be used to plot workspaces. Passed in as parameter to allow mocking
+        :param parent: Parent of the widget
+        :param model: Model to be used by the widget. Passed in as parameter to allow mocking
+        :param view: View to be used by the widget. Passed in as parameter to allow mocking
+        :param ads_observer: ADS observer to be used by the presenter. If not provided the default
+                             one is used. Mainly intended for testing.
+        """
         # Create model and view, or accept mocked versions
         self.model = model if model else MatrixWorkspaceDisplayModel(ws)
         self.view = view if view else MatrixWorkspaceDisplayView(self,
@@ -29,111 +44,51 @@ class MatrixWorkspaceDisplay(object):
                                                                  self.model.get_name())
 
         self.plot = plot
+
+        self.ads_observer = ads_observer if ads_observer else WorkspaceDisplayADSObserver(self)
+
         self.setup_tables()
 
         self.view.set_context_menu_actions(self.view.table_y)
         self.view.set_context_menu_actions(self.view.table_x)
         self.view.set_context_menu_actions(self.view.table_e)
 
+    def replace_workspace(self, workspace_name, workspace):
+        if self.model.workspace_equals(workspace_name):
+            self.model = MatrixWorkspaceDisplayModel(workspace)
+            self.view.get_active_tab().viewport().update()
+
+    @classmethod
+    def supports(cls, ws):
+        """
+        Checks that the provided workspace is supported by this display.
+        :param ws: Workspace to be checked for support
+        :raises ValueError: if the workspace is not supported
+        """
+        return MatrixWorkspaceDisplayModel.supports(ws)
+
     def setup_tables(self):
         # unpacks the list of models returned from getItemModel
         self.view.set_model(*self.model.get_item_model())
 
     def action_copy_spectrum_values(self, table):
-        """
-        Copies the values selected by the user to the system's clipboard
-
-        :param table: Table from which the selection will be read
-        :param ws_read: The workspace read function, that is used to access the data directly
-        """
-        selection_model = table.selectionModel()
-        if not selection_model.hasSelection():
-            self.show_no_selection_to_copy_toast()
-            return
         ws_read = self._get_ws_read_from_type(table.model().type)
-        selected_rows = selection_model.selectedRows()  # type: list
-        row_data = []
-
-        for index in selected_rows:
-            row = index.row()
-            data = "\t".join(map(str, ws_read(row)))
-
-            row_data.append(data)
-
-        self.view.copy_to_clipboard("\n".join(row_data))
-        self.show_successful_copy_toast()
-
-    def show_no_selection_to_copy_toast(self):
-        self.view.show_mouse_toast(self.NO_SELECTION_MESSAGE)
-
-    def show_successful_copy_toast(self):
-        self.view.show_mouse_toast(self.COPY_SUCCESSFUL_MESSAGE)
+        copy_spectrum_values(table, ws_read)
 
     def action_copy_bin_values(self, table):
-        selection_model = table.selectionModel()
-        if not selection_model.hasSelection():
-            self.show_no_selection_to_copy_toast()
-            return
         ws_read = self._get_ws_read_from_type(table.model().type)
-        selected_columns = selection_model.selectedColumns()  # type: list
-
-        # Qt gives back a QModelIndex, we need to extract the column from it
         num_rows = self.model._ws.getNumberHistograms()
-        column_data = []
-        for index in selected_columns:
-            column = index.column()
-            data = [str(ws_read(row)[column]) for row in range(num_rows)]
-            column_data.append(data)
-
-        all_string_rows = []
-        for i in range(num_rows):
-            # Appends ONE value from each COLUMN, this is because the final string is being built vertically
-            # the noqa disables a 'data' variable redefined warning
-            all_string_rows.append("\t".join([data[i] for data in column_data]))  # noqa: F812
-
-        # Finally all rows are joined together with a new line at the end of each row
-        final_string = "\n".join(all_string_rows)
-        self.view.copy_to_clipboard(final_string)
-        self.show_successful_copy_toast()
+        copy_bin_values(table, ws_read, num_rows)
 
     def action_copy_cells(self, table):
-        """
-        :type table: QTableView
-        :param table: The table from which the data will be copied.
-        :return:
-        """
-        selectionModel = table.selectionModel()
-        if not selectionModel.hasSelection():
-            self.show_no_selection_to_copy_toast()
-            return
-
-        selection = selectionModel.selection()
-        selectionRange = selection.first()
-
-        top = selectionRange.top()
-        bottom = selectionRange.bottom()
-        left = selectionRange.left()
-        right = selectionRange.right()
-
-        data = []
-        index = selectionModel.currentIndex()
-        for i in range(top, bottom + 1):
-            for j in range(left, right):
-                data.append(index.sibling(i, j).data())
-                data.append("\t")
-            data.append(index.sibling(i, right).data())
-            data.append("\n")
-
-        # strip the string to remove the trailing new line
-        self.view.copy_to_clipboard("".join(data).strip())
-        self.show_successful_copy_toast()
+        copy_cells(table)
 
     def _do_action_plot(self, table, axis, get_index, plot_errors=False):
         if self.plot is None:
             raise ValueError("Trying to do a plot, but no plotting class dependency was injected in the constructor")
         selection_model = table.selectionModel()
         if not selection_model.hasSelection():
-            self.show_no_selection_to_copy_toast()
+            show_no_selection_to_copy_toast()
             return
 
         if axis == MantidAxType.SPECTRUM:
@@ -167,7 +122,7 @@ class MatrixWorkspaceDisplay(object):
     def action_keypress_copy(self, table):
         selectionModel = table.selectionModel()
         if not selectionModel.hasSelection():
-            self.show_no_selection_to_copy_toast()
+            show_no_selection_to_copy_toast()
             return
 
         if len(selectionModel.selectedRows()) > 0:
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/table_view_model.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/table_view_model.py
index 19fb4fd551a3df6eae27617f78a2707d4ed3f1d3..115ec3a3f6322efe1f38e21a9824a9dfd22c7928 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/table_view_model.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/table_view_model.py
@@ -67,6 +67,12 @@ class MatrixWorkspaceTableViewModel(QAbstractTableModel):
         self.type = model_type
         if self.type == MatrixWorkspaceTableViewModelType.x:
             self.relevant_data = self.ws.readX
+
+            # add another column if the workspace is histogram data
+            # this will contain the right boundary for the last bin
+            if self.ws.isHistogramData():
+                self.column_count += 1
+
         elif self.type == MatrixWorkspaceTableViewModelType.y:
             self.relevant_data = self.ws.readY
         elif self.type == MatrixWorkspaceTableViewModelType.e:
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_model.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_model.py
index 20b841487d4476a8742db2a3e85cfe8f14cd55c1..75357d2bc594c6889d9a7d7324b9a4e1dbe54911 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_model.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_model.py
@@ -14,10 +14,9 @@ import unittest
 from mock import Mock
 
 from mantid.simpleapi import CreateSampleWorkspace
+from mantidqt.widgets.common.test_mocks.mock_mantid import MockWorkspace
 from mantidqt.widgets.matrixworkspacedisplay.model import MatrixWorkspaceDisplayModel
 from mantidqt.widgets.matrixworkspacedisplay.table_view_model import MatrixWorkspaceTableViewModelType
-from mantidqt.widgets.matrixworkspacedisplay.test_helpers.matrixworkspacedisplay_common import \
-    MockWorkspace
 
 
 class MatrixWorkspaceDisplayModelTest(unittest.TestCase):
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_presenter.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_presenter.py
index 88f40e77954088a5311ff7e101a1f5f2d057e0a9..83fd98f3bd1273b0537c1089244e797531e9980a 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_presenter.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_presenter.py
@@ -11,14 +11,24 @@ from __future__ import (absolute_import, division, print_function)
 
 import unittest
 
-from mock import Mock
+from mock import Mock, patch
 
+from mantidqt.widgets.common.test_mocks.mock_mantid import MockWorkspace
+from mantidqt.widgets.common.test_mocks.mock_matrixworkspacedisplay import MockMatrixWorkspaceDisplayView
+from mantidqt.widgets.common.test_mocks.mock_qt import MockQModelIndex, MockQTableView
 from mantidqt.widgets.matrixworkspacedisplay.model import MatrixWorkspaceDisplayModel
 from mantidqt.widgets.matrixworkspacedisplay.presenter import MatrixWorkspaceDisplay
-from mantidqt.widgets.matrixworkspacedisplay.test_helpers.matrixworkspacedisplay_common import MockQModelIndex, \
-    MockWorkspace
-from mantidqt.widgets.matrixworkspacedisplay.test_helpers.mock_matrixworkspacedisplay import \
-    MockMatrixWorkspaceDisplayView, MockQTableView
+
+
+def with_mock_presenter(func):
+    def wrapper(self, *args):
+        ws = MockWorkspace()
+        view = MockMatrixWorkspaceDisplayView()
+        mock_observer = Mock()
+        presenter = MatrixWorkspaceDisplay(ws, view=view, ads_observer=mock_observer)
+        return func(self, ws, view, presenter, *args)
+
+    return wrapper
 
 
 class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
@@ -37,11 +47,10 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         self.assertEqual(3, view.set_context_menu_actions.call_count)
         self.assertEqual(1, view.set_model.call_count)
 
-    def test_action_copy_spectrum_values(self):
-        ws = MockWorkspace()
-        view = MockMatrixWorkspaceDisplayView()
-        presenter = MatrixWorkspaceDisplay(ws, view=view)
-
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    @patch('mantidqt.widgets.common.table_copying.copy_to_clipboard')
+    @with_mock_presenter
+    def test_action_copy_spectrum_values(self, ws, view, presenter, mock_copy, mock_show_mouse_toast):
         mock_table = MockQTableView()
 
         # two rows are selected in different positions
@@ -56,13 +65,14 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
 
         mock_table.selectionModel.assert_called_once_with()
         mock_table.mock_selection_model.hasSelection.assert_called_once_with()
-        view.copy_to_clipboard.assert_called_once_with(expected_string)
-        view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.COPY_SUCCESSFUL_MESSAGE)
+        mock_copy.assert_called_once_with(expected_string)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.COPY_SUCCESSFUL_MESSAGE)
 
-    def test_action_copy_spectrum_values_no_selection(self):
-        ws = MockWorkspace()
-        view = MockMatrixWorkspaceDisplayView()
-        presenter = MatrixWorkspaceDisplay(ws, view=view)
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    @patch('mantidqt.widgets.common.table_copying.copy_to_clipboard')
+    @with_mock_presenter
+    def test_action_copy_spectrum_values_no_selection(self, ws, view, presenter, mock_copy,
+                                                      mock_show_mouse_toast):
 
         mock_table = MockQTableView()
         mock_table.mock_selection_model.hasSelection = Mock(return_value=False)
@@ -74,12 +84,13 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         mock_table.mock_selection_model.hasSelection.assert_called_once_with()
         # the action should never look for rows if there is no selection
         self.assertNotCalled(mock_table.mock_selection_model.selectedRows)
-        view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
+        self.assertNotCalled(mock_copy)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
 
-    def test_action_copy_bin_values(self):
-        ws = MockWorkspace()
-        view = MockMatrixWorkspaceDisplayView()
-        presenter = MatrixWorkspaceDisplay(ws, view=view)
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    @patch('mantidqt.widgets.common.table_copying.copy_to_clipboard')
+    @with_mock_presenter
+    def test_action_copy_bin_values(self, ws, view, presenter, mock_copy, mock_show_mouse_toast):
         mock_table = MockQTableView()
 
         # two columns are selected at different positions
@@ -95,15 +106,14 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         presenter.action_copy_bin_values(mock_table)
 
         mock_table.selectionModel.assert_called_once_with()
-        view.copy_to_clipboard.assert_called_once_with(expected_string)
         mock_table.mock_selection_model.hasSelection.assert_called_once_with()
-        view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.COPY_SUCCESSFUL_MESSAGE)
-
-    def test_action_copy_bin_values_no_selection(self):
-        ws = MockWorkspace()
-        view = MockMatrixWorkspaceDisplayView()
-        presenter = MatrixWorkspaceDisplay(ws, view=view)
+        mock_copy.assert_called_once_with(expected_string)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.COPY_SUCCESSFUL_MESSAGE)
 
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    @patch('mantidqt.widgets.common.table_copying.copy_to_clipboard')
+    @with_mock_presenter
+    def test_action_copy_bin_values_no_selection(self, ws, view, presenter, mock_copy, mock_show_mouse_toast):
         mock_table = MockQTableView()
         mock_table.mock_selection_model.hasSelection = Mock(return_value=False)
         mock_table.mock_selection_model.selectedColumns = Mock()
@@ -114,12 +124,13 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         mock_table.mock_selection_model.hasSelection.assert_called_once_with()
         # the action should never look for rows if there is no selection
         self.assertNotCalled(mock_table.mock_selection_model.selectedColumns)
-        view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
+        self.assertNotCalled(mock_copy)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
 
-    def test_action_copy_cell(self):
-        ws = MockWorkspace()
-        view = MockMatrixWorkspaceDisplayView()
-        presenter = MatrixWorkspaceDisplay(ws, view=view)
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    @patch('mantidqt.widgets.common.table_copying.copy_to_clipboard')
+    @with_mock_presenter
+    def test_action_copy_cell(self, ws, view, presenter, mock_copy, mock_show_mouse_toast):
         mock_table = MockQTableView()
 
         # two columns are selected at different positions
@@ -129,14 +140,14 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         presenter.action_copy_cells(mock_table)
 
         mock_table.selectionModel.assert_called_once_with()
-        self.assertEqual(1, view.copy_to_clipboard.call_count)
+        self.assertEqual(1, mock_copy.call_count)
         self.assertEqual(9, mock_index.sibling.call_count)
-        view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.COPY_SUCCESSFUL_MESSAGE)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.COPY_SUCCESSFUL_MESSAGE)
 
-    def test_action_copy_cell_no_selection(self):
-        ws = MockWorkspace()
-        view = MockMatrixWorkspaceDisplayView()
-        presenter = MatrixWorkspaceDisplay(ws, view=view)
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    @patch('mantidqt.widgets.common.table_copying.copy_to_clipboard')
+    @with_mock_presenter
+    def test_action_copy_cell_no_selection(self, ws, view, presenter, mock_copy, mock_show_mouse_toast):
         mock_table = MockQTableView()
         mock_table.mock_selection_model.hasSelection = Mock(return_value=False)
 
@@ -144,9 +155,9 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
 
         mock_table.selectionModel.assert_called_once_with()
         mock_table.mock_selection_model.hasSelection.assert_called_once_with()
-        view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
 
-        self.assertNotCalled(view.copy_to_clipboard)
+        self.assertNotCalled(mock_copy)
 
     def common_setup_action_plot(self, table_has_selection=True):
         mock_ws = MockWorkspace()
@@ -230,7 +241,8 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         self.assertNotCalled(mock_table.mock_selection_model.selectedColumns)
         self.assertNotCalled(mock_plot)
 
-    def test_action_plot_spectrum_no_selection(self):
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    def test_action_plot_spectrum_no_selection(self, mock_show_mouse_toast):
         mock_plot, mock_table, mock_view, presenter = self.common_setup_action_plot(table_has_selection=False)
 
         mock_table.mock_selection_model.selectedRows = Mock()
@@ -238,7 +250,7 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
 
         presenter.action_plot_spectrum(mock_table)
 
-        mock_view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
         mock_table.selectionModel.assert_called_once_with()
         mock_table.mock_selection_model.hasSelection.assert_called_once_with()
 
@@ -284,13 +296,14 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         self.assertNotCalled(mock_table.mock_selection_model.selectedRows)
         self.assertNotCalled(mock_plot)
 
-    def test_action_plot_bin_no_selection(self):
+    @patch('mantidqt.widgets.common.table_copying.show_mouse_toast')
+    def test_action_plot_bin_no_selection(self, mock_show_mouse_toast):
         mock_plot, mock_table, mock_view, presenter = self.common_setup_action_plot(table_has_selection=False)
         self.setup_mock_selection(mock_table, num_selected_rows=None, num_selected_cols=None)
 
         presenter.action_plot_bin(mock_table)
 
-        mock_view.show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
+        mock_show_mouse_toast.assert_called_once_with(MatrixWorkspaceDisplay.NO_SELECTION_MESSAGE)
         mock_table.selectionModel.assert_called_once_with()
         mock_table.mock_selection_model.hasSelection.assert_called_once_with()
 
@@ -298,6 +311,48 @@ class MatrixWorkspaceDisplayPresenterTest(unittest.TestCase):
         self.assertNotCalled(mock_table.mock_selection_model.selectedColumns)
         self.assertNotCalled(mock_plot)
 
+    @with_mock_presenter
+    def test_close_incorrect_workspace(self, ws, view, presenter):
+        presenter.close(ws.TEST_NAME + "123")
+        self.assertNotCalled(view.emit_close)
+        self.assertIsNotNone(presenter.ads_observer)
+
+    @with_mock_presenter
+    def test_close(self, ws, view, presenter):
+        presenter.close(ws.TEST_NAME)
+        view.emit_close.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_mock_presenter
+    def test_force_close_even_with_incorrect_name(self, _, view, presenter):
+        # window always closes, regardless of the workspace
+        presenter.force_close()
+        view.emit_close.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_mock_presenter
+    def test_force_close(self, _, view, presenter):
+        presenter.force_close()
+        view.emit_close.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_mock_presenter
+    def test_replace_incorrect_workspace(self, ws, view, presenter):
+        presenter.replace_workspace(ws.TEST_NAME + "123", ws)
+        self.assertNotCalled(view.get_active_tab)
+        self.assertNotCalled(view.mock_tab.viewport)
+        self.assertNotCalled(view.mock_tab.mock_viewport.update)
+
+    @with_mock_presenter
+    def test_replace(self, ws, view, presenter):
+        # patch this out after the constructor of the presenter has finished,
+        # so that we reset any calls it might have made
+        presenter.replace_workspace(ws.TEST_NAME, ws)
+
+        view.get_active_tab.assert_called_once_with()
+        view.mock_tab.viewport.assert_called_once_with()
+        view.mock_tab.mock_viewport.update.assert_called_once_with()
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_tableviewmodel.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_table_view_model.py
similarity index 91%
rename from qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_tableviewmodel.py
rename to qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_table_view_model.py
index 968cc1287483a43e0f763d98e438419a3125119c..e374d413f2e880ae34bc3b6f03b0696fc1ff5945 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_tableviewmodel.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/test/test_matrixworkspacedisplay_table_view_model.py
@@ -16,16 +16,39 @@ from mock import Mock, call
 from qtpy import QtCore
 from qtpy.QtCore import Qt
 
+from mantidqt.widgets.common.test_mocks.mock_mantid import AXIS_INDEX_FOR_HORIZONTAL, AXIS_INDEX_FOR_VERTICAL, \
+    MockMantidAxis, MockMantidSymbol, MockMantidUnit, MockSpectrum, MockWorkspace
+from mantidqt.widgets.common.test_mocks.mock_qt import MockQModelIndex
 from mantidqt.widgets.matrixworkspacedisplay.table_view_model import MatrixWorkspaceTableViewModel, \
     MatrixWorkspaceTableViewModelType
-from mantidqt.widgets.matrixworkspacedisplay.test_helpers.matrixworkspacedisplay_common import \
-    MockQModelIndex, MockWorkspace, setup_common_for_test_data, AXIS_INDEX_FOR_VERTICAL, MockMantidAxis, MockSpectrum, \
-    MockMantidSymbol, AXIS_INDEX_FOR_HORIZONTAL, MockMantidUnit
 
 
-class MatrixWorkspaceDisplayTableViewModelTest(unittest.TestCase):
-    WORKSPACE = r"C:\Users\qbr77747\dev\m\workbench_matrixworkspace\test_masked_bins.nxs"
+def setup_common_for_test_data():
+    """
+    Common configuration of variables and mocking for testing
+    MatrixWorkspaceDisplayTableViewModel's data and headerData functions
+    """
+    # Create some mock data for the mock workspace
+    row = 2
+    column = 2
+    # make a workspace with 0s
+    mock_data = [0] * 10
+    # set one of them to be not 0
+    mock_data[column] = 999
+    model_type = MatrixWorkspaceTableViewModelType.x
+    # pass onto the MockWorkspace so that it returns it when read from the TableViewModel
+    ws = MockWorkspace(read_return=mock_data)
+    ws.hasMaskedBins = Mock(return_value=True)
+    ws.maskedBinsIndices = Mock(return_value=[column])
+    model = MatrixWorkspaceTableViewModel(ws, model_type)
+    # The model retrieves the spectrumInfo object, and our MockWorkspace has already given it
+    # the MockSpectrumInfo, so all that needs to be done here is to set up the correct method Mocks
+    model.ws_spectrum_info.hasDetectors = Mock(return_value=True)
+    index = MockQModelIndex(row, column)
+    return ws, model, row, index
+
 
+class MatrixWorkspaceDisplayTableViewModelTest(unittest.TestCase):
     def test_correct_model_type(self):
         ws = MockWorkspace()
         model = MatrixWorkspaceTableViewModel(ws, MatrixWorkspaceTableViewModelType.x)
@@ -483,6 +506,31 @@ class MatrixWorkspaceDisplayTableViewModelTest(unittest.TestCase):
 
         self.assertEqual(MatrixWorkspaceTableViewModel.HORIZONTAL_BINS_VARY_TOOLTIP_STRING.format(mock_section), output)
 
+    def test_histogram_data_has_one_extra_x_column(self):
+        """
+        Test that an extra column is added if the workspace is HistogramData. This is the column that
+        contains the right boundary for the last bin.
+        """
+        mock_data = [1, 2, 3, 4, 5, 6, 7]
+        data_len = len(mock_data)
+        ws = MockWorkspace(read_return=mock_data, isHistogramData=True)
+
+        model_type = MatrixWorkspaceTableViewModelType.x
+        model = MatrixWorkspaceTableViewModel(ws, model_type)
+
+        self.assertEqual(data_len + 1, model.columnCount())
+
+        # test that it is not added to Y and E
+        model_type = MatrixWorkspaceTableViewModelType.y
+        model = MatrixWorkspaceTableViewModel(ws, model_type)
+
+        self.assertEqual(data_len, model.columnCount())
+
+        model_type = MatrixWorkspaceTableViewModelType.e
+        model = MatrixWorkspaceTableViewModel(ws, model_type)
+
+        self.assertEqual(data_len, model.columnCount())
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/view.py b/qt/python/mantidqt/widgets/matrixworkspacedisplay/view.py
index 2c123a1b13c4546338e2de61a8135feb974d9366..fc20d674eae6a53fb0004ec889d30f6760beabe2 100644
--- a/qt/python/mantidqt/widgets/matrixworkspacedisplay/view.py
+++ b/qt/python/mantidqt/widgets/matrixworkspacedisplay/view.py
@@ -12,15 +12,38 @@ from __future__ import (absolute_import, division, print_function)
 from functools import partial
 
 from qtpy import QtGui
-from qtpy.QtCore import QPoint, Qt
-from qtpy.QtGui import QCursor, QFont, QFontMetrics, QKeySequence
-from qtpy.QtWidgets import (QAbstractItemView, QAction, QHeaderView, QMessageBox, QTabWidget, QTableView, QToolTip)
+from qtpy.QtCore import Qt, Signal, Slot
+from qtpy.QtGui import QKeySequence
+from qtpy.QtWidgets import (QAbstractItemView, QAction, QHeaderView, QMessageBox, QTabWidget, QTableView)
 
 import mantidqt.icons
+from mantidqt.widgets.common.observing_view import ObservingView
 from mantidqt.widgets.matrixworkspacedisplay.table_view_model import MatrixWorkspaceTableViewModelType
 
 
-class MatrixWorkspaceDisplayView(QTabWidget):
+class MatrixWorkspaceTableView(QTableView):
+    def __init__(self, parent):
+        super(MatrixWorkspaceTableView, self).__init__(parent)
+        self.setSelectionBehavior(QAbstractItemView.SelectItems)
+
+        header = self.horizontalHeader()
+        header.sectionDoubleClicked.connect(self.handle_double_click)
+
+    def resizeEvent(self, event):
+        super(MatrixWorkspaceTableView, self).resizeEvent(event)
+
+        header = self.horizontalHeader()
+        header.setSectionResizeMode(QHeaderView.Interactive)
+
+    def handle_double_click(self, section):
+        header = self.horizontalHeader()
+        header.resizeSection(section, header.defaultSectionSize())
+
+
+class MatrixWorkspaceDisplayView(QTabWidget, ObservingView):
+    close_signal = Signal()
+    rename_signal = Signal(str)
+
     def __init__(self, presenter, parent=None, name=''):
         super(MatrixWorkspaceDisplayView, self).__init__(parent)
 
@@ -47,12 +70,24 @@ class MatrixWorkspaceDisplayView(QTabWidget):
         self.table_x = self.add_table("X values")
         self.table_e = self.add_table("E values")
 
+        self.close_signal.connect(self._run_close)
+        self.rename_signal.connect(self._run_rename)
+
         self.resize(600, 400)
         self.show()
 
+    @Slot()
+    def _run_close(self):
+        self.presenter.clear_observer()
+        self.close()
+
+    @Slot(str)
+    def _run_rename(self, new_name):
+        self._rename(new_name)
+
     def add_table(self, label):
-        tab = QTableView()
-        tab.setSelectionBehavior(QAbstractItemView.SelectItems)
+        tab = MatrixWorkspaceTableView(self)
+
         self.addTab(tab, label)
         self.tabs.append(tab)
         return tab
@@ -62,6 +97,9 @@ class MatrixWorkspaceDisplayView(QTabWidget):
             self.presenter.action_keypress_copy(self.tabs[self.currentIndex()])
         super(MatrixWorkspaceDisplayView, self).keyPressEvent(event)
 
+    def get_active_tab(self):
+        return self.tabs[self.active_tab_index]
+
     def set_scroll_position_on_new_focused_tab(self, new_tab_index):
         """
         Updates the new focused tab's scroll position to match the old one.
@@ -144,26 +182,6 @@ class MatrixWorkspaceDisplayView(QTabWidget):
                                                                                          model.model_type)
         table.setModel(model)
 
-    @staticmethod
-    def copy_to_clipboard(data):
-        """
-        Uses the QGuiApplication to copy to the system clipboard.
-
-        :type data: str
-        :param data: The data that will be copied to the clipboard
-        :return:
-        """
-        cb = QtGui.QGuiApplication.clipboard()
-        cb.setText(data, mode=cb.Clipboard)
-
-    def show_mouse_toast(self, message):
-        # Creates a text with empty space to get the height of the rendered text - this is used
-        # to provide the same offset for the tooltip, scaled relative to the current resolution and zoom.
-        font_metrics = QFontMetrics(QFont(" "))
-        # The height itself is divided by 2 just to reduce the offset so that the tooltip is
-        # reasonably position relative to the cursor
-        QToolTip.showText(QCursor.pos() + QPoint(font_metrics.height() / 2, 0), message)
-
     def ask_confirmation(self, message, title="Mantid Workbench"):
         """
         :param message:
diff --git a/qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/__init__.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/__init__.py
similarity index 100%
rename from qt/python/mantidqt/widgets/matrixworkspacedisplay/test_helpers/__init__.py
rename to qt/python/mantidqt/widgets/tableworkspacedisplay/__init__.py
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/__main__.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d11d49143c9e97cd34e51493e4c3eb13ce47d3b
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/__main__.py
@@ -0,0 +1,27 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+
+# To Run - target this package with PyCharm, and __main__ will be executed
+
+import matplotlib
+
+matplotlib.use('Qt5Agg')
+
+
+from qtpy.QtWidgets import QApplication  # noqa: F402
+
+from mantid.simpleapi import Load  # noqa: F402
+from mantidqt.widgets.tableworkspacedisplay.presenter import TableWorkspaceDisplay  # noqa: F402
+import matplotlib.pyplot as plt  # noqa: F402
+
+app = QApplication([])
+ws = Load("SavedTableWorkspace.nxs")
+window = TableWorkspaceDisplay(ws, plt)
+app.exec_()
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py
new file mode 100644
index 0000000000000000000000000000000000000000..60d5796e619a21faa63970fc761e7d2759ac26f9
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/error_column.py
@@ -0,0 +1,38 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#    This file is part of the mantid workbench.
+#
+#
+
+
+class ErrorColumn:
+    CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE = "Cannot set Y column to be its own YErr"
+    UNHANDLED_COMPARISON_LOGIC_MESSAGE = "Unhandled comparison logic with type {}"
+
+    def __init__(self, column, related_y_column, label_index):
+        self.column = column
+        self.related_y_column = related_y_column
+        if self.column == self.related_y_column:
+            raise ValueError(self.CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE)
+
+        self.label_index = label_index
+
+    def __eq__(self, other):
+        if isinstance(other, ErrorColumn):
+            return self.related_y_column == other.related_y_column or self.column == other.column
+        elif isinstance(other, int):
+            return self.column == other
+        else:
+            raise RuntimeError(self.UNHANDLED_COMPARISON_LOGIC_MESSAGE.format(type(other)))
+
+    def __cmp__(self, other):
+        if isinstance(other, ErrorColumn):
+            return self.column == other.column or self.related_y_column == other.related_y_column
+        elif isinstance(other, int):
+            return self.column == other
+        else:
+            raise RuntimeError(self.UNHANDLED_COMPARISON_LOGIC_MESSAGE.format(type(other)))
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2894bb946d6ceff73d4daba2fd808c895031868
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/marked_columns.py
@@ -0,0 +1,111 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#    This file is part of the mantid workbench.
+#
+#
+
+
+class MarkedColumns:
+    X_LABEL = "[X{}]"
+    Y_LABEL = "[Y{}]"
+    Y_ERR_LABEL = "[Y{}_YErr]"
+
+    def __init__(self):
+        self.as_x = []
+        self.as_y = []
+        self.as_y_err = []
+
+    def _add(self, col_index, add_to, remove_from):
+        assert all(
+            add_to is not remove for remove in remove_from), "Can't add and remove from the same list at the same time!"
+        self._remove(col_index, remove_from)
+
+        if col_index not in add_to:
+            add_to.append(col_index)
+
+    def _remove(self, col_index, remove_from):
+        """
+        Remove the column index from all lists
+        :param col_index: The column index to be removed
+        :type remove_from: list[list[Union[int, ErrorColumn]]]
+        :param remove_from: List of lists from which the column index will be removed
+        :return:
+        """
+        for list in remove_from:
+            try:
+                list.remove(col_index)
+            except ValueError:
+                # column not in this list, but might be in another one so we continue the loop
+                continue
+
+        # if the column previously had a Y Err associated with it -> this will remove it from the YErr list
+        self._remove_associated_yerr_columns(col_index)
+
+    def add_x(self, col_index):
+        self._add(col_index, self.as_x, [self.as_y, self.as_y_err])
+
+    def add_y(self, col_index):
+        self._add(col_index, self.as_y, [self.as_x, self.as_y_err])
+
+    def add_y_err(self, err_column):
+        if err_column.related_y_column in self.as_x:
+            raise ValueError("Trying to add YErr for column marked as X.")
+        elif err_column.related_y_column in self.as_y_err:
+            raise ValueError("Trying to add YErr for column marked as YErr.")
+        # remove all labels for the column index
+        len_before_remove = len(self.as_y)
+        self._remove(err_column, [self.as_x, self.as_y, self.as_y_err])
+
+        # Check if the length of the list with columns marked Y has shrunk
+        # -> This means that columns have been removed, and the label_index is now _wrong_
+        # and has to be decremented to match the new label index correctly
+        len_after_remove = len(self.as_y)
+        if err_column.related_y_column > err_column.column and len_after_remove < len_before_remove:
+            err_column.label_index -= (len_before_remove - len_after_remove)
+        self.as_y_err.append(err_column)
+
+    def remove(self, col_index):
+        self._remove(col_index, [self.as_x, self.as_y, self.as_y_err])
+
+    def _remove_associated_yerr_columns(self, col_index):
+        # we can only have 1 Y Err for Y, so iterating and removing's iterator invalidation is not an
+        # issue as the code will exit immediately after the removal
+        for col in self.as_y_err:
+            if col.related_y_column == col_index:
+                self.as_y_err.remove(col)
+                break
+
+    def _make_labels(self, list, label):
+        return [(col_num, label.format(index),) for index, col_num in enumerate(list)]
+
+    def build_labels(self):
+        extra_labels = []
+        extra_labels.extend(self._make_labels(self.as_x, self.X_LABEL))
+        extra_labels.extend(self._make_labels(self.as_y, self.Y_LABEL))
+        err_labels = [(err_col.column, self.Y_ERR_LABEL.format(err_col.label_index),) for index, err_col in
+                      enumerate(self.as_y_err)]
+        extra_labels.extend(err_labels)
+        return extra_labels
+
+    def find_yerr(self, selected_columns):
+        """
+        Retrieve the corresponding YErr column for each Y column, so that it can be plotted
+        :param selected_columns: Selected Y columns for which their YErr columns will be retrieved
+        :return: Dict[Selected Column] = Column with YErr
+        """
+        yerr_for_col = {}
+
+        # for each selected column
+        for col in selected_columns:
+            # find the marked error column
+            for yerr_col in self.as_y_err:
+                # if found append the YErr's source column - so that the data from the columns
+                # can be retrieved for plotting the errors
+                if yerr_col.related_y_column == col:
+                    yerr_for_col[col] = yerr_col.column
+
+        return yerr_for_col
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/model.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9f0fb00610aac83dd800207080c8e70a71c9e95
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/model.py
@@ -0,0 +1,97 @@
+# coding=utf-8
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import (absolute_import, division, print_function)
+
+from mantid.dataobjects import PeaksWorkspace, TableWorkspace
+from mantid.kernel import V3D
+from mantidqt.widgets.tableworkspacedisplay.marked_columns import MarkedColumns
+
+
+class TableWorkspaceDisplayModel:
+    SPECTRUM_PLOT_LEGEND_STRING = '{}-{}'
+    BIN_PLOT_LEGEND_STRING = '{}-bin-{}'
+
+    ALLOWED_WORKSPACE_TYPES = [PeaksWorkspace, TableWorkspace]
+
+    @classmethod
+    def supports(cls, ws):
+        """
+        Checks that the provided workspace is supported by this display.
+        :param ws: Workspace to be checked for support
+        :raises ValueError: if the workspace is not supported
+        """
+        if not any(isinstance(ws, allowed_type) for allowed_type in cls.ALLOWED_WORKSPACE_TYPES):
+            raise ValueError("The workspace type is not supported: {0}".format(ws))
+
+    def __init__(self, ws):
+        """
+        Initialise the model with the workspace
+        :param ws: Workspace to be used for providing data
+        :raises ValueError: if the workspace is not supported
+        """
+        self.supports(ws)
+
+        self.ws = ws
+        self.ws_num_rows = self.ws.rowCount()
+        self.ws_num_cols = self.ws.columnCount()
+        self.marked_columns = MarkedColumns()
+        self._original_column_headers = self.get_column_headers()
+
+    def _get_v3d_from_str(self, string):
+        if '[' in string and ']' in string:
+            string = string[1:-1]
+        if ',' in string:
+            return V3D(*[float(x) for x in string.split(',')])
+        else:
+            raise ValueError("'{}' is not a valid V3D string.".format(string))
+
+    def original_column_headers(self):
+        return self._original_column_headers[:]
+
+    def build_current_labels(self):
+        return self.marked_columns.build_labels()
+
+    def get_name(self):
+        return self.ws.name()
+
+    def get_column_headers(self):
+        return self.ws.getColumnNames()
+
+    def get_column(self, index):
+        return self.ws.column(index)
+
+    def get_number_of_rows(self):
+        return self.ws_num_rows
+
+    def get_number_of_columns(self):
+        return self.ws_num_cols
+
+    def get_column_header(self, index):
+        return self.get_column_headers()[index]
+
+    def is_peaks_workspace(self):
+        return isinstance(self.ws, PeaksWorkspace)
+
+    def set_cell_data(self, row, col, data, is_v3d):
+        # if the cell contains V3D data, construct a V3D object
+        # from the string to that it can be properly set
+        if is_v3d:
+            data = self._get_v3d_from_str(data)
+        # The False stops the replace workspace ADS event from being triggered
+        # The replace event causes the TWD model to be replaced, which in turn
+        # deletes the previous table item objects, however this happens
+        # at the same time as we are trying to locally update the data in the
+        # item object itself, which causes a Qt exception that the object has
+        # already been deleted and a crash
+        self.ws.setCell(row, col, data, notify_replace=False)
+
+    def workspace_equals(self, workspace_name):
+        return self.ws.name() == workspace_name
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/plot_type.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/plot_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4a127600497467305e4cfaf9f17c360f71878b9
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/plot_type.py
@@ -0,0 +1,8 @@
+from mantid.py3compat import Enum
+
+
+class PlotType(Enum):
+    LINEAR = 1
+    SCATTER = 2
+    LINE_AND_SYMBOL = 3
+    LINEAR_WITH_ERR = 4
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..a15634f4fd0701e28ffa7ef6d70a2cdb80705a41
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/presenter.py
@@ -0,0 +1,345 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import absolute_import, division, print_function
+
+from functools import partial
+
+from qtpy.QtCore import Qt
+
+from mantid.kernel import logger
+from mantid.simpleapi import DeleteTableRows, StatisticsOfTableWorkspace
+from mantidqt.widgets.common.observing_presenter import ObservingPresenter
+from mantidqt.widgets.common.table_copying import copy_cells, show_no_selection_to_copy_toast
+from mantidqt.widgets.common.workspacedisplay_ads_observer import WorkspaceDisplayADSObserver
+from mantidqt.widgets.tableworkspacedisplay.error_column import ErrorColumn
+from mantidqt.widgets.tableworkspacedisplay.plot_type import PlotType
+from mantidqt.widgets.tableworkspacedisplay.workbench_table_widget_item import WorkbenchTableWidgetItem
+from .model import TableWorkspaceDisplayModel
+from .view import TableWorkspaceDisplayView
+
+
+class TableWorkspaceDisplay(ObservingPresenter):
+    A_LOT_OF_THINGS_TO_PLOT_MESSAGE = "You selected {} spectra to plot. Are you sure you want to plot that many?"
+    TOO_MANY_SELECTED_FOR_X = "Too many columns are selected to use as X. Please select only 1."
+    TOO_MANY_SELECTED_TO_SORT = "Too many columns are selected to sort by. Please select only 1."
+    TOO_MANY_SELECTED_FOR_PLOT = "Too many columns are selected to plot. Please select only 1."
+    NUM_SELECTED_FOR_CONFIRMATION = 10
+    NO_COLUMN_MARKED_AS_X = "No columns marked as X."
+    ITEM_CHANGED_INVALID_DATA_MESSAGE = "Error: Trying to set invalid data for the column."
+    ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE = "Unknown error occurred: {}"
+    TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE = "Too many selected to set as Y Error"
+    CANNOT_PLOT_AGAINST_SELF_MESSAGE = "Cannot plot column against itself."
+    NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE = "There is no associated YErr for each selected Y column."
+    PLOT_FUNCTION_ERROR_MESSAGE = "One or more of the columns being plotted contain invalid data for Matplotlib.\n\nError message:\n{}"
+    INVALID_DATA_WINDOW_TITLE = "Invalid data - Mantid Workbench"
+    COLUMN_DISPLAY_LABEL = 'Column {}'
+
+    def __init__(self, ws, plot=None, parent=None, model=None, view=None, name=None, ads_observer=None):
+        """
+        Creates a display for the provided workspace.
+
+        :param ws: Workspace to be displayed
+        :param parent: Parent of the widget
+        :param plot: Plotting function that will be used to plot workspaces. This requires Matplotlib directly.
+                     Passed in as parameter to allow mocking
+        :param model: Model to be used by the widget. Passed in as parameter to allow mocking
+        :param view: View to be used by the widget. Passed in as parameter to allow mocking
+        :param name: Custom name for the window
+        :param ads_observer: ADS observer to be used by the presenter. If not provided the default
+                             one is used. Mainly intended for testing.
+        """
+
+        self.model = model if model else TableWorkspaceDisplayModel(ws)
+        self.name = self.model.get_name() if name is None else name
+        self.view = view if view else TableWorkspaceDisplayView(self, parent, self.name)
+        self.parent = parent
+        self.plot = plot
+        self.view.set_context_menu_actions(self.view)
+
+        self.ads_observer = ads_observer if ads_observer else WorkspaceDisplayADSObserver(self)
+
+        self.update_column_headers()
+        self.load_data(self.view)
+
+        # connect to cellChanged signal after the data has been loaded
+        # all consecutive triggers will be from user actions
+        self.view.itemChanged.connect(self.handleItemChanged)
+
+    @classmethod
+    def supports(cls, ws):
+        """
+        Checks that the provided workspace is supported by this display.
+        :param ws: Workspace to be checked for support
+        :raises ValueError: if the workspace is not supported
+        """
+        return TableWorkspaceDisplayModel.supports(ws)
+
+    def replace_workspace(self, workspace_name, workspace):
+        if self.model.workspace_equals(workspace_name):
+            self.model = TableWorkspaceDisplayModel(workspace)
+            self.load_data(self.view)
+            self.view.emit_repaint()
+
+    def handleItemChanged(self, item):
+        """
+        :type item: WorkbenchTableWidgetItem
+        """
+        try:
+            self.model.set_cell_data(item.row(), item.column(), item.data(Qt.DisplayRole), item.is_v3d)
+            item.update()
+        except ValueError:
+            self.view.show_warning(self.ITEM_CHANGED_INVALID_DATA_MESSAGE)
+        except Exception as x:
+            self.view.show_warning(self.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(x))
+        finally:
+            item.reset()
+
+    def update_column_headers(self):
+        """
+        :param extra_labels: Extra labels to be appended to the column headers.
+                             Expected format: [(id, label), (2, "X"),...]
+        :type extra_labels: List[Tuple[int, str]]
+        :return:
+        """
+        # deep copy the original headers so that they are not changed by the appending of the label
+        column_headers = self.model.original_column_headers()
+        num_headers = len(column_headers)
+        self.view.setColumnCount(num_headers)
+
+        extra_labels = self.model.build_current_labels()
+        if len(extra_labels) > 0:
+            for index, label in extra_labels:
+                column_headers[index] += str(label)
+
+        self.view.setHorizontalHeaderLabels(column_headers)
+
+    def load_data(self, table):
+        num_rows = self.model.get_number_of_rows()
+        table.setRowCount(num_rows)
+
+        num_cols = self.model.get_number_of_columns()
+
+        # the table should be editable if the ws is not PeaksWS
+        editable = not self.model.is_peaks_workspace()
+
+        for col in range(num_cols):
+            column_data = self.model.get_column(col)
+            for row in range(num_rows):
+                item = WorkbenchTableWidgetItem(column_data[row], editable=editable)
+                table.setItem(row, col, item)
+
+    def action_copy_cells(self):
+        copy_cells(self.view)
+
+    def action_copy_bin_values(self):
+        copy_cells(self.view)
+
+    def action_copy_spectrum_values(self):
+        copy_cells(self.view)
+
+    def action_keypress_copy(self):
+        copy_cells(self.view)
+
+    def action_delete_row(self):
+        selection_model = self.view.selectionModel()
+        if not selection_model.hasSelection():
+            show_no_selection_to_copy_toast()
+            return
+
+        selected_rows = selection_model.selectedRows()
+        selected_rows_list = [index.row() for index in selected_rows]
+        selected_rows_str = ",".join([str(row) for row in selected_rows_list])
+
+        DeleteTableRows(self.model.ws, selected_rows_str)
+        # Reverse the list so that we delete in order from bottom -> top
+        # this prevents the row index from shifting up when deleting rows above
+        for row in reversed(selected_rows_list):
+            self.view.removeRow(row)
+
+    def _get_selected_columns(self, max_selected=None, message_if_over_max=None):
+        selection_model = self.view.selectionModel()
+        if not selection_model.hasSelection():
+            show_no_selection_to_copy_toast()
+            raise ValueError("No selection")
+
+        selected_columns = selection_model.selectedColumns()
+        num_selected_columns = len(selected_columns)
+
+        if max_selected and message_if_over_max and num_selected_columns > max_selected:
+            # if over the maximum allowed selection
+            self.view.show_warning(message_if_over_max)
+            raise ValueError("Too many selected")
+        elif num_selected_columns == 0:
+            # if no columns are selected
+            show_no_selection_to_copy_toast()
+            raise ValueError("No selection")
+        else:
+            return [index.column() for index in selected_columns]
+
+    def action_statistics_on_columns(self):
+        try:
+            selected_columns = self._get_selected_columns()
+        except ValueError:
+            return
+
+        stats = StatisticsOfTableWorkspace(self.model.ws, selected_columns)
+        TableWorkspaceDisplay(stats, parent=self.parent, name="Column Statistics of {}".format(self.name))
+
+    def action_hide_selected(self):
+        try:
+            selected_columns = self._get_selected_columns()
+        except ValueError:
+            return
+        for column_index in selected_columns:
+            self.view.hideColumn(column_index)
+
+    def action_show_all_columns(self):
+        for column_index in range(self.view.columnCount()):
+            self.view.showColumn(column_index)
+
+    def _action_set_as(self, add_to_list_func):
+        try:
+            selected_columns = self._get_selected_columns()
+        except ValueError:
+            return
+
+        for col in selected_columns:
+            add_to_list_func(col)
+
+        self.update_column_headers()
+
+    def action_set_as_x(self):
+        self._action_set_as(self.model.marked_columns.add_x)
+
+    def action_set_as_y(self):
+        self._action_set_as(self.model.marked_columns.add_y)
+
+    def action_set_as_y_err(self, related_y_column, label_index):
+        """
+
+        :param related_y_column: The real index of the column for which the error is being marked
+        :param label_index: The index present in the label of the column for which the error is being marked
+                            This will be the number in <ColumnName>[Y10] -> the 10
+        """
+        try:
+            selected_columns = self._get_selected_columns(1, self.TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE)
+        except ValueError:
+            return
+
+        selected_column = selected_columns[0]
+        try:
+            err_column = ErrorColumn(selected_column, related_y_column, label_index)
+        except ValueError as e:
+            self.view.show_warning(str(e))
+            return
+
+        self.model.marked_columns.add_y_err(err_column)
+        self.update_column_headers()
+
+    def action_set_as_none(self):
+        self._action_set_as(self.model.marked_columns.remove)
+
+    def action_sort(self, order):
+        try:
+            selected_columns = self._get_selected_columns(1, self.TOO_MANY_SELECTED_TO_SORT)
+        except ValueError:
+            return
+
+        selected_column = selected_columns[0]
+        self.view.sortByColumn(selected_column, order)
+
+    def action_plot(self, plot_type):
+        try:
+            selected_columns = self._get_selected_columns()
+        except ValueError:
+            return
+
+        x_cols = list(set(selected_columns).intersection(self.model.marked_columns.as_x))
+        num_x_cols = len(x_cols)
+        # if there is more than 1 column marked as X in the selection
+        # -> show toast to the user and do nothing
+        if num_x_cols > 1:
+            self.view.show_warning(self.TOO_MANY_SELECTED_FOR_X)
+            return
+        elif num_x_cols == 1:
+            # Only 1 X column present in the current selection model
+            # -> Use it as X for the plot
+            selected_x = x_cols[0]
+        else:
+            # No X column present in the current selection model
+            # -> Use the first column marked as X (if present)
+            if len(self.model.marked_columns.as_x) == 0:
+                # If no columns are marked as X show user message and exit
+                self.view.show_warning(self.NO_COLUMN_MARKED_AS_X)
+                return
+            selected_x = self.model.marked_columns.as_x[0]
+
+        try:
+            # Remove the X column from the selected columns, this is
+            # in case a column is being used as both X and Y
+            selected_columns.remove(selected_x)
+        except ValueError:
+            pass
+
+        if len(selected_columns) == 0:
+            self.view.show_warning(self.CANNOT_PLOT_AGAINST_SELF_MESSAGE)
+            return
+
+        self._do_plot(selected_columns, selected_x, plot_type)
+
+    def _do_plot(self, selected_columns, selected_x, plot_type):
+        if plot_type == PlotType.LINEAR_WITH_ERR:
+            yerr = self.model.marked_columns.find_yerr(selected_columns)
+            if len(yerr) != len(selected_columns):
+                self.view.show_warning(self.NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE)
+                return
+        x = self.model.get_column(selected_x)
+
+        fig, ax = self.plot.subplots(subplot_kw={'projection': 'mantid'})
+        fig.canvas.set_window_title(self.model.get_name())
+        ax.set_xlabel(self.model.get_column_header(selected_x))
+
+        plot_func = self._get_plot_function_from_type(ax, plot_type)
+        kwargs = {}
+        for column in selected_columns:
+            if plot_type == PlotType.LINEAR_WITH_ERR:
+                yerr_column = yerr[column]
+                yerr_column_data = self.model.get_column(yerr_column)
+                kwargs["yerr"] = yerr_column_data
+
+            y = self.model.get_column(column)
+            column_label = self.model.get_column_header(column)
+            try:
+                plot_func(x, y, label=self.COLUMN_DISPLAY_LABEL.format(column_label), **kwargs)
+            except ValueError as e:
+                error_message = self.PLOT_FUNCTION_ERROR_MESSAGE.format(e)
+                logger.error(error_message)
+                self.view.show_warning(error_message, self.INVALID_DATA_WINDOW_TITLE)
+                return
+
+            ax.set_ylabel(column_label)
+        ax.legend()
+        fig.show()
+
+    def _get_plot_function_from_type(self, ax, type):
+        if type == PlotType.LINEAR:
+            plot_func = ax.plot
+        elif type == PlotType.SCATTER:
+            plot_func = ax.scatter
+        elif type == PlotType.LINE_AND_SYMBOL:
+            plot_func = partial(ax.plot, marker='o')
+        elif type == PlotType.LINEAR_WITH_ERR:
+            plot_func = ax.errorbar
+        else:
+            raise ValueError("Plot Type: {} not currently supported!".format(type))
+        return plot_func
+
+    def get_columns_marked_as_y(self):
+        return self.model.marked_columns.as_y[:]
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/__init__.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_error_column.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_error_column.py
new file mode 100644
index 0000000000000000000000000000000000000000..4aae73eb81fc9d70ac97e726269ab448600c8a32
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_error_column.py
@@ -0,0 +1,33 @@
+import unittest
+
+from mantidqt.widgets.tableworkspacedisplay.error_column import ErrorColumn
+
+
+class ErrorColumnTest(unittest.TestCase):
+
+    def test_correct_init(self):
+        ErrorColumn(0, 1, 0)
+
+    def test_raises_for_same_y_and_yerr(self):
+        self.assertRaises(ValueError, lambda: ErrorColumn(2, 2, 3))
+
+    def test_eq_versus_ErrorColumn(self):
+        ec1 = ErrorColumn(0, 1, 0)
+        ec2 = ErrorColumn(0, 1, 0)
+        self.assertEqual(ec1, ec2)
+
+        ec1 = ErrorColumn(0, 3, 0)
+        ec2 = ErrorColumn(0, 1, 0)
+        self.assertEqual(ec1, ec2)
+
+        ec1 = ErrorColumn(2, 3, 0)
+        ec2 = ErrorColumn(0, 3, 0)
+        self.assertEqual(ec1, ec2)
+
+    def test_eq_versus_same_int(self):
+        ec = ErrorColumn(150, 1, 0)
+        self.assertEqual(ec, 150)
+
+    def test_eq_unsupported_type(self):
+        ec = ErrorColumn(150, 1, 0)
+        self.assertRaises(RuntimeError, lambda: ec == "awd")
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py
new file mode 100644
index 0000000000000000000000000000000000000000..a4fdbd9d731a4f9e807ffdb1c72386cd53b027c2
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_marked_columns.py
@@ -0,0 +1,343 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+from itertools import permutations
+
+from mantidqt.widgets.tableworkspacedisplay.error_column import ErrorColumn
+from mantidqt.widgets.tableworkspacedisplay.marked_columns import MarkedColumns
+
+
+class ReferenceHolder:
+    def __init__(self, test_func, test_list):
+        self.func = test_func
+        self.list = test_list
+
+
+class MarkedColumnsTest(unittest.TestCase):
+
+    def test_add_x(self):
+        mc = MarkedColumns()
+        self.execute_add(mc.add_x, mc.as_x)
+
+    def test_add_y(self):
+        mc = MarkedColumns()
+        self.execute_add(mc.add_y, mc.as_y)
+
+    def execute_add(self, func_to_add, list_to_check):
+        func_to_add(2)
+        self.assertEqual(1, len(list_to_check))
+        func_to_add(3)
+        self.assertEqual(2, len(list_to_check))
+        func_to_add(4000000)
+        self.assertEqual(3, len(list_to_check))
+
+    def test_add_y_err(self):
+        """
+        Test adding YErr columns that do not overlap in any way
+        """
+        mc = MarkedColumns()
+        ec = ErrorColumn(2, 4, 0)
+        mc.add_y_err(ec)
+        self.assertEqual(1, len(mc.as_y_err))
+        ec = ErrorColumn(3, 5, 0)
+        mc.add_y_err(ec)
+        self.assertEqual(2, len(mc.as_y_err))
+        ec = ErrorColumn(1, 6, 0)
+        mc.add_y_err(ec)
+        self.assertEqual(3, len(mc.as_y_err))
+
+    def test_add_x_duplicate_column(self):
+        mc = MarkedColumns()
+        self.execute_add_duplicate_column(mc.add_x, mc.as_x)
+
+    def test_add_y_duplicate_column(self):
+        mc = MarkedColumns()
+        self.execute_add_duplicate_column(mc.add_y, mc.as_y)
+
+    def execute_add_duplicate_column(self, func_to_add, list_to_check):
+        func_to_add(2)
+        self.assertEqual(1, len(list_to_check))
+        func_to_add(2)
+        self.assertEqual(1, len(list_to_check))
+        func_to_add(55)
+        self.assertEqual(2, len(list_to_check))
+        func_to_add(55)
+        self.assertEqual(2, len(list_to_check))
+
+    def test_add_y_err_duplicate_column(self):
+        mc = MarkedColumns()
+        ec = ErrorColumn(2, 4, 0)
+
+        mc.add_y_err(ec)
+        self.assertEqual(1, len(mc.as_y_err))
+        mc.add_y_err(ec)
+        self.assertEqual(1, len(mc.as_y_err))
+
+        ec2 = ErrorColumn(3, 5, 0)
+        mc.add_y_err(ec2)
+        self.assertEqual(2, len(mc.as_y_err))
+        mc.add_y_err(ec2)
+        self.assertEqual(2, len(mc.as_y_err))
+
+    def test_add_already_marked(self):
+        mc = MarkedColumns()
+
+        relevant_funcs = [ReferenceHolder(mc.add_x, mc.as_x),
+                          ReferenceHolder(mc.add_y, mc.as_y)]
+        all_combinations = permutations(relevant_funcs, 2)
+
+        for combination in all_combinations:
+            self.execute_add_already_marked(*combination)
+
+    def execute_add_already_marked(self, first, two):
+        """
+        If trying to mark a column that is already marked -> all other markings must be removed
+        :type first: ReferenceHolder
+        :type two: ReferenceHolder
+        :return:
+        """
+
+        # add column in first
+        first.func(33)
+        self.assertEqual(1, len(first.list))
+
+        # add the same column in the second
+        two.func(33)
+
+        # it should have been removed from the first and only present in the second
+        self.assertEqual(0, len(first.list))
+        self.assertEqual(1, len(two.list))
+
+    def test_add_y_err_duplicate_column_same_source_column(self):
+        """
+        Test for adding a new YErr column with the same source column
+        -> The new YErr must replace the old one
+        """
+        mc = MarkedColumns()
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
+        mc.add_y_err(ec)
+        self.assertEqual(1, len(mc.as_y_err))
+        self.assertEqual(2, mc.as_y_err[0].column)
+        self.assertEqual(4, mc.as_y_err[0].related_y_column)
+
+        # different source column but contains error for the same column
+        # adding this one should replace the first one
+        ec2 = ErrorColumn(column=2, related_y_column=5, label_index=0)
+        mc.add_y_err(ec2)
+        self.assertEqual(1, len(mc.as_y_err))
+        self.assertEqual(2, mc.as_y_err[0].column)
+        self.assertEqual(5, mc.as_y_err[0].related_y_column)
+
+    def test_add_y_err_duplicate_column_different_reference_col(self):
+        """
+        Test for adding a new YErr column with a _different_ source column but same reference column
+        -> The new YErr must replace the old one
+        """
+        mc = MarkedColumns()
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
+        mc.add_y_err(ec)
+        self.assertEqual(1, len(mc.as_y_err))
+        self.assertEqual(2, mc.as_y_err[0].column)
+        self.assertEqual(4, mc.as_y_err[0].related_y_column)
+
+        # different source column but contains error for the same column
+        # adding this one should replace the first one
+        ec2 = ErrorColumn(column=3, related_y_column=4, label_index=0)
+        mc.add_y_err(ec2)
+        self.assertEqual(1, len(mc.as_y_err))
+        self.assertEqual(3, mc.as_y_err[0].column)
+        self.assertEqual(4, mc.as_y_err[0].related_y_column)
+
+    def test_changing_y_to_x_removes_associated_yerr_columns(self):
+        """
+        Test to check if a first column is marked as Y, a second column YErr is associated with it, but then
+        the first one is changed to X - the YErr mark should be removed
+        """
+        mc = MarkedColumns()
+        mc.add_y(4)
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
+        mc.add_y_err(ec)
+
+        # check that we have both a Y col and an associated YErr
+        self.assertEqual(1, len(mc.as_y))
+        self.assertEqual(1, len(mc.as_y_err))
+
+        mc.add_x(4)
+        # changing the column to X should have removed it from Y and Yerr
+        self.assertEqual(1, len(mc.as_x))
+        self.assertEqual(0, len(mc.as_y))
+        self.assertEqual(0, len(mc.as_y_err))
+
+    def test_changing_y_to_none_removes_associated_yerr_columns(self):
+        """
+        Test to check if a first column is marked as Y, a second column YErr is associated with it, but then
+        the first one is changed to X - the YErr mark should be removed
+        """
+        mc = MarkedColumns()
+        mc.add_y(4)
+        ec = ErrorColumn(column=2, related_y_column=4, label_index=0)
+        mc.add_y_err(ec)
+
+        # check that we have both a Y col and an associated YErr
+        self.assertEqual(1, len(mc.as_y))
+        self.assertEqual(1, len(mc.as_y_err))
+
+        mc.remove(4)
+        # changing the column to NONE should have removed it from X, Y and YErr
+        self.assertEqual(0, len(mc.as_x))
+        self.assertEqual(0, len(mc.as_y))
+        self.assertEqual(0, len(mc.as_y_err))
+
+    def test_remove_column(self):
+        mc = MarkedColumns()
+        mc.add_y(4)
+        mc.add_x(3)
+        ec = ErrorColumn(column=2, related_y_column=6, label_index=0)
+        mc.add_y_err(ec)
+
+        self.assertEqual(1, len(mc.as_x))
+        self.assertEqual(1, len(mc.as_y))
+        self.assertEqual(1, len(mc.as_y_err))
+
+        mc.remove(4)
+        self.assertEqual(0, len(mc.as_y))
+        self.assertEqual(1, len(mc.as_y_err))
+        self.assertEqual(1, len(mc.as_x))
+
+        mc.remove(3)
+        self.assertEqual(0, len(mc.as_x))
+        self.assertEqual(0, len(mc.as_y))
+        self.assertEqual(1, len(mc.as_y_err))
+
+        mc.remove(2)
+        self.assertEqual(0, len(mc.as_x))
+        self.assertEqual(0, len(mc.as_y))
+        self.assertEqual(0, len(mc.as_y_err))
+
+    def test_build_labels_x_y(self):
+        # TODO test this edge case: mark all columns Y, remove one that is not the last one!
+        mc = MarkedColumns()
+        mc.add_y(0)
+        mc.add_y(1)
+        mc.add_y(2)
+        mc.add_y(3)
+
+        # note that the max Y label number will decrease as more Y columns are being changed to X
+        expected = [(0, '[Y0]'), (1, '[Y1]'), (2, '[Y2]'), (3, '[Y3]')]
+        self.assertEqual(expected, mc.build_labels())
+
+        expected = [(1, '[X0]'), (0, '[Y0]'), (2, '[Y1]'), (3, '[Y2]')]
+        mc.add_x(1)
+        self.assertEqual(expected, mc.build_labels())
+        expected = [(1, '[X0]'), (3, '[X1]'), (0, '[Y0]'), (2, '[Y1]')]
+        mc.add_x(3)
+        self.assertEqual(expected, mc.build_labels())
+
+    def test_build_labels_y_and_yerr_change_middle(self):
+        mc = MarkedColumns()
+        mc.add_y(0)
+        mc.add_y(1)
+        mc.add_y(2)
+
+        # change one of the columns to YErr
+        mc.add_y_err(ErrorColumn(1, 0, 0))
+        expected = [(0, '[Y0]'), (2, '[Y1]'), (1, '[Y0_YErr]')]
+        self.assertEqual(expected, mc.build_labels())
+
+        # change the last Y column to YErr
+        mc.add_y_err(ErrorColumn(2, 0, 0))
+        expected = [(0, '[Y0]'), (2, '[Y0_YErr]')]
+        self.assertEqual(expected, mc.build_labels())
+
+    def test_build_labels_y_and_yerr_change_first(self):
+        mc = MarkedColumns()
+        mc.add_y(0)
+        mc.add_y(1)
+        mc.add_y(2)
+
+        # change one of the columns to YErr
+        mc.add_y_err(ErrorColumn(0, 1, 1))
+        # note: the first column is being set -> this decreases the label index of all columns to its right by 1
+        expected = [(1, '[Y0]'), (2, '[Y1]'), (0, '[Y0_YErr]')]
+        self.assertEqual(expected, mc.build_labels())
+
+        # change the last Y column to YErr
+        mc.add_y_err(ErrorColumn(2, 1, 0))
+        expected = [(1, '[Y0]'), (2, '[Y0_YErr]')]
+        self.assertEqual(expected, mc.build_labels())
+
+    def test_build_labels_x_y_and_yerr(self):
+        mc = MarkedColumns()
+        mc.add_y(0)
+        mc.add_y(1)
+        mc.add_y(2)
+        mc.add_y(3)
+
+        mc.add_y_err(ErrorColumn(1, 0, 0))
+        expected = [(0, '[Y0]'), (2, '[Y1]'), (3, '[Y2]'), (1, '[Y0_YErr]')]
+        self.assertEqual(expected, mc.build_labels())
+
+        expected = [(1, '[X0]'), (0, '[Y0]'), (2, '[Y1]'), (3, '[Y2]')]
+        mc.add_x(1)
+        self.assertEqual(expected, mc.build_labels())
+
+        expected = [(1, '[X0]'), (2, '[Y0]'), (3, '[Y1]'), (0, '[Y1_YErr]')]
+        mc.add_y_err(ErrorColumn(0, 3, 2))
+        self.assertEqual(expected, mc.build_labels())
+
+    def test_fail_to_add_yerr_for_x(self):
+        mc = MarkedColumns()
+        mc.add_y(0)
+        mc.add_y(1)
+        mc.add_y(2)
+        mc.add_y(3)
+
+        mc.add_y_err(ErrorColumn(1, 0, 0))
+        expected = [(0, '[Y0]'), (2, '[Y1]'), (3, '[Y2]'), (1, '[Y0_YErr]')]
+        self.assertEqual(expected, mc.build_labels())
+
+        expected = [(1, '[X0]'), (0, '[Y0]'), (2, '[Y1]'), (3, '[Y2]')]
+        mc.add_x(1)
+        self.assertEqual(expected, mc.build_labels())
+
+        self.assertRaises(ValueError, lambda: mc.add_y_err(ErrorColumn(0, 1, 2)))
+
+    def test_fail_to_add_yerr_for_another_yerr(self):
+        mc = MarkedColumns()
+        mc.add_y(0)
+        mc.add_y(1)
+        mc.add_y(2)
+        mc.add_y(3)
+
+        mc.add_y_err(ErrorColumn(1, 0, 0))
+        expected = [(0, '[Y0]'), (2, '[Y1]'), (3, '[Y2]'), (1, '[Y0_YErr]')]
+        self.assertEqual(expected, mc.build_labels())
+
+        self.assertRaises(ValueError, lambda: mc.add_y_err(ErrorColumn(0, 1, 2)))
+
+    def test_find_yerr(self):
+        mc = MarkedColumns()
+        mc.add_y(0)
+        mc.add_y(1)
+        mc.add_y(2)
+        mc.add_y(3)
+
+        mc.add_y_err(ErrorColumn(4, 1, 1))
+        expected = {1: 4}
+        self.assertEqual(expected, mc.find_yerr([1]))
+        # Replace the Y column, which has an associated YErr. This should remove the YErr as well
+        mc.add_y_err(ErrorColumn(1, 3, 1))
+        expected = {3: 1}
+        self.assertEqual(expected, mc.find_yerr([0, 1, 2, 3]))
+        mc.add_y_err(ErrorColumn(4, 2, 1))
+        expected = {2: 4, 3: 1}
+        self.assertEqual(expected, mc.find_yerr([0, 1, 2, 3]))
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_model.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..e952ba7719e06ac06091d9a4ee9fe539ca784e13
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_model.py
@@ -0,0 +1,106 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import (absolute_import, division, print_function)
+
+import functools
+import unittest
+
+from mock import Mock
+
+from mantid.kernel import V3D
+from mantidqt.widgets.common.test_mocks.mock_mantid import MockWorkspace
+from mantidqt.widgets.tableworkspacedisplay.model import TableWorkspaceDisplayModel
+
+
+def with_mock_model(func):
+    # type: (callable) -> callable
+    @functools.wraps(func)
+    def wrapper(self):
+        ws = MockWorkspace()
+        model = TableWorkspaceDisplayModel(ws)
+        return func(self, model)
+
+    return wrapper
+
+
+class TableWorkspaceDisplayModelTest(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # Allow the MockWorkspace to work within the model
+        TableWorkspaceDisplayModel.ALLOWED_WORKSPACE_TYPES.append(MockWorkspace)
+
+    def test_get_name(self):
+        ws = MockWorkspace()
+        expected_name = "TEST_WORKSPACE"
+        ws.name = Mock(return_value=expected_name)
+        model = TableWorkspaceDisplayModel(ws)
+
+        self.assertEqual(expected_name, model.get_name())
+
+    def test_raises_with_unsupported_workspace(self):
+        self.assertRaises(ValueError, lambda: TableWorkspaceDisplayModel([]))
+        self.assertRaises(ValueError, lambda: TableWorkspaceDisplayModel(1))
+        self.assertRaises(ValueError, lambda: TableWorkspaceDisplayModel("test_string"))
+
+    @with_mock_model
+    def test_get_v3d_from_str(self, model):
+        """
+        :type model: TableWorkspaceDisplayModel
+        """
+        self.assertEqual(V3D(1, 2, 3), model._get_v3d_from_str("1,2,3"))
+        self.assertEqual(V3D(4, 5, 6), model._get_v3d_from_str("[4,5,6]"))
+
+    @with_mock_model
+    def test_set_cell_data_non_v3d(self, model):
+        """
+        :type model: TableWorkspaceDisplayModel
+        """
+        test_data = 4444
+
+        expected_col = 1111
+        expected_row = 1
+
+        model.set_cell_data(expected_row, expected_col, test_data, False)
+
+        # check that the correct conversion function was retrieved
+        # -> the one for the column for which the data is being set
+        model.ws.setCell.assert_called_once_with(expected_row, expected_col, test_data, notify_replace=False)
+
+    @with_mock_model
+    def test_set_cell_data_v3d(self, model):
+        """
+        :type model: TableWorkspaceDisplayModel
+        """
+        test_data = "[1,2,3]"
+
+        expected_col = 1111
+        expected_row = 1
+
+        model.set_cell_data(expected_row, expected_col, test_data, True)
+
+        # check that the correct conversion function was retrieved
+        # -> the one for the column for which the data is being set
+        model.ws.setCell.assert_called_once_with(expected_row, expected_col, V3D(1, 2, 3), notify_replace=False)
+
+    def test_no_raise_with_supported_workspace(self):
+        from mantid.simpleapi import CreateEmptyTableWorkspace
+        ws = MockWorkspace()
+        expected_name = "TEST_WORKSPACE"
+        ws.name = Mock(return_value=expected_name)
+
+        # no need to assert anything - if the constructor raises the test will fail
+        TableWorkspaceDisplayModel(ws)
+
+        ws = CreateEmptyTableWorkspace()
+        TableWorkspaceDisplayModel(ws)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa1c894bdabbcfd8a262501cd91758f022f369aa
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_presenter.py
@@ -0,0 +1,601 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from mock import Mock, call, patch
+
+from mantidqt.widgets.common.test_mocks.mock_mantid import MockWorkspace
+from mantidqt.widgets.common.test_mocks.mock_plotlib import MockAx, MockPlotLib
+from mantidqt.widgets.common.test_mocks.mock_qt import MockQModelIndex, MockQSelectionModel
+from mantidqt.widgets.tableworkspacedisplay.error_column import ErrorColumn
+from mantidqt.widgets.tableworkspacedisplay.model import TableWorkspaceDisplayModel
+from mantidqt.widgets.tableworkspacedisplay.plot_type import PlotType
+from mantidqt.widgets.tableworkspacedisplay.presenter import TableWorkspaceDisplay
+from mantidqt.widgets.tableworkspacedisplay.view import TableWorkspaceDisplayView
+from mantidqt.widgets.tableworkspacedisplay.workbench_table_widget_item import WorkbenchTableWidgetItem
+
+
+class MockQTable:
+    """
+    Mocks the necessary functions to replace a QTableView on which data is being set.
+    """
+
+    def __init__(self):
+        self.setItem = Mock()
+        self.setRowCount = Mock()
+
+
+def with_mock_presenter(add_selection_model=False, add_plot=False):
+    """
+    Decorators with Arguments are a load of callback fun. Sources that were used for reference:
+
+    https://stackoverflow.com/a/5929165/2823526
+
+    And an answer with a little more description of the logic behind it all
+    https://stackoverflow.com/a/25827070/2823526
+
+    :param add_selection_model: Adds a mock selection model to the presenter
+    :param add_plot: Adds mock plotting to the presenter
+    """
+
+    def real_decorator(func, *args, **kwargs):
+        def wrapper(self, *args):
+            ws = MockWorkspace()
+            view = Mock(spec=TableWorkspaceDisplayView)
+            if add_selection_model:
+                mock_selection_model = MockQSelectionModel(has_selection=True)
+                mock_selection_model.selectedRows = Mock(
+                    return_value=[MockQModelIndex(1, 1), MockQModelIndex(2, 2), MockQModelIndex(3, 3)])
+                mock_selection_model.selectedColumns = Mock(
+                    return_value=[MockQModelIndex(1, 1), MockQModelIndex(2, 2), MockQModelIndex(3, 3)])
+                view.mock_selection_model = mock_selection_model
+                view.selectionModel.return_value = mock_selection_model
+            twd = TableWorkspaceDisplay(ws, view=view)
+            if add_plot:
+                twd.plot = MockPlotLib()
+            return func(self, ws, view, twd, *args)
+
+        return wrapper
+
+    return real_decorator
+
+
+class TableWorkspaceDisplayPresenterTest(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # Allow the MockWorkspace to work within the model
+        TableWorkspaceDisplayModel.ALLOWED_WORKSPACE_TYPES.append(MockWorkspace)
+
+    def assertNotCalled(self, mock):
+        self.assertEqual(0, mock.call_count)
+
+    def test_supports(self):
+        ws = MockWorkspace()
+        # the test will fail if the support check fails - an exception is raised
+        TableWorkspaceDisplay.supports(ws)
+
+    def test_handleItemChanged(self):
+        ws = MockWorkspace()
+        view = Mock(spec=TableWorkspaceDisplayView)
+        twd = TableWorkspaceDisplay(ws, view=view)
+        item = Mock(spec=WorkbenchTableWidgetItem)
+        item.row.return_value = 5
+        item.column.return_value = 5
+        item.data.return_value = "magic parameter"
+        item.is_v3d = False
+
+        twd.handleItemChanged(item)
+
+        item.row.assert_called_once_with()
+        item.column.assert_called_once_with()
+        ws.setCell.assert_called_once_with(5, 5, "magic parameter", notify_replace=False)
+        item.update.assert_called_once_with()
+        item.reset.assert_called_once_with()
+
+    @with_mock_presenter
+    def test_handleItemChanged_raises_ValueError(self, ws, view, twd):
+        item = Mock(spec=WorkbenchTableWidgetItem)
+        item.row.return_value = 5
+        item.column.return_value = 5
+        item.data.return_value = "magic parameter"
+        item.is_v3d = False
+
+        # setCell will throw an exception as a side effect
+        ws.setCell.side_effect = ValueError
+
+        twd.handleItemChanged(item)
+
+        item.row.assert_called_once_with()
+        item.column.assert_called_once_with()
+        ws.setCell.assert_called_once_with(5, 5, "magic parameter")
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.ITEM_CHANGED_INVALID_DATA_MESSAGE)
+        self.assertNotCalled(item.update)
+        item.reset.assert_called_once_with()
+
+    @with_mock_presenter
+    def test_handleItemChanged_raises_Exception(self, ws, view, twd):
+        item = Mock(spec=WorkbenchTableWidgetItem)
+
+        item.row.return_value = ws.ROWS
+        item.column.return_value = ws.COLS
+        item.data.return_value = "magic parameter"
+        item.is_v3d = False
+
+        # setCell will throw an exception as a side effect
+        error_message = "TEST_EXCEPTION_MESSAGE"
+        ws.setCell.side_effect = Exception(error_message)
+
+        twd.handleItemChanged(item)
+
+        item.row.assert_called_once_with()
+        item.column.assert_called_once_with()
+        ws.setCell.assert_called_once_with(ws.ROWS, ws.COLS, "magic parameter")
+        view.show_warning.assert_called_once_with(
+            TableWorkspaceDisplay.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(error_message))
+        self.assertNotCalled(item.update)
+        item.reset.assert_called_once_with()
+
+    @with_mock_presenter
+    def test_update_column_headers(self, ws, view, twd):
+        twd.update_column_headers()
+
+        # setColumnCount is done twice - once in the TableWorkspaceDisplay initialisation, and once in the call
+        # to update_column_headers above
+        view.setColumnCount.assert_has_calls([call(ws.ROWS), call(ws.ROWS)])
+
+    @with_mock_presenter
+    def test_load_data(self, ws, _, twd):
+        mock_table = MockQTable()
+        twd.load_data(mock_table)
+
+        mock_table.setRowCount.assert_called_once_with(ws.ROWS)
+        ws.columnCount.assert_called_once_with()
+        # set item is called on every item of the table
+        self.assertEqual(ws.ROWS * ws.COLS, mock_table.setItem.call_count)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.copy_cells')
+    @with_mock_presenter
+    def test_action_copying(self, ws, view, twd, mock_copy_cells):
+        twd.action_copy_cells()
+        self.assertEqual(1, mock_copy_cells.call_count)
+        twd.action_copy_bin_values()
+        self.assertEqual(2, mock_copy_cells.call_count)
+        twd.action_copy_spectrum_values()
+        self.assertEqual(3, mock_copy_cells.call_count)
+        twd.action_keypress_copy()
+        self.assertEqual(4, mock_copy_cells.call_count)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.DeleteTableRows')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_delete_row(self, ws, view, twd, mock_DeleteTableRows):
+        twd.action_delete_row()
+        mock_DeleteTableRows.assert_called_once_with(ws, "1,2,3")
+        view.mock_selection_model.hasSelection.assert_called_once_with()
+        view.mock_selection_model.selectedRows.assert_called_once_with()
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_delete_row_no_selection(self, ws, view, twd, mock_no_selection_toast):
+        view.mock_selection_model.hasSelection = Mock(return_value=False)
+        twd.action_delete_row()
+        view.mock_selection_model.hasSelection.assert_called_once_with()
+        self.assertEqual(1, mock_no_selection_toast.call_count)
+        self.assertNotCalled(view.mock_selection_model.selectedRows)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns(self, ws, view, twd):
+        result = twd._get_selected_columns()
+        self.assertEqual([1, 2, 3], result)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns_no_selection(self, ws, view, twd, mock_no_selection_toast):
+        view.mock_selection_model.hasSelection = Mock(return_value=False)
+        self.assertRaises(ValueError, twd._get_selected_columns)
+        self.assertEqual(1, mock_no_selection_toast.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns_over_max_selected(self, ws, view, twd):
+        mock_message = "Hi."
+        self.assertRaises(ValueError, twd._get_selected_columns, max_selected=1, message_if_over_max=mock_message)
+        view.show_warning.assert_called_once_with(mock_message)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_selected_columns_has_selected_but_no_columns(self, ws, view, twd, mock_no_selection_toast):
+        """
+        There is a case where the user could have a selection (of cells or rows), but not columns.
+        """
+        view.mock_selection_model.selectedColumns = Mock(return_value=[])
+        self.assertRaises(ValueError, twd._get_selected_columns)
+        self.assertEqual(1, mock_no_selection_toast.call_count)
+        view.mock_selection_model.selectedColumns.assert_called_once_with()
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay')
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.StatisticsOfTableWorkspace')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_statistics_on_columns(self, ws, view, twd, mock_StatisticsOfTableWorkspace,
+                                          mock_TableWorkspaceDisplay):
+        twd.action_statistics_on_columns()
+
+        mock_StatisticsOfTableWorkspace.assert_called_once_with(ws, [1, 2, 3])
+        # check that there was an attempt to make a new TableWorkspaceDisplay window
+        self.assertEqual(1, mock_TableWorkspaceDisplay.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_hide_selected(self, ws, view, twd):
+        twd.action_hide_selected()
+        view.hideColumn.assert_has_calls([call(1), call(2), call(3)])
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_show_all_columns(self, ws, view, twd):
+        view.columnCount = Mock(return_value=15)
+        twd.action_show_all_columns()
+        self.assertEqual(15, view.showColumn.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as(self, ws, view, twd):
+        mock_func = Mock()
+        twd._action_set_as(mock_func)
+
+        self.assertEqual(3, mock_func.call_count)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_x(self, ws, view, twd):
+        twd.action_set_as_x()
+
+        self.assertEqual(3, len(twd.model.marked_columns.as_x))
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y(self, ws, view, twd):
+        twd.action_set_as_y()
+
+        self.assertEqual(3, len(twd.model.marked_columns.as_y))
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_none(self, ws, view, twd):
+        twd.action_set_as_none()
+
+        self.assertEqual(0, len(twd.model.marked_columns.as_x))
+        self.assertEqual(0, len(twd.model.marked_columns.as_y))
+        self.assertEqual(0, len(twd.model.marked_columns.as_y_err))
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y_err(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(1, 1)])
+        twd.action_set_as_y_err(2, "0")
+        self.assertEqual(1, len(twd.model.marked_columns.as_y_err))
+        err_col = twd.model.marked_columns.as_y_err[0]
+        self.assertEqual(1, err_col.column)
+        self.assertEqual(2, err_col.related_y_column)
+        self.assertEqual("0", err_col.label_index)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y_err_too_many_selected(self, ws, view, twd):
+        twd.action_set_as_y_err(2, "0")
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_TO_SET_AS_Y_ERR_MESSAGE)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_set_as_y_err_failed_to_create_ErrorColumn(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(1, 1)])
+        # this will fail as we're trying to set an YErr column for itself -> (try to set col 1 to be YERR for col 1)
+        twd.action_set_as_y_err(1, "0")
+        view.show_warning.assert_called_once_with(ErrorColumn.CANNOT_SET_Y_TO_BE_OWN_YERR_MESSAGE)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_sort(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns = Mock(return_value=[MockQModelIndex(0, 4444)])
+        order = 1
+        twd.action_sort(order)
+        view.sortByColumn.assert_called_once_with(4444, order)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_sort_too_many(self, ws, view, twd):
+        twd.action_sort(1)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_SELECTED_TO_SORT)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_get_plot_function_from_type(self, ws, view, twd):
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.LINEAR)
+        self.assertEqual(result, mock_ax.plot)
+
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.SCATTER)
+        self.assertEqual(result, mock_ax.scatter)
+
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.LINE_AND_SYMBOL)
+        # the function created for LINE_AND_SYMBOL is a decorated ax.plot
+        self.assertTrue("functools.partial" in str(type(result)))
+        self.assertIsNotNone(result)
+
+        mock_ax = MockAx()
+        result = twd._get_plot_function_from_type(mock_ax, PlotType.LINEAR_WITH_ERR)
+        self.assertEqual(result, mock_ax.errorbar)
+
+        invalid_plot_type = 48903479
+        self.assertRaises(ValueError, twd._get_plot_function_from_type, None, invalid_plot_type)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.show_no_selection_to_copy_toast')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_no_selected_columns(self, ws, view, twd, mock_show_no_selection_to_copy_toast):
+        view.mock_selection_model.selectedColumns.return_value = []
+        twd.action_plot(PlotType.LINEAR)
+        mock_show_no_selection_to_copy_toast.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_more_than_one_x(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1), MockQModelIndex(1, 2)]
+        twd.action_set_as_x()
+        twd.action_plot(PlotType.LINEAR)
+        view.mock_selection_model.selectedColumns.assert_has_calls([call(), call()])
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.TOO_MANY_SELECTED_FOR_X)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_x_in_selection(self, ws, view, twd, mock_do_plot):
+        """
+        Test that the plot is successful if there is an X in the selection,
+        and an unmarked column: which is used as the Y data
+        """
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        # set only the first column to be X
+        twd.action_set_as_x()
+        # add a second selected column, that should be used for Y
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1), MockQModelIndex(1, 2)]
+        twd.action_plot(PlotType.LINEAR)
+        mock_do_plot.assert_called_once_with([2], 1, PlotType.LINEAR)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_x_marked_but_not_selected(self, ws, view, twd, mock_do_plot):
+        """
+        Test that the plot is successful if there is no X in the selection, but a column is marked X.
+        The selection contains only an unmarked column, which is used as the Y data
+        """
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        # set only the first column to be X
+        twd.action_set_as_x()
+        # change the selection to a second column, that should be used for Y, but is not marked as anything
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)]
+        twd.action_plot(PlotType.LINEAR)
+        mock_do_plot.assert_called_once_with([2], 1, PlotType.LINEAR)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._do_plot')
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_selection_without_x(self, ws, view, twd, mock_do_plot):
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        twd.action_plot(PlotType.LINEAR)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.NO_COLUMN_MARKED_AS_X)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_action_plot_column_against_itself(self, ws, view, twd):
+        """
+        For example: mark a column as X and then try to do Right click -> plot -> line on it, using it as Y
+        this will fail as it's the same column
+        """
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        # set only the first column to be X
+        twd.action_set_as_x()
+        # change the selection to a second column, that should be used for Y, but is not marked as anything
+        twd.action_plot(PlotType.LINEAR)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.CANNOT_PLOT_AGAINST_SELF_MESSAGE)
+
+    @with_mock_presenter(add_selection_model=True)
+    def test_do_action_plot_with_errors_missing_yerr_for_y_column(self, ws, view, twd):
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)]
+        twd.action_set_as_y()
+
+        twd.action_plot(PlotType.LINEAR_WITH_ERR)
+        view.show_warning.assert_called_once_with(TableWorkspaceDisplay.NO_ASSOCIATED_YERR_FOR_EACH_Y_MESSAGE)
+
+    @patch('mantidqt.widgets.tableworkspacedisplay.presenter.logger.error')
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot__plot_func_throws_error(self, ws, view, twd, mock_logger_error):
+        mock_plot_function = Mock()
+        error_message = "See bottom of keyboard for HEALTH WARNING"
+        mock_plot_function.side_effect = ValueError(error_message)
+        with patch(
+                'mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay._get_plot_function_from_type') \
+                as mock_get_plot_function_from_type:
+            mock_get_plot_function_from_type.return_value = mock_plot_function
+            view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 1)]
+            twd.action_set_as_x()
+
+            view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, 2)]
+            twd.action_plot(PlotType.LINEAR)
+
+            view.show_warning.assert_called_once_with(
+                TableWorkspaceDisplay.PLOT_FUNCTION_ERROR_MESSAGE.format(error_message),
+                TableWorkspaceDisplay.INVALID_DATA_WINDOW_TITLE)
+            mock_logger_error.assert_called_once_with(
+                TableWorkspaceDisplay.PLOT_FUNCTION_ERROR_MESSAGE.format(error_message))
+        self.assertNotCalled(twd.plot.mock_fig.show)
+        self.assertNotCalled(twd.plot.mock_ax.legend)
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_success(self, ws, view, twd):
+        col_as_x = 1
+        col_as_y = 2
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y_data = twd.model.get_column(col_as_y)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y)]
+        twd.action_plot(PlotType.LINEAR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+        col_y_name = twd.model.get_column_header(col_as_y)
+        twd.plot.mock_ax.set_ylabel.assert_called_once_with(col_y_name)
+        twd.plot.mock_ax.plot.assert_called_once_with(expected_x_data, expected_y_data,
+                                                      label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(
+                                                          col_y_name))
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_multiple_y_success(self, ws, view, twd):
+        col_as_x = 1
+        col_as_y1 = 2
+        col_as_y2 = 3
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y1_data = twd.model.get_column(col_as_y1)
+        expected_y2_data = twd.model.get_column(col_as_y2)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1),
+                                                                  MockQModelIndex(1, col_as_y2)]
+        twd.action_plot(PlotType.LINEAR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+
+        col_y1_name = twd.model.get_column_header(col_as_y1)
+        col_y2_name = twd.model.get_column_header(col_as_y2)
+        twd.plot.mock_ax.set_ylabel.assert_has_calls([call(col_y1_name), call(col_y2_name)])
+
+        twd.plot.mock_ax.plot.assert_has_calls([
+            call(expected_x_data, expected_y1_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y1_name)),
+            call(expected_x_data, expected_y2_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y2_name))])
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_success_error_plot(self, ws, view, twd):
+        col_as_x = 1
+        col_as_y = 2
+        col_as_y_err = 3
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y_data = twd.model.get_column(col_as_y)
+        expected_y_err_data = twd.model.get_column(col_as_y_err)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y_err)]
+        twd.action_set_as_y_err(col_as_y, '0')
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y)]
+        twd.action_plot(PlotType.LINEAR_WITH_ERR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+        col_y_name = twd.model.get_column_header(col_as_y)
+        twd.plot.mock_ax.set_ylabel.assert_called_once_with(col_y_name)
+        twd.plot.mock_ax.errorbar.assert_called_once_with(expected_x_data, expected_y_data,
+                                                          label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(
+                                                              col_y_name), yerr=expected_y_err_data)
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+    @with_mock_presenter(add_selection_model=True, add_plot=True)
+    def test_do_action_plot_multiple_y_success_error_plot(self, ws, view, twd):
+        col_as_x = 0
+        col_as_y1 = 1
+        col_as_y1_err = 2
+        col_as_y2 = 3
+        col_as_y2_err = 4
+
+        expected_x_data = twd.model.get_column(col_as_x)
+        expected_y1_data = twd.model.get_column(col_as_y1)
+        expected_y1_err_data = twd.model.get_column(col_as_y1_err)
+        expected_y2_data = twd.model.get_column(col_as_y2)
+        expected_y2_err_data = twd.model.get_column(col_as_y2_err)
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_x)]
+        twd.action_set_as_x()
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1_err)]
+        twd.action_set_as_y_err(col_as_y1, '0')
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y2_err)]
+        twd.action_set_as_y_err(col_as_y2, '1')
+
+        view.mock_selection_model.selectedColumns.return_value = [MockQModelIndex(1, col_as_y1),
+                                                                  MockQModelIndex(1, col_as_y2)]
+        twd.action_plot(PlotType.LINEAR_WITH_ERR)
+
+        twd.plot.subplots.assert_called_once_with(subplot_kw={'projection': 'mantid'})
+        twd.plot.mock_fig.canvas.set_window_title.assert_called_once_with(twd.model.get_name())
+        twd.plot.mock_ax.set_xlabel.assert_called_once_with(twd.model.get_column_header(col_as_x))
+
+        col_y1_name = twd.model.get_column_header(col_as_y1)
+        col_y2_name = twd.model.get_column_header(col_as_y2)
+        twd.plot.mock_ax.set_ylabel.assert_has_calls([call(col_y1_name), call(col_y2_name)])
+
+        twd.plot.mock_ax.errorbar.assert_has_calls([
+            call(expected_x_data, expected_y1_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y1_name), yerr=expected_y1_err_data),
+            call(expected_x_data, expected_y2_data,
+                 label=TableWorkspaceDisplay.COLUMN_DISPLAY_LABEL.format(col_y2_name), yerr=expected_y2_err_data)])
+        twd.plot.mock_fig.show.assert_called_once_with()
+        twd.plot.mock_ax.legend.assert_called_once_with()
+
+    @with_mock_presenter()
+    def test_close_incorrect_workspace(self, ws, view, presenter):
+        presenter.close(ws.TEST_NAME + "123")
+        self.assertNotCalled(view.emit_close)
+        self.assertIsNotNone(presenter.ads_observer)
+
+    @with_mock_presenter()
+    def test_close(self, ws, view, presenter):
+        presenter.close(ws.TEST_NAME)
+        view.emit_close.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_mock_presenter()
+    def test_force_close_even_with_incorrect_name(self, _, view, presenter):
+        # window always closes, regardless of the workspace
+        presenter.force_close()
+        view.emit_close.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_mock_presenter()
+    def test_force_close(self, _, view, presenter):
+        presenter.force_close()
+        view.emit_close.assert_called_once_with()
+        self.assertIsNone(presenter.ads_observer)
+
+    @with_mock_presenter()
+    def test_replace_incorrect_workspace(self, ws, view, presenter):
+        with patch(
+                'mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay.load_data') as mock_load_data:
+            presenter.replace_workspace(ws.TEST_NAME + "123", ws)
+            self.assertNotCalled(mock_load_data)
+            self.assertNotCalled(view.emit_repaint)
+
+    @with_mock_presenter()
+    def test_replace(self, ws, view, presenter):
+        # patch this out after the constructor of the presenter has finished,
+        # so that we reset any calls it might have made
+        with patch(
+                'mantidqt.widgets.tableworkspacedisplay.presenter.TableWorkspaceDisplay.load_data') as mock_load_data:
+            presenter.replace_workspace(ws.TEST_NAME, ws)
+            mock_load_data.assert_called_once_with(view)
+            view.emit_repaint.assert_called_once_with()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_workbench_table_widget_item.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_workbench_table_widget_item.py
new file mode 100644
index 0000000000000000000000000000000000000000..a72a72a11290ca2891b9598279107321f7d73e04
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/test/test_tableworkspacedisplay_workbench_table_widget_item.py
@@ -0,0 +1,111 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from qtpy.QtCore import Qt
+
+from mantid.kernel import V3D
+from mantidqt.widgets.tableworkspacedisplay.workbench_table_widget_item import WorkbenchTableWidgetItem
+
+
+class WorkbenchTableWidgetItemTest(unittest.TestCase):
+    def test_initialise_editable_int(self):
+        """
+        Test that the widget is correctly initialised and the type is correctly kept in the .data call
+        """
+        mock_data = 12
+        w = WorkbenchTableWidgetItem(mock_data, editable=True)
+        self.assertEqual(mock_data, w.display_data)
+        self.assertEqual(mock_data, w.data(Qt.DisplayRole))
+
+    def test_initialise_editable_bool(self):
+        """
+        Test that the widget is correctly initialised and the type is correctly kept in the .data call
+        """
+        mock_data = True
+        w = WorkbenchTableWidgetItem(mock_data, editable=True)
+        self.assertEqual(mock_data, w.display_data)
+        self.assertEqual(mock_data, w.data(Qt.DisplayRole))
+
+    def test_initialise_readonly(self):
+        """
+        Test that the widget converts everything to string if read only
+        :return:
+        """
+        mock_data = 123
+        w = WorkbenchTableWidgetItem(mock_data, editable=False)
+        self.assertEqual(str(mock_data), w.data(Qt.DisplayRole))
+
+        mock_data = 1.3333333
+        w = WorkbenchTableWidgetItem(mock_data, editable=False)
+        self.assertEqual(str(mock_data), w.data(Qt.DisplayRole))
+
+        mock_data = V3D(1, 2, 3)
+        w = WorkbenchTableWidgetItem(mock_data, editable=False)
+        self.assertEqual(str(mock_data), w.data(Qt.DisplayRole))
+
+        mock_data = True
+        w = WorkbenchTableWidgetItem(mock_data, editable=False)
+        self.assertEqual(str(mock_data), w.data(Qt.DisplayRole))
+
+        mock_data = "apples"
+        w = WorkbenchTableWidgetItem(mock_data, editable=False)
+        self.assertEqual(str(mock_data), w.data(Qt.DisplayRole))
+
+    def test_initialise_editable_with_v3d(self):
+        mock_data = V3D(1, 2, 3)
+        w = WorkbenchTableWidgetItem(mock_data, True)
+        self.assertEqual(str(mock_data), w.data(Qt.DisplayRole))
+        # the original data of the V3D is stored as a string too
+        self.assertEqual(str(mock_data), w.display_data)
+
+    def test_initialise_editable_with_float(self):
+        mock_data = 42.00
+        w = WorkbenchTableWidgetItem(mock_data, True)
+        self.assertEqual(mock_data, w.data(Qt.DisplayRole))
+        self.assertEqual(mock_data, w.display_data)
+
+    def test_lt(self):
+        """
+        Test that the widget properly compares with other widgets.
+
+        :return:
+        """
+        w1 = WorkbenchTableWidgetItem(500, editable=False)
+        w2 = WorkbenchTableWidgetItem(1500, editable=False)
+        self.assertTrue(w1 < w2)
+
+        w1 = WorkbenchTableWidgetItem(100.40, editable=False)
+        w2 = WorkbenchTableWidgetItem(100.41, editable=False)
+        self.assertTrue(w1 < w2)
+
+        w1 = WorkbenchTableWidgetItem("apples", editable=False)
+        w2 = WorkbenchTableWidgetItem("potatoes", editable=False)
+        self.assertTrue(w1 < w2)
+
+    def test_reset(self):
+        w = WorkbenchTableWidgetItem(500, editable=False)
+
+        w.display_data = 4444
+        w.reset()
+
+        self.assertEqual(4444, w.data(Qt.DisplayRole))
+
+    def test_update(self):
+        w = WorkbenchTableWidgetItem(500, editable=False)
+        w.setData(Qt.DisplayRole, 4444)
+        w.update()
+        self.assertEqual(4444, w.display_data)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..b885d3c0f7f684c1dbbf1ff2a655f4062ab53798
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/view.py
@@ -0,0 +1,250 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantid workbench.
+#
+#
+from __future__ import (absolute_import, division, print_function)
+
+import sys
+from functools import partial
+
+from qtpy import QtGui
+from qtpy.QtCore import QVariant, Qt, Signal, Slot
+from qtpy.QtGui import QKeySequence
+from qtpy.QtWidgets import (QAction, QHeaderView, QItemEditorFactory, QMenu, QMessageBox,
+                            QStyledItemDelegate, QTableWidget)
+
+import mantidqt.icons
+from mantidqt.widgets.common.observing_view import ObservingView
+from mantidqt.widgets.tableworkspacedisplay.plot_type import PlotType
+
+
+class PreciseDoubleFactory(QItemEditorFactory):
+
+    def __init__(self):
+        QItemEditorFactory.__init__(self)
+
+    def createEditor(self, user_type, parent):
+        widget = super(PreciseDoubleFactory, self).createEditor(user_type, parent)
+        if user_type == QVariant.Double:
+            widget.setFrame(True)
+            widget.setDecimals(16)
+            widget.setRange(sys.float_info.min, sys.float_info.max)
+
+        return widget
+
+
+class TableWorkspaceDisplayView(QTableWidget, ObservingView):
+    close_signal = Signal()
+    rename_signal = Signal(str)
+    repaint_signal = Signal()
+
+    def __init__(self, presenter, parent=None, name=''):
+        super(TableWorkspaceDisplayView, self).__init__(parent)
+
+        self.presenter = presenter
+        self.COPY_ICON = mantidqt.icons.get_icon("fa.files-o")
+        self.DELETE_ROW = mantidqt.icons.get_icon("fa.minus-square-o")
+        self.STATISTICS_ON_ROW = mantidqt.icons.get_icon('fa.fighter-jet')
+        self.GRAPH_ICON = mantidqt.icons.get_icon('fa.line-chart')
+        self.TBD = mantidqt.icons.get_icon('fa.question')
+
+        item_delegate = QStyledItemDelegate(self)
+        item_delegate.setItemEditorFactory(PreciseDoubleFactory())
+        self.setItemDelegate(item_delegate)
+
+        self.setWindowTitle("{} - Mantid".format(name))
+        self.setWindowFlags(Qt.Window)
+
+        self.close_signal.connect(self._run_close)
+        self.rename_signal.connect(self._run_rename)
+        self.repaint_signal.connect(self._run_repaint)
+
+        self.resize(600, 400)
+        self.show()
+
+        header = self.horizontalHeader()
+        header.sectionDoubleClicked.connect(self.handle_double_click)
+
+    def resizeEvent(self, event):
+        QTableWidget.resizeEvent(self, event)
+        header = self.horizontalHeader()
+        header.setSectionResizeMode(QHeaderView.Interactive)
+
+    def emit_repaint(self):
+        self.repaint_signal.emit()
+
+    @Slot()
+    def _run_repaint(self):
+        self.viewport().update()
+
+    @Slot()
+    def _run_close(self):
+        self.close()
+
+    @Slot(str)
+    def _run_rename(self, new_name):
+        self._rename(new_name)
+
+    def handle_double_click(self, section):
+        header = self.horizontalHeader()
+        header.resizeSection(section, header.defaultSectionSize())
+
+    def keyPressEvent(self, event):
+        if event.matches(QKeySequence.Copy):
+            self.presenter.action_keypress_copy()
+            return
+        elif event.key() == Qt.Key_F2 or event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
+            self.edit(self.currentIndex())
+            return
+
+    def set_context_menu_actions(self, table):
+        """
+        Sets up the context menu actions for the table
+        :type table: QTableView
+        :param table: The table whose context menu actions will be set up.
+        :param ws_read_function: The read function used to efficiently retrieve data directly from the workspace
+        """
+        copy_action = QAction(self.COPY_ICON, "Copy", table)
+        copy_action.triggered.connect(self.presenter.action_copy_cells)
+
+        table.setContextMenuPolicy(Qt.ActionsContextMenu)
+        table.addAction(copy_action)
+
+        horizontalHeader = table.horizontalHeader()
+        horizontalHeader.setContextMenuPolicy(Qt.CustomContextMenu)
+        horizontalHeader.customContextMenuRequested.connect(self.custom_context_menu)
+
+        verticalHeader = table.verticalHeader()
+        verticalHeader.setContextMenuPolicy(Qt.ActionsContextMenu)
+        verticalHeader.setSectionResizeMode(QHeaderView.Fixed)
+
+        copy_spectrum_values = QAction(self.COPY_ICON, "Copy", verticalHeader)
+        copy_spectrum_values.triggered.connect(self.presenter.action_copy_spectrum_values)
+
+        delete_row = QAction(self.DELETE_ROW, "Delete Row", verticalHeader)
+        delete_row.triggered.connect(self.presenter.action_delete_row)
+
+        separator2 = self.make_separator(verticalHeader)
+
+        verticalHeader.addAction(copy_spectrum_values)
+        verticalHeader.addAction(separator2)
+        verticalHeader.addAction(delete_row)
+
+    def custom_context_menu(self, position):
+        menu_main = QMenu()
+        plot = QMenu("Plot...", menu_main)
+        plot_line = QAction("Line", plot)
+        plot_line.triggered.connect(partial(self.presenter.action_plot, PlotType.LINEAR))
+
+        plot_line_with_yerr = QAction("Line with Y Errors", plot)
+        plot_line_with_yerr.triggered.connect(partial(self.presenter.action_plot, PlotType.LINEAR_WITH_ERR))
+
+        plot_scatter = QAction("Scatter", plot)
+        plot_scatter.triggered.connect(partial(self.presenter.action_plot, PlotType.SCATTER))
+
+        plot_line_and_points = QAction("Line + Symbol", plot)
+        plot_line_and_points.triggered.connect(partial(self.presenter.action_plot, PlotType.LINE_AND_SYMBOL))
+
+        plot.addAction(plot_line)
+        plot.addAction(plot_line_with_yerr)
+        plot.addAction(plot_scatter)
+        plot.addAction(plot_line_and_points)
+        menu_main.addMenu(plot)
+
+        copy_bin_values = QAction(self.COPY_ICON, "Copy", menu_main)
+        copy_bin_values.triggered.connect(self.presenter.action_copy_bin_values)
+
+        set_as_x = QAction("Set as X", menu_main)
+        set_as_x.triggered.connect(self.presenter.action_set_as_x)
+
+        set_as_y = QAction("Set as Y", menu_main)
+        set_as_y.triggered.connect(self.presenter.action_set_as_y)
+
+        set_as_none = QAction("Set as None", menu_main)
+        set_as_none.triggered.connect(self.presenter.action_set_as_none)
+
+        statistics_on_columns = QAction("Statistics on Columns", menu_main)
+        statistics_on_columns.triggered.connect(self.presenter.action_statistics_on_columns)
+
+        hide_selected = QAction("Hide Selected", menu_main)
+        hide_selected.triggered.connect(self.presenter.action_hide_selected)
+
+        show_all_columns = QAction("Show All Columns", menu_main)
+        show_all_columns.triggered.connect(self.presenter.action_show_all_columns)
+
+        sort_ascending = QAction("Sort Ascending", menu_main)
+        sort_ascending.triggered.connect(partial(self.presenter.action_sort, Qt.AscendingOrder))
+
+        sort_descending = QAction("Sort Descending", menu_main)
+        sort_descending.triggered.connect(partial(self.presenter.action_sort, Qt.DescendingOrder))
+
+        menu_main.addAction(copy_bin_values)
+        menu_main.addAction(self.make_separator(menu_main))
+        menu_main.addAction(set_as_x)
+        menu_main.addAction(set_as_y)
+
+        marked_y_cols = self.presenter.get_columns_marked_as_y()
+        num_y_cols = len(marked_y_cols)
+
+        # If any columns are marked as Y then generate the set error menu
+        if num_y_cols > 0:
+            menu_set_as_y_err = QMenu("Set error for Y...")
+            for label_index in range(num_y_cols):
+                set_as_y_err = QAction("Y{}".format(label_index), menu_main)
+
+                # This is the column index of the Y column for which a YERR column is being added.
+                # The column index is relative to the whole table, this is necessary
+                # so that later the data of the column marked as error can be retrieved
+                related_y_column = marked_y_cols[label_index]
+
+                # label_index here holds the index in the LABEL (multiple Y columns have labels Y0, Y1, YN...)
+                # this is NOT the same as the column relative to the WHOLE table
+                set_as_y_err.triggered.connect(
+                    partial(self.presenter.action_set_as_y_err, related_y_column, label_index))
+                menu_set_as_y_err.addAction(set_as_y_err)
+            menu_main.addMenu(menu_set_as_y_err)
+
+        menu_main.addAction(set_as_none)
+        menu_main.addAction(self.make_separator(menu_main))
+        menu_main.addAction(statistics_on_columns)
+        menu_main.addAction(self.make_separator(menu_main))
+        menu_main.addAction(hide_selected)
+        menu_main.addAction(show_all_columns)
+        menu_main.addAction(self.make_separator(menu_main))
+        menu_main.addAction(sort_ascending)
+        menu_main.addAction(sort_descending)
+
+        menu_main.exec_(self.mapToGlobal(position))
+
+    def make_separator(self, horizontalHeader):
+        separator1 = QAction(horizontalHeader)
+        separator1.setSeparator(True)
+        return separator1
+
+    @staticmethod
+    def copy_to_clipboard(data):
+        """
+        Uses the QGuiApplication to copy to the system clipboard.
+
+        :type data: str
+        :param data: The data that will be copied to the clipboard
+        :return:
+        """
+        cb = QtGui.QGuiApplication.clipboard()
+        cb.setText(data, mode=cb.Clipboard)
+
+    def ask_confirmation(self, message, title="Mantid Workbench"):
+        """
+        :param message:
+        :return:
+        """
+        reply = QMessageBox.question(self, title, message, QMessageBox.Yes, QMessageBox.No)
+        return True if reply == QMessageBox.Yes else False
+
+    def show_warning(self, message, title="Mantid Workbench"):
+        QMessageBox.warning(self, title, message)
diff --git a/qt/python/mantidqt/widgets/tableworkspacedisplay/workbench_table_widget_item.py b/qt/python/mantidqt/widgets/tableworkspacedisplay/workbench_table_widget_item.py
new file mode 100644
index 0000000000000000000000000000000000000000..31086a8dd48ab43c1a9c538f16e665abe7890f70
--- /dev/null
+++ b/qt/python/mantidqt/widgets/tableworkspacedisplay/workbench_table_widget_item.py
@@ -0,0 +1,61 @@
+from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QTableWidgetItem
+
+from mantid.kernel import V3D
+
+
+class WorkbenchTableWidgetItem(QTableWidgetItem):
+    def __init__(self, data, editable=False):
+        # if not editable just initialise the ItemWidget as string
+        if isinstance(data, V3D):
+            self.is_v3d = True
+        else:
+            self.is_v3d = False
+
+        if not editable:
+            QTableWidgetItem.__init__(self, str(data))
+            self.setFlags(self.flags() & ~Qt.ItemIsEditable)
+            return
+
+        QTableWidgetItem.__init__(self)
+
+        if isinstance(data, V3D):
+            data = str(data)
+
+        self.display_data = data
+        # this will correctly turn all number cells into number types
+        self.reset()
+
+    def _get_v3d_from_str(self, string):
+        if '[' in string and ']' in string:
+            string = string[1:-1]
+        if ',' in string:
+            return V3D(*[float(x) for x in string.split(',')])
+        else:
+            raise RuntimeError("'{}' is not a valid V3D string.".format(string))
+
+    def __lt__(self, other):
+        """
+        Overrides the comparison to other items. Used to provide correct sorting for types Qt doesn't handle
+        like V3D. Additionally, it makes sure strings are converted to floats for correct comparison.
+
+        This is necessary because if the data is a float then it is stored as a string.
+
+        :type other: WorkbenchTableWidgetItem
+        :param other: Other item that will be compared against
+        :return:
+        """
+        if self.is_v3d:
+            return self._get_v3d_from_str(self.data(Qt.DisplayRole)) < self._get_v3d_from_str(
+                other.data(Qt.DisplayRole))
+        try:
+            # if the data can be parsed as numbers then compare properly, otherwise default to the Qt implementation
+            return float(self.data(Qt.DisplayRole)) < float(other.data(Qt.DisplayRole))
+        except:
+            return super(WorkbenchTableWidgetItem, self).__lt__(other)
+
+    def reset(self):
+        self.setData(Qt.DisplayRole, self.display_data)
+
+    def update(self):
+        self.display_data = self.data(Qt.DisplayRole)
diff --git a/qt/python/mantidqt/widgets/test/test_fitpropertybrowser.py b/qt/python/mantidqt/widgets/test/test_fitpropertybrowser.py
new file mode 100644
index 0000000000000000000000000000000000000000..47ef66ca53a755a669a5df37650bf78280076f93
--- /dev/null
+++ b/qt/python/mantidqt/widgets/test/test_fitpropertybrowser.py
@@ -0,0 +1,108 @@
+from __future__ import (absolute_import, division, print_function)
+import platform
+import sys
+import unittest
+
+from qtpy import PYQT_VERSION
+from qtpy.QtWidgets import QApplication
+from qtpy.QtCore import Qt, QMetaObject
+
+from mantid import FrameworkManager
+from mantidqt.utils.qt.test.gui_window_test import GuiWindowTest
+from mantidqt.widgets.fitpropertybrowser import FitPropertyBrowserBase
+
+
+def on_ubuntu_or_darwin():
+    return ('Ubuntu' in platform.platform() and sys.version[0] == '2' or
+            sys.platform == 'darwin' and PYQT_VERSION[0] == '4')
+
+
+@unittest.skipIf(on_ubuntu_or_darwin(), "Popups don't show on ubuntu with python 2. Unskip when switched to xvfb."
+                                        "Qt4 has a bug on macs which is fixed in Qt5.")
+class TestFitPropertyBrowser(GuiWindowTest):
+
+    def create_widget(self):
+        return FitPropertyBrowserBase()
+
+    def start_setup_menu(self):
+        self.click_button('button_Setup')
+        return self.wait_for_popup()
+
+    def start_find_peaks(self):
+        self.trigger_action('action_FindPeaks')
+
+    def start_manage_setup(self):
+        a, pm = self.get_action('action_ManageSetup', get_menu=True)
+        pm.setActiveAction(a)
+        m = a.menu()
+        m.show()
+        return self.wait_for_popup()
+
+    def start_load_from_string(self):
+        self.trigger_action('action_LoadFromString')
+        return self.wait_for_modal()
+
+    def set_function_string_blah(self):
+        self.set_function_string('blah')
+        return self.wait_for_modal()
+
+    def set_function_string_linear(self):
+        self.set_function_string('name=LinearBackground')
+        return self.wait_for_true(lambda: self.widget.sizeOfFunctionsGroup() == 3)
+
+    def set_function_string(self, text):
+        box = self.get_active_modal_widget()
+        box.setTextValue(text)
+        QMetaObject.invokeMethod(box, 'accept', Qt.QueuedConnection)
+
+    def test_find_peaks_no_workspace(self):
+        yield self.start_setup_menu()
+        m = self.get_menu('menu_Setup')
+        self.assertTrue(m.isVisible())
+        self.start_find_peaks()
+        yield self.wait_for_modal()
+        box = self.get_active_modal_widget()
+        self.assertEqual(box.text(), 'Workspace name is not set')
+        box.close()
+
+    def test_load_from_string_blah(self):
+        yield self.start_setup_menu()
+        yield self.start_manage_setup()
+        yield self.start_load_from_string()
+        yield self.set_function_string_blah()
+        box = self.get_active_modal_widget()
+        self.assertEqual(box.text(),
+                         "Unexpected exception caught:\n\nError in input string to FunctionFactory\nblah")
+        box.close()
+
+    def test_load_from_string_lb(self):
+        yield self.start_setup_menu()
+        yield self.start_manage_setup()
+        yield self.start_load_from_string()
+        yield self.set_function_string_linear()
+        a = self.widget.getFittingFunction()
+        self.assertEqual(a, 'name=LinearBackground,A0=0,A1=0')
+        self.assertEqual(self.widget.sizeOfFunctionsGroup(), 3)
+
+    def test_copy_to_clipboard(self):
+        self.widget.loadFunction('name=LinearBackground,A0=0,A1=0')
+        yield self.start_setup_menu()
+        yield self.start_manage_setup()
+        QApplication.clipboard().clear()
+        self.trigger_action('action_CopyToClipboard')
+        yield self.wait_for_true(lambda: QApplication.clipboard().text() != '')
+        self.assertEqual(QApplication.clipboard().text(), 'name=LinearBackground,A0=0,A1=0')
+
+    def test_clear_model(self):
+        self.widget.loadFunction('name=LinearBackground,A0=0,A1=0')
+        self.assertEqual(self.widget.sizeOfFunctionsGroup(), 3)
+        yield self.start_setup_menu()
+        yield self.start_manage_setup()
+        self.trigger_action('action_ClearModel')
+        yield self.wait_for_true(lambda: self.widget.sizeOfFunctionsGroup() == 2)
+        self.assertEqual(self.widget.sizeOfFunctionsGroup(), 2)
+
+
+if __name__ == '__main__':
+    unittest.main()
+    FrameworkManager.clear()
diff --git a/qt/scientific_interfaces/DynamicPDF/DPDFFourierTransform.cpp b/qt/scientific_interfaces/DynamicPDF/DPDFFourierTransform.cpp
index 64ec5400eeb84cd7c52bc2dde727a90fb128fe08..65c4ccb3167814e93ce8e8e98919f410c2281f77 100644
--- a/qt/scientific_interfaces/DynamicPDF/DPDFFourierTransform.cpp
+++ b/qt/scientific_interfaces/DynamicPDF/DPDFFourierTransform.cpp
@@ -101,11 +101,11 @@ void FourierTransform::extractResidualsHistogram(
     // use modelWorkspace as template for the residuals workspace
     auto residualsWorkspace =
         Mantid::API::WorkspaceFactory::Instance().create(modelWorkspace, 1);
-    residualsWorkspace->dataX(0) = modelWorkspace->dataX(0);
-    residualsWorkspace->dataY(0) =
-        modelWorkspace->dataY(2); // residuals is the third spectrum
-    residualsWorkspace->dataE(0) =
-        modelWorkspace->dataE(0); // errors are coming from experiment
+    residualsWorkspace->setSharedX(0, modelWorkspace->sharedX(0));
+    // residuals is the third spectrum
+    residualsWorkspace->setSharedY(0, modelWorkspace->sharedY(2));
+    // errors are coming from experiment
+    residualsWorkspace->setSharedE(0, modelWorkspace->sharedE(0));
     Mantid::API::AnalysisDataService::Instance().addOrReplace(
         m_residualsName, residualsWorkspace);
   } catch (std::exception &e) {
diff --git a/qt/scientific_interfaces/General/MantidEV.cpp b/qt/scientific_interfaces/General/MantidEV.cpp
index 87b6ae7c39d4666fd90dabb78baad99bce24f149..69647e846e8a5bd4cf131026a3da87ef249baf0c 100644
--- a/qt/scientific_interfaces/General/MantidEV.cpp
+++ b/qt/scientific_interfaces/General/MantidEV.cpp
@@ -36,17 +36,18 @@ RunLoadAndConvertToMD::RunLoadAndConvertToMD(
     const std::string &ev_ws_name, const std::string &md_ws_name,
     const double modQ, const double minQ, const double maxQ,
     const bool do_lorentz_corr, const bool load_data, const bool load_det_cal,
-    const std::string &det_cal_file, const std::string &det_cal_file2)
+    const std::string &det_cal_file, const std::string &det_cal_file2,
+    const std::string &axisCORELLI)
     : worker(worker), file_name(file_name), ev_ws_name(ev_ws_name),
       md_ws_name(md_ws_name), modQ(modQ), minQ(minQ), maxQ(maxQ),
       do_lorentz_corr(do_lorentz_corr), load_data(load_data),
       load_det_cal(load_det_cal), det_cal_file(det_cal_file),
-      det_cal_file2(det_cal_file2) {}
+      det_cal_file2(det_cal_file2), axisCORELLI(axisCORELLI) {}
 
 void RunLoadAndConvertToMD::run() {
   worker->loadAndConvertToMD(file_name, ev_ws_name, md_ws_name, modQ, minQ,
                              maxQ, do_lorentz_corr, load_data, load_det_cal,
-                             det_cal_file, det_cal_file2);
+                             det_cal_file, det_cal_file2, axisCORELLI);
 }
 
 /**
@@ -57,18 +58,19 @@ RunFindPeaks::RunFindPeaks(MantidEVWorker *worker,
                            const std::string &md_ws_name,
                            const std::string &peaks_ws_name, double max_abc,
                            size_t num_to_find, double min_intensity,
-                           double minQPeaks, double maxQPeaks)
+                           double minQPeaks, double maxQPeaks,
+                           const std::string &file_name)
     : worker(worker), ev_ws_name(ev_ws_name), md_ws_name(md_ws_name),
       peaks_ws_name(peaks_ws_name), max_abc(max_abc), num_to_find(num_to_find),
-      min_intensity(min_intensity), minQPeaks(minQPeaks), maxQPeaks(maxQPeaks) {
-}
+      min_intensity(min_intensity), minQPeaks(minQPeaks), maxQPeaks(maxQPeaks),
+      file_name(file_name) {}
 
 /**
  *  Class to call findPeaks in a separate thread.
  */
 void RunFindPeaks::run() {
   worker->findPeaks(ev_ws_name, md_ws_name, peaks_ws_name, max_abc, num_to_find,
-                    min_intensity, minQPeaks, maxQPeaks);
+                    min_intensity, minQPeaks, maxQPeaks, file_name);
 }
 
 /**
@@ -477,6 +479,7 @@ void MantidEV::setDefaultState_slot() {
   m_uiForm.IntegrateEdge_ckbx->setChecked(true);
   m_uiForm.Cylinder_ckbx->setChecked(false);
   m_uiForm.CylinderProfileFit_cmbx->setCurrentIndex(5);
+  m_uiForm.axisCORELLI_cmbx->setCurrentIndex(0);
   m_uiForm.TwoDFitIntegration_rbtn->setChecked(false);
   m_uiForm.FitRebinParams_ledt->setText("1000,-0.004,16000");
   m_uiForm.NBadEdgePixels_ledt->setText("5");
@@ -570,12 +573,14 @@ void MantidEV::selectWorkspace_slot() {
                    "Calibration is selected");
       return;
     }
+    std::string axisCORELLI =
+        m_uiForm.axisCORELLI_cmbx->currentText().toStdString();
 
     RunLoadAndConvertToMD *runner = new RunLoadAndConvertToMD(
         worker, file_name, ev_ws_name, md_ws_name, modQ, minQ, maxQ,
         m_uiForm.LorentzCorrection_ckbx->isChecked(),
         m_uiForm.loadDataGroupBox->isChecked(), load_det_cal, det_cal_file,
-        det_cal_file2);
+        det_cal_file2, axisCORELLI);
     bool running = m_thread_pool->tryStart(runner);
     if (!running)
       errorMessage("Failed to start Load and ConvertToMD thread...previous "
@@ -715,9 +720,11 @@ void MantidEV::findPeaks_slot() {
 
     std::string ev_ws_name =
         m_uiForm.SelectEventWorkspace_ledt->text().trimmed().toStdString();
-    RunFindPeaks *runner =
-        new RunFindPeaks(worker, ev_ws_name, md_ws_name, peaks_ws_name, max_abc,
-                         num_to_find, min_intensity, minQPeaks, maxQPeaks);
+    std::string file_name =
+        m_uiForm.EventFileName_ledt->text().trimmed().toStdString();
+    RunFindPeaks *runner = new RunFindPeaks(
+        worker, ev_ws_name, md_ws_name, peaks_ws_name, max_abc, num_to_find,
+        min_intensity, minQPeaks, maxQPeaks, file_name);
 
     bool running = m_thread_pool->tryStart(runner);
     if (!running)
@@ -1761,6 +1768,7 @@ void MantidEV::setEnabledSphereIntParams_slot(bool on) {
   m_uiForm.CylinderLength_ledt->setEnabled(on);
   m_uiForm.CylinderPercentBkg_ledt->setEnabled(on);
   m_uiForm.CylinderProfileFit_cmbx->setEnabled(on);
+  m_uiForm.axisCORELLI_cmbx->setEnabled(on);
   m_uiForm.AdaptiveQBkg_ckbx->setEnabled(on);
   m_uiForm.AdaptiveQMult_ledt->setEnabled(on);
 }
@@ -2083,6 +2091,8 @@ void MantidEV::saveSettings(const std::string &filename) {
   state->setValue("Cylinder_ckbx", m_uiForm.Cylinder_ckbx->isChecked());
   state->setValue("CylinderProfileFit_cmbx",
                   m_uiForm.CylinderProfileFit_cmbx->currentIndex());
+  state->setValue("axisCORELLI_cmbx",
+                  m_uiForm.axisCORELLI_cmbx->currentIndex());
   state->setValue("TwoDFitIntegration_rbtn",
                   m_uiForm.TwoDFitIntegration_rbtn->isChecked());
   state->setValue("FitRebinParams_ledt", m_uiForm.FitRebinParams_ledt->text());
@@ -2214,6 +2224,7 @@ void MantidEV::loadSettings(const std::string &filename) {
   restore(state, "IntegrateEdge_ckbx", m_uiForm.IntegrateEdge_ckbx);
   restore(state, "Cylinder_ckbx", m_uiForm.Cylinder_ckbx);
   restore(state, "CylinderProfileFit_cmbx", m_uiForm.CylinderProfileFit_cmbx);
+  restore(state, "axisCORELLI_cmbx", m_uiForm.axisCORELLI_cmbx);
   restore(state, "TwoDFitIntegration_rbtn", m_uiForm.TwoDFitIntegration_rbtn);
   restore(state, "FitRebinParams_ledt", m_uiForm.FitRebinParams_ledt);
   restore(state, "NBadEdgePixels_ledt", m_uiForm.NBadEdgePixels_ledt);
diff --git a/qt/scientific_interfaces/General/MantidEV.h b/qt/scientific_interfaces/General/MantidEV.h
index 79068fd7ad09e3caa1a87f495578b345d13c8f36..224fa5ac5816b9dfccdca47becdcfc2269b054db 100644
--- a/qt/scientific_interfaces/General/MantidEV.h
+++ b/qt/scientific_interfaces/General/MantidEV.h
@@ -44,7 +44,8 @@ public:
       const std::string &ev_ws_name, const std::string &md_ws_name,
       const double modQ, const double minQ, const double maxQ,
       const bool do_lorentz_corr, const bool load_data, const bool load_det_cal,
-      const std::string &det_cal_file, const std::string &det_cal_file2);
+      const std::string &det_cal_file, const std::string &det_cal_file2,
+      const std::string &axisCORELLI);
 
   /// Calls worker->loadAndConvertToMD from a separate thread
   void run() override;
@@ -62,6 +63,7 @@ private:
   bool load_det_cal;
   std::string det_cal_file;
   std::string det_cal_file2;
+  std::string axisCORELLI;
 };
 
 /// Local class to run FindPeaks in a Non-Qt thread.
@@ -71,7 +73,8 @@ public:
   RunFindPeaks(MantidEVWorker *worker, const std::string &ev_ws_name,
                const std::string &md_ws_name, const std::string &peaks_ws_name,
                double max_abc, size_t num_to_find, double min_intensity,
-               double minQPeaks, double maxQPeaks);
+               double minQPeaks, double maxQPeaks,
+               const std::string &file_name);
 
   /// Calls worker->findPeaks from a separate thread
   void run() override;
@@ -86,6 +89,7 @@ private:
   double min_intensity;
   double minQPeaks;
   double maxQPeaks;
+  std::string file_name;
 };
 
 /// Local class to run PredictPeaks in a Non-Qt thread.
diff --git a/qt/scientific_interfaces/General/MantidEV.ui b/qt/scientific_interfaces/General/MantidEV.ui
index 4d8b7f7a342ae746fdf30672e16874a81bdac0e5..f15f2ae0b2b134da90d71e0a180c3e89c8bf8b05 100644
--- a/qt/scientific_interfaces/General/MantidEV.ui
+++ b/qt/scientific_interfaces/General/MantidEV.ui
@@ -60,7 +60,7 @@
        <string/>
       </property>
       <property name="currentIndex">
-       <number>1</number>
+       <number>0</number>
       </property>
       <widget class="QWidget" name="SelectData">
        <property name="sizePolicy">
@@ -120,7 +120,7 @@
              <x>0</x>
              <y>0</y>
              <width>903</width>
-             <height>696</height>
+             <height>679</height>
             </rect>
            </property>
            <property name="minimumSize">
@@ -465,6 +465,45 @@
                    </item>
                   </layout>
                  </item>
+                 <item>
+                  <widget class="QComboBox" name="axisCORELLI_cmbx">
+                   <item>
+                    <property name="text">
+                     <string>Select Goniometer Axis for CORELLI only</string>
+                    </property>
+                   </item>
+                   <item>
+                    <property name="text">
+                     <string>BL9:Mot:Sample:Axis1</string>
+                    </property>
+                   </item>
+                   <item>
+                    <property name="text">
+                     <string>BL9:Mot:Sample:Axis2</string>
+                    </property>
+                   </item>
+                   <item>
+                    <property name="text">
+                     <string>BL9:Mot:Sample:Axis3</string>
+                    </property>
+                   </item>
+                   <item>
+                    <property name="text">
+                     <string>BL9:Mot:Sample:Axis4</string>
+                    </property>
+                   </item>
+                   <item>
+                    <property name="text">
+                     <string>BL9:Mot:Sample:Axis5</string>
+                    </property>
+                   </item>
+                   <item>
+                    <property name="text">
+                     <string>BL9:Mot:Sample:Axis6</string>
+                    </property>
+                   </item>
+                  </widget>
+                 </item>
                  <item>
                   <widget class="QCheckBox" name="LoadDetCal_ckbx">
                    <property name="toolTip">
@@ -662,8 +701,8 @@
             <rect>
              <x>0</x>
              <y>0</y>
-             <width>903</width>
-             <height>696</height>
+             <width>343</width>
+             <height>436</height>
             </rect>
            </property>
            <layout class="QVBoxLayout" name="verticalLayout_10">
@@ -1133,8 +1172,8 @@
             <rect>
              <x>0</x>
              <y>0</y>
-             <width>903</width>
-             <height>696</height>
+             <width>393</width>
+             <height>759</height>
             </rect>
            </property>
            <layout class="QVBoxLayout" name="verticalLayout_12">
@@ -1975,8 +2014,8 @@
             <rect>
              <x>0</x>
              <y>0</y>
-             <width>903</width>
-             <height>696</height>
+             <width>317</width>
+             <height>360</height>
             </rect>
            </property>
            <layout class="QVBoxLayout" name="verticalLayout_14">
@@ -2570,8 +2609,8 @@
             <rect>
              <x>0</x>
              <y>0</y>
-             <width>903</width>
-             <height>696</height>
+             <width>362</width>
+             <height>202</height>
             </rect>
            </property>
            <layout class="QVBoxLayout" name="verticalLayout_16">
@@ -2824,8 +2863,8 @@
             <rect>
              <x>0</x>
              <y>0</y>
-             <width>903</width>
-             <height>696</height>
+             <width>560</width>
+             <height>764</height>
             </rect>
            </property>
            <layout class="QVBoxLayout" name="verticalLayout_17">
diff --git a/qt/scientific_interfaces/General/MantidEVWorker.cpp b/qt/scientific_interfaces/General/MantidEVWorker.cpp
index 8f0e29c6a676ef0e26d3f4a9744dd77115c3eccc..8cf9a5822bc005aa31755faea6d9cab0abf06e06 100644
--- a/qt/scientific_interfaces/General/MantidEVWorker.cpp
+++ b/qt/scientific_interfaces/General/MantidEVWorker.cpp
@@ -19,9 +19,11 @@
 #include "MantidEVWorker.h"
 #include "MantidGeometry/Crystal/IPeak.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
+#include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidKernel/EmptyValues.h"
 #include "MantidKernel/Logger.h"
 #include <exception>
+#include <string>
 
 namespace MantidQt {
 namespace CustomInterfaces {
@@ -159,6 +161,7 @@ bool MantidEVWorker::isEventWorkspace(const std::string &event_ws_name) {
  *  @param det_cal_file     Fully qualified name of the .DetCal file.
  *  @param det_cal_file2    Fully qualified name of the second .DetCal
  *                          file for the second panel on SNAP.
+ *  @param axisCORELLI      Which axis for phi for CORELLI goniometer
  *
  *  @return true if the file was loaded and MD workspace was
  *          successfully created.
@@ -168,7 +171,7 @@ bool MantidEVWorker::loadAndConvertToMD(
     const std::string &md_ws_name, const double modQ, const double minQ,
     const double maxQ, const bool do_lorentz_corr, const bool load_data,
     const bool load_det_cal, const std::string &det_cal_file,
-    const std::string &det_cal_file2) {
+    const std::string &det_cal_file2, const std::string &axisCORELLI) {
   try {
     IAlgorithm_sptr alg;
     if (load_data) {
@@ -183,8 +186,7 @@ bool MantidEVWorker::loadAndConvertToMD(
         alg->setProperty("FilterByTofMin", 500.0);
         alg->setProperty("FilterByTofMax", 16666.0);
       }
-      alg->setProperty("LoadMonitors", true);
-
+      alg->setProperty("LoadMonitors", false);
       if (!alg->execute())
         return false;
 
@@ -196,6 +198,14 @@ bool MantidEVWorker::loadAndConvertToMD(
 
         if (!alg->execute())
           return false;
+      } else if (axisCORELLI.compare(
+                     "Select Goniometer Axis for CORELLI only") != 0) {
+        const auto &ADS = AnalysisDataService::Instance();
+        Mantid::API::MatrixWorkspace_sptr ev_ws =
+            ADS.retrieveWS<MatrixWorkspace>(ev_ws_name);
+        double phi = ev_ws->run().getLogAsSingleValue(
+            axisCORELLI, Mantid::Kernel::Math::TimeAveragedMean);
+        ev_ws->mutableRun().mutableGoniometer().setRotationAngle(0, phi);
       }
 
       if (load_det_cal) {
@@ -357,13 +367,15 @@ bool MantidEVWorker::convertToHKL(const std::string &ev_ws_name,
  *                        the average intensity will be considered.
  *  @param minQPeaks   Filter with ModQ min
  *  @param maxQPeaks  Filter with ModQmax
+ *  @param file_name  Filename for monitors
  *  @return true if FindPeaksMD completed successfully.
  */
 bool MantidEVWorker::findPeaks(const std::string &ev_ws_name,
                                const std::string &md_ws_name,
                                const std::string &peaks_ws_name, double max_abc,
                                size_t num_to_find, double min_intensity,
-                               double minQPeaks, double maxQPeaks) {
+                               double minQPeaks, double maxQPeaks,
+                               const std::string &file_name) {
   try {
     // Estimate a lower bound on the distance between
     // based on the maximum real space cell edge
@@ -377,21 +389,36 @@ bool MantidEVWorker::findPeaks(const std::string &ev_ws_name,
     const auto &ADS = AnalysisDataService::Instance();
 
     if (alg->execute()) {
-      Mantid::API::MatrixWorkspace_sptr mon_ws =
-          ADS.retrieveWS<MatrixWorkspace>(ev_ws_name + "_monitors");
-      IAlgorithm_sptr int_alg =
-          AlgorithmManager::Instance().create("Integration");
-      int_alg->setProperty("InputWorkspace", mon_ws);
-      int_alg->setProperty("RangeLower", 1000.0);
-      int_alg->setProperty("RangeUpper", 12500.0);
-      int_alg->setProperty("OutputWorkspace",
-                           ev_ws_name + "_integrated_monitor");
-      int_alg->execute();
-      Mantid::API::MatrixWorkspace_sptr int_ws =
-          ADS.retrieveWS<MatrixWorkspace>(ev_ws_name + "_integrated_monitor");
-      double monitor_count = int_ws->y(0)[0];
-      std::cout << "Beam monitor counts used for scaling = " << monitor_count
-                << "\n";
+      double monitor_count = 0;
+      try {
+        IAlgorithm_sptr mon_alg =
+            AlgorithmManager::Instance().create("LoadNexusMonitors");
+        mon_alg->setProperty("Filename", file_name);
+        mon_alg->setProperty("OutputWorkspace", ev_ws_name + "__monitors");
+        mon_alg->execute();
+
+        Mantid::API::MatrixWorkspace_sptr mon_ws =
+            ADS.retrieveWS<MatrixWorkspace>(ev_ws_name + "_monitors");
+        IAlgorithm_sptr int_alg =
+            AlgorithmManager::Instance().create("Integration");
+        int_alg->setProperty("InputWorkspace", mon_ws);
+        int_alg->setProperty("RangeLower", 1000.0);
+        int_alg->setProperty("RangeUpper", 12500.0);
+        int_alg->setProperty("OutputWorkspace",
+                             ev_ws_name + "_integrated_monitor");
+        int_alg->execute();
+        Mantid::API::MatrixWorkspace_sptr int_ws =
+            ADS.retrieveWS<MatrixWorkspace>(ev_ws_name + "_integrated_monitor");
+        monitor_count = int_ws->y(0)[0];
+        g_log.notice() << "Beam monitor counts used for scaling = "
+                       << monitor_count << "\n";
+      } catch (...) {
+        Mantid::API::MatrixWorkspace_sptr ev_ws =
+            ADS.retrieveWS<MatrixWorkspace>(ev_ws_name);
+        monitor_count = ev_ws->run().getProtonCharge() * 1000.0;
+        g_log.notice() << "Beam proton charge used for scaling = "
+                       << monitor_count << "\n";
+      }
 
       IPeaksWorkspace_sptr peaks_ws =
           ADS.retrieveWS<IPeaksWorkspace>(peaks_ws_name);
diff --git a/qt/scientific_interfaces/General/MantidEVWorker.h b/qt/scientific_interfaces/General/MantidEVWorker.h
index b08dfbe665190b0a1adaa2a3e647105c19c78985..a6d66018c759ca410b5cb6ce1a1b877f6641b345 100644
--- a/qt/scientific_interfaces/General/MantidEVWorker.h
+++ b/qt/scientific_interfaces/General/MantidEVWorker.h
@@ -45,14 +45,12 @@ public:
   bool isEventWorkspace(const std::string &event_ws_name);
 
   /// Load and event file and convert to MD workspace
-  bool loadAndConvertToMD(const std::string &file_name,
-                          const std::string &ev_ws_name,
-                          const std::string &md_ws_name, const double modQ,
-                          const double minQ, const double maxQ,
-                          const bool do_lorentz_corr, const bool load_data,
-                          const bool load_det_cal,
-                          const std::string &det_cal_file,
-                          const std::string &det_cal_file2);
+  bool loadAndConvertToMD(
+      const std::string &file_name, const std::string &ev_ws_name,
+      const std::string &md_ws_name, const double modQ, const double minQ,
+      const double maxQ, const bool do_lorentz_corr, const bool load_data,
+      const bool load_det_cal, const std::string &det_cal_file,
+      const std::string &det_cal_file2, const std::string &axisCORELLI);
   bool convertToHKL(const std::string &ev_ws_name,
                     const std::string &md_ws_name, const double minQ,
                     const double maxQ);
@@ -61,7 +59,7 @@ public:
   bool findPeaks(const std::string &ev_ws_name, const std::string &md_ws_name,
                  const std::string &peaks_ws_name, double max_abc,
                  size_t num_to_find, double min_intensity, double minQPeaks,
-                 double maxQPeaks);
+                 double maxQPeaks, const std::string &file_name);
 
   /// Predict peaks and overwrite the peaks workspace
   bool predictPeaks(const std::string &peaks_ws_name, double min_pred_wl,
diff --git a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp
index f780905f3bcdaf5d78d3b5d08061a15f4a68a864..78a2d923fa8f8d3bc10d73c48d98753becbe763e 100644
--- a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp
+++ b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp
@@ -261,8 +261,9 @@ void ApplyAbsorptionCorrections::run() {
         boost::dynamic_pointer_cast<MatrixWorkspace>(corrections->getItem(i));
 
     // Check for matching binning
-    if (sampleWs && (factorWs->blocksize() != sampleWs->blocksize() &&
-                     factorWs->blocksize() != 1)) {
+    const auto factorBlocksize = factorWs->blocksize();
+    if (sampleWs &&
+        (factorBlocksize != sampleWs->blocksize() && factorBlocksize != 1)) {
       int result;
       if (interpolateAll) {
         result = QMessageBox::Yes;
diff --git a/qt/scientific_interfaces/Indirect/CMakeLists.txt b/qt/scientific_interfaces/Indirect/CMakeLists.txt
index f3319726973fef3bf1a82681c244275ec6742406..16a1efc40fb38d955d6920a71c0fdb3900ea7208 100644
--- a/qt/scientific_interfaces/Indirect/CMakeLists.txt
+++ b/qt/scientific_interfaces/Indirect/CMakeLists.txt
@@ -81,6 +81,7 @@ set ( INC_FILES
 	DensityOfStates.h
 	Elwin.h
 	IAddWorkspaceDialog.h
+	IIndirectFitDataView.h
 	IIndirectFitPlotView.h
 	ILLEnergyTransfer.h
 	ISISCalibration.h
@@ -148,6 +149,8 @@ set ( MOC_FILES
     CorrectionsTab.h
     DensityOfStates.h
     Elwin.h
+    IAddWorkspaceDialog.h
+    IIndirectFitDataView.h
     IIndirectFitPlotView.h
     ILLEnergyTransfer.h
     IndirectAddWorkspaceDialog.h
diff --git a/qt/scientific_interfaces/Indirect/ConvFit.cpp b/qt/scientific_interfaces/Indirect/ConvFit.cpp
index ec038f7f165c2039f340a79ac2c508310b7b38a0..fc8875159bf16670c3037c412ffe81df4418b84b 100644
--- a/qt/scientific_interfaces/Indirect/ConvFit.cpp
+++ b/qt/scientific_interfaces/Indirect/ConvFit.cpp
@@ -53,9 +53,9 @@ void ConvFit::setupFitTab() {
   setConvolveMembers(true);
 
   setSampleWSSuffices({"_red", "_sqw"});
-  setSampleFBSuffices({"_red.nxs", "_sqw.nxs"});
+  setSampleFBSuffices({"_red.nxs", "_sqw.nxs", "_sqw.dave"});
   setResolutionWSSuffices({"_res", "_red", "_sqw"});
-  setResolutionFBSuffices({"_res.nxs", "_red.nxs", "_sqw.nxs"});
+  setResolutionFBSuffices({"_res.nxs", "_red.nxs", "_sqw.nxs", "_sqw.dave"});
 
   // Initialise fitTypeStrings
   m_fitStrings["None"] = "";
@@ -66,7 +66,7 @@ void ConvFit::setupFitTab() {
   m_fitStrings["ElasticDiffSphere"] = "EDS";
   m_fitStrings["ElasticDiffRotDiscreteCircle"] = "EDC";
   m_fitStrings["StretchedExpFT"] = "SFT";
-  m_fitStrings["TeixeiraWaterSQE"] = "Teixeria1L";
+  m_fitStrings["Teixeira Water"] = "TxWater";
 
   auto &functionFactory = FunctionFactory::Instance();
   auto lorentzian = functionFactory.createFunction("Lorentzian");
diff --git a/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.cpp b/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.cpp
index 46b0024440a929a37335dbfe0191794a930d02b4..6bd71d1e13dec43f2bbcc17f8fc3caa251b613ea 100644
--- a/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.cpp
+++ b/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.cpp
@@ -8,6 +8,7 @@
 
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidQtWidgets/Common/SignalBlocker.h"
 
 #include <boost/optional.hpp>
 
@@ -18,6 +19,14 @@ MatrixWorkspace_sptr getWorkspace(const std::string &name) {
   return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name);
 }
 
+bool doesExistInADS(std::string const &workspaceName) {
+  return AnalysisDataService::Instance().doesExist(workspaceName);
+}
+
+bool validWorkspace(std::string const &name) {
+  return !name.empty() && doesExistInADS(name);
+}
+
 boost::optional<std::size_t> maximumIndex(MatrixWorkspace_sptr workspace) {
   if (workspace) {
     const auto numberOfHistograms = workspace->getNumberHistograms();
@@ -80,6 +89,8 @@ ConvFitAddWorkspaceDialog::ConvFitAddWorkspaceDialog(QWidget *parent)
           SLOT(workspaceChanged(const QString &)));
   connect(m_uiForm.ckAllSpectra, SIGNAL(stateChanged(int)), this,
           SLOT(selectAllSpectra(int)));
+  connect(m_uiForm.pbAdd, SIGNAL(clicked()), this, SIGNAL(addData()));
+  connect(m_uiForm.pbClose, SIGNAL(clicked()), this, SIGNAL(closeDialog()));
 }
 
 std::string ConvFitAddWorkspaceDialog::workspaceName() const {
@@ -113,8 +124,9 @@ void ConvFitAddWorkspaceDialog::setResolutionFBSuffices(
 }
 
 void ConvFitAddWorkspaceDialog::selectAllSpectra(int state) {
-  if (state == Qt::Checked) {
-    m_uiForm.leWorkspaceIndices->setText(getIndexString(workspaceName()));
+  auto const name = workspaceName();
+  if (validWorkspace(name) && state == Qt::Checked) {
+    m_uiForm.leWorkspaceIndices->setText(getIndexString(name));
     m_uiForm.leWorkspaceIndices->setEnabled(false);
   } else
     m_uiForm.leWorkspaceIndices->setEnabled(true);
@@ -131,8 +143,10 @@ void ConvFitAddWorkspaceDialog::workspaceChanged(const QString &workspaceName) {
 
 void ConvFitAddWorkspaceDialog::setWorkspace(const std::string &workspace) {
   setAllSpectraSelectionEnabled(true);
-  if (m_uiForm.ckAllSpectra->isChecked())
+  if (m_uiForm.ckAllSpectra->isChecked()) {
     m_uiForm.leWorkspaceIndices->setText(getIndexString(workspace));
+    m_uiForm.leWorkspaceIndices->setEnabled(false);
+  }
 }
 
 void ConvFitAddWorkspaceDialog::setAllSpectraSelectionEnabled(bool doEnable) {
diff --git a/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.ui b/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.ui
index e1634010baceac7f32c3a4d3eebe5d8e4af6b115..a0a25e0a49091019764c39e960d363c52259d962 100644
--- a/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.ui
+++ b/qt/scientific_interfaces/Indirect/ConvFitAddWorkspaceDialog.ui
@@ -98,13 +98,48 @@
     </widget>
    </item>
    <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
+    <widget class="QFrame" name="frame">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pbAdd">
+        <property name="text">
+         <string>Add</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pbClose">
+        <property name="text">
+         <string>Close</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
     </widget>
    </item>
   </layout>
@@ -119,36 +154,40 @@
  <resources/>
  <connections>
   <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
+   <sender>pbAdd</sender>
+   <signal>clicked()</signal>
    <receiver>ConvFitAddWorkspaceDialog</receiver>
-   <slot>accept()</slot>
+   <slot>addData()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
+     <x>276</x>
+     <y>160</y>
     </hint>
     <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
+     <x>269</x>
+     <y>319</y>
     </hint>
    </hints>
   </connection>
   <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
+   <sender>pbClose</sender>
+   <signal>clicked()</signal>
    <receiver>ConvFitAddWorkspaceDialog</receiver>
-   <slot>reject()</slot>
+   <slot>closeDialog()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
+     <x>340</x>
+     <y>171</y>
     </hint>
     <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
+     <x>335</x>
+     <y>251</y>
     </hint>
    </hints>
   </connection>
  </connections>
+ <slots>
+  <slot>addData()</slot>
+  <slot>closeDialog()</slot>
+ </slots>
 </ui>
diff --git a/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.cpp b/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.cpp
index 3d8eb339d395389dfc9b2de0dc33707c1b838d88..bea09c66563c3a4e5f4344f6f09311005e4059b4 100644
--- a/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.cpp
+++ b/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.cpp
@@ -25,7 +25,7 @@ namespace CustomInterfaces {
 namespace IDA {
 
 ConvFitDataPresenter::ConvFitDataPresenter(ConvFitModel *model,
-                                           IndirectFitDataView *view)
+                                           IIndirectFitDataView *view)
     : IndirectFitDataPresenter(
           model, view,
           Mantid::Kernel::make_unique<ConvFitDataTablePresenter>(
diff --git a/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.h b/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.h
index 071d6fb43eb5f05c9e17e87001d7efbed10b4884..f4626227864555e7883c7d3ee487e0c46c350b38 100644
--- a/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.h
+++ b/qt/scientific_interfaces/Indirect/ConvFitDataPresenter.h
@@ -16,13 +16,11 @@ namespace IDA {
 
 class ConvFitAddWorkspaceDialog;
 
-/**
-  A presenter.
-*/
-class DLLExport ConvFitDataPresenter : public IndirectFitDataPresenter {
+class MANTIDQT_INDIRECT_DLL ConvFitDataPresenter
+    : public IndirectFitDataPresenter {
   Q_OBJECT
 public:
-  ConvFitDataPresenter(ConvFitModel *model, IndirectFitDataView *view);
+  ConvFitDataPresenter(ConvFitModel *model, IIndirectFitDataView *view);
 
 private slots:
   void setModelResolution(const QString &name);
diff --git a/qt/scientific_interfaces/Indirect/ConvFitModel.cpp b/qt/scientific_interfaces/Indirect/ConvFitModel.cpp
index 0b73a54635633b0efb74c652aadde16dd3415281..633914846d1aaf846a62841d816fec86f1ab7631 100644
--- a/qt/scientific_interfaces/Indirect/ConvFitModel.cpp
+++ b/qt/scientific_interfaces/Indirect/ConvFitModel.cpp
@@ -24,6 +24,10 @@ MatrixWorkspace_sptr getADSMatrixWorkspace(std::string const &workspaceName) {
       workspaceName);
 }
 
+bool doesExistInADS(std::string const &workspaceName) {
+  return AnalysisDataService::Instance().doesExist(workspaceName);
+}
+
 boost::optional<std::size_t>
 getFirstInCategory(CompositeFunction_const_sptr composite,
                    const std::string &category) {
@@ -450,7 +454,7 @@ IAlgorithm_sptr ConvFitModel::simultaneousFitAlgorithm() const {
 
 std::string ConvFitModel::sequentialFitOutputName() const {
   if (isMultiFit())
-    return "MultiConvFit_" + m_fitType + m_backgroundString + "_Result";
+    return "MultiConvFit_" + m_fitType + m_backgroundString + "_Results";
   return createOutputName(
       "%1%_conv_" + m_fitType + m_backgroundString + "_s%2%", "_to_", 0);
 }
@@ -553,7 +557,10 @@ void ConvFitModel::removeWorkspace(std::size_t index) {
 }
 
 void ConvFitModel::setResolution(const std::string &name, std::size_t index) {
-  setResolution(getADSMatrixWorkspace(name), index);
+  if (!name.empty() && doesExistInADS(name))
+    setResolution(getADSMatrixWorkspace(name), index);
+  else
+    throw std::runtime_error("A valid resolution file needs to be selected.");
 }
 
 void ConvFitModel::setResolution(MatrixWorkspace_sptr resolution,
diff --git a/qt/scientific_interfaces/Indirect/ConvFitModel.h b/qt/scientific_interfaces/Indirect/ConvFitModel.h
index badb10a48fdd9bf9aa79875c47e73483b7e28bac..f8bd76cf9fbd61d768772d048fe025f8540cae4e 100644
--- a/qt/scientific_interfaces/Indirect/ConvFitModel.h
+++ b/qt/scientific_interfaces/Indirect/ConvFitModel.h
@@ -15,6 +15,8 @@ namespace IDA {
 
 class DLLExport ConvFitModel : public IndirectFittingModel {
 public:
+  using IndirectFittingModel::addWorkspace;
+
   ConvFitModel();
   ~ConvFitModel() override;
 
diff --git a/qt/scientific_interfaces/Indirect/CorrectionsTab.cpp b/qt/scientific_interfaces/Indirect/CorrectionsTab.cpp
index 3654e9b8b9b8599e7b1bd4e0fb11818ad9271ca9..786fbfea85b524b2d3e942abffc0ea6891e9e1f6 100644
--- a/qt/scientific_interfaces/Indirect/CorrectionsTab.cpp
+++ b/qt/scientific_interfaces/Indirect/CorrectionsTab.cpp
@@ -54,8 +54,8 @@ bool CorrectionsTab::checkWorkspaceBinningMatches(
     MatrixWorkspace_const_sptr left, MatrixWorkspace_const_sptr right) {
   if (left && right) // check the workspaces actually point to something first
   {
-    const auto leftX = left->x(0);
-    const auto rightX = right->x(0);
+    const auto &leftX = left->x(0);
+    const auto &rightX = right->x(0);
     return leftX.size() == rightX.size() &&
            std::equal(leftX.begin(), leftX.end(), rightX.begin());
   } else {
diff --git a/qt/scientific_interfaces/Indirect/Elwin.cpp b/qt/scientific_interfaces/Indirect/Elwin.cpp
index ee340dfee7316ccae7747e8ac4e5aa3d72a8a18e..1f2c3f370e176341d54eec2ac89adc74bde8465c 100644
--- a/qt/scientific_interfaces/Indirect/Elwin.cpp
+++ b/qt/scientific_interfaces/Indirect/Elwin.cpp
@@ -8,6 +8,7 @@
 #include "../General/UserInputValidator.h"
 
 #include "MantidGeometry/Instrument.h"
+#include "MantidQtWidgets/Common/SignalBlocker.h"
 #include "MantidQtWidgets/LegacyQwt/RangeSelector.h"
 
 #include <QFileInfo>
@@ -25,10 +26,30 @@ MatrixWorkspace_sptr getADSMatrixWorkspace(std::string const &workspaceName) {
       workspaceName);
 }
 
+bool doesExistInADS(std::string const &workspaceName) {
+  return AnalysisDataService::Instance().doesExist(workspaceName);
+}
+
 bool isWorkspacePlottable(MatrixWorkspace_sptr workspace) {
   return workspace->y(0).size() > 1;
 }
 
+bool isWorkspacePlottable(std::string const &workspaceName) {
+  return isWorkspacePlottable(getADSMatrixWorkspace(workspaceName));
+}
+
+bool canPlotWorkspace(std::string const &workspaceName) {
+  return doesExistInADS(workspaceName) && isWorkspacePlottable(workspaceName);
+}
+
+std::vector<std::string> getOutputWorkspaceSuffices() {
+  return {"_eq", "_eq2", "_elf", "_elt"};
+}
+
+int getNumberOfSpectra(std::string const &name) {
+  return static_cast<int>(getADSMatrixWorkspace(name)->getNumberHistograms());
+}
+
 } // namespace
 
 namespace MantidQt {
@@ -124,6 +145,9 @@ void Elwin::setup() {
   connect(m_uiForm.pbPlotPreview, SIGNAL(clicked()), this,
           SLOT(plotCurrentPreview()));
 
+  connect(m_uiForm.cbPlotWorkspace, SIGNAL(currentIndexChanged(int)), this,
+          SLOT(updateAvailablePlotSpectra()));
+
   // Set any default values
   m_dblManager->setValue(m_properties["IntegrationStart"], -0.02);
   m_dblManager->setValue(m_properties["IntegrationEnd"], 0.02);
@@ -194,8 +218,7 @@ void Elwin::run() {
   }
 
   // Group input workspaces
-  IAlgorithm_sptr groupWsAlg =
-      AlgorithmManager::Instance().create("GroupWorkspaces");
+  auto groupWsAlg = AlgorithmManager::Instance().create("GroupWorkspaces");
   groupWsAlg->initialize();
   API::BatchAlgorithmRunner::AlgorithmRuntimeProps runTimeProps;
   runTimeProps["InputWorkspaces"] = inputWorkspacesString;
@@ -204,7 +227,7 @@ void Elwin::run() {
   m_batchAlgoRunner->addAlgorithm(groupWsAlg, runTimeProps);
 
   // Configure ElasticWindowMultiple algorithm
-  IAlgorithm_sptr elwinMultAlg =
+  auto elwinMultAlg =
       AlgorithmManager::Instance().create("ElasticWindowMultiple");
   elwinMultAlg->initialize();
 
@@ -260,18 +283,63 @@ void Elwin::unGroupInput(bool error) {
 
   if (!error) {
     if (!m_uiForm.ckGroupInput->isChecked()) {
-      IAlgorithm_sptr ungroupAlg =
-          AlgorithmManager::Instance().create("UnGroupWorkspace");
+      auto ungroupAlg = AlgorithmManager::Instance().create("UnGroupWorkspace");
       ungroupAlg->initialize();
       ungroupAlg->setProperty("InputWorkspace", "IDA_Elwin_Input");
       ungroupAlg->execute();
     }
+
+    updatePlotSpectrumOptions();
+
   } else {
     setPlotResultEnabled(false);
     setSaveResultEnabled(false);
   }
 }
 
+void Elwin::updatePlotSpectrumOptions() {
+  updateAvailablePlotWorkspaces();
+  if (m_uiForm.cbPlotWorkspace->size().isEmpty())
+    setPlotResultEnabled(false);
+  else
+    updateAvailablePlotSpectra();
+}
+
+void Elwin::updateAvailablePlotWorkspaces() {
+  MantidQt::API::SignalBlocker<QObject> blocker(m_uiForm.cbPlotWorkspace);
+  m_uiForm.cbPlotWorkspace->clear();
+  for (auto const &suffix : getOutputWorkspaceSuffices()) {
+    auto const workspaceName = getOutputBasename().toStdString() + suffix;
+    if (canPlotWorkspace(workspaceName))
+      m_uiForm.cbPlotWorkspace->addItem(QString::fromStdString(workspaceName));
+  }
+}
+
+QString Elwin::getPlotWorkspaceName() const {
+  return m_uiForm.cbPlotWorkspace->currentText();
+}
+
+void Elwin::setPlotSpectrumValue(int value) {
+  MantidQt::API::SignalBlocker<QObject> blocker(m_uiForm.spPlotSpectrum);
+  m_uiForm.spPlotSpectrum->setValue(value);
+}
+
+void Elwin::updateAvailablePlotSpectra() {
+  auto const name = getPlotWorkspaceName().toStdString();
+  auto const maximumValue = getNumberOfSpectra(name) - 1;
+  setPlotSpectrumMinMax(0, maximumValue);
+  setPlotSpectrumValue(0);
+}
+
+void Elwin::setPlotSpectrumMinMax(int minimum, int maximum) {
+  m_uiForm.spPlotSpectrum->setMinimum(minimum);
+  m_uiForm.spPlotSpectrum->setMaximum(maximum);
+}
+
+int Elwin::getPlotSpectrumIndex() const {
+  return m_uiForm.spPlotSpectrum->text().toInt();
+}
+
 bool Elwin::validate() {
   UserInputValidator uiv;
 
@@ -355,7 +423,7 @@ void Elwin::setDefaultSampleLog(Mantid::API::MatrixWorkspace_const_sptr ws) {
 /**
  * Handles a new set of input files being entered.
  *
- * Updates preview seletcion combo box.
+ * Updates preview selection combo box.
  */
 void Elwin::newInputFiles() {
   // Clear the existing list of files
@@ -388,24 +456,22 @@ void Elwin::newInputFiles() {
  * @param index Index of the new selected file
  */
 void Elwin::newPreviewFileSelected(int index) {
-  QString wsName = m_uiForm.cbPreviewFile->itemText(index);
-  QString filename = m_uiForm.cbPreviewFile->itemData(index).toString();
-
-  // Ignore empty filenames (can happen when new files are loaded and the widget
-  // is being populated)
-  if (filename.isEmpty())
-    return;
+  auto const workspaceName = m_uiForm.cbPreviewFile->itemText(index);
+  auto const filename = m_uiForm.cbPreviewFile->itemData(index).toString();
 
-  if (!loadFile(filename, wsName)) {
-    g_log.error("Failed to load input workspace.");
-    return;
-  }
+  if (!filename.isEmpty()) {
+    auto const loadHistory = m_uiForm.ckLoadHistory->isChecked();
 
-  auto const ws = getADSMatrixWorkspace(wsName.toStdString());
-  int const numHist = static_cast<int>(ws->getNumberHistograms()) - 1;
+    if (loadFile(filename, workspaceName, -1, -1, loadHistory)) {
+      auto const workspace = getADSMatrixWorkspace(workspaceName.toStdString());
+      int const numHist =
+          static_cast<int>(workspace->getNumberHistograms()) - 1;
 
-  m_uiForm.spPreviewSpec->setMaximum(numHist);
-  m_uiForm.spPreviewSpec->setValue(0);
+      m_uiForm.spPreviewSpec->setMaximum(numHist);
+      m_uiForm.spPreviewSpec->setValue(0);
+    } else
+      g_log.error("Failed to load input workspace.");
+  }
 }
 
 /**
@@ -474,87 +540,63 @@ void Elwin::updateRS(QtProperty *prop, double val) {
     backgroundRangeSelector->setMaximum(val);
 }
 
+void Elwin::runClicked() { runTab(); }
+
 /**
  * Handles mantid plotting
  */
 void Elwin::plotClicked() {
   setPlotResultIsPlotting(true);
-
-  auto const workspaceBaseName =
-      getWorkspaceBasename(QString::fromStdString(m_pythonExportWsName));
-
-  plotResult(workspaceBaseName + "_eq");
-  plotResult(workspaceBaseName + "_eq2");
-  plotResult(workspaceBaseName + "_elf");
-  plotResult(workspaceBaseName + "_elt");
-
+  plotSpectrum(getPlotWorkspaceName(), getPlotSpectrumIndex());
   setPlotResultIsPlotting(false);
 }
 
-void Elwin::plotResult(QString const &workspaceName) {
-  auto const name = workspaceName.toStdString();
-  if (checkADSForPlotSaveWorkspace(name, true)) {
-    if (isWorkspacePlottable(getADSMatrixWorkspace(name)))
-      plotSpectrum(workspaceName);
-    else
-      showMessageBox("Plotting a spectrum of the workspace " + workspaceName +
-                     " failed : Workspace only has one data point");
-  }
-}
-
 /**
  * Handles saving of workspaces
  */
 void Elwin::saveClicked() {
-  auto workspaceBaseName =
-      getWorkspaceBasename(QString::fromStdString(m_pythonExportWsName));
-
-  if (checkADSForPlotSaveWorkspace((workspaceBaseName + "_eq").toStdString(),
-                                   false))
-    addSaveWorkspaceToQueue(workspaceBaseName + "_eq");
-
-  if (checkADSForPlotSaveWorkspace((workspaceBaseName + "_eq2").toStdString(),
-                                   false))
-    addSaveWorkspaceToQueue(workspaceBaseName + "_eq2");
+  auto const workspaceBaseName = getOutputBasename().toStdString();
 
-  if (checkADSForPlotSaveWorkspace((workspaceBaseName + "_elf").toStdString(),
-                                   false))
-    addSaveWorkspaceToQueue(workspaceBaseName + "_elf");
-
-  if (checkADSForPlotSaveWorkspace((workspaceBaseName + "_elt").toStdString(),
-                                   false, false))
-    addSaveWorkspaceToQueue(workspaceBaseName + "_elt");
+  for (auto const &suffix : getOutputWorkspaceSuffices())
+    if (checkADSForPlotSaveWorkspace(workspaceBaseName + suffix, false))
+      addSaveWorkspaceToQueue(workspaceBaseName + suffix);
 
   m_batchAlgoRunner->executeBatchAsync();
 }
 
-void Elwin::setRunEnabled(bool enabled) { m_uiForm.pbRun->setEnabled(enabled); }
+QString Elwin::getOutputBasename() {
+  return getWorkspaceBasename(QString::fromStdString(m_pythonExportWsName));
+}
 
-void Elwin::setPlotResultEnabled(bool enabled) {
-  m_uiForm.pbPlot->setEnabled(enabled);
+void Elwin::setRunIsRunning(const bool &running) {
+  m_uiForm.pbRun->setText(running ? "Running..." : "Run");
+  setButtonsEnabled(!running);
 }
 
-void Elwin::setSaveResultEnabled(bool enabled) {
-  m_uiForm.pbSave->setEnabled(enabled);
+void Elwin::setPlotResultIsPlotting(const bool &plotting) {
+  m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Spectrum");
+  setButtonsEnabled(!plotting);
 }
 
-void Elwin::setButtonsEnabled(bool enabled) {
+void Elwin::setButtonsEnabled(const bool &enabled) {
   setRunEnabled(enabled);
   setPlotResultEnabled(enabled);
   setSaveResultEnabled(enabled);
 }
 
-void Elwin::setRunIsRunning(bool running) {
-  m_uiForm.pbRun->setText(running ? "Running..." : "Run");
-  setButtonsEnabled(!running);
+void Elwin::setRunEnabled(const bool &enabled) {
+  m_uiForm.pbRun->setEnabled(enabled);
 }
 
-void Elwin::setPlotResultIsPlotting(bool plotting) {
-  m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result");
-  setButtonsEnabled(!plotting);
+void Elwin::setPlotResultEnabled(const bool &enabled) {
+  m_uiForm.pbPlot->setEnabled(enabled);
+  m_uiForm.cbPlotWorkspace->setEnabled(enabled);
+  m_uiForm.spPlotSpectrum->setEnabled(enabled);
 }
 
-void Elwin::runClicked() { runTab(); }
+void Elwin::setSaveResultEnabled(const bool &enabled) {
+  m_uiForm.pbSave->setEnabled(enabled);
+}
 
 } // namespace IDA
 } // namespace CustomInterfaces
diff --git a/qt/scientific_interfaces/Indirect/Elwin.h b/qt/scientific_interfaces/Indirect/Elwin.h
index f47943fa8be03426fe78f5599323074abdc383b4..07f26e161d093c10509cc1590f790c0e1247b1ac 100644
--- a/qt/scientific_interfaces/Indirect/Elwin.h
+++ b/qt/scientific_interfaces/Indirect/Elwin.h
@@ -29,6 +29,7 @@ private slots:
   void maxChanged(double val);
   void updateRS(QtProperty *prop, double val);
   void unGroupInput(bool error);
+  void updateAvailablePlotSpectra();
   void runClicked();
   void saveClicked();
   void plotClicked();
@@ -43,14 +44,21 @@ private:
                             const QPair<double, double> &range);
   void setDefaultSampleLog(Mantid::API::MatrixWorkspace_const_sptr ws);
 
-  void plotResult(QString const &workspaceName);
+  QString getOutputBasename();
 
-  void setRunEnabled(bool enabled);
-  void setPlotResultEnabled(bool enabled);
-  void setSaveResultEnabled(bool enabled);
-  void setButtonsEnabled(bool enabled);
-  void setRunIsRunning(bool running);
-  void setPlotResultIsPlotting(bool plotting);
+  void updatePlotSpectrumOptions();
+  void updateAvailablePlotWorkspaces();
+  QString getPlotWorkspaceName() const;
+  void setPlotSpectrumValue(int value);
+  void setPlotSpectrumMinMax(int minimum, int maximum);
+  int getPlotSpectrumIndex() const;
+
+  void setRunIsRunning(const bool &running);
+  void setPlotResultIsPlotting(const bool &plotting);
+  void setButtonsEnabled(const bool &enabled);
+  void setRunEnabled(const bool &enabled);
+  void setPlotResultEnabled(const bool &enabled);
+  void setSaveResultEnabled(const bool &enabled);
 
   Ui::Elwin m_uiForm;
   QtTreePropertyBrowser *m_elwTree;
diff --git a/qt/scientific_interfaces/Indirect/Elwin.ui b/qt/scientific_interfaces/Indirect/Elwin.ui
index 9a006e4f6f6588c8dda39978be6957c52429aca9..f5b32112c1de3d8db9e5ec9412e383c5b3464ecb 100644
--- a/qt/scientific_interfaces/Indirect/Elwin.ui
+++ b/qt/scientific_interfaces/Indirect/Elwin.ui
@@ -46,13 +46,41 @@
        </widget>
       </item>
       <item>
-       <widget class="QCheckBox" name="ckGroupInput">
-        <property name="text">
-         <string>Group Input</string>
-        </property>
-        <property name="checked">
-         <bool>true</bool>
-        </property>
+       <widget class="QFrame" name="frame">
+        <layout class="QVBoxLayout" name="verticalLayout_2">
+         <property name="spacing">
+          <number>3</number>
+         </property>
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QCheckBox" name="ckGroupInput">
+           <property name="text">
+            <string>Group Input</string>
+           </property>
+           <property name="checked">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QCheckBox" name="ckLoadHistory">
+           <property name="text">
+            <string>Load History</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
        </widget>
       </item>
      </layout>
@@ -268,13 +296,43 @@
          <string>Output</string>
         </property>
         <layout class="QHBoxLayout" name="horizontalLayout_5">
+         <item>
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>Workspace:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QComboBox" name="cbPlotWorkspace">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>200</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="sizeAdjustPolicy">
+            <enum>QComboBox::AdjustToContents</enum>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QSpinBox" name="spPlotSpectrum">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
          <item>
           <widget class="QPushButton" name="pbPlot">
            <property name="enabled">
             <bool>false</bool>
            </property>
            <property name="text">
-            <string>Plot Result</string>
+            <string>Plot Spectrum</string>
            </property>
           </widget>
          </item>
diff --git a/qt/scientific_interfaces/Indirect/IAddWorkspaceDialog.h b/qt/scientific_interfaces/Indirect/IAddWorkspaceDialog.h
index ac647e345159094eafef15fe4d16d72884d3d9bf..d9eec8dde61d9f62c7001ffa81ecea1867438745 100644
--- a/qt/scientific_interfaces/Indirect/IAddWorkspaceDialog.h
+++ b/qt/scientific_interfaces/Indirect/IAddWorkspaceDialog.h
@@ -8,17 +8,23 @@
 #define MANTIDQTCUSTOMINTERFACES_IADDWORKSPACEDIALOG_H_
 
 #include <QDialog>
+#include <QObject>
 
 namespace MantidQt {
 namespace CustomInterfaces {
 namespace IDA {
 
 class IAddWorkspaceDialog : public QDialog {
+  Q_OBJECT
 public:
   IAddWorkspaceDialog(QWidget *parent) : QDialog(parent) {}
   virtual std::string workspaceName() const = 0;
   virtual void setWSSuffices(const QStringList &suffices) = 0;
   virtual void setFBSuffices(const QStringList &suffices) = 0;
+
+signals:
+  void addData();
+  void closeDialog();
 };
 
 } // namespace IDA
diff --git a/qt/scientific_interfaces/Indirect/IIndirectFitDataView.h b/qt/scientific_interfaces/Indirect/IIndirectFitDataView.h
new file mode 100644
index 0000000000000000000000000000000000000000..0d01cbbf71c864a205bca727304c1e37bc1f3b07
--- /dev/null
+++ b/qt/scientific_interfaces/Indirect/IIndirectFitDataView.h
@@ -0,0 +1,65 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTIDQTCUSTOMINTERFACESIDA_IINDIRECTFITDATAVIEW_H_
+#define MANTIDQTCUSTOMINTERFACESIDA_IINDIRECTFITDATAVIEW_H_
+
+#include "../General/UserInputValidator.h"
+#include "DllConfig.h"
+
+#include <QObject>
+#include <QTabWidget>
+#include <QTableWidget>
+
+namespace MantidQt {
+namespace CustomInterfaces {
+namespace IDA {
+
+class MANTIDQT_INDIRECT_DLL IIndirectFitDataView : public QTabWidget {
+  Q_OBJECT
+
+public:
+  IIndirectFitDataView(QWidget *parent = nullptr) : QTabWidget(parent){};
+  virtual ~IIndirectFitDataView(){};
+
+  virtual QTableWidget *getDataTable() const = 0;
+  virtual bool isMultipleDataTabSelected() const = 0;
+  virtual bool isResolutionHidden() const = 0;
+  virtual void setResolutionHidden(bool hide) = 0;
+  virtual void disableMultipleDataTab() = 0;
+
+  virtual std::string getSelectedSample() const = 0;
+  virtual std::string getSelectedResolution() const = 0;
+
+  virtual QStringList getSampleWSSuffices() const = 0;
+  virtual QStringList getSampleFBSuffices() const = 0;
+  virtual QStringList getResolutionWSSuffices() const = 0;
+  virtual QStringList getResolutionFBSuffices() const = 0;
+
+  virtual void setSampleWSSuffices(QStringList const &suffices) = 0;
+  virtual void setSampleFBSuffices(QStringList const &suffices) = 0;
+  virtual void setResolutionWSSuffices(QStringList const &suffices) = 0;
+  virtual void setResolutionFBSuffices(QStringList const &suffices) = 0;
+
+  virtual void readSettings(QSettings const &settings) = 0;
+  virtual UserInputValidator &validate(UserInputValidator &validator) = 0;
+
+public slots:
+  virtual void displayWarning(std::string const &warning) = 0;
+
+signals:
+  void sampleLoaded(QString const &);
+  void resolutionLoaded(QString const &);
+  void addClicked();
+  void removeClicked();
+  void multipleDataViewSelected();
+  void singleDataViewSelected();
+};
+} // namespace IDA
+} // namespace CustomInterfaces
+} // namespace MantidQt
+
+#endif
\ No newline at end of file
diff --git a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp
index 40d86abe339064687757c71a9f5b6b876b925020..fac3ad06dc140066ba4715de4f272d452715ee7a 100644
--- a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp
+++ b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp
@@ -236,8 +236,8 @@ bool ISISEnergyTransfer::validate() {
     if (m_uiForm.ckBackgroundRemoval->isChecked()) {
       MatrixWorkspace_sptr tempWs =
           AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name);
-      const double minBack = tempWs->x(0)[0];
-      const double maxBack = tempWs->x(0)[tempWs->blocksize()];
+      const double minBack = tempWs->x(0).front();
+      const double maxBack = tempWs->x(0).back();
 
       if (m_uiForm.spBackgroundStart->value() < minBack) {
         uiv.addErrorMessage("The Start of Background Removal is less than the "
@@ -691,8 +691,8 @@ void ISISEnergyTransfer::plotRaw() {
     MatrixWorkspace_sptr tempWs =
         AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name);
 
-    const double minBack = tempWs->x(0)[0];
-    const double maxBack = tempWs->x(0)[tempWs->blocksize()];
+    const double minBack = tempWs->x(0).front();
+    const double maxBack = tempWs->x(0).back();
 
     if (startBack < minBack) {
       emit showMessageBox("The Start of Background Removal is less than the "
diff --git a/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.cpp b/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.cpp
index 55e7a8d0810fadae73b3299a4c87ac10ee1f41e1..edcb81288fcf603dc4249308e3840dd27ef8c58d 100644
--- a/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.cpp
@@ -18,6 +18,14 @@ MatrixWorkspace_sptr getWorkspace(const std::string &name) {
   return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name);
 }
 
+bool doesExistInADS(std::string const &workspaceName) {
+  return AnalysisDataService::Instance().doesExist(workspaceName);
+}
+
+bool validWorkspace(std::string const &name) {
+  return !name.empty() && doesExistInADS(name);
+}
+
 boost::optional<std::size_t> maximumIndex(MatrixWorkspace_sptr workspace) {
   if (workspace) {
     const auto numberOfHistograms = workspace->getNumberHistograms();
@@ -81,6 +89,8 @@ AddWorkspaceDialog::AddWorkspaceDialog(QWidget *parent)
           SLOT(workspaceChanged(const QString &)));
   connect(m_uiForm.ckAllSpectra, SIGNAL(stateChanged(int)), this,
           SLOT(selectAllSpectra(int)));
+  connect(m_uiForm.pbAdd, SIGNAL(clicked()), this, SIGNAL(addData()));
+  connect(m_uiForm.pbClose, SIGNAL(clicked()), this, SIGNAL(closeDialog()));
 }
 
 std::string AddWorkspaceDialog::workspaceName() const {
@@ -100,8 +110,9 @@ void AddWorkspaceDialog::setFBSuffices(const QStringList &suffices) {
 }
 
 void AddWorkspaceDialog::selectAllSpectra(int state) {
-  if (state == Qt::Checked) {
-    m_uiForm.leWorkspaceIndices->setText(getIndexString(workspaceName()));
+  auto const name = workspaceName();
+  if (validWorkspace(name) && state == Qt::Checked) {
+    m_uiForm.leWorkspaceIndices->setText(getIndexString(name));
     m_uiForm.leWorkspaceIndices->setEnabled(false);
   } else
     m_uiForm.leWorkspaceIndices->setEnabled(true);
@@ -118,8 +129,10 @@ void AddWorkspaceDialog::workspaceChanged(const QString &workspaceName) {
 
 void AddWorkspaceDialog::setWorkspace(const std::string &workspace) {
   setAllSpectraSelectionEnabled(true);
-  if (m_uiForm.ckAllSpectra->isChecked())
+  if (m_uiForm.ckAllSpectra->isChecked()) {
     m_uiForm.leWorkspaceIndices->setText(getIndexString(workspace));
+    m_uiForm.leWorkspaceIndices->setEnabled(false);
+  }
 }
 
 void AddWorkspaceDialog::setAllSpectraSelectionEnabled(bool doEnable) {
diff --git a/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.ui b/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.ui
index 05630bc5a5fa146e31ba0e9f6ca9876bf94b4f08..17cd5c058891d58f6605a11b64c3cbdb7ef2d60c 100644
--- a/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.ui
+++ b/qt/scientific_interfaces/Indirect/IndirectAddWorkspaceDialog.ui
@@ -78,13 +78,48 @@
     </widget>
    </item>
    <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
+    <widget class="QFrame" name="frame">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pbAdd">
+        <property name="text">
+         <string>Add</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pbClose">
+        <property name="text">
+         <string>Close</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
     </widget>
    </item>
   </layout>
@@ -99,36 +134,40 @@
  <resources/>
  <connections>
   <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
+   <sender>pbAdd</sender>
+   <signal>clicked()</signal>
    <receiver>IndirectAddWorkspaceDialog</receiver>
-   <slot>accept()</slot>
+   <slot>addData()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
+     <x>283</x>
+     <y>157</y>
     </hint>
     <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
+     <x>248</x>
+     <y>294</y>
     </hint>
    </hints>
   </connection>
   <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
+   <sender>pbClose</sender>
+   <signal>clicked()</signal>
    <receiver>IndirectAddWorkspaceDialog</receiver>
-   <slot>reject()</slot>
+   <slot>closeDialog()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
+     <x>358</x>
+     <y>160</y>
     </hint>
     <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
+     <x>354</x>
+     <y>227</y>
     </hint>
    </hints>
   </connection>
  </connections>
+ <slots>
+  <slot>addData()</slot>
+  <slot>closeDialog()</slot>
+ </slots>
 </ui>
diff --git a/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp b/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp
index 937fd4278a47866d7e075c76ef548edcf46f6b7d..b15484ec965e87f0d24b3a8974b0a8d5a1000852 100644
--- a/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp
@@ -226,7 +226,7 @@ std::map<std::string, double> IndirectDataReductionTab::getRangesFromInstrument(
   convUnitsAlg->execute();
   MatrixWorkspace_sptr tofWs = convUnitsAlg->getProperty("OutputWorkspace");
 
-  const auto tofData = tofWs->x(0);
+  const auto &tofData = tofWs->x(0);
   ranges["peak-start-tof"] = tofData[0];
   ranges["peak-end-tof"] = tofData[2];
   ranges["back-start-tof"] = tofData[3];
diff --git a/qt/scientific_interfaces/Indirect/IndirectDataTablePresenter.h b/qt/scientific_interfaces/Indirect/IndirectDataTablePresenter.h
index 436fa2ec73e7b7d6bb6cea9c1d5978dc381c896b..17a1c21e7061665685c49938419bca2de4f31f0d 100644
--- a/qt/scientific_interfaces/Indirect/IndirectDataTablePresenter.h
+++ b/qt/scientific_interfaces/Indirect/IndirectDataTablePresenter.h
@@ -21,7 +21,7 @@ namespace IDA {
 /**
   Presenter for a table of indirect fitting data.
 */
-class DLLExport IndirectDataTablePresenter : public QObject {
+class MANTIDQT_INDIRECT_DLL IndirectDataTablePresenter : public QObject {
   Q_OBJECT
 public:
   IndirectDataTablePresenter(IndirectFittingModel *model,
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp b/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp
index f321492824e4281747e9c5ec0df6d6b1d3cb3a41..390f35d6aee103fd98ae33705a5631215549a876 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp
@@ -946,7 +946,7 @@ void IndirectFitAnalysisTab::plotSpectrum(
     if (boost::contains(parameter, parameterToPlot)) {
       auto it = labels.find(parameter);
       if (it != labels.end())
-        IndirectTab::plotSpectrum(name, static_cast<int>(it->second));
+        IndirectTab::plotSpectrum(name, static_cast<int>(it->second), true);
     }
   }
 }
@@ -955,7 +955,7 @@ void IndirectFitAnalysisTab::plotSpectrum(
     Mantid::API::MatrixWorkspace_sptr workspace) {
   const auto name = QString::fromStdString(workspace->getName());
   for (auto i = 0u; i < workspace->getNumberHistograms(); ++i)
-    IndirectTab::plotSpectrum(name, static_cast<int>(i));
+    IndirectTab::plotSpectrum(name, static_cast<int>(i), true);
 }
 
 /**
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitData.cpp b/qt/scientific_interfaces/Indirect/IndirectFitData.cpp
index 5ccd35e13aa97cfe33e367bf806aa4d7ba5fa87b..9073caa3a6a63ff0fc1df83117e5e7016d26715c 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitData.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectFitData.cpp
@@ -276,7 +276,7 @@ IndirectFitData::IndirectFitData(MatrixWorkspace_sptr workspace,
 std::string
 IndirectFitData::displayName(const std::string &formatString,
                              const std::string &rangeDelimiter) const {
-  const auto workspaceName = cutLastOf(workspace()->getName(), "_red");
+  const auto workspaceName = getBasename();
   const auto spectraString =
       boost::apply_visitor(SpectraToString(rangeDelimiter), m_spectra);
 
@@ -291,7 +291,7 @@ IndirectFitData::displayName(const std::string &formatString,
 
 std::string IndirectFitData::displayName(const std::string &formatString,
                                          std::size_t spectrum) const {
-  const auto workspaceName = cutLastOf(workspace()->getName(), "_red");
+  const auto workspaceName = getBasename();
 
   auto formatted = boost::format(formatString);
   formatted = tryPassFormatArgument(formatted, workspaceName);
@@ -299,6 +299,10 @@ std::string IndirectFitData::displayName(const std::string &formatString,
   return formatted.str();
 }
 
+std::string IndirectFitData::getBasename() const {
+  return cutLastOf(workspace()->getName(), "_red");
+}
+
 Mantid::API::MatrixWorkspace_sptr IndirectFitData::workspace() const {
   return m_workspace;
 }
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitData.h b/qt/scientific_interfaces/Indirect/IndirectFitData.h
index 2366846ecb950f6a8a8212a0bcf0e093d08caedc..ff178f150defa2b989c14c6a9a5908b316160cc6 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitData.h
+++ b/qt/scientific_interfaces/Indirect/IndirectFitData.h
@@ -150,6 +150,7 @@ public:
                           const std::string &rangeDelimiter) const;
   std::string displayName(const std::string &formatString,
                           std::size_t spectrum) const;
+  std::string getBasename() const;
 
   Mantid::API::MatrixWorkspace_sptr workspace() const;
   const Spectra &spectra() const;
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.cpp b/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.cpp
index 6c4d509fa83e5098967885b39029cadded5fecec..147f312f1c8c0b0d413b06d15886f007fa104929 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.cpp
@@ -14,14 +14,14 @@ namespace CustomInterfaces {
 namespace IDA {
 
 IndirectFitDataPresenter::IndirectFitDataPresenter(IndirectFittingModel *model,
-                                                   IndirectFitDataView *view)
+                                                   IIndirectFitDataView *view)
     : IndirectFitDataPresenter(
           model, view,
           Mantid::Kernel::make_unique<IndirectDataTablePresenter>(
               model, view->getDataTable())) {}
 
 IndirectFitDataPresenter::IndirectFitDataPresenter(
-    IndirectFittingModel *model, IndirectFitDataView *view,
+    IndirectFittingModel *model, IIndirectFitDataView *view,
     std::unique_ptr<IndirectDataTablePresenter> tablePresenter)
     : m_model(model), m_view(view),
       m_tablePresenter(std::move(tablePresenter)) {
@@ -65,7 +65,7 @@ IndirectFitDataPresenter::IndirectFitDataPresenter(
                                       std::size_t)));
 }
 
-IndirectFitDataView const *IndirectFitDataPresenter::getView() const {
+IIndirectFitDataView const *IndirectFitDataPresenter::getView() const {
   return m_view;
 }
 
@@ -147,17 +147,13 @@ IndirectFitDataPresenter::validate(UserInputValidator &validator) {
 }
 
 void IndirectFitDataPresenter::showAddWorkspaceDialog() {
-  const auto dialog = getAddWorkspaceDialog(m_view->parentWidget());
-  dialog->setWSSuffices(m_view->getSampleWSSuffices());
-  dialog->setFBSuffices(m_view->getSampleFBSuffices());
-  dialogExecuted(dialog.get(),
-                 static_cast<QDialog::DialogCode>(dialog->exec()));
-}
-
-void IndirectFitDataPresenter::dialogExecuted(IAddWorkspaceDialog const *dialog,
-                                              QDialog::DialogCode result) {
-  if (result == QDialog::Accepted)
-    addData(dialog);
+  m_addWorkspaceDialog = getAddWorkspaceDialog(m_view->parentWidget());
+  m_addWorkspaceDialog->setWSSuffices(m_view->getSampleWSSuffices());
+  m_addWorkspaceDialog->setFBSuffices(m_view->getSampleFBSuffices());
+  m_addWorkspaceDialog->show();
+  connect(m_addWorkspaceDialog.get(), SIGNAL(addData()), this, SLOT(addData()));
+  connect(m_addWorkspaceDialog.get(), SIGNAL(closeDialog()), this,
+          SLOT(closeDialog()));
 }
 
 std::unique_ptr<IAddWorkspaceDialog>
@@ -165,6 +161,18 @@ IndirectFitDataPresenter::getAddWorkspaceDialog(QWidget *parent) const {
   return Mantid::Kernel::make_unique<AddWorkspaceDialog>(parent);
 }
 
+void IndirectFitDataPresenter::addData() {
+  addData(m_addWorkspaceDialog.get());
+}
+
+void IndirectFitDataPresenter::closeDialog() {
+  disconnect(m_addWorkspaceDialog.get(), SIGNAL(addData()), this,
+             SLOT(addData()));
+  disconnect(m_addWorkspaceDialog.get(), SIGNAL(closeDialog()), this,
+             SLOT(closeDialog()));
+  m_addWorkspaceDialog->close();
+}
+
 void IndirectFitDataPresenter::addData(IAddWorkspaceDialog const *dialog) {
   try {
     addDataToModel(dialog);
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.h b/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.h
index 30e4e6e0f17501fb890f45aac909d7a013f4e7ec..871434ca3ac436f06f5b3174f19130e4ee817180 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.h
+++ b/qt/scientific_interfaces/Indirect/IndirectFitDataPresenter.h
@@ -8,10 +8,11 @@
 #define MANTIDQTCUSTOMINTERFACESIDA_INDIRECTFITDATAPRESENTER_H_
 
 #include "IAddWorkspaceDialog.h"
+#include "IIndirectFitDataView.h"
 #include "IndirectDataTablePresenter.h"
-#include "IndirectFitDataView.h"
 #include "IndirectFittingModel.h"
 
+#include "DllConfig.h"
 #include "MantidAPI/MatrixWorkspace.h"
 
 #include <QObject>
@@ -20,17 +21,14 @@ namespace MantidQt {
 namespace CustomInterfaces {
 namespace IDA {
 
-/**
-  A presenter.
-*/
-class DLLExport IndirectFitDataPresenter : public QObject {
+class MANTIDQT_INDIRECT_DLL IndirectFitDataPresenter : public QObject {
   Q_OBJECT
 public:
   IndirectFitDataPresenter(IndirectFittingModel *model,
-                           IndirectFitDataView *view);
+                           IIndirectFitDataView *view);
 
   IndirectFitDataPresenter(
-      IndirectFittingModel *model, IndirectFitDataView *view,
+      IndirectFittingModel *model, IIndirectFitDataView *view,
       std::unique_ptr<IndirectDataTablePresenter> tablePresenter);
 
   void setSampleWSSuffices(const QStringList &suffices);
@@ -55,6 +53,8 @@ protected slots:
   void setModelFromMultipleData();
   void showAddWorkspaceDialog();
 
+  virtual void closeDialog();
+
 signals:
   void singleSampleLoaded();
   void singleResolutionLoaded();
@@ -69,25 +69,27 @@ signals:
   void requestedAddWorkspaceDialog();
 
 protected:
-  IndirectFitDataView const *getView() const;
+  IIndirectFitDataView const *getView() const;
   void addData(IAddWorkspaceDialog const *dialog);
   virtual void addDataToModel(IAddWorkspaceDialog const *dialog);
   void setSingleModelData(const std::string &name);
   virtual void addModelData(const std::string &name);
   void setResolutionHidden(bool hide);
   void displayWarning(const std::string &warning);
-  virtual void dialogExecuted(IAddWorkspaceDialog const *dialog,
-                              QDialog::DialogCode result);
+
+private slots:
+  void addData();
 
 private:
   virtual std::unique_ptr<IAddWorkspaceDialog>
   getAddWorkspaceDialog(QWidget *parent) const;
   void updateDataInTable(std::size_t dataIndex);
 
+  std::unique_ptr<IAddWorkspaceDialog> m_addWorkspaceDialog;
   IndirectFittingModel *m_model;
   PrivateFittingData m_singleData;
   PrivateFittingData m_multipleData;
-  IndirectFitDataView *m_view;
+  IIndirectFitDataView *m_view;
   std::unique_ptr<IndirectDataTablePresenter> m_tablePresenter;
 };
 
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitDataView.cpp b/qt/scientific_interfaces/Indirect/IndirectFitDataView.cpp
index f2485a6215e6c3125c8b76807cb7f3b7831881fd..d36114c070d0438ca3ad90cda74d1d569b7d95fa 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitDataView.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectFitDataView.cpp
@@ -21,7 +21,7 @@ namespace CustomInterfaces {
 namespace IDA {
 
 IndirectFitDataView::IndirectFitDataView(QWidget *parent)
-    : QTabWidget(parent), m_dataForm(new Ui::IndirectFitDataForm) {
+    : IIndirectFitDataView(parent), m_dataForm(new Ui::IndirectFitDataForm) {
   m_dataForm->setupUi(this);
   m_dataForm->dsResolution->hide();
   m_dataForm->lbResolution->hide();
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitDataView.h b/qt/scientific_interfaces/Indirect/IndirectFitDataView.h
index 9fc474ba380cd52202076ee38ebaa6e25205f798..7f24863b9f1693962f4242e54c37749302237490 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitDataView.h
+++ b/qt/scientific_interfaces/Indirect/IndirectFitDataView.h
@@ -9,7 +9,10 @@
 
 #include "ui_IndirectFitDataView.h"
 
+#include "IIndirectFitDataView.h"
+
 #include "../General/UserInputValidator.h"
+#include "DllConfig.h"
 
 #include <QTabWidget>
 
@@ -17,44 +20,36 @@ namespace MantidQt {
 namespace CustomInterfaces {
 namespace IDA {
 
-class DLLExport IndirectFitDataView : public QTabWidget {
+class MANTIDQT_INDIRECT_DLL IndirectFitDataView : public IIndirectFitDataView {
   Q_OBJECT
 public:
-  IndirectFitDataView(QWidget *parent);
+  IndirectFitDataView(QWidget *parent = nullptr);
   ~IndirectFitDataView() override = default;
 
-  QTableWidget *getDataTable() const;
-  bool isMultipleDataTabSelected() const;
-  bool isResolutionHidden() const;
-  void setResolutionHidden(bool hide);
-  void disableMultipleDataTab();
+  QTableWidget *getDataTable() const override;
+  virtual bool isMultipleDataTabSelected() const override;
+  bool isResolutionHidden() const override;
+  void setResolutionHidden(bool hide) override;
+  void disableMultipleDataTab() override;
 
-  std::string getSelectedSample() const;
-  std::string getSelectedResolution() const;
+  virtual std::string getSelectedSample() const override;
+  std::string getSelectedResolution() const override;
 
-  QStringList getSampleWSSuffices() const;
-  QStringList getSampleFBSuffices() const;
-  QStringList getResolutionWSSuffices() const;
-  QStringList getResolutionFBSuffices() const;
+  virtual QStringList getSampleWSSuffices() const override;
+  virtual QStringList getSampleFBSuffices() const override;
+  QStringList getResolutionWSSuffices() const override;
+  QStringList getResolutionFBSuffices() const override;
 
-  void setSampleWSSuffices(const QStringList &suffices);
-  void setSampleFBSuffices(const QStringList &suffices);
-  void setResolutionWSSuffices(const QStringList &suffices);
-  void setResolutionFBSuffices(const QStringList &suffices);
+  virtual void setSampleWSSuffices(const QStringList &suffices) override;
+  virtual void setSampleFBSuffices(const QStringList &suffices) override;
+  virtual void setResolutionWSSuffices(const QStringList &suffices) override;
+  virtual void setResolutionFBSuffices(const QStringList &suffices) override;
 
-  void readSettings(const QSettings &settings);
-  UserInputValidator &validate(UserInputValidator &validator);
+  void readSettings(const QSettings &settings) override;
+  UserInputValidator &validate(UserInputValidator &validator) override;
 
 public slots:
-  void displayWarning(const std::string &warning);
-
-signals:
-  void sampleLoaded(const QString &);
-  void resolutionLoaded(const QString &);
-  void addClicked();
-  void removeClicked();
-  void multipleDataViewSelected();
-  void singleDataViewSelected();
+  void displayWarning(const std::string &warning) override;
 
 protected slots:
   void emitViewSelected(int index);
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp b/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp
index b951e21d383883748b16ec59805aa6fda0301adf..cafbeaff4834b00e8fc0134825ca7bc349ea0a69 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp
@@ -136,6 +136,26 @@ std::vector<std::string> getAxisLabels(MatrixWorkspace_sptr workspace,
   return std::vector<std::string>();
 }
 
+std::string cutLastOf(const std::string &str, const std::string &delimiter) {
+  const auto cutIndex = str.rfind(delimiter);
+  if (cutIndex != std::string::npos)
+    return str.substr(0, cutIndex);
+  return str;
+}
+
+bool containsMultipleData(const std::string &name) {
+  return name.substr(0, 5) == "Multi";
+}
+
+std::string constructResultName(const std::string &name,
+                                IndirectFitData const *fitData) {
+  if (containsMultipleData(name)) {
+    const auto formatString = cutLastOf(name, "_Results") + "_%1%_s%2%_Result";
+    return fitData->displayName(formatString, "_to_");
+  } else
+    return cutLastOf(name, "s_1");
+}
+
 void renameWorkspace(std::string const &name, std::string const &newName) {
   auto renamer = AlgorithmManager::Instance().create("RenameWorkspace");
   renamer->setProperty("InputWorkspace", name);
@@ -151,8 +171,9 @@ void renameResult(Workspace_sptr resultWorkspace,
 void renameResult(Workspace_sptr resultWorkspace,
                   IndirectFitData const *fitData) {
   const auto name = resultWorkspace->getName();
-  const auto newName = fitData->displayName("%1%_s%2%_Result", "_to_");
-  renameWorkspace(name, newName);
+  const auto newName = constructResultName(name, fitData);
+  if (newName != name)
+    renameWorkspace(name, newName);
 }
 
 void renameResultWithoutSpectra(WorkspaceGroup_sptr resultWorkspace,
diff --git a/qt/scientific_interfaces/Indirect/IndirectFitPreviewPlot.ui b/qt/scientific_interfaces/Indirect/IndirectFitPreviewPlot.ui
index 8f765dc6d3d9b9140859e0bbe31e667630677e28..f7a016f2cea5fc51d749d838b96bf1037d47d9dd 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFitPreviewPlot.ui
+++ b/qt/scientific_interfaces/Indirect/IndirectFitPreviewPlot.ui
@@ -13,60 +13,95 @@
   <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,5,3,0">
+  <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0">
+   <property name="topMargin">
+    <number>0</number>
+   </property>
    <item>
     <widget class="QComboBox" name="cbDataSelection"/>
    </item>
    <item>
-    <widget class="MantidQt::MantidWidgets::PreviewPlot" name="ppPlotTop" native="true">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
-       <horstretch>0</horstretch>
-       <verstretch>1</verstretch>
-      </sizepolicy>
+    <widget class="QDockWidget" name="dwMiniPlots">
+     <property name="features">
+      <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>0</width>
-       <height>125</height>
-      </size>
+     <property name="allowedAreas">
+      <set>Qt::NoDockWidgetArea</set>
      </property>
-     <property name="showLegend" stdset="0">
-      <bool>true</bool>
-     </property>
-     <property name="canvasColour" stdset="0">
-      <color>
-       <red>255</red>
-       <green>255</green>
-       <blue>255</blue>
-      </color>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="MantidQt::MantidWidgets::PreviewPlot" name="ppPlotBottom" native="true">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
-       <horstretch>0</horstretch>
-       <verstretch>1</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="minimumSize">
-      <size>
-       <width>0</width>
-       <height>75</height>
-      </size>
-     </property>
-     <property name="showLegend" stdset="0">
-      <bool>true</bool>
-     </property>
-     <property name="canvasColour" stdset="0">
-      <color>
-       <red>255</red>
-       <green>255</green>
-       <blue>255</blue>
-      </color>
+     <property name="windowTitle">
+      <string>Mini-plots</string>
      </property>
+     <widget class="QWidget" name="dockWidgetContents_2">
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="MantidQt::MantidWidgets::PreviewPlot" name="ppPlotTop" native="true">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>10</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>0</width>
+           <height>125</height>
+          </size>
+         </property>
+         <property name="showLegend" stdset="0">
+          <bool>true</bool>
+         </property>
+         <property name="canvasColour" stdset="0">
+          <color>
+           <red>255</red>
+           <green>255</green>
+           <blue>255</blue>
+          </color>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="MantidQt::MantidWidgets::PreviewPlot" name="ppPlotBottom" native="true">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>6</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>0</width>
+           <height>75</height>
+          </size>
+         </property>
+         <property name="showLegend" stdset="0">
+          <bool>true</bool>
+         </property>
+         <property name="canvasColour" stdset="0">
+          <color>
+           <red>255</red>
+           <green>255</green>
+           <blue>255</blue>
+          </color>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
     </widget>
    </item>
    <item>
diff --git a/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp b/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp
index 5cffc10d062ec3ab11c967b49c6752a59cce7f77..f7bad0ef0bb860da7cde8cc0d1ba9f1a03d3101e 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp
@@ -25,6 +25,10 @@ using namespace Mantid::API;
 namespace {
 using namespace MantidQt::CustomInterfaces::IDA;
 
+bool doesExistInADS(std::string const &workspaceName) {
+  return AnalysisDataService::Instance().doesExist(workspaceName);
+}
+
 bool equivalentWorkspaces(MatrixWorkspace_const_sptr lhs,
                           MatrixWorkspace_const_sptr rhs) {
   if (!lhs || !rhs)
@@ -384,7 +388,8 @@ std::string
 IndirectFittingModel::createOutputName(const std::string &formatString,
                                        const std::string &rangeDelimiter,
                                        std::size_t dataIndex) const {
-  return createDisplayName(formatString, rangeDelimiter, dataIndex) + "_Result";
+  return createDisplayName(formatString, rangeDelimiter, dataIndex) +
+         "_Results";
 }
 
 bool IndirectFittingModel::isMultiFit() const {
@@ -496,6 +501,9 @@ void IndirectFittingModel::addWorkspace(const std::string &workspaceName,
   if (spectra.empty())
     throw std::runtime_error(
         "Fitting Data must consist of one or more spectra.");
+  if (workspaceName.empty() || !doesExistInADS(workspaceName))
+    throw std::runtime_error("A valid sample file needs to be selected.");
+
   addWorkspace(workspaceName, DiscontinuousSpectra<std::size_t>(spectra));
 }
 
diff --git a/qt/scientific_interfaces/Indirect/IndirectFittingModel.h b/qt/scientific_interfaces/Indirect/IndirectFittingModel.h
index f0c9f168409215fc61e7b8e684834e7a6ac495c5..097f919ccc7880cd682c2624c2c65eedc738913e 100644
--- a/qt/scientific_interfaces/Indirect/IndirectFittingModel.h
+++ b/qt/scientific_interfaces/Indirect/IndirectFittingModel.h
@@ -79,17 +79,17 @@ public:
                          std::size_t spectrum);
   virtual void setEndX(double endX, std::size_t dataIndex,
                        std::size_t spectrum);
-  void setExcludeRegion(const std::string &exclude, std::size_t dataIndex,
-                        std::size_t spectrum);
+  virtual void setExcludeRegion(const std::string &exclude,
+                                std::size_t dataIndex, std::size_t spectrum);
 
-  void addWorkspace(const std::string &workspaceName);
+  virtual void addWorkspace(const std::string &workspaceName);
   void addWorkspace(const std::string &workspaceName,
                     const std::string &spectra);
   void addWorkspace(const std::string &workspaceName, const Spectra &spectra);
   virtual void addWorkspace(Mantid::API::MatrixWorkspace_sptr workspace,
                             const Spectra &spectra);
   virtual void removeWorkspace(std::size_t index);
-  PrivateFittingData clearWorkspaces();
+  virtual PrivateFittingData clearWorkspaces();
   void setFittingMode(FittingMode mode);
   virtual void setFitFunction(Mantid::API::IFunction_sptr function);
   virtual void setDefaultParameterValue(const std::string &name, double value,
@@ -209,7 +209,8 @@ private:
 template <typename F>
 void IndirectFittingModel::applySpectra(std::size_t index,
                                         const F &functor) const {
-  m_fittingData[index]->applySpectra(functor);
+  if (m_fittingData.size() > 0)
+    m_fittingData[index]->applySpectra(functor);
 }
 
 } // namespace IDA
diff --git a/qt/scientific_interfaces/Indirect/IndirectSqw.cpp b/qt/scientific_interfaces/Indirect/IndirectSqw.cpp
index 1c4c0fdd54181bc4f3dd6793dbc25de7d7c94c31..2bdd834593ff452155babd15fe0bdc4188ba56aa 100644
--- a/qt/scientific_interfaces/Indirect/IndirectSqw.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectSqw.cpp
@@ -15,6 +15,26 @@
 using namespace Mantid::API;
 using MantidQt::API::BatchAlgorithmRunner;
 
+namespace {
+
+MatrixWorkspace_sptr getADSMatrixWorkspace(std::string const &workspaceName) {
+  return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
+      workspaceName);
+}
+
+void convertToSpectrumAxis(std::string const &inputName,
+                           std::string const &outputName) {
+  auto converter = AlgorithmManager::Instance().create("ConvertSpectrumAxis");
+  converter->initialize();
+  converter->setProperty("InputWorkspace", inputName);
+  converter->setProperty("OutputWorkspace", outputName);
+  converter->setProperty("Target", "ElasticQ");
+  converter->setProperty("EMode", "Indirect");
+  converter->execute();
+}
+
+} // namespace
+
 namespace MantidQt {
 namespace CustomInterfaces {
 //----------------------------------------------------------------------------------------------
@@ -24,8 +44,8 @@ IndirectSqw::IndirectSqw(IndirectDataReduction *idrUI, QWidget *parent)
     : IndirectDataReductionTab(idrUI, parent) {
   m_uiForm.setupUi(parent);
 
-  connect(m_uiForm.dsSampleInput, SIGNAL(loadClicked()), this,
-          SLOT(plotContour()));
+  connect(m_uiForm.dsSampleInput, SIGNAL(dataReady(const QString &)), this,
+          SLOT(plotRqwContour()));
   connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
           SLOT(sqwAlgDone(bool)));
 
@@ -42,17 +62,18 @@ IndirectSqw::IndirectSqw(IndirectDataReduction *idrUI, QWidget *parent)
           this,
           SLOT(updateRunButton(bool, std::string const &, QString const &,
                                QString const &)));
+
+  m_uiForm.rqwPlot2D->setColourBarVisible(false);
+  m_uiForm.rqwPlot2D->setXAxisLabel("Energy (meV)");
+  m_uiForm.rqwPlot2D->setYAxisLabel("Q (A-1)");
 }
 
-//----------------------------------------------------------------------------------------------
-/** Destructor
- */
 IndirectSqw::~IndirectSqw() {}
 
 void IndirectSqw::setup() {}
 
 bool IndirectSqw::validate() {
-  double tolerance = 1e-10;
+  double const tolerance = 1e-10;
   UserInputValidator uiv;
 
   // Validate the data selector
@@ -67,7 +88,7 @@ bool IndirectSqw::validate() {
     uiv.checkBins(m_uiForm.spELow->value(), m_uiForm.spEWidth->value(),
                   m_uiForm.spEHigh->value(), tolerance);
 
-  QString errorMessage = uiv.generateErrorMessage();
+  auto const errorMessage = uiv.generateErrorMessage();
 
   // Show an error message if needed
   if (!errorMessage.isEmpty())
@@ -77,25 +98,23 @@ bool IndirectSqw::validate() {
 }
 
 void IndirectSqw::run() {
-  QString sampleWsName = m_uiForm.dsSampleInput->getCurrentDataName();
-  QString sqwWsName = sampleWsName.left(sampleWsName.length() - 4) + "_sqw";
-  QString eRebinWsName = sampleWsName.left(sampleWsName.length() - 4) + "_r";
+  auto const sampleWsName = m_uiForm.dsSampleInput->getCurrentDataName();
+  auto const sqwWsName = sampleWsName.left(sampleWsName.length() - 4) + "_sqw";
+  auto const eRebinWsName = sampleWsName.left(sampleWsName.length() - 4) + "_r";
 
-  QString rebinString = m_uiForm.spQLow->text() + "," +
-                        m_uiForm.spQWidth->text() + "," +
-                        m_uiForm.spQHigh->text();
+  auto const rebinString = m_uiForm.spQLow->text() + "," +
+                           m_uiForm.spQWidth->text() + "," +
+                           m_uiForm.spQHigh->text();
 
   // Rebin in energy
-  bool rebinInEnergy = m_uiForm.ckRebinInEnergy->isChecked();
+  bool const rebinInEnergy = m_uiForm.ckRebinInEnergy->isChecked();
   if (rebinInEnergy) {
-    QString eRebinString = m_uiForm.spELow->text() + "," +
-                           m_uiForm.spEWidth->text() + "," +
-                           m_uiForm.spEHigh->text();
+    auto const eRebinString = m_uiForm.spELow->text() + "," +
+                              m_uiForm.spEWidth->text() + "," +
+                              m_uiForm.spEHigh->text();
 
-    IAlgorithm_sptr energyRebinAlg =
-        AlgorithmManager::Instance().create("Rebin");
+    auto energyRebinAlg = AlgorithmManager::Instance().create("Rebin");
     energyRebinAlg->initialize();
-
     energyRebinAlg->setProperty("InputWorkspace", sampleWsName.toStdString());
     energyRebinAlg->setProperty("OutputWorkspace", eRebinWsName.toStdString());
     energyRebinAlg->setProperty("Params", eRebinString.toStdString());
@@ -103,9 +122,9 @@ void IndirectSqw::run() {
     m_batchAlgoRunner->addAlgorithm(energyRebinAlg);
   }
 
-  QString eFixed = getInstrumentDetails()["Efixed"];
+  auto const eFixed = getInstrumentDetails()["Efixed"];
 
-  IAlgorithm_sptr sqwAlg = AlgorithmManager::Instance().create("SofQW");
+  auto sqwAlg = AlgorithmManager::Instance().create("SofQW");
   sqwAlg->initialize();
 
   BatchAlgorithmRunner::AlgorithmRuntimeProps sqwInputProps;
@@ -124,10 +143,8 @@ void IndirectSqw::run() {
   m_batchAlgoRunner->addAlgorithm(sqwAlg, sqwInputProps);
 
   // Add sample log for S(Q, w) algorithm used
-  IAlgorithm_sptr sampleLogAlg =
-      AlgorithmManager::Instance().create("AddSampleLog");
+  auto sampleLogAlg = AlgorithmManager::Instance().create("AddSampleLog");
   sampleLogAlg->initialize();
-
   sampleLogAlg->setProperty("LogName", "rebin_type");
   sampleLogAlg->setProperty("LogType", "String");
   sampleLogAlg->setProperty("LogText", "NormalisedPolygon");
@@ -143,13 +160,8 @@ void IndirectSqw::run() {
   m_batchAlgoRunner->executeBatch();
 }
 
-MatrixWorkspace_const_sptr
-IndirectSqw::getADSWorkspace(std::string const &name) const {
-  return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name);
-}
-
 std::size_t IndirectSqw::getOutWsNumberOfSpectra() const {
-  return getADSWorkspace(m_pythonExportWsName)->getNumberHistograms();
+  return getADSMatrixWorkspace(m_pythonExportWsName)->getNumberHistograms();
 }
 
 /**
@@ -177,27 +189,18 @@ void IndirectSqw::setPlotSpectrumIndexMax(int maximum) {
  *
  * Creates a colour 2D plot of the data
  */
-void IndirectSqw::plotContour() {
+void IndirectSqw::plotRqwContour() {
   if (m_uiForm.dsSampleInput->isValid()) {
-    QString sampleWsName = m_uiForm.dsSampleInput->getCurrentDataName();
+    auto const sampleName =
+        m_uiForm.dsSampleInput->getCurrentDataName().toStdString();
+    auto const outputName =
+        sampleName.substr(0, sampleName.size() - 4) + "_rqw";
 
-    QString convertedWsName =
-        sampleWsName.left(sampleWsName.length() - 4) + "_rqw";
+    convertToSpectrumAxis(sampleName, outputName);
 
-    IAlgorithm_sptr convertSpecAlg =
-        AlgorithmManager::Instance().create("ConvertSpectrumAxis");
-    convertSpecAlg->initialize();
-
-    convertSpecAlg->setProperty("InputWorkspace", sampleWsName.toStdString());
-    convertSpecAlg->setProperty("OutputWorkspace",
-                                convertedWsName.toStdString());
-    convertSpecAlg->setProperty("Target", "ElasticQ");
-    convertSpecAlg->setProperty("EMode", "Indirect");
-
-    convertSpecAlg->execute();
-
-    QString pyInput = "plot2D('" + convertedWsName + "')\n";
-    m_pythonRunner.runPythonCode(pyInput);
+    auto const rqwWorkspace = getADSMatrixWorkspace(outputName);
+    if (rqwWorkspace)
+      m_uiForm.rqwPlot2D->setWorkspace(rqwWorkspace);
   } else {
     emit showMessageBox("Invalid filename.");
   }
diff --git a/qt/scientific_interfaces/Indirect/IndirectSqw.h b/qt/scientific_interfaces/Indirect/IndirectSqw.h
index 14a5126f3e80ac8f2458d7e19da94061a2fdee4c..6e7decdd617390cf5dec85eab1dc7f58d224a9e5 100644
--- a/qt/scientific_interfaces/Indirect/IndirectSqw.h
+++ b/qt/scientific_interfaces/Indirect/IndirectSqw.h
@@ -31,7 +31,7 @@ public:
   bool validate() override;
 
 private slots:
-  void plotContour();
+  void plotRqwContour();
   void sqwAlgDone(bool error);
 
   void runClicked();
diff --git a/qt/scientific_interfaces/Indirect/IndirectSqw.ui b/qt/scientific_interfaces/Indirect/IndirectSqw.ui
index 64fdd7dc5efffb5a1ce49d3d329edc23f6c75ce7..a95068485584d1262129a90f13f2c45fed5e9307 100644
--- a/qt/scientific_interfaces/Indirect/IndirectSqw.ui
+++ b/qt/scientific_interfaces/Indirect/IndirectSqw.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>601</width>
-    <height>303</height>
+    <width>602</width>
+    <height>475</height>
    </rect>
   </property>
   <property name="minimumSize">
@@ -43,9 +43,6 @@
         <property name="autoLoad" stdset="0">
          <bool>true</bool>
         </property>
-        <property name="loadLabelText" stdset="0">
-         <string>Plot Input</string>
-        </property>
         <property name="workspaceSuffixes" stdset="0">
          <stringlist>
           <string>_red</string>
@@ -57,7 +54,7 @@
          </stringlist>
         </property>
         <property name="showLoad" stdset="0">
-         <bool>true</bool>
+         <bool>false</bool>
         </property>
        </widget>
       </item>
@@ -69,283 +66,336 @@
      <property name="title">
       <string>Options</string>
      </property>
-     <layout class="QVBoxLayout" name="verticalLayout_7">
+     <layout class="QHBoxLayout" name="horizontalLayout_3">
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
       <item>
-       <layout class="QGridLayout" name="loOptions" columnstretch="0,0,0,0,0,0">
-        <item row="1" column="4">
-         <widget class="QDoubleSpinBox" name="spELow">
-          <property name="enabled">
-           <bool>false</bool>
-          </property>
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="maximumSize">
-           <size>
-            <width>200</width>
-            <height>16777215</height>
-           </size>
-          </property>
-          <property name="decimals">
-           <number>5</number>
-          </property>
-          <property name="minimum">
-           <double>-1000.000000000000000</double>
-          </property>
-          <property name="maximum">
-           <double>1000.000000000000000</double>
-          </property>
-          <property name="singleStep">
-           <double>0.100000000000000</double>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="3">
-         <widget class="QLabel" name="lbEWidth">
-          <property name="enabled">
-           <bool>true</bool>
-          </property>
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>43</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>E Width:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="0">
-         <widget class="QLabel" name="lbQWidth">
-          <property name="minimumSize">
-           <size>
-            <width>43</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>Q Width:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="3" column="4">
-         <widget class="QDoubleSpinBox" name="spEHigh">
-          <property name="enabled">
-           <bool>false</bool>
-          </property>
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="maximumSize">
-           <size>
-            <width>200</width>
-            <height>16777215</height>
-           </size>
-          </property>
-          <property name="decimals">
-           <number>5</number>
-          </property>
-          <property name="minimum">
-           <double>-1000.000000000000000</double>
-          </property>
-          <property name="maximum">
-           <double>1000.000000000000000</double>
-          </property>
-          <property name="singleStep">
-           <double>0.100000000000000</double>
-          </property>
-         </widget>
-        </item>
-        <item row="3" column="3">
-         <widget class="QLabel" name="lbEHigh">
-          <property name="enabled">
-           <bool>true</bool>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>43</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>E High:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="0">
-         <widget class="QLabel" name="lbQLow">
-          <property name="minimumSize">
-           <size>
-            <width>43</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>Q Low:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="3" column="0">
-         <widget class="QLabel" name="lbQHigh">
-          <property name="minimumSize">
-           <size>
-            <width>43</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>Q High:</string>
-          </property>
+       <layout class="QGridLayout" name="loOptions" columnstretch="0">
+        <property name="spacing">
+         <number>6</number>
+        </property>
+        <item row="5" column="0">
+         <widget class="QFrame" name="fEValues">
+          <layout class="QGridLayout" name="gridLayout_2">
+           <item row="0" column="0" rowspan="2" colspan="2">
+            <widget class="QCheckBox" name="ckRebinInEnergy">
+             <property name="text">
+              <string>Rebin in Energy</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QDoubleSpinBox" name="spEWidth">
+             <property name="enabled">
+              <bool>false</bool>
+             </property>
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>200</width>
+               <height>16777215</height>
+              </size>
+             </property>
+             <property name="decimals">
+              <number>5</number>
+             </property>
+             <property name="minimum">
+              <double>0.000000000000000</double>
+             </property>
+             <property name="maximum">
+              <double>1000.000000000000000</double>
+             </property>
+             <property name="singleStep">
+              <double>0.010000000000000</double>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="lbEWidth">
+             <property name="enabled">
+              <bool>true</bool>
+             </property>
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>43</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>E Width:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QDoubleSpinBox" name="spELow">
+             <property name="enabled">
+              <bool>false</bool>
+             </property>
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>200</width>
+               <height>16777215</height>
+              </size>
+             </property>
+             <property name="decimals">
+              <number>5</number>
+             </property>
+             <property name="minimum">
+              <double>-1000.000000000000000</double>
+             </property>
+             <property name="maximum">
+              <double>1000.000000000000000</double>
+             </property>
+             <property name="singleStep">
+              <double>0.100000000000000</double>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="lbELow">
+             <property name="enabled">
+              <bool>true</bool>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>43</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>E Low:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="0">
+            <widget class="QLabel" name="lbEHigh">
+             <property name="enabled">
+              <bool>true</bool>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>43</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>E High:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="1">
+            <widget class="QDoubleSpinBox" name="spEHigh">
+             <property name="enabled">
+              <bool>false</bool>
+             </property>
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>200</width>
+               <height>16777215</height>
+              </size>
+             </property>
+             <property name="decimals">
+              <number>5</number>
+             </property>
+             <property name="minimum">
+              <double>-1000.000000000000000</double>
+             </property>
+             <property name="maximum">
+              <double>1000.000000000000000</double>
+             </property>
+             <property name="singleStep">
+              <double>0.100000000000000</double>
+             </property>
+            </widget>
+           </item>
+          </layout>
          </widget>
         </item>
-        <item row="2" column="4">
-         <widget class="QDoubleSpinBox" name="spEWidth">
-          <property name="enabled">
-           <bool>false</bool>
-          </property>
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
+        <item row="6" column="0">
+         <spacer name="verticalSpacer">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
           </property>
-          <property name="maximumSize">
+          <property name="sizeHint" stdset="0">
            <size>
-            <width>200</width>
-            <height>16777215</height>
+            <width>20</width>
+            <height>40</height>
            </size>
           </property>
-          <property name="decimals">
-           <number>5</number>
-          </property>
-          <property name="minimum">
-           <double>0.000000000000000</double>
-          </property>
-          <property name="maximum">
-           <double>1000.000000000000000</double>
-          </property>
-          <property name="singleStep">
-           <double>0.010000000000000</double>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="3" colspan="3">
-         <widget class="QCheckBox" name="ckRebinInEnergy">
-          <property name="text">
-           <string>Rebin in Energy</string>
-          </property>
-         </widget>
+         </spacer>
         </item>
-        <item row="1" column="3">
-         <widget class="QLabel" name="lbELow">
-          <property name="enabled">
-           <bool>true</bool>
+        <item row="0" column="0">
+         <spacer name="verticalSpacer_3">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
           </property>
-          <property name="minimumSize">
+          <property name="sizeHint" stdset="0">
            <size>
-            <width>43</width>
-            <height>0</height>
+            <width>20</width>
+            <height>40</height>
            </size>
           </property>
-          <property name="text">
-           <string>E Low:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="1">
-         <widget class="QDoubleSpinBox" name="spQLow">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="decimals">
-           <number>5</number>
-          </property>
-          <property name="minimum">
-           <double>-1000.000000000000000</double>
-          </property>
-          <property name="maximum">
-           <double>1000.000000000000000</double>
-          </property>
-          <property name="singleStep">
-           <double>0.100000000000000</double>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="1">
-         <widget class="QDoubleSpinBox" name="spQWidth">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="decimals">
-           <number>5</number>
-          </property>
-          <property name="minimum">
-           <double>0.000000000000000</double>
-          </property>
-          <property name="maximum">
-           <double>1000.000000000000000</double>
-          </property>
-          <property name="singleStep">
-           <double>0.100000000000000</double>
-          </property>
-         </widget>
-        </item>
-        <item row="3" column="1">
-         <widget class="QDoubleSpinBox" name="spQHigh">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="decimals">
-           <number>5</number>
-          </property>
-          <property name="minimum">
-           <double>-1000.000000000000000</double>
-          </property>
-          <property name="maximum">
-           <double>1000.000000000000000</double>
-          </property>
-          <property name="singleStep">
-           <double>0.100000000000000</double>
-          </property>
-         </widget>
+         </spacer>
         </item>
-        <item row="0" column="2">
-         <spacer name="horizontalSpacer">
+        <item row="4" column="0">
+         <spacer name="verticalSpacer_2">
           <property name="orientation">
-           <enum>Qt::Horizontal</enum>
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeType">
+           <enum>QSizePolicy::Fixed</enum>
           </property>
           <property name="sizeHint" stdset="0">
            <size>
-            <width>40</width>
+            <width>20</width>
             <height>20</height>
            </size>
           </property>
          </spacer>
         </item>
+        <item row="1" column="0">
+         <widget class="QFrame" name="fQValues">
+          <layout class="QGridLayout" name="gridLayout">
+           <item row="1" column="0">
+            <widget class="QLabel" name="lbQWidth">
+             <property name="minimumSize">
+              <size>
+               <width>43</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>Q Width:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="lbQLow">
+             <property name="minimumSize">
+              <size>
+               <width>43</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>Q Low:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="lbQHigh">
+             <property name="minimumSize">
+              <size>
+               <width>43</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>Q High:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QDoubleSpinBox" name="spQWidth">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="decimals">
+              <number>5</number>
+             </property>
+             <property name="minimum">
+              <double>0.000000000000000</double>
+             </property>
+             <property name="maximum">
+              <double>1000.000000000000000</double>
+             </property>
+             <property name="singleStep">
+              <double>0.100000000000000</double>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QDoubleSpinBox" name="spQHigh">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="decimals">
+              <number>5</number>
+             </property>
+             <property name="minimum">
+              <double>-1000.000000000000000</double>
+             </property>
+             <property name="maximum">
+              <double>1000.000000000000000</double>
+             </property>
+             <property name="singleStep">
+              <double>0.100000000000000</double>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QDoubleSpinBox" name="spQLow">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="decimals">
+              <number>5</number>
+             </property>
+             <property name="minimum">
+              <double>-1000.000000000000000</double>
+             </property>
+             <property name="maximum">
+              <double>1000.000000000000000</double>
+             </property>
+             <property name="singleStep">
+              <double>0.100000000000000</double>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
        </layout>
       </item>
+      <item>
+       <widget class="MantidQt::MantidWidgets::ContourPreviewPlot" name="rqwPlot2D" native="true"/>
+      </item>
      </layout>
     </widget>
    </item>
@@ -507,15 +557,14 @@
    <extends>QWidget</extends>
    <header>MantidQtWidgets/Common/DataSelector.h</header>
   </customwidget>
+  <customwidget>
+   <class>MantidQt::MantidWidgets::ContourPreviewPlot</class>
+   <extends>QWidget</extends>
+   <header>MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <tabstops>
-  <tabstop>spQLow</tabstop>
-  <tabstop>spQWidth</tabstop>
-  <tabstop>spQHigh</tabstop>
-  <tabstop>ckRebinInEnergy</tabstop>
-  <tabstop>spELow</tabstop>
-  <tabstop>spEWidth</tabstop>
-  <tabstop>spEHigh</tabstop>
   <tabstop>pbSave</tabstop>
  </tabstops>
  <resources/>
diff --git a/qt/scientific_interfaces/Indirect/IndirectTab.cpp b/qt/scientific_interfaces/Indirect/IndirectTab.cpp
index 58563d2ccc0677a789d93aa4cd8db2fb327e8cd4..8098863abda605621364d89434833e44039b622e 100644
--- a/qt/scientific_interfaces/Indirect/IndirectTab.cpp
+++ b/qt/scientific_interfaces/Indirect/IndirectTab.cpp
@@ -30,8 +30,20 @@ using Mantid::Types::Core::DateAndTime;
 
 namespace {
 Mantid::Kernel::Logger g_log("IndirectTab");
+
+std::string castToString(int value) {
+  return boost::lexical_cast<std::string>(value);
+}
+
+template <typename Predicate>
+void setPropertyIf(Algorithm_sptr algorithm, std::string const &propName,
+                   std::string const &value, Predicate const &condition) {
+  if (condition)
+    algorithm->setPropertyValue(propName, value);
 }
 
+} // namespace
+
 namespace MantidQt {
 namespace CustomInterfaces {
 //----------------------------------------------------------------------------------------------
@@ -136,26 +148,20 @@ void IndirectTab::exportPythonScript() {
  * @return If the algorithm was successful
  */
 bool IndirectTab::loadFile(const QString &filename, const QString &outputName,
-                           const int specMin, const int specMax) {
-  Algorithm_sptr load =
-      AlgorithmManager::Instance().createUnmanaged("Load", -1);
-  load->initialize();
-
-  load->setProperty("Filename", filename.toStdString());
-  load->setProperty("OutputWorkspace", outputName.toStdString());
-
-  if (specMin != -1)
-    load->setPropertyValue("SpectrumMin",
-                           boost::lexical_cast<std::string>(specMin));
-
-  if (specMax != -1)
-    load->setPropertyValue("SpectrumMax",
-                           boost::lexical_cast<std::string>(specMax));
-
-  load->execute();
-
-  // If reloading fails we're out of options
-  return load->isExecuted();
+                           const int specMin, const int specMax,
+                           bool loadHistory) {
+  const auto algName = loadHistory ? "Load" : "LoadNexusProcessed";
+
+  auto loader = AlgorithmManager::Instance().createUnmanaged(algName, -1);
+  loader->initialize();
+  loader->setProperty("Filename", filename.toStdString());
+  loader->setProperty("OutputWorkspace", outputName.toStdString());
+  setPropertyIf(loader, "SpectrumMin", castToString(specMin), specMin != -1);
+  setPropertyIf(loader, "SpectrumMax", castToString(specMax), specMax != -1);
+  setPropertyIf(loader, "LoadHistory", loadHistory ? "1" : "0", !loadHistory);
+  loader->execute();
+
+  return loader->isExecuted();
 }
 
 /**
@@ -167,19 +173,23 @@ bool IndirectTab::loadFile(const QString &filename, const QString &outputName,
  */
 void IndirectTab::addSaveWorkspaceToQueue(const QString &wsName,
                                           const QString &filename) {
+  addSaveWorkspaceToQueue(wsName.toStdString(), filename.toStdString());
+}
+
+void IndirectTab::addSaveWorkspaceToQueue(const std::string &wsName,
+                                          const std::string &filename) {
   // Setup the input workspace property
   API::BatchAlgorithmRunner::AlgorithmRuntimeProps saveProps;
-  saveProps["InputWorkspace"] = wsName.toStdString();
+  saveProps["InputWorkspace"] = wsName;
 
   // Setup the algorithm
-  IAlgorithm_sptr saveAlgo =
-      AlgorithmManager::Instance().create("SaveNexusProcessed");
+  auto saveAlgo = AlgorithmManager::Instance().create("SaveNexusProcessed");
   saveAlgo->initialize();
 
-  if (filename.isEmpty())
-    saveAlgo->setProperty("Filename", wsName.toStdString() + ".nxs");
+  if (filename.empty())
+    saveAlgo->setProperty("Filename", wsName + ".nxs");
   else
-    saveAlgo->setProperty("Filename", filename.toStdString());
+    saveAlgo->setProperty("Filename", filename);
 
   // Add the save algorithm to the batch
   m_batchAlgoRunner->addAlgorithm(saveAlgo, saveProps);
@@ -260,19 +270,19 @@ void IndirectTab::plotMultipleSpectra(
  * @param spectraIndex Index of spectrum from each workspace to plot
  */
 void IndirectTab::plotSpectrum(const QStringList &workspaceNames,
-                               int spectraIndex) {
-  if (workspaceNames.isEmpty())
-    return;
-
-  QString pyInput = "from mantidplot import plotSpectrum\n";
-
-  pyInput += "plotSpectrum(['";
-  pyInput += workspaceNames.join("','");
-  pyInput += "'], ";
-  pyInput += QString::number(spectraIndex);
-  pyInput += ")\n";
-
-  m_pythonRunner.runPythonCode(pyInput);
+                               const int &spectraIndex, const bool &errorBars) {
+  if (!workspaceNames.isEmpty()) {
+    const QString errors = errorBars ? "True" : "False";
+
+    QString pyInput = "from mantidplot import plotSpectrum\n";
+    pyInput += "plotSpectrum(['";
+    pyInput += workspaceNames.join("','");
+    pyInput += "'], ";
+    pyInput += QString::number(spectraIndex);
+    pyInput += ", error_bars=" + errors + ")\n";
+
+    m_pythonRunner.runPythonCode(pyInput);
+  }
 }
 
 /**
@@ -281,14 +291,15 @@ void IndirectTab::plotSpectrum(const QStringList &workspaceNames,
  *
  * @param workspaceName Names of workspace to plot
  * @param spectraIndex Workspace Index of spectrum to plot
+ * @param errorBars Is true if you want to plot the error bars
  */
-void IndirectTab::plotSpectrum(const QString &workspaceName, int spectraIndex) {
-  if (workspaceName.isEmpty())
-    return;
-
-  QStringList workspaceNames;
-  workspaceNames << workspaceName;
-  plotSpectrum(workspaceNames, spectraIndex);
+void IndirectTab::plotSpectrum(const QString &workspaceName,
+                               const int &spectraIndex, const bool &errorBars) {
+  if (!workspaceName.isEmpty()) {
+    QStringList workspaceNames;
+    workspaceNames << workspaceName;
+    plotSpectrum(workspaceNames, spectraIndex, errorBars);
+  }
 }
 
 /**
diff --git a/qt/scientific_interfaces/Indirect/IndirectTab.h b/qt/scientific_interfaces/Indirect/IndirectTab.h
index b62834cb7625deeac80f37a2815074c8e5869aeb..9287989c9b11a521bc4c6a265305d898c6313717 100644
--- a/qt/scientific_interfaces/Indirect/IndirectTab.h
+++ b/qt/scientific_interfaces/Indirect/IndirectTab.h
@@ -75,12 +75,14 @@ protected slots:
   virtual void algorithmFinished(bool error);
 
 protected:
-  /// Run the load algorithm with the given file name, output name and spectrum
-  /// range
+  /// Run the load algorithms
   bool loadFile(const QString &filename, const QString &outputName,
-                const int specMin = -1, const int specMax = -1);
+                const int specMin = -1, const int specMax = -1,
+                bool loadHistory = true);
 
   /// Add a SaveNexusProcessed step to the batch queue
+  void addSaveWorkspaceToQueue(const std::string &wsName,
+                               const std::string &filename = "");
   void addSaveWorkspaceToQueue(const QString &wsName,
                                const QString &filename = "");
 
@@ -92,9 +94,11 @@ protected:
   void plotMultipleSpectra(const QStringList &workspaceNames,
                            const std::vector<int> &workspaceIndices);
   /// Plot a spectrum plot with a given ws index
-  void plotSpectrum(const QStringList &workspaceNames, int spectraIndex = 0);
+  void plotSpectrum(const QStringList &workspaceNames,
+                    const int &spectraIndex = 0, const bool &errorBars = false);
   /// Plot a spectrum plot of a given workspace
-  void plotSpectrum(const QString &workspaceName, int spectraIndex = 0);
+  void plotSpectrum(const QString &workspaceName, const int &spectraIndex = 0,
+                    const bool &errorBars = false);
 
   /// Plot a spectrum plot with a given spectra range
   void plotSpectrum(const QStringList &workspaceNames, int specStart,
diff --git a/qt/scientific_interfaces/Indirect/IqtFitModel.cpp b/qt/scientific_interfaces/Indirect/IqtFitModel.cpp
index 6e48558c75a6c88d74fac1f00eded182a2c865ae..23542075d12eb7e5a3eb910d48184109c34f32d0 100644
--- a/qt/scientific_interfaces/Indirect/IqtFitModel.cpp
+++ b/qt/scientific_interfaces/Indirect/IqtFitModel.cpp
@@ -108,8 +108,8 @@ bool hasConstrainableIntensities(IFunction_sptr function) {
 }
 
 double computeTauApproximation(MatrixWorkspace_sptr workspace) {
-  const auto x = workspace->x(0);
-  const auto y = workspace->y(0);
+  const auto &x = workspace->x(0);
+  const auto &y = workspace->y(0);
 
   if (x.size() > 4)
     return -x[4] / log(y[4]);
@@ -217,7 +217,7 @@ IAlgorithm_sptr IqtFitModel::simultaneousFitAlgorithm() const {
 
 std::string IqtFitModel::sequentialFitOutputName() const {
   if (isMultiFit())
-    return "MultiIqtFit_" + m_fitType + "_Result";
+    return "MultiIqtFit_" + m_fitType + "_Results";
   auto const fitString = getFitString(getWorkspace(0));
   return createOutputName("%1%" + fitString + "_" + m_fitType + "_s%2%", "_to_",
                           0);
@@ -225,7 +225,7 @@ std::string IqtFitModel::sequentialFitOutputName() const {
 
 std::string IqtFitModel::simultaneousFitOutputName() const {
   if (isMultiFit())
-    return "MultiSimultaneousIqtFit_" + m_fitType + "_Result";
+    return "MultiSimultaneousIqtFit_" + m_fitType + "_Results";
   auto const fitString = getFitString(getWorkspace(0));
   return createOutputName("%1%" + fitString + "_mult" + m_fitType + "_s%2%",
                           "_to_", 0);
diff --git a/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.cpp b/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.cpp
index 083fd3ef42e5cc7d9285e12941eb6948e36f355e..b24b0629d44d5284f44783527ba3220a531d53c5 100644
--- a/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.cpp
+++ b/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.cpp
@@ -23,6 +23,8 @@ JumpFitAddWorkspaceDialog::JumpFitAddWorkspaceDialog(QWidget *parent)
   connect(m_uiForm.cbParameterType,
           SIGNAL(currentIndexChanged(const QString &)), this,
           SLOT(emitParameterTypeChanged(const QString &)));
+  connect(m_uiForm.pbAdd, SIGNAL(clicked()), this, SIGNAL(addData()));
+  connect(m_uiForm.pbClose, SIGNAL(clicked()), this, SIGNAL(closeDialog()));
 }
 
 std::string JumpFitAddWorkspaceDialog::workspaceName() const {
diff --git a/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.ui b/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.ui
index b13089f01debef6c24f068d602b867cbf89f029d..ac617568763d48ae4971f88f30877dd3573e80c7 100644
--- a/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.ui
+++ b/qt/scientific_interfaces/Indirect/JumpFitAddWorkspaceDialog.ui
@@ -92,13 +92,48 @@
     </widget>
    </item>
    <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
+    <widget class="QFrame" name="frame">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>147</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pbAdd">
+        <property name="text">
+         <string>Add</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pbClose">
+        <property name="text">
+         <string>Close</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
     </widget>
    </item>
   </layout>
@@ -113,36 +148,40 @@
  <resources/>
  <connections>
   <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
+   <sender>pbAdd</sender>
+   <signal>clicked()</signal>
    <receiver>JumpFitAddWorkspaceDialog</receiver>
-   <slot>accept()</slot>
+   <slot>addData()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
+     <x>273</x>
+     <y>166</y>
     </hint>
     <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
+     <x>270</x>
+     <y>221</y>
     </hint>
    </hints>
   </connection>
   <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
+   <sender>pbClose</sender>
+   <signal>clicked()</signal>
    <receiver>JumpFitAddWorkspaceDialog</receiver>
-   <slot>reject()</slot>
+   <slot>closeDialog()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
+     <x>369</x>
+     <y>164</y>
     </hint>
     <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
+     <x>361</x>
+     <y>212</y>
     </hint>
    </hints>
   </connection>
  </connections>
+ <slots>
+  <slot>addData()</slot>
+  <slot>closeDialog()</slot>
+ </slots>
 </ui>
diff --git a/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.cpp b/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.cpp
index 1c8c56cfe20440ce98a749510b1ef1d2dd3cbe58..e319bde0ae935431f849f061b4c7d98db6fc1f65 100644
--- a/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.cpp
+++ b/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.cpp
@@ -16,7 +16,7 @@ namespace CustomInterfaces {
 namespace IDA {
 
 JumpFitDataPresenter::JumpFitDataPresenter(
-    JumpFitModel *model, IndirectFitDataView *view, QComboBox *cbParameterType,
+    JumpFitModel *model, IIndirectFitDataView *view, QComboBox *cbParameterType,
     QComboBox *cbParameter, QLabel *lbParameterType, QLabel *lbParameter)
     : IndirectFitDataPresenter(
           model, view,
@@ -196,13 +196,10 @@ void JumpFitDataPresenter::setModelSpectrum(int index) {
     m_jumpModel->setActiveEISF(static_cast<std::size_t>(index), m_dataIndex);
 }
 
-void JumpFitDataPresenter::dialogExecuted(IAddWorkspaceDialog const *dialog,
-                                          QDialog::DialogCode result) {
-  if (result == QDialog::Rejected &&
-      m_jumpModel->numberOfWorkspaces() > m_dataIndex)
+void JumpFitDataPresenter::closeDialog() {
+  if (m_jumpModel->numberOfWorkspaces() > m_dataIndex)
     m_jumpModel->removeWorkspace(m_dataIndex);
-  else
-    IndirectFitDataPresenter::dialogExecuted(dialog, result);
+  IndirectFitDataPresenter::closeDialog();
 }
 
 std::unique_ptr<IAddWorkspaceDialog>
diff --git a/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.h b/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.h
index 58bc7c75f7eb5282bd262e927095a7fa94d14939..8e023cc925c08233ff5b667f13356c611e567258 100644
--- a/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.h
+++ b/qt/scientific_interfaces/Indirect/JumpFitDataPresenter.h
@@ -18,13 +18,11 @@ namespace MantidQt {
 namespace CustomInterfaces {
 namespace IDA {
 
-/**
-  A presenter.
-*/
-class DLLExport JumpFitDataPresenter : public IndirectFitDataPresenter {
+class MANTIDQT_INDIRECT_DLL JumpFitDataPresenter
+    : public IndirectFitDataPresenter {
   Q_OBJECT
 public:
-  JumpFitDataPresenter(JumpFitModel *model, IndirectFitDataView *view,
+  JumpFitDataPresenter(JumpFitModel *model, IIndirectFitDataView *view,
                        QComboBox *cbParameterType, QComboBox *cbParameter,
                        QLabel *lbParameterType, QLabel *lbParameter);
 
@@ -50,8 +48,7 @@ private slots:
 private:
   void setAvailableParameters(const std::vector<std::string> &parameters);
   void addDataToModel(IAddWorkspaceDialog const *dialog) override;
-  void dialogExecuted(IAddWorkspaceDialog const *dialog,
-                      QDialog::DialogCode result) override;
+  void closeDialog() override;
   std::unique_ptr<IAddWorkspaceDialog>
   getAddWorkspaceDialog(QWidget *parent) const override;
   void updateParameterOptions(JumpFitAddWorkspaceDialog *dialog);
diff --git a/qt/scientific_interfaces/Indirect/JumpFitModel.cpp b/qt/scientific_interfaces/Indirect/JumpFitModel.cpp
index be4af632db31df389a7632270cb435b90b63cda0..06b74852fe0049d047b3fc9f55313ea2c33b214b 100644
--- a/qt/scientific_interfaces/Indirect/JumpFitModel.cpp
+++ b/qt/scientific_interfaces/Indirect/JumpFitModel.cpp
@@ -340,7 +340,7 @@ JumpFitModel::getEISFSpectrum(std::size_t eisfIndex,
 
 std::string JumpFitModel::sequentialFitOutputName() const {
   if (isMultiFit())
-    return "MultiFofQFit_" + m_fitType + "_Result";
+    return "MultiFofQFit_" + m_fitType + "_Results";
   return constructOutputName();
 }
 
@@ -356,7 +356,7 @@ std::string JumpFitModel::getResultXAxisUnit() const { return ""; }
 
 std::string JumpFitModel::constructOutputName() const {
   auto const name = createOutputName("%1%_FofQFit_" + m_fitType, "", 0);
-  auto const position = name.find("_Result");
+  auto const position = name.find("_Results");
   if (position != std::string::npos)
     return name.substr(0, position) + name.substr(position + 7, name.size());
   return name;
diff --git a/qt/scientific_interfaces/Indirect/JumpFitModel.h b/qt/scientific_interfaces/Indirect/JumpFitModel.h
index e992467267f00d7101a08c54c26d2d633c81bf40..eb95e426631a4bb0bcbd5390c60e0f8cd6113a05 100644
--- a/qt/scientific_interfaces/Indirect/JumpFitModel.h
+++ b/qt/scientific_interfaces/Indirect/JumpFitModel.h
@@ -22,6 +22,8 @@ struct JumpFitParameters {
 
 class DLLExport JumpFitModel : public IndirectFittingModel {
 public:
+  using IndirectFittingModel::addWorkspace;
+
   void addWorkspace(Mantid::API::MatrixWorkspace_sptr workspace,
                     const Spectra &) override;
   void removeWorkspace(std::size_t index) override;
diff --git a/qt/scientific_interfaces/Indirect/MSDFitModel.cpp b/qt/scientific_interfaces/Indirect/MSDFitModel.cpp
index ad095e6ad31edf064f183e80a2c1563726d730ed..484ad8f7ffac85adf7638672e45dd00e51adfc3d 100644
--- a/qt/scientific_interfaces/Indirect/MSDFitModel.cpp
+++ b/qt/scientific_interfaces/Indirect/MSDFitModel.cpp
@@ -18,7 +18,7 @@ void MSDFitModel::setFitType(const std::string &fitType) {
 
 std::string MSDFitModel::sequentialFitOutputName() const {
   if (isMultiFit())
-    return "MultiMSDFit_" + m_fitType + "_Result";
+    return "MultiMSDFit_" + m_fitType + "_Results";
   return createOutputName("%1%_MSDFit_" + m_fitType + "_s%2%", "_to_", 0);
 }
 
diff --git a/qt/scientific_interfaces/Indirect/Stretch.ui b/qt/scientific_interfaces/Indirect/Stretch.ui
index 00fa201f5db87f4d04f80ffd5d61e4982be0de58..8813f60adef9046e91f85b10fbb69f77150eda41 100644
--- a/qt/scientific_interfaces/Indirect/Stretch.ui
+++ b/qt/scientific_interfaces/Indirect/Stretch.ui
@@ -365,6 +365,9 @@
                <height>0</height>
               </size>
              </property>
+             <property name="sizeAdjustPolicy">
+              <enum>QComboBox::AdjustToContents</enum>
+             </property>
             </widget>
            </item>
            <item>
@@ -411,17 +414,17 @@
   </layout>
  </widget>
  <customwidgets>
-  <customwidget>
-   <class>MantidQt::MantidWidgets::DataSelector</class>
-   <extends>QWidget</extends>
-   <header>MantidQtWidgets/Common/DataSelector.h</header>
-  </customwidget>
   <customwidget>
    <class>MantidQt::MantidWidgets::PreviewPlot</class>
    <extends>QWidget</extends>
    <header>MantidQtWidgets/LegacyQwt/PreviewPlot.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>MantidQt::MantidWidgets::DataSelector</class>
+   <extends>QWidget</extends>
+   <header>MantidQtWidgets/Common/DataSelector.h</header>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections/>
diff --git a/qt/scientific_interfaces/Indirect/test/CMakeLists.txt b/qt/scientific_interfaces/Indirect/test/CMakeLists.txt
index c402c6ca2c551c8e65cf191d18256e192ea6dcf3..d7e32222b1c874f57b413bb56f8e2c12deb55ad6 100644
--- a/qt/scientific_interfaces/Indirect/test/CMakeLists.txt
+++ b/qt/scientific_interfaces/Indirect/test/CMakeLists.txt
@@ -2,12 +2,16 @@
 # Testing
 ###########################################################################
 set ( TEST_FILES
+  ConvFitDataPresenterTest.h
+  IndirectDataTablePresenterTest.h
+  IndirectFitDataPresenterTest.h
   IndirectFitDataTest.h
   IndirectFitOutputTest.h
   IndirectFitPlotModelTest.h
   IndirectFitPlotPresenterTest.h
   IndirectFittingModelTest.h
   IndirectSpectrumSelectionPresenterTest.h
+  JumpFitDataPresenterTest.h
 )
 
 mtd_add_qt_tests (TARGET_NAME MantidQtInterfacesIndirectTest
diff --git a/qt/scientific_interfaces/Indirect/test/ConvFitDataPresenterTest.h b/qt/scientific_interfaces/Indirect/test/ConvFitDataPresenterTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..0ba9fd4d76fa6dc81aa170d5a3fae0dee6d4440a
--- /dev/null
+++ b/qt/scientific_interfaces/Indirect/test/ConvFitDataPresenterTest.h
@@ -0,0 +1,149 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTIDQT_CONVFITDATAPRESENTERTEST_H_
+#define MANTIDQT_CONVFITDATAPRESENTERTEST_H_
+
+#include <cxxtest/TestSuite.h>
+#include <gmock/gmock.h>
+
+#include "ConvFitDataPresenter.h"
+#include "ConvFitModel.h"
+#include "IIndirectFitDataView.h"
+
+#include "MantidAPI/FrameworkManager.h"
+#include "MantidKernel/WarningSuppressions.h"
+#include "MantidTestHelpers/IndirectFitDataCreationHelper.h"
+
+using namespace Mantid::API;
+using namespace Mantid::IndirectFitDataCreationHelper;
+using namespace MantidQt::CustomInterfaces;
+using namespace MantidQt::CustomInterfaces::IDA;
+using namespace testing;
+
+namespace {
+
+std::unique_ptr<QTableWidget> createEmptyTableWidget(int columns, int rows) {
+  auto table = std::make_unique<QTableWidget>(columns, rows);
+  for (auto column = 0; column < columns; ++column)
+    for (auto row = 0; row < rows; ++row)
+      table->setItem(row, column, new QTableWidgetItem("item"));
+  return table;
+}
+
+} // namespace
+
+GNU_DIAG_OFF_SUGGEST_OVERRIDE
+
+/// Mock object to mock the view
+class MockConvFitDataView : public IIndirectFitDataView {
+public:
+  /// Signals
+  void emitResolutionLoaded(QString const &workspaceName) {
+    emit resolutionLoaded(workspaceName);
+  }
+
+  /// Public Methods
+  MOCK_CONST_METHOD0(getDataTable, QTableWidget *());
+  MOCK_CONST_METHOD0(isMultipleDataTabSelected, bool());
+  MOCK_CONST_METHOD0(isResolutionHidden, bool());
+  MOCK_METHOD1(setResolutionHidden, void(bool hide));
+  MOCK_METHOD0(disableMultipleDataTab, void());
+
+  MOCK_CONST_METHOD0(getSelectedSample, std::string());
+  MOCK_CONST_METHOD0(getSelectedResolution, std::string());
+
+  MOCK_CONST_METHOD0(getSampleWSSuffices, QStringList());
+  MOCK_CONST_METHOD0(getSampleFBSuffices, QStringList());
+  MOCK_CONST_METHOD0(getResolutionWSSuffices, QStringList());
+  MOCK_CONST_METHOD0(getResolutionFBSuffices, QStringList());
+
+  MOCK_METHOD1(setSampleWSSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setSampleFBSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setResolutionWSSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setResolutionFBSuffices, void(QStringList const &suffices));
+
+  MOCK_METHOD1(readSettings, void(QSettings const &settings));
+  MOCK_METHOD1(validate, UserInputValidator &(UserInputValidator &validator));
+
+  /// Public slots
+  MOCK_METHOD1(displayWarning, void(std::string const &warning));
+};
+
+/// Mock object to mock the model
+class MockConvFitModel : public ConvFitModel {};
+
+GNU_DIAG_ON_SUGGEST_OVERRIDE
+
+class ConvFitDataPresenterTest : public CxxTest::TestSuite {
+public:
+  /// Needed to make sure everything is initialized
+  ConvFitDataPresenterTest() { FrameworkManager::Instance(); }
+
+  static ConvFitDataPresenterTest *createSuite() {
+    return new ConvFitDataPresenterTest();
+  }
+
+  static void destroySuite(ConvFitDataPresenterTest *suite) { delete suite; }
+
+  void setUp() override {
+    m_view = std::make_unique<NiceMock<MockConvFitDataView>>();
+    m_model = std::make_unique<NiceMock<MockConvFitModel>>();
+
+    m_dataTable = createEmptyTableWidget(6, 5);
+    ON_CALL(*m_view, getDataTable()).WillByDefault(Return(m_dataTable.get()));
+
+    m_presenter = std::make_unique<ConvFitDataPresenter>(
+        std::move(m_model.get()), std::move(m_view.get()));
+
+    SetUpADSWithWorkspace m_ads("WorkspaceName", createWorkspace(6));
+    m_model->addWorkspace("WorkspaceName");
+  }
+
+  void tearDown() override {
+    AnalysisDataService::Instance().clear();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_view.get()));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_model.get()));
+
+    m_presenter.reset();
+    m_model.reset();
+    m_view.reset();
+    m_dataTable.reset();
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit tests to check for successful mock object instantiation
+  ///----------------------------------------------------------------------
+
+  void test_that_the_presenter_and_mock_objects_have_been_created() {
+    TS_ASSERT(m_presenter);
+    TS_ASSERT(m_model);
+    TS_ASSERT(m_view);
+  }
+
+  void test_that_the_data_table_is_the_size_specified() {
+    TS_ASSERT_EQUALS(m_dataTable->rowCount(), 6);
+    TS_ASSERT_EQUALS(m_dataTable->columnCount(), 6);
+  }
+
+  void
+  test_that_the_model_contains_the_correct_number_of_workspace_after_instantiation() {
+    TS_ASSERT_EQUALS(m_model->numberOfWorkspaces(), 1);
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit Tests that test the signals, methods and slots of the presenter
+  ///----------------------------------------------------------------------
+
+private:
+  std::unique_ptr<QTableWidget> m_dataTable;
+
+  std::unique_ptr<MockConvFitDataView> m_view;
+  std::unique_ptr<MockConvFitModel> m_model;
+  std::unique_ptr<ConvFitDataPresenter> m_presenter;
+};
+#endif
diff --git a/qt/scientific_interfaces/Indirect/test/IndirectDataTablePresenterTest.h b/qt/scientific_interfaces/Indirect/test/IndirectDataTablePresenterTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..680b7249804417545ddefd1910012424a82bb43f
--- /dev/null
+++ b/qt/scientific_interfaces/Indirect/test/IndirectDataTablePresenterTest.h
@@ -0,0 +1,374 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTIDQT_INDIRECTDATATABLEPRESENTERTEST_H_
+#define MANTIDQT_INDIRECTDATATABLEPRESENTERTEST_H_
+
+#include <cxxtest/TestSuite.h>
+#include <gmock/gmock.h>
+
+#include "IndirectDataTablePresenter.h"
+#include "IndirectFittingModel.h"
+
+#include "MantidAPI/FrameworkManager.h"
+#include "MantidKernel/WarningSuppressions.h"
+#include "MantidTestHelpers/IndirectFitDataCreationHelper.h"
+
+using namespace Mantid::API;
+using namespace Mantid::IndirectFitDataCreationHelper;
+using namespace MantidQt::CustomInterfaces::IDA;
+using namespace testing;
+
+namespace {
+
+std::unique_ptr<QTableWidget> createEmptyTableWidget(int columns, int rows) {
+  auto table = std::make_unique<QTableWidget>(columns, rows);
+  for (auto column = 0; column < columns; ++column)
+    for (auto row = 0; row < rows; ++row)
+      table->setItem(row, column, new QTableWidgetItem("item"));
+  return table;
+}
+
+struct TableItem {
+  TableItem(std::string const &value) : m_str(value), m_dbl(0.0) {}
+  TableItem(double const &value)
+      : m_str(QString::number(value, 'g', 16).toStdString()), m_dbl(value) {}
+
+  std::string const &asString() const { return m_str; }
+  QString asQString() const { return QString::fromStdString(m_str); }
+  double const &asDouble() const { return m_dbl; }
+
+  bool operator==(std::string const &value) const {
+    return this->asString() == value;
+  }
+
+private:
+  std::string m_str;
+  double m_dbl;
+};
+
+} // namespace
+
+GNU_DIAG_OFF_SUGGEST_OVERRIDE
+
+/// Mock object to mock the model
+class MockIndirectDataTableModel : public IndirectFittingModel {
+public:
+  /// Public methods
+  MOCK_CONST_METHOD2(getFittingRange,
+                     std::pair<double, double>(std::size_t dataIndex,
+                                               std::size_t spectrum));
+  MOCK_CONST_METHOD2(getExcludeRegion,
+                     std::string(std::size_t dataIndex, std::size_t index));
+  MOCK_CONST_METHOD0(isMultiFit, bool());
+  MOCK_CONST_METHOD0(numberOfWorkspaces, std::size_t());
+
+  MOCK_METHOD3(setStartX, void(double startX, std::size_t dataIndex,
+                               std::size_t spectrum));
+  MOCK_METHOD3(setEndX,
+               void(double endX, std::size_t dataIndex, std::size_t spectrum));
+  MOCK_METHOD3(setExcludeRegion,
+               void(std::string const &exclude, std::size_t dataIndex,
+                    std::size_t spectrum));
+
+private:
+  std::string sequentialFitOutputName() const override { return ""; };
+  std::string simultaneousFitOutputName() const override { return ""; };
+  std::string singleFitOutputName(std::size_t index,
+                                  std::size_t spectrum) const override {
+    UNUSED_ARG(index);
+    UNUSED_ARG(spectrum);
+    return "";
+  };
+
+  std::vector<std::string> getSpectrumDependentAttributes() const override {
+    return {};
+  };
+};
+
+GNU_DIAG_ON_SUGGEST_OVERRIDE
+
+class IndirectDataTablePresenterTest : public CxxTest::TestSuite {
+public:
+  /// Needed to make sure everything is initialized
+  IndirectDataTablePresenterTest() { FrameworkManager::Instance(); }
+
+  static IndirectDataTablePresenterTest *createSuite() {
+    return new IndirectDataTablePresenterTest();
+  }
+
+  static void destroySuite(IndirectDataTablePresenterTest *suite) {
+    delete suite;
+  }
+
+  void setUp() override {
+    m_model = std::make_unique<NiceMock<MockIndirectDataTableModel>>();
+    m_table = createEmptyTableWidget(5, 5);
+    m_presenter = std::make_unique<IndirectDataTablePresenter>(
+        std::move(m_model.get()), std::move(m_table.get()));
+
+    SetUpADSWithWorkspace ads("WorkspaceName", createWorkspace(5));
+    m_model->addWorkspace("WorkspaceName");
+  }
+
+  void tearDown() override {
+    AnalysisDataService::Instance().clear();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_table.get()));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_model.get()));
+
+    m_presenter.reset();
+    m_model.reset();
+    m_table.reset();
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit tests to check for successful presenter instantiation
+  ///----------------------------------------------------------------------
+
+  void test_that_the_model_has_been_instantiated_correctly() {
+    ON_CALL(*m_model, isMultiFit()).WillByDefault(Return(false));
+
+    EXPECT_CALL(*m_model, isMultiFit()).Times(1).WillOnce(Return(false));
+
+    m_model->isMultiFit();
+  }
+
+  void
+  test_that_invoking_setStartX_will_alter_the_relevant_column_in_the_table() {
+    TableItem const startX(2.2);
+
+    m_presenter->setStartX(startX.asDouble(), 0, 0);
+
+    assertValueIsGlobal(START_X_COLUMN, startX);
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit Tests that test the signals call the correct methods
+  ///----------------------------------------------------------------------
+
+  void
+  test_that_the_cellChanged_signal_will_set_the_models_startX_when_the_relevant_column_is_changed() {
+    EXPECT_CALL(*m_model, setStartX(2.0, 0, 0)).Times(1);
+    m_table->item(0, START_X_COLUMN)->setText("2.0");
+  }
+
+  void
+  test_that_the_cellChanged_signal_will_set_the_models_endX_when_the_relevant_column_is_changed() {
+    EXPECT_CALL(*m_model, setEndX(2.0, 0, 0)).Times(1);
+    m_table->item(0, END_X_COLUMN)->setText("2.0");
+  }
+
+  void
+  test_that_the_cellChanged_signal_will_set_the_models_excludeRegion_when_the_relevant_column_is_changed() {
+    EXPECT_CALL(*m_model, setExcludeRegion("0-4", 0, 0)).Times(1);
+    m_table->item(0, EXCLUDE_REGION_COLUMN)->setText("0-4");
+  }
+
+  void
+  test_that_the_cellChanged_signal_will_set_the_models_startX_in_every_row_when_the_relevant_column_is_changed() {
+    TableItem const startX(1.5);
+
+    m_table->item(0, START_X_COLUMN)->setText(startX.asQString());
+
+    assertValueIsGlobal(START_X_COLUMN, startX);
+  }
+
+  void
+  test_that_the_cellChanged_signal_will_set_the_models_endX_in_every_row_when_the_relevant_column_is_changed() {
+    TableItem const endX(2.5);
+
+    m_table->item(0, END_X_COLUMN)->setText(endX.asQString());
+
+    assertValueIsGlobal(END_X_COLUMN, endX);
+  }
+
+  void
+  test_that_the_cellChanged_signal_will_set_the_models_excludeRegion_in_every_row_when_the_relevant_column_is_changed() {
+    TableItem const excludeRegion("2-4");
+
+    m_table->item(0, EXCLUDE_REGION_COLUMN)->setText(excludeRegion.asQString());
+
+    assertValueIsGlobal(EXCLUDE_REGION_COLUMN, excludeRegion);
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit Tests that test the methods and slots of the presenter
+  ///----------------------------------------------------------------------
+
+  void
+  test_that_tableDatasetsMatchModel_returns_false_if_the_number_of_data_positions_is_not_equal_to_the_numberOfWorkspaces() {
+    std::size_t const numberOfWorkspaces(2);
+    ON_CALL(*m_model, numberOfWorkspaces())
+        .WillByDefault(Return(numberOfWorkspaces));
+
+    EXPECT_CALL(*m_model, numberOfWorkspaces())
+        .Times(1)
+        .WillOnce(Return(numberOfWorkspaces));
+
+    TS_ASSERT(!m_presenter->tableDatasetsMatchModel());
+  }
+
+  void
+  test_that_tableDatasetsMatchModel_returns_true_if_the_table_datasets_match_the_model() {
+    EXPECT_CALL(*m_model, numberOfWorkspaces()).Times(1).WillOnce(Return(0));
+    TS_ASSERT(m_presenter->tableDatasetsMatchModel());
+  }
+
+  void
+  test_that_addData_will_add_new_data_if_the_index_is_smaller_than_the_number_of_data_positions() {
+    std::size_t const index(0);
+
+    ON_CALL(*m_model, numberOfWorkspaces()).WillByDefault(Return(2));
+
+    EXPECT_CALL(*m_model, numberOfWorkspaces()).Times(1);
+
+    ExpectationSet getRanges =
+        EXPECT_CALL(*m_model, getFittingRange(index, 0)).Times(1);
+    for (auto spectrum = 1; spectrum < m_table->rowCount(); ++spectrum)
+      getRanges += EXPECT_CALL(*m_model, getFittingRange(index, spectrum))
+                       .Times(1)
+                       .After(getRanges);
+
+    m_presenter->addData(index);
+  }
+
+  void
+  test_that_the_setStartX_slot_will_alter_the_relevant_startX_column_in_the_table() {
+    TableItem const startX(1.1);
+
+    m_presenter->setStartX(startX.asDouble(), 0);
+
+    assertValueIsGlobal(START_X_COLUMN, startX);
+  }
+
+  void
+  test_that_the_setEndX_slot_will_alter_the_relevant_endX_column_in_the_table() {
+    TableItem const endX(1.1);
+
+    m_presenter->setEndX(endX.asDouble(), 0);
+
+    assertValueIsGlobal(END_X_COLUMN, endX);
+  }
+
+  void
+  test_that_the_setExcludeRegion_slot_will_alter_the_relevant_excludeRegion_column_in_the_table() {
+    TableItem const excludeRegion("2-3");
+
+    m_presenter->setExcludeRegion(excludeRegion.asString(), 0);
+
+    assertValueIsGlobal(EXCLUDE_REGION_COLUMN, excludeRegion);
+  }
+
+  void
+  test_that_setGlobalFittingRange_will_set_the_startX_and_endX_taken_from_the_fitting_range() {
+    std::size_t const index(0);
+    TableItem const startX(1.0);
+    TableItem const endX(2.0);
+    auto const range = std::make_pair(startX.asDouble(), endX.asDouble());
+
+    ON_CALL(*m_model, getFittingRange(index, 0)).WillByDefault(Return(range));
+
+    EXPECT_CALL(*m_model, getFittingRange(index, 0)).Times(1);
+
+    m_presenter->setGlobalFittingRange(true);
+
+    assertValueIsGlobal(START_X_COLUMN, startX);
+    assertValueIsGlobal(END_X_COLUMN, endX);
+  }
+
+  void
+  test_that_setGlobalFittingRange_will_set_the_excludeRegion_when_passed_true() {
+    std::size_t const index(0);
+    TableItem const excludeRegion("1-2");
+
+    ON_CALL(*m_model, getExcludeRegion(index, 0)).WillByDefault(Return("1-2"));
+
+    EXPECT_CALL(*m_model, getExcludeRegion(index, 0)).Times(1);
+
+    m_presenter->setGlobalFittingRange(true);
+
+    assertValueIsGlobal(EXCLUDE_REGION_COLUMN, excludeRegion);
+  }
+
+  void
+  test_that_setGlobalFittingRange_will_connect_the_cellChanged_signal_to_updateAllFittingRangeFrom_when_passed_true() {
+    TableItem const startX(1.0);
+
+    m_presenter->setGlobalFittingRange(true);
+    m_table->item(0, START_X_COLUMN)->setText(startX.asQString());
+
+    assertValueIsGlobal(START_X_COLUMN, startX);
+  }
+
+  void
+  test_that_setGlobalFittingRange_will_disconnect_the_cellChanged_signal_when_passed_false_so_that_startX_is_not_global() {
+    int const row(1);
+    TableItem const startX(2.5);
+
+    m_presenter->setGlobalFittingRange(false);
+    m_table->item(row, START_X_COLUMN)->setText(startX.asQString());
+
+    assertValueIsNotGlobal(row, START_X_COLUMN, startX);
+  }
+
+  void
+  test_that_setGlobalFittingRange_will_disconnect_the_cellChanged_signal_when_passed_false_so_that_endX_is_not_global() {
+    int const row(0);
+    TableItem const endX(3.5);
+
+    m_presenter->setGlobalFittingRange(false);
+    m_table->item(row, END_X_COLUMN)->setText(endX.asQString());
+
+    assertValueIsNotGlobal(row, END_X_COLUMN, endX);
+  }
+
+  void test_the_enableTable_slot_will_enable_the_table() {
+    m_presenter->disableTable();
+    TS_ASSERT(!m_table->isEnabled());
+
+    m_presenter->enableTable();
+    TS_ASSERT(m_table->isEnabled());
+  }
+
+  void test_the_disableTable_slot_will_enable_the_table() {
+    m_presenter->enableTable();
+    TS_ASSERT(m_table->isEnabled());
+
+    m_presenter->disableTable();
+    TS_ASSERT(!m_table->isEnabled());
+  }
+
+  void test_that_clearTable_will_clear_the_data_table() {
+    m_presenter->clearTable();
+    TS_ASSERT_EQUALS(m_table->rowCount(), 0);
+  }
+
+private:
+  void assertValueIsGlobal(int column, TableItem const &value) const {
+    for (auto row = 0; row < m_table->rowCount(); ++row)
+      TS_ASSERT_EQUALS(value, getTableItem(row, column));
+  }
+
+  void assertValueIsNotGlobal(int valueRow, int column,
+                              TableItem const &value) const {
+    TS_ASSERT_EQUALS(value.asString(), getTableItem(valueRow, column));
+
+    for (auto row = 0; row < m_table->rowCount(); ++row)
+      if (row != valueRow)
+        TS_ASSERT_DIFFERS(value, getTableItem(row, column));
+  }
+
+  std::string getTableItem(int row, int column) const {
+    return m_table->item(row, column)->text().toStdString();
+  }
+
+  std::unique_ptr<QTableWidget> m_table;
+  std::unique_ptr<MockIndirectDataTableModel> m_model;
+  std::unique_ptr<IndirectDataTablePresenter> m_presenter;
+};
+
+#endif
diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFitDataPresenterTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFitDataPresenterTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..8998c4adbb67562f17cda9be0e81ff8f326e4f5a
--- /dev/null
+++ b/qt/scientific_interfaces/Indirect/test/IndirectFitDataPresenterTest.h
@@ -0,0 +1,309 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTIDQT_INDIRECTFITDATAPRESENTERTEST_H_
+#define MANTIDQT_INDIRECTFITDATAPRESENTERTEST_H_
+
+#include <cxxtest/TestSuite.h>
+#include <gmock/gmock.h>
+
+#include "IIndirectFitDataView.h"
+#include "IndirectDataTablePresenter.h"
+#include "IndirectFitDataPresenter.h"
+#include "IndirectFittingModel.h"
+
+#include "MantidAPI/FrameworkManager.h"
+#include "MantidKernel/WarningSuppressions.h"
+#include "MantidTestHelpers/IndirectFitDataCreationHelper.h"
+
+using namespace Mantid::API;
+using namespace Mantid::IndirectFitDataCreationHelper;
+using namespace MantidQt::CustomInterfaces;
+using namespace MantidQt::CustomInterfaces::IDA;
+using namespace testing;
+
+namespace {
+
+std::unique_ptr<QTableWidget> createEmptyTableWidget(int columns, int rows) {
+  auto table = std::make_unique<QTableWidget>(columns, rows);
+  for (auto column = 0; column < columns; ++column)
+    for (auto row = 0; row < rows; ++row)
+      table->setItem(row, column, new QTableWidgetItem("item"));
+  return table;
+}
+
+struct TableItem {
+  TableItem(std::string const &value) : m_str(value), m_dbl(0.0) {}
+  TableItem(double const &value)
+      : m_str(QString::number(value, 'g', 16).toStdString()), m_dbl(value) {}
+
+  std::string const &asString() const { return m_str; }
+  double const &asDouble() const { return m_dbl; }
+
+  bool operator==(std::string const &value) const {
+    return this->asString() == value;
+  }
+
+private:
+  std::string m_str;
+  double m_dbl;
+};
+
+} // namespace
+
+GNU_DIAG_OFF_SUGGEST_OVERRIDE
+
+/// Mock object to mock the view
+class MockIIndirectFitDataView : public IIndirectFitDataView {
+public:
+  /// Signals
+  void emitSampleLoaded(QString const &name) { emit sampleLoaded(name); }
+
+  /// Public Methods
+  MOCK_CONST_METHOD0(getDataTable, QTableWidget *());
+  MOCK_CONST_METHOD0(isMultipleDataTabSelected, bool());
+  MOCK_CONST_METHOD0(isResolutionHidden, bool());
+  MOCK_METHOD1(setResolutionHidden, void(bool hide));
+  MOCK_METHOD0(disableMultipleDataTab, void());
+
+  MOCK_CONST_METHOD0(getSelectedSample, std::string());
+  MOCK_CONST_METHOD0(getSelectedResolution, std::string());
+
+  MOCK_CONST_METHOD0(getSampleWSSuffices, QStringList());
+  MOCK_CONST_METHOD0(getSampleFBSuffices, QStringList());
+  MOCK_CONST_METHOD0(getResolutionWSSuffices, QStringList());
+  MOCK_CONST_METHOD0(getResolutionFBSuffices, QStringList());
+
+  MOCK_METHOD1(setSampleWSSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setSampleFBSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setResolutionWSSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setResolutionFBSuffices, void(QStringList const &suffices));
+
+  MOCK_METHOD1(readSettings, void(QSettings const &settings));
+  MOCK_METHOD1(validate, UserInputValidator &(UserInputValidator &validator));
+
+  /// Public slots
+  MOCK_METHOD1(displayWarning, void(std::string const &warning));
+};
+
+/// Mock object to mock the model
+class MockIndirectFitDataModel : public IndirectFittingModel {
+public:
+  using IndirectFittingModel::addWorkspace;
+
+  /// Public Methods
+  MOCK_CONST_METHOD0(isMultiFit, bool());
+  MOCK_CONST_METHOD0(numberOfWorkspaces, std::size_t());
+
+  MOCK_METHOD1(addWorkspace, void(std::string const &workspaceName));
+
+private:
+  std::string sequentialFitOutputName() const override { return ""; };
+  std::string simultaneousFitOutputName() const override { return ""; };
+  std::string singleFitOutputName(std::size_t index,
+                                  std::size_t spectrum) const override {
+    UNUSED_ARG(index);
+    UNUSED_ARG(spectrum);
+    return "";
+  };
+
+  std::vector<std::string> getSpectrumDependentAttributes() const override {
+    return {};
+  };
+};
+
+GNU_DIAG_ON_SUGGEST_OVERRIDE
+
+class IndirectFitDataPresenterTest : public CxxTest::TestSuite {
+public:
+  /// Needed to make sure everything is initialized
+  IndirectFitDataPresenterTest() { FrameworkManager::Instance(); }
+
+  static IndirectFitDataPresenterTest *createSuite() {
+    return new IndirectFitDataPresenterTest();
+  }
+
+  static void destroySuite(IndirectFitDataPresenterTest *suite) {
+    delete suite;
+  }
+
+  void setUp() override {
+    m_view = std::make_unique<NiceMock<MockIIndirectFitDataView>>();
+    m_model = std::make_unique<NiceMock<MockIndirectFitDataModel>>();
+    m_table = createEmptyTableWidget(5, 5);
+
+    m_dataTablePresenter = std::make_unique<IndirectDataTablePresenter>(
+        std::move(m_model.get()), std::move(m_table.get()));
+    m_presenter = std::make_unique<IndirectFitDataPresenter>(
+        std::move(m_model.get()), std::move(m_view.get()),
+        std::move(m_dataTablePresenter));
+
+    SetUpADSWithWorkspace m_ads("WorkspaceName", createWorkspace(5));
+    m_model->addWorkspace("WorkspaceName");
+  }
+
+  void tearDown() override {
+    AnalysisDataService::Instance().clear();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_view.get()));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_model.get()));
+
+    deleteSetup();
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit tests to check for successful mock object instantiation
+  ///----------------------------------------------------------------------
+
+  void test_that_the_model_has_been_instantiated_correctly() {
+    ON_CALL(*m_model, isMultiFit()).WillByDefault(Return(false));
+
+    EXPECT_CALL(*m_model, isMultiFit()).Times(1).WillOnce(Return(false));
+
+    m_model->isMultiFit();
+  }
+
+  void test_that_the_view_has_been_instantiated_correctly() {
+    std::string const sampleName("SampleName_red");
+    ON_CALL(*m_view, getSelectedSample()).WillByDefault(Return(sampleName));
+
+    EXPECT_CALL(*m_view, getSelectedSample())
+        .Times(1)
+        .WillOnce(Return(sampleName));
+
+    m_view->getSelectedSample();
+  }
+
+  void
+  test_that_invoking_a_presenter_method_will_call_the_relevant_methods_in_the_view_and_model() {
+    ON_CALL(*m_view, isMultipleDataTabSelected()).WillByDefault(Return(true));
+    ON_CALL(*m_model, numberOfWorkspaces()).WillByDefault(Return(2));
+
+    Expectation isMultipleData =
+        EXPECT_CALL(*m_view, isMultipleDataTabSelected())
+            .Times(1)
+            .WillOnce(Return(true));
+    EXPECT_CALL(*m_model, numberOfWorkspaces()).Times(1).After(isMultipleData);
+
+    m_presenter->updateSpectraInTable(0);
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit Tests that test the signals, methods and slots of the presenter
+  ///----------------------------------------------------------------------
+
+  void
+  test_that_the_sampleLoaded_signal_will_add_the_loaded_workspace_to_the_model() {
+    std::string const workspaceName("WorkspaceName2");
+    m_ads->addOrReplace(workspaceName, createWorkspace(5));
+
+    EXPECT_CALL(*m_model, addWorkspace(workspaceName)).Times(Exactly(1));
+
+    m_view->emitSampleLoaded(QString::fromStdString(workspaceName));
+  }
+
+  void
+  test_that_setSampleWSSuffices_will_set_the_sample_workspace_suffices_in_the_view() {
+    QStringList const suffices{"suffix1", "suffix2"};
+
+    EXPECT_CALL(*m_view, setSampleWSSuffices(suffices)).Times(Exactly(1));
+
+    m_presenter->setSampleWSSuffices(suffices);
+  }
+
+  void
+  test_that_setSampleFBSuffices_will_set_the_sample_file_browser_suffices_in_the_view() {
+    QStringList const suffices{"suffix1", "suffix2"};
+
+    EXPECT_CALL(*m_view, setSampleFBSuffices(suffices)).Times(Exactly(1));
+
+    m_presenter->setSampleFBSuffices(suffices);
+  }
+
+  void
+  test_that_setResolutionWSSuffices_will_set_the_resolution_workspace_suffices_in_the_view() {
+    QStringList const suffices{"suffix1", "suffix2"};
+
+    EXPECT_CALL(*m_view, setResolutionWSSuffices(suffices)).Times(Exactly(1));
+
+    m_presenter->setResolutionWSSuffices(suffices);
+  }
+
+  void
+  test_that_setResolutionFBSuffices_will_set_the_resolution_file_browser_suffices_in_the_view() {
+    QStringList const suffices{"suffix1", "suffix2"};
+
+    EXPECT_CALL(*m_view, setResolutionFBSuffices(suffices)).Times(Exactly(1));
+
+    m_presenter->setResolutionFBSuffices(suffices);
+  }
+
+  void
+  test_that_setStartX_will_alter_the_relevant_startX_column_in_the_data_table() {
+    TableItem const startX(2.3);
+
+    m_presenter->setStartX(startX.asDouble(), 0, 0);
+
+    assertValueIsGlobal(START_X_COLUMN, startX);
+  }
+
+  void
+  test_that_setEndX_will_alter_the_relevant_endX_column_in_the_data_table() {
+    TableItem const endX(5.5);
+
+    m_presenter->setEndX(endX.asDouble(), 0, 0);
+
+    assertValueIsGlobal(END_X_COLUMN, endX);
+  }
+
+  void
+  test_that_the_setExcludeRegion_slot_will_alter_the_relevant_excludeRegion_column_in_the_table() {
+    TableItem const excludeRegion("2-3");
+
+    m_presenter->setExclude(excludeRegion.asString(), 0, 0);
+
+    assertValueIsGlobal(EXCLUDE_REGION_COLUMN, excludeRegion);
+  }
+
+  void test_that_loadSettings_will_read_the_settings_from_the_view() {
+    QSettings settings;
+    settings.beginGroup("/ISettings");
+
+    EXPECT_CALL(*m_view, readSettings(_)).Times(Exactly(1));
+
+    m_presenter->loadSettings(settings);
+  }
+
+private:
+  void deleteSetup() {
+    m_presenter.reset();
+    m_model.reset();
+    m_view.reset();
+
+    m_dataTablePresenter.reset();
+    m_table.reset();
+  }
+
+  void assertValueIsGlobal(int column, TableItem const &value) const {
+    for (auto row = 0; row < m_table->rowCount(); ++row)
+      TS_ASSERT_EQUALS(value, getTableItem(row, column));
+  }
+
+  std::string getTableItem(int row, int column) const {
+    return m_table->item(row, column)->text().toStdString();
+  }
+
+  std::unique_ptr<QTableWidget> m_table;
+  std::unique_ptr<IndirectDataTablePresenter> m_dataTablePresenter;
+
+  std::unique_ptr<MockIIndirectFitDataView> m_view;
+  std::unique_ptr<MockIndirectFitDataModel> m_model;
+  std::unique_ptr<IndirectFitDataPresenter> m_presenter;
+
+  SetUpADSWithWorkspace *m_ads;
+};
+
+#endif
diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFitOutputTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFitOutputTest.h
index 6cb93ee3c83e5d07119bf73ba41fc57e1a50ebd1..8f610241d1ad54e153f7919a0d3079706435299c 100644
--- a/qt/scientific_interfaces/Indirect/test/IndirectFitOutputTest.h
+++ b/qt/scientific_interfaces/Indirect/test/IndirectFitOutputTest.h
@@ -35,7 +35,7 @@ createPopulatedworkspace(std::vector<double> const &xValues,
   createWorkspaceAlgorithm->setProperty("VerticalAxisUnit", "Text");
   createWorkspaceAlgorithm->setProperty("VerticalAxisValues",
                                         verticalAxisNames);
-  createWorkspaceAlgorithm->setProperty("OutputWorkspace", "workspace");
+  createWorkspaceAlgorithm->setProperty("OutputWorkspace", "OutputResults");
   createWorkspaceAlgorithm->execute();
   return createWorkspaceAlgorithm->getProperty("OutputWorkspace");
 }
@@ -81,14 +81,6 @@ WorkspaceGroup_sptr getPopulatedGroup(std::size_t const &size) {
   return group;
 }
 
-/// Store workspaces in ADS and won't destruct the ADS when leaving scope
-void storeWorkspacesInADS(WorkspaceGroup_sptr group,
-                          ITableWorkspace_sptr table) {
-  SetUpADSWithWorkspace ads("ResultGroup", group);
-  ads.addOrReplace("ResultWorkspaces", group);
-  ads.addOrReplace("ParameterTable", table);
-}
-
 std::unique_ptr<IndirectFitOutput>
 createFitOutput(WorkspaceGroup_sptr resultGroup,
                 ITableWorkspace_sptr parameterTable,
@@ -98,17 +90,6 @@ createFitOutput(WorkspaceGroup_sptr resultGroup,
       resultGroup, parameterTable, resultWorkspace, fitData, spectrum);
 }
 
-/// This will return fit output with workspaces still stored in the ADS
-std::unique_ptr<IndirectFitOutput> getFitOutputData() {
-  auto const group = getPopulatedGroup(2);
-  auto const table = getPopulatedTable(2);
-  IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
-
-  storeWorkspacesInADS(group, table);
-
-  return createFitOutput(group, table, group, data, 0);
-}
-
 std::unordered_map<std::string, std::string>
 getNewParameterNames(std::vector<std::string> const &currentNames) {
   std::unordered_map<std::string, std::string> newParameterNames;
@@ -147,10 +128,10 @@ public:
   test_that_the_group_workspaces_stored_are_equal_to_the_workspaces_inputed() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
 
     TS_ASSERT_EQUALS(output->getLastResultGroup(), group);
     TS_ASSERT_EQUALS(output->getLastResultWorkspace(), group);
@@ -160,48 +141,48 @@ public:
   test_that_isSpectrumFit_returns_false_if_the_spectrum_has_not_been_previously_fit() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
 
-    TS_ASSERT(!output->isSpectrumFit(data, 7));
+    TS_ASSERT(!output->isSpectrumFit(data.get(), 7));
   }
 
   void
   test_that_isSpectrumFit_returns_true_if_the_spectrum_has_been_previously_fit() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
 
-    TS_ASSERT(output->isSpectrumFit(data, 0));
+    TS_ASSERT(output->isSpectrumFit(data.get(), 0));
   }
 
   void
   test_that_getParameters_returns_an_empty_map_when_the_spectrum_number_provided_is_out_of_range() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
 
-    TS_ASSERT(output->getParameters(data, 7).empty());
+    TS_ASSERT(output->getParameters(data.get(), 7).empty());
   }
 
   void
   test_that_getParameters_returns_the_correct_parameter_values_when_the_spectrum_number_and_IndirectFitData_provided_is_valid() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
 
-    auto const parameters = output->getParameters(data, 0);
+    auto const parameters = output->getParameters(data.get(), 0);
     TS_ASSERT_EQUALS(parameters.size(), 2);
     TS_ASSERT_EQUALS(parameters.at("Height_Err").value, 0.047);
     TS_ASSERT_EQUALS(parameters.at("Msd_Err").value, 0.514);
@@ -211,24 +192,24 @@ public:
   test_that_getResultLocation_returns_none_when_the_spectrum_number_provided_is_out_of_range() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
 
-    TS_ASSERT(!output->getResultLocation(data, 7));
+    TS_ASSERT(!output->getResultLocation(data.get(), 7));
   }
 
   void
   test_that_getResultLocation_returns_the_ResultLocation_when_the_spectrum_number_and_IndirectFitData_provided_is_valid() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
 
-    auto const resultLocation = output->getResultLocation(data, 0);
+    auto const resultLocation = output->getResultLocation(data.get(), 0);
     TS_ASSERT(resultLocation);
     TS_ASSERT_EQUALS(resultLocation->result.lock(), group);
   }
@@ -237,10 +218,10 @@ public:
   test_that_getResultParameterNames_gets_the_parameter_names_which_were_provided_as_input_data() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
     std::vector<std::string> const expectedParameters{
         "Height", "Height_Err", "Msd", "Msd_Err", "Chi_squared"};
     auto const parameters = output->getResultParameterNames();
@@ -265,15 +246,15 @@ public:
   test_that_mapParameterNames_will_remap_the_parameters_to_correspond_to_the_provided_parameter_names() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
     auto const newParameterNames =
         getNewParameterNames({"Height_Err", "Msd_Err"});
-    output->mapParameterNames(newParameterNames, data);
+    output->mapParameterNames(newParameterNames, data.get());
 
-    auto const parameters = output->getParameters(data, 0);
+    auto const parameters = output->getParameters(data.get(), 0);
     TS_ASSERT_EQUALS(parameters.size(), 2);
     TS_ASSERT_EQUALS(parameters.at("Width_Err").value, 0.047);
     TS_ASSERT_EQUALS(parameters.at("MSD_Err").value, 0.514);
@@ -283,14 +264,14 @@ public:
   test_that_mapParameterNames_will_not_remap_the_parameters_when_the_provided_old_parameter_names_do_not_exist() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
     auto const newParameterNames = getNewParameterNames({"None1", "None2"});
-    output->mapParameterNames(newParameterNames, data);
+    output->mapParameterNames(newParameterNames, data.get());
 
-    auto const parameters = output->getParameters(data, 0);
+    auto const parameters = output->getParameters(data.get(), 0);
     TS_ASSERT(parameters.at("Height_Err").value);
     TS_ASSERT(parameters.at("Msd_Err").value);
   }
@@ -299,57 +280,99 @@ public:
   test_that_addOutput_will_add_new_fitData_without_overwriting_existing_data() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data1 = new IndirectFitData(getIndirectFitData(5));
+    auto const data1 = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data1, 0);
-    IndirectFitData const *data2 = new IndirectFitData(getIndirectFitData(2));
-    output->addOutput(group, table, group, data2, 0);
+    auto const output = createFitOutput(group, table, group, data1.get(), 0);
+    auto const data2 = std::make_unique<IndirectFitData>(getIndirectFitData(2));
+    output->addOutput(group, table, group, data2.get(), 0);
 
-    TS_ASSERT(!output->getParameters(data1, 0).empty());
-    TS_ASSERT(!output->getParameters(data2, 0).empty());
+    TS_ASSERT(!output->getParameters(data1.get(), 0).empty());
+    TS_ASSERT(!output->getParameters(data2.get(), 0).empty());
   }
 
   void test_that_removeOutput_will_erase_the_provided_fitData() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data = new IndirectFitData(getIndirectFitData(5));
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data, 0);
-    output->removeOutput(data);
+    auto const output = createFitOutput(group, table, group, data.get(), 0);
+    output->removeOutput(data.get());
 
-    TS_ASSERT(output->getParameters(data, 0).empty());
-    TS_ASSERT(!output->getResultLocation(data, 0));
+    TS_ASSERT(output->getParameters(data.get(), 0).empty());
+    TS_ASSERT(!output->getResultLocation(data.get(), 0));
   }
 
   void test_that_removeOutput_will_not_delete_fitData_which_is_not_specified() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data1 = new IndirectFitData(getIndirectFitData(5));
+    auto const data1 = std::make_unique<IndirectFitData>(getIndirectFitData(5));
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data1, 0);
-    IndirectFitData const *data2 = new IndirectFitData(getIndirectFitData(2));
-    output->addOutput(group, table, group, data2, 0);
-    output->removeOutput(data2);
+    auto const output = createFitOutput(group, table, group, data1.get(), 0);
+    auto const data2 = std::make_unique<IndirectFitData>(getIndirectFitData(2));
+    output->addOutput(group, table, group, data2.get(), 0);
+    output->removeOutput(data2.get());
 
-    TS_ASSERT(!output->getParameters(data1, 0).empty());
-    TS_ASSERT(output->getParameters(data2, 0).empty());
+    TS_ASSERT(!output->getParameters(data1.get(), 0).empty());
+    TS_ASSERT(output->getParameters(data2.get(), 0).empty());
   }
 
   void
   test_that_removeOutput_does_not_throw_when_provided_fitData_which_does_not_exist() {
     auto const group = getPopulatedGroup(2);
     auto const table = getPopulatedTable(2);
-    IndirectFitData *data1 = new IndirectFitData(getIndirectFitData(5));
+    auto const data1 = std::make_unique<IndirectFitData>(getIndirectFitData(5));
+    storeWorkspacesInADS(group, table);
+
+    auto const output = createFitOutput(group, table, group, data1.get(), 0);
+    auto const data2 = std::make_unique<IndirectFitData>(getIndirectFitData(2));
+
+    TS_ASSERT_THROWS_NOTHING(output->removeOutput(data2.get()));
+  }
+
+  void
+  test_that_the_resultworkspace_is_renamed_to_have_the_correct_name_after_a_fit_is_executed() {
+    auto const group = getPopulatedGroup(1);
+    auto const table = getPopulatedTable(2);
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
+    storeWorkspacesInADS(group, table);
+
+    (void)createFitOutput(group, table, group, data.get(), 0);
+
+    TS_ASSERT(m_ads->doesExist("ConvFit_1L_Result"));
+  }
+
+  void
+  test_that_the_resultworkspace_is_renamed_to_have_the_correct_name_after_a_fit_is_executed_with_multiple_data() {
+    (void)getFitOutputData();
+    TS_ASSERT(m_ads->doesExist("MultiConvFit_1L__s0_to_4_Result"));
+  }
+
+private:
+  /// This will return fit output with workspaces still stored in the ADS
+  std::unique_ptr<IndirectFitOutput> getFitOutputData() {
+    auto const group = getPopulatedGroup(2);
+    auto const table = getPopulatedTable(2);
+    auto const data = std::make_unique<IndirectFitData>(getIndirectFitData(5));
+
     storeWorkspacesInADS(group, table);
 
-    auto const output = createFitOutput(group, table, group, data1, 0);
-    IndirectFitData const *data2 = new IndirectFitData(getIndirectFitData(2));
+    return createFitOutput(group, table, group, data.get(), 0);
+  }
 
-    TS_ASSERT_THROWS_NOTHING(output->removeOutput(data2));
+  /// Store workspaces in ADS and won't destruct the ADS when leaving scope
+  void storeWorkspacesInADS(WorkspaceGroup_sptr group,
+                            ITableWorkspace_sptr table) {
+    std::string const nameStart = group->size() > 1 ? "Multi" : "";
+    m_ads = std::make_unique<SetUpADSWithWorkspace>(
+        nameStart + "ConvFit_1L_Workspaces", group);
+    m_ads->addOrReplace(nameStart + "ConvFit_1L_Results_1", group);
+    m_ads->addOrReplace(nameStart + "ConvFit_1L_Parameters", table);
   }
+
+  std::unique_ptr<SetUpADSWithWorkspace> m_ads;
 };
 
 #endif // MANTID_INDIRECTFITOUTPUTTEST_H
diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFitPlotModelTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFitPlotModelTest.h
index 886cc92c512b948d82994148728620b7e3df431a..d9471b0fc71d3edfc853f4272d3551e14d89741d 100644
--- a/qt/scientific_interfaces/Indirect/test/IndirectFitPlotModelTest.h
+++ b/qt/scientific_interfaces/Indirect/test/IndirectFitPlotModelTest.h
@@ -129,7 +129,7 @@ IAlgorithm_sptr setupFitAlgorithm(MatrixWorkspace_sptr workspace,
   alg->setProperty("ConvolveMembers", true);
   alg->setProperty("Minimizer", "Levenberg-Marquardt");
   alg->setProperty("MaxIterations", 500);
-  alg->setProperty("OutputWorkspace", "output");
+  alg->setProperty("OutputWorkspace", "OutputResults");
   alg->setLogging(false);
   return alg;
 }
diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFittingModelTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFittingModelTest.h
index 2d9cb283002548950ae439e5a4687b232f17c614..810e972216af872bd9a15ca0dfaa204cc846ba1d 100644
--- a/qt/scientific_interfaces/Indirect/test/IndirectFittingModelTest.h
+++ b/qt/scientific_interfaces/Indirect/test/IndirectFittingModelTest.h
@@ -335,7 +335,7 @@ public:
     std::string const rangeDelimiter = "_to_";
 
     TS_ASSERT_EQUALS(model->createOutputName(formatString, rangeDelimiter, 0),
-                     "WorkspaceName_s0_Gaussian_Result");
+                     "WorkspaceName_s0_Gaussian_Results");
   }
 
   void
@@ -346,7 +346,7 @@ public:
     std::string const rangeDelimiter = "_to_";
 
     TS_ASSERT_EQUALS(model->createOutputName(formatString, rangeDelimiter, 0),
-                     "Workspace_3456_s0_Gaussian_Result");
+                     "Workspace_3456_s0_Gaussian_Results");
   }
 
   void
@@ -359,13 +359,13 @@ public:
 
     TS_ASSERT_EQUALS(
         model->createOutputName(formatStrings[0], rangeDelimiter, 0),
-        "Workspace_3456_s0_Gaussian_Result");
+        "Workspace_3456_s0_Gaussian_Results");
     TS_ASSERT_EQUALS(
         model->createOutputName(formatStrings[1], rangeDelimiter, 0),
-        "Workspace_3456_f0+s0_MSD_Result");
+        "Workspace_3456_f0+s0_MSD_Results");
     TS_ASSERT_EQUALS(
         model->createOutputName(formatStrings[2], rangeDelimiter, 0),
-        "Workspace_3456_s0_TeixeiraWater_Result");
+        "Workspace_3456_s0_TeixeiraWater_Results");
   }
 
   void
diff --git a/qt/scientific_interfaces/Indirect/test/JumpFitDataPresenterTest.h b/qt/scientific_interfaces/Indirect/test/JumpFitDataPresenterTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..f7fba2207c25ad665c6bbaaa40ec500b189213da
--- /dev/null
+++ b/qt/scientific_interfaces/Indirect/test/JumpFitDataPresenterTest.h
@@ -0,0 +1,204 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTIDQT_JUMPFITDATAPRESENTERTEST_H_
+#define MANTIDQT_JUMPFITDATAPRESENTERTEST_H_
+
+#include <cxxtest/TestSuite.h>
+#include <gmock/gmock.h>
+
+#include "IIndirectFitDataView.h"
+#include "JumpFitDataPresenter.h"
+#include "JumpFitModel.h"
+
+#include "MantidAPI/FrameworkManager.h"
+#include "MantidKernel/WarningSuppressions.h"
+#include "MantidTestHelpers/IndirectFitDataCreationHelper.h"
+
+using namespace Mantid::API;
+using namespace Mantid::IndirectFitDataCreationHelper;
+using namespace MantidQt::CustomInterfaces;
+using namespace MantidQt::CustomInterfaces::IDA;
+using namespace testing;
+
+namespace {
+
+QString const PARAMETER_TYPE_LABEL("Fit Parameter:");
+QString const PARAMETER_LABEL("Width:");
+
+QStringList getJumpParameters() {
+  QStringList parameters;
+  parameters << "f1.f1.FWHM"
+             << "f2.f1.FWHM";
+  return parameters;
+}
+
+QStringList getJumpParameterTypes() {
+  QStringList parameterTypes;
+  parameterTypes << "Width"
+                 << "EISF";
+  return parameterTypes;
+}
+
+std::vector<std::string> getTextAxisLabels() {
+  return {"f0.Width", "f1.Width", "f2.Width", "f0.EISF", "f1.EISF", "f2.EISF"};
+}
+
+std::unique_ptr<QLabel> createLabel(QString const &text) {
+  return std::make_unique<QLabel>(text);
+}
+
+std::unique_ptr<QComboBox> createComboBox(QStringList const &items) {
+  auto combBox = std::make_unique<QComboBox>();
+  combBox->addItems(items);
+  return combBox;
+}
+
+std::unique_ptr<QTableWidget> createEmptyTableWidget(int columns, int rows) {
+  auto table = std::make_unique<QTableWidget>(columns, rows);
+  for (auto column = 0; column < columns; ++column)
+    for (auto row = 0; row < rows; ++row)
+      table->setItem(row, column, new QTableWidgetItem("item"));
+  return table;
+}
+
+} // namespace
+
+GNU_DIAG_OFF_SUGGEST_OVERRIDE
+
+/// Mock object to mock the view
+class MockJumpFitDataView : public IIndirectFitDataView {
+public:
+  /// Public Methods
+  MOCK_CONST_METHOD0(getDataTable, QTableWidget *());
+  MOCK_CONST_METHOD0(isMultipleDataTabSelected, bool());
+  MOCK_CONST_METHOD0(isResolutionHidden, bool());
+  MOCK_METHOD1(setResolutionHidden, void(bool hide));
+  MOCK_METHOD0(disableMultipleDataTab, void());
+
+  MOCK_CONST_METHOD0(getSelectedSample, std::string());
+  MOCK_CONST_METHOD0(getSelectedResolution, std::string());
+
+  MOCK_CONST_METHOD0(getSampleWSSuffices, QStringList());
+  MOCK_CONST_METHOD0(getSampleFBSuffices, QStringList());
+  MOCK_CONST_METHOD0(getResolutionWSSuffices, QStringList());
+  MOCK_CONST_METHOD0(getResolutionFBSuffices, QStringList());
+
+  MOCK_METHOD1(setSampleWSSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setSampleFBSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setResolutionWSSuffices, void(QStringList const &suffices));
+  MOCK_METHOD1(setResolutionFBSuffices, void(QStringList const &suffices));
+
+  MOCK_METHOD1(readSettings, void(QSettings const &settings));
+  MOCK_METHOD1(validate, UserInputValidator &(UserInputValidator &validator));
+
+  /// Public slots
+  MOCK_METHOD1(displayWarning, void(std::string const &warning));
+};
+
+/// Mock object to mock the model
+class MockJumpFitModel : public JumpFitModel {};
+
+GNU_DIAG_ON_SUGGEST_OVERRIDE
+
+class JumpFitDataPresenterTest : public CxxTest::TestSuite {
+public:
+  /// Needed to make sure everything is initialized
+  JumpFitDataPresenterTest() { FrameworkManager::Instance(); }
+
+  static JumpFitDataPresenterTest *createSuite() {
+    return new JumpFitDataPresenterTest();
+  }
+
+  static void destroySuite(JumpFitDataPresenterTest *suite) { delete suite; }
+
+  void setUp() override {
+    m_view = std::make_unique<NiceMock<MockJumpFitDataView>>();
+    m_model = std::make_unique<NiceMock<MockJumpFitModel>>();
+
+    m_dataTable = createEmptyTableWidget(6, 5);
+    m_ParameterTypeCombo = createComboBox(getJumpParameterTypes());
+    m_ParameterCombo = createComboBox(getJumpParameters());
+    m_ParameterTypeLabel = createLabel(PARAMETER_TYPE_LABEL);
+    m_ParameterLabel = createLabel(PARAMETER_LABEL);
+
+    ON_CALL(*m_view, getDataTable()).WillByDefault(Return(m_dataTable.get()));
+
+    m_presenter = std::make_unique<JumpFitDataPresenter>(
+        std::move(m_model.get()), std::move(m_view.get()),
+        std::move(m_ParameterTypeCombo.get()),
+        std::move(m_ParameterCombo.get()),
+        std::move(m_ParameterTypeLabel.get()),
+        std::move(m_ParameterLabel.get()));
+
+    SetUpADSWithWorkspace m_ads(
+        "WorkspaceName", createWorkspaceWithTextAxis(6, getTextAxisLabels()));
+    m_model->addWorkspace("WorkspaceName");
+  }
+
+  void tearDown() override {
+    AnalysisDataService::Instance().clear();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_view.get()));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(m_model.get()));
+
+    m_presenter.reset();
+    m_model.reset();
+    m_view.reset();
+
+    m_dataTable.reset();
+    m_ParameterTypeCombo.reset();
+    m_ParameterCombo.reset();
+    m_ParameterTypeLabel.reset();
+    m_ParameterLabel.reset();
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit tests to check for successful mock object instantiation
+  ///----------------------------------------------------------------------
+
+  void test_that_the_presenter_and_mock_objects_have_been_created() {
+    TS_ASSERT(m_presenter);
+    TS_ASSERT(m_model);
+    TS_ASSERT(m_view);
+  }
+
+  void test_that_the_comboboxes_contain_the_items_specified_during_the_setup() {
+    auto const parameterTypes = getJumpParameterTypes();
+    auto const parameters = getJumpParameters();
+
+    TS_ASSERT_EQUALS(m_ParameterTypeCombo->itemText(0), parameterTypes[0]);
+    TS_ASSERT_EQUALS(m_ParameterTypeCombo->itemText(1), parameterTypes[1]);
+    TS_ASSERT_EQUALS(m_ParameterCombo->itemText(0), parameters[0]);
+    TS_ASSERT_EQUALS(m_ParameterCombo->itemText(1), parameters[1]);
+  }
+
+  void test_that_the_labels_have_the_correct_text_after_setup() {
+    TS_ASSERT_EQUALS(m_ParameterTypeLabel->text(), PARAMETER_TYPE_LABEL);
+    TS_ASSERT_EQUALS(m_ParameterLabel->text(), PARAMETER_LABEL);
+  }
+
+  void
+  test_that_the_model_contains_the_correct_number_of_workspace_after_instantiation() {
+    TS_ASSERT_EQUALS(m_model->numberOfWorkspaces(), 1);
+  }
+
+  ///----------------------------------------------------------------------
+  /// Unit Tests that test the signals, methods and slots of the presenter
+  ///----------------------------------------------------------------------
+
+private:
+  std::unique_ptr<QTableWidget> m_dataTable;
+  std::unique_ptr<QComboBox> m_ParameterTypeCombo;
+  std::unique_ptr<QComboBox> m_ParameterCombo;
+  std::unique_ptr<QLabel> m_ParameterTypeLabel;
+  std::unique_ptr<QLabel> m_ParameterLabel;
+
+  std::unique_ptr<MockJumpFitDataView> m_view;
+  std::unique_ptr<MockJumpFitModel> m_model;
+  std::unique_ptr<JumpFitDataPresenter> m_presenter;
+};
+#endif
diff --git a/qt/scientific_interfaces/Muon/ALCPeakFittingPresenter.cpp b/qt/scientific_interfaces/Muon/ALCPeakFittingPresenter.cpp
index b4a87a708e88267b13a7b9ae0a3c63cc7a552434..7a8443e3d05fa817acc794ff88331d9a7b404658 100644
--- a/qt/scientific_interfaces/Muon/ALCPeakFittingPresenter.cpp
+++ b/qt/scientific_interfaces/Muon/ALCPeakFittingPresenter.cpp
@@ -106,7 +106,7 @@ void ALCPeakFittingPresenter::onFittedPeaksChanged() {
   IFunction_const_sptr fittedPeaks = m_model->fittedPeaks();
   auto dataWS = m_model->data();
   if (fittedPeaks && dataWS) {
-    auto x = dataWS->x(0);
+    const auto &x = dataWS->x(0);
     m_view->setFittedCurve(
         *(QwtHelper::curveDataFromFunction(fittedPeaks, x.rawData())));
     m_view->setFunction(fittedPeaks);
@@ -154,7 +154,7 @@ bool ALCPeakFittingPresenter::plotGuessOnGraph() {
   auto func = m_view->function("");
   auto dataWS = m_model->data();
   if (func && dataWS) {
-    auto xdata = dataWS->x(0);
+    const auto &xdata = dataWS->x(0);
     m_view->setFittedCurve(
         *(QwtHelper::curveDataFromFunction(func, xdata.rawData())));
     plotted = true;
diff --git a/qt/scientific_interfaces/test/MuonAnalysisDataLoaderTest.h b/qt/scientific_interfaces/test/MuonAnalysisDataLoaderTest.h
index 264c1a9d2bbe6330deed77f2e5b26217d918b3ad..6955a4be9905e95aeda042d7e9162f05b5003023 100644
--- a/qt/scientific_interfaces/test/MuonAnalysisDataLoaderTest.h
+++ b/qt/scientific_interfaces/test/MuonAnalysisDataLoaderTest.h
@@ -271,7 +271,7 @@ public:
     TS_ASSERT(outputWS);
     const auto &data = outputWS->y(0);
     TS_ASSERT_EQUALS(data.size(), 1958);
-    auto xData = outputWS->x(0);
+    const auto &xData = outputWS->x(0);
     auto offset = std::distance(xData.begin(),
                                 std::lower_bound(xData.begin(), xData.end(),
                                                  options.timeLimits.first)) +
diff --git a/qt/widgets/common/CMakeLists.txt b/qt/widgets/common/CMakeLists.txt
index 5fb28c2bcf8be47c26ad9de1341ee1684628927f..ecb85578ae8b0a98d235cccd86322c27726ec178 100644
--- a/qt/widgets/common/CMakeLists.txt
+++ b/qt/widgets/common/CMakeLists.txt
@@ -14,6 +14,7 @@ set ( QT5_SRC_FILES
   src/FindFilesThreadPoolManager.cpp
   src/FindFilesWorker.cpp
   src/FindReplaceDialog.cpp
+  src/FitPropertyBrowser.cpp
   src/FlowLayout.cpp
   src/FunctionBrowser.cpp
   src/GenericDialog.cpp
@@ -35,18 +36,22 @@ set ( QT5_SRC_FILES
   src/MantidWSIndexDialog.cpp
   src/Message.cpp
   src/MessageDisplay.cpp
+  src/MultifitSetupDialog.cpp
   src/MWRunFiles.cpp
   src/OptionsPropertyWidget.cpp
   src/pixmaps.cpp
   src/PluginLibraries.cpp
   src/ProcessingAlgoWidget.cpp
+  src/PropertyHandler.cpp
   src/PropertyWidget.cpp
   src/PropertyWidgetFactory.cpp
   src/PythonRunner.cpp
   src/QtSignalChannel.cpp
   src/RenameParDialog.cpp
   src/ScriptEditor.cpp
+  src/SequentialFitDialog.cpp
   src/SelectFunctionDialog.cpp
+  src/SelectWorkspacesDialog.cpp
   src/TextPropertyWidget.cpp
   src/UserFunctionDialog.cpp
   src/UserSubWindow.cpp
@@ -91,6 +96,7 @@ set ( QT5_MOC_FILES
   inc/MantidQtWidgets/Common/FindFilesThreadPoolManagerMockObjects.h
   inc/MantidQtWidgets/Common/FindFilesWorker.h
   inc/MantidQtWidgets/Common/FindReplaceDialog.h
+  inc/MantidQtWidgets/Common/FitPropertyBrowser.h
   inc/MantidQtWidgets/Common/FunctionBrowser.h
   inc/MantidQtWidgets/Common/GenericDialog.h
   inc/MantidQtWidgets/Common/InputController.h
@@ -104,15 +110,19 @@ set ( QT5_MOC_FILES
   inc/MantidQtWidgets/Common/MantidWidget.h
   inc/MantidQtWidgets/Common/MantidWSIndexDialog.h
   inc/MantidQtWidgets/Common/MessageDisplay.h
+  inc/MantidQtWidgets/Common/MultifitSetupDialog.h
   inc/MantidQtWidgets/Common/MWRunFiles.h
   inc/MantidQtWidgets/Common/OptionsPropertyWidget.h
   inc/MantidQtWidgets/Common/ProcessingAlgoWidget.h
+  inc/MantidQtWidgets/Common/PropertyHandler.h
   inc/MantidQtWidgets/Common/PropertyWidget.h
   inc/MantidQtWidgets/Common/PythonRunner.h
   inc/MantidQtWidgets/Common/QtSignalChannel.h
   inc/MantidQtWidgets/Common/RenameParDialog.h
   inc/MantidQtWidgets/Common/ScriptEditor.h
+  inc/MantidQtWidgets/Common/SequentialFitDialog.h
   inc/MantidQtWidgets/Common/SelectFunctionDialog.h
+  inc/MantidQtWidgets/Common/SelectWorkspacesDialog.h
   inc/MantidQtWidgets/Common/TextPropertyWidget.h
   inc/MantidQtWidgets/Common/UserFunctionDialog.h
   inc/MantidQtWidgets/Common/UserSubWindow.h
@@ -157,8 +167,10 @@ set ( QT5_INC_FILES
   inc/MantidQtWidgets/Common/InterfaceFactory.h
   inc/MantidQtWidgets/Common/MantidDesktopServices.h
   inc/MantidQtWidgets/Common/MantidTreeWidgetItem.h
+  inc/MantidQtWidgets/Common/MultifitSetupDialog.h
   inc/MantidQtWidgets/Common/pixmaps.h
   inc/MantidQtWidgets/Common/PropertyWidgetFactory.h
+  inc/MantidQtWidgets/Common/SequentialFitDialog.h
   inc/MantidQtWidgets/Common/WidgetScrollbarDecorator.h
   inc/MantidQtWidgets/Common/WorkspacePresenter/ADSAdapter.h
   inc/MantidQtWidgets/Common/WorkspacePresenter/IWorkspaceDockView.h
@@ -172,9 +184,11 @@ set ( QT5_INC_FILES
 set ( QT5_UI_FILES
   inc/MantidQtWidgets/Common/DataSelector.ui
   inc/MantidQtWidgets/Common/ManageUserDirectories.ui
+  inc/MantidQtWidgets/Common/MultifitSetupDialog.ui
   inc/MantidQtWidgets/Common/MWRunFiles.ui
   inc/MantidQtWidgets/Common/ProcessingAlgoWidget.ui
   inc/MantidQtWidgets/Common/RenameParDialog.ui
+  inc/MantidQtWidgets/Common/SequentialFitDialog.ui
   inc/MantidQtWidgets/Common/SelectFunctionDialog.ui
   inc/MantidQtWidgets/Common/UserFunctionDialog.ui
 )
@@ -730,11 +744,12 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsCommon
     Qt5::Qscintilla
   INSTALL_DIR
     ${LIB_DIR}
+    ${WORKBENCH_LIB_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../MacOS
     @loader_path/../Libraries
   LINUX_INSTALL_RPATH
-    "\$ORIGIN/../${LIB_DIR}"
+    "\$ORIGIN/../${WORKBENCH_LIB_DIR}"
 )
 
 ###########################################################################
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h
index e61835aa9a13ef3afd42dcbeead2ba0352513f7e..c2063d26e570a6ff4bb632e4563e8ee2c3166d0e 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/Batch/CellDelegate.h
@@ -26,6 +26,8 @@ public:
   void paint(QPainter *painter, const QStyleOptionViewItem &option,
              const QModelIndex &index) const override;
 
+  bool eventFilter(QObject *object, QEvent *event) override;
+
 private:
   QTreeView const &m_view;
   FilteredTreeModel const &m_filteredModel;
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/FitPropertyBrowser.h b/qt/widgets/common/inc/MantidQtWidgets/Common/FitPropertyBrowser.h
index 97f8699ad5faecb068ae6706dbf4888ac98ec822..e0e962d8e70701da67d9a81df97cf3425f65c109 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/FitPropertyBrowser.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/FitPropertyBrowser.h
@@ -105,6 +105,8 @@ public:
   /// Get function parameter names
   QStringList getParameterNames() const;
 
+  /// Load function
+  void loadFunction(const QString &funcString);
   /// Create a new function
   PropertyHandler *addFunction(const std::string &fnName);
 
@@ -176,6 +178,8 @@ public:
   double endX() const;
   /// Set the end X
   void setEndX(double end) override;
+  /// Set both start and end X
+  void setXRange(double start, double end);
   /// Set LogValue for PlotPeakByLogValue
   void setLogValue(const QString &lv = "");
   /// Get LogValue
@@ -207,8 +211,6 @@ public:
   /// Display a tip
   void setTip(const QString &txt);
 
-  /// return groupMember
-  // const std::string groupMember() const {return m_groupMember;};
   /// alter text of Plot Guess
   void setTextPlotGuess(const QString text);
 
@@ -262,6 +264,11 @@ public:
   /// Allow or disallow sequential fits (depending on whether other conditions
   /// are met)
   void allowSequentialFits(bool allow) override;
+
+  // Methods intended for testing only
+
+  int sizeOfFunctionsGroup() const;
+  void addAllowedSpectra(const QString &wsName, const QList<int> &wsIndices);
 public slots:
   virtual void fit();
   virtual void sequentialFit();
@@ -514,8 +521,7 @@ protected:
 private:
   ///
   QPushButton *createFitMenuButton(QWidget *w);
-  /// load and save function
-  void loadFunction(const QString &funcString);
+  /// save function
   void saveFunction(const QString &fnName);
   /// Check if the workspace can be used in the fit
   virtual bool isWorkspaceValid(Mantid::API::Workspace_sptr) const;
@@ -530,6 +536,8 @@ private:
   QString getStringPropertyValue(QtProperty *prop) const;
   /// Check that the properties match the function
   void checkFunction();
+  /// Return the nearest allowed workspace index.
+  int getAllowedIndex(int currentIndex) const;
 
   void setCurrentFunction(Mantid::API::IFunction_const_sptr f) const;
 
@@ -591,9 +599,6 @@ private:
   /// Default background name
   std::string m_defaultBackground;
 
-  /// The current function index
-  int m_index_;
-
   /// Shows if the PeakPickerTool is on
   bool m_peakToolOn;
 
@@ -609,11 +614,6 @@ private:
   /// The autobackground handler
   PropertyHandler *m_autoBackground;
 
-  /// if isWorkspaceAGroup() is true m_groupMember keeps name of the
-  /// MatrixWorkspace
-  /// fitted with theFunction()
-  // std::string m_groupMember;
-
   /// Log names
   QStringList m_logs;
 
@@ -629,6 +629,14 @@ private:
   /// Should the data be normalised before fitting?
   bool m_shouldBeNormalised;
 
+  /// If non-empty it contains references to the spectra
+  /// allowed to be fitted in this browser:
+  ///   keys are workspace names,
+  ///   values are lists of workspace indices
+  QMap<QString, QList<int>> m_allowedSpectra;
+  /// Store workspace index to revert to in case validation fails
+  int m_oldWorkspaceIndex;
+
   friend class PropertyHandler;
   friend class CreateAttributeProperty;
   friend class SetAttribute;
diff --git a/qt/widgets/common/src/Batch/CellDelegate.cpp b/qt/widgets/common/src/Batch/CellDelegate.cpp
index 97a451189f4de16476406fd9917cc765ed9daad7..8128b429ea1528f2cc2153a49f42ae65043518ac 100644
--- a/qt/widgets/common/src/Batch/CellDelegate.cpp
+++ b/qt/widgets/common/src/Batch/CellDelegate.cpp
@@ -7,6 +7,7 @@
 #include "MantidQtWidgets/Common/Batch/CellDelegate.h"
 #include "MantidQtWidgets/Common/Batch/CellStandardItem.h"
 #include "MantidQtWidgets/Common/Batch/QtStandardItemTreeAdapter.h"
+#include <QKeyEvent>
 #include <QPainter>
 
 namespace MantidQt {
@@ -43,6 +44,20 @@ void CellDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
   painter->drawRect(option.rect.adjusted(1, 1, -1, -1));
   painter->restore();
 }
+
+bool CellDelegate::eventFilter(QObject *object, QEvent *event) {
+  QWidget *editor = qobject_cast<QWidget *>(object);
+  if (!editor)
+    return false;
+  if (event->type() == QEvent::KeyPress) {
+    QKeyEvent *keyPress = static_cast<QKeyEvent *>(event);
+    if (keyPress->key() == Qt::Key_Return || keyPress->key() == Qt::Key_Enter) {
+      emit commitData(editor);
+      return false;
+    }
+  }
+  return QStyledItemDelegate::eventFilter(editor, event);
+}
 } // namespace Batch
 } // namespace MantidWidgets
 } // namespace MantidQt
diff --git a/qt/widgets/common/src/Batch/JobTreeView.cpp b/qt/widgets/common/src/Batch/JobTreeView.cpp
index 38332d8a31bd82e13d29cf240e7ae1a836fad721..d9af6016bd9b0360977a9a19cfb42b30323cd01a 100644
--- a/qt/widgets/common/src/Batch/JobTreeView.cpp
+++ b/qt/widgets/common/src/Batch/JobTreeView.cpp
@@ -37,6 +37,7 @@ JobTreeView::JobTreeView(QStringList const &columnHeadings,
 }
 
 void JobTreeView::commitData(QWidget *editor) {
+  auto current_filtered_index = fromFilteredModel(currentIndex());
   auto cellTextBefore =
       m_adaptedMainModel.cellFromCellIndex(m_lastEdited).contentText();
   QTreeView::commitData(editor);
@@ -48,6 +49,7 @@ void JobTreeView::commitData(QWidget *editor) {
     m_notifyee->notifyCellTextChanged(rowLocation().atIndex(m_lastEdited),
                                       m_lastEdited.column(), cellTextBefore,
                                       cellText);
+    editAt(current_filtered_index);
   }
 }
 
@@ -453,19 +455,22 @@ void JobTreeView::appendAndEditAtChildRow() {
   resetFilter();
   auto const parent = mapToMainModel(fromFilteredModel(currentIndex()));
   auto const child = m_adaptedMainModel.appendEmptyChildRow(parent);
-  editAt(expanded(mapToFilteredModel(child)));
   m_notifyee->notifyRowInserted(rowLocation().atIndex(child));
+  editAt(expanded(mapToFilteredModel(child)));
 }
 
 void JobTreeView::appendAndEditAtRowBelow() {
   auto current = currentIndex();
-  auto const below = findOrMakeCellBelow(fromFilteredModel(current));
-  auto index = below.first;
-  auto isNew = below.second;
-
-  editAt(index);
-  if (isNew)
-    m_notifyee->notifyRowInserted(rowLocation().atIndex(mapToMainModel(index)));
+  if (current != m_mainModel.index(-1, -1)) {
+    auto const below = findOrMakeCellBelow(fromFilteredModel(current));
+    auto index = below.first;
+    auto isNew = below.second;
+
+    if (isNew)
+      m_notifyee->notifyRowInserted(
+          rowLocation().atIndex(mapToMainModel(index)));
+    editAt(index);
+  }
 }
 
 void JobTreeView::editAtRowAbove() {
@@ -480,7 +485,9 @@ void JobTreeView::enableFiltering() {
 }
 
 void JobTreeView::keyPressEvent(QKeyEvent *event) {
-  if (event->key() == Qt::Key_Return) {
+  switch (event->key()) {
+  case Qt::Key_Return:
+  case Qt::Key_Enter: {
     if (event->modifiers() & Qt::ControlModifier) {
       appendAndEditAtChildRow();
     } else if (event->modifiers() & Qt::ShiftModifier) {
@@ -488,21 +495,30 @@ void JobTreeView::keyPressEvent(QKeyEvent *event) {
     } else {
       appendAndEditAtRowBelow();
     }
-  } else if (event->key() == Qt::Key_Delete) {
+    break;
+  }
+  case Qt::Key_Delete:
     removeSelectedRequested();
-  } else if (event->key() == Qt::Key_C) {
+    break;
+  case Qt::Key_C: {
     if (event->modifiers() & Qt::ControlModifier) {
       copySelectedRequested();
     }
-  } else if (event->key() == Qt::Key_V) {
+    break;
+  }
+  case Qt::Key_V: {
     if (event->modifiers() & Qt::ControlModifier) {
       pasteSelectedRequested();
     }
-  } else if (event->key() == Qt::Key_X) {
+    break;
+  }
+  case Qt::Key_X: {
     if (event->modifiers() & Qt::ControlModifier) {
       cutSelectedRequested();
     }
-  } else {
+    break;
+  }
+  default:
     QTreeView::keyPressEvent(event);
   }
 }
diff --git a/qt/widgets/common/src/DataSelector.cpp b/qt/widgets/common/src/DataSelector.cpp
index ee2b491d544d91bc683e6784d878666acafa1d61..476c0ea8447f57e7ae6af0d43407f70a94147f4a 100644
--- a/qt/widgets/common/src/DataSelector.cpp
+++ b/qt/widgets/common/src/DataSelector.cpp
@@ -17,6 +17,23 @@
 #include <QMimeData>
 #include <QUrl>
 
+namespace {
+
+std::string extractLastOf(const std::string &str,
+                          const std::string &delimiter) {
+  const auto cutIndex = str.rfind(delimiter);
+  if (cutIndex != std::string::npos)
+    return str.substr(cutIndex, str.size() - cutIndex);
+  return str;
+}
+
+std::string loadAlgName(const std::string &filePath) {
+  const auto suffix = extractLastOf(filePath, ".");
+  return suffix == ".dave" ? "LoadDaveGrp" : "Load";
+}
+
+} // namespace
+
 namespace MantidQt {
 namespace MantidWidgets {
 
@@ -125,8 +142,8 @@ bool DataSelector::isValid() {
         // don't use algorithm runner because we need to know instantly.
         const QString filepath =
             m_uiForm.rfFileInput->getUserInput().toString();
-        const Algorithm_sptr loadAlg =
-            AlgorithmManager::Instance().createUnmanaged("Load");
+        const auto loadAlg = AlgorithmManager::Instance().createUnmanaged(
+            loadAlgName(filepath.toStdString()));
         loadAlg->initialize();
         loadAlg->setProperty("Filename", filepath.toStdString());
         loadAlg->setProperty("OutputWorkspace", wsName.toStdString());
@@ -179,14 +196,14 @@ QString DataSelector::getProblem() const {
  */
 void DataSelector::autoLoadFile(const QString &filepath) {
   using namespace Mantid::API;
-  QString baseName = getWsNameFromFiles();
+  const auto baseName = getWsNameFromFiles().toStdString();
 
   // create instance of load algorithm
-  const Algorithm_sptr loadAlg =
-      AlgorithmManager::Instance().createUnmanaged("Load");
+  const auto loadAlg = AlgorithmManager::Instance().createUnmanaged(
+      loadAlgName(filepath.toStdString()));
   loadAlg->initialize();
   loadAlg->setProperty("Filename", filepath.toStdString());
-  loadAlg->setProperty("OutputWorkspace", baseName.toStdString());
+  loadAlg->setProperty("OutputWorkspace", baseName);
 
   m_algRunner.startAlgorithm(loadAlg);
 }
diff --git a/qt/widgets/common/src/FitPropertyBrowser.cpp b/qt/widgets/common/src/FitPropertyBrowser.cpp
index 7026a402cdb5356cfca92c8886b8b5562a34d720..109050034d8c99285b56b779ac0d6b4d3b3f161e 100644
--- a/qt/widgets/common/src/FitPropertyBrowser.cpp
+++ b/qt/widgets/common/src/FitPropertyBrowser.cpp
@@ -105,13 +105,13 @@ FitPropertyBrowser::FitPropertyBrowser(QWidget *parent, QObject *mantidui)
       m_setupActionRemove(nullptr), m_tip(nullptr), m_fitSelector(nullptr),
       m_fitTree(nullptr), m_currentHandler(nullptr),
       m_defaultFunction("Gaussian"), m_defaultPeak("Gaussian"),
-      m_defaultBackground("LinearBackground"), m_index_(0), m_peakToolOn(false),
+      m_defaultBackground("LinearBackground"), m_peakToolOn(false),
       m_auto_back(false),
       m_autoBgName(QString::fromStdString(
           Mantid::Kernel::ConfigService::Instance().getString(
               "curvefitting.autoBackground"))),
       m_autoBackground(nullptr), m_decimals(-1), m_mantidui(mantidui),
-      m_shouldBeNormalised(false) {
+      m_shouldBeNormalised(false), m_oldWorkspaceIndex(-1) {
   Mantid::API::FrameworkManager::Instance().loadPlugins();
 
   // Try to create a Gaussian. Failing will mean that CurveFitting dll is not
@@ -175,8 +175,10 @@ void FitPropertyBrowser::init() {
   /* Create function group */
   QtProperty *functionsGroup = m_groupManager->addProperty("Functions");
 
-  connect(this, SIGNAL(xRangeChanged(double, double)), m_mantidui,
-          SLOT(x_range_from_picker(double, double)));
+  if (m_mantidui) {
+    connect(this, SIGNAL(xRangeChanged(double, double)), m_mantidui,
+            SLOT(x_range_from_picker(double, double)));
+  }
   /* Create input - output properties */
   QtProperty *settingsGroup = m_groupManager->addProperty("Settings");
   m_startX = addDoubleProperty("StartX");
@@ -355,6 +357,7 @@ void FitPropertyBrowser::populateFitMenuButton(QSignalMapper *fitMapper,
  */
 void FitPropertyBrowser::initBasicLayout(QWidget *w) {
   QPushButton *btnFit = createFitMenuButton(w);
+  btnFit->setObjectName("button_Fit");
   // to be able to change windows title from tread
   connect(this, SIGNAL(changeWindowTitle(const QString &)), this,
           SLOT(setWindowTitle(const QString &)));
@@ -387,17 +390,22 @@ void FitPropertyBrowser::initBasicLayout(QWidget *w) {
           SLOT(vectorSizeChanged(QtProperty *)));
 
   QVBoxLayout *layout = new QVBoxLayout(w);
+  layout->setObjectName("vlayout");
   QGridLayout *buttonsLayout = new QGridLayout();
 
   QPushButton *btnDisplay = new QPushButton("Display");
+  btnDisplay->setObjectName("button_Display");
   QMenu *displayMenu = new QMenu(this);
+  displayMenu->setObjectName("menu_Display");
   m_displayActionPlotGuess = new QAction("Plot Guess", this);
   m_displayActionPlotGuess->setEnabled(false);
   m_displayActionQuality = new QAction("Quality", this);
+  m_displayActionQuality->setObjectName("action_Quality");
   m_displayActionQuality->setCheckable(true);
   m_displayActionQuality->setChecked(true);
   m_displayActionClearAll = new QAction("Clear fit curves", this);
   QSignalMapper *displayMapper = new QSignalMapper(this);
+  displayMapper->setObjectName("mapper_Display");
 
   displayMapper->setMapping(m_displayActionPlotGuess, "PlotGuess");
   displayMapper->setMapping(m_displayActionQuality, "Quality");
@@ -416,12 +424,17 @@ void FitPropertyBrowser::initBasicLayout(QWidget *w) {
   btnDisplay->setMenu(displayMenu);
 
   QPushButton *btnSetup = new QPushButton("Setup");
+  btnSetup->setObjectName("button_Setup");
   QMenu *setupMenu = new QMenu(this);
+  setupMenu->setObjectName("menu_Setup");
 
   m_setupActionCustomSetup = new QAction("Custom Setup", this);
   QAction *setupActionManageSetup = new QAction("Manage Setup", this);
+  setupActionManageSetup->setObjectName("action_ManageSetup");
   QAction *setupActionFindPeaks = new QAction("Find Peaks", this);
+  setupActionFindPeaks->setObjectName("action_FindPeaks");
   QAction *setupActionClearFit = new QAction("Clear Model", this);
+  setupActionClearFit->setObjectName("action_ClearModel");
 
   QMenu *setupSubMenuCustom = new QMenu(this);
   m_setupActionCustomSetup->setMenu(setupSubMenuCustom);
@@ -432,7 +445,9 @@ void FitPropertyBrowser::initBasicLayout(QWidget *w) {
   QAction *setupActionSave = new QAction("Save Setup", this);
   m_setupActionRemove = new QAction("Remove Setup", this);
   QAction *setupActionCopyToClipboard = new QAction("Copy To Clipboard", this);
+  setupActionCopyToClipboard->setObjectName("action_CopyToClipboard");
   QAction *setupActionLoadFromString = new QAction("Load From String", this);
+  setupActionLoadFromString->setObjectName("action_LoadFromString");
   QSignalMapper *setupManageMapper = new QSignalMapper(this);
   setupManageMapper->setMapping(setupActionSave, "SaveSetup");
   setupManageMapper->setMapping(setupActionCopyToClipboard, "CopyToClipboard");
@@ -485,6 +500,7 @@ void FitPropertyBrowser::initBasicLayout(QWidget *w) {
   layout->addLayout(buttonsLayout);
   layout->addWidget(m_tip);
   layout->addWidget(m_browser);
+  m_browser->setObjectName("tree_browser");
 
   setWidget(w);
 
@@ -1142,6 +1158,7 @@ void FitPropertyBrowser::setWorkspaceName(const QString &wsName) {
       }
     }
   }
+  setWorkspaceIndex(0);
 }
 
 /// Get workspace index
@@ -1151,7 +1168,12 @@ int FitPropertyBrowser::workspaceIndex() const {
 
 /// Set workspace index
 void FitPropertyBrowser::setWorkspaceIndex(int i) {
-  m_intManager->setValue(m_workspaceIndex, i);
+  try {
+    auto const index = getAllowedIndex(i);
+    m_intManager->setValue(m_workspaceIndex, index);
+  } catch (Mantid::Kernel::Exception::NotFoundError &) {
+    // ignore this error
+  }
 }
 
 /// Get the output name
@@ -1347,22 +1369,13 @@ void FitPropertyBrowser::intChanged(QtProperty *prop) {
     return;
 
   if (prop == m_workspaceIndex) {
-    auto const workspace =
-        convertToMatrixWorkspace(getADSWorkspace(workspaceName()));
-
-    if (workspace) {
-      int const numberOfSpectra = getNumberOfSpectra(workspace);
-      int const currentIndex = workspaceIndex();
-
-      if (currentIndex < 0) {
-        setWorkspaceIndex(0);
-        emit workspaceIndexChanged(0);
-      } else if (currentIndex >= numberOfSpectra) {
-        setWorkspaceIndex(numberOfSpectra - 1);
-        emit workspaceIndexChanged(numberOfSpectra - 1);
-      }
-    } else
-      setWorkspaceIndex(0);
+    auto const currentIndex = workspaceIndex();
+    auto const allowedIndex = getAllowedIndex(currentIndex);
+    if (allowedIndex != currentIndex) {
+      setWorkspaceIndex(allowedIndex);
+      emit workspaceIndexChanged(allowedIndex);
+    }
+    m_oldWorkspaceIndex = allowedIndex;
   } else if (prop->propertyName() == "Workspace Index") {
     PropertyHandler *h = getHandler()->findHandler(prop);
     if (!h)
@@ -1733,12 +1746,15 @@ void FitPropertyBrowser::clearFitResultStatus() {
 /// Get and store available workspace names
 void FitPropertyBrowser::populateWorkspaceNames() {
   m_workspaceNames.clear();
-  // QStringList tmp = m_appWindow->mantidUI->getWorkspaceNames();
+  bool allAreAllowed = m_allowedSpectra.isEmpty();
 
   QStringList tmp;
   auto sv = Mantid::API::AnalysisDataService::Instance().getObjectNames();
   for (auto it = sv.begin(); it != sv.end(); ++it) {
-    tmp << QString::fromStdString(*it);
+    auto const &name = QString::fromStdString(*it);
+    if (allAreAllowed || m_allowedSpectra.contains(name)) {
+      tmp << name;
+    }
   }
 
   for (int i = 0; i < tmp.size(); i++) {
@@ -1750,6 +1766,7 @@ void FitPropertyBrowser::populateWorkspaceNames() {
     }
   }
   m_enumManager->setEnumNames(m_workspace, m_workspaceNames);
+  setWorkspaceIndex(0);
 }
 
 /**
@@ -1781,11 +1798,13 @@ void FitPropertyBrowser::setADSObserveEnabled(bool enabled) {
 void FitPropertyBrowser::addHandle(
     const std::string &wsName,
     const boost::shared_ptr<Mantid::API::Workspace> ws) {
-  if (!isWorkspaceValid(ws))
+  auto const qName = QString::fromStdString(wsName);
+  if (!isWorkspaceValid(ws) ||
+      (!m_allowedSpectra.isEmpty() && !m_allowedSpectra.contains(qName)))
     return;
   QStringList oldWorkspaces = m_workspaceNames;
   QString oldName = QString::fromStdString(workspaceName());
-  int i = m_workspaceNames.indexOf(QString(wsName.c_str()));
+  int i = m_workspaceNames.indexOf(qName);
 
   bool initialSignalsBlocked = m_enumManager->signalsBlocked();
 
@@ -1795,7 +1814,7 @@ void FitPropertyBrowser::addHandle(
       m_enumManager->blockSignals(true);
     }
 
-    m_workspaceNames.append(QString(wsName.c_str()));
+    m_workspaceNames.append(qName);
     m_workspaceNames.sort();
     m_enumManager->setEnumNames(m_workspace, m_workspaceNames);
   }
@@ -1806,12 +1825,6 @@ void FitPropertyBrowser::addHandle(
   }
 
   m_enumManager->blockSignals(initialSignalsBlocked);
-  /*
-  if (m_workspaceNames.size() == 1)
-  {
-    setWorkspaceName(QString::fromStdString(wsName));
-  }
-  */
 }
 
 /// workspace was removed
@@ -1883,6 +1896,11 @@ void FitPropertyBrowser::setEndX(double value) {
   m_doubleManager->setValue(m_endX, value);
 }
 
+void FitPropertyBrowser::setXRange(double start, double end) {
+  m_doubleManager->setValue(m_startX, start);
+  m_doubleManager->setValue(m_endX, end);
+}
+
 ///
 QtBrowserItem *FitPropertyBrowser::findItem(QtBrowserItem *parent,
                                             QtProperty *prop) const {
@@ -2419,6 +2437,50 @@ Mantid::API::IFunction_const_sptr FitPropertyBrowser::theFunction() const {
 
 void FitPropertyBrowser::checkFunction() {}
 
+/**
+ * If the current workspace index is set to a disallowed value this function
+ * returns the nearest allowed index. Otherwise the current index is returned.
+ */
+int FitPropertyBrowser::getAllowedIndex(int currentIndex) const {
+  auto const workspace =
+      convertToMatrixWorkspace(getADSWorkspace(workspaceName()));
+
+  if (!workspace) {
+    return 0;
+  }
+
+  if (currentIndex == m_oldWorkspaceIndex) {
+    return currentIndex < 0 ? 0 : currentIndex;
+  }
+
+  auto const allowedIndices =
+      m_allowedSpectra.empty()
+          ? QList<int>()
+          : m_allowedSpectra[QString::fromStdString(workspaceName())];
+  auto const firstIndex = m_allowedSpectra.empty() ? 0 : allowedIndices.front();
+  auto const lastIndex = m_allowedSpectra.empty()
+                             ? getNumberOfSpectra(workspace) - 1
+                             : allowedIndices.back();
+
+  auto allowedIndex = currentIndex;
+  if (currentIndex < firstIndex) {
+    allowedIndex = firstIndex;
+  } else if (currentIndex > lastIndex) {
+    allowedIndex = lastIndex;
+  } else if (!m_allowedSpectra.empty() &&
+             !allowedIndices.contains(currentIndex)) {
+    allowedIndex = m_oldWorkspaceIndex;
+    auto i = allowedIndices.indexOf(m_oldWorkspaceIndex);
+    if (i >= 0) {
+      i = currentIndex > m_oldWorkspaceIndex ? i + 1 : i - 1;
+      if (i >= 0 && i < allowedIndices.size()) {
+        allowedIndex = allowedIndices[i];
+      }
+    }
+  }
+  return allowedIndex;
+}
+
 void FitPropertyBrowser::saveFunction() {
   bool ok(false);
   QString fnName = QInputDialog::getText(
@@ -3287,5 +3349,25 @@ void FitPropertyBrowser::modifyFitMenu(QAction *fitAction, bool enabled) {
     m_fitMenu->removeAction(fitAction);
   }
 }
+
+int MantidQt::MantidWidgets::FitPropertyBrowser::sizeOfFunctionsGroup() const {
+  return m_functionsGroup->children().size();
+}
+
+void FitPropertyBrowser::addAllowedSpectra(const QString &wsName,
+                                           const QList<int> &wsSpectra) {
+  auto const name = wsName.toStdString();
+  auto ws = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name);
+  if (ws) {
+    QList<int> indices;
+    for (auto const i : wsSpectra) {
+      indices.push_back(static_cast<int>(ws->getIndexFromSpectrumNumber(i)));
+    }
+    m_allowedSpectra.insert(wsName, indices);
+  } else {
+    throw std::runtime_error("Workspace " + name + " is not a MatrixWorkspace");
+  }
+}
+
 } // namespace MantidWidgets
 } // namespace MantidQt
diff --git a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp
index 76cc1fd38cdce500b2f8b2f84ccecdaf4bd04aed..6e6aeaa8952c776ce314b91a0fbe9fc4cb1c2c7b 100644
--- a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp
+++ b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp
@@ -106,6 +106,9 @@ void WorkspaceTreeWidgetSimple::popupContextMenu() {
           matrixWS->getInstrument() &&
           !matrixWS->getInstrument()->getName().empty());
       menu->addSeparator();
+    } else if (boost::dynamic_pointer_cast<ITableWorkspace>(workspace)) {
+      menu->addAction(m_showData);
+      menu->addSeparator();
     }
     menu->addAction(m_rename);
     menu->addAction(m_saveNexus);
diff --git a/qt/widgets/instrumentview/CMakeLists.txt b/qt/widgets/instrumentview/CMakeLists.txt
index 8034f4fbc310306c15f286920ae1b57f61c3eb63..7191aa7f186c5b58173e95d7edacb52c87ea7a02 100644
--- a/qt/widgets/instrumentview/CMakeLists.txt
+++ b/qt/widgets/instrumentview/CMakeLists.txt
@@ -201,9 +201,10 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsInstrumentView
     MantidQtWidgetsMplCpp
   INSTALL_DIR
     ${LIB_DIR}
+    ${WORKBENCH_LIB_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../MacOS
     @loader_path/../Libraries
   LINUX_INSTALL_RPATH
-    "\$ORIGIN/../${LIB_DIR}"
+    "\$ORIGIN/../${WORKBENCH_LIB_DIR}"
 )
diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
index e52a888e2e50285b9cf6a1cefbdf256e600e8b0a..e109ececeb3e835c87e6c3c1fc4d23ef8884a8a6 100644
--- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
+++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
@@ -273,6 +273,7 @@ private:
   static double getOutOfPlaneAngle(const Mantid::Kernel::V3D &pos,
                                    const Mantid::Kernel::V3D &origin,
                                    const Mantid::Kernel::V3D &normal);
+  void addPeakLabels(const std::vector<size_t> &detIndices);
 
   InstrumentWidgetPickTab *m_tab;
   InstrumentWidget *m_instrWidget;
diff --git a/qt/widgets/instrumentview/src/InstrumentActor.cpp b/qt/widgets/instrumentview/src/InstrumentActor.cpp
index e62e918249fd563fc4474c136347df802659de6c..3da23f03d2c780ef5336eedfadd7b4b254d4c4cf 100644
--- a/qt/widgets/instrumentview/src/InstrumentActor.cpp
+++ b/qt/widgets/instrumentview/src/InstrumentActor.cpp
@@ -461,8 +461,9 @@ void InstrumentActor::sumDetectors(const std::vector<size_t> &dets,
                                    std::vector<double> &x,
                                    std::vector<double> &y, size_t size) const {
   Mantid::API::MatrixWorkspace_const_sptr ws = getWorkspace();
-  if (size > ws->blocksize() || size == 0) {
-    size = ws->blocksize();
+  const auto blocksize = ws->blocksize();
+  if (size > blocksize || size == 0) {
+    size = blocksize;
   }
 
   if (m_ragged) {
diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
index f0ce5d99791c0aa89585fb830c89fece9e16993f..970327b8d022c712e3951a2b4353f8beb2ae52cc 100644
--- a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
+++ b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
@@ -1160,6 +1160,27 @@ void DetectorPlotController::setPlotData(
                         actor.getWorkspace()->getAxis(0)->unit()->unitID()),
                     "multiple");
   }
+
+  addPeakLabels(detIndices);
+}
+
+/**
+ * Add peak labels to plot for each detector shown
+ * @param detIndices :: A list of detector inidices.
+ */
+void DetectorPlotController::addPeakLabels(
+    const std::vector<size_t> &detIndices) {
+  auto surface = m_tab->getSurface();
+  if (surface) {
+    const auto &actor = m_instrWidget->getInstrumentActor();
+    auto detIds = actor.getDetIDs(detIndices);
+    for (const auto &detId : detIds) {
+      auto markers = surface->getMarkersWithID(static_cast<int>(detId));
+      for (const auto &marker : markers) {
+        m_plot->addPeakLabel(marker);
+      }
+    }
+  }
 }
 
 /**
diff --git a/qt/widgets/legacyqwt/CMakeLists.txt b/qt/widgets/legacyqwt/CMakeLists.txt
index 3cac2c5bac88a1304ebed03158a001878e9b7e92..729764a5edf9f233f7c1c0bb95858e90fe68301b 100644
--- a/qt/widgets/legacyqwt/CMakeLists.txt
+++ b/qt/widgets/legacyqwt/CMakeLists.txt
@@ -1,4 +1,5 @@
  set ( SRC_FILES
+  src/ContourPreviewPlot.cpp
   src/DraggableColorBarWidget.cpp
   src/MantidColorMap.cpp
   src/MantidQwtIMDWorkspaceData.cpp
@@ -22,6 +23,7 @@
 )
 
 set ( MOC_FILES
+  inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h
   inc/MantidQtWidgets/LegacyQwt/DraggableColorBarWidget.h
   inc/MantidQtWidgets/LegacyQwt/ColorBarWidget.h
   inc/MantidQtWidgets/LegacyQwt/MWView.h
@@ -34,6 +36,7 @@ set ( MOC_FILES
 
 # Include files aren't required, but this makes them appear in Visual Studio
 set ( INC_FILES
+  inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h
   inc/MantidQtWidgets/LegacyQwt/MantidQwtWorkspaceData.h
   inc/MantidQtWidgets/LegacyQwt/QwtRasterDataMD.h
   inc/MantidQtWidgets/LegacyQwt/QwtRasterDataMDNonOrthogonal.h
@@ -52,6 +55,7 @@ set ( INC_FILES
 
 set ( UI_FILES
   inc/MantidQtWidgets/LegacyQwt/ColorBarWidget.ui
+  inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.ui
   inc/MantidQtWidgets/LegacyQwt/DisplayCurveFit.ui
   inc/MantidQtWidgets/LegacyQwt/MWView.ui
   inc/MantidQtWidgets/LegacyQwt/PreviewPlot.ui
@@ -91,6 +95,7 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsLegacyQwt
 # Testing
 ###########################################################################
 set( TEST_FILES
+  test/ContourPreviewPlotTest.h
   test/MantidColorMapTest.h
   test/QwtWorkspaceBinDataTest.h
   test/QwtWorkspaceSpectrumDataTest.h
diff --git a/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h
new file mode 100644
index 0000000000000000000000000000000000000000..bf062458cf3560cc08115d1c10e56aad4ebbc9da
--- /dev/null
+++ b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h
@@ -0,0 +1,121 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_MANTIDWIDGETS_CONTOURPREVIEWPLOT_H_
+#define MANTID_MANTIDWIDGETS_CONTOURPREVIEWPLOT_H_
+
+#include "ui_ContourPreviewPlot.h"
+
+#include "DllOption.h"
+#include "MantidAPI/IMDWorkspace.h"
+#include "MantidAPI/MatrixWorkspace_fwd.h"
+#include "MantidGeometry/MDGeometry/MDHistoDimension.h"
+#include "MantidQtWidgets/Common/MdSettings.h"
+#include "MantidQtWidgets/Common/WorkspaceObserver.h"
+
+#include <qwt_plot_spectrogram.h>
+
+#include <QSettings>
+#include <QWidget>
+
+namespace Mantid {
+namespace API {
+class MWDimension;
+}
+} // namespace Mantid
+
+namespace MantidQt {
+namespace API {
+class QwtRasterDataMD;
+class MdSettings;
+} // namespace API
+
+namespace MantidWidgets {
+class ColorBarWidget;
+class SafeQwtPlot;
+
+using MWDimension_sptr = boost::shared_ptr<Mantid::API::MWDimension>;
+using MWDimension_const_sptr =
+    boost::shared_ptr<Mantid::API::MWDimension const>;
+using DimensionRange = std::pair<Mantid::coord_t, Mantid::coord_t>;
+
+class EXPORT_OPT_MANTIDQT_LEGACYQWT ContourPreviewPlot
+    : public QWidget,
+      public MantidQt::API::WorkspaceObserver {
+  Q_OBJECT
+
+public:
+  ContourPreviewPlot(QWidget *parent = nullptr);
+  ~ContourPreviewPlot() override;
+
+  Mantid::API::MatrixWorkspace_sptr getActiveWorkspace() const;
+  void setWorkspace(Mantid::API::MatrixWorkspace_sptr const workspace);
+  SafeQwtPlot *getPlot2D();
+
+  void setPlotVisible(bool const &visible);
+  void setColourBarVisible(bool const &visible);
+
+  bool isPlotVisible() const;
+  bool isColourBarVisible() const;
+
+  void setXAxisLabel(QString const &label);
+  void setYAxisLabel(QString const &label);
+
+protected:
+  void preDeleteHandle(
+      std::string const &workspaceName,
+      boost::shared_ptr<Mantid::API::Workspace> const workspace) override;
+
+private slots:
+  void handleColourRangeChanged();
+  void handleLoadColourMap();
+  void handleSetTransparentZeros(bool const &transparent);
+
+private:
+  void setupColourBarAndPlot();
+  QString colourMapFileName(QString const &filename);
+  void loadSettings();
+  void setCurrentColourMapFile(QSettings const &settings);
+  void setCurrentColourMapFile(QString const &file);
+  void saveSettings();
+
+  void loadColourMap(QString filename = QString());
+  void updateDisplay();
+
+  void checkRangeLimits() const;
+  void checkForInfiniteLimits(DimensionRange const &range,
+                              std::size_t const &index,
+                              std::ostringstream &message) const;
+  DimensionRange dimensionRange(std::size_t const &index) const;
+  Mantid::coord_t dimensionMinimum(std::size_t const &index) const;
+  Mantid::coord_t dimensionMaximum(std::size_t const &index) const;
+  void findFullRange();
+
+  void setVectorDimensions();
+  void clearPlot();
+
+  Ui::ContourPreviewPlot m_uiForm;
+  /// Spectrogram plot of ContourPreviewPlot
+  std::unique_ptr<QwtPlotSpectrogram> m_spectrogram;
+  /// Data presenter
+  std::unique_ptr<API::QwtRasterDataMD> m_data;
+  /// File of the last loaded colour map.
+  QString m_currentColourMapFile;
+  /// Md Settings for colour maps
+  boost::shared_ptr<MantidQt::API::MdSettings> m_mdSettings;
+  /// Workspace being shown
+  Mantid::API::MatrixWorkspace_sptr m_workspace;
+  /// The calculated range of values in the full data set
+  QwtDoubleInterval m_colourRangeFull;
+  Mantid::API::MDNormalization m_normalization;
+  /// Vector of the dimensions to show.
+  std::vector<Mantid::Geometry::MDHistoDimension_sptr> m_dimensions;
+};
+
+} // namespace MantidWidgets
+} // namespace MantidQt
+
+#endif /* MANTID_MANTIDWIDGETS_CONTOURPREVIEWPLOT_H_ */
diff --git a/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.ui b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.ui
new file mode 100644
index 0000000000000000000000000000000000000000..cfac215cf6c33d5a7eeaf9309f080f79b675e89a
--- /dev/null
+++ b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/ContourPreviewPlot.ui
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ContourPreviewPlot</class>
+ <widget class="QWidget" name="ContourPreviewPlot">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>569</width>
+    <height>470</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="1">
+    <layout class="QGridLayout" name="gridLayout_2">
+     <item row="0" column="2">
+      <widget class="MantidQt::MantidWidgets::ColorBarWidget" name="colourBar" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="MantidQt::MantidWidgets::SafeQwtPlot" name="plot2D">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="lbYAxis">
+       <property name="font">
+        <font>
+         <pointsize>8</pointsize>
+        </font>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QFrame" name="fXAxis">
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <property name="spacing">
+         <number>0</number>
+        </property>
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <spacer name="horizontalSpacer">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeType">
+           <enum>QSizePolicy::Fixed</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>30</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QLabel" name="lbXAxis">
+          <property name="font">
+           <font>
+            <pointsize>8</pointsize>
+           </font>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>QwtPlot</class>
+   <extends>QFrame</extends>
+   <header>qwt_plot.h</header>
+   <container>1</container>
+  </customwidget>
+  <customwidget>
+   <class>MantidQt::MantidWidgets::ColorBarWidget</class>
+   <extends>QWidget</extends>
+   <header>MantidQtWidgets/LegacyQwt/ColorBarWidget.h</header>
+  </customwidget>
+  <customwidget>
+   <class>MantidQt::MantidWidgets::SafeQwtPlot</class>
+   <extends>QwtPlot</extends>
+   <header>MantidQtWidgets/LegacyQwt/SafeQwtPlot.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/qt/widgets/legacyqwt/src/ContourPreviewPlot.cpp b/qt/widgets/legacyqwt/src/ContourPreviewPlot.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e5d1003676dba643c0e007118c6ebac3f075d3b
--- /dev/null
+++ b/qt/widgets/legacyqwt/src/ContourPreviewPlot.cpp
@@ -0,0 +1,403 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h"
+
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidGeometry/MDGeometry/IMDDimension.h"
+#include "MantidGeometry/MDGeometry/MDHistoDimension.h"
+#include "MantidGeometry/MDGeometry/MDTypes.h"
+#include "MantidKernel/ReadLock.h"
+#include "MantidQtWidgets/LegacyQwt/MantidColorMap.h"
+#include "MantidQtWidgets/LegacyQwt/QwtRasterDataMD.h"
+#include "MantidQtWidgets/LegacyQwt/SignalRange.h"
+
+#include <boost/pointer_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <qwt_color_map.h>
+#include <qwt_double_rect.h>
+
+#include <cmath>
+#include <sstream>
+
+using namespace Mantid::API;
+using namespace Mantid::Geometry;
+using namespace MantidQt::API;
+
+namespace {
+Mantid::Kernel::Logger g_log("ContourPreviewPlot");
+
+MatrixWorkspace_sptr
+convertToMatrixWorkspace(boost::shared_ptr<Workspace> const workspace) {
+  return boost::dynamic_pointer_cast<MatrixWorkspace>(workspace);
+}
+
+template <typename T>
+auto getValueFromSettings(QSettings const &settings, QString const &key,
+                          T const &defaultValue) {
+  return settings.value(key, defaultValue);
+}
+
+QString getColourMapFile(QSettings const &settings) {
+  return getValueFromSettings(settings, "ColourmapFile", "").toString();
+}
+
+int getScaleType(QSettings const &settings) {
+  int scaleType = getValueFromSettings(settings, "ColourScale", -1).toInt();
+  if (scaleType == -1)
+    scaleType = getValueFromSettings(settings, "LogColourScale", 0).toInt();
+  return scaleType;
+}
+
+double getExponent(QSettings const &settings) {
+  return getValueFromSettings(settings, "PowerScaleExponent", 2.0).toDouble();
+}
+
+bool transparentZeros(QSettings const &settings) {
+  return getValueFromSettings(settings, "TransparentZeros", 1).toInt();
+}
+
+} // namespace
+
+namespace MantidQt {
+namespace MantidWidgets {
+
+ContourPreviewPlot::ContourPreviewPlot(QWidget *parent)
+    : QWidget(parent), WorkspaceObserver(),
+      m_mdSettings(boost::make_shared<MdSettings>()), m_workspace(),
+      m_dimensions() {
+  this->observePreDelete(true);
+  m_spectrogram = std::make_unique<QwtPlotSpectrogram>();
+  m_data = std::make_unique<QwtRasterDataMD>();
+  m_normalization = NoNormalization;
+  m_uiForm.setupUi(this);
+
+  QObject::connect(m_uiForm.colourBar,
+                   SIGNAL(changedColorRange(double, double, bool)), this,
+                   SLOT(handleColourRangeChanged()));
+  QObject::connect(m_uiForm.colourBar, SIGNAL(colorBarDoubleClicked()), this,
+                   SLOT(handleLoadColourMap()));
+
+  this->setupColourBarAndPlot();
+  this->loadSettings();
+  this->updateDisplay();
+}
+
+ContourPreviewPlot::~ContourPreviewPlot() {
+  this->observePreDelete(false); // Disconnect notifications
+  saveSettings();
+  m_data.reset();
+  m_spectrogram.reset();
+}
+
+MatrixWorkspace_sptr ContourPreviewPlot::getActiveWorkspace() const {
+  return m_workspace;
+}
+
+/**
+ * Initialize objects after loading the workspace
+ */
+void ContourPreviewPlot::setWorkspace(MatrixWorkspace_sptr const workspace) {
+  m_workspace = workspace;
+  this->checkRangeLimits();
+  m_data->setWorkspace(workspace);
+
+  // Sets how the signal is normalized
+  m_normalization = workspace->displayNormalization();
+  m_data->setNormalization(m_normalization);
+
+  this->setVectorDimensions();
+  this->findFullRange();
+
+  m_uiForm.colourBar->setViewRange(m_colourRangeFull);
+  m_uiForm.colourBar->updateColorMap();
+  m_uiForm.plot2D->setWorkspace(workspace);
+  m_spectrogram->setColorMap(m_uiForm.colourBar->getColorMap());
+
+  this->updateDisplay();
+  m_uiForm.colourBar->setScale(0);
+}
+
+SafeQwtPlot *ContourPreviewPlot::getPlot2D() { return m_uiForm.plot2D; }
+
+/**
+ * Set the plot to be visible or hidden
+ * @param visible :: false to hide the plot
+ */
+void ContourPreviewPlot::setPlotVisible(bool const &visible) {
+  m_uiForm.plot2D->setVisible(visible);
+}
+
+/**
+ * Set the colour bar to be visible or hidden
+ * @param visible :: false to hide the colour bar
+ */
+void ContourPreviewPlot::setColourBarVisible(bool const &visible) {
+  m_uiForm.colourBar->setVisible(visible);
+}
+
+/**
+ * Checks if the plot is currently visible
+ * @returns true if the plot is visible
+ */
+bool ContourPreviewPlot::isPlotVisible() const {
+  return m_uiForm.plot2D->isVisible();
+}
+
+/**
+ * Checks if the colour bar is currently visible
+ * @returns true if the colour bar is visible
+ */
+bool ContourPreviewPlot::isColourBarVisible() const {
+  return m_uiForm.colourBar->isVisible();
+}
+
+/**
+ * Sets the label for the x axis
+ * @param label :: the label given to the axis
+ */
+void ContourPreviewPlot::setXAxisLabel(QString const &label) {
+  m_uiForm.lbXAxis->setText(label);
+}
+
+/**
+ * Sets the label for the y axis
+ * @param label :: the label given to the axis
+ */
+void ContourPreviewPlot::setYAxisLabel(QString const &label) {
+  m_uiForm.lbYAxis->setText(label);
+}
+
+/**
+ * Slot called when the ColorBarWidget changes the range of colours
+ */
+void ContourPreviewPlot::handleColourRangeChanged() {
+  m_spectrogram->setColorMap(m_uiForm.colourBar->getColorMap());
+  this->updateDisplay();
+}
+
+/**
+ * Slot called to load a colour map
+ */
+void ContourPreviewPlot::handleLoadColourMap() {
+  this->loadColourMap(QString());
+}
+
+/**
+ * Set whether to display 0 signal as "transparent" colour.
+ * @param transparent :: true if you want zeros to be transparent.
+ */
+void ContourPreviewPlot::handleSetTransparentZeros(bool const &transparent) {
+  m_data->setZerosAsNan(transparent);
+  this->updateDisplay();
+}
+
+/**
+ * Remove the displayed data when the associated workspace is deleted
+ */
+void ContourPreviewPlot::preDeleteHandle(
+    std::string const &workspaceName,
+    boost::shared_ptr<Workspace> const workspace) {
+  UNUSED_ARG(workspaceName);
+  auto const deletedWorkspace = convertToMatrixWorkspace(workspace);
+  if (deletedWorkspace && deletedWorkspace == m_workspace)
+    this->clearPlot();
+}
+
+/**
+ * Clear the plot
+ */
+void ContourPreviewPlot::clearPlot() { m_uiForm.plot2D->clear(); }
+
+/**
+ * Setup the ColourBar and Plot. Attach the spectrogram to the plot
+ */
+void ContourPreviewPlot::setupColourBarAndPlot() {
+  m_uiForm.colourBar->setViewRange(1, 10);
+  m_spectrogram->attach(m_uiForm.plot2D);
+  m_spectrogram->setColorMap(m_uiForm.colourBar->getColorMap());
+  m_uiForm.plot2D->autoRefresh();
+}
+
+/**
+ * Load QSettings from .ini-type files
+ */
+void ContourPreviewPlot::loadSettings() {
+  QSettings settings;
+  settings.beginGroup("Mantid/ContourPreviewPlot");
+
+  setCurrentColourMapFile(settings);
+  if (!m_currentColourMapFile.isEmpty())
+    loadColourMap(m_currentColourMapFile);
+
+  m_uiForm.colourBar->setScale(getScaleType(settings));
+  m_uiForm.colourBar->setExponent(getExponent(settings));
+  this->handleSetTransparentZeros(transparentZeros(settings));
+
+  settings.endGroup();
+}
+
+/**
+ * Set the current colour map file
+ * @param settings :: stores the value of the colour map file
+ */
+void ContourPreviewPlot::setCurrentColourMapFile(QSettings const &settings) {
+  if (m_mdSettings != nullptr && m_mdSettings->getUsageGeneralMdColorMap())
+    setCurrentColourMapFile(m_mdSettings->getGeneralMdColorMapFile());
+  else
+    setCurrentColourMapFile(getColourMapFile(settings));
+}
+
+/**
+ * Set the current colour map file
+ * @param file :: the filename as a QString
+ */
+void ContourPreviewPlot::setCurrentColourMapFile(QString const &file) {
+  m_currentColourMapFile = file;
+}
+
+void ContourPreviewPlot::saveSettings() {
+  QSettings settings;
+  settings.beginGroup("Mantid/ContourPreviewPlot");
+  settings.setValue("ColourmapFile", m_currentColourMapFile);
+  settings.setValue("ColourScale", m_uiForm.colourBar->getScale());
+  settings.setValue("PowerScaleExponent", m_uiForm.colourBar->getExponent());
+  settings.setValue("TransparentZeros", (m_data->isZerosAsNan() ? 1 : 0));
+}
+
+/**
+ * Load a colour map from a file
+ * @param file :: file to open; empty to ask via a dialog box.
+ */
+void ContourPreviewPlot::loadColourMap(QString file) {
+  auto const filename = colourMapFileName(file);
+  if (!filename.isEmpty()) {
+    setCurrentColourMapFile(filename);
+    m_uiForm.colourBar->getColorMap().loadMap(filename);
+    m_spectrogram->setColorMap(m_uiForm.colourBar->getColorMap());
+    m_uiForm.colourBar->updateColorMap();
+
+    this->updateDisplay();
+  }
+}
+
+QString ContourPreviewPlot::colourMapFileName(QString const &filename) {
+  if (filename.isEmpty())
+    return MantidColorMap::chooseColorMap(m_currentColourMapFile, this);
+  return filename;
+}
+
+/**
+ * Updates the contour plot
+ */
+void ContourPreviewPlot::updateDisplay() {
+  if (m_workspace) {
+    m_data->setRange(m_uiForm.colourBar->getViewRange());
+
+    std::vector<Mantid::coord_t> slicePoint{0, 0};
+    constexpr std::size_t dimX(0);
+    constexpr std::size_t dimY(1);
+    IMDDimension_const_sptr X = m_dimensions[dimX];
+    IMDDimension_const_sptr Y = m_dimensions[dimY];
+    m_data->setSliceParams(dimX, dimY, X, Y, slicePoint);
+
+    double const left{X->getMinimum()};
+    double const top{Y->getMinimum()};
+    double const width{X->getMaximum() - X->getMinimum()};
+    double const height{Y->getMaximum() - Y->getMinimum()};
+    QwtDoubleRect const bounds{left, top, width, height};
+    m_data->setBoundingRect(bounds.normalized());
+
+    m_spectrogram->setColorMap(m_uiForm.colourBar->getColorMap());
+    m_spectrogram->setData(*m_data);
+    m_spectrogram->itemChanged();
+    m_uiForm.plot2D->replot();
+  }
+}
+
+/**
+ * Verify the limits of the stored workspace
+ */
+void ContourPreviewPlot::checkRangeLimits() const {
+  std::ostringstream message;
+  for (auto i = 0u; i < m_workspace->getNumDims(); ++i)
+    checkForInfiniteLimits(dimensionRange(i), i, message);
+
+  if (!message.str().empty()) {
+    message << "Bad ranges could cause memory allocation errors. Please fix "
+               "the workspace.";
+    throw std::out_of_range(message.str());
+  }
+}
+
+/**
+ * Produces an error message if either of the limits are infinite
+ */
+void ContourPreviewPlot::checkForInfiniteLimits(
+    DimensionRange const &range, std::size_t const &index,
+    std::ostringstream &message) const {
+  if (!std::isfinite(range.first) || !std::isfinite(range.second))
+    message << "Dimension " << m_workspace->getDimension(index)->getName()
+            << " has a bad range: (" << range.first << ", " << range.second
+            << ")\n";
+}
+
+DimensionRange
+ContourPreviewPlot::dimensionRange(std::size_t const &index) const {
+  DimensionRange range =
+      std::make_pair(dimensionMinimum(index), dimensionMaximum(index));
+  if (range.second < range.first)
+    range.swap(range);
+  return range;
+}
+
+Mantid::coord_t
+ContourPreviewPlot::dimensionMinimum(std::size_t const &index) const {
+  return m_workspace->getDimension(index)->getMinimum();
+}
+
+Mantid::coord_t
+ContourPreviewPlot::dimensionMaximum(std::size_t const &index) const {
+  return m_workspace->getDimension(index)->getMaximum();
+}
+
+/**
+ * Finds the full range of values in the workspace
+ */
+void ContourPreviewPlot::findFullRange() {
+  if (m_workspace) {
+    auto const workspace = m_workspace;
+    Mantid::Kernel::ReadLock lock(*workspace);
+
+    m_colourRangeFull =
+        API::SignalRange(*workspace, m_normalization).interval();
+    double minimum = m_colourRangeFull.minValue();
+
+    if (minimum <= 0 && m_uiForm.colourBar->getScale() == 1) {
+      double const maximum = m_colourRangeFull.maxValue();
+      minimum = pow(10., log10(maximum) - 10.);
+      m_colourRangeFull = QwtDoubleInterval(minimum, maximum);
+    }
+  }
+}
+
+/**
+ * Update m_dimensions using the loaded workspace
+ */
+void ContourPreviewPlot::setVectorDimensions() {
+  if (m_workspace) {
+    m_dimensions.clear();
+
+    for (auto i = 0u; i < m_workspace->getNumDims(); ++i) {
+      MDHistoDimension_sptr dimension(std::make_unique<MDHistoDimension>(
+          m_workspace->getDimension(i).get()));
+      m_dimensions.push_back(dimension);
+    }
+  }
+}
+
+} // namespace MantidWidgets
+} // namespace MantidQt
diff --git a/qt/widgets/legacyqwt/test/ContourPreviewPlotTest.h b/qt/widgets/legacyqwt/test/ContourPreviewPlotTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..146ecb2b8a19e8fa40c6e377a35030fe3bd951db
--- /dev/null
+++ b/qt/widgets/legacyqwt/test/ContourPreviewPlotTest.h
@@ -0,0 +1,99 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTIDQT_API_MANTIDCONTOURPREVIEWPLOTTEST_H_
+#define MANTIDQT_API_MANTIDCONTOURPREVIEWPLOTTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidQtWidgets/LegacyQwt/ContourPreviewPlot.h"
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+
+using namespace Mantid::API;
+using namespace MantidQt::MantidWidgets;
+
+namespace {
+
+MatrixWorkspace_sptr createMatrixWorkspace(int numberOfHistograms,
+                                           int numberOfBoundaries = 4) {
+  return WorkspaceCreationHelper::create2DWorkspace(numberOfHistograms,
+                                                    numberOfBoundaries);
+}
+
+} // namespace
+
+/// This QApplication object is required in order to construct the widget
+class QApplicationHolder : CxxTest::GlobalFixture {
+public:
+  bool setUpWorld() override {
+    int argc(0);
+    char **argv = {};
+    m_app = new QApplication(argc, argv);
+    return true;
+  }
+
+  bool tearDownWorld() override {
+    delete m_app;
+    return true;
+  }
+
+private:
+  QApplication *m_app;
+};
+
+static QApplicationHolder MAIN_QAPPLICATION;
+
+/// Unit tests for ContourPreviewPlot
+class ContourPreviewPlotTest : public CxxTest::TestSuite {
+public:
+  static ContourPreviewPlotTest *createSuite() {
+    return new ContourPreviewPlotTest();
+  }
+
+  static void destroySuite(ContourPreviewPlotTest *suite) { delete suite; }
+
+  void setUp() override {
+    m_contourPlot = std::make_unique<ContourPreviewPlot>();
+  }
+
+  void tearDown() override { m_contourPlot.reset(); }
+
+  void
+  test_that_a_ContourPreviewPlot_is_instantiated_without_an_active_workspace() {
+    TS_ASSERT(!m_contourPlot->getActiveWorkspace());
+  }
+
+  void test_that_getPlot2D_will_get_the_contour_plot() {
+    TS_ASSERT(m_contourPlot->getPlot2D());
+  }
+
+  void
+  test_that_setWorkspace_will_set_the_active_workspace_for_the_contour_plot() {
+    auto const workspace = createMatrixWorkspace(3);
+
+    m_contourPlot->setWorkspace(workspace);
+
+    TS_ASSERT(m_contourPlot->getActiveWorkspace());
+    TS_ASSERT_EQUALS(m_contourPlot->getActiveWorkspace(), workspace);
+  }
+
+  void test_that_setPlotVisible_will_hide_the_plot_when_it_is_passed_false() {
+    m_contourPlot->setPlotVisible(false);
+    TS_ASSERT(!m_contourPlot->isPlotVisible());
+  }
+
+  void
+  test_that_setColourBarVisible_will_hide_the_colour_bar_when_it_is_passed_false() {
+    m_contourPlot->setColourBarVisible(false);
+    TS_ASSERT(!m_contourPlot->isColourBarVisible());
+  }
+
+private:
+  std::unique_ptr<ContourPreviewPlot> m_contourPlot;
+};
+
+#endif /* MANTIDQT_API_MANTIDCONTOURPREVIEWPLOTTEST_H_ */
diff --git a/qt/widgets/mplcpp/CMakeLists.txt b/qt/widgets/mplcpp/CMakeLists.txt
index cdb9b1342b330a86c60049022058dab83e796777..e41695db207a828d652ac474fa2246e5979bfabc 100644
--- a/qt/widgets/mplcpp/CMakeLists.txt
+++ b/qt/widgets/mplcpp/CMakeLists.txt
@@ -67,6 +67,7 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsMplCpp
     PythonInterfaceCore
   INSTALL_DIR
     ${LIB_DIR}
+    ${WORKBENCH_LIB_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../MacOS
     @loader_path/../Libraries
diff --git a/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt b/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
index 5b69ac8272225245335b8d5dcfe963cfd04a6635..88184b8500280caa1688dc1afee4484b2c5e27fc 100644
--- a/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
+++ b/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
@@ -55,6 +55,7 @@ set ( UI_FILES inc/MantidQtWidgets/Plugins/AlgorithmDialogs/CatalogPublishDialog
 include_directories ( inc inc/MantidQtWidgets/Plugins/AlgorithmDialogs )
 
 mtd_add_qt_library (TARGET_NAME MantidQtWidgetsPluginsAlgorithmDialogs
+  QT_VERSION 4
   SRC ${SRC_FILES}
   MOC ${MOC_FILES}
   NOMOC ${INC_FILES}
@@ -70,6 +71,31 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsPluginsAlgorithmDialogs
     ${OPENGL_LIBRARIES}
   QT4_LINK_LIBS
     Qt4::QtOpenGL
+  MTD_QT_LINK_LIBS
+    MantidQtWidgetsCommon
+  OSX_INSTALL_RPATH
+    @loader_path/../../Contents/MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
+  INSTALL_DIR_BASE
+    ${PLUGINS_DIR}
+)
+
+mtd_add_qt_library (TARGET_NAME MantidQtWidgetsPluginsAlgorithmDialogs
+  QT_VERSION 5
+  SRC ${SRC_FILES}
+  MOC ${MOC_FILES}
+  NOMOC ${INC_FILES}
+  UI ${UI_FILES}
+  PRECOMPILED
+    inc/MantidQtWidgets/Plugins/AlgorithmDialogs/PrecompiledHeader.h
+  LINK_LIBS
+    ${TCMALLOC_LIBRARIES_LINKTIME}
+    ${CORE_MANTIDLIBS}
+    ${POCO_LIBRARIES}
+    ${Boost_LIBRARIES}
+    ${QT_LIBRARIES}
+    ${OPENGL_LIBRARIES}
   QT5_LINK_LIBS
     Qt5::OpenGL
   MTD_QT_LINK_LIBS
@@ -79,5 +105,5 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsPluginsAlgorithmDialogs
   LINUX_INSTALL_RPATH
     "\$ORIGIN/../../${LIB_DIR}"
   INSTALL_DIR_BASE
-    ${PLUGINS_DIR}
+    ${WORKBENCH_PLUGINS_DIR}
 )
diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt
index cffa4a96bef44543ff077c30171f0aa57c5cc882..0e69b4a00542613f36b9549c7d7966b21f65efce 100644
--- a/scripts/CMakeLists.txt
+++ b/scripts/CMakeLists.txt
@@ -69,9 +69,13 @@ add_dependencies ( PythonInterface ScriptsDotPth )
 set ( _scripts_pth_install "${CMAKE_CURRENT_BINARY_DIR}/mantid-scripts.pth.install" )
 string ( REPLACE ";" "\n" _pth_list_install "${_pth_list_install}" )
 file ( WRITE ${_scripts_pth_install} "${_pth_list_install}\n" )
+
 install ( FILES ${_scripts_pth_install} DESTINATION ${BIN_DIR}
   RENAME mantid-scripts.pth
 )
+install ( FILES ${_scripts_pth_install} DESTINATION ${WORKBENCH_BIN_DIR}
+  RENAME mantid-scripts.pth
+)
 
 # --------------------------------------------------------------------
 # Testing
@@ -132,6 +136,7 @@ set ( TEST_PYQT4_FILES
 # Additional tests
 add_subdirectory(test/directtools)
 add_subdirectory(test/isis_powder)
+add_subdirectory(test/MultiPlotting)
 add_subdirectory(test/Muon)
 add_subdirectory(test/SANS)
 add_subdirectory(test/TOFTOF)
diff --git a/scripts/wish/SX_reduction_august2017.py b/scripts/Diffraction/wish/SX_reduction_august2017.py
similarity index 100%
rename from scripts/wish/SX_reduction_august2017.py
rename to scripts/Diffraction/wish/SX_reduction_august2017.py
diff --git a/scripts/Diffraction/wish/__init__.py b/scripts/Diffraction/wish/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Diffraction/wish/reduce.py b/scripts/Diffraction/wish/reduce.py
new file mode 100644
index 0000000000000000000000000000000000000000..696cc8de7ebdd26f5b65b879fd9a2382db9d466b
--- /dev/null
+++ b/scripts/Diffraction/wish/reduce.py
@@ -0,0 +1,468 @@
+from __future__ import (absolute_import, division, print_function)
+from mantid import logger
+import mantid.simpleapi as simple
+import os
+import numpy as np
+
+
+class Wish:
+    NUM_PANELS = 11
+    NUM_MONITORS = 6
+    LAMBDA_RANGE = (0.7, 10.35)
+
+    def __init__(self, cal_directory, output_folder, delete_ws,
+                 user_directory="/archive/ndxwish/Instrument/data/cycle_", absorb=True):
+        self.absorb = absorb
+        self.cal_dir = cal_directory
+        self.use_folder = user_directory
+        self.out_folder = output_folder
+        self.deleteWorkspace = delete_ws
+        self.is_vanadium = False
+        self.username = None
+        self.data_directory = None
+        self.user_directory = None
+        self.datafile = None
+        self.userdatadir = None
+        self.userdataprocessed = None
+        self.return_panel = {
+            1: (6, 19461),
+            2: (19462, 38917),
+            3: (38918, 58373),
+            4: (58374, 77829),
+            5: (77830, 97285),
+            6: (97286, 116741),
+            7: (116742, 136197),
+            8: (136198, 155653),
+            9: (155654, 175109),
+            10: (175110, 194565),
+            0: (6, 194565)
+        }
+        self.return_panel_van = {
+            1: (6, 19461),
+            2: (19462, 38917),
+            3: (38918, 58373),
+            4: (58374, 77829),
+            5: (77830, 97285),
+            6: (77830, 97285),
+            7: (58374, 77829),
+            8: (38918, 58373),
+            9: (19462, 38917),
+            10: (6, 19461)
+        }
+        # update to reflect new cycles
+        self.cycle_mapping = [("09_2", (586, 774)), ("09_3", (774, 1728)), ("09_4", (1728, 3334)),
+                              ("09_5", (3334, 16777)), ("10_1", (16777, 17322)), ("10_2", (17322, 17339)),
+                              ("10_3", (17340, 17692)), ("11_1", (17692, 18051)), ("11_2", (18053, 18572)),
+                              ("11_3", (18572, 19608)), ("11_4", (19608, 20420)), ("11_5", (20420, 21111)),
+                              ("12_1", (21117, 21919)), ("12_2", (21919, 22437)), ("12_3", (22438, 23128)),
+                              ("12_4", (23128, 23548)), ("12_5", (23548, 24086)), ("13_1", (24086, 24819)),
+                              ("13_2", (24820, 25317)), ("13_3", (25318, 26148)), ("13_4", (26149, 26661)),
+                              ("13_5", (26661, 27805)), ("14_1", (27806, 28904)), ("14_2", (28904, 29587)),
+                              ("14_3", (29587, 30528)), ("15_1", (30528, 31669)), ("15_2", (31669, 32520)),
+                              ("15_3", (32520, 33278)), ("15_4", (33278, 34171)), ("16_1", (34171, 35222)),
+                              ("16_2", (35222, 35830)), ("16_3", (35830, 36442)), ("16_4", (36442, 37035)),
+                              ("16_5", (37035, 38247)), ("17_1", (38247, 38837)), ("17_2", (38837, 39326)),
+                              ("17_4", (39326, 40469)), ("18_1", (40469, 41846)), ("18_2", (41846, 42708)),
+                              ("18_3", (42708, 43124))]
+
+    def set_user_name(self, username):
+        self.username = username
+
+    def set_data_directory(self, directory="/archive/ndxwish/Instrument/data/cycle_09_5/"):
+        self.data_directory = directory
+
+    def set_user_directory(self, directory):
+        self.user_directory = directory
+
+    def startup(self, cycle='14_3'):
+        user_data_directory = os.path.normpath(self.use_folder + cycle)
+        self.set_data_directory(user_data_directory)
+        logger.notice("Raw Data in :   {}".format(user_data_directory))
+        user_data_processed = self.out_folder
+        self.set_user_directory(directory=user_data_processed)
+        logger.notice("Processed Data in :   {}".format(user_data_processed))
+
+    def get_cycle(self, run_number):
+        for cycle_pair in self.cycle_mapping:
+            if run_number in range(cycle_pair[1][0], cycle_pair[1][1]):
+                return cycle_pair[0]
+        logger.notice("Failed to find cycle")
+
+    # Returns the calibration filename
+    def get_cal(self):
+        return os.path.join(self.cal_dir, "WISH_cycle_15_4_noends_10to10_dodgytube_removed_feb2016.cal")
+
+    # Returns the grouping filename
+    def get_group_file(self):
+        return os.path.join(self.cal_dir, "WISH_cycle_15_4_noends_10to10_dodgytube_removed_feb2016.cal")
+
+    def get_vanadium(self, panel, cycle="09_4"):
+        vanadium_string = {
+            "09_2": "vana318-{}foc-rmbins-smooth50.nx5",
+            "09_3": "vana935-{}foc-SS.nx5",
+            "09_4": "vana3123-{}foc-SS.nx5",
+            "09_5": "vana3123-{}foc-SS.nx5",
+            "11_1": "vana17718-{}foc-SS.nxs",
+            "11_2": "vana16812-{}foc-SS.nx5",
+            "11_3": "vana18590-{}foc-SS-new.nxs",
+            "11_4": "vana19612-{}foc-SF-SS.nxs",
+        }
+        return os.path.join(self.cal_dir, vanadium_string.get(cycle).format(panel))
+
+    def get_file_name(self, run_number, extension):
+        if extension[0] != 's':
+            data_dir = self.data_directory
+        else:
+            data_dir = self.data_directory
+        digit = len(str(run_number))
+        filename = os.path.join(data_dir, "WISH")
+        for i in range(8 - digit):
+            filename = filename + "0"
+        filename += str(run_number) + "." + extension
+        return filename
+
+    # Reads a wish data file return a workspace with a short name
+    def read(self, number, panel, extension):
+        if type(number) is int:
+            filename = self.datafile
+            logger.notice("will be reading filename...{}".format(filename))
+            spectra_min, spectra_max = self.return_panel_van.get(panel) if self.is_vanadium else \
+                self.return_panel.get(panel)
+            if panel != 0:
+                output = "w{0}-{1}".format(number, panel)
+            else:
+                output = "w{}".format(number)
+            shared_load_files(extension, filename, output, spectra_max, spectra_min, False)
+            if extension == "nxs_event":
+                simple.LoadEventNexus(Filename=filename, OutputWorkspace=output, LoadMonitors='1')
+                self.read_event_nexus(number, output, panel)
+            if extension[:10] == "nxs_event_":
+                label, tmin, tmax = split_string_event(extension)
+                output = output + "_" + label
+                if tmax == "end":
+                    simple.LoadEventNexus(Filename=filename, OutputWorkspace=output, FilterByTimeStart=tmin,
+                                          LoadMonitors='1', MonitorsAsEvents='1', FilterMonByTimeStart=tmin)
+                else:
+                    simple.LoadEventNexus(Filename=filename, OutputWorkspace=output, FilterByTimeStart=tmin,
+                                          FilterByTimeStop=tmax, LoadMonitors='1', MonitorsAsEvents='1',
+                                          FilterMonByTimeStart=tmin, FilterMonByTimeStop=tmax)
+                self.read_event_nexus(number, output, panel)
+        else:
+            num_1, num_2 = split_run_string(number)
+            output = "w{0}_{1}-{2}".format(num_1, num_2, panel)
+            output1 = self.load_multi_run_part(extension, num_1, panel)
+            output2 = self.load_multi_run_part(extension, num_2, panel)
+            simple.MergeRuns(output1 + "," + output2, output)
+            simple.DeleteWorkspace(output1)
+            simple.DeleteWorkspace(output2)
+        simple.ConvertUnits(InputWorkspace=output, OutputWorkspace=output, Target="Wavelength", Emode="Elastic")
+        lmin, lmax = Wish.LAMBDA_RANGE
+        simple.CropWorkspace(InputWorkspace=output, OutputWorkspace=output, XMin=lmin, XMax=lmax)
+        monitor_run = "monitor{}".format(number)
+        if monitor_run not in simple.mtd:
+            monitor = self.process_incidentmon(number, extension, spline_terms=70)
+        else:
+            monitor = simple.mtd[monitor_run]
+        simple.NormaliseToMonitor(InputWorkspace=output, OutputWorkspace=output + "norm1", MonitorWorkspace=monitor)
+        simple.NormaliseToMonitor(InputWorkspace=output + "norm1", OutputWorkspace=output + "norm2",
+                                  MonitorWorkspace=monitor, IntegrationRangeMin=0.7, IntegrationRangeMax=10.35)
+        simple.DeleteWorkspace(output)
+        simple.DeleteWorkspace(output + "norm1")
+        simple.RenameWorkspace(InputWorkspace=output + "norm2", OutputWorkspace=output)
+        simple.ConvertUnits(InputWorkspace=output, OutputWorkspace=output, Target="TOF", EMode="Elastic")
+        simple.ReplaceSpecialValues(InputWorkspace=output, OutputWorkspace=output, NaNValue=0.0, NaNError=0.0,
+                                    InfinityValue=0.0, InfinityError=0.0)
+        return output
+
+    def load_multi_run_part(self, extension, run, panel):
+        filename = self.get_file_name(run, extension)
+        logger.notice("reading filename... {}".format(filename))
+        spectra_min, spectra_max = self.return_panel.get(panel)
+        output1 = "w{0}-{1}".format(run, panel)
+        simple.LoadRaw(Filename=filename, OutputWorkspace=output1, SpectrumMin=str(spectra_min),
+                       SpectrumMax=str(spectra_max), LoadLogFiles="0")
+        return output1
+
+    def read_event_nexus(self, number, output, panel):
+        simple.RenameWorkspace("{}_monitors".format(output), "w{}_monitors".format(number))
+        simple.Rebin(InputWorkspace=output, OutputWorkspace=output, Params='6000,-0.00063,110000')
+        simple.ConvertToMatrixWorkspace(output, output)
+        spectra_min, spectra_max = self.return_panel.get(panel)
+        simple.CropWorkspace(InputWorkspace=output, OutputWorkspace=output, StartWorkspaceIndex=spectra_min - 6,
+                             EndWorkspaceIndex=spectra_max - 6)
+        simple.MaskBins(InputWorkspace=output, OutputWorkspace=output, XMin=99900, XMax=106000)
+
+    # Focus dataset for a given panel and return the workspace
+    def focus_onepanel(self, work, focus, panel):
+        cal = "WISH_diff{}"
+        if cal.format("_cal") not in simple.mtd:
+            simple.LoadDiffCal(filename=self.get_cal(), InstrumentName="WISH", WorkspaceName=cal.format(""))
+        simple.AlignAndFocusPowder(InputWorkspace=work, OutputWorkspace=focus, GroupingWorkspace=cal.format("_group"),
+                                   CalibrationWorkspace=cal.format("_cal"), Dspacing=True, params="-0.00063")
+        simple.ConvertUnits(InputWorkspace=focus, OutputWorkspace=focus, Target="dSpacing")
+        if self.deleteWorkspace:
+            simple.DeleteWorkspace(work)
+        if panel == 5 or panel == 6:
+            simple.CropWorkspace(InputWorkspace=focus, OutputWorkspace=focus, XMin=0.3)
+        return focus
+
+    def focus(self, work, panel):
+        focus = "{}foc".format(work)
+        if panel != 0:
+            return self.focus_onepanel(work, focus, panel)
+        else:
+            self.focus_onepanel(work, focus, panel)
+            split(focus)
+
+    def process_run(self, number, panel, extension, cycle_vana="09_4", absorb=False, number_density=0.0, scattering=0.0,
+                    attenuation=0.0, height=0.0, radius=0.0):
+        ws_to_focus = self.read(number, panel, extension)
+        if absorb:
+            absorption_corrections(attenuation, height, number_density, radius, scattering, ws_to_focus)
+        focused_ws = self.focus(ws_to_focus, panel)
+
+        panel_crop = {
+            1: (0.8, 53.3),
+            2: (0.5, 13.1),
+            3: (0.5, 7.77),
+            4: (0.4, 5.86),
+            5: (0.35, 4.99),
+            6: (0.35, 4.99),
+            7: (0.4, 5.86),
+            8: (0.5, 7.77),
+            9: (0.5, 13.1),
+            10: (0.8, 53.3)
+        }
+        d_min, d_max = panel_crop.get(panel)
+        simple.CropWorkspace(InputWorkspace=focused_ws, OutputWorkspace=focused_ws, XMin=d_min, XMax=d_max)
+        save_location = os.path.join(self.user_directory, "{0}-{1}{2}.{3}")
+        if panel == 0:
+            for panel_i in range(Wish.NUM_PANELS):
+                focused_ws = "w{0}-{1}foc".format(number, panel_i)
+                simple.CropWorkspace(InputWorkspace=focused_ws, OutputWorkspace=focused_ws, XMin=d_min, XMax=d_max)
+                logger.notice("will try to load a vanadium with the name: {}".format(self.get_vanadium
+                                                                                     (panel_i, cycle_vana)))
+                self.apply_vanadium_corrections(cycle_vana, panel_i, focused_ws)
+                simple.SaveGSS(InputWorkspace=focused_ws,
+                               Filename=save_location.format(number, panel_i, extension, "gss"),
+                               Append=False, Bank=1)
+                simple.SaveFocusedXYE(focused_ws, save_location.format(number, panel_i, extension, "dat"))
+                simple.SaveNexusProcessed(focused_ws, save_location.format(number, panel_i, extension, "nxs"))
+        else:
+            logger.notice("will try to load a vanadium with the name: {}".format(self.get_vanadium
+                                                                                 (panel, cycle_vana)))
+            self.apply_vanadium_corrections(cycle_vana, panel, focused_ws)
+            simple.SaveGSS(InputWorkspace=focused_ws,
+                           Filename=save_location.format(number, panel, extension, "gss"),
+                           Append=False, Bank=1)
+            simple.SaveFocusedXYE(focused_ws, save_location.format(number, panel, extension, "dat"))
+            simple.SaveNexusProcessed(focused_ws, save_location.format(number, panel, extension, "nxs"))
+        return focused_ws
+
+    def apply_vanadium_corrections(self, cyclevana, i, focused_ws):
+        simple.LoadNexusProcessed(Filename=self.get_vanadium(i, cyclevana), OutputWorkspace="vana")
+        simple.RebinToWorkspace(WorkspaceToRebin="vana", WorkspaceToMatch=focused_ws, OutputWorkspace="vana")
+        simple.Divide(LHSWorkspace=focused_ws, RHSWorkspace="vana", OutputWorkspace=focused_ws)
+        simple.DeleteWorkspace("vana")
+        simple.ConvertUnits(InputWorkspace=focused_ws, OutputWorkspace=focused_ws, Target="TOF",
+                            EMode="Elastic")
+        simple.ReplaceSpecialValues(InputWorkspace=focused_ws, OutputWorkspace=focused_ws, NaNValue=0.0,
+                                    NaNError=0.0,
+                                    InfinityValue=0.0, InfinityError=0.0)
+
+    # Create a corrected vanadium (normalise,corrected for attenuation and empty, strip peaks) and
+    # save a a nexus processed file.
+    # It looks like smoothing of 100 works quite well
+    def process_vanadium(self, vanadium, empty, panel, height, radius, cycle_van="09_3", cycle_empty="09_3"):
+        user_data_directory = self.use_folder + cycle_van + '/'
+        self.set_data_directory(user_data_directory)
+        self.datafile = self.get_file_name(vanadium, "raw")
+        vanadium_ws = self.read(vanadium, panel, "raw")
+        user_data_directory = self.use_folder + cycle_empty + '/'
+        self.set_data_directory(user_data_directory)
+        self.datafile = self.get_file_name(empty, "raw")
+        empty_ws = self.read(empty, panel, "raw")
+        simple.Minus(LHSWorkspace=vanadium_ws, RHSWorkspace=empty_ws, OutputWorkspace=vanadium_ws)
+        simple.DeleteWorkspace(empty_ws)
+        absorption_corrections(4.8756, height, 0.07118, radius, 5.16, vanadium_ws)
+        vanfoc = self.focus(vanadium_ws, panel)
+
+        panel_crop = {
+            1: (0.95, 53.3),
+            2: (0.58, 13.1),
+            3: (0.44, 7.77),
+            4: (0.38, 5.86),
+            5: (0.35, 4.99),
+            6: (0.35, 4.99),
+            7: (0.38, 5.86),
+            8: (0.44, 7.77),
+            9: (0.58, 13.1),
+            10: (0.95, 53.3)
+        }
+        d_min, d_max = panel_crop.get(panel)
+        simple.CropWorkspace(InputWorkspace=vanfoc, OutputWorkspace=vanfoc, XMin=d_min, XMax=d_max)
+        spline_coefficient = {
+            1: 120,
+            2: 120,
+            3: 120,
+            4: 130,
+            5: 140,
+            6: 140,
+            7: 130,
+            8: 120,
+            9: 120,
+            10: 120
+        }
+        simple.SplineBackground(InputWorkspace=vanfoc, OutputWorkspace=vanfoc, NCoeff=spline_coefficient.get(panel))
+        smoothing_coefficient = "30" if panel == 3 else "40"
+        simple.SmoothData(InputWorkspace=vanfoc, OutputWorkspace=vanfoc, NPoints=smoothing_coefficient)
+        return
+
+    def create_vanadium_run(self, van_run_number, empty_run_number, panels):
+        self.is_vanadium = True
+        vanadium_cycle = self.get_cycle(van_run_number)
+        empty_cycle = self.get_cycle(empty_run_number)
+        for panel in panels:
+            self.process_vanadium(van_run_number, empty_run_number, panel, 4, 0.14999999999999999, vanadium_cycle,
+                                  empty_cycle)
+        monitor_runs = "monitor{}"
+        simple.DeleteWorkspace(monitor_runs.format(van_run_number))
+        simple.DeleteWorkspace(monitor_runs.format(empty_run_number))
+        simple.DeleteWorkspace("WISH_diff_cal")
+        simple.DeleteWorkspace("WISH_diff_group")
+        simple.DeleteWorkspace("WISH_diff_mask")
+
+    def process_incidentmon(self, number, extension, spline_terms=20):
+        if type(number) is int:
+            filename = self.get_file_name(number, extension)
+            works = "monitor{}".format(number)
+            shared_load_files(extension, filename, works, 4, 4, True)
+            if extension[:9] == "nxs_event":
+                temp = "w{}_monitors".format(number)
+                works = "w{}_monitor4".format(number)
+                simple.Rebin(InputWorkspace=temp, OutputWorkspace=temp, Params='6000,-0.00063,110000',
+                             PreserveEvents=False)
+                simple.ExtractSingleSpectrum(InputWorkspace=temp, OutputWorkspace=works, WorkspaceIndex=3)
+        else:
+            num_1, num_2 = split_run_string(number)
+            works = "monitor{0}_{1}".format(num_1, num_2)
+            filename = self.get_file_name(num_1, extension)
+            works1 = "monitor{}".format(num_1)
+            simple.LoadRaw(Filename=filename, OutputWorkspace=works1, SpectrumMin=4, SpectrumMax=4, LoadLogFiles="0")
+            filename = self.get_file_name(num_2, extension)
+            works2 = "monitor{}".format(num_2)
+            simple.LoadRaw(Filename=filename, OutputWorkspace=works2, SpectrumMin=4, SpectrumMax=4, LoadLogFiles="0")
+            simple.MergeRuns(InputWorkspaces=works1 + "," + works2, OutputWorkspace=works)
+            simple.DeleteWorkspace(works1)
+            simple.DeleteWorkspace(works2)
+        simple.ConvertUnits(InputWorkspace=works, OutputWorkspace=works, Target="Wavelength", Emode="Elastic")
+        lambda_min, lambda_max = Wish.LAMBDA_RANGE
+        simple.CropWorkspace(InputWorkspace=works, OutputWorkspace=works, XMin=lambda_min, XMax=lambda_max)
+        ex_regions = np.array([[4.57, 4.76],
+                              [3.87, 4.12],
+                              [2.75, 2.91],
+                              [2.24, 2.50]])
+        simple.ConvertToDistribution(works)
+
+        for reg in range(0, 4):
+            simple.MaskBins(InputWorkspace=works, OutputWorkspace=works, XMin=ex_regions[reg, 0],
+                            XMax=ex_regions[reg, 1])
+
+        simple.SplineBackground(InputWorkspace=works, OutputWorkspace=works, WorkspaceIndex=0, NCoeff=spline_terms)
+
+        simple.SmoothData(InputWorkspace=works, OutputWorkspace=works, NPoints=40)
+        simple.ConvertFromDistribution(works)
+        return works
+
+    def reduce(self, run_numbers, panels):
+        self.is_vanadium = False
+        for run in run_numbers:
+            self.startup(self.get_cycle(run))
+            self.datafile = self.get_file_name(run, "raw")
+            for panel in panels:
+                wout = self.process_run(run, panel, "raw", "11_4", absorb=self.absorb, number_density=0.025,
+                                        scattering=5.463, attenuation=2.595, height=4.0, radius=0.55)
+                simple.ConvertUnits(InputWorkspace=wout, OutputWorkspace="{}-d".format(wout), Target="dSpacing",
+                                    EMode="Elastic")
+
+            simple.DeleteWorkspace("WISH_diff_cal")
+            simple.DeleteWorkspace("WISH_diff_group")
+            simple.DeleteWorkspace("WISH_diff_mask")
+            simple.DeleteWorkspace("monitor{}".format(run))
+            if 0 in panels:
+                panels = [1, 2, 3, 4, 5]
+            for panel in [x for x in panels if x < 6]:
+                self.save_combined_panel(run, panel)
+
+    def save_combined_panel(self, run, panel):
+        panel_combination = {
+            5: 6,
+            4: 7,
+            3: 8,
+            2: 9,
+            1: 10
+        }
+        input_ws1 = "w{0}-{1}foc".format(run, panel)
+        input_ws2 = "w{0}-{1}foc".format(run, panel_combination.get(panel))
+        combined = "{0}{1}-{2}_{3}foc{4}".format("{0}", run, panel, panel_combination.get(panel), "{1}")
+        combined_save = combined.format("", "{}")
+        combined_ws = combined.format("w", "")
+
+        simple.RebinToWorkspace(WorkspaceToRebin=input_ws2, WorkspaceToMatch=input_ws1, OutputWorkspace=input_ws2,
+                                PreserveEvents='0')
+        simple.Plus(LHSWorkspace=input_ws1, RHSWorkspace=input_ws2, OutputWorkspace=combined_ws)
+        simple.ConvertUnits(InputWorkspace=combined_ws, OutputWorkspace=combined_ws + "-d", Target="dSpacing",
+                            EMode="Elastic")
+
+        simple.SaveGSS(combined_ws, os.path.join(self.user_directory, combined_save.format("raw.gss")), Append=False,
+                       Bank=1)
+        simple.SaveFocusedXYE(combined_ws, os.path.join(self.user_directory, combined_save.format("raw.dat")))
+        simple.SaveNexusProcessed(combined_ws, os.path.join(self.user_directory, combined_save.format("raw.nxs")))
+
+
+def absorption_corrections(attenuation, height, number_density, radius, scattering, input_ws):
+    simple.ConvertUnits(InputWorkspace=input_ws, OutputWorkspace=input_ws, Target="Wavelength", EMode="Elastic")
+    simple.CylinderAbsorption(InputWorkspace=input_ws, OutputWorkspace="absorptionWS",
+                              CylinderSampleHeight=height, CylinderSampleRadius=radius,
+                              AttenuationXSection=attenuation, ScatteringXSection=scattering,
+                              SampleNumberDensity=number_density, NumberOfSlices="10", NumberOfAnnuli="10",
+                              NumberOfWavelengthPoints="25", ExpMethod="Normal")
+    simple.Divide(LHSWorkspace=input_ws, RHSWorkspace="absorptionWS", OutputWorkspace=input_ws)
+    simple.DeleteWorkspace("absorptionWS")
+    simple.ConvertUnits(InputWorkspace=input_ws, OutputWorkspace=input_ws, Target="TOF", EMode="Elastic")
+
+
+def split(focus):
+    for i in range(Wish.NUM_PANELS):
+        out = "{0}-{1}foc".format(focus[0:len(focus) - 3], i + 1)
+        simple.ExtractSingleSpectrum(InputWorkspace=focus, OutputWorkspace=out, WorkspaceIndex=i)
+        simple.DeleteWorkspace(focus)
+
+
+def split_run_string(string):
+    split_list = string.split("+")
+    if split_list[0]:
+        return int(split_list[0]), int(split_list[1])
+
+
+def split_string_event(input_string):
+    # this assumes the form nxs_event_label_tmin_tmax
+    section = input_string.split('_')
+    label = section[2]
+    t_min = section[3]
+    t_max = section[4]
+    return label, t_min, t_max
+
+
+def shared_load_files(extension, filename, ws, spectrum_max, spectrum_min, is_monitor):
+    if not (extension == "nxs" or extension == "raw" or extension[0] == "s"):
+        return False
+    if extension == "nxs":
+        simple.Load(Filename=filename, OutputWorkspace=ws, SpectrumMin=spectrum_min, SpectrumMax=spectrum_min)
+    else:
+        simple.LoadRaw(Filename=filename, OutputWorkspace=ws, SpectrumMin=spectrum_min, SpectrumMax=spectrum_max,
+                       LoadLogFiles="0")
+    simple.Rebin(InputWorkspace=ws, OutputWorkspace=ws, Params='6000,-0.00063,110000')
+    if not is_monitor:
+        simple.MaskBins(InputWorkspace=ws, OutputWorkspace=ws, XMin=99900, XMax=106000)
+    return True
diff --git a/scripts/ErrorReporter/error_dialog_app.py b/scripts/ErrorReporter/error_dialog_app.py
index 7882af1ac0398f29b7024730b0fee91d98b07b69..d2ac6f5f0428750b800c9c3c18bfe8ce2edfd807 100644
--- a/scripts/ErrorReporter/error_dialog_app.py
+++ b/scripts/ErrorReporter/error_dialog_app.py
@@ -4,6 +4,8 @@
 #     NScD Oak Ridge National Laboratory, European Spallation Source
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, print_function)
+
 import sys
 import argparse
 
@@ -16,15 +18,18 @@ command_line_args = parser.parse_args()
 
 sys.path.insert(0, command_line_args.directory)
 
-from PyQt4 import QtGui # noqa
+from qtpy import QtGui, QtWidgets # noqa
 
 import mantid # noqa
-from ErrorReporter import resources # noqa
+try:
+    from ErrorReporter import resources_qt5 # noqa
+except ImportError:
+    from ErrorReporter import resources_qt4 # noqa
 from ErrorReporter.error_report_presenter import ErrorReporterPresenter # noqa
 from ErrorReporter.errorreport import CrashReportPage # noqa
 # Set path to look for package qt libraries
 if command_line_args.qtdir is not None:
-    from PyQt4.QtCore import QCoreApplication
+    from qtpy.QtCore import QCoreApplication
     QCoreApplication.addLibraryPath(
         command_line_args.qtdir
     )
@@ -33,7 +38,7 @@ if command_line_args.qtdir is not None:
 def main():
     if mantid.config['usagereports.enabled'] != '1':
         return int(command_line_args.exit_code)
-    app = QtGui.QApplication(sys.argv)
+    app = QtWidgets.QApplication(sys.argv)
     form = CrashReportPage(show_continue_terminate=False)
     presenter = ErrorReporterPresenter(form, command_line_args.exit_code)
     presenter.show_view()
diff --git a/scripts/ErrorReporter/error_report_presenter.py b/scripts/ErrorReporter/error_report_presenter.py
index 02b9dcfb5a170eb5825994bedf064d5f0bb0ef1a..8902842fedef79f29a8c6efdb999ffa1fa378ff3 100644
--- a/scripts/ErrorReporter/error_report_presenter.py
+++ b/scripts/ErrorReporter/error_report_presenter.py
@@ -4,9 +4,13 @@
 #     NScD Oak Ridge National Laboratory, European Spallation Source
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, print_function)
+
+import os
+
 from mantid.kernel import ErrorReporter, UsageService, ConfigService
 from mantid.kernel import Logger
-from ErrorReporter.retrieve_recovery_files import zip_recovery_directory, remove_recovery_file
+from ErrorReporter.retrieve_recovery_files import zip_recovery_directory
 import requests
 
 
@@ -31,13 +35,21 @@ class ErrorReporterPresenter(object):
 
     def share_all_information(self, continue_working, name, email, text_box):
         uptime = UsageService.getUpTime()
-        zip_recovery_file, file_hash = zip_recovery_directory()
-        status = self._send_report_to_server(share_identifiable=True, uptime=uptime, name=name, email=email, file_hash=file_hash
-                                             , text_box=text_box)
-        self.error_log.notice("Sent complete information")
-        if status == 201:
-            self._upload_recovery_file(zip_recovery_file=zip_recovery_file)
-        remove_recovery_file(zip_recovery_file)
+        try:
+            recovery_archive, file_hash = zip_recovery_directory()
+        except Exception as exc:
+            self.error_log.information("Error creating recovery archive: {}. No recovery information will be sent")
+            recovery_archive, file_hash = None, ""
+        status = self._send_report_to_server(share_identifiable=True, uptime=uptime, name=name, email=email, file_hash=file_hash,
+                                             text_box=text_box)
+        self.error_log.notice("Sent full information")
+        if status == 201 and recovery_archive:
+            self._upload_recovery_file(recovery_archive=recovery_archive)
+            try:
+                os.remove(recovery_archive)
+            except OSError as exc:
+                self.error_log.information("Unable to remove zipped recovery information: {}".format(str(exc)))
+
         self._handle_exit(continue_working)
         return status
 
@@ -62,13 +74,13 @@ class ErrorReporterPresenter(object):
         else:
             self.error_log.error("Continue working.")
 
-    def _upload_recovery_file(self, zip_recovery_file):
+    def _upload_recovery_file(self, recovery_archive):
         url = ConfigService['errorreports.rooturl']
         url = '{}/api/recovery'.format(url)
-        files = {'file': open('{}.zip'.format(zip_recovery_file), 'rb')}
+        files = {'file': open('{}'.format(recovery_archive), 'rb')}
         response = requests.post(url, files=files)
         if response.status_code == 201:
-            self.error_log.notice("Uploaded recovery file to server HTTP response {}".format(response.status_code))
+            self.error_log.notice("Uploaded recovery file to server. HTTP response {}".format(response.status_code))
         else:
             self.error_log.error("Failed to send recovery data HTTP response {}".format(response.status_code))
 
diff --git a/scripts/ErrorReporter/errorreport.py b/scripts/ErrorReporter/errorreport.py
index d8be56ee0296e4a441fd4cf7b2f1077d358c8908..039d87965538400653a21b6851a6f85bb4506f8b 100644
--- a/scripts/ErrorReporter/errorreport.py
+++ b/scripts/ErrorReporter/errorreport.py
@@ -4,15 +4,24 @@
 #     NScD Oak Ridge National Laboratory, European Spallation Source
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
-from PyQt4 import QtGui, QtCore
-import ui_errorreport
-from PyQt4.QtCore import pyqtSignal
-from mantidqtpython import MantidQt
+from __future__ import (absolute_import, print_function)
 
+from qtpy import QtGui, QtCore, QtWidgets
+from qtpy.QtCore import Signal
 
-class CrashReportPage(QtGui.QWidget, ui_errorreport.Ui_Errorreport):
-    action = pyqtSignal(bool, int, str, str, str)
-    quit_signal = pyqtSignal()
+try:
+    from ErrorReporter import resources_qt5 # noqa
+except (ImportError, RuntimeError):
+    from ErrorReporter import resources_qt4 # noqa
+
+from mantidqt.utils.qt import load_ui
+
+ErrorReportUI, ErrorReportUIBase = load_ui(__file__, 'errorreport.ui')
+
+
+class CrashReportPage(QtWidgets.QWidget, ErrorReportUI):
+    action = Signal(bool, int, str, str, str)
+    quit_signal = Signal()
 
     def __init__(self, parent=None, show_continue_terminate=False):
         super(self.__class__, self).__init__(parent)
@@ -23,11 +32,11 @@ class CrashReportPage(QtGui.QWidget, ui_errorreport.Ui_Errorreport):
             self.adjustSize()
         self.setFixedSize(self.width(), self.height())
 
-        self.quit_signal.connect(QtGui.QApplication.instance().quit)
+        self.quit_signal.connect(QtWidgets.QApplication.instance().quit)
 
         self.icon.setPixmap(QtGui.QPixmap(":/crying_mantid.png"))
 
-        self.requestTextBrowser.anchorClicked.connect(MantidQt.API.MantidDesktopServices.openUrl)
+        self.requestTextBrowser.anchorClicked.connect(QtGui.QDesktopServices.openUrl)
 
         self.input_name_line_edit.textChanged.connect(self.set_button_status)
         self.input_email_line_edit.textChanged.connect(self.set_button_status)
diff --git a/scripts/ErrorReporter/resources.py b/scripts/ErrorReporter/resources_qt4.py
similarity index 100%
rename from scripts/ErrorReporter/resources.py
rename to scripts/ErrorReporter/resources_qt4.py
diff --git a/scripts/ErrorReporter/resources_qt5.py b/scripts/ErrorReporter/resources_qt5.py
new file mode 100644
index 0000000000000000000000000000000000000000..05a87cb59235cd3b539b458edc05df3a38054e1e
--- /dev/null
+++ b/scripts/ErrorReporter/resources_qt5.py
@@ -0,0 +1,3579 @@
+# -*- coding: utf-8 -*-
+
+# Resource object code
+#
+# Created by: The Resource Compiler for PyQt5 (Qt v5.9.1)
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\xdc\x69\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\xc8\x00\x00\x00\xfe\x08\x06\x00\x00\x00\x7f\x7b\x4a\x2e\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\
+\x9c\x18\x00\x00\x01\x59\x69\x54\x58\x74\x58\x4d\x4c\x3a\x63\x6f\
+\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\x00\x00\x00\
+\x3c\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\
+\x3a\x78\x3d\x22\x61\x64\x6f\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\
+\x61\x2f\x22\x20\x78\x3a\x78\x6d\x70\x74\x6b\x3d\x22\x58\x4d\x50\
+\x20\x43\x6f\x72\x65\x20\x35\x2e\x34\x2e\x30\x22\x3e\x0a\x20\x20\
+\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\x78\x6d\x6c\x6e\x73\x3a\
+\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
+\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\
+\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\
+\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x3c\x72\x64\x66\x3a\x44\x65\
+\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\
+\x6f\x75\x74\x3d\x22\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
+\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\
+\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\
+\x6f\x6d\x2f\x74\x69\x66\x66\x2f\x31\x2e\x30\x2f\x22\x3e\x0a\x20\
+\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x69\x66\x66\x3a\x4f\x72\
+\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3e\x31\x3c\x2f\x74\x69\x66\
+\x66\x3a\x4f\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3e\x0a\x20\
+\x20\x20\x20\x20\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\
+\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x3c\x2f\x72\x64\x66\
+\x3a\x52\x44\x46\x3e\x0a\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\
+\x61\x3e\x0a\x4c\xc2\x27\x59\x00\x00\x40\x00\x49\x44\x41\x54\x78\
+\x01\xec\xbd\x07\x80\x5d\x57\x75\x2e\xbc\x4e\xb9\x65\x7a\x95\x66\
+\x34\xea\x5d\xb2\x7a\x71\xc1\xc6\x46\x18\x53\x0c\xa1\x04\x30\x49\
+\x80\x60\x8c\x8d\x43\x48\x48\x42\x7e\x1e\x8f\x47\x92\x87\xc8\x7b\
+\xf0\x13\x08\x84\xd0\x9b\x6d\xec\x18\x0c\x36\xfe\x29\x06\x03\xc6\
+\xc6\x72\x95\x6d\x55\xab\xda\xea\x5d\x1a\xcd\x8c\xa6\xcf\x9d\x7b\
+\xef\x29\xff\xf7\xad\x7d\xce\xbd\x77\x46\x12\x36\x8e\xda\xc8\xda\
+\x33\xe7\x9e\x7d\xf6\xd9\x7b\x9f\x5d\xd6\xb7\xd7\x5a\xbb\x8a\x5c\
+\x30\x17\x4a\xe0\x42\x09\x5c\x28\x81\x0b\x25\x70\xa1\x04\x2e\x94\
+\xc0\x85\x12\xb8\x50\x02\xa7\xb8\x04\xac\x53\x1c\xdf\xc8\x8e\x2e\
+\x14\x4b\x3e\x8d\xab\xd4\x2c\x97\x10\x8f\xbc\xce\x7f\xc3\xfc\xbf\
+\x4b\x6c\x91\xeb\x86\xe6\x75\xce\x3d\xa1\xbc\x9c\xca\xa1\x24\xf7\
+\x43\x89\xa1\xe4\xc5\xcb\xc4\x6a\xc9\xf2\x65\x8e\x1c\xea\xb5\xa4\
+\x73\x4a\x20\xf7\xdc\xe3\x9f\x30\xdf\xd7\x5d\xe7\x48\xdd\x2e\x5b\
+\xb6\x55\x85\xb2\x62\x05\xfd\x9c\x3f\x80\x89\xf3\xf6\x87\xf2\x1f\
+\x17\xca\xf2\x65\xae\x96\x55\xcb\x1a\x1f\x80\x09\x62\xe7\xf3\xf9\
+\xfe\xf2\x04\xc8\x72\xb4\x92\x87\x96\x38\xf2\x9d\x35\xf9\x97\x50\
+\xb9\x96\xdc\xbc\xc4\x95\x91\x4e\x24\xcb\x40\xec\xcb\x90\xfb\xe5\
+\x2b\xbc\x61\x65\x60\xc9\x5f\xce\x2f\xaf\x75\xec\x04\xdd\xbb\xfa\
+\xcb\xb2\x72\xcf\xca\xcc\x30\x3f\x82\x32\x48\xfc\xc1\x46\xe5\xb8\
+\x00\x23\xd3\xe1\xe5\x05\x10\x02\x43\x96\xd9\xa5\x44\x51\x7f\xd3\
+\xfc\x71\x76\xe8\x2e\x0a\x2d\x99\x17\x4a\x38\x39\x0c\x65\x34\xec\
+\xe5\xa8\xce\xd0\xb2\xac\x7e\x14\x50\x2b\xdc\x76\x5a\xa1\x6c\x74\
+\xad\xdc\xba\xa3\xdf\xdb\xd4\x5a\xa8\x6a\xb6\xa8\xcb\x47\x18\x47\
+\x21\xc7\xa0\x89\xb8\xe5\xa4\xf7\x4f\x4a\x77\x3b\xb5\x4b\x2c\xb1\
+\x2f\x81\x80\x35\x0f\x6c\x61\xa2\x1f\x84\x0d\x81\x58\x69\x14\x41\
+\xe8\x58\xd2\x8f\x00\x6d\xb6\x58\xbb\xc2\x30\x58\x1f\xba\xce\x53\
+\x9d\xdf\x59\xb3\xb1\x50\x06\x0a\xb4\x15\xc1\xf9\xca\x51\x5e\x3e\
+\x00\x61\x45\xae\x88\x5a\x4b\xb4\x7e\xf5\x41\xf0\x56\x20\xe0\x5d\
+\xfd\x5e\x78\x45\x2e\x90\x16\x09\x81\x1d\x0a\x4f\xc3\x05\x07\x38\
+\x8b\x92\x54\x20\x29\x57\xf6\x96\x5b\xf2\xa8\x58\xe1\x8f\x3a\xbf\
+\xb7\xfe\xfe\x02\x91\x90\xe8\x4e\x26\x9e\x15\x3c\x9d\x75\x8b\x25\
+\xcb\x20\x4e\xc6\x65\xf0\xfe\x85\x93\xea\x1d\xeb\x7d\x39\xb1\xde\
+\xd6\x97\x0f\xe7\x89\x65\xbb\x42\x5e\x52\xc8\x7f\x24\x45\xa2\xb5\
+\x50\xad\x8c\x65\x80\xcb\x92\xa0\xaf\x3a\x61\x3d\x03\xeb\x8f\x8f\
+\x65\x12\x3f\x92\x1f\x3c\xdd\xa3\x39\x2b\x2d\xdf\xb3\x9e\xd5\x53\
+\x97\x80\x97\x03\x40\x86\x10\x46\xf2\x86\x45\x7f\x51\xe9\x58\xff\
+\xcf\x31\x5f\x96\x48\x0e\xd9\x0f\x42\x29\xaf\x10\x99\x53\x6f\x05\
+\xcd\xf5\xb6\x5f\x59\x61\x4b\xc2\x35\x05\x9c\x83\x00\xd6\xd3\x17\
+\x58\xad\x9d\xbe\xfd\x6c\x47\x68\x7b\x19\xf8\x47\x93\x2a\x89\x50\
+\xea\x1d\x79\xac\xdb\x97\xcf\xfb\xb7\xad\xfd\xa5\xfa\x36\xdc\x64\
+\xb8\xb8\x72\xea\x6a\xea\xbf\x13\x93\x72\x4e\x44\xb0\x1c\xe4\xff\
+\xee\x79\x75\x55\xe5\x89\x8f\x81\x39\xdc\xdc\xeb\xdb\x8d\x92\x05\
+\x10\xdc\x50\xe6\x36\x5a\xc1\xc4\xd1\x8e\xdf\x50\x6b\x4b\x59\xb9\
+\x2d\x49\xd7\x52\x45\x2b\x97\x0b\xa5\xaf\x3f\x90\xb6\x63\x81\xb5\
+\xab\xd5\x77\x77\x76\x21\x1e\x82\x26\x25\x52\xeb\x04\xdb\x72\x81\
+\xf5\xc5\x81\x5b\xd7\x7e\x47\x93\x67\x40\x72\x5e\xe9\x68\xc8\xe9\
+\x79\x6c\xe2\x5e\x99\x7b\xc0\x1b\xde\xbb\x78\x5a\x75\x52\xfe\x63\
+\xd0\xb1\xfe\x24\xd7\x07\xa2\x70\x42\xb9\x7a\xaa\xe3\xcd\x9c\x9a\
+\x90\x51\xa3\x5d\xab\xb2\xd2\xb6\x5d\xd7\xb2\x6c\x72\x8c\xc8\xb0\
+\x0d\x0d\x50\xdd\xf9\x7c\x18\xf6\xf5\x05\x41\xeb\x91\x7c\xf8\xdc\
+\x2e\x4f\x1e\xdb\xe5\xbb\x68\x71\xa5\xbc\x2c\x64\x34\x77\xf6\x0e\
+\xe4\x3e\x26\x77\x41\xf4\x3a\x17\x09\xa4\x94\xbb\xbd\x6f\xd1\xb5\
+\xb5\x49\xf9\x72\x97\x38\x33\x04\xe8\x1e\xdf\x68\xf9\xaf\x98\x99\
+\x08\x27\x4d\x4c\x58\x75\xf5\xb6\x9d\x4a\xd9\x26\xff\xc3\xa8\x02\
+\x22\xa6\xf8\x80\x7e\x26\x13\x04\xed\xed\x5e\xb8\x73\x57\x3e\xfc\
+\xe5\xf3\x9e\x23\x19\x94\x57\x8d\x2d\x55\xbe\xff\x50\x77\xce\xfa\
+\x88\xfc\xd7\xda\xad\x00\xa1\x29\x41\x82\xf1\x3c\x30\xc3\x8a\xe2\
+\x3c\xc8\x51\x9c\x05\x56\xd4\xf2\xa8\x8b\xf6\xfa\x45\x6f\xa9\x71\
+\xe5\x96\xee\xd0\x69\x94\x5e\x2f\x7c\xed\xbc\x84\xbf\x68\x5e\xd2\
+\x6e\x6a\x76\x2d\x37\x61\x59\x9e\x17\x00\x04\x3e\x88\x20\x90\x30\
+\x80\xf4\x8d\x60\x16\x94\x0e\xdb\x82\x64\xee\x38\x7a\x39\xae\x03\
+\x4c\xd8\x92\xcd\x06\xd2\x7a\x38\x1f\xac\xd9\x90\xf3\x1f\xd9\xea\
+\x25\xa4\xd6\x95\x9a\xd0\xdf\xdd\x9d\xb7\xdf\x27\x77\xac\x79\x5c\
+\x09\x24\xfe\x6e\x9c\x96\xb3\x75\x2f\x01\x87\x7b\xc3\xc2\xff\x55\
+\xee\xd8\x9f\xed\x19\x44\x95\xbb\x41\xfe\xdd\x4b\x93\xf6\xac\x99\
+\x49\xbb\xba\x86\x2c\x51\x24\x87\xfc\x7b\xb8\x02\x1f\x74\x1d\xa2\
+\x0c\x90\x7f\xbe\x80\x1e\x26\x36\xf2\x6d\x3b\xb6\x38\x60\xad\x0e\
+\xee\x1e\xc0\xd2\xd1\xe1\x85\x1b\x37\x65\xfd\xfb\xd6\x7b\x96\x94\
+\x39\x4e\x8d\xed\x77\x75\xe7\xad\x0f\xc8\x1d\x6b\x7f\xca\x60\x28\
+\x07\x5e\x23\x1e\x24\xe7\x27\x40\x58\xb7\x26\x67\xa1\x5c\xbf\xf8\
+\xe6\xea\x84\x7c\xbb\x67\x40\xa4\xb9\x2a\xcc\xbe\xfd\xaa\x74\x72\
+\xea\x94\xa4\xe5\x40\x84\x18\x18\xf0\x64\x20\x93\x13\x1f\xcd\xa3\
+\x0d\x26\xe3\xd8\xa1\xb8\x20\x0c\xde\x6d\x84\xb7\x23\x22\x01\x95\
+\xa0\xca\x21\x75\xdb\x49\x71\x92\x29\x10\x4a\x42\x72\x00\xca\x8e\
+\x6d\xd9\xe0\x7b\x8f\x67\xf3\x41\xde\x4e\x55\x27\x83\x5c\x4f\xde\
+\xfa\x73\x25\x10\x43\x98\x24\x0e\x32\xa1\xb3\x63\x4a\xc0\x91\xb8\
+\x61\xd1\x7f\x26\x52\xce\xdf\x0d\x74\x78\x72\xc9\x54\x27\xf7\x9a\
+\xcb\xcb\x92\xcd\x63\x5c\xe4\x1b\xe2\x13\xca\x20\x97\x85\x2c\x19\
+\xfa\x9a\x5f\xe6\xdd\x61\xe3\x00\x3e\xc0\xfc\x93\x1d\x30\xfb\xc6\
+\xd8\x90\xae\x5c\xb1\x13\x49\x71\x93\x09\x84\x17\xd9\xbb\x37\x17\
+\xfe\xec\x91\x4c\x6e\x5f\xb7\x95\xaa\x46\xd7\x46\x8f\x17\xdc\x2c\
+\xdf\x5f\xff\xdd\x73\xaa\xa1\x88\x93\xff\x12\xee\x54\xbd\xce\x3f\
+\xb3\x05\x4a\xf3\x96\x2d\x81\x5c\xbf\xe8\xc6\xea\x94\xfd\xdd\x9e\
+\x9e\x50\x96\x4e\xb0\x73\x7f\x7e\x6d\x45\x6a\xc2\x84\x84\x35\x08\
+\xe2\x6e\x3b\x36\x08\xe2\x18\x54\xc2\x70\xed\x40\x92\x28\x89\xa4\
+\x8b\x0b\x77\x17\x8d\x6a\x02\xf7\x04\xee\x60\x1c\xe2\x82\x4a\x5c\
+\x0b\x00\x12\x10\x92\x97\x45\x4b\x1b\x28\x48\x9a\xc6\x24\xad\x05\
+\xe3\x6c\xb7\xfd\x70\x3e\x7b\xb0\x07\x04\x92\x0a\xaf\xcb\x2e\x68\
+\x59\x27\x3f\x7c\xe8\x39\x8c\xaf\xa0\x53\x60\xcf\xd9\x69\x41\x55\
+\xb4\x44\xfe\x61\x92\x37\x2c\xfe\x82\x9b\x76\x3f\x9a\x69\xf3\xc2\
+\x37\x2d\x49\xfa\xaf\xbf\xba\x3c\x51\x07\x05\xaa\xa7\xd7\x93\xce\
+\xee\x2c\x38\x67\x1e\x40\x08\x34\x9f\x9a\x67\xe6\x95\xf9\xe7\x1d\
+\x17\x18\x28\x1a\x0c\xda\x4d\xc3\xe1\x58\x60\x1f\x41\x4e\xb9\x0d\
+\xd8\x8a\x34\x8c\x4a\x5a\xb3\x26\xb8\x6e\xe6\x98\x97\xdb\x75\x34\
+\x74\xaa\xcb\xed\x37\x67\xe7\x37\xef\x92\x2f\x1f\x79\x56\x08\xd2\
+\x2d\x5b\xce\x5e\x23\x71\x0a\x28\x1b\xd9\x3f\xcf\x0c\xfb\xe7\xff\
+\xeb\x41\x4f\xae\x5f\xf0\xba\x6a\xd7\xba\xa7\x07\xfa\xc6\xe5\x53\
+\x9c\xfc\x5b\xde\x50\x91\xac\xad\x75\x00\x8c\x9c\xb4\x77\x1b\x60\
+\xa0\x57\xaa\x00\x8a\x04\x94\x89\x04\x39\x48\xe1\x6e\x88\x42\x89\
+\x84\x84\xc2\x0b\x2d\xa9\xb6\xae\x92\x93\x10\x84\xe5\x05\x8e\xd4\
+\xd4\xa5\x64\xda\x78\xd7\x3d\xd6\xea\xa1\x15\x15\xb7\x26\x29\x7f\
+\x92\x9d\x37\xfa\x37\xf2\x9f\x4f\x1f\xd2\xb1\x82\x35\x87\xcf\x3c\
+\x48\x1e\x01\x38\xf7\xec\x09\xec\x1b\x16\xfd\x75\x3a\xe9\x7c\x66\
+\xa0\xdd\x93\xb7\x5e\x96\x0c\x5e\x75\x65\x99\x9b\x48\x58\x5a\x06\
+\xbd\x19\x00\x03\xf9\x65\x83\xa0\x0d\x01\x41\x10\x71\x4f\x82\xc1\
+\x00\x22\x02\x89\x82\x83\x65\x40\x71\x0b\x17\xb9\x2b\xbb\xbc\x7c\
+\x94\x81\x6f\x49\x05\x58\xf4\xe4\xf1\x09\x27\xd3\xe9\xe5\x77\xb6\
+\x8a\x53\x9d\x42\x19\xcc\x69\x59\x21\x3f\x7a\x68\xcf\x59\x6d\x28\
+\x4e\x01\x69\x9f\x5f\x00\xa1\x92\x7c\xef\x4a\x4f\xfe\x7c\x51\x4b\
+\x4d\xda\xfe\x79\x4f\xd6\xae\x59\x30\xc6\xca\xbd\x0d\xe0\x80\x12\
+\x2e\x07\x8f\x66\x31\xf0\x95\x03\x28\x42\x49\x29\x77\x30\x84\x50\
+\x00\x05\x08\x81\x20\x51\xb0\x00\x28\x04\x47\x91\x58\x28\x7a\x50\
+\xec\xa0\xc8\x41\x22\xf1\xc5\x42\x4b\x9a\xf7\x6c\x10\x48\x4a\x26\
+\xb6\xd8\xce\xbe\x7d\xf9\xdc\x91\xac\x5d\x8e\x6e\xd0\x8b\xb3\x4b\
+\x5b\xee\xc4\x40\x64\xf6\x8c\xb7\xa2\x6c\x20\x58\x06\xd7\x2f\x5e\
+\x82\xde\xba\x1f\xf7\xf5\x04\x2e\x74\x2e\xef\xd5\xaf\x2a\x77\x29\
+\x36\x1d\xee\xc8\x49\x26\xe7\x45\xc0\x88\x80\x1f\x01\x22\xce\xab\
+\xb9\x23\xef\x51\x19\xb0\x51\xd0\x06\x02\xfe\x8c\x6e\x16\x95\x01\
+\x55\x8c\x00\x20\xa1\x1a\x52\x99\x90\x09\x2d\xae\xd3\x76\x28\x9f\
+\xdb\xdf\x67\x27\x6b\x52\x72\x79\x76\x76\xe3\x0f\xe4\xab\xcf\x0c\
+\x9c\xf1\x32\x38\x05\xc0\x88\xa3\x40\x91\x9d\x27\x86\x62\x45\xd4\
+\xc7\x5f\x51\x21\x9f\xed\xb6\xec\x09\x49\xe8\x05\x7f\xf2\xea\xb2\
+\x64\x55\x95\x2d\x07\x5a\xb3\xd2\xd9\x9f\x57\x8e\x41\xc2\xa7\x7c\
+\xcd\x8a\x57\x62\x88\xef\x25\xe0\x20\x48\x92\x0e\x45\x2f\xde\xe3\
+\xcb\x3c\x27\xe0\xce\x56\x57\xc1\x14\xf6\x4a\x0e\xa2\x5a\x43\x63\
+\x42\xde\x7e\x75\x3a\x81\x26\x35\xdf\x63\xdb\x8b\xcb\xf3\xc1\x3f\
+\x6b\xc9\xde\x7d\x0f\x39\x08\x60\x75\x06\xcc\x72\x34\xec\x66\x76\
+\x80\x55\xe5\xca\x17\xd0\x8d\x9b\x9e\xd5\x6c\xe5\x5e\x75\x45\x99\
+\xeb\x82\x5b\x1e\x6a\x27\x38\x7c\x2d\x03\xa3\x67\x99\x32\x30\x00\
+\x20\x08\xcc\xa5\x0d\x83\x82\xa3\xa4\xc1\x60\xd9\x44\x97\x69\x50\
+\x20\x66\xb2\x91\x61\x23\x12\x0e\xa0\x0c\xb2\x52\x8d\x9e\x90\x37\
+\xa1\xbc\xd9\x72\x74\xdb\xce\xcc\xca\xb2\xc4\xa7\x34\xd7\x67\xb2\
+\x0c\x4e\x71\x31\x9f\x3f\x00\x79\x35\x06\xc1\x68\xae\x5f\xf4\x1a\
+\xd0\xe3\xf5\xd2\x85\x46\xf4\x95\x29\x17\x3d\x55\xd2\xda\x91\x97\
+\xf6\x3e\xb4\x9a\x20\x12\x15\x0f\x00\x08\x8a\x17\xe4\x08\xa5\xc4\
+\x11\xb7\xa0\x31\x21\x14\x80\xe1\x16\x81\x52\x10\xc5\xa0\xb7\x18\
+\xfd\x04\x20\x0b\x00\x92\xc1\xbc\x8c\x1b\x9f\xb2\xde\x7d\x59\xd2\
+\x12\x0c\xb2\x40\x86\xff\x7b\x4c\xd9\x98\x0b\x68\x84\x68\x41\xcf\
+\x4c\x39\x6f\xbe\x4e\x81\x98\xfe\xc0\xa2\xbf\xec\xb7\xac\x57\xcb\
+\xa0\x17\xbe\xe1\xf2\xb4\x5b\x8d\xae\xd8\x23\x50\xd0\xfb\xa0\x7b\
+\x51\xcf\xa0\x27\x5e\xa6\x2c\xcc\xbd\x50\x16\x2c\x13\x94\x0d\xc5\
+\x2d\x2d\x0f\x36\x06\x04\x06\x01\x83\xab\x98\x7f\x00\x03\xba\x8b\
+\xe1\x2c\x08\x13\x0c\x48\x7e\x30\x27\x63\xc6\x24\xe4\xfa\x2b\x52\
+\x8e\x74\x7a\xcc\xfa\x87\xe4\xc6\xa5\x8b\xb4\x0c\x38\x48\x39\x02\
+\xcd\x99\xa9\xb8\xd3\x5f\x30\x05\xee\xd1\x90\xb4\xfe\xa1\x1f\x03\
+\x80\xaf\x98\xee\xe4\x66\xcc\x48\xd9\x7d\x03\x81\x1c\xea\xcc\x2b\
+\x61\xa8\x78\x04\x7a\x2d\x10\x06\x2a\x9e\x62\x47\x4c\x1c\x31\x40\
+\x78\x57\x42\x50\xce\x11\x83\x83\xc4\x45\x77\x43\x30\xf4\xe3\xc4\
+\x04\x02\x71\x4b\xf2\xfd\xec\x1e\x0d\x2f\xba\x28\xe5\xce\x1e\x6b\
+\xe5\xba\x7d\xa7\xbc\x3e\xe9\xfc\xad\x66\xfd\x9e\x33\xc0\x45\xc8\
+\x3d\x38\x9a\x7f\xdd\x9c\x64\xda\x96\xbf\x01\xbd\xca\x1b\xe6\x25\
+\xf2\x13\x26\x26\xed\xae\x5e\x5f\x8e\xf5\x2b\x68\x4d\x87\x1c\x1b\
+\x88\xd2\x8b\xe5\x80\x67\x2d\x07\xcd\x57\x04\x0e\x96\x03\xca\x27\
+\x2e\x8b\xb8\xe1\xd0\xbc\xd3\x1f\xcb\x0e\x0d\x45\x7c\x0f\xbd\x8c\
+\x04\x18\x78\x9d\x89\xee\xe3\x05\x13\x6c\xaf\x17\xbd\x7b\x18\x7f\
+\xfd\x88\x96\xc1\xb2\x15\x67\x5e\x17\x3b\x05\x74\x77\x7e\x00\x84\
+\x33\x72\x61\xaa\x6f\x58\x72\x69\x8f\x17\x5e\x2b\x98\x3b\x72\xf1\
+\xfc\x94\x9b\x4c\x59\x72\xb0\x9d\xca\xb4\xa9\x4c\x36\xe6\x06\x24\
+\x68\x35\x69\x47\x18\xbd\x48\x1c\xb0\x18\xb0\xd0\x6e\x08\x24\x6e\
+\x39\x95\x93\x40\x6f\xa1\xc8\x45\x37\x43\x20\x24\x0e\x84\x21\xa1\
+\x21\xa0\x1d\xa2\x47\x68\x30\x6b\x55\x56\x39\x72\xc5\x7c\xb4\xa0\
+\x20\xc8\x7c\x68\xbd\x53\x6e\x5c\x34\x11\x9f\x39\xfd\x5c\x24\xe2\
+\x1e\xe5\x15\x89\x6b\xba\x02\xeb\x12\xce\x9b\x99\x3f\x27\x89\x51\
+\x1e\x91\xa3\x5d\x66\x70\x9b\xf9\xd3\x31\x1e\x24\x48\x7b\xae\xa3\
+\xbb\x29\x03\xe3\x16\x03\x85\x1c\xb6\x54\x07\x4b\x00\x08\x04\x0a\
+\xb9\x48\x2c\x8a\x91\xfb\x16\x1b\x1d\xe8\x65\x50\xdc\xf3\xd9\x2c\
+\x66\x26\x38\xd6\x25\x73\xc1\x49\xc1\xb1\x06\xfc\xf0\xad\x35\x37\
+\x2f\x99\x2a\x1c\x13\x61\xcf\xde\x08\x33\x2c\x9b\x91\x6f\x36\x8f\
+\xd2\xae\x44\x28\xdf\x6f\x87\xd2\xec\x2c\x1a\x67\xfb\x63\xc7\xb9\
+\x56\x0f\xa6\x48\x1c\xc3\x95\x64\xd3\x08\x1a\x55\xa2\x80\x55\x07\
+\xc1\xf4\x5e\x62\x57\xc0\x90\xd8\x49\xf8\x06\x04\x04\x82\x11\xb3\
+\x10\x47\x09\x30\x62\x20\x99\x56\x98\x20\xe1\x85\x59\x4a\xde\x20\
+\x86\x13\x02\x19\x3f\xc1\xb5\x27\x34\x4a\xd0\xeb\x5b\x0d\xa3\x2c\
+\xeb\x4f\xb4\x80\x39\x5d\xfe\xf4\x19\x0b\xdc\x43\x5b\xe8\x8a\xa4\
+\xf5\xa7\x92\xb3\xe5\xaa\x29\x6e\x7e\xf4\x28\x96\x81\xaf\xa2\x95\
+\xcb\x44\xb2\x0c\xf0\x5b\x0a\x8e\xe2\x33\x89\xdd\x10\x7c\xe9\x9d\
+\x20\x28\xe5\x20\xda\xd3\xa5\xf9\x8d\xf3\x6d\xee\x8c\x93\x9f\x08\
+\xbd\x9c\x0e\x36\x8e\x1f\xef\x5a\x13\xeb\xad\x70\x30\xb0\xeb\xdd\
+\x30\x78\xa3\x66\x9d\xcb\x0a\x46\x98\x39\x9d\x95\x76\x66\x8a\x62\
+\x39\x1a\x2e\x88\x16\x4b\xd0\x7b\x93\x0b\xc2\xab\xd1\x6c\xcb\xec\
+\xc9\x6e\x98\x4e\xdb\x56\x1b\x5a\x4e\x30\x0f\x25\x88\xd2\xc4\xb0\
+\x96\xcc\x55\x42\x30\x70\x30\x84\x53\x74\x53\x3d\x25\x02\x09\x45\
+\xab\x02\xc7\x00\xd1\x14\x88\x4c\xe3\x32\xcf\xa0\x0e\xc9\x63\x02\
+\x57\x15\xb8\xc8\x82\x49\x89\x00\xbd\xc1\x68\x36\xc3\xd7\xea\xb7\
+\x23\xe5\xb9\x34\x1d\xa7\xcc\xbe\x5c\xb3\x13\xca\x7b\x2e\xad\x86\
+\x0e\x7e\x15\x39\xe8\x8c\xc9\x09\xce\x12\x90\x63\xbd\x48\x01\xd3\
+\xc8\x0c\x6b\x33\x12\x7d\x95\xcf\x34\xc3\xef\x91\x1b\x9d\x35\x8f\
+\x0a\x9a\x62\x83\xc1\xc6\x41\x01\xc4\xc8\xf0\x6e\xa8\xe1\xf8\x6c\
+\x20\x1e\xca\x00\x9c\xd4\x9e\x3f\xd1\xc1\xf0\x3c\x78\x59\x60\x5d\
+\xad\xfe\x58\x06\xac\xaf\x11\x64\x46\x54\x62\x4f\x58\xae\x5c\xd7\
+\x01\x73\x30\xf4\x66\x40\xbc\x9a\x29\x60\xe2\x63\x5b\x5c\xcb\xc3\
+\x28\x71\x27\xb8\x07\x2b\xf4\x25\x99\xe1\x75\xff\x82\x91\x98\xc1\
+\x7b\x2f\x9f\xe3\xf8\x99\x35\x76\x0c\xd9\x56\x88\x91\x65\x99\xdf\
+\x72\xf3\x92\x46\x0d\x7e\xba\x94\xf5\x15\xcb\x34\x97\xcd\x55\xfe\
+\xac\xee\x9c\x4c\x91\x34\xe6\xec\x8f\xb2\xed\x3c\xca\xa0\x77\x10\
+\x22\x11\x28\x9d\xf3\xa9\x0a\x60\x28\xcd\xcb\x89\xf2\x79\x02\x37\
+\x36\x1b\x74\x66\x3c\x7a\x2f\x8d\x63\x88\x3d\x44\xb7\xaf\xa7\x03\
+\x8c\xe3\x5a\x20\x97\xa2\x85\x1a\x0c\xc2\x79\x85\x32\x88\xd2\x3a\
+\x24\xc8\x39\xfc\xf0\x52\xc9\xe7\x9c\xcb\x12\xa0\x30\x4b\x02\xbb\
+\xaa\xb9\x52\xa4\xa6\xda\xb6\x32\x98\xa5\x3a\x00\x6e\xa2\x6c\x9f\
+\xa9\x2d\xa9\xf4\xb8\x82\x87\x54\x3a\x2b\x9e\x57\x4c\x08\x08\x12\
+\x04\x16\x5b\x3f\xc9\x61\x30\x2c\x1f\x60\x32\x0a\xde\x07\x98\xc9\
+\x1a\xea\x55\x24\x94\x38\x3e\x7e\xc6\xc7\xfc\x8b\x10\x11\xd5\x61\
+\x50\x92\x33\x5e\xf3\x41\xd8\xe2\x84\xfe\x64\xbe\xd3\x55\x89\x6a\
+\x39\xc5\x3f\x33\x8c\xe8\x82\xef\xcc\x02\x0a\xdc\xe9\x35\x56\x58\
+\x59\x65\x63\xc6\x40\x28\x59\xb4\xe0\x2a\x5d\xe1\x93\x0a\x92\xf8\
+\xd3\x4c\x74\x64\x34\xfd\xf8\x19\x7e\x27\xf7\x65\x7e\x3d\x94\x41\
+\x9e\x65\x80\xcb\xc7\xb3\x96\x01\xcb\xa9\xb4\x1c\x18\x18\x86\xe5\
+\xc7\xf9\x5c\x54\xd6\x6b\xb0\xea\x4a\x92\x02\x80\x48\x0b\x98\xe9\
+\x04\xf5\xb0\x4c\x7f\x47\xcc\xcf\xc8\x07\x48\x0b\x96\xc1\xc2\x40\
+\x00\x9f\xc8\x26\x72\x6c\x95\x15\xa6\xd2\x96\x35\x80\x96\x13\x73\
+\x0f\xd5\x14\x08\x1f\x3e\xd5\x5e\x5a\xb1\x05\x40\x50\x38\x60\xe5\
+\xab\x48\xa0\x44\x41\xc2\x20\x38\x72\xbe\xad\x77\x8e\x1a\x73\x2e\
+\x9f\x5e\x11\xa1\xc4\x44\x14\x7f\x23\xc0\x64\x47\x12\x48\x59\x99\
+\x65\x4d\x34\xcb\xae\x52\x59\xb1\xc7\x99\x94\x9c\xa6\xdf\xa8\x0c\
+\x00\xe0\x89\x82\x34\x37\x55\xdb\x41\x32\x65\x63\xcc\x43\x1b\xf0\
+\x42\x19\xb0\x7c\x58\x58\x26\xad\xc3\xef\x11\xc1\x6b\x19\x18\x10\
+\xb0\x81\x60\x19\xc4\x00\xe1\x9d\x0d\x86\xb9\x08\x1e\x02\xa2\x18\
+\x8f\x69\x3c\xe8\x6e\x00\x52\x8e\x32\x68\x29\xa3\x07\x29\xc3\x47\
+\x5b\x34\x21\x23\xec\x67\xc4\xf5\x2a\x1c\x57\xbe\x91\x82\x8e\x4a\
+\x1f\xc5\xda\xaa\xa9\xb0\x03\x4c\x5b\xb7\xb3\xe0\x1e\xac\x30\x1a\
+\xb6\xe8\x4a\x14\x04\x86\xfe\x11\x50\x24\x02\x43\x40\xca\x29\x20\
+\x4f\x53\x28\xf2\x21\x8e\xb0\xd5\x50\x99\x1d\x77\x12\x41\x6c\x57\
+\xc2\xc0\xb3\x87\x78\x0c\x50\x22\x42\x82\x9b\x01\x8a\xb9\x73\x12\
+\x60\x02\x04\x8a\xd1\x64\x11\x34\xee\x81\x15\x34\xc0\x76\xfa\x4c\
+\x54\x06\x18\xdb\xaf\x67\x19\x54\x94\x59\xaa\x2f\x61\xc0\x5c\xd3\
+\x05\x27\x3a\x97\x94\x01\xdd\x91\x77\xb8\x29\x91\x97\xa4\x9f\xf9\
+\x22\xc7\x21\xb7\xa4\x07\xe5\x08\xc8\x2f\x3b\x36\x18\x09\xcb\x2d\
+\xce\x7b\x0c\x16\x96\x73\x0c\x0e\xe3\x1f\xfe\x10\x31\x67\x4a\xd7\
+\xa6\x24\x3c\x84\x41\x19\x3b\x0c\xeb\x11\xa3\x48\x94\x56\xb5\x8f\
+\x80\x9f\x91\x0f\x10\xee\xb8\x71\x8f\x56\x64\x9a\x15\xc8\xc5\x4e\
+\x24\x68\x0f\x3d\x9b\x05\x02\x20\x18\xf0\xd2\x3c\xb3\x32\x23\x60\
+\xb0\x62\x51\x91\x60\x0c\xe8\xa6\x65\x4b\x69\xc2\xe2\x51\x0d\x41\
+\x45\xc0\x18\xe2\x60\x1c\xe4\x1e\xc6\x9f\xb6\xa6\x7c\x1f\xb7\xa8\
+\x11\x91\x18\x82\xa3\x68\xc7\x09\x8f\xaa\xc5\x22\xb8\xc5\x76\xf4\
+\xb4\x1b\x0c\xc3\x60\x99\x2c\xc0\x41\xa4\xe3\x1f\xcc\x8c\x45\x12\
+\x01\xa3\x58\x1e\x26\x8d\x2c\x07\xb8\x95\x5c\x04\x05\xfa\x24\xb4\
+\x1c\x98\x58\xac\xb8\x44\x78\x90\x3c\xb2\x61\x34\x2c\xfa\x37\xe1\
+\x28\x6a\x15\x2f\xf3\xad\xf8\x5d\x69\x19\xb0\xab\x98\x89\x09\xc0\
+\xd4\x68\x1b\x69\x66\xe4\x03\x24\x2e\x71\x2c\x98\x66\x45\x90\xa8\
+\xf1\xaf\x04\xa2\x76\x3c\x18\x22\x60\xc5\x1a\x60\x90\xa8\x0d\xb7\
+\x20\x38\xc0\x31\x10\x92\xad\x26\x81\x81\x47\x35\x04\x03\xba\xfc\
+\x23\x77\x12\x8a\x89\x57\xb9\x0d\x1e\x62\xd1\x83\xa0\x22\x61\x15\
+\x80\x82\xb8\x35\x1d\xf8\xe5\x77\x69\xf0\x05\x0e\x44\x9c\x76\x83\
+\xb4\xeb\x26\x14\x04\x7d\x6c\x58\x06\xca\x2d\xe1\x60\xca\x23\x6e\
+\x1c\xe2\x72\x31\xe9\xb7\x90\x6e\xe6\xd2\x64\xdf\xfc\x86\x00\x06\
+\x89\x9e\x73\xcf\x4c\x09\x98\x5f\x46\xaf\xf9\xd5\x7b\x6c\x27\x60\
+\x18\x27\xcb\x39\x2a\x83\xe8\x3d\x4b\x0e\x63\xa9\x9a\xb6\x38\x5d\
+\x23\xe5\x3e\xf2\x01\xa2\xbd\x22\x2b\x48\xcf\xdd\xac\xdd\xfe\xc1\
+\xc0\x22\x54\x38\xa5\x82\x15\xc5\x4a\x73\x08\x0c\xb8\x05\xa8\x68\
+\x72\x0b\x36\xec\xa6\xb5\x2c\xe1\x1a\x8c\x21\x32\x4a\x54\xac\x5c\
+\xf8\x67\x97\x26\xab\x1b\x8f\x20\xb0\x92\xd6\x13\xfe\x8b\x20\x41\
+\xbc\x20\x30\x8f\x61\xe0\xc7\x02\xda\x3c\x88\x78\xe8\x24\x60\x50\
+\x06\x36\xeb\xb6\xf5\xe1\x34\xfc\xb4\xb5\xe9\x77\x00\xfa\x2e\x26\
+\x36\x83\xc9\xca\xcc\x2f\xe7\x8b\x19\x82\x2d\x05\x85\xc9\x83\x36\
+\x1a\xf0\xc3\xf2\x40\x16\x71\x21\x9f\x0a\x12\x93\x60\xcd\x2b\x5a\
+\x7f\x6d\x38\xe8\x47\x4b\x81\x65\xc0\x72\x2d\x96\x6d\x69\xc3\xa0\
+\x1c\x05\x71\xf2\x9b\x5a\x06\x5e\x18\xa2\x57\x0d\xfb\x3d\xc0\x8d\
+\x69\xa3\x89\xd2\xaa\xf6\x11\xf0\x33\xf2\x95\xf4\xb8\x07\x07\x83\
+\xe6\xac\xe9\xf6\xde\xd0\xe6\x12\xd9\x34\x06\x72\x59\x99\x94\x97\
+\x0d\x50\x08\x16\x03\x98\x21\x95\x4a\xc2\x2e\xb9\xf2\xb0\xf3\xa2\
+\x62\xce\x5e\x1b\xde\xb3\xaa\xa4\x1b\x45\x5d\xdf\xc3\x9d\xbd\x5a\
+\xb4\x17\xc2\xe2\x5b\x54\xe2\x49\x58\x14\x71\xb2\x83\x61\x78\x10\
+\xd3\x3d\xe0\x10\x62\xa0\xed\xf0\x69\xa5\x85\xa8\x0c\x40\x94\xfb\
+\xa9\x40\xb5\xf5\xf8\x76\x1e\x0a\x7a\x1a\x42\x0d\x92\x65\x44\x2d\
+\xa4\x4b\x5b\x7e\xde\x09\x0c\x96\x4d\x54\x3e\x85\xf2\xc0\x3b\x93\
+\x1f\x8c\x89\x33\x6f\xcc\xe7\xf0\xab\x34\xcf\x25\x76\x13\x97\x09\
+\xcf\x26\x85\x65\x90\x41\x19\x1c\xe0\x86\x41\x16\xa6\x19\x84\x51\
+\x19\x2c\x3b\xad\x25\x71\xca\x23\x1f\xf9\x1c\x24\x2a\x12\xd4\xc7\
+\x0e\x92\xc2\xf3\xbd\xb6\xdd\xdf\x1f\x86\x15\x98\xde\xce\xd1\x63\
+\x8a\x40\x98\x6b\xa8\x9c\xc0\x28\xa0\x70\x63\x8b\x08\x37\x30\x88\
+\xa8\x5d\x34\x91\x28\x31\xe1\x87\x84\x84\xf8\x4c\xeb\x8a\x7b\x6c\
+\x08\x38\x56\x7e\x01\x74\x88\x83\x0a\x3b\x09\x49\xef\xfc\x0e\x06\
+\x41\xb8\x2c\xb5\xb7\x27\x2f\x7d\x24\x0e\x57\x8e\x39\xb6\xbb\x5b\
+\xe3\x88\x7a\x9b\xe2\xf8\x4e\xd9\x3d\x8e\x37\x0c\x9f\x67\x0a\xb7\
+\xf4\x88\x35\xd0\x1f\x04\xe5\xd5\x8e\x15\x97\x01\x67\x07\xb0\x81\
+\x80\xb2\xac\x77\xcd\x3f\x12\xc0\xec\x51\x70\x2a\x70\x08\x38\x20\
+\x9b\x6c\xf1\x51\x66\x86\x83\x32\x9d\x2c\x2b\xbe\xe0\x3b\xc3\x45\
+\x8a\x80\x8b\x39\x07\xcb\x45\xcb\x1b\xab\xac\xb8\x76\xa4\xa7\x1b\
+\x50\xcc\x8a\x93\x4a\xcb\x11\xdb\x4f\xee\x63\x3c\xb2\x42\x7f\x47\
+\xcc\xcf\xc8\xe7\x20\xdc\x11\x10\x26\x74\xfc\xad\xd8\x89\xe3\x80\
+\x80\x28\xdb\xda\x3c\x49\x63\x1e\x56\x35\x2e\x8c\x2c\x9b\x96\x13\
+\x35\x6e\x2a\x92\x04\x4e\xe0\x98\x2b\xe6\x02\x85\x96\x12\xee\xf9\
+\xa8\x5b\x57\xbb\x77\xb1\xde\xc3\x74\xf5\x46\xdd\xbd\x40\x97\xe1\
+\x2c\x78\x26\x17\x61\x0b\xcb\xb8\x70\xa7\xbf\x24\x7a\x09\x28\xb2\
+\xb4\x1e\x85\xc0\x85\x6f\x57\xbb\xb2\x7d\x5f\xd3\xaa\x3d\x86\x22\
+\x54\x14\x34\xd6\x53\xfa\x6b\xe2\xed\xcd\xc9\xd6\x8a\xa4\xec\x93\
+\x01\x2c\x8a\x6a\xf7\xc3\x14\xb8\x68\x65\xa1\x0c\xc8\x39\xa2\x32\
+\x00\x50\x0c\x37\x35\x77\x53\x16\x45\x91\x11\x0b\x26\xb5\x7c\xe2\
+\x32\xe1\xfb\x38\x9f\xca\x41\x23\xee\x19\x97\x5d\x5c\x96\xf1\x3d\
+\x89\x19\x8e\x04\xd1\xa1\x23\xe8\x46\x03\xb2\xd0\xa1\xb7\xa5\xed\
+\xfb\xab\x8e\x68\x96\x47\xd8\xa4\xc5\x91\x0f\x90\x68\x0e\x52\xeb\
+\xb7\x36\x1c\xc5\x0c\x8f\xd5\x14\x9a\x77\xed\x43\x7b\x0e\xd1\x66\
+\x0c\xb6\xb0\xc9\xb2\x75\x27\x01\x9f\xe8\x8a\xde\x99\x4a\x8f\x88\
+\xa0\x00\x0e\x43\xf0\xc5\x71\x90\x08\x28\x88\xa7\x00\x0c\x84\x27\
+\x98\x18\x37\x09\xc8\x07\xe1\x54\x94\xbb\xd8\xd8\x21\x0c\x77\xec\
+\x83\x03\xa6\x7a\x60\xc6\x0b\x37\x72\x08\x74\xd1\x10\xef\xa7\xc3\
+\x2c\xd7\x86\x5d\xfa\x6e\x5b\xd7\x86\x45\x52\x2b\xc9\xfe\x76\xee\
+\xc5\x14\x4d\xb0\xc2\x51\x55\x48\x1b\xd2\xc7\xd6\xbd\x28\x52\xc1\
+\xae\x6e\xa6\x5c\x08\x96\x21\xe5\x83\x3c\x31\x3f\x9a\xaf\x28\x6f\
+\x31\x58\x62\xa0\x0c\xf5\x1f\x01\x0a\x7e\x43\xec\x2f\x56\x5e\xe6\
+\x0a\x38\x58\xb8\x69\x0f\x76\x3e\xc1\xfe\x8c\xc0\xcb\x23\x9a\x6d\
+\x4e\x56\x3c\x5d\x65\x70\x3a\xca\x15\x71\x8e\x7c\x80\x90\xeb\x73\
+\x15\x1d\x8d\x1d\xfe\x82\xa3\xd7\xbf\xdb\xe9\x39\xc7\x3a\x7d\x69\
+\xaa\xc7\x72\x50\x6c\xce\x90\x45\xab\x4f\x3d\xa4\x58\xa9\x46\x14\
+\x50\x60\x28\x91\x1b\x02\x8f\x39\x05\x89\x20\x1e\x1c\x8c\xdd\x72\
+\x88\xa3\x08\x16\xf3\x3e\x26\x16\xea\x23\x83\x68\x2c\xd3\x58\x70\
+\x92\x4e\x3b\xd2\xda\x9a\x0f\x9f\x39\xe0\x43\xce\xc0\x74\xac\x20\
+\xbc\x4f\xd3\x36\xc7\x28\xd2\x6a\x3f\xf5\x3f\x9c\x2d\xac\x53\x6e\
+\x32\xf9\xf0\xe7\x82\xce\xde\x07\x76\xa0\x0c\x8e\xf9\x61\x63\x0d\
+\xca\x00\x40\x8d\xcb\xa0\x08\x0c\x43\xd4\xa5\xdc\xb4\x00\x08\x80\
+\xa9\xd0\x68\x94\x70\x8b\x82\x5b\x49\x99\xb1\x0c\x18\x8e\xf1\x60\
+\x53\x14\x49\x61\x0d\x2f\xb6\x0f\x92\x03\x07\xbc\x60\x5b\x5b\x88\
+\xcd\x50\x02\x6c\x98\xe4\xfc\x4a\xb3\x7c\x61\xb2\xe2\xa9\xaf\xf9\
+\x17\x15\x23\xf7\xc9\x85\xe9\xf0\xf3\xf7\xd5\x39\xc1\xee\x00\x03\
+\x53\xcf\x6f\xcb\xfb\x54\xd4\xa7\x8e\xc2\xde\x99\xe8\x60\x2c\x25\
+\x0c\x56\xb4\x87\x8a\x2f\x12\x84\x01\x88\x2a\xde\x11\x38\x8a\x20\
+\x01\xe7\x50\x31\x8b\x1c\x84\xf6\xd8\xaf\x01\x8c\xfa\x03\x38\xc8\
+\x3d\xea\xaa\x92\xba\x7f\xd4\xc6\xe7\x40\x2a\x68\x49\x1b\x13\xd6\
+\x63\x5d\xb7\xac\x7b\x4c\xf3\xf0\xa9\x15\x9a\xc6\x17\x95\x9f\xff\
+\x86\xa7\x9e\x3e\xfb\xfe\x3a\x0b\xba\xc8\xa0\x65\x6f\x7d\x2e\xe7\
+\x61\xc2\xbb\x8c\x47\x43\x91\xc5\xd7\xb5\x0c\xa2\x86\xc2\x00\xc3\
+\x80\x44\x75\xa8\x88\xc0\x4d\x23\x12\x89\x8e\xc3\x80\xa0\x0d\x42\
+\xc4\x5d\x0a\x60\xd1\xb2\x34\x65\x11\x20\xcf\xb5\x55\x09\xec\x9f\
+\x15\xca\xda\xcd\xe8\x25\xc0\x20\x08\x16\x19\xfe\xb6\xf3\x96\xd5\
+\x9b\x34\x4b\xdf\x59\x83\x92\x1a\x59\xe6\x7c\xe0\x20\x62\x44\x18\
+\x6c\x3a\x72\xcb\xe6\x63\xe8\xf3\xbf\x45\xaa\x6d\xf9\xf1\xda\x5c\
+\xd8\x76\xd4\x93\x89\x4d\x8e\x34\x55\x40\x61\xe4\xcc\xda\x98\x8b\
+\x9c\xb0\xe2\x63\x0e\xf1\x62\xee\x46\xdc\x8a\x7b\xb2\x32\x79\x0b\
+\x23\xf8\x49\xa9\xaa\x74\x64\xff\xfe\x5c\xf8\xc0\xa6\xbc\xe3\x60\
+\x4e\x18\xa4\x8c\x6f\x80\x1c\x4c\xeb\x4e\x4d\xf8\x74\x1a\x8a\x9a\
+\xba\xd5\xcf\x1a\x6e\x49\xf1\x6d\x96\xc1\xbd\x6b\xf3\xf6\xe1\xc3\
+\xf9\x70\x6c\xa3\x2d\xa3\xb0\x67\xea\x00\x1a\x8a\xb8\x47\x2f\x6e\
+\xf5\xa9\x54\xc7\x0d\x05\x1b\x0d\x03\x82\xa2\x9b\x3e\x2b\x17\xc1\
+\xbb\xf8\xbd\x82\x09\x8d\x4c\xc4\x3d\x08\x16\x72\xa8\xda\x0a\x57\
+\xca\xb0\x5a\x6b\xe7\x8e\x6c\xf0\xd4\x0e\xcf\x2d\x4b\x62\x3d\x48\
+\x3e\xfc\x9a\x66\xdb\xac\x05\x39\xbd\x65\x70\x1a\xca\xf7\xfc\x00\
+\x88\x16\x8c\x39\xd3\xa2\xbb\x3b\xfb\xcd\xda\xd0\xdf\x81\x6d\x45\
+\x13\x4f\x3c\x33\x98\xe3\x34\x13\x4c\xbb\x86\xa2\x08\x4e\xa2\x20\
+\x89\x2a\x9a\x95\x1c\x55\xb4\x72\x81\xa8\xf2\x8b\xa2\x95\xe1\x1c\
+\xd4\x61\x0a\x62\x96\xda\x0d\x17\xa1\x1b\xc3\x71\xf5\x62\x19\x44\
+\xab\xe6\x86\x84\xf4\x61\x6a\xf9\x43\x2b\xb1\xee\x34\xe5\xda\x95\
+\x41\xf8\x70\xe6\x96\x75\x3f\xd6\xa4\x71\xb4\xff\xf4\x9b\xc2\x37\
+\x32\x3d\xd9\xef\xd6\xf8\xc1\x46\x0c\x46\x38\x0f\x3f\x39\x98\xcf\
+\x61\xfa\xfb\xcc\xb1\xb6\xa4\xb9\x17\x58\x0c\x12\xe4\xdd\x70\x0b\
+\x53\x0e\x24\xf6\x22\x57\x88\x81\x12\xb9\x45\x79\x35\x60\x81\x5b\
+\xc1\xaf\xe1\xc2\x6c\x20\x2a\x21\x5a\x36\xd6\x25\xc2\xf6\x36\x5f\
+\xee\x7d\x12\x53\x24\xc1\x3a\x12\x56\x78\xe7\xe0\xf7\xd7\xaf\xd0\
+\xac\x9b\x4d\xbe\x4f\x7f\x29\x9c\xe2\x2f\xa8\xdc\x7a\x8a\xe3\x3c\
+\x3b\xd1\x71\xff\xa5\x65\x50\x02\xef\x5f\xd5\x9f\x5f\xd8\x72\x14\
+\x93\x05\xdf\xb9\x7d\x9f\xef\x8c\x2a\xb3\xfc\xa9\x93\x13\x36\xe7\
+\x45\x1d\xc4\x50\x15\x7b\xb5\x38\x80\xc6\x5e\x16\x52\x94\x5e\xe0\
+\x2c\xbc\xc7\x03\x60\xc5\xbb\xe9\xe5\x89\x5b\xdd\x58\x44\xd1\x67\
+\x00\x8a\x84\x51\x86\x11\xc9\xc9\xcd\xd8\x50\x0e\xf3\xea\x1f\x7d\
+\x22\xe3\x3d\xbe\xcd\x4f\x56\x95\x85\xf9\x1e\xdf\x7a\xaf\xac\x3f\
+\x7c\x40\x57\xd1\x2d\xdf\x73\x46\xc4\x2b\xdd\x83\x4a\xb7\x3d\x7a\
+\x76\xd0\x5b\xd0\xb4\x17\x8b\xa7\xde\xb3\xfb\x70\xe0\x80\x99\xf8\
+\x53\xa7\xb8\x76\x2d\xe6\x68\xb5\xf7\x62\x20\x11\x82\x0e\xbb\x7f\
+\x4d\xde\x59\x5d\xec\xc3\x45\x59\xb0\x1c\xe0\xc8\xfc\xb1\xe3\x97\
+\x3d\x0a\xb4\xeb\xc5\x67\x80\x2a\xee\xfd\xa2\x38\x45\xfb\x20\xca\
+\xa0\x0a\x0b\x28\xc7\x8d\x4e\xb0\x73\xc2\xfa\xf5\xc3\x99\xdc\xb6\
+\x56\x49\xd6\x24\x82\xd6\x9e\x8c\xf5\x6e\xd9\x78\xa4\x5b\x39\x1b\
+\xf7\x29\x1b\x81\xe6\xfc\x01\x08\x0b\x7f\xf7\x9e\x50\x3e\x8d\x8a\
+\x5e\x7f\x78\xb3\xb3\xa0\xb9\xd9\xab\x4c\x2c\xdd\xb8\x2d\xe7\x8f\
+\xab\xb6\xec\xa9\x93\xb0\xff\x2c\x94\xd7\x36\x8c\x69\x53\xdc\x22\
+\x81\x90\x2c\x08\x06\x1d\x35\x86\xbd\x94\x40\x0c\x61\x40\x6e\x8f\
+\x08\x44\x27\x28\x92\x70\x40\x18\xe4\x1e\xd8\x56\x4a\x6a\xca\x1c\
+\x99\x32\x26\x09\xc5\xd4\x96\x55\xab\x06\xfd\x7b\x57\x66\xed\x74\
+\x03\x0e\x49\xf0\xe4\x93\xc1\xf7\xd7\xfe\x58\x09\xe3\x1b\xf7\x9f\
+\x59\xc2\x58\x73\x98\xa7\x41\xd9\xe1\x97\x8f\x6c\x93\x05\xcd\x29\
+\xa7\x2a\x71\xe5\xa6\x6d\xb9\x70\x34\xf2\x3e\x99\x65\x40\x71\x13\
+\xcb\xe7\xd1\x25\x1c\x8d\x73\x20\xdf\x28\x09\x05\x4b\xc9\x3d\xce\
+\xbf\x4e\x4e\x8c\x41\x12\xdd\x59\x4e\x54\xc8\x79\xd5\x96\x3b\x32\
+\xa1\xc9\xec\xb2\xf8\xf0\xa3\x19\xef\xe1\xcd\x5e\xa2\x0a\x88\xec\
+\xc9\x85\x37\xc8\x9d\xeb\x56\x9a\x46\xeb\xfe\x33\xd3\x40\x90\x06\
+\x4e\xb1\x39\xbf\x00\x02\x70\x44\xad\x55\xe8\x5d\x3c\xf6\x81\x0a\
+\xdf\xbf\x3c\x5f\x96\x98\xb6\x76\x4b\x36\x3f\xb6\x42\xac\x29\xd8\
+\xa4\xb9\x19\xbd\x3a\xb9\x2c\x80\x82\x51\x6e\xf6\xf7\x73\x31\x11\
+\x91\xa2\x44\x12\xb7\x9e\xd1\xbd\xd0\x7a\x46\x84\x41\x11\x84\x22\
+\x0a\x03\xb4\x60\x4f\xde\x49\x20\x0c\x0e\x88\x3d\xb3\x6a\x30\xbc\
+\xeb\x51\x8c\x88\x35\x24\x6c\x27\xe7\xdd\x96\xbb\x6d\xdd\xc7\xb5\
+\x9e\xde\xb5\x05\x9b\x49\x28\xed\xe9\xe3\x19\xfb\x59\x86\x04\xe2\
+\xbb\xfe\xfa\x23\x0f\x25\xe7\x37\x5f\xe4\x57\xb9\xf3\x9e\xdd\x9c\
+\xf3\x1b\x92\x98\x0f\x8f\xa5\xb0\x4d\xb5\xc8\x30\x40\x4e\x65\x85\
+\xca\xbb\x99\x6b\xc5\xd4\x95\x00\x05\x79\x2e\x94\x09\xdd\xf1\xcc\
+\xc6\x82\xe2\xd5\x20\xf4\x0d\x6e\xb2\xc5\x32\x18\x3b\xda\x95\x41\
+\x28\xe5\x0f\x3d\x92\xf1\x7e\xbb\x3e\xef\x56\x34\xa2\x8b\x37\xef\
+\x7f\x32\xbc\x7d\xfd\x77\x34\x82\x1b\xf6\x10\x7b\x23\xd6\x9c\x5f\
+\x00\x61\x35\x50\xd4\xa2\x42\xf8\xc5\x95\x5e\x7e\xf6\xa8\xfb\x30\
+\xa0\x7e\x65\xae\x3c\x31\x69\xed\xc6\x9c\x5f\x01\x85\x64\xdc\x58\
+\xc7\xa6\xe2\x8e\x5d\xce\x25\x87\x56\xb4\x13\x9b\x39\x53\xe4\xa0\
+\xf8\xc4\x9a\xd4\x89\x7d\x24\x06\x80\x27\x56\x5e\xa9\x87\x50\x09\
+\xe5\xdf\x68\x28\xe2\x93\x46\x25\x64\x74\x9d\x03\xa2\x11\x79\x62\
+\x65\x26\xfc\xc9\x8a\x6c\x98\x1a\x9d\xb0\xdd\x5c\x70\xcf\xe0\xad\
+\xeb\xde\x87\x68\x8c\x62\xfe\x8d\xb3\x24\x56\x10\x94\x14\x37\xb1\
+\xbb\xa2\x77\x71\xcb\xcf\xcb\xf3\xfe\x82\xb0\x2a\x31\x1b\x20\xb1\
+\x9c\x6c\x10\x60\xa5\x9f\xc5\x2e\xf0\x6a\x70\x15\x40\x45\x06\xa0\
+\x47\xb1\x0c\xd8\x00\x0c\x11\x2f\xf1\x4c\x25\x9e\xa0\x60\xfe\x79\
+\x4f\x62\x96\xc0\x68\x70\x88\x89\x00\x46\x2d\xf6\x1b\x3b\x82\x1d\
+\xef\x7f\xf1\x60\x26\x0f\xd1\x32\x51\xd9\xc0\x31\x20\xff\xff\xfa\
+\xb7\xad\xff\x57\x44\xcb\xc8\xed\xb3\xd2\x40\xe8\xc7\x4f\xcd\xcf\
+\xf9\x07\x10\x96\x0b\xf7\xc4\x25\x48\xb0\xab\x5f\x6e\xda\xd8\x7b\
+\x2b\xdc\x60\x5e\x50\xe9\xce\xda\xbc\x2d\x6f\x73\x8b\xd0\xfa\x4a\
+\x2c\xe4\x69\x72\xac\xb1\x8d\xe8\xe1\xaa\xc4\x71\x52\x2e\xc2\x80\
+\xd8\x8d\xd2\x6e\x08\x82\x00\xc1\x2a\x06\xdd\x81\xb1\x06\xb2\x7b\
+\x0b\x06\x1d\x27\xa1\x75\x6c\x02\x30\x38\x42\x8d\x06\x94\x4a\x79\
+\x78\xcb\xc3\xd9\xc0\x4f\x39\x4e\xb5\x84\x77\xf6\xde\xb2\xf6\xdd\
+\x1a\x93\xea\x42\x67\x59\xac\x00\x38\x14\x24\xd8\x65\x31\x3f\xed\
+\xca\x7b\xd2\x6e\xdb\x78\x9c\xfb\xb1\x68\xd3\x26\x4f\x1a\xab\x2d\
+\x19\x87\x4d\x2d\xd2\xe8\x02\x46\x59\x48\x03\xc4\xae\x4a\xe4\x49\
+\xf7\xcc\xd2\x39\x25\x28\x8e\x88\x83\x90\x43\x96\xa9\x3f\x5b\xc6\
+\x62\x4f\xdf\x71\x28\xb3\xfa\x6a\x47\x06\x07\x42\x59\xbf\x7e\xd0\
+\xfb\xc6\xef\x06\xc3\xc3\x3d\x56\x82\x1b\x57\xe3\x30\xa2\x7f\xf4\
+\x6f\x5b\xf7\x59\x56\x81\x11\x2f\xcf\x52\x03\xa1\x09\x38\x35\x3f\
+\x6c\x40\xce\x5f\x43\x90\x44\x67\xf0\xa5\x6f\x58\xf4\x59\xe8\xe6\
+\xff\xab\x1f\x63\x1a\x50\x22\xc2\xd7\x5e\xe4\xe6\xe7\xcd\x4a\xba\
+\xa3\x9b\x5c\x3b\x0d\x00\x50\x9c\xca\x63\x76\x48\x1e\x2d\x29\x5b\
+\x51\x35\x60\x11\xe8\xca\xc7\xf4\x11\x03\x08\x82\xc8\xb4\xb0\xd0\
+\x61\xd0\xb4\x0c\x62\x32\xde\x3d\xf7\xf5\x07\xeb\xda\xc5\xa9\x4f\
+\x86\x37\x1f\xfb\xee\xda\xef\xea\xa0\xe5\x4b\x3b\xfb\xf0\xf4\xd4\
+\x43\x49\x19\x34\xdc\xb4\xe8\x81\x8e\x01\xfb\xb5\x6f\x5b\xe4\x78\
+\x57\x5d\x59\xe6\xa0\x11\x50\x38\x00\x03\x6a\xe1\x24\x46\x72\x0c\
+\xee\xda\x4e\xd6\xe2\xa2\xe1\xe0\x7c\x2c\x30\x0d\x7d\xcf\x09\x90\
+\xdd\xdd\x7e\xb8\x77\x5f\xde\x7b\x6c\x63\x5e\xf6\xb4\x87\x09\x01\
+\x58\xaa\x43\x7f\x7b\x8f\x67\xff\xad\xdc\xbe\xe6\x01\xcd\xc4\x75\
+\xe8\x72\xe7\x99\x2c\xe7\x81\x39\x3f\x39\x48\x5c\x31\xe4\x24\x6c\
+\xcd\x29\x6a\x40\x1e\x0f\xe7\x35\x3d\x8a\x71\xac\xc9\xf9\x94\x33\
+\x69\xe7\x11\x71\x56\x6e\xce\x5b\x87\x0f\x7a\x5e\x7f\x97\x1f\xe4\
+\xb0\xb3\x40\x08\x80\x70\x16\x11\xa7\xa9\x80\x26\x30\x6d\xc4\xb6\
+\x12\xe8\x1a\xd5\x9e\x1d\x10\x0e\xef\x34\x71\xab\xc2\x15\x73\x5d\
+\x1d\x9e\xf7\xfc\xa1\xd0\x29\x4f\x86\xb9\xcc\xda\x23\x77\x0b\x37\
+\xab\x86\x92\x7c\xce\x88\x16\x2b\xf6\xc4\x70\x17\x7b\xe1\x98\x1b\
+\xf3\x79\x6b\xd2\xa5\x33\xdd\x60\x4c\x0b\xf6\x69\x89\x48\x58\xf3\
+\x47\x5f\xc8\x18\xc1\x92\x84\xf8\xc9\x96\x80\x23\xf1\xdd\x9d\x41\
+\x70\xe4\x88\x17\x6e\xdf\x9e\x0f\x9e\x5e\x97\x0d\x7e\xb0\x32\xeb\
+\x6c\xd8\x11\x38\x5d\x98\x95\x59\x95\x0c\xbb\x70\xd8\xd6\x57\xfb\
+\x06\xbd\xeb\xe5\xce\xf5\x9b\x35\xdf\xcb\x10\xcb\x37\x20\xa9\x9e\
+\x27\x86\xc2\xc5\xf9\x6d\xb8\x5f\x2f\x5b\xb4\x39\xd8\x94\x67\xf9\
+\xb3\x0f\x77\x2f\x97\x47\x92\xfb\x16\xbf\x13\x5d\xb1\x37\xf5\xf9\
+\xb2\x6c\xf3\x31\x49\x6c\x3e\x0c\x4a\xc1\x96\x3d\xdc\x60\x00\x47\
+\x80\x48\x3d\x4a\xa5\x1d\xf3\xa9\xfe\x62\x49\x52\x96\x2c\x81\xa0\
+\x1e\x99\x48\xfa\xd0\x27\x12\x15\x7b\xc2\x5a\x9a\xc1\x63\x7c\x6c\
+\x1a\xc7\xcd\xda\x6e\x9a\xdb\x24\x3c\xe4\x53\x37\x71\xc3\x32\xc7\
+\x73\xc1\x44\xe7\x12\x62\x57\x91\x09\x87\xb2\xc1\x6c\x2e\x19\xe4\
+\xa6\x16\xcc\x0b\xf3\x40\x51\x31\x36\xfa\x0c\x77\x6c\x5c\xcf\x2e\
+\xeb\xf0\x97\xab\xd8\x8f\x8d\xe9\xc9\x28\x1a\x45\x0f\xa9\x05\x0d\
+\x46\x7d\x75\xb8\xcd\x0f\xfd\x7b\xbb\x43\xff\x76\xb9\x75\x03\x66\
+\x10\xc3\xb0\x7b\x79\xf9\x4b\x3a\x35\x58\x83\x9f\xab\x3f\xe7\x3f\
+\x40\x58\xf2\x64\xf7\xf7\xa0\x65\x8b\x2a\x31\x27\x6b\xef\xee\x10\
+\xb9\x3b\x81\x1d\xd0\x9b\xd3\xc1\x35\x7e\xd2\x7a\x65\x9f\x1f\xce\
+\xca\xf8\x61\x53\x90\xb5\xca\xb1\x19\xa3\x83\x10\xd6\x5d\x8f\x64\
+\xc3\x31\xcd\x8e\x85\xed\x3b\xa1\xd0\xa3\x4f\x07\xc4\x53\x6a\x48\
+\x50\x0d\x8d\x8e\x93\xa8\xc0\x8a\x28\xdf\x1a\x5f\xe7\x24\x2f\xe9\
+\x14\x31\x73\xaf\x4a\x3d\x9e\x4d\x3b\xd7\x8a\xac\x60\x77\x75\x30\
+\x03\xed\xfa\xe8\x72\x2c\xfe\xc5\xa9\x52\x40\xc7\xb0\x44\x45\xcf\
+\x3c\x58\x68\xcf\xce\x5c\xf8\xcb\xa7\x81\x12\xe8\x2a\xd8\x3c\xaf\
+\x0d\x1c\xa5\xad\xdc\x0e\xf7\x41\x47\x59\x87\x1d\x4a\x1e\x3b\x96\
+\x0f\x57\xe2\x90\x1c\xb3\x00\x4a\xb9\xe5\x32\x6c\x9a\xbd\x42\xfb\
+\xf7\x86\xc5\x3a\xe2\x1f\x5f\x1e\x00\x31\xd5\x14\xea\xce\xe7\xe4\
+\x26\x6d\xcb\x74\x2f\xdf\xfc\xed\x6b\xd7\x1c\x11\x59\x83\xd7\xff\
+\x56\xfb\xfe\x85\xb5\xb5\x15\x36\x46\x35\x82\x3a\xcf\x73\x52\xbe\
+\x15\x7e\xf6\x88\x58\x97\xad\x5a\x9f\xf3\x9b\xc7\x24\x1c\x1e\x24\
+\x43\x19\xbd\x14\x24\x5c\xda\x8a\x4d\xe2\xac\x57\xb4\x38\xf9\x47\
+\xf7\x48\x22\x99\xf0\xb9\x49\xdc\x7d\xc2\xdd\xcc\xcd\x30\xcb\x70\
+\x32\x3c\xf3\x04\x13\xef\x78\x22\xe1\x42\x9e\x92\x35\xb3\x46\xc2\
+\x8a\x72\xcb\x66\xda\x4b\xf3\xc2\x84\x92\x9b\xf0\x88\x39\x1c\x2f\
+\xe7\x49\x85\x93\x18\x9b\x0a\x7f\xdd\x15\xe4\x6e\xa8\xcc\xda\x7d\
+\xad\xff\xb5\x01\xa3\x27\x25\x86\x7d\xc0\xdc\x30\x5c\x47\xc8\xc1\
+\xa5\xcf\x53\xf3\x72\x02\x88\xa9\x42\x55\x1e\x57\x88\xb0\xe5\x8b\
+\xf6\xb3\xe5\xce\x8c\x5d\x68\x11\xd1\x24\x9a\x56\x11\x3e\xcb\xdf\
+\xbf\xf0\xff\x58\x49\xfb\x57\x38\xb0\xd3\x59\x72\x20\x1f\x4c\x9a\
+\x9a\xb4\x7d\xec\x33\x55\x4a\x54\x04\x4c\x12\xbd\x3f\x93\x31\xb6\
+\xf0\xe8\xb6\x9c\xe4\xd2\x72\x35\x76\x74\xc7\x88\x0b\x88\x89\xf1\
+\x2f\x3f\xae\x9d\x3e\xd3\x64\x84\x73\x02\x0d\xf1\x42\x04\x5c\x42\
+\xb5\x79\xdc\x28\xc7\xc7\xb6\x48\x0e\xd3\x3e\xdc\x70\x15\xe0\x81\
+\xfd\xf9\xe0\xc9\x5d\x5e\x22\x85\xd3\x7e\x0f\x0e\xfa\x5f\x90\x3b\
+\x36\xb5\x16\x90\x51\x28\x33\x88\x8f\xe8\xd7\x40\x77\xe1\x79\x0b\
+\x8c\xb8\x6c\x4a\x24\xd0\xd8\xe9\x65\x72\x5f\x8e\x0a\xe6\x6e\xe8\
+\xbc\x58\xdd\xac\x7c\x4e\xf6\xc3\xee\xe8\xb4\x0f\x7c\x7f\xfd\xfd\
+\x75\x4e\xf8\x20\xe6\x33\xc9\x86\xe7\xf2\x41\x0e\xe0\x20\x17\xa1\
+\x58\x45\xa3\x40\x81\x9d\x77\x88\x61\x98\xe8\x84\x9d\x1c\x73\x32\
+\xa7\x31\xed\x2c\x35\x3e\x20\x76\x9c\x6d\x13\xed\xe4\x58\xff\x91\
+\x4b\xab\x31\x28\x3a\x8f\x8a\x77\xd3\x68\x1b\x73\xd0\xb1\x4f\x49\
+\x09\x37\x64\x9e\x98\x0f\x0f\x9d\x14\x5b\x9e\x47\x3f\x1e\xba\xe8\
+\x2a\xec\xf0\x77\x72\xc7\xb3\x0f\x6b\x16\xd8\x13\x66\xca\x28\x2a\
+\x33\xed\xa1\x8a\x4a\xe2\x6c\x67\xf2\xf4\x7e\xff\xec\x57\xe2\xe9\
+\xcd\xdf\x8b\x8d\x9d\xd3\x33\xa2\xca\xdf\x8c\xe1\x43\x43\xdc\x98\
+\x6f\xf4\x75\x12\xfe\xef\xb7\x7b\x2e\x66\x06\x07\xba\x9d\x4e\x29\
+\x59\x80\xa8\x28\xaa\xe0\x68\x37\xeb\x92\x66\xec\x43\x8b\xbe\x2f\
+\x60\xe8\xf5\xfa\xd1\x15\x2f\xf6\xd3\xa7\xd1\x5f\xb4\x06\xc5\xcf\
+\xe4\xa7\x0f\x04\xd8\x92\x14\xab\x66\xea\xeb\xd1\xa9\x50\x9a\x07\
+\x7e\x1e\xcf\xd8\x4b\x4c\x8e\xb5\x7b\xc1\x6f\xb7\x7b\x98\x3b\xa3\
+\x23\xec\xb7\x69\xca\xd8\x0b\x68\xb8\xd0\xf0\x50\xa7\x31\xe1\xe7\
+\x4e\xd4\x17\x00\x72\xc2\xba\x58\xa1\x02\x48\xd7\x2d\x6b\xee\x1b\
+\x65\xcb\x53\x82\x35\x10\xcf\x61\x7d\x09\x5b\x58\x30\x94\x02\x17\
+\x61\x50\x76\x95\x72\x27\xc7\xa9\xe3\xb1\x49\x10\x76\x32\xc9\xf8\
+\x72\x8d\x0e\x52\xb2\xf7\x8c\x5c\xe9\x6c\x9a\x68\x93\x36\xec\xe2\
+\xb2\x40\xc4\x2e\x9b\x86\xd1\xcc\xea\x2a\xa3\x7f\x80\x67\x16\x0d\
+\xec\xe4\x22\x3b\x77\x63\x37\x31\x8c\xaa\x57\x59\xc1\xe6\x7e\xab\
+\xfc\x97\xea\x61\x94\xd9\x39\xbf\xe8\xf9\xe5\x65\x3b\xbb\x15\x78\
+\xae\x96\x35\xb9\x89\x59\xa1\xe7\x0f\xfa\xf2\x1d\xa9\x80\xe6\xbd\
+\x15\xa3\xf0\x18\x17\x50\x2e\x12\xa5\x9b\x62\x09\x18\x88\x6e\x71\
+\xd3\x82\xe9\x1b\x82\x41\x01\x6c\x56\xbd\xa0\xf9\x48\xdf\x62\xf5\
+\x12\x6d\xac\x7d\x56\xb2\x49\x25\xda\x88\x8f\x44\xe9\x25\xdc\xa9\
+\x62\x4a\x93\xe3\xa5\xca\x70\xa6\x6d\x89\xfe\x41\x51\x8b\x03\x82\
+\xbd\xbd\x7e\xf8\xcc\xb6\xbc\x23\x69\x1d\x18\xfc\x91\xdc\xfa\x44\
+\xaf\x8e\x21\x45\x71\x9c\x95\x3c\x9c\x03\x1f\xbd\x00\x90\x17\xa8\
+\x84\xde\x5c\xe2\x5e\x9c\x92\xb4\x15\x2b\xf4\x9c\x1d\x3b\x72\xe0\
+\x21\xa4\xbc\x22\x17\x51\x90\x60\xb1\x77\x43\xbd\x63\xcf\x6b\xe4\
+\x5e\x21\x76\x32\xef\x05\xec\xcd\x12\xd9\x66\xf6\x0d\x56\xfb\x99\
+\xfe\x79\x57\xc4\xbd\xae\x9d\x96\x02\x88\x17\x73\xd3\xae\x96\xd1\
+\x8e\x8a\x52\xb1\xce\xa1\x49\x42\x66\x38\x28\x72\x60\xbf\x17\xec\
+\x6e\x0f\xed\xa4\x1d\x60\x68\xd0\xfa\x91\xbe\x5b\x76\xa6\x13\x7d\
+\xee\x7d\xef\x02\x40\x4e\x56\x27\xba\x42\x0f\x2a\xc5\x0f\x9e\xc6\
+\x06\x3e\xd6\x6d\xd0\x5a\x05\x53\xb9\xed\x9e\xae\x20\x74\xa8\xb2\
+\x46\x86\x60\xe1\xd4\x8c\x74\xb9\x65\xcd\x9c\xe0\xea\x79\x18\x18\
+\x32\x79\x8d\xbe\x3e\xab\x62\xd6\x75\x9a\x84\xaa\xa6\x9a\xc9\x18\
+\xa3\x99\xc5\xf5\xf1\x0d\x0d\x9c\x59\x55\x34\x04\x0a\x3b\x1e\xd0\
+\xb5\x1b\x6e\xde\x0e\xf9\x10\xbb\x2b\x60\x88\xe4\xbe\x9e\x5b\xd6\
+\xee\x50\xf1\xd0\xe8\x1e\xc5\x00\x2f\x43\xdb\x05\x80\x9c\xbc\xd2\
+\x43\x1d\x2f\xc1\x7b\x4f\xbc\xbb\x2a\xc4\x3f\x84\xb5\x24\xce\xce\
+\x5d\x98\xd3\x4a\xb6\x11\x1b\x5a\x41\x68\x3c\x3f\x7c\xfc\x58\x88\
+\x59\x78\xc0\xb1\x63\x4b\x2a\x3f\xb8\x60\x8e\xf1\x62\x14\xfe\xd8\
+\xfb\x99\xbe\xa7\x13\xe1\x7c\xcc\x4a\xae\x1a\x57\x63\x05\x35\x35\
+\x38\x12\x88\xa8\x88\x4d\x94\xee\xa3\xad\x5e\xf8\xc4\x1e\xdf\xe5\
+\x06\x62\x58\x7c\x78\x87\x79\x7d\x76\xd3\x1d\x27\xf1\x6c\xdf\x2f\
+\x00\xe4\x0f\xd5\x00\x39\x00\x06\x16\x33\xdf\xdb\x70\x00\x8d\xeb\
+\x5d\x98\x76\x21\xcf\x6c\xc9\x5b\x7d\x18\x76\xa7\xdc\x4e\xf9\x5d\
+\x0d\x60\x11\x40\xcc\xaa\x6f\x70\x9c\x29\x75\xd4\xdb\xed\xea\x44\
+\xe0\xbe\x4a\xdf\x9d\x9d\xdd\xcc\x2d\x89\x96\xf9\x42\x41\xbf\x84\
+\x6b\x3f\xa6\x8f\xb2\xfd\x74\x89\xfe\xa1\x62\x16\x6a\x9f\x3b\xd1\
+\xef\xd8\x05\xe5\x1c\x2b\x24\x47\xb9\xf2\x44\xf7\x2d\xeb\x7e\xaf\
+\xe9\x3e\x43\x9b\x4c\x98\x02\x3c\x77\x7f\x2f\x00\xe4\x85\xea\xa6\
+\x6e\x89\x96\x11\x48\xe8\xd6\x94\x15\xf4\x3e\x7f\x34\x70\xf6\xee\
+\x41\x9b\x4c\x2e\x42\xee\x11\x19\x2a\xbe\xe5\x98\xdc\x38\x77\x82\
+\xe3\xf1\xe8\x35\xec\xe9\x7b\x8d\xbe\xa2\x92\xbb\x3c\xd2\x07\x62\
+\xcf\xa7\xfb\x4e\x26\xc1\x8e\x06\x18\xa8\x1e\x4b\xd9\xbb\x86\xe9\
+\xfd\x38\x8e\xa0\xa8\x3b\xf1\x1d\xc5\xab\xae\x2e\x3f\x7c\xe0\x39\
+\x4c\x71\x46\x47\x44\x4e\xac\x3b\xe0\x7c\x66\x36\x99\x60\x02\x46\
+\x80\xb9\x00\x90\x17\xaa\xa4\xe8\x5c\x3d\x6c\xca\xb6\x05\x8b\xaf\
+\xee\xe5\x36\x81\x6b\xb6\xe4\xc2\xcc\x00\x74\x11\x10\x58\xac\xf0\
+\x1a\x79\x9e\x62\x16\x78\x0d\x5a\x65\xcc\x75\xbc\x4c\x6e\x9a\x3f\
+\xee\x85\xa2\x2f\x79\x4f\xb8\x9d\xec\x2a\xf1\xf6\x22\xac\xef\x32\
+\xe7\xb2\xd7\xdf\x74\xe9\xb8\xae\xbc\xe8\x04\x45\xea\x1f\x9c\xba\
+\xae\xe9\x2d\x44\x81\x79\x57\xe8\xda\xcd\xf4\x89\x03\xc6\xb7\x07\
+\x33\xd9\x7f\x5a\x78\x75\xc1\xa2\x25\x00\x41\xe1\x82\x79\xe1\x12\
+\xa0\x3c\xbe\x22\xc8\xf8\xc1\x37\xca\x5c\xeb\xbd\x6b\xf7\x89\x7b\
+\xd9\xbe\x7c\x30\x63\x56\xca\xa2\x88\x42\x1d\x84\x86\xf2\x7d\x7d\
+\x23\xa6\x81\x57\x86\x21\x4e\xb8\x1d\xd3\x98\x4c\x2c\x68\x17\x39\
+\x20\x7b\x96\x25\x65\x19\x54\x99\x68\x93\x69\xe3\x1b\xbf\x9c\x27\
+\xa5\x22\x18\xa6\x6e\x60\xb6\x31\x5a\xfd\x28\xa6\x82\x0f\x72\x02\
+\x0b\x53\x62\x6c\xdd\x15\x7d\x19\xdc\x4b\x37\x5f\x8b\xc3\x53\x9c\
+\x32\x61\x4d\x78\x0e\x10\x22\xca\x94\x93\x9f\xeb\x65\x65\x74\x0d\
+\xb8\x03\x67\xf0\xea\x4b\xfc\x60\xe9\x8b\x72\x8f\x7e\x9c\xe1\xb8\
+\xfa\x39\xb0\x17\x74\x40\x04\xe2\xff\x58\xb0\x33\x23\x52\x80\x59\
+\x64\xe6\xc4\xdc\x92\x54\xbc\x6c\xad\x17\x00\xf2\x62\xaa\x3e\xda\
+\xb2\x06\xd3\x4f\x56\x35\xde\xb4\xe8\xde\x4c\xda\xf9\xb3\x67\x36\
+\xe5\xbc\xf1\x13\x13\x49\xec\x01\x1c\x42\xfc\x52\x61\x8b\x62\x56\
+\x65\x85\x6d\x2d\xc6\xe4\xc5\x47\xf6\xf2\x2c\x9f\xe0\x0d\x88\xfe\
+\x57\xf2\xfd\x15\x58\xfd\x0d\xb3\x42\x7f\x4f\xfc\x03\x82\x3e\xa1\
+\x51\xc2\x8f\x5e\xae\x38\xa1\x0f\xce\x56\x36\x86\x63\x37\x04\x47\
+\xc7\x01\xf0\x36\x00\x32\x94\x59\x10\xf6\x64\x66\xbd\xe5\x97\x57\
+\x40\x41\xd7\x41\x1b\xbc\x01\x48\x30\xdd\x44\x0e\x1e\xc8\x79\x5b\
+\x5b\x83\x44\xba\x02\xfb\x6c\xfb\xf6\x0f\x34\x92\x4f\x63\x02\xa2\
+\x40\xf7\xba\x60\xb4\x04\x2e\x00\xe4\xc5\x11\x82\x59\xe3\x0d\xa5\
+\xbd\xdd\xb3\xbe\x5c\xe9\xfa\xef\x5a\xbd\x4b\x92\x4b\xf6\xe4\xfd\
+\x8b\x2e\xc2\x9e\x37\xe4\x22\x30\x14\x5f\x12\x98\xbc\x38\xba\xc1\
+\x76\x65\x75\x56\x82\x71\xce\x4d\xd5\x37\x2d\xae\xc3\x0c\xf3\xaf\
+\x55\xba\xde\xae\xc0\x72\x9b\x41\xa3\x8d\xd0\x60\x1a\x3c\x2b\xc4\
+\xac\x61\xa9\xc5\xb8\x4a\x15\xa8\x11\x0b\x56\x2d\x4c\x6a\x09\x93\
+\x58\xd9\x88\x33\x72\x31\xf6\x48\x6a\xb6\x2c\xf6\x98\x65\x31\x37\
+\x64\x10\x9d\x64\xfd\xae\x84\xbd\x90\x92\xba\x12\x96\x7d\x0c\xc2\
+\x52\x3b\x66\x84\xb4\x87\x96\xdf\x5e\x1b\x24\x3a\xd7\x50\x14\xa4\
+\xbe\x63\xc0\xe2\xc9\x07\x66\x56\x21\x9e\x37\xe8\xfc\xab\x06\x47\
+\x27\x55\xb2\x53\x81\x69\xe4\x6a\xc8\x2c\x16\x88\xad\xdd\x8c\x19\
+\xea\x10\x19\x53\x76\x70\x5f\xf7\xad\x6b\x36\x6a\x26\xa8\x9c\x2f\
+\x57\xdb\x85\x1f\x94\xc0\x05\x80\xbc\x58\x32\x58\xa1\x84\x83\x19\
+\xba\x6b\x9f\xb2\x6f\x5c\x74\xa7\x54\x39\x7f\xf9\xe0\x33\xd9\xa0\
+\xb9\xc9\x75\x6a\xb0\x5e\x9d\x63\x21\xb1\x7c\x3f\x19\xbb\xa7\x4c\
+\x9f\xe6\x05\xdb\x5b\x71\x24\x5a\xda\x79\x4f\x8f\xe5\xbf\xa3\x35\
+\xe7\xb4\x83\x58\x21\xea\x87\x15\x6c\xd5\x0d\xcf\x89\x54\x0e\x83\
+\x2f\xa4\x44\x19\x51\x94\x22\x32\x81\xd8\x44\xee\x7a\x83\x67\x7e\
+\x88\x7d\x68\xb6\x05\xed\xc1\xe9\x4a\xd8\xc1\xd1\xba\x9b\x16\x1d\
+\xc4\x20\xf8\x4e\x4c\x9b\xdc\x82\x89\x89\x53\x33\x81\xbc\xe7\xf0\
+\x60\x38\x41\x8e\x06\x61\x65\x45\x02\xeb\xff\x2c\x8b\x87\xfa\xd0\
+\x70\xcd\xc7\xae\xdd\x58\x21\xb8\xd7\x77\x1d\xa0\x17\xa7\x3f\x7d\
+\x5b\x5f\x70\xbd\x8c\x75\xfe\x2d\x7a\xd2\xbc\xbd\xc4\x9f\xd2\x1a\
+\x79\x89\x51\x9c\xe7\xc1\xd8\x03\xc5\x29\x23\x1c\x15\x27\x48\x40\
+\x99\xf2\xbe\x25\x13\xaa\xdc\xe0\xd9\xde\x1e\xa9\xbd\xe1\xb5\x29\
+\x6f\xc1\xe2\xb4\xcb\xf5\xda\xb1\xe1\x7c\x2d\x6e\x85\xb3\x77\x4f\
+\x2e\x58\x0b\x72\x5d\x73\x28\xe0\x36\x83\x32\x1a\x4d\x7e\x1d\x56\
+\x2c\x72\x83\x84\x32\xdc\xb1\x77\x70\x98\x4c\x48\xe8\x26\xec\x90\
+\xad\xba\x76\x8c\x21\xac\x9a\x08\x07\xc4\x02\x44\x38\xac\xf2\x0b\
+\x2c\xec\xc2\x62\x61\xaf\x48\x8b\x3b\x44\xf6\x60\x76\xf1\x11\x6c\
+\x5f\x34\x00\x3b\x84\x29\xf4\x33\xa3\x2a\x59\x9b\xc4\x15\xef\x48\
+\x4f\x39\x92\x7c\xd5\x14\x37\x5c\xba\x30\x65\xa1\x0b\x1a\xb3\x75\
+\xcd\xb4\x92\x01\x74\x30\xfc\xf4\xfe\x81\x60\x4d\xab\x38\xb5\xc9\
+\xe0\x5e\xec\x1f\xfc\x4e\x84\xa0\x61\xc8\x62\x46\xd4\xe9\xe5\xfd\
+\x73\x81\x83\x9c\xb8\xfe\x2d\xcc\xc5\xb2\x55\x9e\x5f\x4e\x79\x7c\
+\x8d\x76\x99\x42\xf4\xb0\x47\xed\x5f\x3c\x15\xa7\xd6\xbe\x11\x8d\
+\x31\xa8\x1c\x7b\xf2\x62\x26\x2f\x89\x98\x17\x17\x1c\x71\xa8\x9a\
+\x84\x5e\x8d\x2d\x71\x16\x2e\x4c\xdb\x53\xa7\x25\xed\x2b\x0e\x7a\
+\x3e\x4f\x9c\x4d\xa2\x89\xc7\x3b\x6e\x86\xa3\x9e\x40\xd3\x7a\x8e\
+\x0d\xc2\xaa\x02\xad\x77\x92\x27\xe3\x30\x5e\x42\xc6\x17\xc7\xc9\
+\x60\xa0\x7f\x9e\x47\x8a\x5f\x1c\x3f\x82\xf9\xc3\x1e\x80\x80\xd3\
+\xac\xf4\xb8\x81\xde\xbe\xc0\x6a\xed\x08\x70\x1e\xa9\x15\xcc\x9c\
+\x92\x90\x09\x13\x5c\xbb\xae\xce\xb1\x98\x2e\x72\x38\xc6\xc9\x01\
+\xcd\x9e\x1e\x2c\x8a\x3a\x84\x19\x98\xd8\xf8\x0e\xdf\xbc\x0b\xce\
+\xe8\x0c\x58\xe6\xaa\x8e\xc4\xc9\x89\xe6\x48\x09\x7e\xe5\x65\x6f\
+\x58\x66\x17\x8c\x29\x01\x03\x0a\xda\x87\x4d\xd0\x1b\xf3\x57\x8b\
+\x96\x62\x37\xc5\xd7\xf5\x05\x72\x4d\xd6\x0f\x17\x43\x43\xa8\x91\
+\x63\xbe\xa4\x31\xf7\xea\x5f\xde\x5b\x69\x57\x63\x67\x0f\xce\xf4\
+\xa5\x0e\x9c\x03\x72\xb0\x3b\x2f\xce\x4a\x0c\xa5\x0f\x17\x37\x66\
+\xc3\x22\x3d\xdd\x17\x18\x1b\x39\xcb\x00\x80\xd2\x8f\x6b\x00\xee\
+\xfd\xbc\xf0\xae\x07\xe1\x18\xf6\xb8\xb6\x3b\xaa\x1d\x30\x1c\xee\
+\x45\x2d\xe8\x66\x16\x6c\xcb\x85\x23\x1d\x70\xc7\x98\x46\x39\xb8\
+\x12\xf6\xcc\xc6\x5d\x74\x6b\x1e\x6c\x11\x1c\xf2\x04\xa9\x0a\x00\
+\xb1\xae\x06\xa7\x4b\xc1\xaf\xcf\x74\x51\xf7\x40\xf4\xbc\x13\x2c\
+\x3c\xa2\xee\xe7\xbf\xee\x0f\x57\xee\x0b\xed\x71\x95\xe1\xcf\xf3\
+\xb6\xfb\x8f\xad\xdf\x5c\xb5\x8b\x59\x8f\x4c\xd4\x40\x68\xef\x18\
+\x42\xbd\x7c\xcd\x05\x80\xb0\x61\x5d\xae\x3d\x37\xdc\x8d\xa4\x40\
+\x0c\x55\x1f\x98\x3f\xb3\xc2\x75\xdf\x82\x8d\x1d\xde\xd2\x97\x0f\
+\x2e\xc6\x11\x56\x29\x7d\xeb\x86\xb2\xb0\x4e\xc2\x96\x51\xd8\x72\
+\x13\x2b\x09\x5b\xc6\x61\xd3\x6a\x00\xa1\xb3\x1f\xbb\x80\x60\xaf\
+\xa8\x0e\x9c\x70\x85\x0d\x1f\xa4\x03\x20\x38\x8a\xd8\x7c\x8c\x62\
+\xab\x01\x8f\x40\xc7\x91\x60\xa3\x75\x10\x3a\xc4\x1f\x5c\x29\x5c\
+\xd8\xb5\x94\x9b\x3c\xe3\x1d\x8f\x8e\x06\x01\xc3\x33\x39\x10\xe8\
+\x5c\x3f\xc7\xdd\x0c\x41\xe3\xba\x67\x57\x16\x2c\xa7\x1f\x17\xba\
+\x90\xa5\x15\x71\x73\xaf\x5c\xa5\x7c\xdc\x18\x70\x2c\x80\xd3\x1c\
+\x8b\x71\xb0\xb3\x77\xa0\x1e\xa8\xaa\xc6\xce\xee\x65\x40\x19\x8f\
+\xc8\x66\xbc\x09\x00\xeb\xd9\x0d\x83\xe1\xf7\x7e\x84\xc4\x8e\x46\
+\xaa\x5c\xe9\x6d\x74\xc3\x27\x12\xb6\xf5\xb3\xc3\x79\xe7\x7e\xb9\
+\x6d\xd5\x7e\x93\x68\xfc\xb2\x67\xac\x0d\x3d\x63\xe8\xa0\x28\xb8\
+\xbd\x8c\x2c\x2c\xda\x97\xaf\xa1\x52\xca\xb3\x45\x62\x60\x5c\x37\
+\xa7\xb2\xa6\x2a\xf9\xfa\x94\x6b\xbd\xfb\x68\x0e\x23\xe1\x9e\x5d\
+\x4d\xea\x4c\x80\xd0\x96\x8e\xb2\x82\xf1\x63\xdc\xa0\x1e\xc0\x70\
+\xb0\x64\x15\xea\x80\xd5\x09\x3d\x63\x4f\x77\x28\xfb\xa1\x0b\xb4\
+\x46\x40\x80\xbe\x2e\x75\x20\xfc\x2a\x4c\x0e\xac\xc4\x12\x91\x4a\
+\xac\xa3\xc2\x0e\x54\xd0\xd5\x7d\xec\x4a\x18\x00\x10\x3e\x36\x42\
+\x08\x70\xf9\x11\x28\x08\x0c\x76\x58\x51\x66\x33\x20\x61\x85\x68\
+\xc5\x14\x6a\x87\x03\x7c\x7c\xcb\xed\x3f\xd1\x69\x86\x69\x21\xf9\
+\xc0\xc1\xf6\x5e\xd8\x40\xdb\x73\xc1\x91\x1c\xe9\xc5\x4c\xf5\xae\
+\xbc\x2d\x47\x31\x28\xbe\x9f\x69\x21\xcb\x80\xa9\x42\x1a\xa6\x82\
+\xcb\x8c\xc5\xe6\x70\xcd\x38\x6d\xaa\x1e\xf7\x7a\xee\x66\x8d\x49\
+\x57\x1b\x36\x64\xc3\xad\xbb\x3d\xd9\xd8\xc1\x4d\xc1\x90\xf0\x34\
+\xc0\xeb\x06\x47\xcb\x1d\xeb\xfe\xfe\x30\xf8\x61\xbf\xed\xac\xd0\
+\x75\xfc\x8c\x28\xee\x42\x56\x91\x93\x0e\x2f\x0f\x53\xa8\x82\x97\
+\x47\x76\x35\x97\x16\xd6\x39\x38\xc2\xb3\xf2\x22\x60\xd4\xdc\x34\
+\x7f\x32\x56\x0f\xfe\x39\x9a\xe1\x77\xf7\x06\xd6\x5c\xe1\xa8\x05\
+\x88\xf8\x92\x66\xdb\x9f\x34\xde\x0d\xeb\x1a\x21\xc7\xa7\x2d\x7b\
+\x00\x9d\xae\x6d\xe0\x12\x7b\xb1\x43\xfa\x16\x80\x82\xdd\xa6\x2d\
+\xe0\x28\x0d\xb8\xaa\x01\x80\x2a\x4c\xe6\xad\x70\xf3\x00\x84\x87\
+\x41\x3a\x2f\x02\x03\x66\x66\x29\x77\x20\x08\x22\xc2\x47\xf4\xc6\
+\x6e\x8a\xdf\x80\xc3\x38\x1a\x18\xc4\xef\x79\x27\x2c\xe2\x67\xc6\
+\xc1\x67\x13\x17\x9c\xd5\xd0\x85\xc0\xf1\x7c\xec\x93\x8b\x6b\x20\
+\x9f\xc0\xe6\xd1\x09\x2c\x01\x4e\x48\x5b\x0e\x67\x96\xe4\x6c\x69\
+\xc5\x7a\x10\x82\x06\x9c\x42\x66\x00\xf0\x93\xb0\x3f\x6f\x73\x8d\
+\x4d\xee\x15\x66\x7a\x83\x00\xfb\x83\x85\xcf\xee\xf6\xec\x6d\xad\
+\x58\x16\x89\x53\xa2\x04\x67\x7b\x60\xc9\xf1\x4a\x3f\xb4\xef\x0c\
+\x72\xf9\x9f\xf4\xfd\xd7\x86\xa3\xfa\x31\xb3\x4e\xe6\x38\x31\x34\
+\x4a\xca\x79\x77\x33\x35\x74\xde\x65\xeb\x84\x19\x32\x72\x75\x89\
+\x02\x5a\x77\xd3\xe2\xf9\x18\xfd\xfe\x20\x74\xd6\x3f\xc3\xc4\x91\
+\x51\x82\x3d\x6d\x9a\xb0\x81\xef\x25\x53\x5d\xaf\xb9\xc5\x75\x1c\
+\x0c\xfa\xf5\x03\x14\xad\x10\x9f\xf6\xf4\x61\x79\x87\x19\xee\x93\
+\xe9\x89\x40\x1a\x41\x40\xb5\x00\x03\x01\x51\xee\xe4\x41\x53\x1e\
+\x66\x8b\x1b\x30\x0c\x05\x42\x04\x02\x24\x49\xdd\xf1\x48\x02\x57\
+\xc5\x3b\x22\x74\xfa\x88\xc3\x10\x00\x34\x85\x67\x7d\x30\x61\x8c\
+\x55\x23\x40\x1c\x34\x06\x28\x06\x30\x11\x27\x52\x37\x8c\xa8\x80\
+\xce\xb1\x48\x18\x3a\x50\x02\xfa\x4f\x52\xba\xb2\x29\x39\x3a\x98\
+\x94\x03\x83\xae\x6c\x00\x60\xd8\xf3\x55\x01\xb0\x2c\xc5\xd6\x3e\
+\x04\x4b\x23\x38\x0b\x3a\xd4\xc2\xde\x4e\x3f\xd8\xb6\x33\x1f\xfc\
+\x6e\x3b\x02\x72\x17\x4a\x8c\xd0\x94\x5b\xc1\x1e\xe8\xf3\x77\xf6\
+\x87\xfe\xad\x83\xdf\xdb\xb0\x5b\x3f\xbd\x1c\x42\x1d\x37\xbd\x28\
+\x29\x4f\x75\x3f\xcf\x7e\x4c\x39\x9f\x67\x99\x3a\x2e\x3b\x5c\x57\
+\x5d\xc2\x31\x12\xef\x5f\xb8\xb0\xda\xb5\xff\xae\xc3\x0b\xdf\x85\
+\x59\xac\x15\x14\xa3\xe6\x37\x5a\xfe\x9c\x69\x6e\x50\x3b\xda\x71\
+\xb3\xe8\xea\x51\x50\x80\x53\x6c\x03\xa7\x60\x37\xd5\x64\xac\x16\
+\x6c\x4a\x62\x64\x4f\x41\x81\x33\x72\x00\x8a\xe3\x01\xa1\xbd\x54\
+\x4a\xbc\x24\x70\x15\x9d\x90\x18\x12\xbd\x01\x44\x4c\xf8\x31\x61\
+\x9f\xec\x99\x81\xc8\x17\xe2\xf7\x26\x47\x06\x34\x11\x80\xe8\x04\
+\x0f\x43\x2b\x30\x7e\x32\xf1\x1b\xd1\x8d\x3b\x10\x99\x65\x5e\x1e\
+\x8e\x4d\xcc\x78\x29\xe9\x1a\x4c\x4b\xeb\x40\x5a\x76\x0f\x24\x64\
+\x4d\x86\x9a\xbc\x48\x53\x2a\x94\x05\xd8\xf9\x7e\x32\x36\xb5\xae\
+\x47\xef\x9c\x97\x09\x82\x7d\x7b\xb1\xc5\xe8\xd6\xbc\x7b\xa8\x0b\
+\x1d\xd7\x70\xab\x70\x82\x76\x00\xe5\x8e\x76\xdf\xfd\xba\xdc\x1a\
+\x29\xf5\xe7\x39\x47\x89\x4b\xd4\xd4\xc0\xf9\xf6\xcb\x56\x8e\x63\
+\x18\xf1\x5e\xb9\xef\x5f\x32\xab\xde\x0d\x3f\xda\xe3\xcb\xfb\xbc\
+\xbc\x95\x66\xb7\xce\x2b\xc6\xda\xfe\xb4\xa9\x89\xb0\x12\x53\xd5\
+\xbb\xf3\x96\xb5\x1b\x3a\xc5\x36\x70\x8c\x0c\xc4\xf2\x31\xd0\x19\
+\x9a\x12\xbe\xd4\x27\x73\x90\xcd\xb3\x50\xaa\xf3\xaa\x3f\xc4\xad\
+\x3b\xc9\x33\x26\x5a\x55\xb0\x49\xb0\x04\x03\x69\x37\xb2\xb3\x80\
+\x69\x3f\x31\xc7\x88\x80\x42\xbf\x71\x18\x72\x80\xc8\x8e\x5b\x21\
+\xfe\xa2\x9d\x36\xe3\xae\x77\xf8\x2f\x7d\xd6\x87\x21\x3f\x8c\x8d\
+\xf1\xf3\x5b\x86\xcb\x90\xf3\x40\x74\xc2\x19\x27\x29\x39\x96\x29\
+\x97\x03\x7d\x65\xb2\xbd\x3f\x21\x9b\x30\x66\xcf\xe8\xe6\x95\x87\
+\x32\x17\xbd\x09\xe3\xea\xb0\xfc\xd6\x0f\xc3\xd6\x03\x9e\xff\x14\
+\xb6\x69\xdd\x8a\xc3\x78\x80\x12\xa9\x4b\xf8\xd8\x1f\x4f\xbe\xdd\
+\x99\x0b\xbe\x86\x9d\x4f\x0e\xea\x07\xa8\xcf\x99\x33\x08\x4d\x82\
+\xd4\x71\xe4\xff\x98\xd2\x1b\xf9\xf9\x38\x3e\x07\xa6\xc2\xcc\x6e\
+\x7f\x7f\x39\x7f\x74\x55\xc2\xf9\x28\xda\xd2\x0f\xf7\xfb\x50\xbc\
+\xb1\xd0\xfc\x95\x93\x6d\x6f\xea\xd4\xa4\x95\xae\xb5\xed\x8e\xac\
+\x65\x3d\x87\xd3\x0d\x37\x41\x84\x1a\x03\x1a\x69\x86\x08\xd5\x90\
+\xc8\x49\x4d\x22\x0b\x7d\x22\x87\xdd\x36\x31\xd9\x0a\x25\xa5\x03\
+\x18\xb8\x73\x5b\x90\x02\xd1\x17\xb8\x43\xc4\x25\xd4\x5f\xc4\x3d\
+\x22\xfb\x49\x39\x48\xc9\x7b\x58\xa3\x6f\xf0\x3b\xb4\x17\x09\x5f\
+\x9f\x99\x43\xf5\x5f\x74\x8f\x9c\x78\xd3\xb0\x6a\xa1\x3d\xb6\x90\
+\xda\x0b\x66\xa8\x2b\x01\x63\x43\xcf\xe2\xdd\x07\xdd\xf7\xe6\xca\
+\xa4\xb5\xbf\x42\x76\xf5\xa6\x65\x2d\xc0\x72\x0c\x25\x37\x0a\x5c\
+\xf3\xf2\x46\x1e\x84\x8a\x5d\x8d\xc0\x46\x8f\x1e\xf2\x82\x95\x1b\
+\xf2\xe1\xe6\x03\x18\xf8\xac\x05\x50\x2c\xff\x50\x4e\xc2\xff\xe8\
+\xcf\x05\xdf\x94\x78\x63\x39\x8e\xa7\x9c\x47\x8a\x7c\xb1\xd4\x0a\
+\x05\x39\xc2\x2d\xda\xdb\x52\xec\xbf\xaf\xb9\x71\xf1\x5f\xa1\xdb\
+\xff\x13\x03\xe2\x4c\x12\xf4\xd9\x5e\x3c\xde\xce\xcf\x99\x99\x70\
+\x12\x35\x8e\x7d\x14\x80\xd8\xda\x05\x8e\x81\xfb\x64\x8c\x25\x34\
+\x27\x3c\xa9\x03\xa7\x50\x6e\x01\x11\x8a\x9c\x20\xa6\x3c\xc3\x21\
+\x4a\x80\x41\x02\x23\xc1\xe2\x2a\x8a\x52\x43\x39\xc5\x10\xae\x41\
+\x7f\x08\x63\x80\x66\xc2\x11\x04\xac\x80\x21\x6e\xa5\x7e\xf4\xfb\
+\x78\x1f\xb9\x45\x8f\x71\x92\x22\x20\x00\x04\x4c\x07\x5f\x46\xa6\
+\xd4\x3e\xe4\x45\xec\x21\xc2\x8d\xf9\xba\x89\xdf\x86\xfe\x44\x93\
+\xc5\xae\x3f\xed\x03\x15\xb2\xa7\xa7\x42\x36\xf6\xa6\x50\x36\x88\
+\x0d\x3d\x61\xd7\x36\x58\x32\x73\x34\x96\x8d\xa0\xab\xb8\xf5\x40\
+\xde\x7f\x60\x4d\x2e\xd8\xd7\x0e\xb5\x05\xdd\x76\xb5\xe2\x6f\xc0\
+\x68\xff\xff\xc1\x92\x80\x9f\x68\x24\xc3\xea\x40\xdd\x46\xe8\xcf\
+\x90\xb2\x1c\xa1\x79\x88\x93\x6d\xc6\x33\xa2\xd6\x2b\x71\xc3\x92\
+\x4b\x21\x33\x7f\xae\x2b\xb0\x97\x41\x5e\x92\xe9\xb5\xe2\x5d\x32\
+\x37\x29\x15\x8d\x8e\xdb\x0a\x40\x6c\xe9\x84\xe2\x8d\x01\xbd\x09\
+\xa8\xf0\x31\xc9\x3c\x74\x8b\x41\x48\x0f\x59\x23\x42\x31\x46\x50\
+\x2d\x01\x62\xe4\xf8\xd2\x7b\x44\x50\x28\xb9\x52\x70\x18\xb0\x44\
+\xa0\xd1\xe0\xa5\x76\xfa\x35\xe4\x68\x40\x13\x3d\x33\x0e\xf3\x29\
+\xdc\x87\x81\x87\xcf\x78\xc7\x9f\xd8\x8f\x79\x34\xfe\xa2\x57\x85\
+\xf7\x7c\xa6\xd1\x30\x25\x16\xc6\x3b\xdc\x18\x97\x82\x4f\x1d\x1b\
+\x61\x37\x32\xf5\x1e\xec\x79\xa2\x17\xf5\x95\xce\xc1\x0a\xd9\xdb\
+\x5d\x25\xcf\xf6\xa4\x65\xe3\x00\x4a\x23\x02\xca\xec\x66\x55\x49\
+\x64\x37\x36\xb1\xf8\xe1\x6a\xec\x13\x94\xb3\x1d\x17\xfb\x13\x63\
+\x57\xa1\x7b\x8f\x65\xfd\x7f\x92\x3b\xa3\x0d\xad\xcf\x03\x6e\x52\
+\x2c\xa5\xe1\xa5\x38\x92\x9e\x4b\x15\xc5\x37\x2f\x29\xaf\x6a\x0a\
+\xff\x29\x1f\x84\x1f\x1f\xcc\xda\xe8\x8a\xf2\xc3\x37\xcf\x4d\x7a\
+\xa3\xc7\x27\x12\xc7\xd0\xd5\xb9\x09\xa2\xd4\x01\xd4\x69\x33\x2a\
+\xbb\x29\x91\x07\xc7\x18\x44\x2f\x14\x8e\x4f\x53\x71\x23\xd2\x29\
+\x50\x2a\x27\x03\x47\xcc\x2d\x4e\x0a\x08\x0d\x1b\x11\xfb\x30\x3b\
+\x0b\x3b\x0e\xaf\x5c\x03\x0e\x27\x04\x86\xba\xe3\x1d\x03\xc0\x94\
+\xfa\x21\x02\x22\x67\x73\xd7\xe7\x12\x92\x8f\x5f\x6a\x38\x0d\x7e\
+\xd2\x9f\x02\x74\x22\x0b\x6f\x0a\x14\x86\x25\xa0\x01\x16\x3e\x77\
+\x01\x28\xbb\xba\xaa\x65\x7d\x37\x80\x92\xb1\x75\x7c\xe7\x1a\x9c\
+\x3f\x3f\xab\x19\x53\x55\x70\xd6\xca\x46\xec\xe5\x7b\xff\x7a\xf4\
+\x2d\x83\xbd\xd4\x27\xfc\x6e\x0c\x66\x2e\xcf\xde\xba\xf6\xcb\xfa\
+\x61\x82\x04\x6b\x69\xe2\x2e\xf5\x93\x26\xe6\x1c\x7d\x51\x52\x9c\
+\xe7\x68\x0a\x5f\x28\x59\xa5\xba\xc6\xf5\x8b\xae\xaa\x71\xad\x2f\
+\x77\x5b\xf6\x22\xe9\xf6\xe5\xca\x69\x76\x7e\xc6\xcc\xa4\x93\x4b\
+\x39\x36\x4f\x50\xdf\x84\x4d\x66\xc7\xa3\xba\x46\xb9\x1e\x8e\x38\
+\xc8\x44\xc0\x20\x59\x80\x5b\x90\xd0\xb4\x05\x25\x11\x13\x20\x86\
+\xc8\x8b\x1c\xa4\xd4\xad\xe8\x57\xb9\x48\xc1\x2f\x63\x8a\xc3\x45\
+\x7e\x18\x7b\x14\x37\x0b\xfb\xc5\x73\x10\x13\x9e\xd9\xd7\xf0\xbc\
+\xeb\x03\xef\xf8\x46\xc1\x4e\x8b\x31\xb1\x1b\x9f\xd4\xae\xce\x05\
+\x18\x18\x4f\x85\x5f\xe3\xa3\xf0\x16\x96\xd8\xae\x77\x7d\xa6\x1f\
+\x72\x15\x4c\xfa\x42\x8c\x9d\x03\x95\xb2\xbd\xab\x46\x56\x75\xa5\
+\x65\x3b\x44\xaf\xf1\xe8\xea\x5e\x36\xd6\x91\x49\x0d\xb6\x74\x1f\
+\xcd\x07\x0f\x3e\x9d\xf5\xb7\x1c\x08\x13\x36\x4e\xa1\xaa\x0e\xfc\
+\x07\xbb\xb2\xfe\x47\xa1\x9b\x6c\xd2\x4f\xb2\x27\x71\x04\x8e\xc6\
+\xa3\x8f\x6f\xc4\x1a\x8a\x54\x7a\x16\x21\x73\x50\xf9\x81\xc5\xcb\
+\x41\xd5\xb7\x0e\x64\xed\x96\x06\x27\xc8\xbf\xed\x15\x49\x69\x9e\
+\x92\x74\xf7\x64\x2c\xeb\xa1\xa3\xa1\x1e\x7a\x3f\x3d\xe5\x4b\x4b\
+\x22\x03\x05\xbc\x5f\x7b\xa4\x78\xc4\x1a\x29\x89\x53\x40\x0a\xe0\
+\x40\x5c\x05\xbb\xb6\xa2\x86\xa8\x8b\x80\x29\xbe\x27\xf9\x94\x02\
+\xa8\x18\xce\xb8\x93\x0b\x29\x80\x4a\x80\x37\xfc\x99\xe1\x15\x90\
+\x88\xac\x78\x1f\xea\x36\x3c\x8c\x79\x8e\xe3\x2e\x49\x0f\xb3\x13\
+\xc5\xc3\xfb\x8b\xb9\x14\xb0\x91\xdf\x62\x7a\x0d\x27\x8d\x9f\x31\
+\x46\xc4\x9c\x48\x45\x6a\x10\x8b\x20\xfb\x64\x72\x39\x8e\x71\xb3\
+\x5c\xd9\x91\x4d\xc8\x53\x1d\xe8\xf1\xc3\xf9\xf0\xe3\x46\xbb\xd6\
+\x92\x59\x49\x67\x0c\x3a\xcd\x37\x6e\xcb\x87\x83\x8e\x3b\xad\x36\
+\x61\xbd\x37\x5c\xd0\xd4\x83\xc3\x44\x57\xf1\x10\x23\x3d\x7e\x82\
+\x07\x0c\x8d\x20\x83\xa2\x19\x81\xa6\x54\x09\xbc\x7e\xc9\xd4\x9a\
+\x44\xf8\x9d\x6e\xb1\xaf\xc6\x21\x1d\xe4\x1a\xb9\x99\xb3\x53\xc9\
+\x2e\xa0\x65\x7d\x47\x20\x6d\xe8\x8d\x99\x88\xd3\x5d\x1b\x9c\x41\
+\xa9\xb2\x07\x75\xec\x82\x94\xc3\x8c\x63\x8d\x9d\x21\x4a\x6d\x25\
+\x0d\x81\x16\x89\xbc\xc8\x09\x4e\x04\x0e\xc3\x09\x4c\x78\x43\x94\
+\xa6\x55\x37\x04\x17\x85\xc5\x37\x18\x9f\xbe\x2f\xb1\xeb\xb7\x95\
+\x28\x4d\x18\x25\xe4\xf8\xbd\xde\x49\x8e\x51\x58\xdc\xf9\xa0\xcf\
+\xd1\x3b\xb6\xea\x7c\x8e\xdd\x23\x2b\x6f\xfa\x2d\xb5\xe8\x8f\xe1\
+\x09\xea\xb7\xe8\x88\xd0\xb1\x29\xbe\xe1\x1c\x2d\x9a\xf8\x9d\xde\
+\xf1\x63\x9e\xc1\xb3\x60\xe1\x31\x75\xa6\x7c\x3c\x1c\xe8\xe9\xca\
+\x81\x9e\x5a\x59\xdb\x51\x2d\x2b\xfa\xc0\x96\xe1\xe1\xad\x4d\x96\
+\xcc\x05\x47\xc9\x74\x79\xe1\x6f\x1e\x1b\xcc\x6f\x39\x14\x26\x53\
+\xf5\xe8\x01\xf3\x83\x1f\x75\xe7\x13\x7f\x2b\x77\x3c\xd3\x61\xa6\
+\xac\x14\x3b\x51\xcc\x57\xcf\xdd\xdf\x62\x09\x9d\xbb\x69\x1c\x9a\
+\xb2\x12\xc5\xcf\x79\xdf\xe2\x3f\xad\x48\x86\xdf\xea\xc9\x3b\xa3\
+\xb1\xd9\x8e\xff\xce\xc5\x49\xa9\xc6\x59\x1e\xdb\xd0\x33\xb5\xb1\
+\x17\x4b\xf7\xd0\xf0\x8d\xc6\xc0\x5e\xad\x33\x10\x71\x8c\x08\x18\
+\xc8\x75\x69\xcb\x5d\x0a\x8a\xb8\x45\xd5\xf7\xf8\x72\xf1\x5d\x64\
+\x57\xb7\xa2\xbd\x94\x83\x94\xda\x35\x5c\xc1\x2f\xbe\x47\xa2\xc6\
+\x77\x0d\x18\x8a\xc0\x62\x05\xd0\xaf\xb9\xc3\x7e\xc2\x67\x38\xc6\
+\xe1\x69\x85\x89\xfd\x1b\xbb\x21\xe3\x52\xc0\xc4\x7e\x78\x3f\x99\
+\x89\x42\xe9\x6b\xb5\x47\x0e\x11\xfc\x14\x14\xb1\x1f\x02\x84\x76\
+\xbe\xa3\x5e\xc2\xd3\x0f\xd8\xf3\xd5\x97\x2d\x93\x6d\xc7\xea\xe5\
+\xf1\x63\xe5\xda\xe3\x35\x07\xeb\x22\x97\x4d\x76\xa4\x11\xf3\xba\
+\x36\x3c\x9b\xf5\x7e\xfa\x14\xe6\xba\xd4\xb9\x56\x83\xe5\x6f\xeb\
+\xc8\x87\x1f\x90\xdb\xd7\x3f\xa1\x1f\x64\x23\x37\x6c\xd6\xf4\xc9\
+\xd2\x79\x36\xdd\x59\xce\x23\xc7\x94\xe8\x1b\xe5\x10\xa9\x30\x57\
+\xef\x53\x59\x4c\x01\x99\x3b\x4a\xf2\x4b\x17\xa5\xdc\x81\xa4\x63\
+\xad\x6e\x0b\xd0\x9b\x8b\x41\x3e\x0e\xf0\x81\x63\x54\xe2\x72\xc0\
+\x29\x48\x52\x45\x50\x18\x62\x25\x18\x4e\xe6\xc6\x82\x51\x82\xd7\
+\x3b\x43\x83\xa8\x21\x73\xf0\x40\xcb\x02\xe7\x81\xa7\x02\x71\x47\
+\x4d\x70\x69\x9c\xca\x39\x22\x0e\x62\xe2\x2b\xf1\x1f\x7d\x9b\xee\
+\x43\x38\x08\xe3\xd4\xd4\x0e\xf5\x0b\x27\xe3\xae\xef\x0d\xd9\x32\
+\x5c\xec\xce\x97\xd1\x63\xd1\x4d\x49\x1a\x8f\xa5\x2f\xf8\x56\x83\
+\x33\x47\x43\x8d\x3e\xe3\x27\x76\x8f\xb2\x84\x67\xc3\x41\x4a\xdd\
+\x75\x04\x05\x40\xa1\x7e\xc2\x10\x47\xfa\x6a\x64\xf5\xd1\x3a\xf9\
+\x6d\x2f\xfa\xcb\x71\x3a\xe8\xdb\x5b\x6c\xe5\x26\x1d\x07\xf3\xc1\
+\xd7\x1f\xc2\xc0\x93\x67\x27\xea\xca\x82\x7c\xb7\x6f\x7d\x24\xb8\
+\x6d\xed\xb7\xf5\xcb\x25\x8d\xdd\xd0\x94\x9c\x3b\x4f\xc3\x8b\xee\
+\xdc\x49\xd9\xf0\x94\xc4\xe0\xc0\x8c\xdb\xea\xea\xd4\xad\x3d\x8e\
+\x7d\x9d\x74\x7a\xf2\x9a\xd9\x6e\x7e\xe2\xcc\x54\x62\x3f\x66\x6e\
+\x3f\x8d\xae\x5b\x1c\xdf\x0d\x71\x0a\x5c\xc3\x1a\xc0\x64\x41\x0f\
+\xc4\x47\xb1\xa0\x14\x08\xa5\xf6\x61\x40\xc1\x37\x8b\x2d\x3f\xa6\
+\xa0\x13\x10\xe0\x42\x7a\x51\x20\xa7\xc1\xe8\x7b\xc8\x35\xe8\x90\
+\xa4\xe9\x97\x7e\xb8\x89\x1c\xa7\x90\xe3\x34\x3f\x9d\x96\xc2\x4d\
+\xe4\xf0\xaa\x00\x24\x03\x80\xe2\xb7\x58\xe8\x0a\x2c\xa6\x2b\xb6\
+\xeb\x9d\xee\xb8\x48\x92\x7a\xa7\xbd\xe8\xc6\x07\x7d\x86\xdb\x10\
+\x3b\x49\x3a\x7a\x17\xbd\xe2\xcd\x78\x36\x36\xf3\x0b\x6f\xf8\x1f\
+\x6a\x4e\xe4\x86\xc8\xe8\x4f\xfd\x46\xef\xf5\x59\x1d\x8a\x60\x21\
+\x80\x28\x76\xd1\xd9\x45\x59\x73\x64\x7e\x6b\x7b\x83\x3c\xdc\x51\
+\x29\xbb\xb0\x04\xe0\x92\x2a\x91\xab\xa6\x62\x31\x25\xd6\x00\xff\
+\xf6\x91\x4c\x6e\xed\x9e\x20\x59\x85\x11\xfa\x20\x1f\x7c\xa5\xff\
+\xd6\x75\x7f\xaf\x09\x89\xeb\x75\x68\xaa\xce\x99\x27\x96\xf7\xb9\
+\x6e\x8a\xe3\x1b\x7f\x39\x7f\x72\x75\xc2\xf9\x71\x8f\xed\x5c\x8c\
+\xd5\x46\xfe\xdb\x97\x26\xa5\x12\x22\xd5\xa6\xce\x40\x76\x0d\xa0\
+\x87\x0a\x23\xbf\xb5\xca\x35\x32\xaa\x78\x93\xca\x0a\xeb\x2c\x22\
+\x31\x46\x5b\x7f\xe4\xb8\xc8\x39\x8c\x3d\x26\x4c\x05\x05\x08\x9e\
+\x8b\x8d\x58\xf3\x1e\x1a\xbf\x0c\xb6\x6c\xef\xc7\xd0\x72\x2f\x0e\
+\x2f\xec\x07\x28\x33\x98\xdd\x98\x07\x01\x70\xff\x9c\x44\x0a\xe7\
+\xac\x57\x39\x82\x39\x5c\xd2\xd8\xe2\xea\x55\x5d\x87\x73\x42\x10\
+\x3e\xc0\x1c\x2f\x16\x30\xbf\xa9\x84\x8d\x9f\x98\x2b\xe9\xf7\xf8\
+\x8c\x8f\x14\xc0\x50\xc2\x6d\x62\xff\xa5\x60\x89\x41\x10\xbb\xb1\
+\xe2\xe8\x2f\x76\xa7\x95\x26\x76\x63\x06\x68\x8f\xbb\x6e\x35\xef\
+\xe8\xde\xe6\xf7\x68\x48\xd8\xb1\xe1\xda\x92\x00\x9c\x97\xe7\xc3\
+\x93\xf0\x0b\x7e\x98\x4d\xc4\x42\xbf\x05\x8e\x12\xbb\xe9\xdd\xb8\
+\x63\x36\xb4\x72\x13\xce\xfa\x3a\xd4\x53\x27\x4f\xb4\xd6\xc9\xa3\
+\x7d\x38\x87\x1e\xdd\xe7\x6f\x99\xe2\xca\xd8\x6a\xac\xcb\x5c\x35\
+\xe8\xdd\xf7\x4c\xde\xb5\x9b\x5c\xa9\xf4\x82\x5f\xf5\x74\x04\xef\
+\x95\x9f\xe1\xac\xc3\x73\x18\x24\x51\x51\xc5\xc5\x74\xce\xdd\xcd\
+\xd4\x74\x76\x0f\xbe\x67\xe1\xc5\x35\x65\xf6\x4f\xba\x3d\x7b\x42\
+\xb5\xf8\xf9\x37\x5f\x8a\x09\xe5\x55\xae\xfd\x34\x44\x2a\x2c\xd7\
+\x96\x26\x74\xdd\xd6\x5a\x19\x4c\x89\xe0\x4a\x8d\xe1\x5c\x23\x52\
+\xc6\x41\x80\x43\x74\x0c\xe4\x5e\x09\x16\x15\xcd\x0d\xa9\xc9\x09\
+\xd8\xa2\xe7\xb0\xec\xaf\xb7\x2d\x27\x6d\xbb\x33\x72\xf0\xb9\x7e\
+\xd9\xf6\x28\x17\x7e\xc7\x86\x45\xa6\xed\x7e\xc9\x9d\xef\x8c\xfb\
+\xb8\x19\xae\x2c\xba\x26\x21\xb3\x00\xde\x86\x31\x44\x09\x48\x06\
+\x44\x47\xe0\x1d\xc7\x35\x10\x44\x43\xe9\xbd\x14\x28\x74\x3f\xfe\
+\x99\x9e\xe9\x3f\x06\x94\x7e\x95\x0e\x4a\xc2\xbc\x9b\xf7\xea\xc9\
+\x3c\x9a\x6f\x63\xd6\xae\x8b\x2b\x87\xb9\x56\xbd\x5d\xae\x74\x1d\
+\x73\x64\xa0\xcf\x6c\x34\x91\x40\xa3\x52\x5e\x15\x48\x55\xad\x2f\
+\x95\xd5\x3e\x76\x65\xe1\xca\x48\x4c\x56\xc4\x58\x11\x57\x39\xb2\
+\xa3\x8f\xc0\xc0\x3f\x7e\x8a\x40\xe1\xb3\x71\x87\x5b\xf4\x3e\xe6\
+\x26\x8e\xe5\x61\xcd\x7c\xb9\xac\x69\x6d\x94\xfb\x3b\xd3\x58\x45\
+\x19\xc8\x3b\x20\x72\xcd\x1b\xe7\xc8\xee\xe7\xb2\xc1\xed\x0f\x62\
+\x8d\x65\x83\xeb\xd6\x86\xc1\xda\x2e\x2f\x78\x07\x0e\x04\xdd\x73\
+\xae\x82\x44\x8b\x37\x2a\xcb\x73\xeb\xc6\x66\x8b\x3b\x04\x52\x91\
+\xbb\x7e\xc1\xeb\xaa\x5d\xe7\x9e\x9e\xac\x55\x3d\xad\x32\xc8\x5d\
+\x7e\x49\x3a\xd1\x89\x19\xb7\x4f\xb6\x87\x58\xb3\x80\x55\x73\x98\
+\x44\x58\x0d\x91\x2a\xa1\x2b\xbc\x49\xf4\x86\xf0\x87\xde\x87\xba\
+\x31\xe3\x6c\xbd\xd9\xd2\xbb\x5c\xd3\x8a\xa6\xb3\xaf\x23\x2f\x47\
+\x77\x0e\xc8\xae\xd5\x3d\xb2\x7b\xe5\x50\x50\x24\xb1\xb9\x1a\xfd\
+\x71\x4d\x37\x43\x2a\x08\x91\x44\xde\xb9\x63\x21\xa9\x36\xc0\x40\
+\x64\x37\x8e\xcf\xa5\xa9\x4c\x3b\xf2\x8e\xff\x99\x94\xd9\x17\x27\
+\x14\x78\x14\xbb\x0c\x48\x48\xc4\x11\x50\xe1\x6f\x28\x68\xcc\xbb\
+\x18\x00\xe6\x3e\x14\x28\x8c\x5b\xc3\xd0\x02\x13\xfb\x35\x4f\xe6\
+\x59\xed\x4a\xd1\xdc\x86\x08\x0d\x08\x80\xb1\x77\x47\x5a\x36\xad\
+\x4e\xca\x96\x67\x1c\xd9\xb2\x06\xeb\xa3\xe2\x00\xb8\xa3\x71\x97\
+\x69\x97\x06\x32\x7d\x61\x20\xd3\xe6\xe6\x65\xfc\xd4\x9c\x54\xd7\
+\xe6\x75\xc5\x62\x9e\x0b\xa9\x98\x65\x98\x18\x08\xc3\x81\x51\xfa\
+\x4c\x50\x11\x28\x0e\x14\xf8\x3c\x46\xe3\xb7\xb6\x8f\x92\x07\xda\
+\xaa\x64\x37\x38\xee\xab\xeb\x45\x5e\x39\xcd\x95\x63\x87\xf2\xe1\
+\xd7\xef\xcf\x78\x92\x76\x13\x75\xb6\xbf\xb7\x33\x2b\x6f\x95\x3b\
+\xd7\x3d\x1b\x81\x84\x2b\x17\xa3\xd4\xeb\x67\xcf\xea\x4f\x94\xf5\
+\xb3\x9a\x86\xe3\x3f\xbe\x5c\x9b\x68\x91\xe5\x12\x38\xd7\x2f\x7a\
+\x47\xda\x95\xbb\xfa\x07\xac\xc4\xa2\x66\xc9\xcd\x5f\x98\x4e\x1e\
+\xc0\xf1\xdd\xab\xa1\x6f\xb4\x40\x1f\xac\xb5\xa0\x88\x4b\x26\xe2\
+\x0c\x45\x91\x8a\x44\x34\x14\x20\x78\x46\x5d\xab\x48\x83\x2f\x92\
+\x58\x13\x58\x69\x47\x7d\xa2\xfb\xf0\xa0\xec\xdf\xd0\x2b\xeb\x7e\
+\xde\x23\x79\xcc\xe4\xa5\xb1\xb1\x74\x35\x85\x15\x78\x01\xbe\x15\
+\xa0\x35\xd5\x5d\x43\x50\xf1\xfc\x23\xc5\x18\x2e\x65\xc0\x61\xe1\
+\x43\xbc\xb8\x19\x1b\x76\x28\x91\x14\xd6\x56\x1c\xdb\x83\x79\x4d\
+\xf0\xf7\xb6\x0f\xbb\xf2\x8a\x6b\x13\xd8\x97\xca\x7c\x4b\xf5\x14\
+\xc6\x8f\x68\x0a\x5c\x02\xf6\xa1\xa2\xd6\xd0\x77\xe6\x8b\x70\x8b\
+\xc4\x44\x26\x21\x76\x33\x76\x93\x66\xba\xe9\x0b\x3e\xe2\x21\x09\
+\x70\xb4\x1d\x4e\xc9\xa3\xf7\x57\xc8\x8f\xbf\x9d\xa0\xda\x24\x13\
+\x5b\xc0\x31\xea\x0c\xc7\xa4\x77\x12\xb7\x07\xb4\xf4\x03\xd8\x07\
+\xcc\x92\x28\x79\xe5\x35\xbe\x5c\xfa\xda\xbc\xcc\x5a\x90\x91\xca\
+\x5a\x74\xe9\x62\xfd\x88\x0f\x2e\xcd\xf8\x19\xf5\xc9\x80\x42\x70\
+\xb0\x5d\xe3\x7b\x2e\x15\x66\x9e\xb8\xa7\xd0\xbe\xae\x06\x79\xf0\
+\x70\x9d\xac\xc5\x74\x95\x85\x98\x29\xfc\x86\x99\xae\x78\xbd\x5e\
+\xf8\xef\xbf\xcc\xe4\x71\x7a\x57\xb2\x3e\x15\xb6\x1f\x1b\x0c\x00\
+\x92\xf5\x4f\x62\x31\x1b\x07\x14\x8d\xe6\xcf\x04\x9e\x65\x83\xf6\
+\xf3\x1c\x33\x2c\xe1\x47\x70\x11\x1c\xef\x5f\xf8\xee\x94\x63\xfd\
+\x78\x60\xc0\x72\x2e\x9b\x60\xe5\xe6\x2c\x2c\x4b\x6e\x1f\xc0\x74\
+\x11\x4c\x49\x57\x7d\x03\xc0\xa8\x00\x40\x62\x62\x2d\xd5\x37\x94\
+\x00\x87\x83\x04\x15\x46\x77\x72\x02\x0e\x0e\x76\x1d\xce\xca\xd6\
+\x15\x1d\xf2\xfb\xaf\xb4\xc9\xc1\x8d\xd8\xe8\x0d\x84\x82\x8d\x18\
+\xc4\x81\x5e\xe1\x43\xa7\xc9\xf7\x1f\x0f\x0e\x0e\x2e\x2a\x18\x08\
+\x08\x5c\x0a\x0e\x34\xe3\x31\x47\x09\x41\x48\x39\x84\xab\xc2\x0c\
+\xd8\x6a\x8c\x01\xac\x7e\x10\x5c\x6e\x14\xf4\xa3\xe9\x88\x17\xa5\
+\x4d\x22\x53\xb1\x0e\x16\xa6\xc5\x5c\x86\x4b\xc4\x76\xbd\x13\xcc\
+\x25\xef\x8b\x76\xc4\xa1\xee\x11\x17\x8a\xfc\xa8\x5b\xd4\xd8\x53\
+\x2c\x4a\x26\x7d\xd9\xf3\x7c\xa5\xdc\xfe\xef\x55\xf2\xbb\x5f\xb8\
+\x32\x7b\x2e\x56\x3f\x8e\x47\xde\x91\x37\xea\x1a\xe4\x76\xbc\x87\
+\xb8\x98\xf6\x32\x2c\x9c\x1a\x33\x0e\x2b\x0e\x91\xd6\x7d\x6b\x6d\
+\x79\xf0\x37\x58\x6b\x7f\x0c\xdd\xe6\x58\x1f\xd2\x30\x1a\xd3\xfc\
+\x21\xa2\x71\x67\x14\xa6\xc3\xe4\xd5\xe4\x85\xdf\x45\x0c\xfa\x80\
+\x5f\xcd\x1f\xdf\x23\x09\x00\x24\x5d\x6c\xa9\x2f\xef\x93\x71\x65\
+\x08\x8c\x63\x80\x9f\x1a\x70\xe4\x50\x87\x2f\x33\xc7\x24\xac\x57\
+\xcd\x70\x9d\xdd\x7b\x72\xb9\xa3\x03\x56\x15\xd6\xa0\xbc\x2b\x33\
+\xbf\x69\xa5\xfc\xec\xa9\x5d\x0a\x92\x3d\x7b\x88\xc5\xb3\x6e\xce\
+\x35\x80\x58\xb2\x05\x62\xd5\x37\xb6\x80\x73\x2c\x7c\x4f\xd2\xb5\
+\x7f\x90\xc1\xf4\x10\x4c\x4d\xcf\x4f\x9b\x9b\x4e\x6e\xc6\xf6\x1f\
+\x7b\x41\xb8\x2d\x98\x8e\x5e\x13\x66\xb0\x86\x07\x47\xdb\xb0\x32\
+\x50\x0f\x05\xa5\x3b\x1e\xfc\x53\xb7\x22\x91\x71\xd1\x13\x37\x4c\
+\x73\xd1\xdb\x94\xc1\x8e\xce\x3b\x9e\xec\x94\x07\xff\xfd\x88\x1c\
+\xe1\xda\x59\x98\x14\x08\x81\x1c\x20\x87\x31\x14\x1f\x93\x19\xd9\
+\xf8\x11\x00\x36\xd8\x8e\x12\x04\xbe\x53\xbc\x47\x6e\x7c\x3f\xc4\
+\xdd\xf8\xb1\xf1\x1d\x2f\x6b\xc2\xd6\x00\x24\x4f\x3f\x20\x32\x7d\
+\x3e\xe6\x7f\x8d\x43\xa2\x90\x0e\x4d\x2f\x3e\x51\xe0\x72\xb0\x0f\
+\xc9\x43\x21\xed\x91\x5f\x3e\x2b\x60\x86\x82\xc2\xb8\x45\xf1\x10\
+\x1c\xf0\x87\x7f\x49\x61\xc6\xc0\x8e\xcd\xd5\xf2\x9f\xff\xa3\x0a\
+\xdc\xd1\x96\xe9\x38\xa1\x30\xc0\xea\xc0\x3c\xd2\x44\x3d\xa2\x98\
+\x8f\xc8\x8e\x30\x54\xd2\xa9\x77\xf0\x7d\x75\x33\xc6\x8f\x9a\x44\
+\x76\x3d\x65\xcb\x6f\x7e\x9e\x94\xda\x86\x84\x34\x8d\xc3\xd1\xe9\
+\x95\x7e\x61\x13\x0a\xf3\x2d\x86\x47\x60\x98\xc2\x73\x64\x67\x4a\
+\xf4\x15\x7e\x38\x9d\xbe\x2a\x9d\x91\x71\xe5\x59\x49\xe7\xd3\xf2\
+\x0c\x56\x35\xee\x6a\xf3\x65\x06\x06\xaa\x2e\x9d\x95\x70\x0e\x1d\
+\xc8\xe7\x0e\xf5\x4a\x79\x43\x4a\xde\x99\x99\xdb\xf4\x94\xfc\xfc\
+\xe9\x5d\x66\x1b\xa2\x3d\x64\x7a\x67\xd5\x9c\x4b\x00\x89\x97\xc4\
+\xfa\xce\xf5\x0b\xde\x95\x74\x9d\xbb\x32\x10\x77\xae\x9c\xea\xe4\
+\x27\x5d\x94\x4e\x6c\x00\xd7\xe0\x2c\xdc\x66\x1c\xf2\x52\x1d\x62\
+\xe0\x0f\x8a\x60\x11\x1c\x31\x81\x95\xde\x63\x62\x22\x4b\x82\x38\
+\x85\x2d\x36\xb1\x06\x55\x0e\x6d\xe9\x93\xc7\x6e\x39\x24\x3b\x1f\
+\x01\xd2\x60\x92\x58\x6e\xca\xda\xcd\x63\x60\x31\x28\x51\x3b\x34\
+\x6e\x52\x20\x0c\x37\x96\x3a\x1e\x08\x45\x90\x98\x74\x0c\x25\x3c\
+\x82\x84\xe2\x59\x12\x67\x8a\xf4\x77\xd9\xd2\x7e\x00\x47\x76\x5c\
+\x86\x65\xae\xe8\xfa\xa4\xac\x53\x00\x34\x3e\x1f\xdb\x49\x64\x2f\
+\x0e\x28\x4c\x72\x11\x3c\x71\x38\x12\x77\x3a\xed\xcb\xfe\x5d\x95\
+\xf2\xb5\x7f\xc2\xdc\x5a\x6c\xee\x30\x7a\x0a\x36\xb1\xeb\x67\x3e\
+\x08\xd8\xa1\x69\x64\xba\x99\x77\xde\x4d\x1e\xe8\x0d\xdc\x13\xdc\
+\x85\x1d\x0b\x35\x63\xb0\xba\xb0\x51\xe4\xb7\x3f\xc3\x3a\xf7\x9e\
+\x94\x8c\x9d\xe4\x4b\x5d\x43\x1e\x5c\x27\x2e\x17\xc6\x4a\xc3\xf0\
+\xc6\x0e\x9b\x5a\xe8\xae\xef\x34\x6e\x8a\x5c\x0e\xb6\x26\xca\xc9\
+\xd8\xca\x8c\x54\x7b\x65\xb2\x7a\x30\x01\xfd\x04\x20\x81\x12\x79\
+\xe9\xec\x84\x73\xe4\x60\x3e\x77\xa0\xc7\x2a\xab\x4f\xdb\x6f\xcf\
+\xcc\x1d\xfd\x98\xfc\xe7\xd3\x46\x71\x3f\xcb\x53\x53\xce\x1d\x80\
+\x70\xd0\xe8\x1b\xf7\x43\x21\x5f\xf8\xa6\x32\xd7\xf9\x09\xc0\x61\
+\x93\x73\x4c\x9c\x6d\xc0\x71\x0c\xc4\x3b\x0a\x9b\x22\x54\x05\x54\
+\xc6\xc1\xae\x51\xf0\x31\x61\x15\xee\xc3\xb8\x07\x65\x7c\xd5\x35\
+\x20\x56\xf4\xb7\x65\x65\xd3\x6f\xdb\xe4\x99\xef\x77\x48\x16\x5c\
+\x82\xa2\x14\xc5\x0b\x0f\x20\x0c\xa1\x16\x96\x97\x97\xe3\x38\xe7\
+\x5a\xa9\xaa\xaa\x42\x0b\x9c\x92\x1c\xb6\x31\xf4\x21\x53\x38\xe8\
+\xde\x32\x8a\xf9\x89\x08\xac\x84\xbb\x44\xc4\x57\x0a\x24\xe5\x24\
+\x98\xd4\x37\x6a\xbc\x2d\xbb\x9f\xc7\xe4\xbe\xa9\xb8\xa6\x21\xe9\
+\xe0\x22\x24\x28\x82\xa1\x94\xd0\x0b\xf9\xe0\x3b\x10\xd8\x90\x67\
+\xf5\x7f\x3c\x28\x14\x50\x1c\x08\x05\x38\x52\x00\x47\x67\x7b\x5a\
+\xfe\xeb\x2b\x75\x72\x78\xbb\x2d\x63\xa6\x59\x00\x07\x44\xbb\x18\
+\x18\x00\xba\x82\x02\x71\x53\x7e\x29\x4d\x2b\x13\xa4\x40\x89\xdc\
+\x49\xde\x01\xca\x85\x79\x1f\x3b\x41\xe4\xd9\x87\x1d\xd9\xbe\x39\
+\x85\xf4\x23\x3f\x2d\x59\x00\x08\x09\x52\x6e\x84\x00\x30\x71\x58\
+\xb8\xaa\x89\xbf\xc3\xe7\xf8\x1d\x57\x31\x72\x33\x8b\x96\xca\x7e\
+\xa9\xf7\xd1\x69\x30\x98\x92\xb5\xe0\x24\x33\xb1\xf8\xea\x12\x70\
+\x92\xc3\xe0\x24\x07\x7b\xa4\xbc\x3e\x6d\xbd\x0d\x9c\xe4\x77\x72\
+\xeb\xba\x83\x67\x7b\xfe\xd6\xb9\x01\x10\xf6\x83\x7f\x71\xa5\x07\
+\x70\x5c\x51\xe1\x5a\x3f\x87\xce\x91\x7a\xc5\x04\x3b\x3f\x79\x8e\
+\x01\x47\x27\xc0\xd1\x88\x5d\x43\x2a\xa1\x18\xb8\xec\xa9\x42\xa5\
+\x51\x87\x50\xe2\x38\xe1\x9d\x5c\x03\x5d\x9b\x1c\xbc\xc3\xfb\x23\
+\x5b\xfb\xe4\xe1\xaf\x1e\x90\xc3\xeb\xb1\xde\x03\x8a\x37\xb6\xed\
+\x91\x1c\x16\x8d\x52\x41\x4f\x42\x7b\x9e\x38\x71\x22\x24\x1f\x9c\
+\xb8\xd6\xd6\x26\xfd\xfd\xfd\x92\xc9\x64\xd4\xad\xba\xba\x5a\x3a\
+\x3b\x3b\x51\xc1\x1c\xd7\x88\x5b\xcd\x3f\x92\x9b\x20\xa1\xfc\xf3\
+\xba\xc1\x49\x40\xac\x73\x2f\xc5\xc6\x6e\x95\xf8\x36\xb2\xc1\x11\
+\x7e\xa6\xcf\x80\xe5\x78\x7b\x31\x7f\x45\x60\xc4\x6e\xd8\xdd\xba\
+\x90\x7f\x44\xa1\x3a\x82\x87\x59\x1d\xf7\xdf\xdd\x28\xf7\xdd\x9d\
+\x90\xd9\x8b\xc0\x39\xa0\xaf\xf1\x54\x5e\xd5\x9b\xf0\x11\xe6\x91\
+\xc4\x9a\x42\x9e\xd3\x69\xae\x38\xe6\x74\x76\xb8\x11\x38\x24\xeb\
+\x12\x90\xa8\x1d\x59\xe6\x18\x0a\x45\xaf\xa6\x49\x82\x19\xbb\xd8\
+\x1e\xeb\x07\x49\x99\xb4\x10\xe0\x1b\x4f\x39\x14\xa1\x22\x90\x30\
+\x3c\xf3\x41\xb7\xe8\xa6\x36\xb5\xeb\x3b\xe3\x4e\x90\xb8\x58\xca\
+\x4c\x90\xd4\xfa\x29\xd9\x81\x8d\x24\x56\x83\x93\xcc\x02\x27\xb9\
+\x18\x0b\xd9\x76\xef\xcd\xe7\x5a\x33\x76\x65\x7d\x4a\xde\x94\xb9\
+\xa8\xe5\x3e\xb9\x6d\x6d\xfb\xd9\x04\xc9\xd9\x07\x48\x3c\x48\xf4\
+\xde\xf9\x33\x2b\x13\xce\xfd\xfd\x39\xab\x7e\x31\x7a\xab\xa6\x43\
+\xe7\xd8\x82\x4d\x13\xc8\x39\x08\x8e\x8a\x08\x1c\xac\x04\x43\x24\
+\x31\x81\x94\xdc\x51\xa1\x24\x63\x56\x0a\x45\x2a\x0e\xf2\xed\x78\
+\xec\x98\x3c\xf9\xcd\xa3\xe2\x41\xa2\x4a\x61\x55\x9c\x07\x3d\xc6\
+\xe7\x2a\x39\x00\xa8\xbe\xbe\x5e\x2a\x2a\x2a\xe4\xc0\x81\x03\x32\
+\x30\x60\x44\x2e\xbc\x50\xd3\xd5\xd5\x25\xdd\xdd\xdd\x32\x6d\xfa\
+\x74\x19\x1c\x1c\x54\x8e\xe2\x62\xa0\x84\x21\xff\xa0\xc8\x45\x22\
+\x43\x02\xb5\xd5\x84\x9d\xad\x34\x89\xac\x0a\x1c\x6b\xc7\x56\x5b\
+\xe6\x5f\x0a\x31\x71\x3c\x08\x53\x01\x32\x3c\x2f\x7c\x2e\x72\x97\
+\xa1\xf9\x3c\xde\x6f\x5c\x16\xbc\x27\x31\xb5\x66\xcd\x13\x8d\xf2\
+\x95\xff\x5b\x2e\x4b\x2e\x46\x07\x43\xa4\x03\x69\x4b\x0e\x10\x38\
+\x48\x7b\x0d\x00\x9f\x2e\x2b\x93\x7e\xe4\xf5\x68\x5b\x3b\xb6\x4d\
+\xad\x01\xb0\x5c\x88\x54\x10\x57\x4f\x06\x12\xcd\x2f\x44\x50\xa8\
+\x6a\x55\xe8\xfd\x4a\x54\x5a\xf2\xd3\x3b\x93\x32\x63\x8e\x23\x63\
+\x27\x1a\x90\xe8\xdc\x2c\xd3\x7e\x98\xf2\xe1\xaf\xf9\x67\x68\x75\
+\x33\xcf\xc6\xce\x41\x45\xae\xbf\x19\x53\xd1\x2f\x55\x00\xc9\x73\
+\xe0\x24\x9b\x09\x12\xec\x1f\x7c\xf1\xf4\x84\xf3\xcc\xb6\x5c\xae\
+\x2f\x70\xea\xea\x92\xc1\xb2\xc1\xb9\x13\xee\x91\x5b\x57\xf7\x9d\
+\x2d\x90\x44\xd9\x52\x9a\x38\xf3\x3f\x14\xab\xb8\xa1\xc2\x75\x73\
+\xea\x2b\x53\xee\x4f\x30\x87\xaa\x79\x76\xad\xe4\x66\x02\x1c\xdb\
+\xd0\xda\x42\x2a\xc2\xb4\x11\x5f\xca\x41\xdd\x8e\x76\x52\x92\xf0\
+\x50\xe4\xb8\xb4\xd4\x4b\xee\xea\xa6\xfd\x1e\xd8\xe8\x0d\x22\xd5\
+\x40\x47\x4e\xd6\xde\x7d\x48\xd6\xde\xd9\xa1\x5d\xb6\xe4\x1c\x59\
+\x4c\xcd\xd6\xee\x5a\x80\x63\xd4\xa8\x51\x72\xec\xd8\x31\x69\x6d\
+\x6d\x95\x7f\xf8\x87\x7f\x90\xdf\xfd\xee\x77\xb2\x75\xeb\x56\xd9\
+\xb1\x63\x87\xac\x59\xb3\x46\xee\xb8\xe3\x0e\xb9\xf2\xca\x2b\x65\
+\xc7\xf6\xed\x10\xbb\x2a\x85\xdc\x24\x9f\xf7\x20\x72\x44\xad\xad\
+\xd6\x78\x11\x08\x26\x5d\xd1\x33\xde\xc5\x00\x61\x62\xf9\xc7\x0e\
+\x00\x26\x7a\xef\xf3\x98\x6a\x01\xd1\xc5\xf4\x68\x01\xdc\x48\x0b\
+\x81\xc0\xf0\x43\x45\x2a\xba\xc7\xe0\x8f\xdf\x95\x3e\x23\x0e\x82\
+\x09\xb1\xa6\xd3\x79\x39\xbc\xbf\x5a\x7e\xf2\xdd\x4a\x99\x35\xd3\
+\xb4\xfa\xf8\xa2\xbe\x73\xd0\xaf\x5c\x53\x53\x83\xef\x39\xf2\xf8\
+\xe3\x8f\xc9\xe3\x8f\x3d\x2a\x9b\x36\x6e\x90\xa6\xa6\xd1\xb2\x7a\
+\xd5\x33\xd2\x89\x86\x20\x05\x6e\x42\x73\x42\xe0\x47\x62\x19\x3b\
+\x38\x72\x83\x58\x49\x08\x80\xcc\x9a\x6d\xc9\x67\x3e\x5a\x21\xeb\
+\x57\xd6\x60\xb7\xc6\x40\x39\x21\x39\xb6\x01\x34\xf3\x62\xec\x71\
+\x9e\xf4\xce\xf8\x23\x77\xa6\x9b\x20\x21\x27\x59\x3a\xe6\x88\xfc\
+\x69\xfd\x80\x0c\x80\x4b\xdf\xfd\xbc\x27\x83\x28\xa7\xbf\x7f\x33\
+\x36\x53\xcd\xf9\xf9\x4e\x71\xe6\x57\x27\xbc\x1f\x22\xa8\xad\x74\
+\xb2\x0c\xf4\x72\x86\x0d\xcb\xf7\xec\x98\x92\xd9\x9c\x55\x37\xb9\
+\x45\xa7\xf1\x00\x00\x40\x00\x49\x44\x41\x54\x2e\xfa\x55\x6f\xe8\
+\xbc\xb1\xc5\xf6\x73\x57\x5e\x92\x4e\xee\x87\x72\xb9\xbb\x0f\x44\
+\x8c\xc2\x27\x38\x12\xd8\x97\x46\xbb\x56\xa3\x02\x2e\x12\x4e\x54\
+\x29\xca\x39\x8c\x9d\x63\x1b\xdd\x98\x98\xf5\xd4\xf7\x0f\x48\xcf\
+\x1e\xec\x66\x08\xae\x91\x87\x82\x4f\x3d\x83\x44\x4b\x91\x82\x9c\
+\x83\xe0\xa0\xf9\xc5\x2f\x7e\x21\x6f\x78\xc3\x1b\x30\x97\x0a\x83\
+\x2a\xc3\x0c\x45\xae\x1f\xfe\xf0\x87\x0a\x20\x86\x41\x0c\x10\xbf\
+\x20\xe6\xe1\xb0\x3f\x56\x7a\xdc\xe5\xcb\x78\x8b\xf2\xbc\x91\xf3\
+\x63\x80\x28\x37\x41\xb8\x14\xb6\xcd\x39\xb4\x13\x1c\xe4\x2a\x91\
+\xf7\x7f\x3c\x2b\x35\x75\x38\xd3\x09\xdd\xca\x31\x50\x94\x88\x18\
+\x27\xbf\x82\x7c\xf2\x6e\x80\x63\xec\xfa\xbd\x92\x77\x7c\xa6\x78\
+\xe3\xa0\x8c\xa8\x54\xdf\x7b\x7b\x8b\xfc\xf0\xeb\x29\x59\xf0\x0a\
+\xec\x07\x9c\x21\x24\x51\x1e\x20\x6e\x8a\x51\xeb\xd7\xaf\x83\x67\
+\x91\x8f\x7d\xec\x63\xf2\x4a\x80\x7e\xda\xd4\xa9\x52\x57\x57\xa7\
+\x0d\xc2\xc7\x3e\xf6\x71\xe5\x9e\x35\x35\x6c\x00\x90\x20\x46\x8c\
+\x32\x32\x6d\xcd\xf1\xf7\x00\x83\x1d\x2e\xca\x38\x87\x81\xbf\xed\
+\x5b\x45\xfe\xf9\xeb\x7d\x32\xef\xe2\x6e\x70\x58\x34\x61\x1a\x08\
+\x6d\x10\xd2\x85\x28\xb4\x49\x23\x77\x89\x07\x0f\x87\xbb\x71\x6c\
+\x85\x4b\x7c\x73\x18\x50\x7c\xf2\xe0\x18\xf9\x09\x46\xdd\x27\x82\
+\xb3\xfc\xf9\x9c\x04\x1a\xb8\x7c\xf8\xd5\xff\x2f\xe3\x63\xa7\x3b\
+\xb7\x3c\xef\x7d\x67\xe0\xd6\xf5\x7f\xa5\x99\x58\x0e\xb0\x2c\xd7\
+\xa8\xf5\xf1\x74\xff\x9c\x2d\x11\x0b\xdd\xb9\x5b\x20\x64\xa0\x57\
+\xe7\xc6\x85\x5f\xea\xb3\xdd\xf7\x61\x87\x33\xef\x75\x17\xa7\xdc\
+\x36\x08\xcd\xdb\xd0\xa3\x34\x0a\xfd\xee\x65\xf9\x8c\xd1\x39\x50\
+\x69\x71\xeb\x54\xb8\x13\x14\xa8\x4b\xbd\x50\x9d\x54\x11\xa8\x73\
+\xb4\x6d\xeb\x93\xdf\x7f\x66\xbf\x2a\xe2\x29\x88\x35\x39\xe5\x1a\
+\xc5\x62\x24\x10\x28\x56\x51\xd7\xf8\xf5\xaf\x7f\x2d\x6f\x7a\xd3\
+\x9b\xb4\x75\x25\x21\xc5\x32\x79\x2c\xab\xd3\xdf\x65\x97\x5d\x26\
+\x93\x27\x4f\x96\xbb\xee\xba\x4b\x46\x8f\x1e\x8d\xcd\xa9\x41\x44\
+\xa8\xe9\x62\xf7\x6f\x04\x08\x24\xe4\xa4\x20\x61\xfa\xd1\x32\x26\
+\x91\xbe\xf6\xbd\xb6\x5c\xfc\x5a\xec\x5a\xd8\x80\x83\x65\x95\x40\
+\x8a\x22\x55\x69\x2b\x6c\xf2\x16\x35\x00\x9a\xcf\x52\x7b\x04\x1e\
+\x64\x2b\x95\xca\xcb\xf3\x1b\x1b\xe5\x8b\x9f\xac\x92\xf9\x10\xad\
+\x08\x16\xfe\x91\x63\x10\x9c\xad\xad\x47\xe5\x13\x9f\xf8\x84\x7c\
+\xf5\xab\x5f\x91\x77\xbe\xf3\x9d\x32\x67\xce\x1c\xcd\x07\x3b\x23\
+\xa6\x4c\x99\x02\x5d\x6b\x82\x7c\xe5\x2b\xff\x29\x93\x61\xf7\x28\
+\x6a\x21\xad\xc4\x1e\x81\xa2\x36\x3c\x14\x9f\x61\x47\x9c\x3e\x1a\
+\x9b\x24\x98\x4e\x15\xb8\xf2\x63\x77\x25\x65\x16\x7a\xe7\x46\xb7\
+\x0c\x62\x5c\x05\x95\x10\xfb\x37\xa1\xa3\x67\x8d\x81\x91\x9a\xb8\
+\xe2\xf8\xe1\xcc\xc5\x58\x2e\xa4\x84\xa6\x8a\x8c\xa4\x72\xe5\xb2\
+\x2a\xe3\x4a\x4f\x97\x2f\x0b\x26\x27\xac\x69\x68\x93\xd6\xad\xcf\
+\x49\xb2\x3a\xb1\xd4\x5f\xd0\xdc\x1d\xae\x3b\xfc\x94\x8c\xc2\x30\
+\xc0\x96\x2d\x8c\xe1\x8c\x98\xb3\x03\x10\x8a\x56\x2b\xf6\x04\xe9\
+\x1b\x16\xdf\x90\xb1\xed\xcf\x71\xb7\x91\x37\x63\x2d\xc7\x20\xe6\
+\x56\x6d\x82\x8e\xc0\xed\x31\xd3\x00\x87\x72\x8e\x08\x1c\xa5\x4a\
+\xa9\x21\x9e\x98\x48\x40\x38\xc8\x05\x95\xd1\xc3\x1b\xbb\xe5\xf1\
+\x2f\x1d\xd4\x82\x4b\x60\x4f\xa7\xdc\xb1\xa8\x49\x83\x0b\x2b\x9e\
+\x66\x2a\x5a\xcf\xfd\xfb\xf7\xcb\xf2\xe5\xcb\xe5\xc6\x1b\x6f\x54\
+\x77\xd3\x5b\xe5\x28\xd1\x93\xf0\x79\xd1\xd0\x9d\xf6\x99\x33\x67\
+\xaa\x02\xff\xfb\xdf\xff\x1e\x47\x0a\x4c\xc0\xc9\xb0\xdd\x2a\xbb\
+\x17\xb8\x04\xc4\x03\xb5\x23\x61\xbc\x1b\xa0\x0c\xe3\x24\x70\xa7\
+\xe8\x77\xe8\xa8\x2d\x97\xbd\x3a\x90\xe6\xb1\x58\xc6\xfa\x07\xf4\
+\x10\xe6\x91\x49\x36\x79\x1d\x06\x0e\x28\xe8\x24\xb5\x04\xf4\x8e\
+\x81\xfe\xb4\xfc\xec\x07\x8d\x18\xf4\xc4\xb2\x8b\xd1\x1c\x15\x47\
+\xfa\xd1\x5a\xa4\x52\x49\x79\xe6\xe9\xa7\xe4\x9b\xdf\xfc\x86\xfc\
+\xcd\xdf\x7c\x58\x45\x4a\xea\x1b\x34\x6c\x08\xe2\xbc\x51\xd4\x7c\
+\x7e\xdb\x36\xec\xd3\xbb\x09\xc0\x19\x65\x40\x82\x3c\x6b\x69\x21\
+\x01\xf1\x9d\xe1\x62\x3b\xd3\x44\x90\xe0\xe8\x36\x6c\x27\x8f\x23\
+\x14\x57\xa5\x64\xd6\xa2\x00\x5d\xc2\x83\x70\x47\x65\xe0\xbd\xfa\
+\xc5\xaf\x09\xc3\x67\xe3\x52\x6a\x33\x1e\xcd\xb6\x43\x49\xac\xdb\
+\x69\x2a\xc3\x26\x42\x98\xc3\xf5\x74\x3f\xe2\xc0\xb6\xf7\x0b\x67\
+\x24\x2d\xe8\xee\xfe\x86\x6d\x79\xbb\xb6\x4c\x5e\x3f\x38\x6f\xcc\
+\x13\x72\xd7\x43\x3b\xcf\xa4\x3e\x62\x28\x81\xb9\x3f\x53\x86\x4a\
+\x39\x77\x1e\xb9\x61\xc9\xe2\xd0\x0a\xbf\xca\x82\xb8\x7a\x56\xc2\
+\xb3\x1b\x12\x36\x47\xc8\xb9\xf9\x73\x0a\x27\x4f\xba\x46\x26\x02\
+\x91\x98\x16\x36\x2e\x5e\x54\x89\xa6\x94\xee\xb4\xb2\x65\x66\x57\
+\xe4\x81\x75\x5d\xf2\xe4\x57\x0e\xe9\x3b\x17\x3b\x04\xe6\x31\x15\
+\xa5\xd4\x90\x2b\xb0\x2b\xf7\xe8\x51\x33\x9f\xe2\xcd\x6f\x7e\xb3\
+\x12\x33\x89\x85\xad\xed\x70\x43\x42\xa7\x3b\xdf\x97\x41\xb1\x7d\
+\xcd\x6b\x5e\xa3\x5e\x18\x4f\x2a\x8d\x79\x28\x34\xa4\x60\x12\x91\
+\xde\x48\x02\xe6\x6f\x88\x9b\xbe\xa7\xc8\x61\x06\x29\x49\x14\xbd\
+\x18\x17\x21\x17\x22\xd7\x63\x58\x8a\x8c\xcc\x1f\x09\x2f\x96\xd3\
+\x69\x37\xa2\x64\x29\x38\x68\xc7\xa5\x7e\xa1\x6b\x61\x82\xe6\x8e\
+\x2d\x75\xf2\xd3\x1f\x25\x65\x3c\x74\x0f\x4e\x46\x64\x79\x28\x48\
+\xa3\x3c\x65\xb3\x66\x20\x94\xe9\x8e\x39\x24\x41\x1f\xe7\x99\xba\
+\xd5\x15\x97\x5f\x2e\xad\x47\x0e\x4a\x1a\xdd\xdb\x4c\x87\x82\x3d\
+\x06\x7d\x74\x57\xd0\x13\x38\x51\x7e\xf8\x9d\x3c\x74\x92\xba\x66\
+\xec\x66\xbf\x0b\x53\xe6\xbe\x5b\x23\x5d\x1d\x69\x00\x13\x5c\x08\
+\x79\x8d\xd3\xaf\xf9\x2b\x16\x95\xa6\x9f\x6e\xe6\x32\x79\x71\x20\
+\x66\xf9\xd8\x6a\xab\x2a\x3d\x20\x57\x8d\x69\x97\x8b\xcb\x03\x79\
+\xb4\x5b\x64\xfd\x3e\x5f\x16\xe2\x70\xa2\xcb\xa6\x39\xf9\xce\x01\
+\xcb\x6a\x48\x5a\xb7\xc8\x75\x0b\xc6\xaa\x3e\xc2\x46\xf6\x0c\x18\
+\x96\xf5\x99\x33\xd4\x3b\xa8\x94\xdf\xbc\xa4\xbc\xdc\x0a\xbf\x9b\
+\xf5\xec\x8a\xc5\x63\xec\x5c\xdd\xf8\x84\xbb\x05\x9c\x03\x62\x3a\
+\x96\x67\xe6\x24\x41\xe1\x9c\x15\x81\x94\x1d\x77\x45\x85\x6b\xc0\
+\x01\x22\x42\x0e\x0e\xac\xe9\x94\x67\xbe\x79\x58\x3d\x3b\x50\x22\
+\x3d\x00\xad\xd4\xb0\x52\x69\x1a\x1a\x1a\xb4\x67\xea\xd2\x4b\x2f\
+\x95\xb1\x63\xc7\x96\x7a\x39\xa1\x3d\x26\x06\xbe\x8c\x75\x14\x9f\
+\x80\xe2\x47\x91\xb2\xf8\x4f\xd3\xaa\xe9\x32\xc4\xa9\x29\x57\x0a\
+\x60\x36\x8c\x1b\xf5\x05\x12\x15\xc3\xf5\x74\x42\x35\xa7\x78\x85\
+\x68\x62\x25\x3d\x26\x28\x03\x92\x18\x1c\x0c\x1f\xdb\x8b\x40\xa1\
+\x9b\x72\x8f\xbe\x72\x79\xe2\xa1\x4a\x99\x34\xd6\xc4\x4b\x22\xe6\
+\x45\xc3\xdf\x71\x13\x26\xc9\x3d\x3f\xb9\x57\xb9\x5f\x5c\x06\xf1\
+\x5d\x3d\xe1\x87\x60\x99\x34\x69\xb2\x79\x44\xd8\x82\xa2\x8e\x84\
+\x68\xda\xe3\x3b\xdf\x95\x5e\x48\x39\xf3\x93\xc5\xfc\xaa\x09\x0b\
+\x45\x1e\x79\xc0\x91\x07\xee\x45\x0f\x4b\xd6\xd1\xb4\x31\x42\xe6\
+\x45\xc1\xcf\x3b\x2a\x8c\xcf\x88\x02\xd7\xd0\xbc\xd0\x8d\xba\x88\
+\x17\x24\xb0\x47\x70\x8f\xbc\xba\xb9\x53\xa6\xa3\x2b\xfe\x67\x47\
+\x02\xd9\x8b\xa5\x0c\xd7\xbc\xaa\x2c\x51\x51\x16\xe6\x71\x36\xd0\
+\x84\xda\x1a\xe7\x5b\x9a\x58\x36\xb2\xcb\xb5\xad\x30\x69\x3f\x4d\
+\xbf\x67\x12\x20\xdc\xe8\x18\x23\x7c\x18\x94\xf3\xc2\x2f\x0c\xd8\
+\xf6\xe2\xa6\x44\x90\x9f\x34\x33\x99\xd8\x8d\x6e\xd7\x3c\xde\x94\
+\x83\x6b\x24\x7c\xb4\x78\x2c\x44\xcd\x70\x44\xe8\x6c\x61\xb5\x60\
+\x23\x77\xb4\x86\x68\xd8\x94\x73\x1c\x04\xe7\x58\xfd\x9d\x23\x90\
+\xb1\xf0\x0f\x84\xf9\x50\xee\x4b\x4d\x29\x41\xc4\x76\xca\xde\xe4\
+\x0a\x2f\x64\x62\x5d\x84\xf7\xe7\x9e\x7b\x4e\xbd\xa7\x92\x29\xc9\
+\x62\x10\xd1\x8c\x4a\xb3\xb2\x41\x38\x9a\x5a\x93\x68\x93\x4e\xe3\
+\xa6\xdf\xd3\x74\x17\x89\x8b\x39\xe8\xee\x80\xdc\x00\x5d\xc1\x6c\
+\x35\x64\xf2\x54\x20\x24\xf8\x8f\x89\x69\x08\x81\xd1\x5d\x09\xcb\
+\x70\x9b\x24\xb6\x2c\xda\xbb\xa3\x56\x56\xfc\x38\x29\x4d\xe3\x29\
+\xf2\xa0\x75\x57\x62\x06\xe1\xb2\x70\x60\xaa\xa0\x43\x79\xd0\x99\
+\x54\xf9\x56\x97\xe2\x0f\xd3\xc6\x7c\xd1\x8c\x1f\x3f\x4e\xef\x79\
+\xe4\x4b\xc5\x30\xe6\x09\x17\xe3\x31\x77\x3e\x17\xb9\x07\xdd\xe2\
+\x4b\x41\x82\x4e\x81\xc5\xd0\x7f\xee\xfa\x6e\x52\x56\xad\xa8\x45\
+\xd9\x90\x3b\x9a\x74\xc2\xab\xa6\x9b\x77\xea\x58\xb8\x29\x55\xab\
+\x9d\x6e\x51\xdd\xc6\x79\xf5\xc0\x49\xc6\xd5\x74\xc8\x35\xa3\x7a\
+\xb0\x85\x10\x0e\xa6\xdf\x0d\x31\x12\xa1\x6e\xbc\x06\x53\x56\x7b\
+\xbc\xa0\xc7\xb6\xff\xa4\xec\xc6\x45\xff\x53\x13\xcc\xcd\xb3\x4f\
+\xb3\x39\x73\x00\x59\xbe\x4c\xe5\x98\x24\x66\xe7\x82\x5b\x7e\x98\
+\xc7\x2c\x5d\x7c\x51\xc2\xe9\x40\xad\xb6\xa2\x47\xa4\x0a\x2d\x88\
+\x93\xe3\xfc\x57\x18\x2d\x38\x16\x5e\x74\x45\x85\xc0\x42\xa5\x61\
+\x61\xaa\xce\xb1\xa9\x5b\x56\x7f\x0b\x9c\x03\xc6\x4c\x30\xc4\x7b\
+\x8d\x40\x9d\xf4\x87\x44\x40\x71\xa2\xa5\xa5\x45\x95\x53\x3a\xf6\
+\xf4\xf4\xa8\xac\x5d\xf4\x75\x62\x1b\x45\x12\x9a\xdd\xbb\x77\xcb\
+\x27\x3f\xf9\x49\xb5\x73\xbc\xc4\xb4\xd2\xfc\x10\x13\x18\xa5\x91\
+\x44\x53\x78\xa0\x9b\x79\x36\x6e\x0c\x6a\x12\x46\xb9\xa0\xab\x1d\
+\x63\x34\x18\x7c\x1b\x22\x62\xe1\xb5\x01\x49\xdc\xd2\xf2\x5e\xda\
+\xd2\xc2\x8e\xda\x62\x2c\x09\xcc\x28\xc8\x61\xec\x60\xdd\x33\x95\
+\x92\xe4\xc0\x27\xba\x60\xf5\x8f\xdf\x54\x90\xd0\xcd\x95\xdd\xfb\
+\x0e\xca\x45\x17\xcd\xd6\x1e\x2b\xa6\x60\xb8\x89\x01\x32\x61\xc2\
+\x44\x79\xc3\xb5\x6f\x94\xc3\x47\x5a\x75\x00\x91\x71\x19\x91\x0a\
+\x36\xc6\x59\x12\xaf\xba\xc7\x6e\xf1\x1d\xfe\x39\xf9\x71\xee\x3c\
+\x91\xdb\xfe\xa5\x5c\x76\x6e\xa9\xc6\x60\xa4\xb6\x85\x88\x27\x2e\
+\x1f\x53\x6f\x71\x43\x80\xa0\x9a\x17\x12\x20\x1b\x04\xe6\x4b\xdf\
+\xe1\x1e\x42\x71\x9f\xd1\xd0\x26\xaf\xad\xcb\x08\x36\xe3\x90\x87\
+\x76\xfa\x52\x33\x3a\x61\xfd\xd9\x2b\x93\x41\x80\x89\x8e\xe8\x44\
+\xfb\x57\x88\xe8\x97\x6a\x83\x4b\x91\xfd\x34\x9a\x33\x03\x10\xf6\
+\x5f\x93\x25\xbe\xff\xe2\x66\x54\xe6\x97\x04\x80\xb8\x6a\x9a\x9b\
+\x0f\x6a\x12\xf6\x2e\xb4\xf8\xd5\x6c\x71\x00\x0e\x1e\x67\x4f\x54\
+\xb0\xb0\x68\x78\x2f\xbd\x62\x37\x07\x2d\x4b\xdb\xb6\x5e\x59\xf5\
+\x35\xa3\x90\x53\xac\xf2\x71\x6e\x87\x1a\x62\x84\xa5\x0f\x43\xf9\
+\x7a\x3a\x06\xfa\x28\x5a\x1d\x3a\x74\x48\x56\xaf\x5e\xad\xee\xbf\
+\xfa\xd5\xaf\x74\xfc\x43\x1f\x4e\xf2\x13\xeb\x26\x54\x66\x39\x26\
+\x42\x33\x7b\xf6\x6c\x69\x6b\x6f\x8b\x14\x74\xf3\x1d\x4d\x6d\x54\
+\xdb\xc3\x41\x51\x20\x2e\xe4\x82\x5e\xa8\x87\xd4\x63\xd6\xec\xb1\
+\x56\x70\x4c\xea\x0b\x6c\x65\x0b\x2d\x68\x04\x06\x7c\x27\x06\x06\
+\xc3\x18\x7b\x0c\x14\x12\x12\x7a\xc3\xb0\x99\xf6\xd1\xc3\x35\xf2\
+\xcc\x23\x29\x6c\x6d\x64\xe2\xe5\xb7\xe2\x16\xdf\xc1\x72\x48\x8a\
+\x4e\x83\xfd\x5d\xf2\xb6\xb7\xbd\x4d\xb9\x25\xf3\x43\xb7\x52\x13\
+\x97\x53\x73\x73\x93\xbc\xee\xb5\xaf\xc5\x98\xcf\xf3\x26\x6f\x0a\
+\xb2\x52\x8e\x51\x6a\x67\x5e\xf0\x1c\x01\x51\xf3\x08\x3b\x4f\xd2\
+\x4a\x41\x2c\xaa\x18\x6d\xc9\xcf\xef\xa8\xc2\x94\x97\x32\xe8\x23\
+\x00\x09\xea\x83\x20\x19\x02\x8c\x02\xf0\x99\x7f\x96\x63\x74\x45\
+\x79\xe7\xd1\x0d\xe4\x40\x0b\x47\xb5\xcb\xd5\x38\x24\xfb\x59\x8c\
+\xe1\xae\xdf\xef\xcb\xec\x39\x29\x77\xe9\x64\x3b\xd7\x9d\xb5\x93\
+\xb5\x4e\xf0\x15\xb9\x76\x5a\xca\x8c\xa3\x41\x74\x3f\x4d\xe6\x4c\
+\x28\x3a\x3c\xbe\x4b\x9b\x93\x2a\xdb\xfb\xb7\xde\xc0\x99\x30\xa3\
+\x26\xc8\xd7\x8d\x4b\x24\xb6\x81\xa8\x51\xa6\x92\xc0\x82\x04\x47\
+\x27\xfe\xb0\xfa\x4d\x6b\x02\x67\x63\x68\x89\x0a\x90\x0e\x9c\xaa\
+\xde\xb5\xaf\x5f\x9e\xfa\xd2\x7e\x7d\xef\xa0\x05\xf5\xd1\x2d\x5c\
+\x6a\xd8\x32\x52\x21\x27\xa7\xe0\x45\x73\xed\xb5\xd7\xca\x55\x57\
+\x5d\xa5\x3d\x52\xec\xae\xe5\x45\x13\x13\x89\x3e\x44\x3f\xa5\xc4\
+\xf4\xd3\x9f\xfe\x54\x3e\xfd\xe9\x4f\xa3\xf7\x6b\x9a\x1c\x3e\x7c\
+\x18\x95\x4e\x05\x3d\x4e\x54\x5c\xb1\x4c\x37\x13\x69\xe2\x53\xa2\
+\x89\x9e\x59\xfb\xe6\x1b\xc8\x19\xf4\x8e\x72\xf4\xae\x1d\xdd\x8b\
+\x39\x52\xd8\xa1\x50\x97\xc0\xe2\x7d\x81\x78\x10\x73\x6c\x27\x51\
+\x29\x78\xd4\xad\xc4\x8e\xc1\x35\x2a\xc1\x3b\x9f\xaf\x96\xad\xcf\
+\x62\xe4\xf9\x4a\xec\xf9\xc5\xae\x5d\xd0\x3e\xbf\xc3\xbc\xb3\x0b\
+\xf7\xb7\xbf\xf9\xb5\xfc\xdb\xbf\x7d\x5e\x07\x3b\x99\xad\x13\xe5\
+\x93\x6e\x71\x5e\x97\x2e\x5d\x42\x6f\x58\xfb\x01\x1d\x10\xe3\x3c\
+\x1e\xba\xa9\x4c\xae\x10\x27\x08\x3a\x62\xde\xea\xe7\x44\x3f\xc4\
+\x1e\xda\x38\x00\x36\x94\xc7\x7f\xef\xc8\xac\x85\xd5\x72\xed\x75\
+\xe8\x6c\x41\x7a\x3d\x74\xff\x9a\x12\x33\x00\x27\x4c\xc9\x9b\x4b\
+\xdd\x4c\xbd\xb3\xac\x50\x06\x00\x07\x67\x00\x73\x1f\xae\xcb\x46\
+\x77\xca\xe1\x2c\xa6\xcf\xb4\x86\x32\xa6\x46\xe4\xea\x2b\xd2\x89\
+\xd5\x77\x0f\x78\x5d\xb6\x73\x49\x79\x4b\xd5\x27\x81\x9d\x4f\x9d\
+\x28\x3d\xa7\xca\xed\xf4\x03\xe4\xe6\x25\x3a\x5a\x5e\xf6\x81\xc5\
+\x7f\x8a\x99\x23\xef\xa3\xb2\x31\x7d\x5e\xd2\x39\x8a\xc1\xc0\x7e\
+\xac\xd9\xae\x81\xde\xe1\x78\x98\x4f\x82\x92\x61\x81\xc5\x26\x6e\
+\x59\x4d\xc1\xb1\x95\x34\x23\xe4\x9c\x74\xb8\xea\x96\x7d\xea\xcd\
+\xc5\x4c\x5c\x0f\x13\x0f\x87\x1b\xea\x17\x14\x85\x5e\xf9\xca\x57\
+\x62\x1b\xa6\xeb\xf4\x4e\xbd\x83\x23\xca\xa5\x84\xc2\x38\x4b\x9f\
+\x19\x4f\x4c\x30\xb4\x73\x74\x9d\xe1\x6b\x30\x89\x71\x60\xa0\x1f\
+\xad\x24\x8e\x4e\xe3\x41\x7f\x48\x28\x2b\xd2\xa4\x58\x2d\x1a\x8f\
+\x3e\x33\x13\xfa\x9e\x4f\xf8\x2b\x79\x26\x40\xd2\x38\x90\x66\xcf\
+\x5e\x0c\x38\x62\xbd\x36\x09\x45\x81\xc0\xbb\xda\x4d\x8b\x1a\x83\
+\x83\x6e\x0c\x1f\x83\x86\x8a\x7e\x12\x33\x62\x07\xfa\x2a\x65\xd3\
+\xda\x32\xac\xef\x00\xb8\xd8\x76\x02\x20\x14\x7d\x02\xf4\x1b\x57\
+\x2b\x38\x1e\x92\xbf\xfa\xd0\x87\xe4\xe6\x9b\x3f\xa8\x1c\xa1\x34\
+\x4f\xf0\x7d\x42\x33\x6d\xda\x34\x79\xfd\xeb\xaf\x95\x83\xe0\xb4\
+\xa3\x1a\x1b\x40\xa0\x46\x44\x3a\xde\x33\x33\x14\x01\x86\xd6\x12\
+\xc3\x74\xe7\x06\x45\x16\x2d\x09\xe5\x7b\x5f\x4a\xcb\xd4\xd9\xd5\
+\x72\xd1\xe2\x2e\x4c\xed\x61\x4e\x4c\x1e\x19\x96\xeb\x44\x0a\x80\
+\x80\x3b\xa3\x29\x5c\x88\x9a\xef\x18\x17\x7b\xb6\x9a\x2a\xbb\xe4\
+\x4a\xec\x21\xb4\xf3\x48\x95\x3c\xb4\xc7\x93\x77\xcd\x49\x58\xd7\
+\x5f\x95\xb2\x6e\x7f\x20\x2b\xc9\x7a\xf9\xc4\xc0\xfb\x17\xfe\x52\
+\xbe\x7f\xcf\x2a\xed\xfa\x8d\x8f\xb9\xe0\xc7\x4e\x91\x31\x29\x3f\
+\x45\x91\x1d\x17\x4d\xdc\x6b\x75\xdd\x92\x1a\xf4\xfa\x7f\x46\x30\
+\xfd\xfb\x8a\x89\x4e\x9e\x6b\xc9\x0f\x60\x43\xe9\x4a\x8a\x17\x68\
+\xb1\xd4\xa0\x40\xb4\x90\x78\xa7\x25\x32\x6a\x05\x21\x73\x10\x30\
+\xd7\xe7\xc9\x96\x5f\x1c\x92\x2c\x4e\xb0\x74\xb1\xce\x62\x38\x38\
+\x62\x62\x1f\x33\x66\x8c\x86\xfe\xe0\x07\x3f\x28\x7f\xf7\x77\x7f\
+\x27\x8b\x17\x2f\xd6\x99\xba\xf1\x7b\x12\xcc\x8b\x01\xc7\xeb\x5e\
+\xf7\x3a\x8d\x87\xc0\xe2\xbc\xac\x78\x1c\x81\xd5\xab\xa9\xd5\xb4\
+\x2a\x0c\xcc\x23\x9f\xa3\x3f\x7a\xe1\xf7\x0a\x7f\x78\xd6\xae\x5e\
+\x70\x40\xfa\x1a\x00\x40\x48\x2c\x24\x7e\x12\x43\x0c\x82\x02\x38\
+\x34\xfc\x50\x77\xbe\x73\x31\x1b\xb6\xed\x48\xb5\xac\x7b\x3a\x21\
+\xa3\xd1\x11\x47\x65\xdf\xe8\x44\x66\x46\xf2\xbe\xfd\x07\x30\xf8\
+\xf9\x3a\xf9\x27\xe8\x4c\x9c\x9d\xfc\x42\xe0\x88\xcb\x84\xe3\x21\
+\xaf\xb8\xfc\x32\x4c\x43\x79\x0e\xa0\x8b\xc6\x84\x86\x8b\x52\xcc\
+\xcf\x1f\xba\x90\x40\xe6\x91\xf5\x37\xa9\x45\xe4\xd7\x3f\xaa\xd4\
+\xae\x5f\x2e\xde\x22\xa6\xe8\x3e\xf4\x32\x0d\x42\xa1\x31\x24\x3d\
+\x94\xf8\x61\xe1\x53\xdc\x9a\x56\xd7\x21\xaf\xab\xce\xc9\x4e\xac\
+\x6c\x5c\x77\xc0\x97\x49\x53\x13\xce\x25\xd8\x56\xb6\x0b\xa2\x56\
+\x83\x8b\x71\x34\x1a\x82\x63\xb9\xb6\x33\xfa\x78\xaa\x7e\x4e\x27\
+\x40\xb0\x00\x02\x3b\xe8\xc1\x94\x57\xfb\xff\x23\x67\x39\xb3\xc7\
+\x97\x05\x1e\xbb\x74\xf7\x01\x1c\x14\xad\x1c\xf6\x06\xa1\xd5\x33\
+\x84\x04\x8f\x70\x8b\x0d\xad\xf1\x23\x7b\x4a\xb8\x43\xc8\xee\x47\
+\xda\xe4\xe8\x2a\x4c\xf5\x20\x38\xb0\xd7\xee\xc9\x4c\xdc\xc7\xcf\
+\x99\xb8\xb1\x89\x41\x41\x60\x50\x16\x8f\x09\x23\x7e\x1f\x0f\x9c\
+\xf1\x99\x62\x55\x0c\x8e\x49\x93\x26\x61\x8c\xe0\x88\x24\x55\xb4\
+\x42\x05\x32\x55\xfc\xd7\x1b\x7e\x60\x8a\xf6\xf8\x9d\x81\x85\x66\
+\x40\xfd\x46\xcf\x28\x0d\x33\x3c\x61\x49\x5f\x0f\x8a\x1e\xcf\x0a\
+\x88\x28\x8e\x02\x38\xf8\x8c\xab\x08\x1e\xf3\xcc\x5e\x2f\x8c\xcc\
+\xc9\xde\xdd\x15\xf2\xdc\x36\x07\x62\x24\x1e\xc1\x95\x98\x97\x44\
+\x32\x21\x1c\xf3\xe8\xee\xe9\x95\x7f\xfd\xd7\x4f\xa3\x67\x6a\xfc\
+\x0b\x82\x03\x9f\xd0\xb0\x2c\x13\x82\x7f\xfe\xfc\xf9\x70\x41\x47\
+\x09\xe2\x8b\xcb\x48\xeb\x06\xcf\x27\xbc\x23\x95\x43\xff\x4c\x7e\
+\x70\x38\x91\x34\xa0\x63\xec\xd1\x87\x1c\x59\xf3\x18\x56\xbc\x33\
+\xdf\xd4\xb7\xf8\x3d\xbd\x4a\xed\xb1\x5b\x7c\x8f\xdf\x99\xc6\x83\
+\x73\xb6\x52\x18\x44\x5c\xd8\xd8\x29\xf3\xcb\x42\xf9\x4d\x1b\xd6\
+\x05\x61\x11\xdd\x95\x97\xa4\x1c\x48\x23\x61\x87\x58\x57\x57\x7d\
+\x60\xf1\xcd\x88\x56\xf4\xb0\x24\xb5\x9c\xba\x9f\xd3\x07\x90\x65\
+\xe8\xb5\x5a\x0e\x6e\x7a\xe3\xfc\xb9\x38\xbe\xf8\x1f\x04\xd3\x6a\
+\x66\x4d\xc5\xc1\x92\xa8\x76\x9e\x15\x9e\x84\xf2\x6b\x61\x6a\x03\
+\x4a\xbe\x90\x9b\x62\x01\xc6\x4e\x8a\x2f\x54\x96\x25\x47\x36\x74\
+\xc9\xae\x5f\x1e\x13\xd5\x39\xba\x4e\x0c\x0e\x56\x34\x4d\x5f\x1f\
+\x76\x93\x83\x79\xf2\xc9\x27\x55\x6f\xa0\xbd\xb4\x82\xf9\x5c\x6a\
+\x08\x1e\x82\x8a\x6b\x40\xbe\xfd\xed\x6f\xcb\xdb\xdf\xfe\x76\xb4\
+\xbe\x75\x3a\xe5\xfd\xe0\xc1\x43\xa6\x4b\x58\xe3\x26\xa1\xb0\x22\
+\xa3\x94\xd2\xae\xe9\x57\x47\xf3\x0d\x7d\x87\xd8\x49\x54\xd1\x1f\
+\x9d\x62\x7f\xf1\xbd\xb7\x0b\x3b\xa3\x43\xcc\x2c\x55\xd4\x55\xb4\
+\x30\x41\x0b\xe0\x60\xf4\xca\x5d\xe0\xee\x62\x60\x30\x3b\x88\x1d\
+\x0c\xb7\x94\x61\x6d\x39\x73\xa0\x11\xab\xc2\x9c\xc4\x14\x9a\x27\
+\x1e\x7f\x4c\x96\x7f\xea\x9f\x95\x63\xea\x5b\x4d\x1b\x6d\x7f\xd8\
+\xc4\xe5\x36\x15\x62\x68\x5d\xe3\x18\xed\xe1\xb3\x21\xbb\x95\x96\
+\xd9\xc9\xed\x9a\x55\x66\xd7\xe4\x91\xf9\xc6\xc5\x5e\xad\x8b\x2e\
+\x12\xf9\xd5\xed\x69\x39\xb4\xb7\x02\x0d\xcc\x0b\x70\x11\xa0\x08\
+\x51\x98\x78\xe2\x7b\x94\x77\x76\xfd\x36\x94\xf7\xc8\xa5\xf5\xa8\
+\x57\x34\x96\x8f\x61\x00\x31\x5d\xeb\xda\xef\xbc\x18\x67\xe2\x61\
+\xfc\x0c\x93\x1f\x3f\x29\x7f\x31\xb7\x49\xb9\xc8\x29\x9e\xd0\x78\
+\xba\x00\x42\xc5\x1c\xd4\x8f\xdd\x3d\xc4\xfd\x67\xac\x26\xab\x58\
+\xd8\x64\x63\xbe\x80\xeb\x1e\xce\x86\x61\x19\x0b\x23\x16\xad\x58\
+\x18\x51\xe1\x68\x53\xc3\x40\x7c\x66\x69\xc1\xb8\x38\x48\x9c\x4a\
+\xf9\x46\xac\x02\xa4\x09\x39\x15\xea\xc4\xf8\xd0\xf7\xfc\xa1\x32\
+\xcd\xc9\x78\x77\xdf\x7d\xb7\xea\x1f\x8f\x3f\xfe\xb8\x56\x1a\x09\
+\x21\x26\x86\xd8\x33\x9f\xd9\x5a\x92\xdb\x50\x19\xff\x10\x64\x77\
+\xae\x0f\xa1\x92\x4f\x79\xbc\xbc\x1c\xa9\x2d\x01\x07\x53\x4b\xa3\
+\x04\x11\xd9\x79\x33\xa0\xe1\x0b\xbe\x8b\x9e\xe8\x55\xed\xea\x21\
+\x4a\x03\x1d\x31\xa1\xb2\x13\xa3\xf4\xe8\xf9\x31\x00\x01\x08\xa2\
+\x3c\x33\xde\x58\xdc\x2a\xd8\xe1\x9f\xdc\x85\xe2\x55\x4f\x67\xa5\
+\x6c\xdd\xc0\x25\xb0\x10\xaf\x34\x3c\x46\xe8\x1d\x9c\x6e\x3b\x98\
+\x95\x99\xb3\xe6\xc8\xb2\x57\xbd\x4a\xe3\x27\x47\x64\x3a\xfe\x18\
+\xd3\xd4\xd4\x24\x97\x5f\xb6\x14\x0d\x4c\x3f\x1a\x8c\xe1\x3d\x57\
+\x86\xf0\x8b\x40\xd1\xac\x99\x1f\x7e\x27\xba\xe2\xf7\x4c\x5b\x39\
+\xd6\xbd\xec\xdb\x67\xc9\xd3\x0f\x57\xa2\xd7\x0e\x73\xae\x30\xb1\
+\x12\xd9\xd4\x52\x8b\xeb\x9c\x29\xd4\x2b\x8e\x82\xcf\xb4\xab\xbb\
+\xa1\x03\xda\x01\x03\x99\x52\x77\x4c\x5e\x5f\x95\x97\xcd\xd0\xcc\
+\xb7\xb7\x62\xa1\xd5\xec\x64\x62\x74\x8d\xe4\xbb\x42\x67\x62\x55\
+\xb9\xfb\x8f\xf0\x26\xf2\xb0\x76\x08\x31\xc8\x29\x31\xa7\x07\x20\
+\x54\xcc\x61\x2a\x6e\x5a\xf8\xda\x3e\x3f\xfc\x33\x1b\x0a\x5f\xd3\
+\x78\xd7\x69\x63\x6f\x0b\x24\x2a\x87\x93\xe2\xd0\x6a\xc7\x05\x51\
+\xc8\x09\x0b\x26\xba\x48\x94\xec\xdf\x1f\xec\xce\xc9\xd6\x9f\x45\
+\xdd\xb9\x98\x42\x12\xa0\x8b\xf8\xc5\x18\x12\xfc\x8c\x19\x33\x64\
+\xd7\xae\x5d\x3a\x7d\x9d\x61\x86\x13\x0c\xbf\x41\x37\x4e\x79\xff\
+\xc8\x47\x3e\x22\x9f\xfd\xec\x67\xb5\x97\x8b\x04\xd2\xd6\x76\x54\
+\xca\xcb\xca\x0d\x38\xe0\x87\xe9\xd2\x6a\x8b\xd2\x57\x6a\x57\x38\
+\xa8\x3b\x6d\xb4\xe8\xc7\xd4\x4e\x2b\x03\xeb\x1f\x1e\x88\xb5\x5a\
+\x3c\x75\x1f\xc3\x4e\x21\x9c\x37\x45\xb9\xdb\x78\x51\x10\xc4\xe0\
+\x20\x20\xe2\x77\xbc\xeb\x05\x11\xeb\xe8\x91\x0a\xd9\xf2\x34\xc4\
+\x2b\x10\x5f\x00\x0e\xc4\xf4\x93\xfb\xf5\x82\x6b\xce\x9d\x7b\x91\
+\x76\x69\x9b\x4f\xea\x97\x69\x3d\xa9\x89\xc5\xce\xb8\x5c\xd8\x2d\
+\x3e\x63\xc6\x74\x69\xef\x38\x66\xf4\x2d\xcd\x37\x52\x5e\x72\x37\
+\x89\x45\x94\x70\xd3\x7c\x95\xbc\x53\x7f\x9a\x53\xf3\x9a\xa2\xd6\
+\xec\x05\x22\xf7\x7f\x2b\x89\x65\xc0\x15\xd0\x23\xa3\x3a\x37\x41\
+\xe3\x28\x70\x8f\x81\x10\x95\x05\x1b\x0b\x7e\x22\xba\x98\x77\xea\
+\x22\x69\x37\x27\xf3\xea\xbb\x65\x22\xe6\xb4\xfd\x16\x9b\xc4\x67\
+\xa1\x23\xbd\xf9\x12\xb6\xa0\x58\xf9\x89\xe3\xf5\xe4\x86\x45\x17\
+\x21\x50\x28\x94\x5e\x4e\x91\x39\xf5\x00\x59\x8e\xc6\x30\xea\x4d\
+\xf0\x02\xeb\x13\x38\x45\x56\x96\xb6\x38\x5e\xbe\xdc\xb1\x8f\xe5\
+\xb0\x82\x0f\xc0\xc0\x94\x58\x53\x82\x25\x99\xd0\xf2\x8e\x9f\x41\
+\x44\x14\xab\xb8\xe2\x6f\xff\x93\xed\xd2\xbb\x0d\x23\xbc\xd0\x3b\
+\xfc\x61\x53\x48\x62\xef\xc3\xef\x71\x85\x93\x00\x68\xb8\xf8\x89\
+\x2d\x6a\xa9\x89\xc1\xc1\x51\xe6\x2f\x7f\xf9\xcb\xf2\x83\x1f\xfc\
+\x40\x45\x93\x3d\x7b\xf6\x82\xd8\x7a\x75\xc0\x2c\x24\xab\x22\x01\
+\x68\x40\xd6\xaa\xa9\xb4\xd8\x32\xd4\xdd\x3c\x19\x3f\x51\x18\x3a\
+\xc5\xe1\x19\x16\x76\xea\x0c\x55\xe8\x43\xe0\x60\x61\x1e\x53\xc4\
+\x63\x0e\xa2\xf9\x8f\x08\x85\xe0\x28\x76\xf1\x1a\xe2\xa1\x3f\xec\
+\xf3\x22\x07\xf6\x95\x69\xb7\xa9\x2e\xa3\xc5\xc7\xe2\xbc\x96\x95\
+\xa5\xa5\xab\xb3\x4b\xf5\x90\xd2\x7c\x9e\xc8\xce\xbc\xb3\x6c\x62\
+\x3d\x23\x2e\x27\x76\x61\x8f\x69\x1e\x23\x7b\xf7\xb7\x2a\xe8\x4c\
+\x9a\x86\x02\x44\x41\xc0\x3c\x0d\xbf\x98\x16\x7e\x2c\xce\x33\xde\
+\x93\x43\xa6\x30\x61\xa1\x07\x79\x5e\xf3\x58\x05\x7a\xb8\x38\x73\
+\x17\x20\x41\x56\xd4\xdb\x30\x20\x98\xef\x31\xef\xc5\x2b\xf2\x88\
+\x1b\xba\x7e\x43\xf6\x6a\x41\xd4\xaa\xc1\x00\x22\xba\x8e\x37\x1c\
+\xf6\xc3\x96\x09\x09\x67\x21\x56\x9f\x76\x79\x0e\xce\x61\xb5\x3e\
+\xa6\xf9\x35\xc3\x0a\x9a\x9c\x13\xe5\xff\x8f\x71\x3b\xf5\x00\xe1\
+\xa9\xb2\x30\x65\x37\x2d\x7e\x7b\x36\xb4\xae\xc6\x80\x8e\xd4\x36\
+\xbb\x76\x3b\xb8\x07\x26\xe9\x42\xb4\x82\xe4\xc5\x66\x94\x85\x00\
+\x7f\x71\x61\xc4\x89\x8e\x73\xc5\xf5\xdc\x1d\xdb\x7b\x65\xcf\x2f\
+\xb1\xe0\x09\x53\x48\xbc\x93\xe8\x1d\x71\xb8\xd2\x3b\x09\x80\x26\
+\x9e\xa8\xc7\xee\xda\x58\x61\x27\x50\xf8\x3e\xf6\xc3\x01\xc4\xcf\
+\x7d\xee\x73\x18\xdf\x48\xcb\xc1\x83\x07\x41\x38\x18\xa9\x85\x3c\
+\x6f\xde\x47\x95\x1e\x25\xd6\xa4\xcd\x24\x7c\xa8\x3d\x4a\xb5\xbe\
+\x1a\x6a\xd7\x27\x64\x52\xff\xf0\x40\xcc\x95\xa1\xf5\x3f\x86\xe5\
+\xab\x9c\xb7\x14\x73\x09\xfa\x2b\x5e\xe0\x18\x78\x36\x40\xa1\x3b\
+\xc5\x28\x0f\xfe\xcb\xa1\xa0\x83\x88\xa7\xb2\x08\xe1\x0a\x4f\x24\
+\x54\xd3\xbd\x5b\x2d\x0f\x3d\xf4\x3b\xd9\xbc\x79\x0b\xb3\xae\xe9\
+\x8f\x39\x44\x9c\x5f\xde\xe9\xc6\x30\x04\x07\xa7\xfc\x73\x8a\x3b\
+\x39\x10\xdf\xd1\xad\xa2\xb2\x02\x62\x6c\x4f\x01\x3c\xc7\x01\x81\
+\x79\x89\x2f\x93\x2b\x4d\xb7\xfe\x0c\x71\x47\xda\x90\xbe\x1c\xa6\
+\x11\xcd\xc6\x5c\xad\xfb\xb1\x2f\x17\x75\x11\x72\x11\xfa\x85\xd7\
+\xe3\x2f\xa4\x9b\x40\x30\xc6\xdc\xf9\xac\xe5\x42\x54\x21\xcf\x9c\
+\xd8\x38\xa3\xae\x5b\x16\x94\x05\xf2\x40\x7b\x68\x75\xa2\x5b\xf9\
+\x8a\x25\x49\x47\xd0\xc3\x39\x18\x84\xef\xc5\x11\xdf\x98\xf4\x82\
+\x40\xd1\xcc\x8d\x28\xb2\x97\x7c\x3b\xb5\x00\x89\xb9\x07\xf2\x84\
+\x23\xd0\xfe\x9e\xab\xf7\x66\x8e\x71\xf2\xb9\xb4\x6d\x77\xe7\xc9\
+\x3d\x40\x9c\x91\x62\xce\x4c\x6b\xce\x4b\x92\x4e\x37\x56\x94\x83\
+\x2e\xdd\x0c\x56\x04\x6e\xbd\xe7\x80\x79\xcb\x17\x86\x19\x94\xf8\
+\x7e\x61\x2b\x97\xd2\x52\x9f\x78\xe2\x89\x27\x54\x61\x67\x88\xb8\
+\xab\x36\x0e\xdd\xd1\x61\xb6\x42\xe4\x0a\x3b\x76\xe5\x26\xb0\xe2\
+\x8a\x69\x60\xe2\x58\x89\xb1\x31\x56\xfc\x9a\x7f\x7d\x6f\xec\x91\
+\x27\x75\x3f\x91\x9d\xe4\x1d\x19\x44\x48\x0e\xc2\xb5\x14\xad\xd8\
+\x54\x21\x8b\x33\xca\xc9\x29\x0a\x1c\x83\x71\x44\x5c\xc4\x10\x50\
+\xf4\x0e\xc1\x09\x90\xfe\xbe\x0a\xd9\xb7\x1b\xe7\x66\x62\x9d\x84\
+\xe9\x4e\x35\xc4\xaa\xb1\x23\xec\xe2\xa5\x97\xc8\xed\xb7\xdf\xa1\
+\x22\x23\x89\x3e\xe6\x10\x05\x82\x46\xa4\x74\x23\x48\x36\x6d\xda\
+\x24\xff\xf2\x2f\xff\x5b\xbe\x7f\xfb\xed\x0a\x14\xfa\xa1\x61\x79\
+\x8c\x19\x3b\x51\xcb\xe0\x64\xf3\xaf\x98\x23\xfd\xd3\x44\x22\x10\
+\xee\xfa\x0d\x75\xe5\x73\xd1\x8d\xf9\x75\x31\x37\xa4\x1f\x8e\xab\
+\x1f\x2f\x57\x5d\xc4\x41\xa3\x49\xa3\xde\x62\x00\xf0\x6e\x1c\xf4\
+\x1e\x59\x23\x3f\x71\x94\xe4\x22\xd8\x07\x39\xdd\x2f\x0b\x6b\x33\
+\x1a\xc3\xc6\xc3\xd8\x69\xa5\xc9\xb5\xae\x98\xee\x7a\xfd\x59\x3b\
+\x81\x13\xc6\x3e\xaa\x91\x2f\x3f\x35\xba\xc8\xa9\x05\x88\x2c\xd3\
+\xf8\x92\x37\x2d\x7c\xab\x17\x5a\x57\xd5\x41\x66\xae\x1e\x0d\xdd\
+\x03\xb2\x28\xce\xa9\xc7\xb6\x3a\xe4\x1e\x5a\x9e\xc8\x03\x89\x90\
+\x26\x6a\x21\xa2\xe7\xb8\x4b\xf7\xe0\xd3\x6d\x92\xc7\xbe\xbb\x0e\
+\xe6\xbf\x07\xf1\x34\x12\x13\xe0\x45\xfd\xb2\xc2\x48\xe8\x6c\x25\
+\x69\xd8\x33\xb5\x72\xe5\xca\x21\x2d\x28\xdd\xb9\xbc\x96\x86\x9b\
+\x37\xa8\x18\xc6\x9a\x61\xb5\xe8\xdd\xd8\xb5\xe2\x22\x37\xe3\x6c\
+\xde\x93\x48\xd4\xe8\x63\x6c\x37\xc4\x63\xdc\x0b\x3e\x34\xd3\xf4\
+\xc1\x5c\x73\x4c\xa7\x13\xe2\xc7\x20\x00\xa2\xe0\x28\x94\x81\x29\
+\x1b\xfa\x2b\x6d\x39\xf9\x7d\x07\x00\xe9\xee\x2c\x93\x03\x7b\xb1\
+\x7d\x0e\xbb\x77\x75\xbc\x21\x22\x4c\x78\xf0\xb0\x1c\xb8\x09\xb3\
+\x03\xd6\xae\x5f\x2f\x9f\xff\xfc\x17\x64\x1b\x96\x0a\x97\x4e\x52\
+\xa4\xbd\xbd\xbd\x5d\x56\xad\x5a\x25\x5f\xfc\xe2\x97\xe4\x7d\xef\
+\xff\x80\x3c\xf8\xfb\x15\xf2\xa9\xe5\x9f\x91\x7f\xf9\xdf\x9f\x52\
+\xf7\x87\x1e\x7a\x48\x1e\x7b\xec\x09\x99\x39\x63\x9a\xe9\xc9\x42\
+\xbc\x05\x70\x21\x45\xf1\x5f\x29\x05\x9f\x0c\x18\x26\x0f\xc8\x07\
+\x28\x82\xf3\xce\x66\xcc\x0b\xe5\xa1\x1f\x26\xc1\x45\xca\xd1\x08\
+\x01\x20\x11\x1d\xd0\x5f\xa9\x29\x84\x83\x85\xf9\xe6\xa5\xc5\x4c\
+\xbb\x96\x9e\x5a\x64\x52\x75\xb7\x5c\x52\xee\xcb\x23\x5d\xe0\xc6\
+\x98\xcd\xb8\x68\x2e\x06\x98\xb0\x8b\x7c\xa7\x2f\x6f\xaf\xfe\x60\
+\xc4\x45\x22\x5d\xb8\x34\xfe\x3f\xd6\xae\xca\xf4\x1f\x1b\xe8\x84\
+\xfe\xc9\x3d\x38\xdf\x0a\xc6\x0e\xed\xbf\x26\xf7\x98\x36\xd6\xc6\
+\x9c\x3a\x3b\x39\x00\xc5\x3a\x0d\xd1\x26\xc0\xbe\x54\x68\xc0\x86\
+\x18\xe6\xbf\xd4\x10\x20\x9d\x10\xad\xf6\xff\xfa\x98\x58\x50\xc6\
+\x02\x1c\xef\xf5\x52\x0c\xc1\xc1\xca\x23\x51\x70\x10\x8c\xcb\x67\
+\x2f\xc7\xba\x87\x8f\x7f\xfc\xe3\x32\x6f\xde\x3c\x1d\x44\xe3\x7a\
+\xf3\x8f\xfe\xa3\xe9\xfc\x38\x7a\xb4\x4d\x45\x0d\x50\x9e\xca\x36\
+\x26\x5d\xa6\x32\xe2\xef\x17\xdc\xe0\x50\x48\xb7\x7a\x89\x9e\xd4\
+\x1e\xf9\x26\x71\x15\x02\x46\x76\xd6\x36\xc5\x84\x48\x85\xcc\x60\
+\x97\x41\x1a\x3a\x9b\xcb\x88\x56\xc5\x67\xf3\x1d\x7d\x86\xbf\x0e\
+\x6c\xe9\xd3\x01\xd1\xac\x09\xdb\x87\x72\x35\xa2\xfe\xe1\x65\x4c\
+\xc4\x04\xc1\x94\xc9\x93\xe4\xc1\x87\x1e\x96\xc7\x1e\x7f\x52\xde\
+\xf8\xc6\x37\xc8\xb8\x71\x63\xb5\xa1\x60\xfe\x36\x6f\xda\x2c\x0f\
+\xad\x78\x1c\xa3\xed\x15\x32\x63\xfa\x14\x34\x0a\x29\xf8\x9f\x28\
+\x8f\x3c\xf2\x98\xfc\xc7\xd7\x6e\x91\x89\x63\x47\x61\x39\xee\x64\
+\x88\x9b\x6c\x2c\x20\x8a\x11\xbd\x71\xf1\x33\xe9\x4c\x6c\x04\x66\
+\x4d\x99\x71\x30\x53\x51\x34\x35\xfa\x1a\x3e\x22\xea\xc7\x23\xb7\
+\x38\xe2\x92\x5b\x2e\x16\xeb\x00\x31\xaf\x5b\x59\x8e\xfd\xb5\xd8\
+\x4b\x06\x6e\x80\x46\x82\x85\x84\x7f\x73\x31\x58\x64\xc7\x0d\x86\
+\x1f\x60\x2e\xd9\x88\x32\x9f\x7c\x62\xde\xb1\x11\x5d\x6a\x40\xe6\
+\xd6\x0c\xc8\x33\x03\x55\xb2\xf1\x88\x2f\x57\x4d\x76\xec\xcb\x27\
+\xdb\xde\x93\x87\x30\x6c\x62\x07\x1f\x42\xc0\x55\xd0\x85\x49\x8f\
+\x8c\x92\x11\xbd\x24\x73\xea\x00\xa2\x53\x8f\xef\xc1\x59\x81\x0b\
+\x5e\xdd\x17\x84\xaf\xe3\x28\x79\x75\x63\xd2\x3d\x02\xdd\x23\xc1\
+\x42\x02\xf7\x30\x2d\x80\x49\xe7\xd0\x82\x80\x1b\xb2\x60\x73\xb4\
+\xbc\x37\x2f\xbb\x7e\x6b\xba\x74\x2d\xb0\xe6\x00\xf3\x7b\x5e\xaa\
+\x89\x41\x42\x70\x34\x36\x36\xaa\x08\xf5\xf9\xcf\x7f\xfe\xb8\xe8\
+\xaa\xaa\xaa\xa3\xb5\xe6\x2c\x0e\x56\x05\x6f\xfa\x6b\xec\xfc\x35\
+\x8f\x26\xac\xf1\xa0\xf6\x82\xb3\x7a\x89\x9e\xd4\x6e\xbc\x32\x1e\
+\x75\x2d\x71\x33\x51\x63\x34\x9d\x2b\xe7\x60\x8a\x0a\xb9\xf9\xac\
+\xe9\xb5\x22\x4e\x8d\xd8\xc1\x49\x9c\xec\xc5\xe9\x68\x33\xf3\xc0\
+\xd8\xc8\x68\xff\x83\xc6\xad\xa4\x83\x38\xcc\x9d\x20\xe1\x32\xda\
+\x7c\x3e\x27\x77\xfd\xe8\x6e\xd9\x7f\xa8\x0d\x33\x6b\x5d\x19\xdd\
+\x58\x2f\x2d\x63\x9a\xe4\x8a\x57\x2c\x85\x5f\x74\x10\xc0\x1f\xc7\
+\x7d\x98\x36\x4e\x77\xe7\x85\x43\xba\x24\x8f\x79\x71\x0a\x0e\xbe\
+\x00\x98\xe9\xc1\x50\x97\xe1\xf4\x25\x0e\x11\x30\xe8\x62\x3c\x19\
+\x60\xe0\x99\xe1\x10\x4a\xc3\x31\x5d\xac\x7f\x00\x6d\xca\xac\x50\
+\x1e\xc1\x4e\x8d\x4b\x5e\x59\x2e\xe3\xa7\xf4\x61\xd7\x77\xec\x12\
+\xa3\x9f\x30\x00\x50\xed\x5d\xe3\x8a\x00\xc1\xb2\xc1\x55\x6a\xe2\
+\x67\x36\x25\xe3\xab\x7a\xe4\xb2\x9e\x72\x79\x0c\xeb\x6b\x16\x8c\
+\x11\x6b\xe1\x9c\xa4\x3c\xb9\x7b\x50\xfa\x12\xf2\x8e\xca\x9b\x16\
+\xff\x7b\xdf\xf7\xd6\x6e\x85\x2e\x62\x26\xca\x96\x46\xf2\x47\xd8\
+\x4f\x15\x40\x0a\x6b\x3d\x42\xcb\xbe\x09\x02\x81\x4c\x1f\x15\xe6\
+\xb3\x69\x27\x31\x88\x9e\x2b\x97\x8a\x31\xb8\x87\xb6\x48\x28\xb6\
+\x38\x93\xa5\xe9\x64\x41\xb1\x44\xdb\x36\x75\x49\xff\xf6\x9c\x8a\
+\x56\xfe\x1f\xa1\x98\x97\xc6\x55\x6a\x8f\x41\x42\x4e\xc2\x39\x5a\
+\x93\x26\x4d\x52\x4e\x41\x77\x1a\x8e\x99\xf4\xf7\x63\x63\x08\x28\
+\xe6\x5a\xd5\x48\x47\x0e\xbd\x6c\x94\xd5\xd9\x92\xc6\x55\x64\xd2\
+\x8c\x5f\xf3\xcf\xa0\xd1\x2b\xf3\x26\xf6\x67\xde\x47\x6e\xc8\x54\
+\xfc\xb6\xf8\x9e\x2e\xc6\x95\x47\x10\x90\x08\x99\x77\x5e\x34\xf1\
+\xdb\xe2\x9d\x1c\xc7\x83\x08\x95\x06\x17\x4c\xe0\xa8\x02\xe3\xcb\
+\x70\x0d\x06\x88\x03\x33\x1e\x73\x11\x1c\xdc\xf0\x6e\xee\x9c\x8b\
+\x64\xc1\x7c\xb2\x6c\xf8\x41\x7e\x39\x01\xd1\xf3\xd8\x49\xa1\x8c\
+\x5e\xf3\xc8\xd8\x08\x16\x9a\x04\xe6\x99\xa5\x5d\x28\x48\x28\x1a\
+\xfa\x8d\x3b\x39\x98\x16\x13\x87\xbe\x82\xd5\x94\x9d\x01\x02\xdd\
+\xf0\xcc\x64\xc4\xc0\xd0\x00\x48\x0b\xbe\xa9\x3e\x91\x2e\xce\x45\
+\x2d\xab\x0a\x65\xeb\x73\x96\x6c\x5c\x5d\x2e\x63\x26\x60\xa7\x1a\
+\x72\x11\x70\xc2\xd8\xd0\xa6\x17\x7e\x0a\xae\x26\x6a\xe3\x1e\x79\
+\x64\x83\xc2\x11\xf6\x8a\xe4\xa0\xcc\xae\xc2\x06\x1d\xfd\x95\xb2\
+\x05\xe3\x22\xaf\x18\xef\xba\x0b\x5a\x6c\xef\xd9\x2e\x0b\x27\x65\
+\xe0\xec\x11\x91\x7f\x92\x4f\x41\x17\x59\xae\xc1\x4d\xa2\xe3\x8f\
+\xbd\xc8\xfb\x30\x81\xe7\x45\x86\x1a\xee\x2d\xea\x77\x06\x6a\x67\
+\x63\x99\xc7\x5b\x9c\xd0\x97\xea\x51\xae\xdd\x8b\xcc\xeb\xec\x88\
+\x9c\x19\x45\x65\xb0\x42\xc6\xa3\x38\x94\x30\x50\x90\x36\xa6\xb0\
+\x0f\xb4\x0d\xca\xce\x1f\x99\xf5\x1d\x41\xb4\xcb\xfa\xf0\x4f\xbd\
+\x94\xe7\x18\x0c\xdc\x10\x6e\x3b\xc4\x2a\x2e\x7e\x7a\xfe\xf9\xe7\
+\xf5\xe2\xa4\x46\x17\xd3\xc3\x99\x0e\x1f\x44\xc1\x5d\x3b\x26\x4c\
+\x18\x8f\x15\x87\x2d\xb0\xc7\x73\xb6\x86\x7f\xd5\xd4\x60\x21\x2f\
+\xe6\x31\xce\x91\xb9\x17\x5e\xe2\x91\x84\x4b\xd7\x21\xfe\xa0\xb8\
+\x02\x20\x54\x62\x4d\x4f\x56\xd4\x70\x90\xf8\xe8\x2f\xe2\x1e\x0c\
+\xa6\x47\x09\xe4\xca\xa4\xbd\xcd\x95\x72\xac\x03\xd7\x32\x83\x27\
+\xfd\x53\xbf\x91\x1b\x5f\xf0\x5b\xe0\x10\xcc\x33\xf7\xf3\x1a\x18\
+\xc8\x48\x06\x79\xcc\xc0\x4e\x70\x10\x44\x04\xbf\x5e\xb0\xd3\x1f\
+\x1b\x87\x34\x96\x11\x77\x60\xfc\x63\xfd\xb3\x9b\x64\xd5\x9a\xf5\
+\x6a\xe7\xe6\x72\xf1\xe2\x2b\xcd\x41\xfc\x2d\x26\x50\x13\x19\xdf\
+\xf8\x6c\xec\x74\x8f\x9e\x4c\x5a\x62\x67\xdc\x39\xff\x71\xfa\x54\
+\xcc\xf6\xfd\x55\x12\xf3\xc9\xd2\xc7\xe9\x22\x85\x70\x8c\x0b\x46\
+\xbf\xa2\x3f\xd1\x43\xec\xc6\x97\x6a\x2c\x9c\xba\xdb\x2b\x8b\xd1\
+\xa3\xf5\x7b\xcc\x2a\x02\xed\xc9\xd2\x8b\x70\xe0\x49\x1f\xbb\x92\
+\xc3\xbf\x90\xbf\x58\xd2\x88\x48\x42\xcc\x38\x7d\xc9\x74\xfe\x92\
+\x03\xc6\x49\x2c\xbd\x07\x61\xf8\x6e\x94\x7c\xe5\xb4\x1a\xdb\x0f\
+\xca\x6d\x7b\x00\x05\x62\x41\x96\xc5\x91\x5b\x9a\xdb\xe1\x05\xc0\
+\x12\x40\xfd\x28\x67\xa1\x7e\xd2\xba\xd6\xf4\x28\xd9\x98\xc2\x1e\
+\xa2\xd7\xeb\x8f\x31\xec\xc3\x37\x5c\xe0\x0f\x87\x8a\x09\x84\xbd\
+\x3c\xf4\x4f\x42\xa1\xc9\x66\x73\x3a\x05\xbe\xa5\x65\x8c\xee\x8d\
+\xb5\x73\xe7\x4e\x69\x6e\x6e\x2e\xc8\xe3\x11\x55\x16\x2b\x5f\x43\
+\x0d\xcb\x11\x1e\x8b\x2e\x25\x84\xa2\x7e\xf9\x63\xde\x32\xcf\x14\
+\xae\xc8\x41\xb8\x13\x08\xc1\xa0\x6f\xa3\xf0\x71\x1c\xac\x5b\xc6\
+\xc2\xc3\x32\x07\x33\x20\x60\x8c\x9d\xa4\x75\x21\x64\xe4\x43\x6f\
+\xf4\x11\xfd\x31\xbc\x02\x04\xcf\xa5\x40\x40\x5e\x4f\xd4\xab\xc5\
+\x3c\x71\xa6\x00\x39\xc8\xca\xa7\xd6\xa8\x68\xf6\xa1\xbf\xba\x51\
+\xfe\xfe\x23\x7f\x2d\x13\x30\x97\x6b\xcd\xda\x0d\x85\x70\x9a\x3e\
+\x7c\x47\xf3\xa0\xdf\xa1\x4d\x13\x10\x39\x15\xed\x25\x6f\x60\x35\
+\xee\xbc\x51\x59\xaf\x6e\x10\x74\x45\x5b\xb2\x75\x7d\xb9\x8a\x5d\
+\x5a\xfc\xcc\x3e\xbd\xe1\x8a\x6e\xfc\x5c\xc1\x14\xdc\x0a\x16\xf3\
+\x8a\x5c\xa4\x32\x99\x91\x99\x55\xe8\xeb\x05\xf7\xda\xd9\x1e\x60\
+\x5b\x54\xd7\x1d\x55\x2b\x10\x3e\xec\xc9\xb5\x15\xe1\x9b\x0b\x91\
+\xbc\x44\xcb\x7f\x5f\xc4\x32\xfb\x5b\x79\xa3\x3e\x3c\xa7\xb2\x2d\
+\x23\xef\xa0\x46\x56\x8f\x6d\x49\xb0\x11\x18\x06\xcb\xd1\x65\xcb\
+\xb5\xb4\xa0\x08\xf6\x66\x0c\x37\xcc\x2f\x79\x30\xc7\x3c\xfa\xf6\
+\xf7\xcb\xa1\xdf\x1a\x80\x84\x2f\x72\xb4\x3c\x8e\xaf\xb2\xb2\x52\
+\x89\x9d\x22\x14\x09\x9e\x23\xe3\xac\x74\x12\x49\xcc\x3d\x62\xbf\
+\xc5\x67\xf3\x8e\x75\x13\xa0\xeb\x99\xd3\xe1\xb9\x69\x1c\xcd\x87\
+\x3f\x8c\x05\x8f\xe8\xfd\xba\x1d\x5d\xa0\x9c\x11\xcb\x96\x34\xa7\
+\x62\x8b\xd1\x19\xd4\x53\xfc\x83\x4c\x14\x08\x25\x76\x2b\xbd\x23\
+\x0d\x9a\xcf\xd8\x0d\x0f\x04\x48\x0d\xbb\x3e\xf5\x94\x27\x07\x3a\
+\x82\x07\x37\x00\x05\xba\x06\xfd\x2a\x30\x8c\xc5\x94\x0f\xd8\x70\
+\x26\x93\x94\xce\x0e\x1b\x8a\xb5\x09\xaf\x40\x80\x4f\xa5\x3f\xf5\
+\x6b\x02\xc4\xd6\x52\xa0\xc4\x76\x26\xa1\xb4\x4c\xc8\x35\x76\xef\
+\xde\x8b\x85\x65\x55\xf2\x9d\x6f\x7d\x45\xb8\x56\x9f\x53\x74\xe8\
+\x87\xfb\x86\xfd\xec\x67\x3f\x93\xcf\xfc\xbf\x5f\x90\x8b\x66\xcd\
+\x40\x07\x11\x36\xad\x56\xc0\xf2\xfb\xfc\x0a\x32\x81\x1b\xc5\xaa\
+\x52\x11\x2b\xb6\xab\xfe\x42\x3f\x6a\x8c\x7f\xa6\x95\xdb\x0d\x4c\
+\x9e\x80\x79\x72\xbf\x49\xca\xfc\x4b\x52\xd8\x39\x1e\x7b\x20\x63\
+\x96\x2e\x63\x89\x22\x44\xd4\x2c\x33\xd3\x38\xe8\x77\xf8\xa1\xe8\
+\x59\xfd\xb1\x4c\x51\x88\xe8\xf2\x51\xee\x3b\x0e\x3b\x34\x4e\xc7\
+\x88\xe4\x3a\x6c\x75\x36\xab\xc9\xb6\x96\xcd\x76\x83\x7b\x9e\xf6\
+\x9d\x64\x75\xf8\x1e\x04\xbc\x2d\x5a\xea\x6d\x22\xd1\xf4\xbc\xf8\
+\x9f\x13\x90\xed\x8b\x0f\xac\x3e\xeb\x76\x69\x1c\x7d\x83\xc9\xd7\
+\x23\x63\xb3\x9b\x51\x81\x4e\xd5\xff\x4f\xdd\x9b\x80\xd9\x79\x54\
+\x67\xc2\x75\xd7\xde\x5b\xdd\xda\x77\xcb\x96\x77\x79\x93\xe4\x85\
+\xd8\x2c\x82\x80\x81\x60\x92\x31\x03\x66\x33\xc1\x36\x24\xcc\x0f\
+\xff\x13\x12\x12\xc8\x0f\xc9\x93\x11\x93\x90\x67\x78\x48\x20\x04\
+\xc2\x24\x84\xc5\xc0\x30\x18\x3c\xcc\x24\x71\xc2\x6a\x40\x0e\x31\
+\x5e\x65\xc9\x96\x77\x5b\xb2\x64\xc9\xda\xb7\xde\x6f\xf7\x5d\xbe\
+\xff\x7d\xcf\xa9\x53\x5f\x7d\xb7\x6f\x4b\xdd\xf2\x26\x57\xf7\x77\
+\xab\xea\xd4\xa9\x53\xdb\x39\xb5\x2f\xf9\xfc\x08\xe4\xa2\x80\x95\
+\xf0\x3a\x05\x84\x51\x6b\x52\xcc\x2c\x32\x2b\x67\xad\xea\x13\xd8\
+\x42\xb1\xc9\xb7\x1e\xbd\xe8\x1e\x68\x97\xb8\xc9\x47\xd6\xca\x42\
+\xa4\xa2\x70\x70\x73\x22\x17\x02\xb9\xe8\xc7\xb5\x0f\x5e\xf2\x46\
+\xa6\x26\x7d\xc3\xcb\xfa\x46\x94\x7c\xf8\x1c\x98\x9a\x70\xf0\xc4\
+\xe0\x8f\x7f\xfc\x63\xf7\x99\xcf\x7c\xc6\x7d\xf1\x8b\x5f\x94\x93\
+\x84\x5c\x85\x27\x2e\x17\x0f\x55\x69\xb8\xfa\x3b\x89\x6a\x40\x99\
+\xe4\x0e\x22\x84\x09\x1c\x0c\xd0\xd1\xc7\x16\x84\x27\xf1\xd2\x16\
+\x84\x8e\x0c\x4b\x3e\x8f\x4b\x7c\x4e\x26\x8d\x8d\x96\xdd\x00\x6e\
+\x43\xc1\x59\xa6\x54\xd1\x51\x94\x1a\xe8\x4f\x3c\x83\x90\x19\x99\
+\x7e\xfb\xf4\x1c\xbd\x76\xb1\xca\xd8\xfd\xbb\x75\xdb\x76\x77\xd9\
+\xa5\x17\xbb\xcf\xff\xcd\x67\xdd\x55\x57\x5d\x15\xae\x06\x62\x6b\
+\xc3\x99\xbf\xf7\xbd\xef\x7d\xee\x8f\x3e\xf2\x7b\xee\xae\x7b\x37\
+\xf9\xfc\xcc\x84\x16\x59\x7c\x44\xa0\x79\x93\x18\x62\x33\x91\x19\
+\xbf\x2a\xa6\xfc\xfb\xe6\x27\xee\x1e\x3c\xad\xf0\xe4\x43\x1d\x80\
+\xb1\x8c\x52\x52\x62\x0c\x1e\x95\x5e\x64\x15\x44\xc3\xa1\xb0\xf0\
+\x5e\xad\x5e\xdc\x86\x72\x56\xd7\x84\xdb\x85\x16\x6a\x2f\x36\x2f\
+\x2e\x5b\x8e\x3e\x33\xd6\x5a\xf6\xd7\xdc\x2b\xe7\xe8\x94\xaf\x73\
+\x27\xb8\xfd\xe4\xd9\x0a\x48\xce\xb6\x95\x60\x2c\xfe\x16\x36\x13\
+\x0b\x66\xe7\x6b\xb5\xb6\x3c\x17\x0a\x39\xd2\x93\x7e\xe7\x54\x4c\
+\x2a\x99\x06\x01\x19\xdd\x3b\xe6\xf6\x6f\xc0\x1c\x20\x52\x9e\x8c\
+\xda\xbc\xa2\x66\xda\x54\xbf\x64\x7e\x76\x81\x28\x1c\x3c\xef\x71\
+\xdb\x6d\xb7\xb9\x9f\xfc\xe4\x27\xee\xdd\xef\x7e\xb7\xb4\x20\x74\
+\x67\xb7\x8b\x7a\x2b\x45\x38\xd7\x3d\x4c\x38\xae\xbd\xf6\x5a\xf7\
+\xa3\x1f\xfd\x48\xb6\xb9\x73\xa3\x22\x05\xef\x3d\xef\x79\x8f\x08\
+\xcb\x5e\x6c\x77\xa7\xd0\x51\x98\x94\xf3\x5a\x51\x54\xa7\xd6\x2e\
+\x59\x28\xa3\xc4\xc5\x42\x11\x10\x5e\xb6\xe0\x99\xa4\x15\x23\x18\
+\xf3\x8c\x8e\x16\xdd\x10\x0e\x47\x72\x8a\x58\x6b\x70\xa3\x49\x5f\
+\x68\xa9\xd1\x72\x72\xec\x41\xb3\xf8\xd1\x1f\x2f\x28\x26\x24\xc4\
+\xe1\x91\xdd\xb2\xdb\xbe\x7d\xa7\xbb\xee\xb7\xaf\x75\x9f\xfa\xd4\
+\x5f\x60\xd7\xed\xb9\x92\x4f\x36\xe6\xa2\xce\x15\x76\xb6\xc6\x5c\
+\x3f\x5a\xbb\xfa\x7c\x6c\xa3\x1f\x44\x2b\x8d\x0e\x87\x55\xf4\x16\
+\x3c\xf4\x6c\xbc\x63\xdb\x64\x33\x21\x1c\x8b\x2c\x42\x05\x71\xf7\
+\x86\x36\x6c\xfb\x2f\x61\xcf\x1d\xda\x1d\x69\x95\x94\xa8\xf8\x8a\
+\xbd\x4e\x0a\xc3\x17\x03\x70\x78\x8b\x63\x09\x2f\xec\x2e\xe7\xc5\
+\x73\x88\xde\x13\xb8\x0b\xad\xdc\x5d\xc8\xbd\xfc\x14\xdc\x46\x57\
+\xcd\x97\xca\xb9\xdc\x5b\x94\xea\x89\xfd\x3e\x3b\x01\xf1\xcb\xf9\
+\xed\xef\x5b\x7d\x0a\x9e\xdc\x7a\x0d\xf7\x51\x74\xe2\x2a\xfb\xd1\
+\x06\x8a\x0a\x7c\xce\x96\xc1\x0a\x38\x8e\x9e\xc0\x90\xd1\xac\xd1\
+\x38\x3e\x39\xf8\x90\x9e\xdb\x28\xcc\x42\xeb\x81\x81\xd6\x74\x14\
+\x57\xc4\x6d\xcc\xf1\xd1\x8f\x7e\x54\x8e\xd3\xbe\x0e\xe7\xaa\xbf\
+\xfc\xe5\x2f\xbb\xbf\xff\xfb\xbf\x97\x19\x18\x6e\xbe\x6b\x5e\x39\
+\x37\xda\x64\x02\x13\x0e\x0a\x02\xf7\x63\xf1\x52\x38\xc2\xf9\xd9\
+\xde\x2d\xd2\xa4\xe2\x80\xb7\xc8\x56\x24\xc8\xdb\xb1\x4a\xd0\xbb\
+\x35\xa1\x68\xd8\xa8\xf7\x40\x83\x57\xa4\x8e\xe2\xfe\xe1\x9a\x08\
+\x88\xba\xf0\x97\x5e\xec\x13\xbb\x1f\x9f\x8c\x41\x40\x70\xeb\x3d\
+\xf2\x0c\x50\xf8\xb7\x7c\x25\x2e\x2b\x02\x8c\xff\x84\xf9\x69\x36\
+\x25\x38\x1e\xd1\xa2\xc2\xbb\xaf\xf6\xa2\x0b\xfa\x86\xd7\xbf\xd6\
+\x5d\x7f\xfd\x75\x72\x3c\x97\xd3\xba\x54\x14\x08\x0a\x10\x75\xb6\
+\x22\x54\xac\x84\xde\xf0\xfa\x2b\xdd\x2f\x6f\xdf\xa8\x15\x4e\x9a\
+\x01\x70\x35\xaa\x82\xda\xf2\x67\x12\x06\x00\xec\x66\xcd\x5d\x8e\
+\x85\xc3\x7f\x2b\xe0\x66\x96\x0e\x84\x35\xb9\x52\x14\x7f\x4d\x9e\
+\x9b\xac\x12\x1e\x61\x1c\x8b\xcc\xee\x18\xc1\x60\xbd\xee\xee\xc4\
+\xf1\xeb\x11\xcc\x5e\x9f\x79\x2a\xa4\x0e\xe3\xd8\xa1\x5a\xee\xaa\
+\xc5\xb8\x66\xca\x71\x77\xf9\xdb\x64\xe8\xd7\x32\x9e\x53\x01\x9f\
+\x9d\x80\x3c\x34\x4f\xd9\xa5\x01\xe1\xc8\xe5\x16\x2e\x47\xaf\x26\
+\xd7\x99\x2f\x54\x90\x5e\x0e\xce\xeb\x38\xe4\xd4\x32\x0f\x01\xa6\
+\xc7\x1c\x5a\x8f\xca\xe1\x71\xb7\xef\xa7\xda\xbd\x6a\x60\xf6\x61\
+\xba\x8a\xeb\x1a\xbc\x21\x91\xa7\x07\x79\xb4\x96\x8a\xf3\xfa\xac\
+\xfd\xaf\xbb\xee\x3a\xd9\x9d\xcb\xf5\x8f\xa5\x4b\x97\x4e\x22\xc9\
+\xd6\x83\xb7\x9c\x70\xa7\x2f\x17\x0d\x3f\xf5\xa9\x4f\xc9\x2e\x58\
+\x0a\x85\x31\x88\xb5\x7a\x3c\xdf\x4d\xb5\x0b\xfb\xb4\xca\x61\x1b\
+\xca\x24\x92\xad\x01\x9a\x3b\x2d\xdd\x0a\x90\xb5\x31\x08\x08\x77\
+\xf4\x86\x41\xba\x61\xb2\xd4\xc3\xc0\x5d\x33\x8b\x8b\x8a\xbc\x61\
+\x9d\x02\x22\x64\xe5\x07\x53\xc0\xe8\x4d\x3c\xfe\xc4\x56\x30\xfd\
+\x7e\x6c\x2d\xdf\x29\x63\x0a\xab\x38\x32\xbc\x0c\x92\x52\x21\x21\
+\xed\xdc\xad\xcc\x2e\x15\xf3\x8a\x95\x81\x75\xbd\x2c\xf8\x58\xa7\
+\xb0\xf0\xd6\x13\x1c\xfc\x8f\xc1\xcf\xca\xcc\x0a\x22\x07\xf9\xc3\
+\x5e\x69\xb7\xf9\xce\xb2\x6c\xb9\xe1\x94\x6f\x73\x7c\x45\x20\x5a\
+\x49\x45\x14\x3a\xf3\x8e\x02\xd2\x51\x1a\x77\x2b\xba\x90\x41\x98\
+\x15\xdc\x8d\x6e\xd6\x9c\x79\x85\x7c\x7b\x37\xd2\x5a\x4d\xce\x03\
+\xba\x32\x48\xff\xda\x19\xf3\xfb\x8c\x3d\x84\xb8\xad\xc7\x08\xc9\
+\xdf\x73\xd5\xc8\x25\x6f\xe2\xc0\xaa\xbf\x3f\x8f\x6d\xff\x88\x32\
+\xba\x57\x6c\x3d\x58\x55\x36\xa7\x4f\xed\xda\x0d\xe0\xc6\xbd\x81\
+\x6d\xe8\x37\x40\xe5\x67\xd0\x7a\x10\xdf\x98\x80\x5d\x00\xf6\x97\
+\xa9\xd8\x5a\x90\xc9\x59\x8b\x72\xc0\x49\x65\x8c\x2e\x16\xff\xc3\
+\xf1\x89\xcd\xfd\xff\xed\xdf\xfe\xad\x9c\xbe\xa3\x3f\xd6\x9a\xcd\
+\xf8\xb6\x16\x30\x67\x36\xce\x69\x63\x1a\xd8\x6a\xee\x49\xa5\x29\
+\x0c\x6b\xa1\x64\x2c\x06\xf4\x3a\xbb\x13\xec\x2a\x25\x68\x41\xd0\
+\x27\xf7\x5d\xac\x80\x64\x82\x21\x00\x8e\x25\x74\x91\x70\x0c\x97\
+\x3c\x08\x63\x21\x03\xa9\x33\x63\xd9\xe5\x79\xec\xf1\x27\xdd\xfb\
+\x6e\x78\xaf\xfb\xfe\xcd\x37\xb9\xef\xde\xf4\x6d\xdc\x62\xf2\x66\
+\x81\x31\x7f\xe2\x58\xd0\xcc\x43\x50\x7c\xfa\x60\x39\x16\x05\x79\
+\xf6\xc3\x54\x73\x9a\x0d\x6e\x3a\x85\x50\x94\x10\x8c\x4b\x34\x0e\
+\xc1\xb0\xb3\xfa\x54\x18\xec\x66\x2d\x3d\xc3\xb9\x3b\xf0\x7e\xe2\
+\x9e\x9d\xed\xb2\x89\x91\xb8\x69\xfe\x2a\x1d\x09\x8d\x3f\x3e\xd8\
+\x38\xf4\x34\x24\x1d\x73\xcd\xef\x18\x73\x8b\xf0\x8c\xf5\xe3\xb8\
+\x6c\x2e\xdf\x91\xcf\x5d\xb1\x04\xdd\x2c\xec\x26\xc7\x26\xc6\x37\
+\x0a\xee\xe2\x8d\x08\x75\x66\xea\xc4\x05\xc4\x5f\xda\xd5\x71\xfd\
+\x25\xcb\x20\x0b\x2f\x67\x41\xb6\xf7\x16\xf2\xbc\xd9\x2a\x27\x83\
+\x73\x36\x23\x2d\x22\x03\x98\x64\x04\xba\x57\xb5\x91\xaa\x3b\xe0\
+\x07\xe7\x33\x9d\xb9\x32\xca\x2c\x68\x13\x16\x63\x72\xea\x5b\xb6\
+\x6c\x31\x94\x49\x3a\xfd\xb0\x75\xe1\x19\x10\x6e\x3f\xa1\x6a\x66\
+\x12\xb3\x6f\xdf\xbe\x5d\xdc\x67\xcf\xee\x97\xa9\x60\x29\xc1\x29\
+\x4a\x7d\x0a\xb0\xf8\x67\xaa\x63\x77\xb6\x04\x15\x1c\xfc\xe1\x85\
+\x6f\x41\xb5\x60\x02\xa9\x21\xb1\xb5\xa2\x82\x8b\x0f\xc0\xdf\x81\
+\x16\xc7\x11\x1c\x64\x5f\xf3\xd6\xb7\xb8\x0f\x61\xd6\x8d\xdd\x45\
+\x4e\x32\xfc\xd1\x1f\xfe\xa1\x7b\xf7\x3b\xdf\xee\xb6\x3d\xb5\x1d\
+\xb3\x63\xdc\x78\xc9\x56\x99\x42\x89\x9a\x96\xad\x05\x38\x90\xeb\
+\x41\x35\x3e\x6d\x3b\x4d\xc5\x59\x41\x51\x8c\x9f\x48\xe7\x34\x3d\
+\x66\x52\x1c\xa7\x9e\xe9\xc6\x2d\x9a\xa8\xe1\x77\x63\xe6\xe9\xd1\
+\xfb\xb1\xb5\x05\x3b\x2e\xa4\x75\xcc\xa2\x4d\x33\x20\x6d\x45\x7a\
+\x31\xe5\x7b\x36\xba\x59\x5b\x90\xaf\xc3\xe8\x66\xad\x58\xce\xab\
+\xef\xd1\xe5\x6a\x24\xaf\x5e\xf0\x9e\x0b\xba\xb0\x60\xd8\xc0\x9a\
+\x48\xc8\xc5\xe9\x10\x8f\x4a\x67\x3a\xe8\x93\x71\x0a\x85\xfa\xe5\
+\x18\x9c\x2f\x58\xdc\x8e\x76\xa3\x23\x5f\xe0\x92\x07\xcf\x71\xf0\
+\x5d\xf0\x29\x15\x9c\xb8\xaa\x3e\xba\x6f\xcc\x55\xb6\x22\x25\xd8\
+\x62\x32\xd3\x75\x0f\xa3\xcd\xbb\x76\x6d\x20\x6e\x82\x72\xcb\x2d\
+\xb7\xe0\xca\x9b\x4f\xcb\x14\x2d\x07\xd8\xa6\x8c\xe9\xad\x7f\xcd\
+\x6e\x06\x19\x4d\xbb\x19\x69\x56\xd0\x4e\x5c\x76\xd9\x78\x03\x3c\
+\x15\x17\xd9\x44\xf9\x64\xb5\x4e\x5d\xea\x38\xc9\xbd\x09\xc0\x9a\
+\xb2\x82\x13\xa4\xbc\xb2\xe7\x58\x8a\x78\x3c\x9a\x3b\x8e\x07\x71\
+\xb0\x38\x2e\xca\xb6\x8a\x70\xed\xe6\x1d\xef\x78\xbb\x74\x95\x38\
+\xa8\x66\xc5\xc0\x1b\xe9\xdf\xf5\xae\x77\x62\xe3\xe2\x3c\x37\x8c\
+\xa9\x6a\xae\xa8\x73\xba\x9d\x8a\x38\x1d\x68\x3d\x9f\xd8\xfa\x94\
+\xe3\x3a\x8f\x29\xcb\x3f\xb3\x53\x67\x1e\x50\x71\xba\xf7\xb6\xdb\
+\xfe\xdd\x5d\x72\xe9\xcb\x70\x7e\x05\x42\x25\x42\x42\x91\xf3\x0a\
+\x86\x20\xfa\x02\x0c\x2e\x86\x11\xf4\x8c\x0b\xfd\x21\x88\x25\x73\
+\xb1\x61\x74\x43\x11\xf7\x68\xf1\x3d\x79\xb6\x96\xad\xeb\x55\x21\
+\x72\x8c\xac\xe2\x60\xbd\x5c\xa8\xba\xc5\x1d\xe8\x66\xc1\xbc\x77\
+\x90\x37\xe8\x17\xb1\x70\x84\x35\xae\x9a\x3b\x6f\xa2\x54\xc2\x9b\
+\x5b\x50\xab\x0e\x1c\x83\x8a\x60\x64\x7e\x52\xae\xc8\x80\x8f\x6b\
+\xe1\xd6\x12\xc9\x41\x08\xc4\xaf\x33\x49\xb3\x7a\x0a\xb8\xd8\x1b\
+\xf5\x1d\x54\x1d\x40\x66\xba\x31\x64\x4c\x4d\x32\x00\xc2\x91\x60\
+\x8c\x32\xf0\x94\xef\xd7\xa2\x3f\x0e\xd9\x9e\x91\xb2\x73\xe7\x9c\
+\x8e\xbd\xeb\xae\xbb\x24\x3c\x0a\x03\x07\xe9\x57\x5f\x7d\x75\xa0\
+\xc5\xda\xd2\xe2\xc1\x38\xb1\x7b\xc5\xa9\x60\x2a\x6e\x85\x37\x45\
+\x37\x7e\xc6\x18\x84\x73\xf7\xef\x17\xbe\xf0\x05\xe9\x82\x0d\x0c\
+\x1c\x45\x0d\xc7\xec\xca\x14\xb3\x58\x03\x83\x18\xb1\x58\x27\xdd\
+\xc8\x4e\xb3\xe4\x0d\x48\x8d\x62\xe2\x8e\xa7\xee\x28\x04\x53\x2b\
+\xd6\x8e\x3c\x2f\xcf\x81\xb3\xf8\x96\xae\x24\x6f\x2f\xf9\x4f\xbf\
+\xf9\x26\x39\x35\x49\xbf\x71\xf7\x90\x2d\xe4\xea\x8b\x2e\x94\x9b\
+\x12\x09\xd7\xb4\xe9\xe4\x43\x0d\x42\xb4\x62\xf9\x52\xf7\xdd\xef\
+\xdd\x2c\xad\x28\xd3\x64\xe9\x56\x3c\x9d\xdd\xb3\x4a\xe4\xc7\x3f\
+\xfe\x89\xbb\xe9\xa6\x7f\x96\x9b\xdf\x79\xe5\x2a\xf3\x52\xd3\x13\
+\xa7\x2a\x8d\x7d\x80\xc2\x10\x9b\x03\x06\xf2\x43\x14\xd2\xcc\x31\
+\x55\xff\x92\xc4\xdd\x77\x37\x2e\xa2\x78\x02\xb3\x16\x50\x52\x21\
+\x88\xa9\xc5\x4f\x20\x98\x75\xd3\xec\xc3\x2f\xfe\x67\xb7\x57\xa4\
+\x9b\xb5\x15\xd7\x41\xe1\xa9\xbd\xfc\x15\x0b\xb0\xca\x9a\xe0\xbe\
+\xfb\x7c\xf2\x4a\xf5\xb5\x81\x9c\xa6\x5e\xb2\x64\x5a\xda\x4e\x4c\
+\x40\xd6\x6b\x3d\xc2\x66\x6b\xbc\x91\x5c\x8e\x1c\x76\x6d\x3d\x98\
+\xda\x65\xe6\x61\x9c\x5b\x97\x55\xf0\x29\xe2\x80\x44\x72\x70\xce\
+\x4d\x89\x47\x1e\xf4\xb7\x8e\xcc\x50\x38\x98\x12\xae\x7b\xf0\x3a\
+\x1e\x2a\x0a\x04\xaf\xf7\xe1\x78\xe4\x03\x1f\xf8\x80\x74\xb9\xb8\
+\xe0\xa5\x6b\x18\x88\x93\x15\x0a\x70\x59\x8b\x9a\x3f\x1b\x5f\x98\
+\x00\x91\x16\x19\x86\x1f\x07\xf0\x1f\xfe\xf0\x87\x09\x92\x5a\x98\
+\xfb\xb3\xa8\x42\x19\x89\x21\xd8\xc4\x8d\x8e\x29\x24\x2b\x18\xea\
+\x39\x75\x25\x26\xf8\x03\x02\x49\x86\x8b\xe1\x31\x0d\x0a\x0f\x85\
+\x36\x2f\x97\xb2\xb1\x05\x61\x5a\x8a\x60\xfa\xc7\x1f\x7d\xd8\xad\
+\x5d\xbb\x26\xdc\x31\x2c\x8c\xeb\xc9\xb0\xe5\xe3\xcb\x51\x1d\x58\
+\x08\xa4\xc0\xab\x00\xa8\x23\xd3\xcc\xbc\xb9\xff\x81\x2d\xee\x4b\
+\x5f\xfa\x1f\xb2\xdb\xd9\xd2\x4c\x1a\xfc\x28\x1c\x6c\x8d\x6e\xb9\
+\xe5\x5f\xdd\x7f\xfb\x8b\xbf\x74\xaf\x7b\xdd\xcb\xb5\x7b\xc9\xf4\
+\x0b\x19\xfc\xf0\x5f\x34\x01\xa8\xdd\xd2\xd1\x04\xa7\xa3\xc7\xd2\
+\x6c\x90\x5f\xf5\x4f\x81\x28\xc2\x75\x0b\xae\x31\xe2\x44\x04\x2b\
+\x81\xa8\xb8\x3c\xe6\xf4\x34\x4e\x15\x77\x63\x7f\xd6\x4a\x5c\x10\
+\xb1\x19\xdd\xac\x0a\x86\x8c\x2b\x96\x62\x1c\x82\x9b\xa5\x10\xbe\
+\x0a\x08\xbb\x59\xca\xbf\xd3\x22\x7a\x62\x02\xb2\x61\x9d\xf8\x1b\
+\x2b\xe7\x56\x21\xb8\xb3\xfa\x91\xa8\x5c\x07\x36\x44\x30\x63\xb0\
+\x65\x84\xb3\x57\x4c\x78\x9c\x29\x71\x6c\xe8\x56\x39\x58\x71\xd5\
+\x67\xd0\x11\x05\xa5\x84\xb3\x5d\x27\xa0\x78\xc0\x89\x2b\xdd\xec\
+\x66\xb1\x25\x61\x8d\xcf\xa9\x5a\xb6\x12\x14\x20\x65\x9a\x94\x36\
+\xed\xd2\xcd\xf0\x17\x57\xf3\x52\x07\xd2\x30\xc6\xa0\x4e\x75\xff\
+\xfd\xf7\xbb\xf7\xbf\xff\xfd\xa2\xf3\x46\xc5\xc3\x87\x71\xaa\x11\
+\xad\x5e\xaa\x94\x66\xa0\x0c\x43\xca\x02\x1e\x2a\x9a\x99\x53\x57\
+\xc9\x14\x0f\x46\xb9\x81\x11\xa3\x8c\x22\xdc\xbb\x31\x2c\x33\x72\
+\x95\x9d\x3d\x3c\x0a\x0b\x15\xa7\x74\xa9\x96\xf8\x19\x3a\x0a\x00\
+\x19\xda\xe2\xf8\x00\x98\xff\xa7\xb7\xfe\x42\xb6\xce\x4c\xe0\xba\
+\x43\xba\xcb\xe7\xbb\x4d\x9c\xb2\x3e\x05\xfb\xcd\x6e\xf9\xd7\x1f\
+\xb8\xff\xef\xe3\x9f\x70\x3f\xff\xf9\x2f\x64\x91\x95\x15\x0a\xb7\
+\xc5\x6f\xda\xb4\x49\x6e\x65\xfc\xe0\xff\xfb\x61\xb7\x14\x5b\x6f\
+\x98\x2f\xb2\x06\x64\x91\x47\xf0\x1a\x03\xfd\xa5\x25\xa4\xb0\xc9\
+\x1c\xb9\xc0\xe8\xf1\x19\x79\x98\x99\xdd\xbc\x68\xee\xb4\xb3\x12\
+\xf7\xf3\xef\xe5\xdd\xfe\xdd\xd8\xfa\xd3\x34\x42\x08\x3e\x82\x81\
+\x9e\xb3\xca\x9c\x38\x9b\x55\xc6\xc6\xce\x05\x78\x92\x8e\xb3\x59\
+\x87\xb0\x9f\x6f\x2e\x66\xb3\x70\xfe\xc2\x8d\xd4\x93\xd5\xee\xb7\
+\xd7\x2e\x17\x9f\x33\xb8\xf4\xda\xf7\x6a\xb3\x01\x1e\xd7\x76\xe6\
+\x50\xce\x6d\x40\xdf\x2e\xc9\x5f\x86\x15\xaa\x62\x4f\x07\x7a\x93\
+\x58\x91\xa9\x23\xd1\xbc\xbf\x8a\x85\x41\xc6\x97\x8c\x8b\xf9\x0a\
+\x00\xae\x65\x71\x7c\x32\xfc\x8c\x1e\x64\xca\x75\xa2\x06\x6d\xba\
+\x91\xfd\xb8\xe1\x47\x08\x2c\x54\x2e\xe2\x71\xcd\x83\x35\x27\xa7\
+\x7e\x19\x3e\x0b\x55\xe2\x11\xe1\x9a\xfd\xe9\xa7\x9f\x96\xed\xef\
+\x9c\xde\xe5\xe6\x45\xde\x5f\xcb\x99\x30\xce\x6c\x91\x39\xfe\xe4\
+\x4f\xfe\x44\x7c\xf1\xb1\x9d\x3d\x7b\x71\xdd\x28\xce\x4d\xb0\x10\
+\x58\xd7\xb3\x7f\x8b\x7f\x6d\xa3\xc5\x8a\xb0\xd4\xa6\x21\xc5\x30\
+\x45\xf7\xb8\xc8\x17\xc4\x89\x34\x00\x96\xf8\xd1\x03\xf8\x9a\x36\
+\xc9\x2b\x9a\xd4\x4c\x5c\x98\xe8\x9f\x7f\xb0\xb0\xa5\x11\x8f\xc0\
+\xa0\x30\x50\xf1\x51\x4e\x2a\xb6\x12\xd6\x25\x7a\xfc\xf1\xc7\xdd\
+\x67\x3f\xf7\x39\xb7\x6c\x29\xee\x05\x42\x3e\x70\x8d\x83\x03\x73\
+\x86\x29\xe1\x02\x46\x9d\xe3\x17\x6e\xca\x7c\x1c\xb3\x60\x6f\x7d\
+\xc7\x7b\xdc\x65\x17\x5f\x84\xed\xf0\x0b\xdd\x51\x54\x18\xff\x71\
+\xfb\x5d\xb2\x47\xeb\xfc\xf3\xce\x11\xff\x32\xa0\x67\xf0\x68\xe9\
+\x59\xb4\x4a\x41\x0c\x92\xc7\x6a\xd7\x00\x62\xb3\x61\xc2\x45\x3d\
+\x2a\x4a\xc6\x4c\x5e\x68\xc3\x01\x30\x74\x84\xdd\x13\x5b\x8a\x6e\
+\xf1\xa9\xbc\x97\x8b\x69\x12\x5f\xfa\xc3\x08\x43\x51\xf3\x46\xb1\
+\x67\x7f\x18\x32\x06\xfa\x38\x92\xdb\xdf\x86\xae\x60\xa1\xc3\xed\
+\xc6\x9a\xc8\xe2\x85\xf9\xc2\x8a\x59\x2e\xd9\x3e\x9a\x9b\x3f\xb7\
+\x23\xb9\xf0\x20\x2e\x5a\xc9\xfa\x3b\xb6\xed\xc4\x04\x44\x0f\xa2\
+\x70\x1d\xe6\x65\x24\xdf\xd9\x85\x7b\x4b\x0a\xb9\x02\x17\xf9\xd8\
+\x7a\x4c\x99\x08\x3a\xa0\x26\xae\x8f\xd5\xdc\x90\x8d\x3f\xc8\x1c\
+\x53\xfb\x20\xf9\xe3\x2a\x0e\x24\xf9\xc5\xca\x84\x21\x86\xc5\x66\
+\x6e\x7f\xe7\x1a\x0a\x5b\x11\x7e\xb1\xea\xc4\x96\x59\xbe\xb6\xc4\
+\xad\x2b\xba\xf0\x86\x88\x37\x09\x86\xae\x53\x30\xee\x5e\x09\x8a\
+\x17\x96\x8c\x19\xf9\x21\x82\x01\x3c\x32\x27\x4b\x5f\x14\x33\x03\
+\x76\x32\x5d\x06\xe2\xed\x06\x24\x16\x05\x04\x8c\xc4\x16\x84\xe9\
+\xd2\xf7\x49\x30\x86\xc1\x94\x2d\x15\x85\x83\x5d\x27\x8e\xc5\x3e\
+\xf3\x57\x9f\x95\x16\x95\x17\x52\x8f\xa3\xf5\x60\xf7\x89\x63\x74\
+\x15\x11\xfd\x15\x4f\xf8\xa1\x9f\xd9\xfd\x7d\xee\x55\x2f\xbf\x4c\
+\x76\x24\x3c\xf4\xc8\xa3\xb2\xa5\xe6\x12\x08\x0b\x2b\x18\x0a\x11\
+\xcf\xe8\x8b\x6f\x46\x94\x31\x55\x09\x91\x78\x20\x36\x4a\x4a\xc0\
+\xb4\x45\xf6\xd4\x26\x7e\xe8\x22\xae\xf4\x6f\x66\x40\x98\x1d\x9c\
+\x50\x5b\xd0\x8b\x35\x91\xdb\x0b\x6e\xcd\x2b\x0b\xae\xbb\x0f\xbd\
+\x10\x6c\x1b\x51\x7c\x41\x9f\xf2\xc7\x93\x53\x77\x96\x11\x52\xda\
+\x53\x1e\x77\x67\xe3\xd4\xe2\x0e\xbc\x5d\xb2\x06\xb5\xc3\x99\x8b\
+\xf2\xd5\xed\x8f\x25\x5c\xe6\xbd\x14\x88\xb7\xc8\x85\x86\x37\x4f\
+\x8f\xf1\x66\x2e\x20\x5c\x8d\xbc\xd9\xd5\xb9\x3a\xb9\x7b\x3c\xb9\
+\x80\x2d\x45\xb1\x93\x47\x7a\x50\x7c\x7e\x71\x50\x58\x80\xa9\xf3\
+\xbc\xa0\x59\xa2\x19\x24\x19\x3f\x84\xfb\x65\x79\xb9\x11\x15\xf6\
+\xa8\xbc\xd0\x8a\x71\xa0\xe2\xa0\x9e\x03\x75\xce\xfc\xb0\xf5\x21\
+\xa3\x91\xa1\x38\x05\xcc\xd5\x66\x9d\x26\x45\x32\x2c\x41\x28\x00\
+\x13\x0c\x4d\x1e\x7e\x45\x70\x5a\x09\x86\x38\x69\x16\xa0\x14\x33\
+\x42\x42\x3f\x3e\xd9\x96\x7a\xd3\x2d\x2f\x48\x91\x8a\xc2\x41\x5c\
+\x3f\x11\x45\x88\xb4\x74\xe7\x9d\x7f\x21\xe6\x49\xfe\xb7\x30\x71\
+\xa5\x32\x86\x5a\xff\x57\xee\x1f\xbf\xf6\x4d\x77\xce\x99\x2b\xe5\
+\xe8\x2d\xbb\x51\x5c\xf7\xe0\x0c\x16\xe3\x4f\x1a\xd6\x45\x13\xc2\
+\x42\x5b\x17\x57\x19\x36\x5b\xc9\xf6\xb9\x1d\x22\x10\x6c\x49\x75\
+\xec\x22\xc1\xc1\x2f\xe2\x0f\x24\xd1\x11\xbe\x08\x02\x3d\x45\x30\
+\xd8\xc4\x4e\x44\x71\xcf\xd8\xc5\x55\x23\xe1\xd1\xd4\xb3\x7a\x61\
+\xa5\x3a\x0b\xa7\x24\xef\xbd\x3d\xe7\x5e\xbf\x1d\x6f\xbc\xaf\x61\
+\xb7\x90\x79\xed\xfd\x05\x3f\x59\x3b\xe3\x94\x55\x08\x19\xfe\xda\
+\x30\x9b\xb5\x00\xd7\x9d\x6e\x18\xc1\x11\x0a\x54\xda\x0b\xe7\xa1\
+\xdf\xf6\x10\xce\xc2\x24\xc9\xc5\x82\xbf\x1e\xec\x9a\xa1\x9e\xa5\
+\x12\xdb\x66\x2e\x20\xb2\x1a\xb9\xb1\x8e\xeb\xaa\x56\x22\x36\x2b\
+\xba\x20\x20\x85\x76\x0c\xd0\x99\x59\xa8\xe5\xea\xf8\xf4\xae\xd8\
+\x6c\xec\x69\x33\x78\xe5\x10\x87\xa7\x88\x21\x9e\x2d\x78\x36\xdd\
+\x2b\x21\x72\x02\x3f\x2c\x68\x0a\x09\x85\x61\xc7\x8e\x1d\x42\x81\
+\x8b\x8c\x8c\x63\x82\xb6\x9d\x63\x98\x12\xec\xec\xeb\x73\xe8\xc1\
+\x4c\xf7\x32\x25\x38\xac\xa7\x54\x30\x7c\x2e\x8b\xb5\x95\x90\xa0\
+\xc0\xe0\x51\xca\x99\x8c\x63\x44\x84\x0a\x83\x65\x81\xe2\x13\x13\
+\xc3\x21\x44\x48\xd3\x31\xc2\x52\x1b\x99\x9d\x38\xdc\x59\x3c\x1f\
+\x5d\xc2\xdb\xfe\xfd\x97\xf2\x38\x27\x5d\x57\x9c\x7a\x86\xbb\x64\
+\xf5\x05\x22\xe4\x3c\xfb\xc1\xb4\xe9\x1a\x08\x43\x27\x55\x15\x34\
+\xb3\xd1\x8f\x2a\xa5\x59\xc5\xe4\x05\xe3\x42\x5c\xaf\xc1\x48\x33\
+\x60\xe1\x33\x23\xe0\xfe\x4f\x48\x0b\x4a\x80\x90\x84\xfa\x15\x1c\
+\x0b\x86\xee\xea\xa4\xf4\xbc\x99\x38\x60\x57\x9b\xc2\x7e\xe2\x81\
+\x82\x5b\x79\x1e\xcb\x06\x07\xbc\xd8\xad\x34\x3f\x62\xd2\x1f\xd2\
+\x11\x62\xcd\x30\xa0\x73\x1c\xc2\xbd\x59\xb3\xf1\x14\x9c\x1b\x2a\
+\xb9\x01\x5c\x73\xdb\x3f\x0b\xe3\x10\x08\xcd\xd1\x6a\x72\x2e\x2a\
+\xf6\xb9\xbb\xbf\xbc\xf1\xa0\x5b\x8f\x0c\x59\xdf\x4c\x25\x22\xe8\
+\x8d\x33\x17\x10\xef\xb1\x92\x4b\xce\x05\xf7\x74\xe3\x45\x6b\x2c\
+\xe9\xe2\x1e\x38\xc4\x5a\xba\x57\x94\xcd\x78\xa0\xc5\xd4\x58\xa9\
+\x40\x98\x38\x46\x19\xdb\xaf\xad\x07\x67\xb3\x34\xdb\x3c\xd1\x17\
+\x58\x63\xb9\xf3\x49\x67\xdb\x6a\xc1\xf5\x05\x33\x8b\x10\x31\xe2\
+\x52\x46\x88\xa5\x08\x09\x3c\x40\xc7\x3f\xc0\x66\x6e\x25\x18\x44\
+\xd3\x84\x73\x5b\x36\xf1\xe9\x83\x66\x51\xa4\xe1\x8d\x22\x33\x44\
+\x20\x39\x38\x0a\x98\xee\xf8\xa3\x37\xc1\x83\x01\xfc\x2e\xf9\xab\
+\x18\xb8\x9f\x00\x0c\x3d\x0f\xdb\x6d\x5e\x77\xe5\xeb\x51\x0c\x88\
+\x37\x78\x80\x17\x37\x70\x12\x82\xcf\xa8\x89\x70\x90\xae\x10\x01\
+\x21\x1f\x1f\x1f\xac\x44\x83\xb4\x34\x1e\x3e\x70\x6f\x37\xa1\xa0\
+\x9e\xf9\x23\xb2\x7c\x1a\xd1\xd8\x5d\x08\x92\x78\xf0\x63\x41\xa8\
+\x1f\x3a\x89\xa2\x7f\x28\xf9\xf5\xb4\xa8\x51\xf8\x17\xf4\x27\xee\
+\x81\x5f\xe1\x91\xd3\x2b\xb1\xb7\x6a\x41\x03\x6f\x20\x02\x8f\xf1\
+\x17\xa5\x06\xc9\x15\xf1\x6c\x0e\x52\x12\xc0\xf0\xee\x70\xe3\x01\
+\xb4\x5e\xbc\xc0\x85\x99\x23\x77\x18\x02\xb2\xa8\x2f\x97\x9b\x8b\
+\x83\x66\x98\x17\x5a\x92\x6b\x4f\x4e\x07\x32\x86\x22\xeb\x90\xa3\
+\x1b\xc8\xad\xc7\x54\x40\x9a\xa1\x3a\x72\x9a\x10\x05\x03\xac\x62\
+\xf5\xda\xde\x06\xd9\xc0\x2d\x66\xcc\x2c\x1e\xab\x94\xb8\x83\xa4\
+\xe9\x19\xea\xe0\x06\x6e\x41\x19\xd9\xa5\x03\xf4\x13\x9d\xbd\xca\
+\xd0\x7c\x56\x16\xc6\x92\x4c\xa2\x5f\x6a\x36\xa2\x84\x9b\xd9\xd2\
+\x04\x80\xfe\x8b\x5f\x35\x7b\x24\x81\x93\xa5\xd4\x49\x99\x25\x6b\
+\xa6\x1b\x31\x18\x26\x4d\x79\x4e\x6b\x7a\x94\x50\xc8\xe6\x45\x74\
+\x16\x3c\x3b\xb0\x58\xc7\x40\xeb\x4c\x6f\xe6\x97\xe7\xc7\xab\x10\
+\x0a\xb6\x28\x63\x63\x38\x31\xe8\x07\xef\x96\x1e\xb6\x80\x14\x14\
+\xd1\xa5\xf5\xe1\x94\x6f\xf4\x09\x4c\xe3\xa2\xb8\x8a\x6f\xfe\x27\
+\xe9\x1a\x73\xfd\x95\x88\x68\x44\x03\x9e\xb8\x58\xe4\x95\x6e\x48\
+\x9b\x46\x3c\x4d\x2b\xed\x1e\x95\xf9\xc0\x95\xf5\x1e\x2c\x1a\x3e\
+\xfa\xa0\x73\xbb\xb7\xa3\x86\x65\xb2\x85\xe7\x3d\xe3\x1b\xae\x7a\
+\xa3\xd7\x94\x96\xd8\x14\x40\x01\x22\x4a\x17\x04\x64\x29\xf6\x2b\
+\xe2\x8c\x12\x2a\xf0\x5c\x6e\x65\x1f\xa4\xa6\x91\xc3\x1d\xda\xa8\
+\xd8\xa9\x76\x63\xa2\x69\x1a\x6a\x66\x02\xc2\xd0\xfd\xfe\x2b\xc4\
+\xfe\x6c\xd2\x2f\xb7\xe7\xd9\xaa\x61\xff\x15\xd7\x18\xa2\xd8\xc3\
+\x2d\xd8\xbc\x81\xb5\x65\x0d\x9d\xc2\xb1\x07\x55\x40\x38\x3f\xfd\
+\x62\x2b\x16\xae\xa9\xd4\xc8\xc2\x0d\x50\x6f\x06\x80\xff\x02\x4f\
+\xcd\xc0\xf4\x70\x9a\xd4\x4c\x24\x31\x93\x84\x37\xab\x37\xba\x1b\
+\xbe\x38\x7a\x01\xd1\x42\x15\xa7\x14\x45\x50\xb9\x42\x4c\x5a\x1c\
+\x3f\x90\x89\x02\x33\x92\x2e\x3e\x0e\xa2\x75\x71\x53\x3d\x9a\x50\
+\x70\x3f\x9c\xe0\x7a\xc1\x08\x42\x42\xbb\xff\xb2\xb8\x10\x1c\xf3\
+\x43\xba\x9e\x7e\x73\x78\xc1\x2e\xb1\xd2\x30\x60\xd4\x74\x9a\x1f\
+\x71\x53\x18\x13\x21\x7e\x68\x95\x24\x6b\x3c\xc5\x0b\x21\x70\xa7\
+\xe2\x5c\x40\x01\xa7\x65\x89\xb5\xf5\x61\xec\x1c\xc0\x85\x73\x6c\
+\x35\xe9\x2c\x18\xea\x24\xb8\x91\x51\xec\x46\x46\xf0\x60\xb1\x71\
+\xc8\x3c\xac\xcc\xef\x45\x6f\x1e\xd9\xe6\xe6\xcf\xc6\xfe\x72\x74\
+\xd9\xb0\x86\xad\x02\xe2\x2b\x7a\x25\x30\xf5\xef\xcc\x04\xe4\x1a\
+\x7f\xb6\xf7\xba\x15\xed\xc8\xc0\x53\x19\xb1\x62\x1b\x64\x03\x9c\
+\x6f\xe3\x0f\xe9\x32\x44\xe1\x59\x62\x24\xf2\x10\x90\xea\x30\x9a\
+\x3e\x28\x5e\xe9\x73\xa2\xdb\x4b\x22\xf2\x27\x6c\x64\xa1\x99\x4a\
+\xcd\xcc\xdc\x00\x6d\x61\x56\x47\x2d\x34\x8f\x08\x0d\xbe\xd4\x53\
+\xb3\x19\x88\xe2\xc2\x1f\x31\xab\x9d\xbf\x16\xa6\x0c\x9c\xe9\x4f\
+\x04\x81\xb4\x28\x10\x91\x5d\xfa\x18\x98\xb9\xe2\xee\x6d\x9c\xb4\
+\x14\x0a\xa4\x35\xad\xcf\xb7\x1e\x82\xab\x66\x32\xbe\x7d\xda\x9a\
+\xa4\xc2\x90\x0a\x85\x2e\x2e\x4e\x2b\x0c\x89\x91\xc4\x8a\x91\x67\
+\xc4\xe5\x13\xbf\xb4\xe2\x33\x18\x75\x41\xe1\xaf\xe0\xa9\x3b\xa1\
+\xc4\x67\x03\x38\x07\x47\x8a\x1f\xbe\x8b\x2f\x01\x43\x40\xd0\x90\
+\x10\x4d\xd0\x3d\x2d\xa1\x47\x9a\x5e\x79\x32\x66\x15\xdd\xc6\x21\
+\xfd\x78\x47\x7e\x17\xa4\x63\x1c\x74\x67\xf1\x9d\x3f\x78\xae\xd4\
+\xdd\x99\x82\xa4\x15\xfd\x71\x5b\x91\x99\x09\x88\xdf\xc7\xd2\x91\
+\xc3\x08\xb1\xe1\x96\xb0\xaf\x97\xc7\xfa\x07\x23\xcd\x3e\x64\x66\
+\xee\x3a\x13\x65\xb5\x30\x31\x13\x18\xdd\x8b\x92\x87\x64\x5a\x20\
+\xbd\x80\x20\x16\x8a\xa9\xd4\xcc\xc2\x0a\x50\x2b\x47\x00\x14\x2e\
+\xb9\xec\x4b\x2c\x63\x86\x27\xb1\xc3\xaf\x32\x87\x27\x42\xb8\xb8\
+\x09\x89\x50\xe2\xac\x31\x7b\xe0\x83\x57\x71\x6a\x2b\xa1\x4e\x0c\
+\x3b\xfd\x30\xbd\x0b\x3b\x73\xb8\x54\xc6\xb1\xdb\x71\x4f\xcb\x68\
+\xc6\x3a\xf3\x5f\xec\xda\x12\x64\x98\x3d\xb4\x0c\x51\xf7\x8a\x5d\
+\x2d\x29\x33\xd2\x54\x73\xaa\x1f\x23\x9c\x38\xcc\x66\xb3\xe6\x80\
+\xc4\xdf\x12\x61\x69\x67\x6e\x48\x1e\xc3\x8f\xcf\x3e\x9f\x5f\x6a\
+\x97\xde\x05\xd2\xd7\xb7\x2c\x71\x8f\x3d\x84\x9b\x27\x77\xa5\xac\
+\x49\xbf\xb1\x52\x5a\x9a\x4f\xcd\x70\xda\x59\xd9\x70\x3d\xa4\x07\
+\x02\xe2\xf0\x3f\x8a\xa5\x95\xae\x6e\xd0\x43\x77\x76\xa0\xe1\x96\
+\xaf\x5b\xbf\x0e\x3b\xb4\xa0\xd6\x6b\x27\x4e\xcc\x53\xfc\xa4\xb1\
+\x98\x02\xa1\x15\xb8\x96\x4f\x16\x01\x3e\xbb\x8d\xbe\xd1\xbf\x63\
+\x9a\x59\xe0\x2c\x4c\x2a\x4b\x80\xda\xec\x17\x35\x23\x10\x26\x8e\
+\xea\x0c\x96\x41\x5f\x6c\x5d\x0a\xcd\x47\xc2\x0a\x93\x29\x88\xcd\
+\x56\xa0\x92\x32\x24\x8e\xe9\x8d\xcd\xc0\x56\x0a\x74\x93\x3f\xef\
+\x4c\x06\x0a\x76\x98\x68\x97\x0f\xe5\x86\xf1\x44\xd7\x1c\x6d\x19\
+\xe8\x9b\x34\x43\xeb\x61\xad\x88\xc0\x51\xc9\xa1\xb0\x29\x48\x13\
+\xa4\x25\x34\x89\x6f\xb4\x62\x9d\x02\x60\x76\x63\x7a\x0a\x0e\x84\
+\x90\x42\xd2\xf4\x05\x81\x82\x1f\x13\x28\xd3\xad\x75\x89\xbb\x5d\
+\x29\x6d\x0b\x63\x0a\xdd\xa7\x19\x64\x7d\xc2\x98\x38\xe0\x6a\xb6\
+\x48\x1c\x69\x27\x40\x61\x74\x43\x1c\xc0\x43\x36\x9b\xb5\xe3\x31\
+\x74\x87\xb8\x4f\x0d\x3c\x96\x69\x5d\xc5\x83\x55\xfc\xbe\xb5\xf5\
+\xf9\x25\x23\x7a\x4f\x93\x93\x12\x5d\x78\x0d\x98\x22\x80\x9d\xbd\
+\x18\x0a\x40\x04\x31\xa1\x84\x71\xef\xc2\x9d\xfb\x47\xe6\x21\x2a\
+\x18\xa3\x73\xa0\x7e\x6c\x75\x5c\x84\x8c\xf7\x0d\x6a\x43\x86\x2f\
+\x41\xb5\xd6\x46\x01\xc9\x15\xd9\xbf\xa2\x80\x30\xb3\xe8\x8e\x18\
+\x79\x5d\xf3\x00\xce\xb4\x03\xcc\x19\xac\x71\xde\x36\x4c\x05\xfc\
+\x17\x5b\x69\x7c\x59\x00\x5a\x78\x12\x1f\x9a\x2d\x6a\xc1\x0c\x80\
+\x37\x2b\xa6\xe2\x98\x99\xe9\x55\x1a\x34\x80\x0a\x71\xfd\x5f\xb0\
+\x0b\x4c\x9d\xf9\xcb\x19\xbf\xae\x3e\x6d\x41\xb8\x95\xc4\x7b\x93\
+\xb0\x81\x2a\x95\x8d\xea\xc8\x38\x14\x76\x19\x2d\x08\xfd\x29\xc3\
+\xc6\x82\x60\x30\xe8\x0c\x03\x1f\x05\x22\xe8\x14\x0a\x81\x4f\xdd\
+\xc5\x0a\x34\x05\x37\x2b\x58\x46\xb3\x59\x07\x51\x49\x67\x2b\x3d\
+\xe0\x32\xbe\x12\x6b\xa2\xa6\xf8\x93\x61\x1e\x09\xe9\xac\x61\xbd\
+\x00\xd7\x67\xbb\xc7\x36\xe1\x52\x0b\x1c\x15\x52\x01\x31\x22\xcc\
+\x81\xec\x07\x6b\x00\xc4\x6e\x44\x6c\xc3\xb6\x13\xe6\xdd\x30\xef\
+\x66\xc3\x2b\x09\x8b\xd9\x6e\xa0\x91\xc2\xfe\x41\x56\xf0\xce\x71\
+\x47\xc8\x71\xd4\xcc\x04\x64\x9e\x9e\x20\x44\x6f\x6e\x09\x67\xb0\
+\x30\x43\x8a\x3d\x0e\x88\x02\x12\xef\x27\x50\xa6\x0e\x0e\x72\xc4\
+\xe3\xb5\x13\x7e\x0d\x84\x4d\xdf\x8b\xaf\x94\x79\x24\x1e\xc2\x44\
+\x16\x23\xc2\xb3\x66\xb5\xe2\x97\xff\x62\x49\xcd\x81\x0d\xc4\x8d\
+\x36\x75\x53\x5c\xb5\xab\x17\x41\x00\x61\x8c\x27\x50\xab\x75\xf7\
+\x6a\xcb\xc0\xae\x29\x69\x12\x07\xf3\x55\x5e\x38\xa0\xd3\x8e\x81\
+\x25\x29\xc8\xad\xe8\x34\x49\x3c\x27\xeb\x26\x14\xa1\xc6\xf7\x82\
+\x21\x2d\x42\x24\x24\xda\x32\x50\x80\xbc\x90\xc1\x2d\xf8\x9d\x82\
+\x76\x73\x98\x8c\x6c\x0a\x63\xdc\x63\xbb\x4f\x23\x60\x19\x3c\x49\
+\xb5\xe2\x11\x2e\x6e\x06\xf3\xe9\x82\x55\x76\xf8\xce\x39\x15\xd3\
+\xbd\x77\xe1\xaa\x55\x5c\xb3\xca\xae\x17\xf3\xc5\x3e\xe2\x98\xf2\
+\x64\x26\xbb\x91\x3c\xf2\xb1\x8c\x8d\xbc\x0b\x31\x8e\x19\x44\x5e\
+\x63\x5d\x25\x37\x97\xef\xfe\x35\x72\x6d\x8d\xa4\xa6\x02\x62\x84\
+\x8e\xa1\xcf\x44\x40\x72\xee\x7b\xba\xc5\x1d\x5d\x3c\x04\x40\x01\
+\xc9\x61\x7c\x9e\x76\xb1\x18\x8e\x25\x84\xba\x28\x03\x30\x6e\x98\
+\xe2\xad\xed\xd7\x2e\x16\xca\xe7\xa4\x51\x2c\x60\x55\xbe\xa0\xc5\
+\x62\x85\x4e\x0b\xdc\x81\x23\x05\xe2\x53\xa8\x85\xe3\x13\xa7\xce\
+\x70\xd1\x3f\x45\xf7\xfe\xc5\x42\x12\xde\x4e\x8f\xa8\xd5\x2a\xd8\
+\x7f\xd6\x3d\x2b\x3b\x06\x01\xaf\xfa\x30\x7c\xd7\x01\x76\x8e\x4f\
+\xa8\x4a\x38\x29\xc7\x78\xb0\xab\x14\xd3\x6a\x66\x4e\x63\x76\xd1\
+\x45\x30\xd8\x72\xf8\x56\x81\xf6\xa6\xcf\xf0\x09\x37\x73\xc0\x47\
+\x5c\x63\x73\x73\x58\x96\x07\xa2\x4b\xec\x7c\xfc\x01\x30\x5c\x4d\
+\x90\xc5\x99\x29\xe0\xa7\xee\x3e\xb1\x02\x14\x18\xd3\x87\x9e\x05\
+\x6f\x83\xa7\xda\xb3\x83\x15\x84\x9a\x43\x18\x4a\x4a\xbc\xaa\x8b\
+\x77\x87\xa6\xb4\xbd\x0e\x4b\x11\x02\xd2\xc7\x71\x07\xe6\x85\x12\
+\x54\xe8\xbd\x98\x00\xa0\xc4\x61\x01\xf2\x79\x10\x10\x86\xae\xe5\
+\xc6\xc4\xcf\x67\xb4\x0a\x78\x1c\x0e\x30\x89\x91\x25\x84\x70\x53\
+\x71\x84\x09\xab\xe3\x86\xc5\x06\x46\x49\xa2\x50\x20\x27\x93\xd2\
+\x02\xf5\x31\x92\x02\xb6\xd8\xb1\x30\x35\xd3\xe5\xd7\xbb\xb1\x40\
+\x69\x17\x37\xc2\x68\xf7\x88\x42\x4b\x20\xea\xd1\x98\x45\xb0\x00\
+\x62\x36\x0e\x62\xa7\x7f\x2f\xf6\x1c\xc9\x20\x9d\xad\x84\x78\x37\
+\x9d\x82\xa1\x61\x8a\x8e\x6c\x96\xe7\xcb\x48\xd3\x87\x15\x68\x46\
+\x8c\x7d\xac\xd6\x83\x8c\xae\x1f\x99\x5e\x19\x5f\x98\x9f\xf4\x5a\
+\xd1\x20\x7c\x3a\x9f\x4f\x67\xc0\x15\xbb\xc6\x3d\xc0\x3c\x1d\x9f\
+\x48\xd5\x34\xf7\x42\x18\xf0\xa6\x9e\x60\x60\x57\x9c\x96\xa7\x1f\
+\xe3\x7e\x31\x3f\x0e\x21\xa3\x11\xec\x99\xb0\x79\xbc\x66\xe3\x0f\
+\x5b\x59\xa4\x3b\xdf\x12\xe9\x82\x80\x1c\x41\x4f\x8b\xa3\x91\xee\
+\x0e\x00\x00\x87\x55\xf8\xd7\x3d\x8e\xbb\x50\x8f\xa3\xa6\xdf\x82\
+\x7c\x92\x31\x54\x85\x66\x03\xf7\xe3\x71\xde\x9a\x02\x82\x42\x45\
+\x06\xd7\x59\x92\x5e\x21\x3f\x26\x2b\x76\xb1\x78\x4e\x9d\x2a\x50\
+\x52\xeb\x8b\xf9\x6b\x85\xa8\x71\x50\xa6\x88\xcd\x9a\x16\x24\x48\
+\x0a\xd9\x97\x11\x4b\x2a\xd8\x99\x58\xda\x05\xa4\x05\xee\xed\x0a\
+\x9b\xcc\xd4\x4c\xff\x18\x1c\x29\x20\x05\xcc\xd5\x6b\x37\xca\xfc\
+\x47\x5d\x2c\xa1\x49\xa1\x21\x93\x30\xef\xc0\x3c\xc8\x67\x8b\xb3\
+\xea\x93\xbb\x4a\x69\xad\xaf\xe3\x8e\x20\x38\x12\xe7\xa8\x7b\x45\
+\x7b\x93\x70\x98\xd0\xa4\x34\x18\x9e\x6f\x81\xc4\xbf\x0a\x58\x36\
+\x0e\xcd\x71\xf2\x76\xcd\x19\xc4\x3a\xeb\x0e\x82\x3e\xb1\x4c\x11\
+\xbf\xd4\x1d\x56\xd9\xbc\xd8\x87\x17\x6d\x9f\xdc\x84\x5b\x49\x70\
+\xa8\x2c\x5e\x0f\x21\xbe\xa9\x40\x86\xe4\x00\x8c\x3f\xb6\xbc\x5c\
+\x5c\xc5\x1d\xbd\xee\x00\xa4\x83\xec\xd9\xde\xae\x53\xbd\x30\x63\
+\x49\x12\x6a\xdd\x06\x66\xea\x31\xb9\xf1\x44\xb6\x9a\xb0\xc4\xfa\
+\x19\x1d\x6e\x15\xa1\xb2\x88\x8a\x19\x3f\x84\x0a\xcc\x07\xcd\x88\
+\x33\xf6\x72\x05\x29\xcd\xf4\xc7\x18\x9f\x44\x8a\x05\xce\x98\x4b\
+\x9f\x57\xcc\xb0\xd1\x82\x88\xab\x95\x6e\xc0\x91\x7f\x9f\x30\xd6\
+\x0f\xc1\x0e\x43\x00\x23\x8b\x88\x48\xe5\x61\xac\xd9\x3c\x35\x09\
+\x83\x84\x7a\xfb\xb9\x4d\x9d\x0c\x07\x17\xa0\xe3\xf2\x0b\xf1\x82\
+\x62\x94\xf1\x07\xd6\x5f\x75\x1c\x02\xbb\x84\x4d\xaa\x88\x8c\x8f\
+\x9e\xf8\x09\xf4\xc5\x27\x7e\x48\x42\xc2\x34\x83\xc6\xc4\x2e\x9a\
+\x36\x34\xd3\x43\x3c\x09\x20\x6d\xd1\x49\x06\x26\x6f\x97\xbc\xa1\
+\x95\x7f\x0c\x9c\x70\xf9\x52\x98\xda\x0d\xde\xac\x13\xcf\xe3\x7a\
+\x1a\x86\x4f\x5a\xe2\x48\xb8\xb9\xa1\x9b\xd5\x89\x2a\xf8\xe9\xa7\
+\x50\xfb\x1f\xc0\xa9\xc3\x05\xdc\x3a\xe3\x69\x68\xf0\x62\x67\x54\
+\x4d\x29\xfd\x40\x0a\x94\x98\xdf\xd8\x4a\x8f\xfc\xc5\x11\x25\x6c\
+\x54\xe4\x86\x4c\x00\xa0\x70\x2c\x03\xfc\x0b\xb5\x5e\xb2\xd7\x4a\
+\x48\x40\xcd\x3f\xd3\x6f\x41\x1e\xf2\x45\x71\xdd\x0a\x1e\x8e\x90\
+\xbb\x70\x70\xae\x5c\x4a\x9d\x91\x97\x81\x26\xa9\x4b\x14\x7c\x98\
+\x28\x78\xb1\x7a\x70\x10\x90\xe9\x87\xda\x1c\xdf\xe7\xc5\x2e\x85\
+\x2e\x94\x7d\xc1\xfa\x50\x52\x26\x20\xc0\x0a\x5d\x4c\x62\x67\x29\
+\x49\xc1\x51\x67\x4a\xc5\x42\xcd\xe3\x7a\x3f\x74\x52\x5c\x85\x8b\
+\x54\x01\x38\xab\x1f\xc7\x2f\x31\x88\xb4\xbc\x93\xe9\x4c\xe0\x72\
+\xa0\x4e\x2f\xac\x43\x74\x8d\x84\x8b\x66\x42\x24\x6d\x41\xe0\xc8\
+\x9a\x7d\xd2\xb4\x2c\x6b\x7b\x84\x9f\x1d\x9c\xd3\x6e\x5d\xac\xac\
+\x1e\xe2\x0a\x7a\xe6\x27\x6d\x45\x10\x86\x84\xa3\x61\x99\x39\x0d\
+\x53\xd3\x13\x68\xf8\x74\x5b\x5a\xa9\xdb\xa7\x38\x96\x45\xf0\xc7\
+\x3f\xcb\x27\xd1\x35\x4b\x81\x21\x61\xe6\xb1\x30\x4a\xb5\xff\x19\
+\xc4\xcb\x77\x3c\xc8\xf4\x9a\x0b\x4d\xba\xaf\x60\xe8\x28\x41\x12\
+\x0f\x66\x62\xb1\x93\x43\x38\x0e\x9c\xe5\x70\x8b\x9c\xd0\xc4\xe3\
+\x4e\x7a\x90\x86\xb6\xf5\x40\x3b\x86\x9a\x41\x0b\xf2\x36\x90\xb9\
+\x19\xd3\x0c\x0b\xca\x49\x79\x02\x47\x5c\x10\x01\xef\x1b\x79\xaf\
+\x11\x82\xbc\x68\xd2\x25\x4e\x1a\x2c\x22\x67\x8a\x03\x30\x51\x8c\
+\x52\x04\x37\xf7\x17\x53\x67\x61\x51\x49\xab\xc1\x02\x53\x5b\xd4\
+\x6a\x88\xeb\x24\x3b\x99\x5d\x72\x18\x1e\xd4\xe4\x13\xe6\xd3\xa8\
+\xf5\x98\x87\x29\x09\x5f\xe0\x6c\x41\xd8\x33\x26\x63\x62\xb3\xa1\
+\xe4\x9c\x0a\x07\xcd\x14\x12\x2e\xc4\x32\x5a\xd2\x5d\x60\xb3\x42\
+\x1c\x02\xf8\x4f\xab\x29\xda\x11\x9e\xf0\x82\xd7\x19\x29\xa6\x82\
+\x30\xb8\xc8\xbf\xa1\x67\x74\x71\xb7\xf4\xc2\xc5\xd2\x4e\x9a\x3e\
+\x20\xa2\x68\xb8\x8a\x17\xcc\x1e\x97\x76\xfb\xc4\x3f\x23\x17\xc1\
+\x0c\x9f\x30\xfd\x4c\x23\x3d\xff\x17\xf0\xd5\x0d\xbf\x0c\xd5\xed\
+\xdb\x81\xc5\x65\xcc\xeb\xb0\x22\xc1\x71\x6e\x85\x1a\x19\xd3\x05\
+\x93\x6e\x48\xa7\x37\x8b\x01\x3f\x45\xe6\x1b\xfe\x71\x12\x03\x3b\
+\xb4\x35\x37\x30\x06\xc1\xd6\xc5\xe9\xa9\x99\xd7\xe5\x5d\x35\xcc\
+\x31\xe4\xe4\xfa\x3e\x6e\xa1\x63\x30\x4c\xb7\x17\xf2\x4c\xa8\x00\
+\x6b\x82\x3c\x0e\x6b\x20\x2a\x4d\x86\x18\x4f\x9a\x1f\x8d\x19\xd3\
+\xa2\x85\xad\x11\x53\xb3\x94\xab\x00\x22\x3b\x3c\xb0\x68\x99\x78\
+\xf5\xe3\xfd\x0a\x54\xe1\xea\x6c\xee\xaa\xb3\x0a\xa9\xc9\x19\x18\
+\x34\xc3\x18\x83\xb0\x50\x99\x2d\xe9\xf4\xae\x0a\x84\xe4\x29\x5b\
+\xe0\x40\x0a\x06\x60\xb1\xf6\xd6\x1a\xfc\x58\xad\x47\x3a\x66\x30\
+\x5c\xa9\xfd\xd9\x1a\x34\x7f\x71\x2b\x04\xb7\xb4\x75\x68\xdd\x12\
+\x59\xfe\x3c\x5b\x5d\x13\x86\x24\x49\xfa\x10\xaf\xf8\x8f\x79\x2a\
+\x0c\x85\x6b\x81\xb6\x61\xf7\x37\x8e\xce\xf2\x16\x1c\x44\x0f\xf8\
+\xcc\x41\xf1\x36\x59\x17\x5a\x46\x53\x71\x38\x8f\x44\xc5\x6e\x56\
+\x9e\x15\x3a\x98\x0f\xc6\x76\xac\xa6\x6b\xf5\x7e\x9c\xe3\xb7\x33\
+\x68\x41\x24\x1c\x88\x33\x82\xc9\x51\x48\x30\x94\x80\x78\x49\x64\
+\x2d\x62\x08\x5c\x0a\x14\x6e\x12\xad\x66\x49\xd0\xb8\xaa\x9b\x27\
+\x77\xd2\x68\x2c\x14\x89\x0c\xea\x7c\x49\x87\x8f\x2c\x72\x34\x1d\
+\x7b\x10\x01\x8e\xc8\x74\x49\x1a\x50\xac\xd6\x92\x14\xab\x93\x50\
+\x51\xb8\xd1\x50\x6f\x0c\x80\x35\xe1\xc8\x60\xce\x2d\x59\xd4\xc0\
+\x3a\x08\x37\x1a\x6a\xc1\xb3\x1c\x79\x5e\x84\x22\xa3\x63\x10\xb4\
+\x20\x30\xb3\xdb\xa5\xdd\x2c\xd2\x52\xc6\x91\x3c\x26\x2d\x86\x24\
+\xfe\x24\x4a\xa1\x15\xa1\x03\x53\x23\xe3\x0e\x31\x7b\x44\x68\xe2\
+\x47\xbd\x11\x98\x2a\xd2\x21\x31\x2a\xcb\x0b\x83\x05\x3b\xe3\xa3\
+\x78\x14\x10\xc1\xf3\x6e\x26\x30\x01\x46\xf8\x34\x3f\x09\x96\x41\
+\x47\xf8\x9c\x8c\xe8\xc5\xc2\xde\x53\xf7\x34\x70\x7f\x2f\x16\x0f\
+\x39\xea\x25\x0e\x13\x20\xb8\x56\x79\x44\xba\x38\x99\x00\x29\x22\
+\xf3\x90\x8a\x9d\x17\x1c\x5b\x12\x33\x48\x97\x77\x1d\xda\x85\x92\
+\x90\x7d\x8c\x02\x9b\xea\x67\xe6\x02\xd2\x90\xc7\xbd\xb1\x3c\xc8\
+\x7d\xf7\x59\xb2\x1a\x95\x14\xc6\x04\xb1\xe9\x27\x5c\xdc\x9a\x3d\
+\xa4\xa8\x27\x91\x49\x99\x80\x11\xca\x0e\xd2\x05\x32\x59\x58\x98\
+\x32\x9f\x0f\xc2\xe4\x92\x12\x02\x24\xc5\x62\x93\x1f\x58\x99\x1f\
+\xdc\x4a\xb1\x7b\x6f\xe2\x5e\xfe\xda\xc4\x75\x74\xd5\xa5\xe9\x57\
+\x21\x50\x7c\x1d\x98\x43\x48\xe0\x89\xad\x0a\xc9\x88\x2e\x35\xa1\
+\x67\x50\x61\x24\x1f\xa8\x84\x63\xe1\x79\x9d\x99\x2e\xce\x0c\x14\
+\x84\xb4\xa1\xb7\x68\x88\x1e\x7e\x24\x58\xd0\x35\x40\x6c\x67\x38\
+\x84\x7b\x3d\x08\x85\x80\xe0\x22\xf1\x50\x1c\x13\x86\x00\x33\xb7\
+\x58\x07\x35\xc3\x6b\xad\x93\x30\xc9\xea\x4e\x83\x36\xac\x13\x0d\
+\x62\x83\xe6\x20\x2e\xa4\x9e\x7f\x0a\x23\x22\x41\x4a\x9c\x24\x5e\
+\x0a\x0a\x76\xc2\xe4\xc3\x0f\x48\xa8\x92\x7c\xf3\x76\xe6\x09\x3e\
+\x54\x1d\xc5\xd1\xf1\x59\x14\x90\xe3\xaa\x99\x0b\x48\x01\x6b\xf6\
+\x89\x5e\x07\x68\xb5\x27\x23\x63\x11\x22\x8c\x59\xa6\x6e\x74\x90\
+\x92\x42\x44\xa0\x9b\x80\x30\xf2\x04\x5b\x22\x8e\x1b\xcd\x17\x06\
+\x81\x05\xa3\xaa\xb9\x15\x01\x14\x4c\x26\x29\x33\x14\x4b\x00\xd2\
+\xe1\xcb\xc0\x27\xa7\x29\x61\xb4\xd2\x3b\xfc\xb1\xdb\xa0\xb7\x76\
+\x60\x2b\xc2\xa9\xb8\xe7\x02\x53\x99\x72\xab\x09\xdc\x45\x48\x8c\
+\x82\x36\x61\x12\x02\x33\x4a\xba\x16\xe2\x86\x2e\x90\x74\x3d\x88\
+\x2f\x9e\xa4\xd5\xb1\x10\x25\x0c\xc6\x47\x70\xed\x57\xf1\xf0\x0b\
+\xe5\x23\xa3\x16\xfc\x6a\x62\x42\x92\xc4\xa0\x0c\xcf\x00\xbc\x15\
+\x3a\x4c\xc1\x0e\xdb\x24\x33\x61\x44\xf1\x78\x74\x8f\x3f\xa1\x60\
+\xfe\xa0\x37\xff\xc5\xb8\xa4\x01\xc5\x7d\x59\x7c\x0d\x8e\x71\x1c\
+\x38\xc4\x74\x6b\x7a\xe8\x6c\x93\x19\xa6\x03\x85\x68\x3e\x0e\x62\
+\x14\x10\x7d\x5b\x8a\x3d\x59\x82\xa0\x92\xfc\xdc\xde\x5a\x6e\xb7\
+\x5a\x8e\xf9\x3b\x73\x01\x51\x72\x92\x0a\x26\xd4\x94\x08\x06\x63\
+\x61\x31\x82\x03\x5d\xe3\x8f\xcf\xac\x89\xf2\x85\xac\x96\x93\xf1\
+\x97\x85\x69\xf1\x52\x61\x21\x20\x80\x98\x48\x70\xa3\x24\x15\xc0\
+\x0c\x3c\xb2\x09\x82\x77\xa4\x26\x9f\xd8\x71\xca\x6d\x19\xee\x02\
+\xc6\x4c\x0d\xdf\xe8\x93\xa2\x07\x31\xec\x86\xd0\x2e\x16\x70\xd9\
+\x6a\x68\x17\x0b\xfe\x00\xcf\xf9\x41\x3a\xbb\x1e\x9c\x3c\x94\x3e\
+\xba\xe5\xb5\xe4\x3b\x2d\x24\xee\x05\xc3\xc3\x24\x38\x40\xa9\xd4\
+\x45\xcd\xfc\x8d\xdd\xd4\x12\xa5\xd1\xd2\xdb\xa4\x07\x21\xa0\x7f\
+\x86\x41\x77\x8f\x93\xb1\x07\xb8\x77\x37\x7b\x2b\x1d\x81\x87\x3f\
+\xef\xce\x74\x6a\x9a\x71\x7f\x33\x5e\x8e\x92\x9b\x52\x91\xc4\x30\
+\xe3\xe7\xe3\xcf\x34\x08\xef\x41\x37\xe6\x0b\x82\x23\x30\x05\x37\
+\xa5\x15\xa3\x7f\x73\x3c\xb6\x3e\x73\x01\xc9\x61\xc2\x4c\xc7\x96\
+\xc8\xf0\xc9\x0a\xe9\x93\xbc\xd6\x88\xc3\x1d\x48\x62\xc6\x4f\xae\
+\xec\x5b\x35\x4e\x4a\x4f\xab\x81\x9b\x4c\xff\xf9\x86\x48\x21\x5b\
+\x20\x93\x5a\x0d\x3a\x20\x41\x14\x0e\x24\x81\xe9\x4a\x15\x73\xc3\
+\x43\x2c\x63\x22\x2b\x5b\x01\x76\xaf\xc6\x79\xc2\x0d\x78\xf3\x16\
+\x43\x04\xd0\xa2\xf2\x10\x14\x8f\xf9\x32\x5c\xd6\x1b\xd6\xc5\x62\
+\x23\xc2\xd6\x97\xf9\xc9\x31\x88\x4e\xf3\x62\x60\x0e\x24\x99\xd1\
+\xa1\x43\x5c\x02\xb0\x02\x0d\xf1\x82\x81\x02\x44\x57\xc3\xf1\xf1\
+\x21\xac\xa5\x12\xbc\x10\x7b\x18\xc8\xae\x50\xc7\xd2\x63\x37\x31\
+\x53\x10\xbc\x1f\xda\xed\x03\xa5\x60\xf6\x30\x41\x24\x72\xd3\x27\
+\x78\xc4\xf7\x7e\x34\x46\x89\x3b\x0a\x01\xa9\x62\x13\x63\x01\xd3\
+\xb4\xc2\xfc\x48\x8f\x09\x41\x13\x09\x25\xc9\x68\x30\xfe\xc8\x1f\
+\x6d\x7d\x69\x82\x52\xa0\x80\x0f\xcf\xed\x9a\x56\x35\x3d\x73\x01\
+\xc9\xe3\xba\x08\xbe\xe9\x0c\xc5\xc0\x19\x30\x0b\x53\x22\x4a\x60\
+\x93\x22\x5c\x23\x87\x9a\x8f\x2f\x9c\x98\x52\x0e\x30\xdb\x49\xa5\
+\xb3\xa0\xb2\xd3\xbd\x16\x3d\xb6\x26\x48\x90\xfe\x1b\x10\x3a\x53\
+\x68\x09\x55\x23\x1d\x25\xdd\xde\xca\x7c\xe0\xa3\xb9\x7b\xb6\xe5\
+\xdc\x99\xe7\xe2\xee\xa6\xb9\xb8\xda\x06\x45\x24\x53\xb8\x70\x64\
+\x8b\xa1\x6c\x4d\x7f\xfc\xa3\x62\x40\x3a\x48\x17\x89\x84\x9d\x02\
+\xc2\x95\xe5\x90\xf7\xf0\xab\x17\x43\x00\xd7\xfb\x92\xc2\x30\x21\
+\xc9\x44\x16\x54\xe3\x48\xd1\xcd\xab\x60\x22\xbd\x00\xf4\x66\x83\
+\xc5\x7a\xb3\xb9\x95\x5d\x60\xa0\x41\xdd\x7f\x12\x1d\x04\x00\x6b\
+\xf4\xa9\x7b\x04\x10\x47\xb6\x22\x30\x48\x0b\x32\x81\xb3\x22\x1d\
+\xd8\x3e\x2e\x10\xfa\x15\x17\xea\x9a\x6b\x19\x9d\xee\xac\x2d\xe0\
+\x4a\x38\x95\xf1\xa8\x98\x31\x91\xd8\x5e\x3b\x30\x2d\x01\xf1\x7d\
+\x1e\xa1\x31\xbd\x9f\x89\x84\xd7\x2d\x63\x7f\x24\x6a\x3f\xf4\x13\
+\x63\x25\x52\x0d\x40\x26\xb2\x62\x67\x84\x51\xb0\x6d\x05\x57\x3c\
+\xc5\x3f\xf0\x32\xf3\x90\xe3\xa0\x9e\x77\xb3\x15\xa8\xea\x16\x5c\
+\x5a\xd0\x06\xd7\x82\x66\x41\xa4\x6e\x56\xd0\x59\x1a\xa8\x20\xb0\
+\xd8\xc7\x33\x1d\x67\xad\xc6\x1e\x21\x0c\x40\x39\xfe\x20\x0f\x50\
+\x48\xa8\x33\xdf\x82\x99\x70\xb1\xab\x30\xd0\x2f\xdf\xd3\xd0\x63\
+\x05\x3a\x05\x1b\xd3\x97\xe9\x59\x48\x4f\x98\xa6\x85\x99\xee\xb4\
+\x07\x18\xfa\x65\xbc\xb5\x45\x3e\x9a\x9b\xdd\xbd\x1f\xa1\x6b\xb4\
+\x0c\x16\xe9\x31\xbd\xd4\xcc\xf4\x47\xe1\x83\x76\x1c\x3f\x9a\x11\
+\xfd\x49\x30\xc5\x51\xfe\x10\x94\xc8\x9f\xad\x9b\x1d\xdd\x87\x7c\
+\xd3\x3d\xae\x48\x0b\x71\xd9\x92\xc4\x7a\xec\xdf\xca\x0a\x30\x18\
+\x99\x9f\x54\x1c\xfe\xd6\x38\x95\x85\x7f\x98\xc7\xaf\x9d\x73\xad\
+\x1e\x6d\xe5\xda\xde\x31\xd4\xcc\xd9\xb4\x88\x23\x81\x78\xf7\x46\
+\x69\x32\x9a\x2a\x9d\x80\x79\x50\x1a\x59\x0f\x21\x86\xd6\x7c\xe8\
+\x62\x95\x16\xe8\x1a\x0d\xce\x91\xa4\xce\x27\x67\xce\x45\xf9\x00\
+\x00\x40\x00\x49\x44\x41\x54\xa1\x29\x2d\x5c\x46\x6e\x72\x61\x67\
+\x0a\xd3\xbb\xfb\x52\x6b\xc9\x04\xec\x4f\xf3\xac\x03\x69\xad\x38\
+\xab\xe1\x4a\xa8\x27\x78\xb3\x3b\x21\x56\xe8\x2a\x2c\xcc\x3f\x13\
+\x1c\x2d\x60\x16\x72\x01\x07\xa6\xba\x67\xb1\x2f\x8e\xb8\xf0\x0f\
+\xc8\x64\x74\x61\x50\x63\x5e\x32\x97\xc1\xbc\x59\x85\x21\x15\x14\
+\xe2\xc7\x9f\xa4\x93\xfe\x3d\x8d\x49\xf4\x3c\x3e\xf1\xc4\x8d\x74\
+\xbd\x70\x49\x1c\xc4\x4e\xd8\x73\xf5\x79\xfe\x41\x1a\x39\x50\xe7\
+\x2b\x27\x07\xb7\xe2\x98\x04\x4f\x53\x82\x8f\x10\x8c\x74\x45\x59\
+\xfd\x6b\x6e\x52\x6f\x6a\x45\xe0\x20\x6e\xc8\x37\xbc\x7c\x06\x4c\
+\x74\x6f\xa1\x91\x1e\x15\x98\xbe\xb2\x7e\xfd\x7a\x6d\x41\x56\x09\
+\xaa\x3a\xb4\xf8\x8d\xfa\x3c\x2d\x5c\x33\x20\x2f\x69\x43\xc5\x71\
+\xac\x43\x8e\x50\x20\x78\x8b\x09\x15\x9b\x2f\x46\x83\x91\x67\x7c\
+\x68\xa6\x12\x3b\x75\x00\x98\x81\x58\xd6\x74\x6d\xf3\x3a\xb1\x51\
+\x0f\xb7\x20\xce\x5c\x34\x85\xe6\x0b\xf9\xc3\x4c\x66\xd6\x33\xea\
+\x59\x25\xa9\x95\x84\x4a\x5a\xbd\xfb\x24\x34\xef\x89\xfe\xf1\xca\
+\x82\x1b\xc2\x65\x33\xb8\xca\x0d\xd7\x6b\x02\x00\x8f\xec\x62\x61\
+\x47\xb4\x30\x1e\x51\xd9\xb1\x62\x27\x82\x25\xa7\xdd\x2c\xe0\xe1\
+\x9f\x33\x38\x1c\xd0\x77\xe1\x5a\x9c\x1a\xda\x6e\x34\x25\x9a\x9f\
+\x24\x22\xe5\x0b\x1d\x81\xb4\xba\x83\x4b\xe3\x07\x22\x54\x62\xf1\
+\x7e\x3c\x48\x1d\x94\x8a\x98\x7d\x62\xc5\x39\x98\x61\x93\x7f\xaf\
+\x33\x2c\x22\x8b\xee\xf3\x87\x66\x6f\x17\xdc\xc8\x6e\xc2\x93\x81\
+\x9b\xfb\x14\x3a\x71\xd9\x82\x94\xdb\x71\xaf\x2e\xa6\x7a\xab\x58\
+\x5c\x05\x2a\xc0\x2a\x24\xd9\x4a\x44\xa2\xa2\xee\xf4\x17\xe1\xd8\
+\xdd\x5a\x5c\xb3\xab\x54\xc1\x89\x70\xc7\x24\xc7\xb0\xa5\xfb\x78\
+\xfa\xf4\xd9\xd4\x24\xed\xe6\x87\x26\x50\x8a\xf2\x6e\x01\xfa\x88\
+\x8c\x33\x06\x9b\xf8\x20\x25\x2a\xd5\x4d\xd2\xac\xa5\x22\x91\xa7\
+\x24\x95\x71\x7b\x9f\x28\xdb\x76\x72\xbc\x18\xbe\x98\xee\x2d\x0b\
+\x8f\x85\x41\x06\x61\x69\x79\xa6\x98\x86\xce\x2e\xd2\x51\x9c\x01\
+\x79\xc5\x6f\xe2\x99\x6c\x3c\xee\x54\x9d\xa2\xf5\x88\xbb\x55\xc6\
+\x04\xac\x05\x8b\x38\x51\xd8\x8d\xcd\x8d\xe3\x78\xe4\x94\x61\x5b\
+\x2b\xc0\x5a\x3c\xae\xd9\x83\x39\x6e\x11\x2c\x7e\xbe\xd5\x89\x6b\
+\xfe\x40\x87\x38\xb1\x9f\x8c\xd9\x87\xd1\x14\x96\xe4\x03\x69\x5a\
+\x8b\x62\xe1\xc4\xba\xb8\x5b\x3e\x69\xb7\xce\x04\x66\x4a\x5d\x52\
+\x88\x30\xc1\x23\x05\xbf\x67\x83\x07\xcc\xc8\x6b\x20\x9d\x7e\xb4\
+\x53\x18\xa0\x5b\xbe\x05\x1c\x81\x21\x9f\x7d\x8d\xcd\x16\x84\x03\
+\x7d\x2a\xec\x38\xd1\x67\xcd\x68\x59\x2f\xde\x69\x6a\xa9\xa6\x2f\
+\x20\xb1\x77\x6c\xb2\x24\xb3\xcb\x18\x04\x31\xa6\x70\xe0\x5f\x22\
+\x4e\x34\x4b\x84\x79\x91\x68\xe1\x87\xf0\x52\x7f\x1b\x66\x61\x90\
+\x28\x5c\xe8\x85\x9d\x64\x86\x72\xd2\xea\xd9\x42\x44\x1a\x24\x3f\
+\x35\x31\x59\x37\x63\x82\xc9\x3a\x07\xd8\xc4\xa5\xef\x73\xd6\x62\
+\xb0\x8e\x16\x80\xd3\x96\x2c\x5c\xf0\x8f\xff\x9a\xcd\xde\x0e\x5f\
+\x9c\x0a\x2e\xe2\x4a\x1c\xee\xfe\x1d\xc5\xbb\x17\x60\x1d\xa1\x17\
+\x18\x13\xcc\x2c\x82\xe1\x99\x35\x2b\x24\xea\x66\x82\xd0\xac\x4b\
+\x1a\x28\x0c\xb1\x40\x44\xe3\x13\x83\x87\xb0\x4c\x18\x10\x56\x08\
+\x07\x69\x8b\xf3\xc2\x36\x3b\xc6\xb0\xe3\x9a\x91\xaa\xf0\x17\xd1\
+\x93\x6d\x05\x70\xe1\x19\x7e\x19\x7b\x48\x2e\xfa\x4a\x98\x0d\x82\
+\x16\x85\xea\x70\x63\x77\x54\x72\x5a\xf2\x15\xcf\x6c\x20\xef\xb8\
+\xba\x80\x16\x24\xc1\x55\xc3\x32\xfb\x81\x55\x6e\xbd\xc8\x79\xbd\
+\xf4\x63\x88\x3e\xa5\x9a\xbe\x80\xfc\x57\x09\x57\x08\x41\x18\xd0\
+\x59\x60\x21\xe3\xfa\x38\x90\x67\x0b\xc2\x41\x90\x45\xd6\x42\x33\
+\xbb\xe8\x94\x74\x64\x6a\xa1\xab\xec\xda\xd7\xea\x6e\xe3\x1c\x8f\
+\x40\xbe\x04\x94\x16\x2e\x23\x8a\xc4\x22\x31\xc7\x2d\xec\x26\x1c\
+\xee\x4c\x3d\xbc\x8b\x57\xf1\x27\xee\x94\xb3\xb4\x10\x4d\x30\xa4\
+\x95\xf0\x82\xa2\x2d\x46\x56\x50\x08\xe3\x60\xbe\x80\x13\x85\xbd\
+\xb3\xb1\xed\xc2\xe2\x40\x61\xc8\x30\xa9\x17\x1a\xc2\xc1\xc4\x81\
+\x79\x05\xcf\x04\x80\x38\xfe\x33\xa1\xf0\x82\xc1\x34\x89\x1f\x81\
+\x1b\xf3\x2b\x4d\x85\x9b\xbb\xb9\x69\x38\x92\x17\x12\x0f\xc5\x8d\
+\x5b\xa7\x69\xe7\x13\xd2\x14\xe3\xc2\x12\xec\x36\x7b\x87\x68\x23\
+\x7e\x29\x8f\x01\x85\x68\xfe\x33\x41\x89\x74\x94\x56\x1d\x9b\x40\
+\xc7\x21\x20\xb3\x29\x20\x60\xb5\x4a\x05\x16\x28\x98\x85\x7f\x9f\
+\xdb\x4b\x1b\x48\x5a\x25\x0e\x86\x9c\x2c\xb3\xe0\xa2\x38\xb9\x30\
+\x8e\xc2\xc1\x3e\x1e\x13\x40\x35\x49\xd2\x29\x1c\x80\x23\xef\xb1\
+\xe7\x38\xef\x3a\x4f\xd5\x87\x6f\x04\x28\x3e\x5e\x0a\x3f\x69\xa1\
+\xc5\x85\x79\x3c\x33\x2f\x67\x60\x3f\xba\x82\xd2\x7c\xe5\x5b\x72\
+\xae\x6f\x5e\x5e\xce\xa3\x6b\x5f\x3a\x15\x06\x15\x0e\xcd\x43\x9d\
+\xc9\x82\x9b\x67\x0a\xb6\x20\x7c\x15\x97\x53\xc3\xc8\x5d\x65\x9e\
+\x49\xad\x08\x19\x37\x62\x52\x78\x26\x63\x07\x41\x81\x59\x98\x57\
+\x04\x26\x9b\x16\xc5\x83\x5f\x0a\x47\xf0\x43\xbb\xe1\x79\xba\x46\
+\xdf\xf4\xe0\x6e\x78\xa9\x1e\xe2\x62\xe1\x52\x3f\xc6\x07\x47\xe3\
+\xf6\x2c\x9e\x30\x15\xfc\x4a\xca\x99\x7a\xcd\x33\xc9\x3f\x81\x29\
+\x6f\xa5\xee\xc4\x51\xe4\x3a\x1e\x1e\x1a\x43\xe5\x32\x0b\x23\x6d\
+\xcc\x91\x24\xc3\x63\x90\x18\xb8\xe2\x16\x66\x5d\x26\x9c\xc6\xa5\
+\x0d\x33\x18\xa4\x83\xf2\x43\xbc\x38\x0e\x83\xf5\x9c\xdb\xc3\x04\
+\xa1\x4f\x87\x9b\x1c\x71\x27\x07\x46\x3d\xb8\x18\x5d\x22\xaf\x49\
+\x81\x99\xe9\x65\x44\xa9\x44\x6e\x09\xa3\x2b\x06\xea\x0b\xbb\x78\
+\x1b\x0a\x62\x8c\x02\xc7\x05\x72\x78\x68\x44\xf1\x4e\xe2\x5f\xc6\
+\xfd\x44\x14\x19\xbb\xe2\x2f\x92\x3c\xe7\x62\xde\xa1\xcb\x96\x17\
+\x83\x71\xae\x8c\xe3\x3f\x0c\xc6\x41\xdc\xcc\xcc\x2e\xcd\x32\x76\
+\x5d\x31\x30\xe7\x6c\x0e\xba\x58\xfd\xf3\x54\x40\xc8\x33\x21\x3a\
+\x34\x03\x47\xfa\x0e\xb1\x99\x91\x25\x12\x08\xe1\x17\x4a\x29\x8a\
+\x31\xf3\x43\x4f\x19\x00\xac\x29\xcc\xcc\x82\xe2\x03\xb5\xbc\xd0\
+\xf2\x54\xdc\x63\x9a\xe1\x4f\xe9\xe0\x97\x66\x7a\x11\x5d\xe1\x34\
+\x13\x18\x60\xde\x4c\xd6\xa7\xf0\x4a\x04\xd1\x6d\xd1\x4a\x84\x7e\
+\xe2\x8a\x45\xbc\x8a\x9b\x27\x43\x52\xa2\x6a\x90\x87\x11\x54\x2e\
+\x73\xb8\xb5\x16\x99\x36\xc4\x45\x5a\x38\xe2\x10\xd5\x1e\xc5\x38\
+\xfe\x2f\x18\x7e\x06\xca\x5f\x1c\x97\xab\xbb\xdd\x0c\x10\xd7\xc2\
+\xe6\x71\x7e\x38\x61\x17\x8b\xb3\x31\x16\xc1\x98\xa2\x24\x4f\xd3\
+\x0f\x30\x12\x86\xbe\x64\xb1\xb7\xcd\x75\x5e\xa1\x57\x13\xf1\x01\
+\xcf\x97\x8a\x6a\x2e\xc0\x63\xda\x51\x8c\xdc\xd6\xde\x81\x77\x2f\
+\x0e\x63\x25\xf8\xca\x77\xe5\xdc\xfc\xa5\x78\xeb\x1c\x33\x7f\x9a\
+\x4f\x71\x21\xa7\xe6\xd0\x7a\x20\xcf\x28\x08\xb4\xb3\x05\xe1\x20\
+\xaf\x7f\x3e\x05\x84\x2f\x78\x99\xa0\xf8\x9a\x1d\x88\xa1\xa5\x10\
+\xb3\xd5\xf8\x60\x26\xd8\xf5\x23\xac\xe9\x63\x8b\x41\x77\x44\x88\
+\x5f\xda\x92\x98\x3d\x76\xa7\x5f\xe2\x64\xf5\xd8\xbf\xd1\x49\x75\
+\xc5\x4d\xed\x69\x58\xd3\x81\xb1\x7b\x55\xf7\x95\x27\xc7\xad\x88\
+\x2a\xe2\x10\x7f\x69\xbe\x05\xb8\x17\x1e\xda\x6b\x78\x1d\x78\x00\
+\x79\xd7\x83\x4a\x0a\x53\xbc\xc9\x21\xd2\xc2\x23\xbf\x85\xa4\xb0\
+\x77\xba\x3c\x37\x93\x16\x24\xd0\xac\xe7\x72\xcf\xa0\xfd\x1f\xc3\
+\x14\x5a\x07\xb6\x4a\x24\xb8\x5c\x51\x8e\x8e\x12\x41\x22\x4a\x1d\
+\x31\x61\xcd\xa6\x4d\x61\xa4\x23\x95\x39\xec\x77\xec\x3e\xa3\xdf\
+\x8d\xdc\x86\x17\x6a\xd9\x8a\x70\x4d\x44\x0e\xea\x87\x20\x4e\x5a\
+\x03\x0b\x76\x3a\x8a\x8f\x09\x95\x31\x18\x1f\xf2\xef\xfa\x5c\xf8\
+\x0a\xbc\xe2\x8a\xd6\x92\xd3\xb4\x71\xeb\x91\x9d\xd6\x65\xf9\xb1\
+\x8d\xa5\x42\xe1\xcb\x2f\x99\x4a\xa7\x84\xd9\xc5\xe2\x0d\x9a\x7c\
+\x3e\x1a\xcf\x79\x80\xb9\xf1\x85\xd6\x03\x78\x28\x07\xa9\x6e\x80\
+\x4f\xb8\x52\x89\x2a\xa0\x63\x44\x9d\x25\x24\x2a\x68\x30\xb4\x32\
+\xfb\xf4\x5b\x3e\x08\xa3\x13\x91\xff\xe2\xa6\xf1\x6d\x86\x93\xbe\
+\xb8\x03\x47\xdc\x3c\xfe\x64\xb8\xb9\x03\x01\x19\x31\x81\x29\x5e\
+\xaa\xa2\x6c\x33\xd1\x0a\x03\x75\x06\xa8\x31\xa7\x34\x8a\x34\x33\
+\xaf\xc2\xf4\xae\xe4\x02\xee\x60\x83\x80\xe0\xc9\x74\xbc\x5b\x88\
+\xfc\x43\x65\xb5\x9b\x8b\x8d\x05\x77\xb8\xb3\x13\x3d\x20\xaa\xe7\
+\xf4\xd2\x06\x12\xdc\xc0\x1f\xa8\x12\x02\xc0\x40\x87\x4d\x1e\x6a\
+\x49\x49\x01\x9e\xd3\x90\x99\x2c\x89\x24\x50\x24\xf2\xf8\xa1\x6b\
+\xf8\x04\x8e\xc4\xa0\x15\x69\xc3\x82\x61\x18\xac\x43\xc2\x5f\x4a\
+\x4a\x0b\x38\x2d\xc8\xa9\xec\xe5\x2e\x5c\x5a\x36\x54\x77\x6f\x7c\
+\x6f\xd1\x2d\x5c\x51\x94\x69\x46\xc9\x17\x16\x28\x0c\x52\xa8\xc8\
+\x43\x6d\x35\x58\xf8\xca\x00\xcd\x76\xc9\x67\xf4\xa5\xbb\xb1\x0e\
+\xb2\xea\xd5\x0d\xb7\x67\x0f\x58\xa2\x10\x0d\xc4\x03\xd3\x11\xe6\
+\x6b\x6d\x10\x63\xed\x4e\xbb\x7e\x93\xe3\x6b\x2d\x86\xf8\xf1\xf8\
+\x4c\x4b\xb0\xb7\x32\x1b\x2c\xd2\x35\xfd\xd6\xb2\x34\x87\x13\xc5\
+\x07\x7e\x5a\xe5\x15\x19\x64\x12\x1c\xf1\xe1\xe2\x2a\x7c\xbb\xfe\
+\x45\x10\x10\x54\x2e\xcc\x17\xc9\x3f\xc9\x27\xcd\x2f\xe6\x0d\xbc\
+\x87\x0f\xde\x82\x79\xbc\xce\x0d\x7f\x39\xdc\xf4\x8e\xd9\xac\x0a\
+\x5c\xb8\x76\x9e\x77\x7b\x3b\x47\xfd\x20\x5d\x2f\x6d\x38\x26\xeb\
+\xcd\xac\x8b\xe5\x2f\x8e\x73\x8b\x7b\x0e\xa0\xb2\xda\x45\xca\x90\
+\x4c\xc6\x59\xba\x58\x9c\x29\x50\x1b\x74\xc0\x9a\x3f\x54\x6b\x44\
+\x15\x01\xc9\x61\xdb\xc9\xac\x0b\xb5\x9b\xc5\xda\x56\x84\x5e\x5c\
+\x5f\x1a\x3f\x93\x0a\x34\x2a\x64\xee\x21\xa2\x70\x1c\xc0\xcc\x15\
+\x4e\x97\xb9\x0b\x5f\xd5\xc6\x1b\x60\xd2\x95\x73\x48\x86\x14\xb6\
+\x08\x87\x16\xb4\x09\x05\xf3\x8f\x85\x6c\x76\x31\x23\x4b\xb8\xea\
+\xde\x8e\x16\xe9\xf4\xf3\x29\x56\x98\x0d\xc4\xba\x0a\x5f\xf4\x32\
+\x21\x90\xf8\x00\x39\x63\x67\x9c\x3c\x8c\x03\xf0\x54\x58\xbc\x20\
+\x59\x17\x8b\x38\x3e\xfe\xb1\x70\x18\x8c\x7a\x06\x6e\xf8\xa2\x5b\
+\x1c\x52\x1a\xe9\x94\xb0\xc1\xbc\x90\xf8\x30\x62\xba\xc7\x32\xe3\
+\x51\x3f\xa4\xb5\xe1\x66\x2f\xcd\x43\x40\xf4\x29\x39\x90\x40\x5c\
+\x4d\x28\xb4\xa2\x11\x18\xf2\x99\xd8\xd6\xb2\x70\x06\x6b\x14\x02\
+\xc2\x09\xa4\x0e\x2c\xc7\x8f\xe0\x54\x22\x6f\x77\xef\xc9\xbb\xa7\
+\x36\x7e\x79\x23\x45\x65\x5a\x0f\xe8\xcc\x4c\x40\xfc\xdb\xe8\x6e\
+\xfd\x06\x14\x57\x6e\x2b\xc3\x18\x1f\x47\x63\x0e\xc5\x9d\xaa\x45\
+\xeb\x27\x32\xb2\x51\x42\x44\x30\x68\x97\x04\xe0\x07\xee\xbc\xfb\
+\xa8\x7d\x71\xb7\xeb\xbc\x1c\xd7\x57\x60\x01\x27\xc7\xcb\x85\xa1\
+\xf2\x68\x8a\x4a\x6d\x72\x10\x40\xec\x27\xf3\xcf\x54\x85\xcb\xc4\
+\x17\xdb\x98\xda\xba\x7b\xeb\xc7\xda\xdd\xdc\x25\x25\xdd\x2a\x81\
+\xc2\x52\xc6\xd7\x82\x05\x7f\xa5\x82\x00\xec\xd6\x6e\x5a\x73\x72\
+\xd8\xc1\xb1\xde\x29\x67\x4a\xbb\x03\x81\x01\xf3\xf1\x0f\x61\xb5\
+\x12\x0a\x13\x86\x4c\x1c\xc9\xd0\xf1\x47\xbf\xfe\x0b\xf8\xc2\xf4\
+\x84\x9b\x10\xa9\x59\xfc\x01\x57\x04\x25\xa3\xa7\x34\x44\x30\xe0\
+\x3f\x3b\x1e\x8a\xdc\x03\x6d\x4f\x93\x61\x67\x60\x11\x2e\xdc\x74\
+\x8a\xb7\xe1\xfa\x16\x16\x31\xcd\x9d\x97\x75\x37\xc9\x23\xe4\x15\
+\x9d\x99\xc3\xfc\xc2\x22\x21\x61\x02\xc4\xd8\x85\x03\xf4\x7a\xde\
+\x2d\x00\x5f\xc2\xab\x1b\x1c\xe2\x40\x0e\x9b\x10\x8a\xb9\xc7\xa9\
+\xbb\x75\xeb\xd8\xbc\x10\xfb\x98\x6a\x66\x02\x42\x82\xeb\xd6\x21\
+\x48\xa8\x9c\x7b\x84\xd6\xf1\x4a\x03\x2f\x78\x92\xb1\x11\x11\x7c\
+\xa8\x94\x34\x92\x51\xb0\x12\x67\xd8\x25\x51\x94\x7e\x9a\xd1\xcd\
+\x72\xa5\x82\xeb\x5b\x83\x65\x65\xda\x87\x1a\xae\xad\xb7\xdb\xcd\
+\x9a\x3d\xcf\x75\xf6\xf4\x62\x40\x7b\x8a\xeb\x9d\xab\xf7\x7b\xd1\
+\x5d\x4f\xf7\xd1\x74\x72\x29\x63\x30\xd1\x91\x32\xbe\x11\xdf\x35\
+\x17\x03\xf3\x3d\x75\xb7\xe6\x75\x78\x6f\xef\xd2\x0e\x0c\xb2\xc1\
+\x34\x28\x1f\xd6\x7c\x30\x4a\xd7\x2a\x98\x69\x17\x78\xb3\xd0\xc4\
+\x76\xc5\xe1\xb6\x89\xf9\x4b\x9d\xbb\xe8\xd7\x12\xb7\x63\x1b\xf6\
+\x73\xc9\x81\x2b\x5f\x3b\x33\x0c\xe9\x5e\xa5\x76\x13\x9c\x14\x1e\
+\xd7\xe4\xd9\x16\x25\x08\x0e\x0a\x49\x85\x23\x65\xde\xc0\xf0\xa4\
+\x2f\x0c\x3d\x95\x1e\x33\x78\x1c\x96\xd2\x64\x1e\xa5\xb4\x52\xdc\
+\x56\x30\x0a\x07\xbb\xe2\xcc\xad\xfe\x45\x25\xb4\xc0\x10\x10\xf2\
+\x16\x20\xe4\xa3\x54\x28\xac\x32\xb6\x7c\x54\x9c\x2a\xc6\x1f\x83\
+\x10\x90\xb9\xe8\xbe\xf3\x71\xbd\x83\x47\x30\xe7\x0b\x9a\xa5\x42\
+\xee\x21\xe1\xa0\x69\x4c\xf1\x12\x6f\xa6\x02\x12\x2e\xfc\xc5\x45\
+\x66\x0f\xb1\xb4\x2b\x78\x0b\xba\x56\xc5\xbd\x1c\xa0\x54\xe6\x6c\
+\x01\x13\xc0\x2f\x6a\x06\xc5\xce\x56\xc5\xdc\x44\x07\x03\xa0\x5a\
+\x2c\xcf\xed\x74\xfd\x57\x2f\x83\x2b\x66\x7b\x07\x87\xdd\x91\xfd\
+\x7b\xb0\xbd\x79\xbf\xdb\xbf\x6b\x87\x1b\x84\xbe\x68\xc5\xe9\xd2\
+\xa2\x30\x73\xf9\x44\xda\xc9\xa8\x82\x70\xb0\x55\xc4\x85\xd4\x47\
+\xf6\x30\x17\x9c\x7b\xc5\x5b\xba\x5d\x1b\x1e\x71\xc4\x9b\x98\xd2\
+\x88\x8a\x70\x98\x90\xb0\x90\x69\x66\x6b\x2a\x66\xcd\x33\x15\x16\
+\xba\x59\x81\xab\x3b\x99\x82\x74\x3a\x71\x61\xcd\x9a\x75\xb0\x20\
+\x37\xb9\x3f\x2b\x30\x2c\x10\x54\x20\x4c\x50\x3c\x03\x82\x90\xc0\
+\xa1\x93\x11\xad\xa5\x10\xa6\xf4\x6e\x4a\x43\xf1\xd5\x5d\xfd\x28\
+\x43\xa7\xc2\x60\x8c\x6c\xba\xa4\x9b\xe1\xfa\x4f\xe1\x86\x4f\x78\
+\x6c\x4e\xf1\x0c\x5f\xdc\x23\xff\x01\x8e\x78\xf1\xc6\x9c\x89\x51\
+\xa6\x13\x6f\x7b\x2c\x04\x63\xa1\xaf\xc4\x8a\x98\x10\xe6\x11\xbc\
+\xc9\x07\x54\x6f\xf6\xfc\x05\x37\x22\x8d\xd7\x8b\x6e\x10\x15\xca\
+\x6c\x4e\xf1\x56\x93\x64\xc7\x10\x1c\x0a\x8d\xf1\x42\x92\x43\xc5\
+\x0e\x35\x8d\x01\x3a\xd1\x66\xce\x71\x8b\xf5\xba\x46\xec\x67\x79\
+\x04\x31\x3b\x52\x45\xa4\x6b\x78\x94\x94\x35\x7c\x09\x91\xe1\x3c\
+\x8a\x44\x1e\xc4\x99\x18\xf9\x42\x22\x2c\x31\x11\x1c\x38\x7d\xe7\
+\xcc\x77\x78\xd0\x4d\xd4\x27\xbe\xf0\x35\xf7\x57\xdf\xf9\x37\x77\
+\xc3\xc7\xd6\x8b\x7d\xcf\xf6\x27\x5d\x47\x77\x8f\x9b\xb3\x68\x29\
+\xe8\x2a\xe3\x29\xe6\xc9\xf5\x4b\x26\xe3\x6a\xb7\x6e\x8d\xa8\xbb\
+\x77\xfd\xd9\x2c\x37\x6f\x79\x1b\x5e\xcf\x05\x63\x88\x10\xa4\x8c\
+\xae\xad\x47\x2a\x18\x59\xa1\x48\x05\x83\x15\x28\x3f\x13\x24\x39\
+\x9e\x8b\xf2\x3f\xfd\x7c\x3c\x9b\xbc\x00\x87\xe2\x9e\xb0\x56\xc4\
+\x33\x23\x99\xcd\x33\x7d\xda\x6a\x44\x8c\xe9\xdd\x0c\x27\x30\xa4\
+\x6f\x79\xc4\x8f\xe1\x00\x16\xec\xa0\x1b\x04\x2a\x30\x3d\xdd\x35\
+\x5c\xd3\xb3\x82\x96\x86\xab\xee\x8a\x1b\xe3\xa4\xe1\xa7\xb8\x06\
+\xcb\x63\xdd\x67\x02\xfb\xce\x4a\x1d\x79\x37\x6b\x1e\x6e\x7c\x93\
+\x7c\x20\xff\x28\x7f\x21\x9a\x6a\x06\x1b\x48\x25\x03\x9d\x38\xf8\
+\x97\x4a\x7a\x14\x02\x32\x00\xdc\x7e\xec\xd6\xa8\x8e\x27\x8d\xfd\
+\x58\x8b\xca\x17\x73\x4f\x0f\x36\x92\x6d\xc2\x39\xd3\x18\xa0\x13\
+\x6f\xe6\x02\xe2\x36\x08\x97\x8e\xe7\x8a\x4f\xa1\xdc\xb7\xb1\x6a\
+\x94\x19\x02\x8c\x44\x78\x31\x97\xad\xa8\x33\xa2\x16\x61\x32\x88\
+\xd8\x5b\xe8\x75\x48\x58\x67\x17\xee\xa1\x5b\xed\xdc\x87\xfe\xfc\
+\xaf\xdc\x55\xef\xba\xde\xbd\xe2\x37\x7e\xc3\x5d\xf7\x07\x1f\x77\
+\xdf\xb9\xf3\x11\xf7\x3b\x9f\xf8\x73\x37\x78\xe8\x80\x3b\xb4\x67\
+\x97\xb4\x26\x7c\xda\x98\xea\x64\xeb\x72\xb1\x71\xeb\xe8\xc3\x63\
+\x2d\x87\x1b\xee\xf5\xbf\xd3\xeb\x4e\x5f\xd3\x29\xe3\x04\xed\x5a\
+\x91\xc9\x3d\xa3\x23\xbf\xd4\x6c\xf6\xa9\xdc\xcc\x3d\xc2\x47\xba\
+\x27\xf0\x7e\xf8\xec\x85\x39\xf7\xba\x6b\x11\x16\x32\x38\x8f\xab\
+\x4b\xc9\x16\x9c\x18\xc8\x08\x85\xd8\xd3\x16\x43\x6b\x6b\xd4\xe8\
+\x5e\x18\x4c\xa7\x1f\x32\x30\xfd\xa7\xcc\xeb\x99\x9f\x30\xb8\x09\
+\xae\x37\xa7\xcc\x1e\xe1\x07\x3c\x86\x97\xc5\x8f\xfd\x1a\xf3\x67\
+\x68\x04\x81\xb3\xb0\xa0\xe3\xcf\x06\xe8\x67\xbf\x12\x6b\x66\x7d\
+\x45\x3f\xe6\x4a\xf3\x42\x2a\x1d\xa6\xdc\x0b\x0c\x82\x45\xd8\x2a\
+\x3c\x5c\x41\x1f\x81\x80\xb0\x57\x82\x25\x37\x37\x34\x80\x6a\x86\
+\xad\x49\x31\xf7\xd0\x91\x2f\x6f\x1c\x00\xd8\xb9\xf5\xc2\x92\x62\
+\x3c\xd6\xcf\xcc\x05\x64\x3d\x04\x74\x3d\x04\x0b\x33\x01\xe8\x56\
+\x6d\x66\x2c\xc6\xb1\x5c\xc9\xe6\x0f\xcf\x21\xb8\x76\x7c\xb2\xb0\
+\x85\x08\x8b\x60\x50\x3f\xd6\x87\x96\x47\x66\xb1\x30\xa1\xb5\xf4\
+\xa2\x95\x92\xa8\x0a\x9e\x04\xca\x63\x69\xfe\xb4\xb3\xcf\x76\xef\
+\xf9\xbd\x3f\x76\x7f\x77\xcb\x6d\xee\xec\xd5\x97\x3a\xb6\x26\x73\
+\x16\x2f\xc5\xbb\x1a\xb3\xa5\xe0\x4e\xa6\x2e\x57\xf7\xbc\x9c\x1b\
+\xde\x9f\xb8\xcb\xae\xee\x70\x17\xbd\x16\x5b\x69\x20\x31\x78\x70\
+\xd6\x77\xad\x58\xb0\x69\xcd\x17\xb7\x18\x5a\xc0\xcd\x6e\x29\x23\
+\x80\xf7\xc4\xaf\x5e\x5a\x80\x99\x30\xca\x03\xf2\xfc\xbc\x97\xe5\
+\xdc\x19\xe7\x27\x6e\xe7\x36\xf4\xab\x3b\xc0\xd0\xe0\x01\x32\x60\
+\x60\x50\x32\x2a\x19\xd7\x7f\x84\xb7\xfa\xcc\x3d\x08\x82\x17\x98\
+\x8c\xb0\x90\x06\xe9\x05\x66\x9e\xdc\x7a\x84\x70\x03\xae\xc5\xc5\
+\xfc\x9a\x9e\xed\x76\x19\x73\x9a\xf0\x50\x97\xf1\x07\xe8\x50\x2d\
+\x5b\xd5\x89\xcb\x1b\x7c\x5e\xc2\x4e\xa8\x08\x07\x0c\x44\x25\x1a\
+\x3f\x6d\x95\xe9\x03\xb3\xb9\x49\xc1\x0d\x63\x06\x6b\x2e\xea\xd2\
+\x4e\x74\xd5\xf6\x1f\x84\x74\xc0\x8c\x89\xc4\x7b\x88\x21\xfc\x4b\
+\xc4\x69\xa8\x99\x0b\x08\x89\xee\x5e\x2b\xd5\x38\xb6\x41\xdc\xc9\
+\x60\x46\x47\x1b\x79\x0e\x4e\xb9\xdd\xa4\x1d\x12\xcb\x6e\x01\x43\
+\x67\x02\x44\xa7\x39\x7c\x60\x06\x31\x47\x3a\x11\xa1\x86\x6b\x83\
+\x52\x93\xb1\x95\x68\x60\xab\x70\x65\x4c\x05\xe5\xe2\x57\xbc\xd2\
+\x7d\xfa\x5b\xff\xc7\xbd\xe7\xf7\x3f\x8e\xa9\xd3\x1d\x6e\x6c\x68\
+\x20\x74\xb9\x4e\x86\x96\xa4\x1b\x35\xfa\x10\x4e\xbd\x5d\x70\x65\
+\x9b\xbb\xfc\x2d\x73\x31\x66\xe2\xb8\x03\x69\x82\xf0\x93\xb1\xad\
+\x10\x63\xc1\x50\x73\xb3\x5b\x6a\xe7\x16\xf7\x66\x1c\xcd\x57\xce\
+\x1c\x62\x7f\xd1\xdc\x3c\xd6\x57\x58\x7c\x58\x59\x87\x03\x5b\x92\
+\x58\x48\xd2\xd6\x84\xad\x06\x98\x13\x91\x68\xf9\xd1\x8d\x8c\xef\
+\x05\x43\x18\x9d\xb8\x1e\x2e\x2d\x4b\xc6\xdc\xd4\xba\x18\xae\xe9\
+\x82\xeb\x69\x06\x18\xfc\x04\xb3\xd1\xc6\x84\x06\x6a\x90\x89\xf1\
+\x71\xb4\x8a\xe3\x30\x57\x7d\x98\x60\x7f\x74\xaf\xc6\x07\x30\x3e\
+\xed\xc9\xb9\x05\x2b\x3b\x25\x1f\xa4\x22\x61\x6a\x5b\xb4\x18\x22\
+\x1c\x70\xd3\xca\x46\xc7\x1f\x47\x31\x40\x5f\x88\xc9\xd0\x02\x32\
+\x62\xdb\xfe\x46\x11\x7b\x3e\xe0\x37\xb9\x1b\x68\x0e\x5b\xa6\xd8\
+\xb8\x4c\x4b\x9d\x98\x80\xf8\x01\x4e\x21\x97\xbf\x1b\x29\x1f\x1b\
+\xc3\x36\xe2\x2a\x16\x62\xd8\xcd\xa0\x80\x50\x59\x82\x2c\xd2\xa9\
+\x4e\x37\xfb\xb4\xe6\xac\xa3\x70\x28\xe1\xbb\x0e\x6c\x45\x61\xeb\
+\x14\x35\x99\x2b\x15\x94\x09\xb7\x60\xc9\x12\x77\xc3\x47\xff\xcc\
+\xfd\xd9\xff\xf8\x96\x08\x8f\x76\xb9\x56\x4a\xa6\x6a\x88\x2f\xfc\
+\x2f\x67\xee\xba\x16\xa0\xe5\xc0\x3d\x57\xe7\xbd\xae\xcd\xbd\xea\
+\x9d\xf3\x5d\x7b\x77\x01\x05\x8e\xf4\xa1\x9a\x07\x4f\x48\x7f\xd8\
+\x0a\x55\xec\x81\xf1\x35\x0f\x9a\x85\x60\xb2\x5d\x99\x22\x03\x47\
+\x52\x27\x90\x4d\x2b\xcf\x2f\xb8\xff\xfc\xc1\xbc\xdb\xf7\x4c\xc3\
+\xb5\x63\x68\xa8\x35\xbc\xaf\xa1\x11\x98\xb5\x18\x26\x00\x53\xe9\
+\x01\x8f\x4c\xcc\xcf\x04\x46\xcc\x6a\x17\xc1\x31\xe6\xf7\x38\x0a\
+\xf3\x02\xe3\x05\x8c\x7e\x5b\x09\x83\xd2\x24\x2d\xc4\x8b\xfb\x3e\
+\xa0\xcf\x9a\xb7\xd0\x2d\x3d\xfd\x2c\xb7\xfc\x8c\x73\x60\x5e\x84\
+\xb2\xe7\x45\x7a\x58\x43\xea\xd6\xb2\xbc\xf4\x2d\xbd\xae\x6b\x76\
+\xc9\x9f\xa2\x4c\x85\x03\x28\xf0\xef\x5b\x5d\xe6\x33\xd0\x85\xa7\
+\xa0\xd3\x3c\xd6\x28\xb9\xfd\xa8\x98\x16\x62\xdd\x08\xe3\x98\xc6\
+\x76\x9c\x5e\x2a\x17\x73\x3b\xf7\x55\x8b\x78\x64\x7a\x66\xea\xc4\
+\x04\xc4\x0f\x70\x26\xf2\xb9\x47\x70\xc3\xc6\xc3\x6c\xf3\xc7\x47\
+\x91\xb5\x88\x78\x19\x2b\x9e\xbc\xbc\x44\xba\x02\x88\x8b\x45\x1c\
+\x4e\x2d\x5a\x11\x46\x16\x7e\x1b\x13\x90\xf6\x0b\xdc\xbf\x3c\x7d\
+\x93\xdb\xb9\x77\x2b\x2e\x37\x40\x0b\x22\x85\x04\x36\x83\xa0\xe4\
+\xd0\x97\x1b\xaf\x8c\x83\x76\x9b\x7b\xc3\x35\xd7\xba\xbf\xfb\x97\
+\x0d\xf4\x88\x2e\xd7\x56\xb7\xe4\xb4\x33\x31\x9b\x83\x7e\x5d\xb3\
+\x82\xbf\xe7\x53\x95\x50\xbb\x15\x70\xf6\x6b\x04\x2d\xc7\x9a\xdf\
+\xea\x74\xaf\x7a\xf7\x02\xec\xbb\x2a\x86\xf5\x0e\xa6\x5b\x99\x9a\
+\xba\x99\xd3\x16\x22\xeb\xd6\xc2\x5d\x5a\x1e\xe0\xfb\x16\x88\xad\
+\x07\xa7\xd0\xe9\x8f\xcc\xc1\x85\x43\xce\xec\x5c\xfa\xfa\x12\x0e\
+\x61\xe5\xdc\x4e\x1c\x4b\xed\x9a\x4d\xe6\xe3\xa7\x2d\x82\xb5\x18\
+\x02\x43\x84\x4c\x10\x32\x3a\xf0\x0d\x2f\x08\x06\x71\x09\x87\x2e\
+\x0c\x4f\x5d\xec\xa9\x30\x88\x9b\xe1\x34\xeb\xbe\xec\x62\x61\x61\
+\x59\xb0\xc5\x10\xc1\xc0\xf4\x7d\xdf\xfc\x45\xee\xd0\xde\x67\xdc\
+\xce\x27\x1f\x73\x4f\x3f\xf1\x88\xcc\x5e\xf6\xf4\xcf\xc3\xa3\xb0\
+\x89\x1b\x3d\x40\x6e\x71\x6e\xc5\x6a\x8c\x4d\x91\x46\x9e\x3b\xe2\
+\x40\x1c\xd1\x90\xbc\x64\xfa\x11\x64\x2a\x18\x02\xf7\x95\xad\x8c\
+\x3f\x4a\x8e\xc7\x8d\xe6\x74\xe4\xdc\x91\x43\x5c\x4d\xc5\x2e\x84\
+\x82\xbb\xd7\xdd\x78\x8f\xee\xc1\x5a\x75\x33\x5c\xa7\xa7\xa4\xab\
+\x34\x3d\xd4\x08\x6b\x03\xcc\x1c\x87\xfc\xf5\x9e\x7a\x7e\xf5\xe2\
+\x73\x30\xc7\xfb\xb2\x02\x56\x36\xba\x66\x15\x0a\x1c\x43\x4f\xe0\
+\xc4\xfa\x28\x6a\x51\x2e\x1c\x52\x71\x66\x8b\xa6\x54\xa7\x39\xfd\
+\x90\xed\x48\x40\x97\xdb\x3d\xb1\xd9\x8d\x1d\xac\xb8\x73\x97\x5d\
+\xea\xfa\x7a\xfb\xc0\x04\xc8\x19\xd6\xc5\x14\x12\x34\x4f\xac\x61\
+\x98\x4b\xcb\x56\x9e\xe6\x5e\xfd\x9b\xd7\xb8\x6d\x8f\x3e\xe2\x9e\
+\x78\x60\xa3\x5b\xb0\x6c\x85\xac\x9d\xb4\x75\x76\x63\xfc\x83\x2e\
+\x07\xe7\x43\x4d\x89\x5f\x8d\x87\x81\x9e\x8d\x2e\xad\xe4\x6c\x4c\
+\x4c\xe0\xe0\x12\xe4\xda\xbd\xf2\xfa\x59\x6e\xed\x9b\xe6\xe2\x10\
+\x54\x41\x6a\x3a\x5e\xe5\xa3\x29\xb3\x50\x34\xed\x62\x93\x68\x68\
+\x5c\xc2\x2f\x0a\x4f\x54\x28\x32\xda\x51\xd8\x0a\xf5\xbf\xb4\x7b\
+\x3c\x40\xcc\x8d\xd9\x81\xc7\x29\xdd\xb2\x33\xb1\x29\xef\x60\xdd\
+\x3d\x76\x1f\x56\x9d\xb1\x2d\xa3\x8a\x3d\x47\x1c\x13\xca\x0d\x2c\
+\x82\x0d\x1f\xf4\xd4\xea\xcb\x00\x3d\x6d\x09\x40\x91\x83\x11\x78\
+\x93\xcd\x06\x13\x17\x61\x7e\xc5\xa1\x60\x31\xea\xd4\x61\x40\x19\
+\x54\x2b\x15\x5c\x54\xd1\x8f\xb5\xad\x79\x98\xc2\x7f\x1a\xd7\xaf\
+\x0e\xb8\xd5\xaf\x78\x8d\x7b\xeb\xfb\x3f\xe4\x5e\x75\xd5\x5b\x50\
+\xd6\x35\xf7\xd4\x23\x0f\x60\xd5\x7c\x8e\x1b\x1b\x18\x73\xeb\x7e\
+\xb7\xdf\x2d\x3b\xaf\x07\x42\x45\x1a\x9a\x1f\x14\x8a\xac\x70\xa8\
+\xa0\x18\x8c\xe1\x4d\xa0\xf5\x78\x66\xa2\xc3\xa1\xae\x72\x67\xf7\
+\xb9\xe4\xb1\x47\xab\xb9\x67\x2a\xb8\x2f\x24\x9f\xfb\x87\xca\xa6\
+\x3d\x77\xb9\xb7\xbd\xad\xe0\xbe\xf4\x30\x1b\x9a\x69\xa9\x13\x13\
+\x10\x92\x9e\x87\xad\xef\x0f\x3f\x9c\xe4\x2f\x5a\x54\x42\x26\xbc\
+\xab\x8a\x95\x99\x59\xfd\x85\x04\x9b\xca\x72\x0d\xd4\x6e\x43\xd8\
+\x50\xc7\xb5\x11\x96\xab\x14\x39\x75\xf9\xc8\xec\x34\x67\xf5\x1a\
+\xae\x49\x5d\x58\x5e\xe9\xee\x3a\xfc\xcf\xee\xe8\x9e\xa3\xee\xb4\
+\xf9\xe7\xb9\x39\x7d\x73\x35\x43\xe4\xf0\x3b\xbb\x5c\xd8\xda\x87\
+\x0c\xe7\xcc\xd7\xfc\xc5\x0b\xdd\xc5\xaf\xfc\x75\x1c\x82\xa9\xb8\
+\x7b\x37\xfc\x54\x32\x7c\x6c\x78\x08\xfd\xff\x0e\x37\x7f\xc9\x32\
+\xd7\x05\x01\x63\x86\x56\xf5\x22\xaa\x69\x65\xc6\xf1\x90\x4a\xb3\
+\x10\x07\xb4\x90\x13\x47\xa4\xf4\xdd\x9b\xfe\x78\xbe\x3b\xeb\xf2\
+\x3e\xd9\x46\xc2\x31\x98\x8d\x87\x98\x5e\x4d\x75\xfc\xab\x50\x0d\
+\x83\xe6\x29\xec\x64\x02\x41\xca\xe2\xc4\x30\x15\x16\xb8\xe3\x9f\
+\xf7\x6a\x75\x60\x17\xc2\x8a\x73\x30\x73\x33\x08\x21\xd9\x88\x67\
+\xc7\xe6\x6b\x6d\x4a\x41\xa1\x40\xab\x22\x85\x56\x5f\xe4\x6a\xce\
+\x3e\x06\x12\x66\xfa\x83\xbc\x27\xae\xfc\x48\x39\x88\xcd\xc3\xcc\
+\x2d\xd5\x3d\x1e\x90\x38\xce\x98\x8f\x4a\x6c\xf8\xe8\x11\xdc\x92\
+\x78\xd0\xad\x45\xb9\x7d\xe4\xbf\x7f\xc1\xbd\xe3\x03\x1f\x76\x6b\
+\x5e\xbe\xce\x9d\x77\xf1\xa5\xee\xfc\x4b\xaf\x70\xdb\x9e\x78\xd0\
+\x6d\xdd\xfc\x90\x5b\x79\xd9\x62\xb7\xfa\xaa\x0e\xec\x46\x40\xa5\
+\x83\x3d\x68\x54\x41\x38\x68\xc6\x67\x42\x91\xea\xc4\xc2\x38\xb6\
+\xde\xe1\x1e\x1d\x2f\xbb\x73\x7a\x73\x18\xa4\x27\xf5\x7f\x79\xb0\
+\x56\x04\xcf\x8c\xb5\x95\x73\xeb\xc7\x37\xee\xd9\xe3\xae\x99\x5f\
+\x70\x1b\xb6\xbf\x00\x02\xb2\x6a\x95\x08\x48\x72\xfe\xbc\xa3\xa8\
+\x35\xaf\x41\x57\xa0\xaf\xab\x33\x5f\x6f\xef\xc2\x5e\x55\xe4\xcd\
+\x20\xe6\x9d\xa9\xf3\x16\x40\x15\x12\xad\x49\x99\xdc\xc9\x9f\x66\
+\x42\x2d\xa9\xba\x25\xed\x67\xb8\x3b\x8f\xfe\x93\xdb\xf2\xc4\xbd\
+\x6e\x49\xe7\x69\x6e\xfe\x9c\xa5\xae\x0d\x5d\x2b\xeb\x3a\x50\x48\
+\xa8\x6a\x10\x92\xbe\xd9\xfd\xee\x02\x64\xec\xaa\x4b\x2e\x77\x0b\
+\x96\x9e\x82\xe9\xbc\x01\x77\x70\xf7\x4e\xbc\x4a\x74\xd8\x0d\x0f\
+\x1c\x81\x70\x54\x64\x6a\xb8\xb7\x7f\x0e\x06\xb1\xd8\x2c\x88\x82\
+\x62\xb3\x3f\x53\x55\xe8\xcc\xb9\xb6\x3e\x08\x06\xee\x88\x6d\x80\
+\xe9\xce\x7e\x5d\x87\x7b\xed\x87\x16\xb9\xc5\x67\xe2\x22\x1e\x90\
+\x93\xda\x3a\x30\xa2\xa6\x45\xc3\xb0\x94\x5a\x88\x6a\x4f\x31\x5a\
+\xbb\x2b\x6b\x65\xfd\xb4\x6a\x41\xc8\x1c\xac\x6d\x78\x7c\xb7\x03\
+\x9b\x8c\x56\xac\xc2\x96\x0c\x6c\x62\xdc\x72\x7b\x4d\xd6\xa4\x3a\
+\x30\x99\x36\x81\x8a\x8a\x8a\x34\x5b\x7e\x00\x0a\x53\x4b\xa0\x86\
+\xa1\xb8\x2a\x0b\xe2\xa0\x38\x26\x1c\x46\x10\xf6\x54\x20\x14\x18\
+\xdb\x1b\x90\x5e\x8e\x37\x96\xae\x3c\xcb\xed\xc6\x0c\x64\x1d\xf6\
+\x8f\x7c\xfa\x8b\xee\x7d\x18\x4b\x9e\xbd\x7a\xb5\x6b\xeb\xc0\x7b\
+\xb6\x68\xf1\x1b\xe8\x05\xcd\x99\x3f\x47\x2e\x66\xf8\xe5\x0f\xff\
+\xc9\xbd\xfe\xf7\x96\xba\xde\x85\x98\xe8\xc0\xf6\x74\x26\x91\x34\
+\xb5\x6b\xd9\x5a\x38\x44\x78\xc0\x55\xc4\x3b\x52\xef\x74\x8f\xe3\
+\xfa\xe8\x5f\xc3\xc1\xb4\xca\xa1\x5a\x7d\xcb\x8e\xa4\x80\xb3\x34\
+\x1b\xcf\x29\xe4\x3e\xbd\x67\x23\x56\x70\xe7\x5d\x92\x63\xc5\xce\
+\xd8\x4e\x47\x9d\x78\x0b\x72\xcd\xc3\x39\xec\xee\xc5\x73\xa4\xfb\
+\x46\xf2\xab\x17\x5d\x94\xe4\x0b\x17\xe1\x2d\x86\x5a\x57\x6f\x01\
+\xfd\x2c\x76\xb3\xb0\xf1\x1e\x5d\x10\x9a\xa9\x8c\x15\x54\x37\x61\
+\xf1\xad\x88\x77\x27\xd6\x44\x32\xee\x96\xb4\x9d\xe1\x76\x57\x77\
+\xb9\x7f\x7a\xe4\xf3\xae\x3c\xd8\xe1\x66\x77\x2f\x74\x7d\x3d\x98\
+\x1d\x02\x93\xb3\x9b\x85\x6c\x93\xda\x9a\x5d\xb0\x8e\xce\x2e\x4c\
+\x07\x9f\xe5\x2e\xb8\xec\xe5\xee\x55\xbf\xf1\x9f\xdc\x2b\xdf\x74\
+\xb5\x3b\x67\xf5\x25\x78\x5a\x60\xb6\xdb\xfa\xf0\x03\xa8\xb5\x0e\
+\x8b\xc0\x8c\x8f\x8e\x60\x37\xec\x6c\xd7\x87\xfe\x6f\x5b\x47\xa7\
+\xcb\x97\xb0\x83\x4d\x0a\x87\xdd\xb8\x26\x45\xbe\xc3\xb0\x86\x82\
+\x51\xc4\x57\x1b\xc2\xd9\x0e\x30\x5a\xff\xca\x82\x7b\xf9\xf5\xf3\
+\xdd\xaa\xd7\xcc\x76\xdd\x32\x78\x54\x7f\xac\x03\x34\x85\xf1\xaf\
+\xa6\xd1\x63\xb4\x70\x57\x97\x66\x77\xda\x43\x0b\x21\xb9\xa6\x0c\
+\x62\x78\x22\x14\x64\x06\xef\x26\x70\x84\x4f\x21\x69\xef\xcc\xbb\
+\xe5\xe7\x14\xdd\xbc\xa5\xce\xdd\xf7\xf3\x2a\x5a\x14\xbc\x79\x8c\
+\x19\x36\x11\x62\xb4\x70\xc2\xbc\x8c\x2b\xd9\x43\xbe\x60\xf0\x80\
+\x18\x6c\x6e\x0a\x63\x38\x9a\x5f\x60\x7a\x94\x81\x7c\x60\x7e\xd3\
+\x13\xc2\x62\x3b\x02\xed\xee\x9b\x83\x0b\x2a\x16\xbb\x5d\x5b\x1f\
+\xc7\xe2\xe6\x6a\xf7\x97\x37\xfe\x6f\xb7\x0e\xdd\xa9\xce\x9e\x6e\
+\xf0\x07\x6e\x08\x63\x1f\xd1\xd3\x2d\x94\x8a\xee\xe8\x81\xc3\xee\
+\xe0\xac\x6f\xba\x33\xd6\x2c\x84\x70\x68\x39\x33\x16\xc7\x12\x0e\
+\x8d\x25\x85\x83\xdd\xab\x22\xba\x57\x9d\x78\x97\x30\xe7\xce\xed\
+\xcb\x25\x5b\x1f\x9f\xc8\x3d\x3d\xe4\x72\xbd\xc5\xe4\xc6\xa7\xbe\
+\x72\xdf\xad\xd2\xbd\xba\xf9\xe6\x16\x05\x2e\xd1\x68\xf9\x23\x45\
+\xdb\xd2\x65\x3a\x40\xee\xcb\xda\xb0\xa1\x56\xb8\x61\xcd\x35\x18\
+\x44\x7e\xb7\x08\x71\x5b\x71\x66\xb9\x51\xee\xc8\xe7\x07\x0e\x39\
+\xf7\xcc\x41\x6c\xda\x43\x5f\x90\xdd\x72\x3e\x06\xd3\x4a\x27\x73\
+\x35\xbb\xa1\xe3\x80\x47\xe0\x3b\x84\x05\x1e\x1b\xf9\x95\x5b\xd6\
+\x75\xb6\x7b\xf7\xf9\x7f\xe4\x2e\x5b\xf5\x3a\xb7\x60\xee\x72\x56\
+\x9a\xd2\x67\xd5\x41\x25\xb3\x08\x8c\x8c\x80\x0a\xb8\x56\x48\xdc\
+\xf0\xdc\xf4\xc8\xd0\x20\x9e\x11\xde\x8b\x75\x82\x27\xdd\x63\xf7\
+\xdf\xe7\xee\xf8\xd9\x8f\xdc\xc3\xf7\xde\x91\x49\x55\xdf\xbc\x05\
+\x18\xf8\xb7\x63\xda\xb4\x82\xd6\x07\xc7\x94\xf1\xf0\x3c\x56\x5b\
+\x85\xa1\xea\xb8\x81\xc4\x54\xcf\xb2\xbc\x3b\xff\x8d\x73\xdc\xd2\
+\xf3\xba\x5d\xcf\x6c\x24\x08\x55\x16\xf7\x92\xb1\x31\xe3\xc7\x4c\
+\x64\x7f\x9f\x75\x01\xc3\xa7\x99\x30\xa6\xd7\xcc\x0a\x07\x8c\xac\
+\x4d\xb8\xe1\x36\xd9\x01\xce\xfa\x21\x00\x4a\xfd\x68\x9c\x68\x16\
+\x98\x6a\xe2\x46\x23\x07\xd2\x05\xc4\x9f\xee\xbb\x9e\xa8\xba\x9f\
+\x7d\x67\xd4\x3d\x72\x27\x5a\x13\x84\x3a\x6b\x31\xc7\x01\x5c\x9d\
+\x06\x2d\xc6\xd9\xd3\xf0\x24\x44\x5e\xf4\x47\x28\x09\x58\x73\x56\
+\xe9\xd6\x70\x90\x85\x35\x7e\x1b\x2a\x24\xae\x51\x51\xfc\x35\x36\
+\xf8\x95\x7f\xc5\x66\x0b\xcf\x49\x95\x02\x76\xaf\xb2\xd5\xa0\xba\
+\xe6\xbf\xfc\x81\x7b\xe7\x07\xff\xc0\x2d\x5e\xbe\x0c\xdd\x2d\x0a\
+\x12\x4e\xda\xf9\x05\x5f\xba\xb3\x1c\x4b\xd8\x86\xf1\xf8\x13\xbf\
+\x72\xb7\x3e\x78\x85\xeb\x6c\x3f\xc7\x57\x84\xc0\xe5\xf8\x03\xa4\
+\x45\x07\x6e\xe8\x6a\x11\x26\x7e\x75\x1c\xc2\xb3\xdf\x03\xf5\x2e\
+\x77\xf7\x68\xa7\x5b\x8b\xd6\xfe\xf4\x8e\xa4\xfe\xdd\x9f\x57\x0a\
+\x03\xb8\xe8\x70\x71\x5b\xb2\x6e\xf7\x57\x37\xdf\xee\x7e\x77\x6d\
+\x89\xeb\x77\x0c\x73\xba\x0a\xa5\xfd\xec\x55\xbd\x5c\xdb\xe0\x2a\
+\x85\xed\xb5\x7a\x7e\xc5\xf8\x70\x52\x6f\xeb\xcc\xe5\xdb\x3b\x29\
+\x1c\x3c\x0d\x07\xc6\x81\xe0\x30\x91\xcc\x42\xb2\x0e\x5b\x00\xea\
+\x98\x5e\x10\xa6\x52\xb8\xb9\x93\xb1\x0a\xd8\xaa\x3c\x86\x85\x9d\
+\x82\x3b\xaf\xe7\x55\xee\x70\x6d\xaf\xfb\xef\x77\xbd\xdf\x5d\xf8\
+\xe8\x15\xee\xcd\xab\x7e\xc7\xad\x3d\xfb\xd5\xd2\xf5\x2a\x60\x9e\
+\xb5\x86\xe6\xb9\x8e\x0c\xaf\xa2\x0a\xe5\xc2\x1c\x07\xf3\x2c\xa4\
+\xee\x59\x7d\xb8\x05\xa4\x0f\x02\x7b\xb6\x7b\xd9\xab\xaf\x74\x57\
+\x5f\xf7\x01\xb7\x67\xe7\x0e\xb7\xed\x91\x07\xdd\xfd\x77\xdd\xee\
+\x7e\xf0\xbf\xbe\x86\x1a\x4b\x8f\x26\x33\x07\xba\x7a\x66\x43\xa8\
+\x0e\x4b\x9c\x2c\x47\x96\xfe\x5a\xbb\x3b\x65\x6d\x9f\xcc\xc5\x77\
+\xf5\x63\x2a\x1d\x4c\x55\xc5\x43\xa4\xe4\x2d\xde\xa7\x2b\x9d\x35\
+\xa4\x4f\x99\x17\x50\x0a\x06\x12\x43\xe6\xa1\xd0\xb3\x30\x69\x26\
+\x3e\x53\x4d\x5d\x21\xa9\x7d\xb2\x7b\xe4\x87\xfe\x49\x50\x7c\xe9\
+\xaf\xd0\x22\x88\xc1\x81\xa6\x28\xc1\x23\x8c\xb3\x5b\xa2\xb9\xa5\
+\x67\x95\xdd\x7f\xfe\xfd\x82\x7b\xe8\x8e\x8a\xfb\xbf\x9f\x1f\x43\
+\xd7\x13\x67\x70\x50\x81\xf4\x60\x5a\x1a\x33\x8e\xae\x8a\x9b\xa1\
+\x90\xbd\x22\x2c\xd2\xb2\x18\x2d\xa5\x28\xe5\xc5\x31\x55\x0d\xd3\
+\xee\x45\x30\xef\xfc\x45\xa7\x82\xb9\x2b\xee\xc0\xee\x5d\x1e\x63\
+\x7a\xda\x27\xbe\xf0\x75\xf7\xeb\xbf\xf5\x36\xe4\x6f\x17\xd6\xb5\
+\xb0\x1e\x00\x9a\xb1\x70\x90\x0a\x6b\x7f\xa6\xab\x56\xc0\x3a\x18\
+\x0c\x0d\xb4\x04\x18\x69\x8a\x50\x04\x81\x10\x3c\xe2\xaa\x40\x08\
+\x1c\xbe\xd4\x9d\x6b\x6f\xdc\x9c\x58\x96\x1b\x0d\x17\x76\xe7\x92\
+\x81\xfd\xb5\x04\x63\x7d\xd7\xd1\x91\xdb\xb8\x7b\xf0\xf4\x3b\x1d\
+\xd6\xb4\xdd\x62\x0c\xd0\x66\xa8\x18\xaf\x67\xa3\xcc\x7f\x92\xbf\
+\x61\xcd\x3f\x34\x5c\xfe\x77\xfb\x7a\x5c\x6d\xe1\x69\x65\xce\x5f\
+\xe5\xf6\x61\x52\xed\x30\xe6\xa0\x4b\xd8\x54\x47\x26\xb2\x96\x42\
+\x6a\xdb\xc8\xde\xaa\x65\x91\xda\x97\x4c\x85\x97\x4a\xdb\x71\x39\
+\x52\x09\xc2\x70\xa4\xb6\xdb\x3d\x53\xd9\xe6\xce\xeb\xbd\xd8\xbd\
+\xf9\x9c\x0f\xb8\xd5\x67\xbe\xca\x2d\x9c\xbb\x02\x97\xb2\x95\x90\
+\xa9\x6c\x55\x74\xb1\xc9\x12\x24\x91\x93\x02\x61\x8d\x86\x5a\x0f\
+\x0c\xc1\xf1\xc2\xe8\xf0\xb0\x3b\xb8\x77\xb7\x7b\xea\xb1\x87\xdd\
+\x03\x77\xff\xca\x7d\xe7\x8b\x9f\x11\x2f\x5d\xb3\x7b\x5d\xff\xb9\
+\x75\x37\x6f\x65\x87\x9b\x73\x4a\x97\xeb\x5d\x50\x46\x8d\xc9\xeb\
+\xa4\xa1\x38\xcf\x8a\xf8\x48\x5c\xa5\xd5\x50\x33\xd3\x45\x46\x95\
+\xf8\x4e\x32\xc3\x8d\x42\x03\x9f\x9a\x7e\xc5\x55\x3f\x0a\x13\xc1\
+\x11\x7f\x44\x92\xff\xd4\x0f\x40\x01\x26\x66\xa5\x25\x46\x12\x85\
+\xf2\x9a\xea\x66\x51\x8e\x97\xd6\x84\x38\x07\x76\x55\xdd\x96\x5f\
+\x8e\xb9\x9f\x7f\x9b\x17\x62\xf2\xdd\x3e\xb4\x28\x4b\xd0\x9d\x45\
+\x6b\x22\x1b\x02\xbd\x9c\x11\x97\x84\x68\x25\x29\x2e\xde\x71\x66\
+\xb0\x8c\x2e\xe9\xe1\x7d\x7b\xe8\x2a\x8a\x2d\x65\xcf\xec\xb9\xd2\
+\xbd\xe2\xec\x13\xf1\xf8\xb5\x63\xcb\xd0\xe2\x53\x4e\x75\xcb\x4e\
+\x3f\xd3\x9d\x7d\xc1\x1a\x77\x29\x2a\xa6\x33\xce\xbb\x10\xb4\xd0\
+\x7a\xe1\x62\x2b\x1b\x3f\x7a\x32\xa2\xb1\x35\x29\xe2\x9d\x83\x6a\
+\x6d\xcc\xfd\xe4\xae\x3f\x75\x4f\x1f\xfa\x2c\x76\x63\x5c\x88\x65\
+\x02\xdc\xdf\x22\xad\x87\x56\x44\x4c\x52\x68\x4d\xe0\x33\x15\x14\
+\x52\x6f\xb8\x4a\x03\x2d\x50\xa5\x07\xbc\x96\x77\xaf\x58\xe8\x92\
+\x07\xee\xae\x34\xee\xdc\xeb\x0a\x0b\x3a\x93\x3f\xd9\xf7\xe5\xfb\
+\xfe\xd2\xf1\x45\x29\x1c\xd3\x88\xc3\x9e\x8e\xd9\xb2\x74\x3a\xb8\
+\xad\x71\x7c\xb3\x55\xbc\x61\xed\x95\xa8\xcd\x7f\xcc\xb3\xc3\xa7\
+\x9e\x51\x6e\x60\xd0\x98\xe7\x13\xbe\x4f\xa3\x92\x2e\xe0\x64\x98\
+\x32\x90\x67\x30\x84\x6a\xc2\x32\x59\x47\xe1\x80\xa9\x84\x11\xbd\
+\x4e\x21\x29\x80\xab\xca\xf9\x12\x04\xa5\xe4\x06\x6b\xfb\xdd\xf6\
+\xca\xe3\xee\xb4\xce\x53\xdc\x9b\x56\x7e\xc8\xad\x3e\x63\x9d\x5b\
+\xb6\xf0\x4c\xd7\xdd\x35\x4b\x18\x91\x2b\xcb\x1c\x10\xb2\x9d\x92\
+\xd2\x0e\x31\xa7\xc0\xa9\xb0\xb0\x3b\xc6\x4c\x1f\x1b\x1d\x75\x03\
+\x07\x0e\xb9\x7b\x36\xfd\xc0\xdd\xf6\xf4\x7f\x71\xb3\x67\x9f\x03\
+\x86\x60\x2b\x04\x49\x82\xd0\xb1\xdb\x62\x71\x54\x26\x57\x26\xb5\
+\xf4\x08\x0c\xf4\x45\x10\x90\x2e\x66\x68\x70\x13\x38\x61\x0c\xd7\
+\xc3\x9b\x60\xc4\xa7\x9b\xb4\x06\xa2\x2b\x0d\x85\xd1\x51\xc3\x23\
+\x1e\x89\x8b\xae\xc6\xe0\x46\x27\x2a\xc3\x51\x1b\x7e\x91\x3e\xd2\
+\x29\xf2\xb0\x16\xc6\x20\xfb\x76\x4c\xb8\x47\xee\xae\xb8\x0d\xdf\
+\x46\xd5\x2a\xed\x5f\xde\xf5\x62\x8b\x0c\xfd\x81\xbf\xf1\x0a\x31\
+\x85\x82\x9c\xc8\xb5\x87\x1a\x18\xbe\x5b\xc6\x15\x63\x23\xc3\xee\
+\x9d\x1f\x42\x17\xf7\x35\x57\xba\xd9\xe8\x96\xca\x18\x8e\x7d\x4b\
+\x2a\x64\x22\x7c\xd0\x80\xb0\xf2\x98\x1c\x28\xcb\xb8\xb0\x0b\x47\
+\x16\x4a\x58\xcf\x9a\x18\x47\x39\xa0\xf6\x62\xb7\xcb\x14\x5b\x0c\
+\x89\x1c\x7e\x4b\xc5\x32\x7a\x00\x75\x77\xef\x43\xff\xd3\xdd\xbd\
+\xf5\x3a\x1c\x6e\x5a\x85\xba\x88\x15\x1d\xd7\x5c\x90\x2b\x8c\x0e\
+\xb1\xcd\x0c\x3b\x73\xc4\x5a\x0e\xe2\x10\xe3\x70\xad\xc7\x6d\xa9\
+\xb4\xb9\xcb\xe6\xe4\xdc\xd2\x7c\xa3\xf1\x95\x9f\x56\x70\xca\xca\
+\x0d\xcf\x29\xe5\x2e\x3d\xf4\x95\xfb\x1e\x39\x91\xf1\x07\x08\x83\
+\xc4\xb3\x55\xbe\xd9\xaa\x15\xdd\x2f\xc0\xc7\x1b\x93\x46\x6e\xed\
+\xe8\x40\xbd\xd1\xde\x9d\x47\x37\x0b\x7b\x61\xda\x50\x63\xf3\x31\
+\x78\x9e\x0e\x63\x22\x51\xe0\x92\x3d\x92\x50\x82\x98\x09\x69\x97\
+\x8b\xd1\x61\x56\x0a\x0e\x32\x42\x5d\x70\x9a\x0c\xa6\x09\x94\x60\
+\x35\x99\x70\x9d\x85\x3e\xb7\xa6\xf7\x55\xd8\x9a\x72\xc0\x7d\xe1\
+\xa1\x8f\xb9\xf6\xc7\x9c\xbb\x6a\xd1\x07\xdc\x9a\x53\x5f\xeb\x56\
+\x2e\xb9\xc0\xcd\x9b\xbd\x14\x37\x81\x77\x0a\x73\x48\x06\x93\x3e\
+\x32\x9c\x66\x52\xae\xb3\x5b\x86\x8f\x8b\x36\x3d\xdd\xc0\x43\x2e\
+\x94\xf6\x56\x5d\x27\xf8\xa6\x80\x02\xab\xf1\x1a\x3f\xe0\x89\x90\
+\x22\x32\x5a\x20\xb0\x03\xca\x0e\x16\x85\x01\x40\x9a\x24\x86\xa4\
+\xaa\x66\xc6\x1d\x5d\xab\x60\x37\xe6\x46\xb7\x42\xfc\x9a\x3d\xc5\
+\x67\x0a\x85\xb1\x85\x02\xe1\xfc\x0c\x96\xe2\x01\x0c\x59\x01\x5c\
+\x91\x33\x7e\xe8\x46\x25\x4e\x4c\x23\xe3\x67\x0a\xc6\x1a\xc6\x1e\
+\xec\xe6\x2e\x5a\xd9\xe6\xe6\x2e\x2b\xbb\x55\x97\x77\xba\xa7\x1e\
+\xac\xb8\xcd\x3f\x1b\x75\xbb\x1f\xb7\x4a\x35\x8f\xd5\x78\xcc\xd6\
+\x61\x6d\x2e\xc1\x6b\x99\x38\xbf\x88\x71\x41\xbb\x3b\xbc\x77\x9f\
+\xfb\xe3\xcf\xfd\xa3\x7b\xe3\xdb\xaf\x05\xe3\x63\xef\x06\x94\xe6\
+\xa3\x05\xe0\x75\x06\xee\x83\x65\x6b\xce\x45\x41\x76\xa9\xf2\x10\
+\x1a\x0a\x47\x2c\x14\x52\x49\xa1\xa2\x63\x5a\x06\x86\xf7\xb9\x4d\
+\x8f\xfd\x2f\xf7\xc0\xce\x8f\xb8\xf6\xe2\x59\x10\x0e\x1d\xec\x33\
+\xb7\x19\x4e\x73\x57\x8a\xf9\x4f\xb8\xba\x29\x7f\xd4\xd0\x25\x1b\
+\xa8\xe3\xcc\x3f\x32\x79\x21\x26\x55\xf6\x3d\x85\xd9\x9b\x46\x3e\
+\x3f\xb7\x9c\xdc\x7a\xf0\x1f\x21\x1c\x54\x76\xd8\x4f\x2c\xd3\xff\
+\x41\xb6\x3d\x4b\xb5\x01\xd9\xc2\xe6\xeb\xaf\xef\xa8\xe5\x57\x2f\
+\xec\xc5\xa2\xe1\x95\x35\xf4\xd3\x7b\xfa\x70\x93\x3b\x6f\x73\x40\
+\x45\x3c\x84\x29\x5f\x76\x6f\xa8\x8c\x91\xd8\x17\x65\x06\x49\xa1\
+\xf2\x87\x66\x83\x05\x5d\xe1\x19\x7f\x40\x04\x7b\x43\x50\xc6\xd0\
+\xa2\x74\xba\xe5\xed\x2b\xdd\xac\xc2\x6c\xb7\x79\xe0\x5f\xdd\xbf\
+\x3d\x7d\xb3\x7b\x74\xdb\x0f\xdc\xde\x3d\xbb\xdd\xe0\xe0\x61\x6c\
+\xc7\xc0\x4c\x09\x72\x52\xfa\xbd\x28\x28\x36\xf1\x2c\x30\x86\xd5\
+\x40\xdf\x7a\x70\xe4\xa0\x7b\x78\xeb\x9d\xee\xfb\xb7\xff\xa9\xbb\
+\x77\xe7\x5f\xbb\x9e\xc2\x05\xa8\x31\x39\x86\x83\x1f\x4a\x07\x10\
+\xf9\xab\x2a\x36\x35\x9b\xd5\x1e\xff\xa6\x7e\x52\x5c\x09\x38\xa2\
+\x18\xe3\x08\x6f\x79\x06\x33\xbc\x18\xa6\x4e\x26\x3a\x5e\x07\xd0\
+\xbc\x50\x0f\x66\xa9\x6d\x15\x87\xa2\xc6\xf4\x73\x45\x5e\xc6\x27\
+\x48\x57\x37\xc6\x53\x8b\x4e\x2b\xbb\x33\x2f\xe9\x70\xa7\xad\x2e\
+\xbb\x1e\xb4\x22\x43\x87\x6b\x6e\xf8\x70\x1d\x5d\x2e\x7c\x58\x86\
+\xee\x28\x2f\x70\x47\x0f\xed\x76\xef\xf8\xd0\x47\xb0\x5e\xf1\xfb\
+\x10\x8e\x0e\xbc\x6d\x32\x0e\x1a\xc8\x7d\xf0\xdf\xb1\x3e\xce\x6c\
+\x51\x31\x5c\x8d\x15\xcb\xa0\x80\x6e\x2e\x98\x18\x5d\x5d\xc6\x73\
+\x70\x64\x9f\x7b\x6c\xc7\xad\xee\x97\x5b\xfe\xd4\x6d\x3b\xf8\x25\
+\x69\x39\x58\x56\xd2\x7a\x78\xe1\x90\x6e\x14\xe2\x9f\x15\x08\x2f\
+\x34\x48\x23\x85\x87\x21\x0c\x37\xda\xdd\x2e\x3c\x9b\x79\x0a\x56\
+\xce\x97\xb6\x27\xc9\xcf\xef\x9d\xc8\x0f\xa3\xcb\x81\x05\xba\x3f\
+\x1d\xdf\xb4\x57\x5b\x8f\x87\xa7\xbf\x38\x08\x92\x41\x3d\xfb\x16\
+\x84\xa4\x1e\xc2\x95\x81\x50\x8d\x7a\xe1\x7b\xd8\x1d\xf6\xb1\x4a\
+\x25\x37\x6f\x6c\xa8\x51\xef\x99\x53\x28\x60\xd2\xc3\x15\xd1\xd5\
+\xe2\x82\x4f\x91\xe7\xa8\x81\x49\x64\xfd\xc8\xec\xac\x2b\x58\x98\
+\xfc\x33\x38\x68\x49\xd2\xbd\x9b\xb5\x3a\x80\x19\x7e\x0e\x47\x2a\
+\xab\x58\xca\xae\x61\x5a\x98\xdd\xae\x95\xed\x97\xc3\x77\x1d\xdb\
+\x56\x8e\xba\x5b\x9f\xf9\x9c\xfb\xd6\xd3\xce\x2d\x41\xea\x4e\xef\
+\x7e\x85\x5b\xda\x83\x45\xc7\x6e\xec\x02\x6e\x9f\x85\x38\xe0\xf8\
+\x2b\xfa\xbb\x03\xa3\x7b\xdc\xce\xa3\x77\xba\x9d\x23\xb7\xb9\x5e\
+\x4c\xe9\xce\x6e\xbf\x88\xbe\x7d\x4d\x8f\x6c\xa7\x60\x81\x22\x8b\
+\x80\xb1\xa3\xbc\xb0\x39\x8f\x5b\x88\x6c\xab\x40\x6c\xf5\x43\x1f\
+\xb4\x29\x8b\xb2\x55\x61\xbc\x53\x7b\xb3\x3b\x13\x2e\x95\x43\x84\
+\x03\xa3\x50\x51\x3a\xb4\xa9\x7f\xd5\xc5\x2a\xee\x6a\xa2\x5b\xaa\
+\xd4\x0c\xa2\xcc\xd0\x26\x07\xf0\x36\x98\x1c\xf1\x44\xe4\xb9\x8d\
+\xfc\xb4\xfe\x92\x5b\x7e\x6e\xa7\x5b\x7d\x65\xcd\x1d\x7a\x66\xc2\
+\xed\xdb\x3e\xee\xf6\x3c\x99\x77\xdb\xef\xd1\x5d\xe1\xaf\xb9\xea\
+\xed\x98\x96\xed\xc0\xb8\x6d\x14\x63\x05\x6e\xe9\x89\x09\xa6\x61\
+\x9a\x29\x6d\x29\x90\x67\x68\xb6\xf8\x51\x4e\xd8\x62\x8f\x8e\x1d\
+\x76\x87\x07\x76\xb8\x9d\xfb\xee\x72\x5b\xf7\xdd\xe4\x0e\x21\xef\
+\xb1\x16\xe8\xba\xca\xab\xd1\xcd\x1a\x45\xfe\x72\x2b\x0a\x73\x58\
+\xf3\xda\x78\x25\x74\xb5\x7c\x92\x6c\xfc\x41\xbc\x1a\xb6\x96\x0c\
+\x27\x6d\x6e\x3f\xd2\xf5\x0a\xcc\x5e\x61\x70\x5e\xdb\x3b\x92\x2b\
+\xcd\xea\x4e\xb6\x0c\x0c\x4c\xfc\xd8\xe2\x75\xa2\xfa\xb1\x53\x3b\
+\x7d\xaa\xe4\x05\x29\x41\x0c\xd6\xff\xae\x91\xcb\x7f\xb0\xb7\x23\
+\xa9\xa2\x49\x2f\xb1\x69\x3f\x7c\xc0\xb9\x7d\xb8\xfe\x86\xe7\xd6\
+\x6d\x7c\x61\xba\xf5\xef\xc9\x6c\x66\x9e\xe4\x86\x58\x36\xc3\x98\
+\xe9\x01\x1f\x81\xd3\x8c\xcd\x93\x68\x66\x51\x53\x61\x40\xcf\x31\
+\x4b\x03\xc2\x53\x69\xec\xc7\xd9\x80\x5d\x0e\x67\x6f\xd8\x98\x09\
+\xa3\x63\x31\x9c\x7b\x73\xdc\xac\xf2\x1c\x4c\x27\x9f\x2a\x7e\x71\
+\x36\x12\x45\xcf\x9a\x4e\xe9\x06\xdd\xfb\xa1\xdd\x04\x45\xcc\x08\
+\x8f\x71\x9e\x64\x16\x98\xd2\x81\x17\xa1\x4d\x5d\xfd\xa7\xf8\x02\
+\xf3\x70\x11\x2c\xa1\x9f\xe2\xc1\x49\xd2\x4c\x3c\x2a\xfa\x37\xdd\
+\x60\x26\x90\x06\x57\x8c\xf4\x37\xc5\x4b\x61\xc2\xdf\x9e\xd1\xa4\
+\xcc\xe0\x44\x61\xc9\x63\xec\xc8\x30\x28\x3c\xe3\xa3\xb8\xf0\x00\
+\xf2\xb1\xe7\x99\xa7\xdc\xdb\x7e\xf3\x76\x77\xee\xaa\xcb\x79\xb4\
+\x1a\xe8\xec\xa6\xb2\xa0\xf9\xc5\x4a\x43\x92\x19\x44\x10\x63\xeb\
+\x4b\x5a\x1c\x98\x0f\x8f\x1e\x84\x50\x6c\x77\x7b\x0f\x6f\x71\xbb\
+\x0f\xdd\xe6\xf6\x0f\x7d\xc7\x4d\xa0\x20\xda\xc1\x0b\xc5\xc2\x6a\
+\xd0\xc3\x20\xbf\x51\x91\x8a\x4f\xc6\x19\x20\x1b\x04\x40\xe2\xc9\
+\x72\x24\x4c\x53\x6b\x66\x1d\x7b\x60\xdf\x56\xa3\xcd\x6d\xaf\x76\
+\xc9\xb6\xf6\x2b\x16\xe6\x92\xdb\x7f\x39\xd6\x78\x70\x28\x5f\xe8\
+\x29\x36\x3e\x36\xf4\x95\xfb\x3e\xe3\xd6\xa3\x98\xd6\x4b\x84\x9b\
+\x23\x1d\x27\x60\x4a\x33\xa2\xf9\x9c\xa8\xc4\x5d\x83\x3d\x2e\xee\
+\x66\xd4\x13\xc9\xd7\x51\x5d\xfc\xce\x20\x76\x7d\xf4\x0f\x37\xea\
+\xdd\xfd\xf9\x02\x0e\x04\xba\x03\xb8\x4f\x1b\x8e\x68\x66\x59\x3b\
+\xa0\x40\x84\xdd\xd8\x6a\x90\xed\xf0\x87\x0c\x65\x2d\x4b\x15\xbb\
+\xb1\x58\xf8\xdf\x0c\x23\x73\xd2\x2f\xdd\xe5\xcd\x3e\xd8\xeb\xc8\
+\xc5\x04\x5d\x24\x36\xd3\xac\xf1\x09\xef\x28\x2c\x70\x3d\xc5\xa5\
+\x32\xde\xa0\x9d\x05\xa7\x82\xc0\x69\x44\xb4\x40\x28\x1c\x4e\x02\
+\x10\x9f\x6e\xbc\xf0\x90\xad\x97\xb6\x14\xac\xeb\xa9\x18\x92\xd2\
+\xe3\x46\x3f\x31\xe3\x97\x61\xd3\x6c\x58\x42\xd7\xdb\x45\x98\x60\
+\x96\x31\x8b\xc0\x0c\x17\xf8\x48\x6c\x88\x07\xdc\x8c\x82\xd2\x52\
+\x3c\x83\x12\xa6\x66\x44\x0c\x4a\x86\x17\x00\x2a\xdc\x42\x16\x27\
+\x0f\x53\xb3\x64\x1a\xf1\xcd\x6a\xba\x66\xa7\xd9\x04\x03\xdd\x7e\
+\x9c\xc9\xf1\xf4\x91\x11\x65\x4c\xd1\xb7\xa3\xd5\xe8\x5d\x84\x93\
+\xa9\xfb\xff\xda\xb5\xa1\x36\x99\x3f\xe7\x5c\x1c\xa9\xee\x42\xf9\
+\xb1\x24\xc4\x9b\x05\x81\x5c\x80\x11\x3f\x9c\x91\x1a\xaf\x8e\xb8\
+\xd1\xca\x61\x77\xe0\xf0\xe3\xd8\x9d\x7d\x87\xdb\x77\xf4\x17\xee\
+\xe8\xd8\x2f\x64\xf3\x2a\xc7\x08\xe5\xe2\xb9\x98\xa5\xe2\x9e\xb9\
+\x51\xc0\x86\x94\xf1\x91\x1f\x4c\xb5\xb4\x14\x9e\x96\xd0\x13\xb8\
+\xa7\xdd\x6c\x66\x78\x80\x8d\xa0\xf5\xd8\x81\x61\xd4\xd5\x58\x39\
+\x1f\x3e\x58\xab\x3f\x78\x08\x72\xd7\x5e\xdf\x53\xab\x17\x6f\x62\
+\x34\xf5\xfe\xdd\x99\xcf\x5e\x89\x5f\xfc\x4c\xca\x3f\x73\x98\xb1\
+\xbe\x1e\x3c\xb5\x1e\xfc\x00\x85\x56\xe4\xbb\x68\x45\xae\xe9\xeb\
+\x49\x26\x16\xae\xc0\x2e\x18\x84\x72\x10\xad\xc8\x81\x23\x70\x83\
+\x48\x0a\xf3\x00\x16\x5a\x80\xc8\x6c\x30\xea\xc2\xb0\x91\x9b\xb4\
+\x22\xa4\xef\x61\xb1\xbb\x9a\x29\x2c\x9a\x28\xa3\x43\x3f\xea\xcf\
+\xeb\xe2\xd7\xe2\x00\x41\x80\xc7\x38\x2c\xc1\xf7\x61\x88\x3f\x4f\
+\x6f\x6a\xb3\xa6\x03\x68\xca\xf4\x0c\xcf\xfc\x0b\x4c\xed\x8c\x9f\
+\xe2\x64\xed\x04\x0a\x7b\x04\x77\x78\xa2\x3f\x7e\x22\x0d\x66\x16\
+\xb0\xc0\xc5\x9d\x08\xc2\x9a\xea\x2e\xae\x9e\x86\x98\x67\xf8\xa3\
+\xe2\xa1\xcc\x48\xe6\x24\xe7\xe6\x72\xed\x60\xe2\xc7\xa4\x42\x9b\
+\xdd\x7d\x83\xeb\xef\xb9\x10\xdd\xd4\x85\x10\x94\x6e\x74\x9d\x30\
+\xfb\x02\x55\xaf\x8f\x63\x8a\x76\xd4\x55\x26\x06\x30\x19\xb3\x1f\
+\xd7\x7b\xee\x70\x47\x47\xef\x75\xc3\x95\x8d\xe2\x8f\x8b\xc7\xc5\
+\xfc\x59\xc8\xe3\x5e\xd8\x31\x76\x49\x86\xa1\x73\x96\x8a\x63\x11\
+\xa4\x1c\x61\xc9\xe7\xcd\x64\x20\x69\x49\x3c\x3c\xb6\x9b\x99\x15\
+\x2c\xb0\x70\xdf\x31\x84\x03\xad\x07\xee\xfe\xc0\xd4\x6e\x2e\xd9\
+\x7c\x57\xa5\x71\xcf\xa1\x5c\xa1\xb7\x58\xff\xec\xe0\x57\x37\xfd\
+\x21\xf8\xf1\x59\xb5\x1e\x0c\xe5\xb9\x6a\x41\x9c\x08\x87\x9f\x6b\
+\x46\x02\xfe\x01\x03\x92\x6b\x8e\x1e\x75\xe5\xbe\x91\x06\x76\xf9\
+\xe6\x0b\xbd\x68\x45\x0e\x61\x4d\x84\x33\x1c\xf2\x68\x25\x12\xc8\
+\x0c\x92\x16\x80\xc9\x65\x86\x48\xb2\x15\xc6\x62\x67\x2d\x1e\xc3\
+\xa4\x15\x01\xd3\x30\xa3\xd8\xe4\x98\x3b\xed\xd9\x16\x85\xf4\x8c\
+\xb6\xb0\x9a\xcc\x9e\xd1\xc4\x2e\x82\xe4\x2f\x2c\x34\x69\x66\xb3\
+\x26\x16\x28\x74\xa5\xab\x34\x59\xab\x91\x51\x48\x5f\xe9\x91\x86\
+\x9a\x19\x43\xa6\x80\x4a\xfd\xd3\x6c\xad\x03\xa9\x8b\x5d\xdc\x52\
+\x5c\x62\x2a\xdc\xc2\x53\xdf\x0a\xa3\x7f\xc0\x61\x11\x3b\xd2\x60\
+\x66\x66\x90\x09\x99\x04\x49\x34\x02\x18\xa6\xfc\xe2\x87\x24\xd5\
+\x8b\xb7\x98\xc3\x54\xba\xfa\x64\x90\xb1\x12\x2b\x68\xb3\x8b\x9a\
+\xcf\x9f\x0b\xba\x63\xe8\x01\x7c\xcd\x61\x0f\xa9\xcf\x2f\x0d\x8a\
+\xfe\x88\x1b\xfb\x67\x05\xc5\x2e\x6e\x5b\xe9\x1c\x44\xa5\x03\x6e\
+\x18\xd4\x27\x15\x08\xda\x21\xc1\xc5\x0b\xcc\xd0\x8b\x42\x47\xfd\
+\x7a\x21\xf1\x74\xb4\xdc\x94\x26\x73\x90\xe5\x63\xe1\xd8\x58\x44\
+\xed\x98\x58\x40\xf7\x6a\x37\x5a\x8f\xd7\x63\x6a\xb7\x72\xa4\xde\
+\xb8\x67\x0f\xae\x32\x69\x77\x03\xa5\x42\xfe\x1f\x25\x3d\x72\xb0\
+\x6f\x66\x2b\xe7\x71\x3e\xd0\xfc\xdc\x09\x88\x50\xde\x20\xbc\xeb\
+\xbe\x7e\xdf\xcf\x73\xd7\xaf\xbe\x25\xc9\x15\xde\x3c\x70\xa0\xd6\
+\x68\xef\x2a\x17\xca\x38\x3b\x31\x17\x37\x72\xec\xe7\x55\x9c\xe0\
+\x66\x65\x4c\x65\x36\x32\x2a\xa7\x7b\xa5\x3b\x25\xce\x64\x3d\xfc\
+\x79\x26\x27\x69\x15\x00\xf8\x43\xa6\x89\x60\xa0\x20\xc2\x40\x3e\
+\x83\xe7\xe9\xc0\x3d\xfb\xac\x32\xa9\x68\x78\x26\x94\x42\x8b\x02\
+\x87\x42\x30\x01\x60\x38\xda\x2d\x62\xf1\x29\x13\x72\xf7\x25\x13\
+\xa6\xc2\x43\xf7\x94\xf9\x19\x17\x73\x23\xbb\x49\x0b\x24\x5e\x53\
+\x41\x10\xb8\xf8\x26\x8d\xd4\x2f\xe1\x44\x4d\x61\xb0\x34\xe1\x29\
+\x24\x85\xa7\x76\xf5\x47\x3b\x15\x69\x89\xd2\x68\x07\xe1\x31\xf0\
+\x54\x7a\xcc\xdc\xc4\xf1\xde\x05\x9d\x03\x66\x0a\x07\x3a\x46\xe8\
+\xb7\x9c\x8f\x8f\xa9\x65\x48\xc8\x01\x78\xb4\x94\xb0\x9c\xa8\xe8\
+\x57\xd7\x2f\xb0\x86\x81\xee\x16\x36\x7f\x78\x3c\xd2\xd1\xd9\x2b\
+\x65\x6e\xe2\xb1\x9c\xa8\xfb\x8f\xb9\x40\x33\x60\x2a\x14\xcc\x15\
+\x75\x8b\xbb\x5e\x34\x13\x6b\x2c\x29\xbb\x43\x38\x77\xbe\x02\x33\
+\xcf\x73\xdb\x5d\xb2\x65\x0b\x16\x7a\xd0\x87\x9b\x93\x4f\xbe\x7d\
+\xe8\xcb\x1b\x1f\x95\x75\x8f\x2f\xdf\xcc\x29\xc9\x67\xa5\xd0\x38\
+\x3d\x87\x6a\x03\x62\xce\x85\x43\xee\x9a\x5c\xbd\x78\x1f\x72\xe9\
+\xbd\xb8\x16\xa8\xd0\xd5\x5d\xa8\xb7\x75\xe4\xf2\x6c\x6e\xb9\xcb\
+\x97\xb3\x80\x3a\x8d\xaa\x05\x6b\x85\x9b\xd6\xae\x3e\x4e\x70\x90\
+\xd9\x1d\x41\x50\x2c\xfe\x2a\xeb\x99\x5f\x22\x29\x4c\x31\x22\x33\
+\xe1\xa1\x96\xf5\xae\x1e\x97\x21\x28\x04\xd4\xbc\x93\x41\x8c\x5e\
+\x8c\x13\xdc\x08\xf4\x3e\xc5\x28\xe6\x40\x40\xdc\x62\x5b\x16\xd7\
+\xfc\x2a\x06\x19\x40\x15\xec\x11\x93\x19\xcc\x18\x90\x34\x04\x17\
+\x3f\xe6\x87\xba\x32\xa6\x8a\x97\xe1\x52\x97\x8f\xb8\x1e\x59\x70\
+\x89\x1f\x7d\x29\x13\xa6\xfe\x59\x31\x98\xff\xc0\x94\x80\xf1\xbc\
+\x0e\xbb\x45\x1c\xb3\x25\xf8\xd4\xcc\x19\x27\xc2\xf8\x8d\x8b\x5b\
+\x03\xb3\x8a\xe6\xc6\x4a\x44\xe2\x24\x2d\x06\xe8\x32\x3e\x3e\xce\
+\x84\x6b\xf8\x84\xd1\x8d\xe1\x6a\x7c\xcd\x2e\xee\x1e\x3f\x36\x93\
+\x0e\xe3\x79\x34\xc1\xf9\xa1\x5a\xde\x5d\x32\x17\x15\xe6\x60\xbd\
+\xf1\xc3\x07\x6a\xc5\xb6\xf6\x64\x70\xc2\xd5\xdf\x5f\xdf\xb4\xef\
+\x90\x9b\x8f\x6d\xed\xdb\xb7\x6b\x85\x0d\x3a\x27\xaa\x9e\x5b\x01\
+\x61\x2c\x36\xe2\xe2\x58\xaa\xcd\x7b\x9e\xca\xad\x5d\x74\x3e\x24\
+\xe1\x5c\xcc\xc7\x36\x3a\x7b\x8b\xdc\x40\xeb\xb8\x5e\xc8\x75\x11\
+\xa4\x11\x1f\x7e\x84\x57\xac\xb6\xa5\x55\x00\xa2\x0b\x0a\x69\xd1\
+\x46\xd4\x94\x93\x83\xbb\xba\x0a\x0a\x11\xbc\x6f\xef\x87\x1a\x94\
+\xf9\x0b\xae\x3e\x4c\x75\x15\x0c\x31\xa6\x21\x9b\x4b\x1a\x03\xc5\
+\x62\x24\x52\xb7\x60\x8c\x7d\x4b\xea\xb3\xfe\xd4\xd3\x64\x98\x30\
+\x90\x91\xf3\x34\x14\xa6\x94\x35\x23\x95\xb1\x48\xc3\xdc\xcc\x5f\
+\xec\xde\x6c\x26\x8e\x7c\x64\x3e\x61\x40\xaf\xc7\x76\x84\x49\x7f\
+\xfc\xa4\x36\x37\x3b\x00\x0a\xa7\x70\x44\x74\x60\x0e\xb5\xbe\xe0\
+\x92\x26\x71\x3d\x9e\x85\xc3\x36\xde\x9b\x33\xcc\x4d\x5a\x46\x9b\
+\x3a\x70\x32\xf4\xc4\x0f\xe1\x6c\x45\x2c\x5c\x6f\xf7\x7e\xe9\x38\
+\x8a\x7e\xd4\x01\xec\xbb\x9a\xdb\xe6\x92\xb3\x7a\x72\xb9\x8d\x9b\
+\xc6\xeb\xfb\x1b\x85\xc2\xac\x42\xf2\xc5\xe1\xaf\xde\xff\x1d\x19\
+\x7b\xdc\xb8\x9d\x93\x96\xcf\x5a\xb1\x47\xf1\x5c\x2b\x5d\x38\x04\
+\x55\x6c\xd3\xf8\x34\x5a\x91\xea\xc0\xd1\x46\x71\x74\x10\xed\x06\
+\xca\x9d\x33\x5a\x58\x73\x82\x9b\x06\x1b\x32\x4c\x32\xd9\x17\x94\
+\x64\x9e\x66\x52\xa8\x69\x24\xd3\xe0\x8f\x19\x05\xaf\x84\x4b\x26\
+\x46\xba\x64\xac\xcf\xc8\xac\x3b\x33\x19\x85\x01\x5c\xd5\x8d\x06\
+\x0b\xcc\xc3\x19\xbe\x37\xab\x4e\x37\x1f\x4e\x13\x4d\x8b\x93\xe1\
+\x69\x58\x3e\xbe\x42\x27\x8d\x5f\x4a\x33\x0b\xe3\xd6\x2e\xf1\x87\
+\x05\x3c\xa3\x93\xc2\x3c\x2d\xc6\x27\xb8\x1b\x7e\x13\x9d\xe0\x8e\
+\x35\x59\x89\x7f\x4a\x2f\x8d\x17\xfc\x20\xbf\x33\x76\x9f\x6f\x0c\
+\xbb\xd9\x5f\x6a\x6f\x0e\x93\xf6\x94\x7e\x88\xaf\xa5\xc5\xdc\x32\
+\x61\x19\xfd\x38\x7c\xa3\x61\xb0\xc9\xf6\x90\xf7\x46\x13\x61\x10\
+\xc6\x1b\x4b\x46\x9c\xae\x7b\xac\xea\xcf\xe7\x8e\xee\xab\xd5\xb6\
+\xec\x4d\x4a\x3d\x85\xfa\xbe\xc3\xf5\xe2\xe7\x85\xab\x66\x70\x29\
+\x83\x72\xe1\xd4\xbf\xcf\x7d\x0b\xc2\xb0\x36\x6c\xd7\xf6\xf5\xea\
+\xbd\xcf\xe4\xd7\x2c\x5e\x98\x14\x0b\x97\x54\x47\xeb\x75\xac\xae\
+\x17\x70\x4b\x77\x82\x81\x4f\x6e\x00\xbb\x49\x91\x76\x28\xad\x2d\
+\xd5\xa4\x75\xbc\xd4\xf8\x00\x6b\x03\x63\xee\xde\x8d\x88\xf0\xa3\
+\x2d\x8a\xf9\x52\x3d\xfc\xb6\x6a\x49\x48\xcf\x87\xa5\x14\x8d\x6e\
+\xf0\x45\x43\xc0\x11\x8b\xc7\x6f\x6d\x6e\xf6\x3f\x13\x3b\xd2\x2e\
+\xb4\x63\x3f\x31\x4c\x05\x58\xc3\x25\x9c\x8a\x30\xe2\xab\xae\x66\
+\xef\xe6\x99\x87\x78\xf6\xd1\x87\x56\x26\x29\xfe\x64\x3b\x18\x34\
+\xf2\x63\x4c\x69\x34\x9a\x5b\x0f\xf1\x0f\xc7\xac\x9f\x94\x3e\x05\
+\x50\xfc\x8a\x0e\x38\x0a\x58\xec\x0c\xa3\x19\x46\xbb\xb8\x53\x38\
+\xcc\x9f\xa7\x25\x6e\x0c\x87\x34\x62\xbf\xd8\xb6\x94\xeb\x40\xd7\
+\xaa\xe8\xce\xec\xc2\x51\x63\xac\x9a\xdf\x7a\xd7\xb8\x1b\x6e\x2b\
+\xe4\xf1\x4a\xf6\x9f\x57\xbf\xbe\xf1\x87\x7e\xcf\x15\xa3\xf8\x9c\
+\xa8\xe7\xa3\x05\x61\xc4\xb8\x2e\x22\xb4\x71\xfc\xf6\xd3\xb9\x7a\
+\x7d\xdf\x18\x76\xaf\x0f\x1e\xc4\x32\x14\x52\x8d\x0d\xa2\x6e\x0e\
+\x06\xec\xb2\x72\x47\x64\x66\x94\x65\x0a\x74\xc9\x30\x0f\xd3\xc2\
+\x50\x77\xab\x8d\xb5\x20\xd3\x8c\x55\x7c\xa5\x63\x66\xcb\x5c\xda\
+\xcd\x1f\x69\x05\x33\xc3\x43\x01\x04\x7c\x0b\xcf\xe3\x43\x13\x37\
+\xe2\x6b\x78\xe6\x37\xb2\x8b\x7f\xa5\xa1\x74\x52\x73\x1a\x6e\x0c\
+\xa3\x99\x35\x2a\x68\x21\x32\x59\x1c\xb8\x05\x58\x1a\x96\xd4\xe6\
+\xd2\x4a\x18\xfe\x64\xbf\xa1\xc6\x9f\xd4\x9a\x44\x7e\x24\x5d\x59\
+\x7b\xf0\x87\x38\xa5\x2d\x95\xc6\x31\xe4\x13\xe2\x64\x2d\x85\xea\
+\x91\x7b\x1c\x2f\x89\x7b\xe4\x16\xc2\x53\xda\x1a\x96\x85\x6f\x78\
+\xc7\xb6\x87\x7c\x17\x41\xc1\x5e\x3c\x54\xad\x83\x18\x9c\x63\x38\
+\xee\xce\xea\xc7\xf3\x02\xdb\xaa\xb5\xbd\x63\x58\x67\x73\xf5\x07\
+\x46\x6a\xb5\xbf\x43\x91\x99\x02\xc6\x73\xa3\x9e\x9f\x16\x84\x71\
+\xe3\xb1\x46\x1e\x90\xff\xce\xad\x47\xd1\x8a\xd4\x92\x62\xfe\x0d\
+\xa3\x43\x8d\xa4\xa7\x37\x9f\x2b\x75\x60\x9f\x0c\x42\xe6\xb9\x75\
+\x6e\x3f\x67\x6b\x40\x66\xa5\x62\x2d\x9f\xd6\xab\x6a\x16\x3b\x7e\
+\xe2\x16\x85\x30\xc3\xb4\x16\xc7\xfc\xab\x4e\x04\xc3\x88\x71\x63\
+\x2c\xa2\xa4\xa1\x49\x00\x74\x16\x15\xc1\x25\x46\x6a\x8f\x7f\x0d\
+\x93\xd4\x83\x02\xb3\x65\xec\xe2\x90\x85\x69\x5a\x9b\x61\x44\x54\
+\x98\xb8\xfb\x22\xb6\x92\x36\x3f\x41\x8f\xdc\x69\x0c\x78\x30\x98\
+\x3d\xe0\x82\x2e\xcd\xf6\x85\x96\xc1\xfb\x93\xca\x09\x66\xf0\x78\
+\xf0\xab\x66\x56\x06\xa9\x3f\xf1\x1f\xd3\x87\x39\xd0\x12\x3c\x4f\
+\x43\x70\xcc\x2f\x68\x9a\x1f\xa3\x15\xec\x0c\x8f\xc2\xe2\xc3\x0d\
+\xfe\xcc\x8f\x0a\x12\xa2\x25\x15\xcb\x70\xae\x0b\xeb\x1e\x79\xd9\
+\xb1\xdb\x55\xa9\x37\xbe\x7f\xcf\x44\xd1\xe1\xd2\x0a\xdc\x78\xfb\
+\xa1\x89\x6f\xdc\x7f\xbf\x4c\x10\x7d\xeb\x56\xdb\x7d\x49\x6f\xcf\
+\x5a\x3d\x7f\x02\xc2\xa8\xd9\xb9\xf5\xdf\x3a\xeb\xde\xdc\x91\xb1\
+\xd7\xe2\xe2\xac\x53\x92\x6a\xa3\xd6\x35\x0b\x3b\xa2\x30\x60\xc7\
+\x2e\x6c\x99\xd5\x12\xbe\x00\x3a\x99\x55\xf8\xd5\x33\x36\xd9\xc5\
+\x98\x2d\xb0\x3a\x80\x31\x53\x1b\x5c\x74\xf5\x00\x72\x91\x4f\x4f\
+\x4b\x29\x45\x70\x0b\xd4\x87\xe1\xbd\x4a\x78\x59\x73\x6a\xb3\xb8\
+\x88\x17\xf1\xdf\x4c\x2f\xc2\x6d\x16\x14\x14\x3e\xfd\x93\xe1\xb2\
+\x4a\x19\x30\xa6\x2d\xa8\x9e\x3e\x19\x48\xfd\xa9\x2f\x75\x53\xc6\
+\x0a\x6e\xa4\xeb\x3f\x0b\x43\x19\x3b\xc5\xa3\x3f\xfd\x88\x1b\xc3\
+\x35\x4e\x81\xd1\x23\x5a\x52\x83\x07\x7f\xf0\x03\x8f\x16\x8e\xb6\
+\xd0\x46\x2b\x72\x33\x21\xa0\x3f\xc1\xa7\xae\xf1\x9b\x2c\x08\xea\
+\x5f\xe1\x1a\x0f\x89\x1b\xfd\x89\x1f\x0a\x86\x12\x1a\x43\xd7\xea\
+\x50\x52\x72\x73\x21\x0d\xbc\x90\xe1\xfe\xfb\xc7\x6b\x7b\xc6\x0b\
+\x05\xdc\x13\x72\xf3\xe0\xd7\x36\xfd\x37\x60\xa5\x13\x44\x62\x79\
+\x6e\x7e\x9e\x5f\x01\x61\x2b\xc2\x69\x5f\xec\xf4\x75\x6b\x17\x3d\
+\x89\x54\xbf\xb7\x32\x9a\x14\xdb\xcb\xf9\x3a\xb6\x2f\xc8\xac\x16\
+\x8e\x1d\xe0\x66\x12\x14\x2b\x62\xc2\x0c\xa5\x12\x96\xa0\xa4\xf0\
+\x5f\x34\x42\x68\x35\xd6\x27\x5c\x61\x06\x0f\x7a\x00\x07\x83\xf8\
+\xa4\xbb\x2a\xc0\xbd\x93\x51\x53\x2b\x7f\x01\x11\x8b\x37\x9b\x17\
+\xef\x21\xc6\x0b\x4e\xe2\xa6\x2e\x81\xbe\x05\x20\x00\xa5\x65\xf8\
+\x9a\x44\x83\xa9\x3f\x9f\x6c\xa0\x28\x5c\x19\x3f\xcd\x0f\xf3\x13\
+\x0b\x04\x71\x8d\x01\x49\x9b\x38\x46\xc7\xcc\xc2\x64\xb0\xa8\x5d\
+\x99\x54\xcc\x64\x3e\xc2\xfd\x17\xb7\x1c\x74\x17\x3b\xdd\x18\x86\
+\xff\x4c\x20\x02\xae\xb8\x2b\x8e\x74\x43\x05\xcf\xd3\x94\xb8\x30\
+\x0c\xef\xdf\x70\x83\x9d\x70\xc3\x35\x3c\xb5\x5b\x38\x9a\x36\xe4\
+\x07\x10\x27\x70\x68\x78\x28\xdf\x8e\x75\x8f\x9c\xbb\x7c\x7e\xde\
+\x0d\xed\xae\xd6\x7e\xf1\x68\xbd\xd4\xd3\x91\x1c\x19\x18\xcf\xbd\
+\xc3\x3d\xb0\xe7\xb0\xec\x28\x9f\xc1\x6d\x25\x08\x7e\x5a\xea\xf9\
+\x15\x10\x46\x81\x6b\x22\xec\x6a\xfd\xcf\x9f\x6d\x47\x57\xab\x3b\
+\x29\x15\xae\x18\x19\xaa\x37\xa4\xab\xd5\x96\xcb\xb5\xa1\x25\x61\
+\x57\x8b\x82\x42\xe6\x34\xe6\xa0\x57\x63\x60\x18\xf8\xef\x55\x80\
+\x4e\x2d\x24\xe2\x97\xe8\x1e\x57\xfc\x2b\x05\xfb\x8d\xe9\x79\xa9\
+\x08\xf4\xbd\x41\x23\x64\x16\x91\x9c\xd4\x97\xc6\x28\x8d\x8b\xd9\
+\x0d\x3d\xd8\xc9\x09\xa2\x94\x59\x14\x6e\x30\xea\xca\x20\x06\x21\
+\x63\xa8\x4a\xf1\x2d\x4f\xcc\xcd\x50\x8c\x79\x85\x06\xe9\x44\x5f\
+\x0a\xf3\x8c\x08\x4f\x26\x10\xf4\x6f\xb8\xcd\x2d\x47\x60\x50\xd0\
+\x0a\xc2\x40\x7c\xfa\x8f\xfc\x05\xe6\x6f\x76\x83\x10\x28\x0d\xc3\
+\xa7\x5f\x1f\x37\xa3\x21\xba\xa6\x5b\x68\x46\xf6\x20\x9c\xe2\x87\
+\xe3\x9f\x9c\x1b\x2d\x74\xca\x9a\xc7\x6a\xec\xd6\xed\xc7\xf5\x36\
+\x37\xdd\x81\xf3\x3a\x3d\x58\xaf\x6f\xb8\x8f\x56\xbf\x71\xdf\x8f\
+\xdd\xba\x75\x45\x77\xe3\x89\xef\xb7\x42\x12\xa6\x54\xcf\xd7\x20\
+\xbd\x65\x80\x8d\x6a\xed\x93\x18\xb0\xdf\x8f\x6b\xfc\x8b\x87\x50\
+\x0b\x50\x28\xf8\x18\xe5\x22\x3c\x32\x45\xc5\xcc\xa2\xb2\x9a\x4f\
+\x9a\x5e\x66\x14\x1c\x42\xa6\xd3\x6c\x30\x9f\xb1\xc4\x33\x5c\xd1\
+\x59\xb8\x01\x47\x0b\x22\x75\xa7\x9b\xd2\x48\x71\xb2\x34\x9b\xe1\
+\x93\xed\x1a\x9e\x0e\xaa\x8d\x9e\x87\x21\x5c\xa5\x1f\xdb\xa7\x80\
+\x81\x1b\x6c\x00\xcc\x30\xe2\x2f\x85\x37\xd3\x51\xbc\x30\xe8\x25\
+\x0d\x7c\xd9\x30\x95\xee\xa4\x41\x75\x14\x06\xdd\x9a\xc3\x10\x7b\
+\x86\x96\x0f\x2b\x1a\xfc\x33\x1c\x0b\x2f\xa6\x1f\xe2\xe3\xcb\x22\
+\x4e\x4b\x1c\x37\xc5\x8b\xf3\xc3\xd2\x1d\xa7\x41\xdd\x79\x9a\xb3\
+\x82\x96\xe3\x08\x76\x90\x2c\x04\x9f\x9c\x82\x03\x78\x9b\x1f\x18\
+\xc7\x21\xf9\x42\xa1\xb7\x51\xff\xe1\xe8\xd7\xee\xd3\x81\xf9\x34\
+\x9f\x32\x50\xee\x9a\xd9\xef\xf3\xdf\x82\x30\x3e\xec\x6a\x71\x9f\
+\xd6\xdf\x62\x4e\xee\x82\x45\x0f\x63\x3b\xc6\x7b\x2b\x23\x49\xb1\
+\xad\x98\x93\xae\x16\x4e\x69\xca\x02\xe2\x08\x5a\x12\x6e\xbd\x46\
+\x1e\x43\x65\x6b\x67\xa9\xbb\x59\x8b\xeb\x7f\xc6\xbd\x65\x77\x4b\
+\x3d\x10\x5d\x95\xf6\x9d\xc4\x2c\x94\x53\x07\x0f\xa3\xa6\xc0\xf4\
+\x37\x20\x05\x37\x41\x16\x3c\x50\x09\xce\x34\x04\x8b\x37\x4f\x86\
+\x69\x4b\x90\xc5\x8b\x61\x9a\x6e\x0d\xc1\xe0\xac\x2c\xa8\xcc\x2d\
+\x6e\x45\x02\x0e\xc2\x56\x3c\xe8\x34\x4f\xfa\xd4\x3f\x69\x4c\x76\
+\x8b\xf0\x11\x96\xb9\x6b\xcb\xe2\xfd\xc1\x23\xfd\x42\x76\xd4\x3d\
+\xc2\x13\x9a\xde\x5d\x5b\x19\xa5\x41\xa1\x10\x37\xfa\x31\x7c\x4b\
+\x8b\xb8\x01\x4f\xe0\x74\xf7\xb8\x1e\x8f\x7e\x09\x9c\xc8\x95\xdd\
+\x30\x04\x04\x8f\x8f\xb9\x2b\xd0\xb5\x3a\xb0\x7d\xa2\xfa\x1f\x5b\
+\x93\x72\x57\x5b\xe3\xf0\x70\xad\xf1\x36\x77\x3f\x56\xcc\x65\xff\
+\xdf\x73\xb3\x28\x88\x50\x27\xa9\x17\x46\x40\x18\x2c\xd7\x46\xd6\
+\x63\x89\xf5\xf3\x7b\xd1\xd5\x5a\x94\x24\xe5\xc2\xab\x87\x06\x6a\
+\x49\x4f\x37\x66\xb4\xda\x0b\xb9\x36\x08\x09\x4f\xba\xe2\x6e\x37\
+\x1d\x8f\x48\x54\x55\x48\x94\x45\x4c\x60\x60\xd3\x7f\x60\x18\x0c\
+\xa6\x94\x5b\x05\x2e\xde\x3d\x0d\x6a\x59\x1a\x0a\x51\x98\x77\x0d\
+\x16\x1a\x82\x45\xcc\xa9\x6d\xb2\x5b\x33\x6e\xd6\xee\x69\x7b\x7a\
+\x2c\x77\x51\xc2\x28\x64\xa4\x48\x79\xe6\x10\xff\x9e\x71\xe8\xaa\
+\x38\xca\x74\x74\x23\x03\x73\x01\x49\x19\x59\xfd\x0b\x3f\x11\xd7\
+\x13\x0c\x76\xef\xdf\x98\xde\xfc\x4c\x72\x07\x20\xc3\xd0\x91\x3f\
+\x69\x3d\x7d\xb8\xc1\x3f\xf0\x8d\x46\xec\xae\x66\xef\x26\x38\x88\
+\xa7\x4f\x8b\xe0\xe3\x27\xed\xc2\x69\x7c\x15\xee\xd3\xe3\xdd\x49\
+\x1c\x57\x39\xb8\x31\x5c\xfd\xf4\x4c\x35\xe7\xae\xc0\x76\x92\xd2\
+\x50\xad\xf1\x7f\xee\xc2\xac\x15\x2e\xc8\xc3\xf5\xbd\x1f\xac\xde\
+\xb8\xf9\x67\x2a\x1c\xcf\x4f\xd7\x4a\x73\x16\x15\xb7\x19\x5e\x10\
+\xdd\x5f\x57\x9a\x6c\xda\xfb\xef\xb9\x8b\x16\x5e\x8e\xc3\x05\x67\
+\x54\x86\xea\xb5\x6e\x5c\x36\xc7\x67\x7e\x79\x33\x3c\x0e\xae\xe9\
+\x78\xc4\x5a\x12\xcf\xf8\xca\xa4\x26\x10\xb0\xe9\x3f\xa2\x6d\xb0\
+\xd4\x14\x70\xd5\x90\x62\x88\xdd\x5c\x99\xe2\x66\xb3\xa7\x15\xe3\
+\x05\xb3\xe2\x6a\x3e\x11\x2f\x6b\x37\x5a\xe6\x9e\xb5\x93\x69\x88\
+\x1f\xfb\x89\x61\x64\x90\x54\xa9\x59\xf1\xc5\x1f\x00\xe6\x4e\x3d\
+\x35\x2b\x63\x91\xae\x32\x2f\x74\x30\x64\x2a\x40\x11\x3c\xc6\x89\
+\xcc\x29\xc3\xa6\xb8\xda\x7a\x90\x96\x86\xc5\xf0\xb4\xf5\xa0\x20\
+\x29\x5e\x2b\x1c\xc5\xf7\x74\x4c\x30\x8c\x86\xf7\x27\xb4\x9a\x61\
+\xde\x4e\xda\x0c\x94\xb4\x2b\x10\x8e\xfd\x78\xc2\xe0\x5c\xcc\x58\
+\x2d\xc6\xd9\xd9\x1f\xde\x31\x5e\x1f\x6d\x2b\x16\x66\xe5\x1a\x5f\
+\x1d\xfa\xaa\x9f\xb5\xfa\xc5\xf6\xc4\x7d\x32\xcd\xb7\xe7\xc3\xf4\
+\xc2\x0a\x88\xcd\x6a\x71\xe0\xbe\x66\xe1\x2f\x73\x8d\xe4\x1a\xdc\
+\x52\xde\x97\x54\x93\x2a\x6f\x64\x94\x07\x78\xd0\x92\x1c\xe5\x5e\
+\x2d\x64\x1a\x65\x83\x19\x6a\x7d\x19\x58\x69\xc1\x9f\xea\xf1\xaf\
+\xc0\xf0\xd3\xca\x55\xd0\x83\x4b\x8a\x43\x42\x8a\xaf\x14\x0d\x4f\
+\x75\xef\xc6\x42\x0b\xca\x9b\x03\x28\x18\x3c\x06\xed\x31\x4c\xed\
+\x92\x06\x62\x68\x62\x3c\x0e\x19\x29\x55\xa9\x00\x79\x38\x1c\xcd\
+\xdd\xdc\x62\x5d\x04\xc1\x93\x4c\xf1\xd4\x0f\xed\xd2\x9a\x78\x1a\
+\x62\x8f\x70\xcd\x5d\x5a\x8d\x26\x1c\x13\x04\xe2\x98\x30\xc4\xc2\
+\x97\xa1\x25\x7e\xbd\x40\x20\xdd\xa1\xb5\xf0\x34\x33\xc2\xe7\x61\
+\x42\xcb\x84\xc5\x60\xb1\x1d\xe6\x0a\xae\x01\x3a\x8a\x16\x84\xef\
+\x0b\xae\xc2\x7d\xc8\x5b\x36\x55\xaa\x5b\x87\xf2\xe5\xbe\x42\xb2\
+\x69\x60\x70\xe2\xed\xee\xe1\x03\x13\x32\xf1\x73\xcd\x89\x9d\x33\
+\x47\xd2\xa6\xad\x5e\x58\x01\x61\xb4\x28\x1c\x9c\xfa\xfd\xca\x7d\
+\x87\xdd\x9a\x45\x8f\x80\x85\xde\x3d\x36\x82\x57\xa4\x71\x79\x7b\
+\x3b\x66\x26\x70\x47\x19\x4e\x01\x62\x7d\x04\x5b\x51\x94\x81\xb5\
+\xd0\xad\x0b\xa5\xec\xe7\xc5\xa0\x55\xb7\x4a\x10\x14\x8b\xc1\x35\
+\xfb\x4b\xf9\x97\x38\x29\x1e\xcd\xa9\x2d\x72\x13\x60\xea\xa2\x78\
+\x59\x7b\x96\x8e\x84\x9a\xa1\xad\xcc\x1c\xd1\x84\x2b\x78\x43\x71\
+\x44\x00\x53\xa1\x20\x34\x16\x04\xd2\x16\x5c\xcf\x4c\xea\xee\xfd\
+\x1b\x63\x09\x8e\xd2\x0f\xcc\xec\x61\xc2\xb4\xc4\x6b\xfe\xcc\x3d\
+\xd2\x43\xab\x40\x98\xd0\xd6\x70\x82\xd0\x48\x1c\x18\x1f\xff\x79\
+\x1c\x71\xf7\xf1\xcb\x08\x00\xd3\x62\x70\xc1\x25\x5d\x4f\x33\xa6\
+\x65\x38\x20\x34\x51\x6c\x73\x23\x79\xdc\x2c\x83\xe4\x5c\x86\x47\
+\x82\x76\x3f\x39\x5e\xbd\x7d\x5b\xa3\x8c\x03\x8e\x43\x83\xe3\xc9\
+\x6f\xb9\x9b\x1e\xd8\xf9\x7c\x2c\x08\x32\x5f\x5b\xa9\x17\x5e\x40\
+\x18\x0b\xee\xf8\xe5\xd4\xef\xb7\x7f\xf6\x38\x6e\x42\xc1\xa9\xc9\
+\xe2\x95\xc3\xb8\x55\xa3\xa3\x3d\x97\xb4\xe1\x62\x55\x0e\xda\xb9\
+\x88\x38\xcc\x96\xc4\xcf\xb3\x21\x0f\xc1\x2b\x64\x02\x63\xc7\x54\
+\x48\x14\x4a\x78\x0b\xf7\xe0\xc3\xdc\xa8\x9b\x39\xa5\x26\x68\x02\
+\x3f\xb6\x9b\xb8\x6a\x64\x02\x9d\x66\xbf\x6a\xe7\xaf\x86\x25\xe8\
+\x01\x68\x30\x0d\x87\x60\x75\x9f\x0a\x4e\x77\x65\x48\xd2\xa3\xd9\
+\x74\xf1\x87\x1f\xea\xf6\x19\x3d\xb3\x4f\x57\x0f\x42\x40\x5a\xf0\
+\xa4\x76\x2f\x08\x0c\x17\x0c\xae\x02\xe4\xc3\x0a\x38\xb4\xab\x3b\
+\xf5\x8c\xb0\x04\x3f\x74\x4f\xfd\xa5\xf8\x06\x53\xf7\x6a\x01\x37\
+\x23\x42\x40\x8e\x60\x77\xc5\xcb\x17\xe4\xdd\xd8\x9e\x89\xfa\x0f\
+\x36\x55\x8b\xa5\x5e\xdc\xaf\x55\x4f\xae\x4b\xbe\xb9\x49\xc7\x1d\
+\x5c\x57\x7b\x81\xd4\x8b\x23\x20\x4c\x9c\xbf\xfc\x3a\xd9\xbc\xf7\
+\x57\xf9\x0b\x17\x2c\x83\x90\xac\x1d\x3c\x52\xab\x75\xe1\xda\xd2\
+\x72\x07\x06\xed\x18\x8f\xf0\x7c\xb8\xcd\x6c\x85\xfc\x98\x52\x48\
+\x94\xe1\x44\x48\x42\xcb\xa2\xcc\x14\xac\xc2\x5c\x46\x49\xf1\xd5\
+\x66\xa2\xd5\xda\xcd\x04\xca\x70\x4d\x50\x83\x7d\x12\x5d\x0d\x97\
+\x5c\x8b\x7f\x28\x6f\x8f\xf0\xc8\x30\x06\x57\xa6\xa7\x3d\xc5\x17\
+\x98\xaf\x75\x03\x3c\xd0\xf3\x8c\xe5\xe9\x6a\x6d\x0f\x26\x83\x5d\
+\x69\xa5\x66\x83\x1d\x4b\x6f\x6e\x39\x52\xbb\x85\x03\xdd\x87\x4d\
+\x3a\xe2\x6e\x2d\x02\xc3\x0c\x6e\xc4\xa7\xdd\x87\x1f\xc3\x03\x8c\
+\x6e\x4a\x8f\x74\x84\x16\xfa\x7a\x35\xdc\x36\x33\x8e\x7b\xb8\xf6\
+\x83\xf5\x2f\xe7\xa0\x7c\xa0\xd6\xf8\xfe\x5d\x78\xb1\xb4\xaf\x98\
+\x6b\xaf\x27\x9f\x9c\xf8\xfa\xa6\x2f\xca\x24\xcf\xfa\xed\x94\xc1\
+\x17\x4c\xbd\x78\x02\xb2\x01\xf9\xc4\x05\x1e\x1c\x6a\x49\x66\x9d\
+\xfd\x83\xdc\xac\xb1\x2b\x30\x68\x3f\x7d\xf8\x28\x84\xa4\x3b\x5f\
+\x28\x61\x8f\x0d\xee\x95\x76\x39\xd4\x26\xa3\x38\xd4\xc6\xe9\xdf\
+\x54\x65\x99\xbb\x15\xdc\x58\x5e\x31\x8f\xf1\x0b\xa7\xd4\x35\x35\
+\x19\xf3\xc6\xb4\xd5\xd5\x20\xb4\xc5\x90\x66\xbb\x32\x4b\x16\x87\
+\x7e\x95\x79\x8c\x0a\xf5\x8c\xb0\x80\x91\x04\x26\xbf\xa4\x41\x65\
+\x7e\xa0\x7b\x46\x53\x98\xb9\x2b\x0d\x61\x3c\x60\xab\x1f\xd5\x63\
+\x58\x2b\x73\x73\xcb\x91\xc5\x69\x21\x0c\xa4\x0f\xa4\x0c\x1e\xe2\
+\x14\x84\x0a\x0e\x81\xa6\xe0\x19\x0d\xef\xc7\xc3\x34\x1d\x4a\xac\
+\x86\x23\xba\xd5\x32\x85\x23\xe7\x2e\xc1\x26\xc4\x59\xd5\x7a\x72\
+\xd3\xaf\x70\xb9\x5a\x57\xb1\xd0\x53\xaf\x7f\x6b\xe4\x6b\x9b\x3e\
+\x0c\x4c\xbc\x49\x83\xfe\xc4\xc3\x42\x5e\xac\x2f\xc4\xcf\x8b\x27\
+\x20\x4c\x1d\x4f\x7c\x71\x3c\xf2\x7d\x34\x99\x17\x2d\xfa\x21\x58\
+\xe3\x8d\xb8\x5f\x64\x11\x6e\x66\x9c\xe0\xcc\x56\x89\x33\x5b\x10\
+\x12\xde\xba\x31\xc6\xed\x28\x4d\x42\x62\xec\xa9\xad\x06\x59\x86\
+\xca\x8b\x86\x58\x0c\x23\x40\x15\x43\x9a\x14\x73\xa3\xee\xcd\xa2\
+\x19\x5c\x50\x53\x37\xf5\xa9\x76\x72\x47\x50\x91\x7f\x81\xa5\xf6\
+\x14\x2d\x85\x11\x45\xe1\x0a\x63\x8d\x6b\x2a\xc6\x57\xb8\x0a\x84\
+\xf9\x31\xf7\x54\x57\xc1\x89\x71\xa5\x06\x07\x4d\xd3\x99\x36\x33\
+\x9b\x1e\x98\xd9\xdc\x28\x74\x5e\xf0\x62\xb7\xb8\xbb\x64\x4c\x2f\
+\xee\x91\x40\xa8\x3d\x12\x18\x4f\x47\xc2\x12\x33\xdc\x10\x61\x0b\
+\xdb\x04\x43\xd2\x00\xa2\x75\x08\x47\x0d\x35\xe1\x3e\x08\xc7\x05\
+\x18\x90\xcf\xc7\x76\xef\x9f\xdc\x39\x5e\x1d\xc5\x76\xbd\x5e\x57\
+\xbf\x75\x68\x73\xfe\xed\x6e\x0f\xc6\xad\xac\x4c\x7f\xf0\xfc\xad\
+\x77\x58\x19\x34\xeb\x2f\xae\x80\x30\x36\x36\x68\xff\xea\x7d\x43\
+\xee\xc2\x05\xb7\x82\x77\xaf\xc6\x2d\xf1\xfd\x95\xe1\xda\x44\x57\
+\x8f\x4e\xff\x76\x4c\x57\x48\x42\xea\x94\xe9\xe2\x5f\x75\x52\xa6\
+\x0c\x68\x60\x90\x94\x3d\x63\xec\x56\x78\x29\xa6\x0a\x14\xec\xc6\
+\xa9\x42\x30\xf2\x23\x70\x65\xcc\x20\x7c\x1e\x87\x0c\x12\x60\xc2\
+\x38\x74\x20\xad\x14\x5f\x5b\x94\x94\xbc\xf9\x21\x93\x11\xd7\x98\
+\x99\x3e\x19\x94\x7d\x62\x27\xcd\xf8\x8b\xdc\x0d\x2f\xb4\x00\x86\
+\xe7\x71\x4c\x08\x52\x3c\x8d\x53\x60\x6e\x09\x3b\x0d\x4f\xf0\x48\
+\x83\x70\x7c\xf1\x3a\x48\xba\xae\x62\xf1\xf5\xfe\x3c\x3e\x23\x5d\
+\xc7\x1d\x66\x75\x08\x07\x5b\x0e\x4e\xe7\x2e\xc5\xe3\x3f\xff\x71\
+\x77\x65\x62\x4f\xad\x50\xee\xcd\xd7\x37\x0f\x0e\x4e\xbc\xd9\xdd\
+\x7a\xff\x48\xa8\x44\x99\xc0\x17\x58\xbd\xf8\x02\xc2\x04\x07\x21\
+\xd9\x74\xc0\x5d\xb4\xe4\x36\xdc\x53\xf5\xb6\x6a\x35\xdf\x3d\x31\
+\x5a\x9b\xe8\xa4\x90\x94\x71\x3f\x06\x84\x84\x8d\xab\x75\xb7\xc8\
+\x2a\x2c\xa0\xc0\x6c\xde\x4c\x38\x55\x96\xf5\x01\xf5\x03\x11\x75\
+\x9f\xfc\x9b\xfa\x31\x0a\x0a\x69\xa6\xd4\x1c\x9e\xd8\x35\x22\x3e\
+\x42\xf4\xaf\x34\x0c\xac\xf6\xc9\x30\x63\x78\x86\x94\xe2\xd2\xac\
+\x34\x82\xae\x5e\x05\xc7\xf0\x54\x08\x52\x3c\xe2\x06\x7c\x6f\x16\
+\x18\x05\xaf\xf9\xa3\xbb\x17\x48\xf3\x17\xb7\x1c\x84\x65\x98\xdd\
+\xc7\x4f\x04\x08\x11\x30\x3f\x81\xbe\x77\x0f\x82\x97\xa1\x0f\x7c\
+\xf8\x51\xe1\xb3\xb8\xe0\x8a\x51\xdc\x22\xd8\x80\x70\x1c\x80\x70\
+\xe0\xd8\xac\x5b\x8e\x8d\x55\x77\xdc\x53\x99\x78\xaa\x92\x2f\xf7\
+\x16\x1b\x4f\x0c\x8e\x14\xdf\xe8\xbe\xb7\x79\x9f\x08\xc7\x0c\xdf\
+\xf4\x60\x7e\x3e\x57\xea\xe4\x10\x10\xa6\x26\x08\xc9\xc6\x67\xdc\
+\xea\x05\xbf\x02\x83\xbf\x15\xcf\x29\x77\xe2\x19\xdf\x89\xce\x6e\
+\xdf\x92\xe0\xa8\x2e\x9e\xbb\x76\x23\xbc\x68\x03\xdd\x2d\xb2\x87\
+\x32\x8c\x72\x90\xca\x80\xe7\x26\xd2\x04\xc6\x24\x41\x11\xb8\xba\
+\x05\xa3\x50\xca\xfa\x33\x26\x57\x9c\xd8\xcd\xfc\xc6\x30\x9a\x63\
+\x7b\xca\xe4\x69\x18\xea\x8f\x4c\x65\xb8\xc6\xec\xb4\x1b\x5c\x75\
+\x65\x2a\x26\x8e\xcc\x25\xad\x0b\x18\x5a\xf0\x84\xd9\x49\x8b\x61\
+\xa8\x12\x14\x6f\x37\x33\x5d\xcc\x7c\x2c\x5d\x19\x37\x8b\x1b\xd7\
+\xfe\x19\xc1\xf1\x42\x65\x7e\x0c\x2f\xe0\x04\xa1\x03\x3d\x04\xaa\
+\xe1\xc6\xc2\x08\x18\x3c\x35\xf0\xd8\x51\x03\xb3\x30\x07\xbd\x70\
+\x2c\x83\x70\xdc\x7d\x6f\x65\xe2\xc9\x51\x08\x47\xa9\xb1\x63\xb0\
+\xda\x78\xa3\xfb\xf6\x7d\x4f\xbd\xd8\xc2\xc1\x3c\x3c\x79\x04\x84\
+\xb1\xa1\x90\x70\x6f\xcd\xe7\xee\xda\xee\x2e\x5a\x70\x27\xc6\x1c\
+\x6f\xc5\x1b\xf3\x1d\xe3\xa3\x75\x11\x92\x92\x6f\x49\xf8\xf8\xc8\
+\x08\xa7\x80\xa1\xdb\xde\x2d\x63\x30\x81\x19\x13\x92\xa7\x44\x05\
+\x03\x6c\x6a\x4e\x7f\x53\x93\xb9\x79\x4f\x82\x9b\xfa\xa4\x29\xb5\
+\x29\x4e\x33\xcc\xec\x64\x78\x53\x1e\x26\xcc\x63\x30\xea\xa9\x50\
+\xd0\x26\xcc\xa4\x1c\x05\x27\xf8\xe1\x87\xcb\xb6\x65\x9a\x5b\x6f\
+\xca\x83\x9c\x78\x38\xf1\x45\x50\x52\xe6\x33\x7a\xcc\x87\xf8\x9b\
+\x0a\x1e\x98\xba\x09\x5f\xe0\x31\xa3\x47\x71\xb3\xe8\x05\xfa\xcd\
+\x78\x40\x88\x5b\x0a\x0d\x83\x71\xd5\xf4\xc9\xe6\x4e\x1c\x04\x62\
+\xcb\x71\x08\xc2\xc1\x73\x1d\x8b\x51\xe3\xdd\x83\x96\x43\x85\x23\
+\xd9\x39\x58\x6b\xfc\x86\xfb\xe6\xfd\x8f\x9e\x0c\xc2\xc1\x72\x39\
+\xb9\x04\x84\x31\x92\x3d\x5b\x10\x92\xbf\xb9\x73\x1b\x06\xee\xbf\
+\x82\x00\x5c\x3d\x51\xcd\x77\x8d\x0d\xa2\xbb\x85\x96\x84\x03\x77\
+\x76\xb7\xda\x70\xb0\x9d\xdb\xe4\x59\x22\x36\x78\x67\x01\x92\x21\
+\x4c\x1d\xbb\xf5\x48\xf1\x62\x3f\x6a\x6e\x76\x3b\x96\xdd\xc2\x4c\
+\x71\xd2\x78\xa4\x30\x62\x05\x21\x16\xa6\xf4\xb1\x24\xf3\x80\xd1\
+\x44\x20\xf8\xa0\x23\x84\x41\x6a\x59\x9c\x2b\x6d\x60\x22\xa7\x81\
+\x87\x2c\xeb\xe3\xb8\x7c\x6d\x02\x76\x4c\x81\xf2\x31\x53\x89\x23\
+\x85\x06\xab\xab\x22\x40\xf0\x2f\x60\xd2\x92\x70\x4e\x4c\x17\xe6\
+\x8e\x68\xc4\x42\x94\x5d\x55\xf7\xf4\x03\xae\x75\xc9\x4c\x38\x99\
+\x26\x8b\x83\x17\x62\xda\xb1\xc0\xd5\xc0\xa6\x3b\x2c\x79\xb9\xf3\
+\xb1\x75\x7d\x5e\xa3\x9e\xdc\x7e\xcf\x78\x75\xfb\xb8\x6f\x39\x26\
+\x72\x6f\x70\xdf\xdc\xfc\xf0\xc9\x22\x1c\x2c\xa1\x6c\x09\x12\x72\
+\x72\xa8\x1c\x5a\x92\x82\xbc\x08\x74\xdd\x9a\x97\x61\xf7\xef\x3f\
+\xe3\x34\xe2\xfc\x72\x21\x99\x58\x72\x5a\xb9\xd4\xd1\x5d\xc0\x93\
+\x74\xb8\xfe\x05\xab\xed\x3b\xf7\x83\x69\x90\xe1\x7c\xe3\x43\xba\
+\x23\x88\x3f\x13\xc5\x0b\xdc\xc8\x43\x19\xb3\xb7\xcb\x55\xa3\x82\
+\xe3\xf1\x82\x99\xf8\x60\xe3\x80\xa7\xee\x4a\x23\xa5\x75\x6c\x3b\
+\x88\x45\x34\x8c\x16\xa1\x72\xa9\x9c\xe8\xb4\xc1\x0e\xa6\xa1\x70\
+\xf3\xd1\x21\x1e\x48\xaf\x0f\xe2\x31\xec\x43\xb8\x2f\x18\x1c\x54\
+\x1f\xc2\xbd\x53\x10\x0a\xee\x49\xe7\x0d\x30\xa4\x83\x1b\xb9\xf1\
+\xb8\x28\xee\x81\xc2\x03\x3f\xc5\x2e\x7c\xb3\x8a\xae\x84\xc7\x44\
+\x4b\xd0\x73\x78\xee\x8e\xf7\xeb\x12\x4d\x7f\x68\x98\xbe\x42\x48\
+\xca\xd1\xde\x8b\xd8\xcd\x1c\x59\xd4\x48\xa6\x57\x47\x73\xca\xd8\
+\xa5\x65\xf1\xe4\x80\x40\xc1\xe3\x6e\xd4\x2a\xba\x56\xc3\xb0\x5c\
+\x88\xa9\xdc\xae\xd1\x5a\x72\xeb\xc6\x89\xda\x51\x3c\xa2\xdd\x93\
+\x6b\x3c\x3a\x54\xcf\x5d\xe5\xbe\xb1\x71\xeb\xc9\x24\x1c\x4c\xa1\
+\xe4\xa7\x26\xf5\xa4\xfb\x4d\x85\xe4\x7d\x17\x9f\x97\x6b\x34\xfe\
+\x09\x6f\x8f\xac\xcc\x27\xf5\xea\xb2\x15\xe5\x02\xae\xee\x67\x5d\
+\x8b\x47\x5a\x9c\xdb\x8b\x7b\x7f\x65\xf0\xee\xdb\x43\x16\x1a\x13\
+\x26\x0c\x09\x03\x78\x50\x99\x9e\x42\x13\x99\x15\xc7\xdc\x53\xc1\
+\x50\xa6\xf6\x02\x06\xa4\x40\xcb\xfc\x1a\xed\x16\x76\x22\x1b\x5d\
+\xd1\xbd\xb0\x00\x55\xe3\x40\x03\x61\xb4\x83\xa9\xb1\x1f\x4d\x84\
+\xa2\xba\x7d\xd4\x55\x77\xe2\x02\xb6\x01\xd4\xdb\xe4\x36\x0a\x0d\
+\xff\x95\x88\x70\x9b\xdc\xac\x4e\x6e\xc3\x27\x0c\x49\x81\xc1\x1b\
+\xe9\x6d\x4b\xcb\xae\xf3\xac\x4e\x57\xc6\x79\x54\xc1\x81\x77\xf1\
+\xc7\xa0\xa6\xa1\x8c\xb9\x63\x54\x2f\x6a\x02\x8a\xdd\x99\xb7\x54\
+\x06\x53\x7b\x56\x60\x82\x1b\x1c\xd9\x2d\xcc\x77\x60\x85\x1c\x47\
+\x46\x71\xac\x44\x5a\x8e\xdc\xe1\x6a\xe3\x9f\x37\xe1\xa5\xd5\xb6\
+\x42\xb1\xc7\xd5\xef\xc6\x3d\x69\x57\xbb\x9b\x36\xed\x3e\xd9\x84\
+\x83\xe9\x64\xf6\x9f\xdc\x8a\xeb\x24\x9c\xc5\xb8\xfe\x92\x65\x78\
+\x02\xe6\xfb\xbc\x42\xc8\x8d\xd7\xeb\x4b\x96\x97\xf0\xf0\x0b\x2e\
+\xc3\x04\xc7\x57\xb1\x45\xfe\x30\xae\x34\x3d\xc4\x27\x2d\x58\x23\
+\xe3\xb3\x42\xd2\x56\xc4\x33\x3f\x52\x2b\x97\x67\x42\x57\x21\x80\
+\x1d\x42\xc3\x4c\x10\x7b\x64\x56\x41\xf2\xfe\xcc\xbd\x45\xcb\x10\
+\xb7\x0a\x92\x99\xa4\x1d\xe3\x31\x77\x63\x18\x39\x8a\x71\x04\x73\
+\x27\x83\x78\x31\xeb\xc9\x61\x57\x7d\x14\x52\xce\xda\x1f\x8b\xa3\
+\x0e\x42\x43\xe1\x48\x78\xcd\x27\x23\x25\xd5\x00\x89\x68\x1c\x99\
+\xb0\x1c\x5a\x95\x1c\x1e\xb2\xc9\xe1\xad\x78\xde\x0c\x53\xc7\x31\
+\x01\xd2\xeb\x59\x37\xcb\xb5\x9f\x82\xfb\x70\x79\x92\xc9\xf0\xc5\
+\xd4\xfa\xc7\xf2\xa8\xd9\x55\x7d\x2b\x34\xc6\x09\x70\xdf\x42\x10\
+\x43\x60\xf8\xa1\xae\xee\x2a\x2c\x22\xa8\xb8\x4a\xb3\xd0\x5e\x76\
+\x78\x89\x19\x37\xec\xe3\x26\x12\xcc\x56\x8d\xec\x9e\xa8\xfd\x78\
+\x0b\x6e\x5e\xe8\x29\xe5\x7b\x1a\xb5\x1f\x0c\x55\xca\xef\x74\xdf\
+\xbe\x6b\xf0\x64\x14\x0e\xa6\x0f\x25\xf0\x12\x50\x26\x24\x37\x20\
+\x8b\x93\xae\x6f\x24\xa5\xfc\xd5\x6e\xb4\xe6\xe6\x2d\x2a\xd5\xfa\
+\x17\xe1\xf5\x74\x1c\xdb\x65\x37\x6b\x10\x02\xb2\x9b\x77\xff\x82\
+\x69\x8e\xdd\xe5\x22\xb3\x45\xad\x89\x31\x34\x72\x83\x19\x92\x15\
+\x1a\xc2\xa6\x2f\x28\x24\x40\x1a\xf2\x79\x81\x4b\x61\xa0\x03\x46\
+\x26\x27\x35\x76\x8d\xb8\xea\x66\x5c\xff\x7f\xa4\x86\x87\xbc\xd1\
+\xf4\x51\xa8\x21\x10\x0d\x30\x55\x1d\x57\xbe\x34\x30\xbe\x68\x40\
+\x48\x44\xa7\x59\x24\x5d\x89\x73\x6c\xc5\xfb\x82\x8b\x10\x90\xf2\
+\x50\xc5\x15\x47\xd0\xf2\xa0\x92\xa0\x60\xcd\x7a\xc3\x6c\x57\xc4\
+\xa3\x38\xf2\x9c\x01\x23\xe1\x95\x19\x95\x89\x0d\x3a\x59\x4f\xdd\
+\x35\x9e\xa9\xdd\x04\x00\xba\x07\x9a\x9b\x8c\xa1\x40\x4a\xec\xf8\
+\xa1\x7b\x0e\x83\x44\x9e\xa7\x1e\x44\x6b\xb7\x14\xcf\xa2\x2d\x2b\
+\x25\x6e\xcf\xd6\x09\x6c\x3c\xc4\x5b\x69\xb3\x0a\xae\xb3\x5a\xff\
+\x87\xd1\xaf\x6f\xfa\x7f\xc4\x9b\x95\xef\xe4\xe8\xbc\xe8\x90\x93\
+\x6f\x90\xde\x2a\x4b\x6c\x0a\xf8\x2b\x5b\xc6\x70\xa5\xe9\x77\xf3\
+\x17\x2e\xec\x82\x90\x5c\x31\x3a\xd8\xc8\xe3\xc9\xb0\x6a\x7b\x67\
+\x3e\x5f\x6a\xcb\xe7\xda\x30\x0d\xdc\x83\x01\x7c\x15\x57\x16\x63\
+\x8a\x58\xb8\x94\x95\x30\x0b\x0e\xac\xa9\x80\x40\xdf\x58\x86\x80\
+\x56\x6e\x33\x71\x57\x1a\x1a\x46\x08\x20\xd0\x15\x06\x62\x24\xd8\
+\x2a\x70\xc0\xfd\xd0\x80\xab\xdd\x33\xa8\xad\x16\x5f\xf2\x31\x85\
+\xc8\x32\xd4\x02\xce\xb4\x92\xf9\x8b\x78\x69\xa6\x38\x81\xa3\x43\
+\x6c\x2d\xf0\xa7\xc2\x42\x01\xca\xbb\x5a\xb9\xe8\x2a\x1d\x25\x57\
+\xe9\xeb\x80\x5c\x60\xef\xd2\x38\xee\xcd\xc5\xdb\xee\x39\x30\x5f\
+\x71\x1e\xee\xe4\x24\xa3\xc2\xcf\x4c\xbe\x78\x50\x4e\xff\xec\xcd\
+\x69\xde\x45\xe6\x66\xba\xd1\x78\x83\xe3\x42\x74\x83\x5d\xa1\x13\
+\x0f\x73\x96\x70\xe0\x09\xb8\x67\x61\xa6\x6a\x1e\x5e\x55\x7d\xe4\
+\xc1\xf1\xea\x7d\x7b\x31\x4c\x87\xb0\x14\xeb\xf5\x8f\x56\xbe\xbe\
+\xf9\x13\x92\x6c\xce\x5a\xbe\x80\x9b\x0f\x2d\xab\xa7\xab\x47\xa5\
+\x33\x5d\x2f\x2f\x12\x9e\x4d\x01\xe3\xe6\x8a\x64\xd3\x9e\x9f\x62\
+\x86\x6b\x07\x5a\x81\x2b\xc7\xab\xb9\xf6\xa1\x23\xf5\x1a\x2a\xab\
+\x5c\xb9\x1d\x77\x6e\x61\x93\x23\xde\xe5\x94\x9e\xca\x88\xbe\x7a\
+\x1c\xba\x53\x2c\x6c\x15\x86\xf8\x37\x2b\x08\xca\xa2\x96\xc6\xe9\
+\x0a\x8e\xd2\x10\xfa\x60\x98\x58\xe0\x34\x4c\x80\xd0\x0a\x24\xc3\
+\xb8\xf1\x7c\xe3\x11\x97\x3c\x36\x2a\x8c\x8c\xb7\xe3\xc0\x79\xc0\
+\x20\x27\xb2\x8b\xc5\x37\xd8\xc7\x70\x43\x2b\x84\x48\xbf\xba\xcb\
+\xa3\xab\x5e\xc4\xbb\x69\x25\xbc\x16\x5b\xae\xd4\x5c\x01\xc2\xc2\
+\x5b\xeb\xe5\xf2\x6f\x04\x55\x85\xa0\xe0\x22\x0c\xd7\x71\x04\xdd\
+\x34\xbc\x02\x95\x9b\x5b\x72\x85\xf9\xb8\xee\x1c\x64\x19\xf6\x74\
+\xbf\x58\x18\x54\x38\x52\xe1\x0a\x82\x83\x70\xd5\x1c\x09\x0c\xc3\
+\x80\x67\x26\x23\x8f\xe7\xb8\x29\x1c\xa3\xe8\xe3\xf2\x05\xa9\xb3\
+\xd1\xa5\x2a\x1c\xa9\xd5\x6f\xbb\x7f\xa2\xb1\x6b\xbc\x50\xea\x2c\
+\x35\xf6\x57\x93\xdc\x3b\xaa\x5f\xdf\x74\x23\xbc\x39\xd9\xd1\xfd\
+\xa5\x1f\xa0\xbd\x3f\x79\x95\x96\xec\xc9\x1b\xbf\xc9\x31\xe3\x36\
+\x79\x7d\xb1\x34\x71\xd7\x5d\x74\x09\x9e\xfd\xfa\x2a\x2e\xa5\x3b\
+\xdf\x55\x6a\xc9\xfc\x85\xa5\x5a\xdf\xc2\x22\x5e\x16\x46\xe1\xa2\
+\xd0\xc6\xb0\x56\x72\x00\x5d\xae\xb0\xb0\xc8\x6e\x0c\x39\x06\x2a\
+\x33\x36\x81\x7d\xd2\x80\x9e\x75\x2f\x72\x47\xd8\x9d\xba\x98\x9b\
+\x60\x11\x0e\x69\xb6\xc2\x21\x87\xca\x60\x7c\x70\xdc\xe5\xee\x41\
+\x64\x0e\xa3\x79\x63\x97\x8a\x2f\x3a\x8d\x21\x92\x1c\x2f\x94\x11\
+\xb1\x2e\xc0\xf8\x26\x3b\xcd\xec\x86\x91\xe3\x30\xc5\x9b\x8c\xa0\
+\xef\x38\x00\x1e\x62\xc4\x31\x83\xc5\x59\x2c\xa4\xd7\x35\xf0\xc4\
+\x72\x1d\xdd\x98\x3a\x6a\x86\x3c\x04\xa8\x3c\x30\xe6\x92\xa3\x35\
+\x57\xba\xb8\xd7\xb5\xad\xea\xd5\x19\x2d\x46\xfe\x38\x0a\x54\xb3\
+\x0a\x80\x66\x58\xb0\x07\x37\x0a\x0f\x14\xed\x8c\x17\x5a\xc6\x72\
+\x07\xf6\x54\xa1\x7b\x88\x28\xbb\xa5\x78\x4c\x73\x3e\xd6\x37\x0e\
+\xee\xa8\x56\xff\xe3\xa9\x5a\x09\x9b\x0e\x5d\x77\x52\xff\xf7\xe1\
+\x5a\xfe\x06\x99\xa9\x5a\xcf\x0e\x25\xd4\x7a\xa9\x1a\xc4\x78\xb2\
+\xfe\x4c\x23\x0b\x4f\xc2\xa8\xb3\xdf\xf0\x6a\x4c\x03\x6f\xc0\x79\
+\xe4\x77\x5f\xd6\x9b\x2f\x57\xff\xa6\x51\xc8\x5d\x8f\xbb\x29\x1d\
+\x1e\x0d\x9d\x98\xbf\xa4\x54\xa3\xb3\x91\x09\x00\x00\x1a\x85\x49\
+\x44\x41\x54\xea\xe8\x45\xc7\x03\xa9\xab\xa1\xab\x35\x84\xe7\xdf\
+\xf6\xf1\xf1\x17\xf0\x1a\x57\x7e\x98\x68\x16\x30\xf5\x78\x90\x3d\
+\x59\x68\x88\xe3\x85\x02\xc8\x8a\x1f\xc1\x22\x1a\x30\x66\x05\x4a\
+\xdc\xc8\x3c\xbc\x9e\x7f\xc2\x15\xee\xc0\x54\x1b\x6a\x78\xd7\x0e\
+\xde\xc0\x14\xae\x08\x01\xaf\xea\x40\x6d\x9f\xeb\xc7\x01\x98\x4e\
+\x3c\x76\x89\x16\x45\xc6\x28\x12\x16\xfc\x02\x1d\x13\x12\xae\x71\
+\x04\xdd\xa7\xfd\x15\x97\xec\x43\x62\x4c\x58\x98\x0e\x7c\x12\x29\
+\x44\x1c\xcf\x6f\xbb\x04\xf4\xdb\xde\x30\xcf\x15\x16\xa0\x8b\x45\
+\x01\x64\x84\x3d\x8a\x9a\xf4\x97\x69\x9f\xa4\x3c\x30\x75\x53\xcf\
+\xc1\x0e\x43\x30\xc3\x33\xd7\x63\x98\xc1\x05\xbc\xc2\x59\x44\x73\
+\x81\x36\x51\xde\x09\x5c\x0e\xe1\x28\xe1\x46\x85\x47\x1f\xab\xd6\
+\x9f\xc4\x33\x7c\x78\xa4\xca\xb5\x27\xee\x33\x63\x43\x2b\x3f\x8e\
+\x8a\xad\xee\xd8\xa5\x5a\xbf\x81\xad\x46\x4c\x6e\x52\x74\x4e\x16\
+\x80\xcf\xc2\x93\x25\x3a\x33\x8c\x47\x3c\xb8\xbb\xfe\xa2\xf7\xa2\
+\xc0\xfe\x0a\x8f\xf6\xcc\xc5\x93\xba\xc9\x82\x85\xc5\xfa\xac\x79\
+\xc5\x62\xb1\x0d\x5d\x1b\xd4\x72\x13\xe8\x81\x1c\x81\x90\x1c\xe6\
+\x49\x45\x32\x9e\x17\x14\x0b\x51\x06\xed\xc2\x98\xc6\xe8\x53\xb4\
+\x16\x86\x03\x8f\x26\x5c\x64\x44\x66\x64\x73\x0b\x42\xda\x64\xf8\
+\xfc\x08\x6e\xaa\xd9\x78\xd0\xe5\xf7\x82\xc9\x71\x5c\x32\x59\x86\
+\x99\xa6\x65\x5d\xce\xf5\x95\xb5\xc5\x00\x83\xcb\xd4\x2e\x58\xc6\
+\xd7\xcd\x42\x4f\xe8\x52\x6a\xa9\x78\xd7\x0e\x5a\x93\xe4\x28\x84\
+\xe5\x30\xbe\x23\x68\x89\x86\xc0\x67\xe8\x82\x09\xab\x61\x01\xb5\
+\xb0\xaa\xc7\x15\x56\xe0\xe2\x63\x7a\x21\xfb\x79\xaf\xf4\x7e\x4c\
+\xe5\x59\xb5\x99\x63\x83\xdd\xd3\x62\x63\xc1\x8f\xaa\x80\x6e\x5d\
+\x11\x69\xe1\x44\x02\x5f\xa9\x59\x0c\xc1\x98\x8d\x66\x6f\xe0\x99\
+\x6a\xfd\x97\xdb\x70\xbb\x34\x5a\xb7\xce\x5c\xfd\xa9\xf1\x7a\xee\
+\xc3\xf5\x1b\xef\xbb\x45\x3c\xc5\xe5\x25\x80\x93\xff\x67\xba\x59\
+\x78\xf2\xa6\x64\x1d\x6a\x24\xb6\x24\x54\xd7\xae\x39\x1d\x8f\x63\
+\x7f\x16\x53\xa4\x6f\xe6\x0d\xc7\x1d\x1d\xae\x3a\x6f\x71\x29\xdf\
+\x89\xad\xf3\x7c\x6d\x97\xf7\x00\xb3\xdb\x75\x18\xb3\x5d\xb2\x0a\
+\x4f\x3f\xa8\xd0\x43\x26\xc0\x00\x71\x6a\x6a\x09\x8e\x67\x07\x8d\
+\x68\x7a\x98\xb4\xe4\x03\x8c\x4a\x68\xa3\x65\x28\x1e\xae\xb8\xc2\
+\xd6\x41\xd7\x00\x03\x37\xe6\xb4\x89\xe0\x08\x0a\x6b\x62\xe1\x66\
+\xe0\x0a\xb2\xf7\x03\xb3\x58\x41\x46\xe0\x8c\xa7\x09\x0b\xbb\x5f\
+\x14\x0c\xb4\x2e\x0e\xe3\x16\x09\x07\xcc\x9a\x43\x57\x46\x38\xd8\
+\x33\xb1\x3a\xc8\x6f\xf6\x27\x76\xf7\x2e\x2d\x40\xcc\x09\x75\x85\
+\xa3\x08\x07\x20\x3c\x1b\x8d\x03\x6d\x32\xdb\x86\xdd\x22\x78\x42\
+\x3b\xe7\x16\x62\x86\xca\xe1\x79\x8b\x27\xb6\x56\x93\x6d\xc3\xae\
+\xc8\xcb\x72\x31\xdd\xf8\x8d\xa1\x89\xfa\xc7\xdc\xb7\x1e\xd8\x2f\
+\x84\x78\x99\x39\x5b\x90\x97\x98\xf2\x39\xf0\x12\x8b\x75\x73\x74\
+\xd9\xa7\xdd\xb0\x0e\x9f\x0a\x4a\xe1\x86\x35\xbf\x0b\x1e\xfa\x24\
+\x04\x65\x21\xdf\x1c\x9e\x3d\xa7\x30\xd1\xb7\xa0\x58\x6a\xeb\xc4\
+\x93\x9d\x48\x31\x2f\xac\xe3\x2a\xfc\x61\x74\xbd\x64\x7c\x42\xee\
+\x80\x00\x91\xff\xc8\x08\xd6\x32\x68\x8b\xa0\x0c\x4a\x26\x9e\xca\
+\x4e\x3e\x62\x46\x06\x7f\x8c\x5f\x80\xd1\x48\xa2\xf8\x67\x38\x7e\
+\x71\x90\x2d\x06\xfd\x08\x5c\x0c\x62\x54\x3b\xe1\x50\xe6\x8e\x58\
+\xa9\x5f\x0f\x14\x81\xc1\x8f\xea\x82\x4a\x14\xd4\x00\xfc\xf1\xfe\
+\xc4\x34\xbd\x1f\xf5\x35\x19\x97\x70\x8e\xe5\xa8\x17\x10\xef\xb6\
+\x76\x64\x12\xc6\x48\x55\x04\xdc\x01\xfb\x7c\x08\x46\xfb\x78\x23\
+\x39\xb0\xab\x5a\xdd\xb8\xbb\x51\x76\x70\x2f\x37\xea\x5b\xcb\x49\
+\xf2\xf1\xe1\x1b\x37\xdf\x2c\x14\x5f\x62\x5d\xaa\xe6\x5c\xf0\x45\
+\xd3\x0c\x7e\x89\xda\xe3\xd6\xe4\xb7\xd7\x2e\xcf\x17\x93\x4f\xe2\
+\x11\xce\xeb\xd8\x2e\x80\x49\xeb\x78\x95\x2b\xe9\x99\x5b\x2c\x94\
+\xdb\x85\xb5\x5c\x1d\x7d\x83\x51\xb4\x28\x47\x21\x28\x43\xe8\x82\
+\x09\x27\x48\x4d\x6d\x4c\xa6\x4c\x4c\x6c\x66\x94\xea\x59\x41\x61\
+\x4e\x05\xc1\x30\x3c\x01\x66\xfd\x2a\x1e\x70\x45\x02\x95\x96\xc0\
+\xe8\xdf\x1b\x44\xa7\x30\xa8\x81\x50\x75\xa3\x3e\x09\x96\xb2\x75\
+\x70\x0a\x06\xf1\x2a\x3f\x2d\x40\x02\x4f\x7d\xa7\xb8\x66\x32\xc1\
+\xa0\x5d\x5a\x0c\x8c\x9b\x0a\xe8\xaa\x4e\x20\x12\x6d\xe8\x32\x62\
+\xa2\xcc\x75\x62\xea\x76\x64\x5f\xad\xbe\xf9\xe9\x5a\x61\x84\x52\
+\x9f\xaf\xd7\x70\x69\xff\x17\xc7\x6a\xc5\xbf\x70\xdf\xbc\xfb\x90\
+\xd0\xd2\x09\x95\x97\x5c\xab\x61\xf9\x40\x7d\xaa\xfc\x8b\x71\x5e\
+\x5a\x66\xb6\x26\x7c\xdd\xd4\xce\x10\xbc\xf7\xc2\x2b\x73\x85\xfc\
+\x7f\xc5\xfc\xfc\xe5\xec\x76\xe1\xd6\x94\xfa\xdc\x79\xc5\xa4\x7b\
+\x36\x37\x3e\x4a\x9b\x21\x82\x82\xf7\x4b\xdc\x20\x04\xe5\xa8\xdf\
+\x00\x09\x2a\x2a\x10\xc8\xa1\xb8\x0b\xc4\x0c\x8b\x05\x85\x99\xa3\
+\x76\xc5\x33\x46\x4e\x61\x44\xf0\xc2\x22\xc8\xc4\xd3\x8f\x86\x98\
+\xb6\x77\xa6\x16\x04\x82\xb8\x2a\xb9\x29\x8c\x10\x85\xd3\xa4\xca\
+\xc2\x35\xfb\x89\xe8\xd2\x8d\x82\x74\x90\x56\x09\x5d\xa7\x22\x5a\
+\x84\x02\x66\x04\xeb\xc8\x26\x6e\xec\xc1\xb6\x2f\xd7\x5d\x6f\x24\
+\x63\x78\x8f\xfc\xa9\x67\x6a\xf9\x67\x2a\x58\xf4\x40\x77\xaa\xcb\
+\x35\x7e\x34\x51\xcf\x7d\xb2\x7a\xe3\x7d\x77\x4a\xb8\x3a\xd6\x60\
+\xb7\xf7\x58\x72\x78\x22\x51\x7c\xc1\xfd\x34\xe7\xf3\x0b\x1e\x81\
+\xe7\x2d\x40\x6d\xda\x75\x6c\x82\x96\xa5\x70\xda\xe0\xf5\x78\x58\
+\xf2\x63\xd8\xf4\x78\x3a\xa7\x56\xb1\x77\xae\x01\x41\xa9\x77\xf6\
+\xe3\x3a\x2e\x5c\xa2\xcd\x8c\xe0\xe3\xa2\xe3\x58\x3b\x19\x46\xf7\
+\x8b\x77\x73\x71\xc1\x31\x6e\x55\x20\x33\x60\x9e\xb4\xcc\x55\x08\
+\xa2\x16\x45\xdc\xf1\x23\xfa\x54\x42\x31\x19\xae\xf8\xe2\x6d\x92\
+\xc0\x88\x9b\x3a\x05\xa1\xf1\x56\x2f\x24\xd9\xf8\x98\xdb\x74\x75\
+\x13\x0a\xe2\x17\xd0\x3a\x94\x21\x10\x9c\xd8\xe0\x8c\x1a\x1f\xb9\
+\x2d\x03\x86\xb5\x47\xb4\x18\x8d\xa4\xf2\xff\xb7\x77\xe6\xbf\x71\
+\x5d\xd7\x1d\x7f\xcb\x6c\x1c\x92\xe2\x90\x23\xda\x94\x22\xc9\x5b\
+\x64\x43\x52\x8b\xb8\xb6\x8a\x1a\x46\x6d\xcb\x51\x80\xa2\x06\x0a\
+\x14\x2d\x64\xb4\xbf\xb5\x01\x9a\xa2\x05\xfc\x4b\xff\x01\x51\x40\
+\xd1\xfe\xde\xdf\x6a\x37\xed\x2f\x45\x80\xca\xfd\x35\x2d\x5c\x27\
+\x90\xe4\x38\x40\x12\x28\x9b\x01\xc9\xae\x52\xc7\xd6\x6e\x89\x8b\
+\xb8\x93\x33\xf3\x66\x5e\x3f\xdf\x73\xdf\x1b\x3e\xce\x70\xb8\x44\
+\x5e\x24\x8b\x97\x7c\xf3\xee\xbe\x9c\x7b\xce\xb9\xe7\xde\x7b\xee\
+\x7d\xd3\x51\x74\xf5\x7a\x94\xbb\xb2\x4c\xcb\x21\xa0\xa2\xd7\xfa\
+\x65\x3e\xf6\xff\x71\xe1\xdf\x7e\xf6\x1f\x56\x5e\x87\xa8\xbb\xd5\
+\x3a\xdc\xcb\xf1\xbe\xbc\x04\x22\xa8\x0b\x17\x4f\xb1\x1c\x3c\x9e\
+\x4c\xe2\xff\xe2\xe9\x0a\x8c\xf0\x6f\xd8\xec\xfa\x5b\xce\x5a\xec\
+\x13\xa1\x70\x3c\xa1\x59\xad\x86\xad\x7e\x08\x45\x1b\x8d\x36\x99\
+\x47\xee\xd6\xf2\xf0\x32\xa3\x89\x3e\x38\x8a\x36\x87\x5b\x22\x56\
+\x9e\x20\x8d\x1d\xd3\x48\x08\x25\x05\x60\x96\x58\x14\xcd\xb9\x13\
+\x4e\xbf\xce\x08\xb2\x1a\xc7\x21\xb8\xe5\xc3\x4f\x9a\x5f\x1a\x6e\
+\xef\x0c\x23\x56\xbe\xa9\xc9\x58\x53\x2f\xcb\x60\x5d\xff\xd5\x18\
+\x6e\xc2\x6d\xc5\xa2\x6e\x03\x40\x34\x5a\x14\x20\x0a\x8d\x16\x31\
+\x04\x21\x5d\x36\x2e\x96\xf1\xfa\xa9\x77\xb1\xc1\x37\x6c\xef\x34\
+\x1b\x57\xf8\xee\xd9\xf5\x1a\x2d\x67\x1d\x80\xed\xc2\x4b\xfd\x81\
+\xff\x4f\x0b\xd1\xae\x6f\x73\xab\xba\xb6\x63\xbd\xfb\x6d\xf9\x36\
+\x03\x8e\x0d\xad\x9b\xc1\x72\xc3\xc4\xf7\x4b\xe0\x09\x64\xe1\x89\
+\x23\x47\xfc\xb3\xe3\xe3\x36\xa2\x1c\xfa\xd6\x0b\x7b\x6e\x36\x57\
+\xfe\xaa\x11\xc7\xdf\x6c\x86\xe1\x23\x52\xee\xeb\xe3\x8e\xa1\x81\
+\xe1\xa0\x55\x84\x84\xf2\x65\xb6\x1f\x11\x27\x64\xb4\x77\xc2\xa1\
+\x2d\xd3\x1a\xd6\xb5\xa8\x0b\xd8\x6d\x3f\x45\x81\x46\x2c\x89\xd6\
+\xaf\xdc\x40\x33\x05\xa8\x44\xa7\x14\x99\xe5\xd7\xb6\x27\xc4\x92\
+\x44\x5f\x9b\xc6\x12\xbb\x11\x26\x1b\xde\xb6\xcb\x82\xb1\x68\x49\
+\x41\xa9\x88\xe6\x42\xdc\x6f\x5a\x56\xd6\x4f\x23\x85\xe6\xf0\x4a\
+\x26\x02\xcf\x33\x6d\x28\xb0\x34\x9c\x63\xd2\x6d\xaa\xf2\xf8\xb1\
+\xcf\xe7\xa1\x45\xef\xf5\xc1\x20\x82\x95\x38\x5e\x9e\x6e\x36\xaf\
+\xdd\x6e\xe6\x6e\xf2\xb9\x65\x11\x46\x29\x6e\xbe\x5f\xf2\x83\x37\
+\x46\xc2\xca\xbf\xfe\xfa\xf5\xef\x49\x35\xd4\x3b\xc6\x48\x7d\xd6\
+\x3b\xcb\x41\xb7\x7b\x7f\xd3\x2f\x0b\x8f\xad\xda\x13\x30\x6f\x35\
+\xfa\x17\x1f\x8f\x3d\x0d\xab\xf3\xa9\x53\xa7\xec\x7d\xf2\xe4\xc9\
+\x9e\x95\x3a\xa5\x90\x53\xa7\x3c\xe2\xa0\x07\x48\xf4\x13\xa0\xb4\
+\x5b\x5b\x71\x13\xc7\x23\x2c\xdd\x3f\x5d\xfd\x73\x2f\x18\xfa\x4b\
+\xba\xf7\x59\xdb\x6b\xc8\xf7\x7b\x0f\x57\xfa\xe3\x32\x72\x45\x01\
+\x3d\xa2\x80\x15\x59\xdb\x90\x13\x82\x41\x2c\xe9\x05\xdb\x52\xaf\
+\x17\xb1\xd4\x25\x86\x69\xa5\x55\xb5\x81\xc8\x60\xc8\xed\x07\x9f\
+\x64\x24\x21\xb1\xec\xc9\x8f\xbd\xcd\xed\x30\x76\xd5\xed\x22\xa5\
+\x6e\x8b\x6e\x8e\x55\xa2\x59\xf5\x4b\xf2\x93\xc7\x3a\xc6\x44\x27\
+\xf9\x53\x84\xea\xc4\x51\x0c\x2f\xc7\x48\xa1\xd1\x42\x27\x33\x03\
+\x08\xc4\xf6\x82\x78\x31\x78\x78\xa8\xb1\x79\x85\x06\x2b\x19\xf3\
+\xb1\x37\x33\x15\x79\x17\xa7\x97\x1d\xed\xb1\xcf\x01\xd4\x7e\x14\
+\xfa\xe1\xbf\x3c\x92\x0b\xfe\xf3\xd7\xaf\xff\xd4\x08\xe3\xb5\xd7\
+\x5e\x2b\x56\xff\xa4\xda\x3c\x79\xec\xa4\x5a\xbf\xae\xa1\x8f\x0c\
+\xf6\x0a\x74\x42\x6c\x66\x28\x5c\x37\xc5\xbd\xe7\x69\xe0\xbf\x57\
+\xaa\x95\x22\xbf\xe0\xf9\xe6\x9b\x6f\x52\xb7\x13\xa8\xeb\x78\x2c\
+\x9f\xbf\xc9\xfb\x44\x2c\x80\x8f\x8f\x8f\xf7\xec\x90\x6d\xb4\x23\
+\x1c\x3f\x7d\x3a\x1c\x7f\xf5\x55\x04\x29\xcf\x7b\xe3\xdf\xdf\xd8\
+\x77\xee\xd2\xf9\x3f\x9d\x5a\x9a\xf9\xd6\xf9\x3b\x97\x0f\x4f\xcc\
+\x5e\x04\x9b\x46\x60\xa7\x15\x6f\xdf\x50\x29\xee\x83\x58\xfc\x7e\
+\x90\x1f\x81\x3b\x40\xf4\x50\x37\x6b\xf9\x33\x12\xc1\x40\x24\xba\
+\x91\x7e\x85\x67\x19\x62\x41\xb7\x30\x39\xf5\xe7\x6a\x23\xba\xd4\
+\x23\x24\xb5\x77\x32\xb2\xe0\xc4\xc3\xfe\x3b\x08\x27\x21\x06\x8b\
+\xe0\xc2\x5d\x4e\xbd\x7e\x51\x47\x71\xf4\x67\x11\xd2\xb2\x34\x1a\
+\x68\x94\x10\x41\x84\xf6\x50\x07\x89\x4f\xf8\x1b\xc1\x90\x3f\xcb\
+\xb1\x5e\xb1\xc9\x90\xc6\xc8\xb8\x38\xdd\xf4\x2e\xdf\x59\xf6\x67\
+\xe6\xee\x90\x4f\xcd\x2b\xf6\x3f\x52\x3f\x50\x2c\xbd\x3d\x19\x2d\
+\xfe\xf3\x9d\x6f\xff\xec\xbb\x78\xde\x15\xdc\x93\xbe\xed\xd9\xaf\
+\x09\x13\xcb\xb4\xa4\x57\x7b\x3f\x5f\xff\xa4\x1b\x3e\xdf\x42\x05\
+\x2c\x90\x3e\x18\x1d\x1d\x6d\x97\x3f\x31\x31\x11\xbf\xfa\xea\xab\
+\xdb\x5a\x12\x3c\x73\xe6\x4c\xae\x5e\xaf\x17\xc3\x30\x2c\xd5\xeb\
+\x61\x5f\xa1\xd0\x2c\x63\x2f\x23\x3a\xf5\x87\x71\xd8\x8f\xf6\xc5\
+\x40\x10\xc4\xfd\x14\x57\x06\x8b\xd8\xba\x66\xc1\x05\x66\x49\xa1\
+\x25\x96\x7f\xfb\x40\x2d\x89\xd9\x85\x5c\xbe\x10\xe5\xc3\x30\xaa\
+\x47\x8d\xe1\xeb\x93\x37\xf6\xbd\x77\xf9\xe2\xe8\x8f\xaf\xbc\x57\
+\x7d\x67\xf2\x62\xd9\xab\xdf\x32\x62\x29\xf4\x0f\x7b\x7b\x86\x8a\
+\x2d\x8e\xbf\xf9\x7c\x2e\x0f\xc5\x3c\x88\x45\xab\x9b\x64\x66\xdc\
+\x1a\xf4\xd1\xfe\x4a\x04\x91\x68\x72\x9f\x3e\x75\x54\x3e\xb4\xa7\
+\xa7\x05\x00\x89\x38\xc9\x56\x85\x01\xdc\x58\x34\xe9\x05\x04\x21\
+\xb6\x4c\xf2\x6a\xbb\x45\x8d\xa9\x9f\x85\xe3\x48\x89\x40\x65\x8b\
+\xc1\x8b\x18\x72\x20\xbf\x46\x09\xed\x57\x70\x61\x88\x3d\xb6\x8b\
+\x4f\xb8\xb8\xb7\xe8\x3a\xa4\xa2\x21\x15\xc8\xb1\x21\xef\x33\x40\
+\xd4\xe7\xe2\xf8\xfa\xf4\x72\x70\x7b\x1e\xa2\xe0\x6c\x9f\xd7\xf7\
+\x15\xef\x1b\xa3\x47\x16\x9e\x7b\xf4\xe9\xe9\x23\xfb\x0e\x5e\xdd\
+\x3b\xba\xf7\xe7\x28\xb6\x4d\xd7\x1a\xb5\xb0\xd1\x68\xb4\xc8\x07\
+\x3d\xc4\x60\x9e\x7a\x2c\x70\xd0\x6b\xa9\xe9\xfb\xcc\xd0\x9a\x8b\
+\x5c\xa4\xbb\x88\x50\xba\x18\x04\xd1\x52\x3e\x9f\x5f\xba\x13\x04\
+\x2b\xb5\x1b\x37\x6a\xf4\xa7\x31\x20\xd5\x7b\x2b\x66\x3c\x8e\x83\
+\x93\x54\xf7\xec\xd9\xb3\xd6\x64\xe1\xc4\x45\x2e\x3c\xff\x22\x89\
+\xc7\x2a\xb2\x95\xca\x6f\x37\x4e\xf7\x68\x20\xe5\xcd\x13\x12\x75\
+\x36\xe5\x44\x17\x2e\x5c\x28\x5c\xbd\x7a\x75\x20\x1c\x18\x18\xc8\
+\x35\x1a\xbb\xc0\xbb\x0a\x37\x2c\x56\x10\x09\x86\x38\xeb\x51\x01\
+\x2b\x87\xc8\x67\x30\x8e\x5b\x83\xa0\x2a\x5f\x18\x89\xcb\x70\xf4\
+\x3e\x44\x01\xbe\xc2\xee\xf3\xf9\x88\x18\x9e\xe9\x73\xcb\x92\x8e\
+\x1d\x69\xdd\xc9\x89\x65\x36\xc0\xbb\x8d\x08\xe9\xc3\xea\x0e\x01\
+\xde\xd4\x94\x97\x7e\x83\x20\xa8\x17\x72\x85\x1a\xdb\x89\xf1\xcc\
+\xc2\x5c\xe9\xa3\x4f\x2e\x8f\x5c\xbc\x71\xa9\xfa\xd3\xeb\x17\x2b\
+\x3f\xb9\xf3\x41\xc9\xab\xdd\x00\x13\x1f\x62\x49\x67\xc4\xdb\x33\
+\x50\xe4\x1e\x61\x26\xb6\x90\x5c\x88\x2c\xcf\x9c\xc5\x26\xf8\x9a\
+\xe0\xca\xa4\x44\x63\x17\x15\x40\x1c\x46\x20\xcc\x75\xec\x4d\xc1\
+\xe6\x9f\xbc\x2d\xae\x25\x12\x39\x38\x8b\x88\x40\x9d\x23\xe4\xb6\
+\xd1\x47\x92\x4e\xf2\x98\xd4\xa3\x91\x40\x6e\xde\xda\x61\x57\xb9\
+\xf6\x28\x1d\x8f\x76\x7e\x74\x66\x24\xa4\x6c\x60\xc7\xd1\x4b\x5a\
+\xc9\x48\xb1\xcc\x99\xd7\xab\x73\xcb\xfe\xe2\x02\x8a\x93\xcd\x49\
+\x88\xe2\x80\xf7\xfb\xd5\x43\xcb\xcf\xec\x3d\x74\xe7\xc8\xfe\xa7\
+\x26\xf7\x8f\xee\x9f\xd9\x55\xee\x67\xfb\x2f\x0e\x20\x8a\x81\x56\
+\x8b\x0d\x40\xab\x0b\x90\xe5\xa0\x8c\xf5\xab\x2a\x29\xc5\x5e\x60\
+\xc8\x12\x36\xca\x62\x90\x88\xef\x43\x0c\x52\xe6\xe7\xf0\xa0\xc6\
+\x24\x3f\x16\xd1\xcc\x13\x5f\x7a\xfd\x2c\xa0\x7b\x33\x68\x21\xcf\
+\x34\xfd\x26\xba\xfe\xc1\x2c\x61\xb3\xf5\x42\x3c\x5f\xc9\xe5\x16\
+\x9e\x7f\xfe\x79\xed\x42\x6d\x68\xc6\xc7\xe3\xe0\xf0\x61\x49\x15\
+\x0e\x8f\x52\xf1\x4d\xf0\xc1\x38\xb0\xc9\xf6\x29\x1b\xcb\xfd\x6e\
+\xf3\x14\xd0\xa8\xb0\x0f\xa5\x5b\x56\xaa\xfc\x46\xa2\x90\x38\x7f\
+\xad\x58\xdc\x55\xac\xd5\x46\xe0\x42\xa3\x74\x02\x43\x49\xb8\x9b\
+\x7e\x1e\x25\x2f\x64\x1b\xbf\x02\x0a\x0d\x81\xb7\xfd\x00\x40\x9c\
+\xbe\x08\x1f\x05\xe9\x6d\x8d\x45\xbd\x04\x46\x08\xd3\xb1\xe8\x73\
+\x12\xdc\x0c\x4f\xf7\x71\xaf\x00\x2a\x7b\x7a\xcb\x5f\x9d\x07\xe0\
+\xb8\xf6\x40\x48\x86\x57\xa6\x95\xb4\x5a\x79\x04\x24\x4c\x0d\x11\
+\xc4\xd0\x49\xc4\xd1\x24\xe2\x17\xc2\x02\xf3\xf7\xb0\xd5\x68\x36\
+\x82\x99\x85\xd9\xbe\x6b\x13\x37\x86\x2e\xdd\xfc\x70\xe4\xbd\x5b\
+\x97\x86\xce\x4d\x5d\x2a\x7b\xcb\x57\x48\x8a\xb2\x61\x61\x18\x24\
+\xeb\xf7\x76\xf7\xe5\xe3\x3e\x54\x30\x74\x65\x6a\xc8\x13\x98\x9c\
+\x4f\x14\xb0\xc2\x88\xa6\x13\xd2\xd4\x47\x84\x91\xd6\xcb\xec\x69\
+\x65\xb2\xef\x24\x9d\xc3\x03\x02\x70\xb7\xed\xce\x69\x79\xe8\x3a\
+\x24\xda\x1e\xfb\x70\x13\xed\xf7\xe8\xb2\x87\x08\x6d\xe1\x95\x25\
+\xb4\x6a\x97\xea\xfe\xbc\x74\x6c\x6a\x3a\x4d\x46\x84\xfe\xc7\xbc\
+\x6f\xec\x7e\x72\xf1\xb7\xf7\x3c\x39\xf3\xe4\xd8\xe3\xd3\x7b\xab\
+\x7b\xe6\x86\x07\x2b\xcb\xac\xe1\xc5\x51\x54\x0f\xeb\xcd\x88\x43\
+\xff\xfc\x89\x99\xa1\x38\x9c\xd6\x91\xc4\xce\xa8\x0e\x84\x1a\xcc\
+\x00\x34\xb0\x84\x74\x48\xec\xa7\x6f\x48\x09\x3b\x0d\xa4\x4b\x4d\
+\x17\xce\x62\x91\x4a\x12\x02\x27\xc4\x90\xe1\x20\x24\x72\x5e\x04\
+\x04\xf3\x44\x9a\x81\x20\xa7\x83\x20\x9c\xa4\xf3\x26\x20\xec\x89\
+\x38\x6e\x40\xbd\xde\x14\x12\xc2\xcc\x2b\xaf\xbc\xa2\xf8\xeb\x1a\
+\xc3\x05\x32\xce\x04\xaa\xde\xd9\xde\xce\x04\x6d\xdf\x9a\xcd\x78\
+\x5b\xa9\x55\x31\x89\x49\x4a\xd4\x4b\x34\x3a\x7d\xfa\x74\x61\x6c\
+\x6c\xac\x5a\x8b\xe3\xb1\x20\x8a\xf7\xd1\x87\xfb\xe8\xdc\x3d\x24\
+\x79\x98\xf7\x30\x40\xd9\x05\x60\xcb\x34\x0f\xfe\xab\x29\x23\xbf\
+\xd6\x34\x21\xb7\xa4\x7c\x61\xf7\x2a\xf2\x13\x26\x79\xa3\xab\xc3\
+\xd4\x51\x42\xf6\x14\xc9\xdb\x78\xbf\x56\x3a\x21\xbb\xcd\x8d\x80\
+\x4b\xbd\x80\x0b\xb9\x89\x88\xe8\xf7\x7c\x2e\xdf\x44\x99\xab\xd5\
+\x6c\x35\x83\xb9\xc5\xf9\xe2\xad\xd9\x89\x81\x2b\x13\x57\x87\xfe\
+\xef\xf6\xc7\x43\xef\x4f\x5d\xee\xff\xf9\xdc\xd5\xa2\x57\xa7\x3f\
+\x55\xe5\x00\xf9\x2b\xcf\x81\x14\xee\x7d\xda\x55\xc8\xc7\xfd\x28\
+\xed\x15\x58\x29\x92\x0a\xbe\xe6\x02\xbe\x56\xc7\xc4\xed\xad\xb5\
+\x94\x93\xd2\xa8\xfc\x92\xea\x09\x04\x6d\x3b\x8e\x76\x77\xab\x81\
+\x3a\xd8\x4d\x31\x42\xb5\x18\x5c\xe7\xdb\x2a\x76\xe3\x49\x03\x95\
+\x9a\x25\x34\x03\x6f\x71\x70\xca\x26\x45\x0d\x98\x72\x93\x47\x05\
+\x15\x1f\xf2\x7e\x77\x68\xff\xca\xa1\xea\xa3\x8b\x5f\x1d\x7b\x74\
+\xe6\x40\xf5\x2b\xb3\xa3\x95\xdd\x8b\x83\xe5\x41\x46\x4b\x4e\xf9\
+\xd3\xae\x46\xd4\x40\xfa\x92\x14\x45\x8f\x50\xba\xde\x09\xf2\xf5\
+\x06\x5a\xd2\x5b\xaa\x0f\x49\xa8\x1d\xf5\x21\x99\xd2\xca\x9e\x35\
+\xca\x91\xfc\xd4\x4d\xea\xc1\x90\x48\xb4\x58\x0c\x4f\x3e\x22\x79\
+\x63\x50\x2a\x99\x71\x36\xd6\xd2\xc7\x0a\xbe\x8c\x40\x3e\x07\x68\
+\xbc\x09\x62\xdd\xa4\xe1\xd7\x69\xcd\x35\x18\xea\xcd\xa5\xa1\xa1\
+\x89\x3f\x3a\x7a\x94\x31\xb1\xdb\xa8\x1c\xd6\x62\xfc\x8b\xa7\x4e\
+\xc5\x1b\x31\xea\xee\x94\xdd\x3e\x69\x3f\x74\x87\xf4\xf0\x01\xe9\
+\x43\xcd\x1d\x5e\x7e\xf9\x65\xf1\xaa\xb6\x91\x7f\xb5\x5a\x1d\x8b\
+\xc3\xf0\x00\x98\xf5\x04\xed\x7d\x9c\xd6\xee\x07\x18\x10\x83\xbf\
+\x8b\x88\x68\x3d\xc7\xcc\x76\x05\x43\xe3\xee\x02\x04\xdd\x0d\x40\
+\xc0\x49\x81\x17\x7b\x9b\xe3\xab\x73\x52\xa4\xc7\xdf\x12\x59\x61\
+\xdb\x44\x7a\xb2\x25\x99\x72\xd8\xbe\x51\x47\x0b\x69\x54\x17\xd9\
+\x85\x4c\x10\x0a\x4f\x20\x4c\x8a\x6b\x8d\x7a\x6e\x6e\x69\xbe\x38\
+\x31\x3b\xd5\x7f\x73\xfa\x93\xc1\x6b\xd3\x37\x07\xae\xce\xde\x2c\
+\x7f\x34\x7f\xab\xf8\xbf\xcb\x93\x79\xaf\x81\x6c\x8f\x88\x6e\x55\
+\x60\x39\x4c\x4b\x62\xc8\x63\x88\x69\x50\x09\x13\x05\xe6\x3d\x68\
+\xb9\x43\x80\xda\x5e\x00\x55\x74\x32\x50\x26\x5b\xd7\x08\x96\x2a\
+\xd5\xf2\x08\x99\x6c\xb9\xd9\xe2\x3c\x95\xe4\x35\x40\xaf\x8d\x9a\
+\x26\x8c\x55\x4f\x8c\x5d\x69\x43\x34\x79\xf3\xc3\xde\xa1\xf2\xee\
+\xc6\x63\x83\x63\x2b\xfb\x2b\x63\x4b\xfb\x46\xf6\x2c\xec\x19\x19\
+\x9b\x1f\xdd\x55\x85\x20\x06\x6a\x4c\xb8\x9a\x6a\x4f\xb3\x19\x05\
+\xe4\xc9\xba\xdb\x2a\x26\xab\x7d\x0a\x5b\x0b\x29\x07\x3f\xc5\xb3\
+\x7e\xd0\xdb\x70\xdd\x4a\x5c\x1b\x75\x33\x97\xeb\x46\x8d\xfc\xca\
+\x6b\xdd\xbe\xb6\x00\xf1\x25\x1b\x81\xf4\xe9\x4a\x88\x48\xc4\x64\
+\xfd\x6e\x8c\xb3\x4e\x9c\x79\x12\x4f\xd2\xe4\x6b\x64\xf6\x11\x14\
+\xf6\x21\x23\xce\xc7\x8c\xf8\x37\xc0\x4b\xb7\x2f\x93\xd4\x45\x78\
+\x29\x6b\x2f\x26\xbe\x79\x95\x37\x8b\x91\x84\x43\x89\xc8\x80\x87\
+\xfd\x6c\x41\x88\x4a\x63\x7c\xb1\xf6\x10\x20\xfd\x2d\x1a\xf4\x14\
+\x58\x7c\x80\xc6\x8c\x00\x52\x5d\x3f\x20\x0e\x0c\xf2\xfb\x9c\xd0\
+\xe7\x78\x10\x13\x06\xc2\xc4\x6b\xda\x86\xc6\xbb\xce\x50\xe3\x53\
+\x4e\x6a\x80\x68\x47\xd9\xd0\xe2\xba\x6e\xc3\x28\x9f\x4a\xa0\x43\
+\x08\xc8\x98\x5e\xca\x1a\x21\x54\x2e\xcc\xa1\xf1\xcd\x74\x07\x23\
+\x4e\xbc\xb8\xb2\x9c\x5f\x58\x9e\x2f\xce\x2c\xce\x95\xa6\xe7\xa6\
+\xcb\x53\x8b\x77\x4a\xd3\x8b\x33\x3c\xb3\x85\xa9\x95\xd9\xc2\xe4\
+\xca\x5c\xee\x93\xc6\x42\x38\xdd\x58\x42\x30\xd4\x0e\xa4\x1e\x89\
+\xef\xe2\x37\xca\x86\xc7\xe4\x2d\x40\x23\x26\xab\x87\x01\x56\x93\
+\x1c\x8f\x0f\x5a\x7a\x61\x9f\x57\xcd\x97\x5b\x63\x85\xc1\x68\x77\
+\x71\x30\xaa\xf6\x0d\x35\xaa\xe5\x4a\x6d\xb8\xbf\x52\xab\x0e\x0e\
+\x2f\x0f\x0f\x54\x96\x47\x78\x06\xfb\x06\x6a\xa5\x62\x29\x4a\xeb\
+\xc6\x27\x85\x39\xc9\xdb\x44\xfe\xd4\x10\xb4\x6a\x7e\x33\xd6\xb1\
+\x9a\xfe\x53\xb5\x69\x14\xa2\xf9\x06\x6f\xc1\x9a\x7f\xd9\x33\x65\
+\xc0\x5b\x25\xba\x79\x39\xfa\x82\xf1\x98\xdd\x19\x10\x0c\xec\x11\
+\xd1\xcc\x60\xbf\x8e\xeb\x57\x0c\x89\x17\xf2\xf9\xe0\xc2\x8b\x2f\
+\xbe\xf8\x71\x9a\x5e\x84\x2f\x89\x27\x8b\xbf\x99\x7c\x7b\x5a\xd7\
+\x00\xab\x57\x2c\x32\x97\x5c\x6e\x48\x00\x51\x54\x70\x3e\xaf\x6f\
+\xba\xd3\x9e\xc3\x54\xf6\x61\x9e\x12\x08\xae\x79\x00\x47\x96\x34\
+\xe8\xc7\x04\xc3\x78\x5d\xed\xd5\x6c\x23\x00\x55\xf2\x9e\xea\x90\
+\x5e\x0d\xee\xf0\x57\x0f\x75\x02\x8a\xb6\xb5\x63\xa9\x5d\x72\x88\
+\x60\x98\xe8\x73\xef\x42\x8e\x89\x8c\xbe\x6e\x02\xba\x83\x90\x20\
+\xa7\x26\xbc\x61\x1d\xf9\x7e\xa5\x5e\xcb\xf3\xe4\x6a\x8d\x95\x5c\
+\x4d\xee\x46\x2d\x57\x27\xac\xd9\x8a\x82\x66\xab\xc5\x70\x0a\x18\
+\x99\x4d\x69\x42\x6c\xa3\x95\x23\xc2\x26\x9c\xbf\xc5\x02\x42\xb3\
+\x54\x28\x71\xbc\xb8\xd4\xe8\x2b\x14\x79\x17\xa2\x42\xa1\xc8\x1d\
+\x0f\xb9\xe6\x7a\x65\x92\x1f\x43\x82\x23\x88\x14\x51\xee\x47\xf8\
+\x0b\x8e\x74\x80\x03\xb8\xb0\x10\x7b\x82\x5f\x8e\x9d\x22\xb2\xe1\
+\x27\xe1\x95\x7b\x8f\x20\x1e\xc7\x94\xa7\x68\xfb\xaf\x88\xf7\x63\
+\x56\x32\x7f\xc0\xc8\x72\x4d\xd9\x8c\x83\xbc\xe3\x1d\x8c\x5a\xfe\
+\xbd\x4c\x67\xbf\x77\xc5\x33\xa4\x86\x8a\xdf\x7a\xeb\xad\xfe\x5c\
+\xa1\x70\x82\xaa\xfd\x21\x35\x3d\x40\x44\xa8\x97\xb3\x32\x9a\x70\
+\x71\x1f\x8f\xaa\x4f\x65\x52\x0a\xdf\x34\xdf\xae\x82\xbe\x04\x1e\
+\x29\xd1\xa4\x04\xa3\x26\x09\x31\x45\x2c\x7a\x0b\xe1\xf5\x16\x70\
+\xa0\x00\xd7\xe1\xd6\x6e\x67\x55\xba\x8c\x27\xf0\x74\xe9\x2d\x8a\
+\xb9\x9c\x4d\x48\xef\xe6\x49\x36\x6a\x99\x08\x98\x8a\x82\x2e\xc6\
+\x6a\xba\x2c\x41\xa8\xec\x6c\xdd\xd2\xb8\xf7\xfd\x5b\xc4\xe3\x80\
+\x6f\x4c\x1c\x50\x69\x91\x81\x85\xf8\x58\x04\x23\x22\xba\x0d\xb7\
+\x7e\x87\xd9\xd6\x77\x8e\x1f\x3f\x7e\x9d\xf6\x0a\xb4\x59\x50\xf7\
+\x04\x01\x2b\xe6\xbd\x8d\xc4\x2a\x80\xda\xfa\xaf\x33\x67\xf6\x21\
+\x0c\x8e\xd3\x2f\xcf\x90\xf5\xb2\x1e\x00\xad\x09\x34\x03\x87\xad\
+\xfd\x59\x3e\x56\xc7\x2d\x15\xdb\xbb\xcc\xfb\x39\x24\x45\x46\x21\
+\x62\xda\x0e\xc1\xc4\xc4\x1a\xba\x89\x91\x44\x1d\xb3\xc6\x64\xe3\
+\xae\x09\x58\xc7\xb1\x1e\x72\xaf\xa6\x67\x90\x4e\xca\x95\xdf\x7a\
+\x71\xd7\xf3\x5b\xa7\x98\x7b\xde\x4b\xc0\x5d\x03\x48\x37\x8e\xc8\
+\x6b\x55\x50\xd7\x2a\x19\xab\xda\xf4\x89\x26\x56\x5c\x74\xea\xfd\
+\x19\x8b\xde\x2f\x7c\xff\xfb\xef\xfc\xfd\xf1\xe3\x2f\xfe\x04\xbf\
+\xb6\x54\xb4\x51\x83\x7b\x12\x88\x80\x89\x31\x8a\x2c\x7a\xc1\x5f\
+\x33\x8b\xfe\x3d\xe0\x7e\x9d\x6e\xc8\x21\x02\x28\x73\x9b\xfc\x38\
+\xc2\x4d\x8a\x70\x94\xbc\xa6\xee\x1b\x15\xfe\x20\x84\xad\x21\x1a\
+\xe0\x23\xb8\x76\xb6\xbb\x4d\x4d\xed\x80\xd4\x27\x4d\xed\x02\x84\
+\xf8\xed\x28\x89\x45\x7e\x9d\x79\x76\xba\x3b\xd3\xdc\xef\xee\x2e\
+\x00\xd2\x20\x07\x18\x51\x0a\xa1\xbc\xc0\x4b\xce\x36\x63\x05\x3c\
+\xbc\x75\x89\xeb\x12\xf2\xfd\x1e\x1c\x7f\x77\xfe\xfc\xf9\x6f\x02\
+\xb7\x25\xe0\xb4\x29\x91\xf4\x24\x90\x2c\x10\xc9\x7c\x52\x25\x42\
+\xb7\x05\x06\x77\x5b\xc1\x31\x12\x76\x94\x6b\x51\x09\x54\xad\xd6\
+\xab\x7b\x36\xab\x07\xd6\xbe\x11\xd2\x76\x03\xad\xdb\xa7\x17\xe0\
+\x36\xca\xb7\x57\x9a\x2f\xa3\xbf\x83\x58\x07\xdc\x12\xd1\x0b\xd4\
+\xd5\xfa\x32\x42\x50\x4b\xf3\x94\xa9\xf9\xf9\x79\x56\x45\xa4\xa6\
+\x77\x6a\x53\x50\xa4\x43\x52\x57\x44\x71\x26\x89\x58\x0a\x98\x9e\
+\xb8\xf5\x3a\x85\x7c\x07\xf4\xd7\x4e\xf5\x2e\x88\xa1\xa0\xc9\x06\
+\x6e\x64\x06\xdb\xa7\xed\x66\x8b\x4a\xa8\x0a\xee\x98\x1d\x08\x7c\
+\x9e\x10\x00\xe7\x0c\x27\x35\x2f\x76\x3b\x66\x9c\xa2\x0f\x06\x40\
+\xd7\x5d\x8c\x25\x3f\x60\xe3\xff\x1f\xb4\x45\xa1\xd1\x63\x2b\x7b\
+\x24\x1d\x24\xd7\xdd\x12\x71\x28\x11\x8b\x42\xde\x3e\x73\xe6\x39\
+\xbf\x15\xfc\x31\x85\xfd\x0e\x3e\xc3\x92\xf8\x20\x94\x3a\x6e\xae\
+\xd9\xd0\x85\x9f\xa2\x09\x1b\xf3\xb5\x1d\xb4\x69\xde\x8a\xbf\x63\
+\x76\x20\x70\x57\x10\x48\x46\x09\xf2\xb0\xe9\x00\x68\xa7\xbd\x1d\
+\xce\x39\xfa\x3c\x0c\x1c\xe8\x8d\x81\xc2\x1f\xf8\x41\xeb\xbb\xd3\
+\x13\x13\xff\xad\x65\x5e\x31\xfe\xad\x10\x87\xea\xb5\x25\x24\xee\
+\xcc\xf0\x7f\xce\x9d\x3b\x18\x46\xf1\x73\xcc\x0a\x9f\x61\x3e\xf2\
+\x55\x48\x96\x0f\xf7\xb2\xc4\xc6\xae\x37\x84\xa2\x95\xad\x3a\x84\
+\xa3\x8d\x40\xb7\xd4\x6b\x34\x46\x4b\x24\x7f\x53\x65\x23\xba\xad\
+\x15\xad\x3a\xee\x98\x07\x19\x02\x59\x29\x44\x24\x20\x34\xe2\x1f\
+\x42\x70\xb8\x65\x3b\xf2\x2c\xf1\xb2\x6a\x05\x66\x69\xca\xa0\x95\
+\x90\x19\xa2\x5e\xe6\xfd\x0b\xd6\x10\x7f\xf4\xf5\xaf\xbf\xf0\x0b\
+\x98\xbc\x11\x10\xb8\xb7\xe9\xbc\x23\x0b\xee\x2d\x11\x88\x12\x18\
+\x72\x6b\xa9\xdf\x37\x7d\x1a\xcb\x43\x7e\xdf\xfb\xe1\x0f\xf7\xfb\
+\x51\xf4\x14\xf4\x7b\x88\xb0\x83\xd4\x7a\x3f\x75\x1f\x81\x50\xd0\
+\x9a\xb5\xd1\x44\x15\xd3\x2e\x98\x74\x70\x34\xca\x68\xf7\x7c\x8d\
+\xe8\x45\x83\x5d\x3d\x24\xb4\x19\x01\x91\xd0\x4a\xd8\xf9\x79\x20\
+\x20\x20\x22\x00\x4b\x24\xa9\x80\x0b\x20\xc1\x3a\xf8\x81\x27\x41\
+\xe8\x1b\xf8\xda\x5d\x67\x2e\xe1\x16\x89\xc0\x13\x34\x99\xe2\x59\
+\xf6\x53\xaf\xa3\x71\xf0\x21\xde\xef\x87\x5e\xf4\xc1\xca\xca\xca\
+\xc7\x59\x1d\x2e\xe2\x68\x51\xc9\xcd\x9f\xb7\x01\xd4\x2d\x4d\xd2\
+\x95\x9f\x2a\xcf\xcb\xd4\x14\x50\x47\x0e\xa5\x8a\x9c\x10\xcb\x15\
+\xfc\xf5\xbc\x9d\xa8\xa1\x70\x57\xb1\xbf\x1f\x3a\x7d\x0c\xb2\x7a\
+\x94\xb6\x6a\x77\xfd\x21\x5a\x8e\x56\x9f\x87\xda\xb9\xc7\xa9\x0a\
+\x08\xc2\xd8\x80\x76\xb1\x8c\xe0\xec\x1d\x07\x2c\x1d\xe3\x8f\x7c\
+\x26\xc5\xc3\x36\x11\x61\x05\x0e\x1d\xba\x56\xdb\x5c\x10\x70\x99\
+\xb9\x5f\xc1\x5a\x26\xe9\x0c\xb3\xef\xfc\x74\x43\x40\xf0\x91\x71\
+\xb8\x69\x88\xdb\x1d\x29\xeb\x23\xb0\xb6\x7b\x2d\x09\x50\x4f\x63\
+\x6c\xce\x9a\x84\xb6\x15\x48\x9d\x3f\xdd\x4b\xff\x5a\xa7\x73\x3f\
+\xa6\x53\x2b\x91\x98\x04\x42\x4b\x8d\x40\xe8\x20\xfe\xcc\x46\xb4\
+\xb6\x17\xb8\x36\x0f\xfb\x04\x5e\xd7\xa8\xd1\xc7\xac\xa1\x7f\x04\
+\xcf\xbe\xc2\x3c\xe3\xd6\xf1\xe3\x6b\xd5\x4c\x24\xf9\xbc\xf4\xd2\
+\x4b\xc1\xb9\x73\xe7\x44\x18\x36\x05\x48\x6a\xb5\xe5\x97\xc3\x94\
+\x2d\x47\x5f\x1b\x51\x15\x90\xfa\x89\x7c\x7b\x6d\xe1\xab\xf1\xef\
+\xbe\xfb\x6e\x85\x9d\xe4\x87\x68\xe1\x1e\xda\xbb\x17\x15\xac\xbd\
+\x46\x34\x1e\xa2\x99\x8f\xfa\x7a\xcb\x1b\x00\x40\x25\xe2\x4a\x76\
+\x84\x4b\x88\x20\x20\x48\xfe\xa0\xc2\x16\x50\x32\xbd\x2d\x8a\xd1\
+\x68\x64\x8f\x05\x75\x70\x1a\xe1\xbd\xca\x03\x58\x4a\x4a\xd6\xca\
+\x49\x79\x25\x66\x9b\x44\x95\x26\xdb\x79\x27\x10\x00\x9c\x6d\x58\
+\xa8\x17\xe4\xc6\x87\xbe\x04\xea\xa0\xed\x6a\xa8\x45\x4b\xfa\x40\
+\xdd\x40\x0c\xed\x96\x82\xec\x8c\x02\x04\xd2\xa5\x09\xf2\xa7\x7d\
+\x2d\xee\xce\x17\x4f\x08\xd3\xfe\xc5\x22\xfd\x88\x8a\x7c\x3c\x45\
+\x4a\x6d\xf2\xdd\x84\x67\xde\xe0\x56\x89\x1b\x7e\xa3\x71\xbb\x52\
+\xa9\x4c\x1f\x3d\x7a\x54\x71\xbb\x0c\xe9\x02\x54\x4a\xfc\xad\x1e\
+\xad\xe8\xca\xa0\xc3\x63\x15\x79\x3a\x02\xb6\xeb\x14\x62\xae\x51\
+\x79\x27\x83\x71\x37\xea\x74\x80\x6d\x35\x67\xd4\x56\x4a\xcd\x66\
+\x73\x08\x25\xb3\x61\xd2\x57\x59\x40\xe6\x62\x59\xaf\x8a\x1c\x37\
+\x42\x76\x23\x68\x6d\x56\xc0\x72\xce\x7c\x78\x03\xb0\x18\x9d\x0a\
+\x2d\x12\x0f\x22\x62\x17\x5f\xc0\x06\x7a\x2e\x37\x88\xc9\xe9\xbe\
+\x71\xa8\x03\x31\x4e\xe3\x90\x9d\x57\x48\x08\xca\x75\x24\x94\xb2\
+\xaa\x20\x67\xe9\x54\xb3\x24\x07\xd5\x9f\x0c\x31\xee\x57\x9d\x6b\
+\xce\xcc\x8f\x7a\x5a\xf1\x32\x5e\x69\xf4\x35\x5e\xf7\x84\x43\x08\
+\x9c\x31\x42\x62\x39\x3b\xb1\x58\x30\x49\xa3\x65\x39\x7b\x1b\xd9\
+\x69\xad\x83\x4d\x66\x04\x37\x40\x00\x28\x78\xbb\xc0\x46\xd6\x6c\
+\xc6\x49\x39\x46\xdc\x5e\x44\x60\xbc\x89\x6c\x93\x7e\xd1\x02\x8e\
+\x6f\x37\x94\xf2\x21\x13\x08\x80\xb3\x22\xe4\x3f\xcf\xe1\xab\x19\
+\xfa\x78\x06\x88\x4f\xc5\xa8\xba\xa3\xcc\x36\x85\x86\xd5\x64\x10\
+\xf1\x11\xba\xdd\xbb\x67\xfe\xe0\x6b\x5f\x43\xd3\xb3\xb7\x11\x83\
+\xce\x1e\xb1\xf8\x2c\x0e\x56\xad\xed\xec\xde\x75\xf9\x4d\x43\x80\
+\x19\x57\x1c\x72\x56\x44\x23\x8d\xb4\x80\xd3\x53\x62\x34\xae\xa3\
+\x0b\xbb\x8b\x60\x43\x27\x1f\x45\x51\xdf\x5c\x14\x0d\xe4\xea\xdc\
+\x66\xe9\x37\x06\x39\x2d\xc8\x17\xf9\xc2\x21\x14\x82\x59\xb6\x33\
+\x2d\xe1\x21\xb0\x14\x35\xd6\x16\x0f\xa7\x06\x51\x9f\x07\x19\x38\
+\x3f\xc2\x61\x0d\xa7\x6a\xe0\x54\xe9\x13\xce\x45\xdf\xd2\x6b\x0e\
+\xc9\x85\x1c\x0e\x3b\x78\x25\x44\x24\x4a\x13\x19\x18\x26\x39\x0b\
+\x6e\x75\x34\x3e\x09\xd7\x94\xdb\xac\x4a\x4e\x06\xca\x03\x94\x70\
+\x59\xa9\x19\x49\xcb\x56\x91\x52\x9e\xab\xc1\x72\xdd\x8d\xa1\xfc\
+\x76\xf2\x36\xc1\x3a\xba\x56\x31\xed\x40\xb5\xd3\x46\x53\x00\x85\
+\xc1\xa5\xa6\xa9\x31\x86\xc1\xce\x6e\x99\xad\x75\x2b\xa6\x64\x1e\
+\xc5\x53\x41\x6a\xaf\x8d\x10\x62\x3c\x30\x20\x1c\x88\x2b\xad\xe4\
+\x90\x14\xaa\x46\x71\x5c\x23\xce\x12\xf1\xd9\x8c\x0b\x16\x40\xfa\
+\x05\x0a\x99\x67\x96\x30\x8b\x4a\x0c\x23\x41\x6e\x96\xed\xb3\x39\
+\x6e\x65\x9c\x8d\x6b\xb5\x05\x98\xe2\xc2\x13\x4f\x3c\xb1\x78\xf0\
+\xe0\xc1\x9e\xe7\x3c\xd2\x06\xaa\x7d\x52\x32\x4c\x71\x47\x23\x03\
+\x61\x06\xcc\x14\xbe\x69\xdc\xcf\xe2\xdd\x06\xe6\x67\x91\xf9\x46\
+\x79\xaa\xe1\x59\xc2\x51\xdc\x63\xc7\x8e\x59\xe3\x69\xf8\xa6\xc4\
+\xd3\x99\xb7\x0e\x61\x0d\x0e\x0e\x16\x26\x27\x27\x4b\x28\x0c\xf2\
+\xf4\x95\x9a\x61\x54\x46\xd7\x16\x82\xf1\xfa\xe2\xa0\x55\xa6\x4c\
+\x8e\xdf\x06\xe6\xa6\x43\xfb\xd0\x96\xe1\xe0\x86\xd4\xf0\x4d\xfb\
+\x58\xd7\x33\x70\xd5\x53\x8b\x37\xf3\x24\x1d\xd0\x62\xbe\x44\x78\
+\x8e\xf4\x4c\x0c\x35\x29\xb4\xdd\x59\x14\xaa\x98\x61\xb9\xd1\x4b\
+\x58\x24\xd4\xb4\xc5\x05\xec\xa2\x24\x43\x45\x57\x3f\xc0\x4b\x3b\
+\xcd\x9e\x22\xb0\x39\x40\xc1\xd4\xdf\x45\xdc\xf0\xd7\x21\x82\xe1\
+\x44\x9b\xf8\x28\x06\x0f\xe7\x97\x22\xb0\xa8\x1d\x5f\xaa\x60\x14\
+\x2e\xaa\xc8\x1e\x20\x03\xa9\xa5\x5d\xad\x11\x96\x53\x80\x68\xbe\
+\xe0\xd6\x57\x49\xb8\x49\xd4\x94\x4c\xd1\x88\xf5\x6b\x54\x53\x93\
+\x5e\x21\x2e\xf7\x9a\xc4\xdc\x80\xa5\x1d\x67\x54\x36\x62\x3b\x62\
+\x0b\x01\xb4\x96\xf2\x1c\xb5\xe5\x34\xd9\x52\x6b\x25\x5e\xce\xe7\
+\x5b\x3a\x0d\x28\xb1\x68\x05\xf8\xd7\x7a\x89\x3e\x1b\x35\x30\x15\
+\xd5\xd3\x23\xd8\x62\xa2\x29\x21\xb8\xb6\x6f\x94\xfa\xb3\x0d\xfb\
+\xc2\x08\x64\xa3\x66\x65\x90\x47\xdc\xa3\xab\x8e\x9f\xd5\x39\x65\
+\x11\x19\xf5\xca\xd5\x47\x46\xc2\xa5\xe9\xe9\x3c\xc7\x1a\x73\x88\
+\x7f\xf9\x5a\xad\x06\x4e\x94\x72\x41\xd0\xc8\x83\x4d\xa6\x66\x0d\
+\xe5\xf0\xa1\x24\x8e\x3f\xe5\xdc\x3b\xe4\x98\x08\x7c\x35\x87\xa8\
+\x00\x21\x45\x39\xda\xc0\xb1\x11\xf4\x48\xb5\x7a\xd2\xe4\x28\x38\
+\xac\x95\xce\x46\x10\x67\x4a\xc5\x54\x14\xd4\xe4\x50\x49\xb2\x14\
+\xe3\x2e\xe3\xb2\x76\x8a\x33\x28\x95\x2e\x73\xe7\x98\x08\xc9\xd1\
+\x10\x86\xfd\x82\xd2\x86\xf0\xe6\x90\x72\x97\x4d\x3a\x59\x4a\x8f\
+\x5b\x2d\x4a\x8c\xd0\xdc\x6d\x52\x85\x88\x23\xad\x9a\x8c\xda\xaa\
+\x61\x98\xcb\xf1\x11\x44\x1d\x35\xe0\x64\x7c\x9c\xe7\x23\xb3\xad\
+\x06\xdc\xbd\xd1\x2c\x16\x1b\xf1\xe2\x62\x54\x2e\x97\x45\x1c\xd1\
+\x0d\x0e\x93\x3f\x5e\x28\x44\xcf\x3e\xfb\xac\x46\x85\x84\x12\x37\
+\xea\xa1\xad\x85\xa9\x1f\xc5\x04\x25\x06\x75\xf6\xa3\x08\x80\xb0\
+\xf4\xc6\x99\x4f\xad\xcc\xad\xd5\xec\xc1\x88\x05\x53\xd3\xc0\x1f\
+\xfb\xe2\x40\xf6\x08\x9b\x36\x79\x14\x2f\x4d\xf7\x60\x80\xe9\x2e\
+\x5a\xd9\x01\xdf\xad\xc0\x36\x0b\x5f\xc1\xf9\x2e\x4a\xbf\x27\x92\
+\xde\xf7\x0d\xb8\x0b\x28\x5a\xdb\xe9\xc4\xae\x2c\xc4\xf5\xe4\x99\
+\x4e\x00\x3b\xb9\x5f\x9a\x00\x2e\x98\x5a\xb9\x58\xfe\x6c\x1b\x96\
+\x89\xa8\x08\xd7\x54\xb0\xfd\xb4\xe3\x7d\xaa\x16\xca\x57\x0d\xd6\
+\x2b\x5b\xe5\xf4\x2a\x3f\x11\x5f\xda\xba\x48\x9a\xdc\xae\x57\x2f\
+\x46\x13\x79\xaf\x1b\xb6\x5e\xfc\x1d\xbf\x1d\x08\xec\x40\xe0\x01\
+\x83\xc0\xff\x03\xd0\x0c\x15\x8a\x5c\xa1\xe0\xdb\x00\x00\x00\x00\
+\x49\x45\x4e\x44\xae\x42\x60\x82\
+"
+
+qt_resource_name = b"\
+\x00\x11\
+\x02\xf1\xa7\xe7\
+\x00\x63\
+\x00\x72\x00\x79\x00\x69\x00\x6e\x00\x67\x00\x5f\x00\x6d\x00\x61\x00\x6e\x00\x74\x00\x69\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\
+"
+
+qt_resource_struct_v1 = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+"
+
+qt_resource_struct_v2 = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x63\xcf\x10\xc9\xb0\
+"
+
+qt_version = QtCore.qVersion().split('.')
+if qt_version < ['5', '8', '0']:
+    rcc_version = 1
+    qt_resource_struct = qt_resource_struct_v1
+else:
+    rcc_version = 2
+    qt_resource_struct = qt_resource_struct_v2
+
+
+def qInitResources():
+    QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+
+def qCleanupResources():
+    QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/scripts/ErrorReporter/retrieve_recovery_files.py b/scripts/ErrorReporter/retrieve_recovery_files.py
index 519c79ba65b4f910060d272dc4b93d514983aabb..245e1d52bb100df0aedb3d68414e2b4a21e27d1a 100644
--- a/scripts/ErrorReporter/retrieve_recovery_files.py
+++ b/scripts/ErrorReporter/retrieve_recovery_files.py
@@ -10,31 +10,23 @@ def get_properties_directory():
 
 
 def get_recovery_files_path():
-    recovery_files_path = ''
     properties_directory = get_properties_directory()
     if 'recovery' not in os.listdir(properties_directory):
-        return recovery_files_path
+        return None
 
-    recovery_dir_contents = os.listdir(properties_directory + 'recovery')
-    if not recovery_dir_contents:
+    recovery_files_path = os.path.join(properties_directory, 'recovery')
+    if len(os.listdir(recovery_files_path)) > 0:
         return recovery_files_path
-
-    recovery_files_path = properties_directory + 'recovery'
-    return recovery_files_path
+    else:
+        return None
 
 
 def zip_recovery_directory():
     path = get_recovery_files_path()
+    if path is None:
+        return "", ""
     directory = get_properties_directory()
     hash_value = hashlib.md5(str.encode(directory + str(datetime.datetime.now())))
-    zip_file = os.path.join(directory, hash_value.hexdigest())
-    if path:
-        shutil.make_archive(zip_file, 'zip', path)
-        return zip_file, hash_value.hexdigest()
-    return ''
-
-
-def remove_recovery_file(file):
-    directory = get_properties_directory()
-    zip_file = os.path.join(directory, file)
-    os.remove(zip_file + '.zip')
+    base_name = os.path.join(directory, hash_value.hexdigest())
+    zip_file = shutil.make_archive(base_name, 'zip', path)
+    return zip_file, hash_value.hexdigest()
diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py
index 97cdf86d4c6c53bc8f10bb48be77326f859f23d1..8b54c45d8adcd24469f33965700abc8d8f5b0f02 100644
--- a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py
+++ b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py
@@ -14,7 +14,7 @@ from abc import ABCMeta, abstractmethod
 from inspect import isclass
 
 from six import with_metaclass
-from qtpy.QtWidgets import (QListWidgetItem, QMainWindow, QMessageBox)  # noqa
+from qtpy.QtWidgets import (QListWidgetItem, QMainWindow, QMessageBox, QFileDialog)  # noqa
 from qtpy.QtCore import (QRegExp, QSettings)  # noqa
 from qtpy.QtGui import (QDoubleValidator, QIcon, QIntValidator, QRegExpValidator)  # noqa
 
@@ -307,6 +307,8 @@ class SANSDataProcessorGui(QMainWindow,
 
         self.load_button.clicked.connect(self._load_clicked)
 
+        self.export_table_button.clicked.connect(self._export_table_clicked)
+
         self.help_button.clicked.connect(self._on_help_button_clicked)
 
         # --------------------------------------------------------------------------------------------------------------
@@ -460,6 +462,9 @@ class SANSDataProcessorGui(QMainWindow,
     def _load_clicked(self):
         self._call_settings_listeners(lambda listener: listener.on_load_clicked())
 
+    def _export_table_clicked(self):
+        self._call_settings_listeners(lambda listener: listener.on_export_table_clicked())
+
     def _processing_finished(self):
         """
         Clean up
@@ -553,6 +558,7 @@ class SANSDataProcessorGui(QMainWindow,
         self.user_file_button.setEnabled(False)
         self.manage_directories_button.setEnabled(False)
         self.load_button.setEnabled(False)
+        self.export_table_button.setEnabled(False)
 
     def enable_buttons(self):
         self.process_selected_button.setEnabled(True)
@@ -561,6 +567,7 @@ class SANSDataProcessorGui(QMainWindow,
         self.user_file_button.setEnabled(True)
         self.manage_directories_button.setEnabled(True)
         self.load_button.setEnabled(True)
+        self.export_table_button.setEnabled(True)
 
     def disable_process_buttons(self):
         self.process_selected_button.setEnabled(False)
@@ -587,6 +594,10 @@ class SANSDataProcessorGui(QMainWindow,
         msg.setEscapeButton(QMessageBox.Ok)
         msg.exec_()
 
+    def display_save_file_box(self, title, default_path, file_filter):
+        filename = QFileDialog.getSaveFileName(self, title, default_path, filter=file_filter)
+        return filename
+
     def get_user_file_path(self):
         return str(self.user_file_line_edit.text())
 
@@ -794,24 +805,25 @@ class SANSDataProcessorGui(QMainWindow,
                 self._setup_add_runs_page()
 
     def update_gui_combo_box(self, value, expected_type, combo_box):
-        # There are two types of values that can be passed:
+        # There are three types of values that can be passed:
         # Lists: we set the combo box to the values in the list
         # expected_type: we set the expected type
+        # str (in the case of "Variable" Q rebin): We set the combo box to the text if it is an option
+        gui_element = getattr(self, combo_box)
         if isinstance(value, list):
-            gui_element = getattr(self, combo_box)
             gui_element.clear()
             for element in value:
                 self._add_list_element_to_combo_box(gui_element=gui_element, element=element,
                                                     expected_type=expected_type)
+        elif expected_type.has_member(value):
+            self._set_enum_as_element_in_combo_box(gui_element=gui_element, element=value,
+                                                   expected_type=expected_type)
+        elif isinstance(value, str):
+            index = gui_element.findText(value)
+            if index != -1:
+                gui_element.setCurrentIndex(index)
         else:
-            # Convert the value to the correct GUI string
-            if issubclass(value, expected_type):
-                gui_element = getattr(self, combo_box)
-                self._set_enum_as_element_in_combo_box(gui_element=gui_element, element=value,
-                                                       expected_type=expected_type)
-            else:
-                raise RuntimeError(
-                    "Expected an input of type {}, but got {}".format(expected_type, type(value)))
+            raise RuntimeError("Expected an input of type {}, but got {}".format(expected_type, type(value)))
 
     def _add_list_element_to_combo_box(self, gui_element, element, expected_type=None):
         if expected_type is not None and isclass(element) and issubclass(element, expected_type):
@@ -1551,15 +1563,6 @@ class SANSDataProcessorGui(QMainWindow,
                 for element in value:
                     self._add_list_element_to_combo_box(gui_element=gui_element, element=element,
                                                         expected_type=RangeStepType)
-            else:
-                gui_element = getattr(self, "q_1d_step_type_combo_box")
-                if issubclass(value, RangeStepType):
-                    self._set_enum_as_element_in_combo_box(gui_element=gui_element, element=value,
-                                                           expected_type=RangeStepType)
-                else:
-                    index = gui_element.findText(value)
-                    if index != -1:
-                        gui_element.setCurrentIndex(index)
 
     @property
     def q_xy_max(self):
diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui
index 00b3511c876bb023b87f61edb5b7562e6608def7..1602f69e88ad4ea7895ca9ef1fe90beea5b3c4d9 100644
--- a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui
+++ b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui
@@ -355,6 +355,16 @@ QGroupBox::title {
                 </property>
                </widget>
               </item>
+              <item>
+               <widget class="QPushButton" name="export_table_button">
+                <property name="toolTip">
+                 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Export the current table as a csv file. This can then be re-loaded at a later date.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                </property>
+                <property name="text">
+                 <string>Export Table</string>
+                </property>
+               </widget>
+              </item>
               <item>
                <spacer name="horizontalSpacer_3">
                 <property name="orientation">
diff --git a/scripts/MultiPlotting/AxisChanger/__init__.py b/scripts/MultiPlotting/AxisChanger/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2cfb75232dfb59fac674e2b49dba356e18ebbc1
--- /dev/null
+++ b/scripts/MultiPlotting/AxisChanger/__init__.py
@@ -0,0 +1,6 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
diff --git a/scripts/MultiPlotting/AxisChanger/axis_changer_presenter.py b/scripts/MultiPlotting/AxisChanger/axis_changer_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a0eaa266028c4431a9743e16908f7bf9bb3a3a0
--- /dev/null
+++ b/scripts/MultiPlotting/AxisChanger/axis_changer_presenter.py
@@ -0,0 +1,39 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+
+
+class AxisChangerPresenter(object):
+
+    def __init__(self, view):
+        self.view = view
+
+    def set_enabled(self, state):
+        self.view.set_enabled(state)
+
+    def hide(self):
+        self.view.hide()
+
+    def show(self):
+        self.view.show()
+
+    def get_bounds(self):
+        return self.view.get_bounds()
+
+    def set_bounds(self, bounds):
+        self.view.set_bounds(bounds)
+
+    def clear_bounds(self):
+        self.view.clear_bounds()
+
+    def on_bound_changed(self, slot):
+        self.view.on_bound_changed(slot)
+
+    def unreg_on_bound_changed(self, slot):
+        try:
+            self.view.unreg_bound_changed(slot)
+        except TypeError:
+            return
diff --git a/scripts/MultiPlotting/AxisChanger/axis_changer_view.py b/scripts/MultiPlotting/AxisChanger/axis_changer_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..945cb89af487affda0cddcedf96fdacf36223349
--- /dev/null
+++ b/scripts/MultiPlotting/AxisChanger/axis_changer_view.py
@@ -0,0 +1,70 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from qtpy import QtGui, QtCore, QtWidgets
+
+
+class AxisChangerView(QtWidgets.QWidget):
+    sig_bound_changed = QtCore.Signal(object)
+
+    def __init__(self, label):
+        super(AxisChangerView, self).__init__()
+        layout = QtWidgets.QHBoxLayout()
+        self._label = QtWidgets.QLabel(label)
+        layout.addWidget(self._label)
+
+        self.lower_bound = QtWidgets.QLineEdit()
+        self.lower_bound.setValidator(QtGui.QDoubleValidator())
+        self.lower_bound.returnPressed.connect(self._bound_changed)
+
+        self.upper_bound = QtWidgets.QLineEdit()
+        self.upper_bound.setValidator(QtGui.QDoubleValidator())
+        self.upper_bound.returnPressed.connect(self._bound_changed)
+
+        layout.addWidget(self.lower_bound)
+        self._to = QtWidgets.QLabel("to")
+        layout.addWidget(self._to)
+        layout.addWidget(self.upper_bound)
+        self.setLayout(layout)
+
+    def set_enabled(self, state):
+        self.lower_bound.setDisabled(state)
+        self.upper_bound.setDisabled(state)
+
+    def show(self):
+        self.lower_bound.show()
+        self.upper_bound.show()
+        self._label.show()
+        self._to.show()
+
+    def hide(self):
+        self.lower_bound.hide()
+        self.upper_bound.hide()
+        self._label.hide()
+        self._to.hide()
+
+    def get_bounds(self):
+        bounds = [self.lower_bound, self.upper_bound]
+        return [float(str(bound.text())) if bound.text()
+                else 0 for bound in bounds]
+
+    def set_bounds(self, bounds):
+        lower, upper = [str(bound) for bound in bounds]
+        self.lower_bound.setText(lower)
+        self.upper_bound.setText(upper)
+
+    def clear_bounds(self):
+        self.lower_bound.clear()
+        self.upper_bound.clear()
+
+    def _bound_changed(self):
+        self.sig_bound_changed.emit(self.get_bounds())
+
+    def on_bound_changed(self, slot):
+        self.sig_bound_changed.connect(slot)
+
+    def unreg_bound_changed(self, slot):
+        self.sig_bound_changed.disconnect(slot)
diff --git a/scripts/MultiPlotting/QuickEdit/__init__.py b/scripts/MultiPlotting/QuickEdit/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2cfb75232dfb59fac674e2b49dba356e18ebbc1
--- /dev/null
+++ b/scripts/MultiPlotting/QuickEdit/__init__.py
@@ -0,0 +1,6 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
diff --git a/scripts/MultiPlotting/QuickEdit/quickEdit_presenter.py b/scripts/MultiPlotting/QuickEdit/quickEdit_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..82f0e7542465707c6ef5ad3eece4550896e6e0ae
--- /dev/null
+++ b/scripts/MultiPlotting/QuickEdit/quickEdit_presenter.py
@@ -0,0 +1,53 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import absolute_import, print_function
+
+
+class QuickEditPresenter(object):
+
+    def __init__(self, view ):
+        self._view = view
+
+    @property
+    def widget(self):
+        return self._view
+
+    def connect_autoscale_changed(self, slot):
+        self._view.connect_autoscale_changed(slot)
+
+    def connect_errors_changed(self, slot):
+        self._view.connect_errors_changed(slot)
+
+    def connect_x_range_changed(self, slot):
+        self._view.connect_x_range_changed(slot)
+
+    def connect_y_range_changed(self, slot):
+        self._view.connect_y_range_changed(slot)
+
+    def connect_plot_selection(self, slot):
+        self._view.connect_plot_selection(slot)
+
+    def add_subplot(self, name):
+        current = self._view.current_selection()
+        self._view.add_subplot(name)
+        index = self._view.find_index(current)
+        self._view.set_index(index)
+
+    def all(self):
+        return [self._view.plot_at_index(index) for index in range(1, self._view.number_of_plots())]
+
+    def set_plot_x_range(self, range):
+        self._view.set_plot_x_range(range)
+
+    def set_plot_y_range(self, range):
+        self._view.set_plot_y_range(range)
+
+    def set_errors(self, state):
+        previous = self._view.get_errors()
+        if previous == state:
+            return
+        self._view.set_errors(state)
diff --git a/scripts/MultiPlotting/QuickEdit/quickEdit_view.py b/scripts/MultiPlotting/QuickEdit/quickEdit_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a460f54be9bc7b1b3a0dc722464441ce24d0311
--- /dev/null
+++ b/scripts/MultiPlotting/QuickEdit/quickEdit_view.py
@@ -0,0 +1,121 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import absolute_import, print_function
+from qtpy import QtWidgets, QtCore
+import qtpy
+from MultiPlotting.AxisChanger.axis_changer_presenter import AxisChangerPresenter
+from MultiPlotting.AxisChanger.axis_changer_view import AxisChangerView
+
+
+class QuickEditView(QtWidgets.QWidget):
+    error_signal = QtCore.Signal(object)
+
+    def __init__(self, subcontext, parent=None):
+        super(QuickEditView, self).__init__(parent)
+
+        button_layout = QtWidgets.QHBoxLayout()
+        self.plot_selector = QtWidgets.QComboBox()
+        self.plot_selector.setEditable(True)
+        self.plot_selector.completer().setCompletionMode(
+            QtWidgets.QCompleter.PopupCompletion)
+        if qtpy.PYQT5:
+            self.plot_selector.completer().setFilterMode(
+                QtCore.Qt.MatchContains)
+
+        self.plot_selector.addItem("All")
+        self.x_axis_changer = AxisChangerPresenter(AxisChangerView("X"))
+
+        self.autoscale = QtWidgets.QPushButton("Autoscale y")
+        self.autoscale.setStyleSheet("background-color:lightgrey")
+
+        self.y_axis_changer = AxisChangerPresenter(AxisChangerView("Y"))
+
+        self.errors = QtWidgets.QCheckBox("Errors")
+        self.errors.stateChanged.connect(self._emit_errors)
+
+        button_layout.addWidget(self.plot_selector)
+        button_layout.addWidget(self.x_axis_changer.view)
+        button_layout.addWidget(self.autoscale)
+        button_layout.addWidget(self.y_axis_changer.view)
+        button_layout.addWidget(self.errors)
+        self.setLayout(button_layout)
+
+    """ plot selection """
+
+    def current_selection(self):
+        return self.plot_selector.currentText()
+
+    def find_index(self, name):
+        return self.plot_selector.findText(name)
+
+    def set_index(self, index):
+        self.plot_selector.setCurrentIndex(index)
+
+    def plot_at_index(self, index):
+        return self.plot_selector.itemText(index)
+
+    def number_of_plots(self):
+        return self.plot_selector.count()
+
+    def add_subplot(self, name):
+        self.plot_selector.addItem(name)
+
+    def connect_plot_selection(self, slot):
+        self.plot_selector.currentIndexChanged.connect(slot)
+
+    """ x axis selection """
+
+    def connect_x_range_changed(self, slot):
+        self.x_axis_changer.on_bound_changed(slot)
+
+    def set_plot_x_range(self, range):
+        self.x_axis_changer.set_bounds(range)
+
+    """ y axis selection """
+
+    def connect_y_range_changed(self, slot):
+        self.y_axis_changer.on_bound_changed(slot)
+
+    def set_plot_y_range(self, range):
+        self.y_axis_changer.set_bounds(range)
+
+    def get_y_bounds(self):
+        return self.y_axis_changer.get_bounds()
+
+    """ auto scale selection """
+
+    def connect_autoscale_changed(self, slot):
+        self.autoscale.clicked.connect(slot)
+
+    """ errors selection """
+
+    # need our own signal that sends a bool
+
+    def _emit_errors(self):
+        state = self.get_errors()
+        self.error_signal.emit(state)
+
+    def connect_errors_changed(self, slot):
+        self.error_signal.connect(slot)
+
+    def set_errors(self, state):
+        self.errors.setChecked(state)
+
+    def get_errors(self):
+        return self.errors.isChecked()
+
+    """ load/save from/to context """
+
+    def loadFromContext(self, context):
+        self.x_axis_changer.set_bounds(context["x bounds"])
+        self.y_axis_changer.set_bounds(context["y bounds"])
+
+    def getSubContext(self):
+        subcontext = {}
+        subcontext["x bounds"] = self.x_axis_changer.get_bounds()
+        subcontext["y bounds"] = self.y_axis_changer.get_bounds()
+        return subcontext
diff --git a/scripts/MultiPlotting/QuickEdit/quickEdit_widget.py b/scripts/MultiPlotting/QuickEdit/quickEdit_widget.py
new file mode 100644
index 0000000000000000000000000000000000000000..4fbe0b316cfe7bc80ae1a67da4e505f01c8e0fcc
--- /dev/null
+++ b/scripts/MultiPlotting/QuickEdit/quickEdit_widget.py
@@ -0,0 +1,60 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import absolute_import, print_function
+
+
+from MultiPlotting.QuickEdit.quickEdit_view import QuickEditView
+from MultiPlotting.QuickEdit.quickEdit_presenter import QuickEditPresenter
+
+
+class QuickEditWidget(object):
+
+    def __init__(self, parent=None):
+        view = QuickEditView(None, parent)
+        self._presenter = QuickEditPresenter(view)
+
+    @property
+    def widget(self):
+        return self._presenter.widget
+    """ connect statements"""
+
+    def connect_autoscale_changed(self, slot):
+        self._presenter.connect_autoscale_changed(slot)
+
+    def connect_errors_changed(self, slot):
+        self._presenter.connect_errors_changed(slot)
+
+    def connect_x_range_changed(self, slot):
+        self._presenter.connect_x_range_changed(slot)
+
+    def connect_y_range_changed(self, slot):
+        self._presenter.connect_y_range_changed(slot)
+
+    def connect_plot_selection(self, slot):
+        self._presenter.connect_plot_selection(slot)
+    # add subplot
+
+    def add_subplot(self, name):
+        self._presenter.add_subplot(name)
+
+    def get_selection(self):
+        name = self._presenter.widget.current_selection()
+        if name == "All":
+            return self._presenter.all()
+        return [name]
+
+    def set_plot_x_range(self, range):
+        self._presenter.set_plot_x_range(range)
+
+    def set_plot_y_range(self, range):
+        self._presenter.set_plot_y_range(range)
+
+    def set_errors(self, state):
+        self._presenter.set_errors(state)
+
+    def set_mock(self,mock_presenter):
+        self._presenter = mock_presenter
diff --git a/scripts/MultiPlotting/__init__.py b/scripts/MultiPlotting/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2cfb75232dfb59fac674e2b49dba356e18ebbc1
--- /dev/null
+++ b/scripts/MultiPlotting/__init__.py
@@ -0,0 +1,6 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
diff --git a/scripts/MultiPlotting/multiPlotting_context.py b/scripts/MultiPlotting/multiPlotting_context.py
new file mode 100644
index 0000000000000000000000000000000000000000..9cd418ee5be570cf38f6730cbbfaabf9723b9a78
--- /dev/null
+++ b/scripts/MultiPlotting/multiPlotting_context.py
@@ -0,0 +1,46 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import absolute_import, print_function
+from MultiPlotting.subplot.subPlot_context import subPlotContext
+
+
+xBounds = "xBounds"
+yBounds = "yBounds"
+
+
+class PlottingContext(object):
+
+    def __init__(self):
+        self.context = {}
+        self.subplots = {}
+        self.context[xBounds] = [0., 0.]
+        self.context[yBounds] = [0., 0.]
+
+    def addSubplot(self, name, subplot):
+        self.subplots[name] = subPlotContext(name, subplot)
+
+    def addLine(self, subplotName, workspace, specNum):
+        try:
+            if len(workspace) > 1:
+                self.subplots[subplotName].addLine(
+                    workspace.OutputWorkspace, specNum)
+            else:
+                self.subplots[subplotName].addLine(workspace, specNum)
+        except:
+            print("cannot plot workspace")
+
+    def get_xBounds(self):
+        return self.context[xBounds]
+
+    def get_yBounds(self):
+        return self.context[yBounds]
+
+    def set_xBounds(self, values):
+        self.context[xBounds] = values
+
+    def set_yBounds(self, values):
+        self.context[yBounds] = values
diff --git a/scripts/MultiPlotting/multiPlotting_widget.py b/scripts/MultiPlotting/multiPlotting_widget.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e68f877ad7fb12c0355e2d540f8582e99c743fd
--- /dev/null
+++ b/scripts/MultiPlotting/multiPlotting_widget.py
@@ -0,0 +1,144 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+from qtpy import QtWidgets, QtCore
+from copy import deepcopy
+from MultiPlotting.subplot.subPlot import subPlot
+from MultiPlotting.QuickEdit.quickEdit_widget import QuickEditWidget
+
+
+class MultiPlotWidget(QtWidgets.QWidget):
+
+    def __init__(self, context, parent=None):
+        super(MultiPlotWidget, self).__init__()
+        self._context = context
+        layout = QtWidgets.QVBoxLayout()
+        splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
+        self.quickEdit = QuickEditWidget(self)
+        self.quickEdit.connect_x_range_changed(self._x_range_changed)
+        self.quickEdit.connect_y_range_changed(self._y_range_changed)
+        self.quickEdit.connect_errors_changed(self._errors_changed)
+        self.quickEdit.connect_autoscale_changed(self._autoscale_changed)
+        self.quickEdit.connect_plot_selection(self._selection_changed)
+
+        # add some dummy plot
+        self.plots = subPlot(self._context)
+        self.plots.connect_quick_edit_signal(self._update_quick_edit)
+
+        # create GUI layout
+        splitter.addWidget(self.plots)
+        splitter.addWidget(self.quickEdit.widget)
+        layout.addWidget(splitter)
+        self.setLayout(layout)
+
+    """ plotting """
+
+    def add_subplot(self, name, code):
+        self.plots.add_subplot(name, code)
+        self.quickEdit.add_subplot(name)
+
+    def plot(self, subplotName, ws, specNum=1):
+        self.plots.plot(subplotName, ws, specNum=specNum)
+
+    # gets inital values for quickEdit
+    def set_all_values(self):
+        names = self.quickEdit.get_selection()
+        xrange = list(self._context.subplots[names[0]].xbounds)
+        yrange = list(self._context.subplots[names[0]].ybounds)
+        for name in names:
+            xbounds = self._context.subplots[name].xbounds
+            ybounds = self._context.subplots[name].ybounds
+            if xrange[0] > xbounds[0]:
+                xrange[0] = deepcopy(xbounds[0])
+            if xrange[1] < xbounds[1]:
+                xrange[1] = deepcopy(xbounds[1])
+            if yrange[0] > ybounds[0]:
+                yrange[0] = deepcopy(ybounds[0])
+            if yrange[1] < ybounds[1]:
+                yrange[1] = deepcopy(ybounds[1])
+        self._context.set_xBounds(xrange)
+        self._context.set_yBounds(yrange)
+        self._x_range_changed(xrange)
+        self._y_range_changed(yrange)
+        # get tick boxes correct
+        errors = self._check_all_errors(names)
+        self.quickEdit.set_errors(errors)
+        self._change_errors(errors, names)
+
+    """ update GUI """
+
+    def _update_quick_edit(self, subplotName):
+        names = self.quickEdit.get_selection()
+        xrange = self._context.subplots[subplotName].xbounds
+        yrange = self._context.subplots[subplotName].ybounds
+        if len(names) == 0:
+            return
+        # if all selected update everyone
+        if len(names) > 1:
+            self.quickEdit.set_plot_x_range(xrange)
+            self.quickEdit.set_plot_y_range(yrange)
+        # if changed current selection
+        elif names[0] == subplotName:
+            self.quickEdit.set_plot_x_range(xrange)
+            self.quickEdit.set_plot_y_range(yrange)
+        # if a different plot changed
+        else:
+            pass
+
+    def _selection_changed(self, index):
+        names = self.quickEdit.get_selection()
+        xrange = self._context.get_xBounds()
+        yrange = self._context.get_yBounds()
+        errors = True
+        if len(names) == 1:
+            xrange = self._context.subplots[names[0]].xbounds
+            yrange = self._context.subplots[names[0]].ybounds
+            errors = self._context.subplots[names[0]].errors
+        else:
+            errors = self._check_all_errors(names)
+        # update values
+        self.quickEdit.set_errors(errors)
+        self._change_errors(errors, names)
+        self.quickEdit.set_plot_x_range(xrange)
+        self.quickEdit.set_plot_y_range(yrange)
+
+        # force update of plots if selection is all
+        if len(names) > 1:
+            self._x_range_changed(xrange)
+            self._y_range_changed(yrange)
+
+    def _autoscale_changed(self, state):
+        names = self.quickEdit.get_selection()
+        self.plots.set_y_autoscale(names, True)
+
+    def _change_errors(self, state, names):
+        self.plots.change_errors(state, names)
+
+    def _errors_changed(self, state):
+        names = self.quickEdit.get_selection()
+        self._change_errors(state, names)
+
+    def _x_range_changed(self, xRange):
+        names = self.quickEdit.get_selection()
+        if len(names) > 1:
+            self._context.set_xBounds(xRange)
+        self.plots.set_plot_x_range(names, xRange)
+        self.quickEdit.set_plot_x_range(xRange)
+
+    def _y_range_changed(self, yRange):
+        names = self.quickEdit.get_selection()
+        if len(names) > 1:
+            self._context.set_yBounds(yRange)
+        self.plots.set_plot_y_range(names, yRange)
+        self.quickEdit.set_plot_y_range(yRange)
+
+    def _check_all_errors(self, names):
+        for name in names:
+            if self._context.subplots[name].errors is False:
+                return False
+        return True
diff --git a/scripts/MultiPlotting/navigation_toolbar.py b/scripts/MultiPlotting/navigation_toolbar.py
new file mode 100644
index 0000000000000000000000000000000000000000..20ae35e3c441c266309be31f9ee03753a6a3fd49
--- /dev/null
+++ b/scripts/MultiPlotting/navigation_toolbar.py
@@ -0,0 +1,23 @@
+from __future__ import (absolute_import, division, print_function)
+#from qtpy import QtGui
+from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
+
+
+class myToolbar(NavigationToolbar):
+    # only display the buttons we need
+    toolitems = [tool for tool in NavigationToolbar.toolitems if
+                 tool[0] in ("Home", "Save", "Pan", "Zoom")]
+
+    def __init__(self, *args, **kwargs):
+        super(myToolbar, self).__init__(*args, **kwargs)
+        self.layout().takeAt(5)  # or more than 1 if you have more buttons
+        #pm = QtGui.QPixmap()
+        #ic = QtGui.QIcon(pm)
+        # self.add = self.addAction(ic, "Add line")
+        # self.rm = self.addAction(ic, "Remove line")
+
+    def setAddConnection(self, slot):
+        self.add.triggered.connect(slot)
+
+    def setRmConnection(self, slot):
+        self.rm.triggered.connect(slot)
diff --git a/scripts/MultiPlotting/subplot/__init__.py b/scripts/MultiPlotting/subplot/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2cfb75232dfb59fac674e2b49dba356e18ebbc1
--- /dev/null
+++ b/scripts/MultiPlotting/subplot/__init__.py
@@ -0,0 +1,6 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
diff --git a/scripts/MultiPlotting/subplot/subPlot.py b/scripts/MultiPlotting/subplot/subPlot.py
new file mode 100644
index 0000000000000000000000000000000000000000..98979c6e31cfeb02730ffb2a4b0d04ff7d660768
--- /dev/null
+++ b/scripts/MultiPlotting/subplot/subPlot.py
@@ -0,0 +1,96 @@
+from __future__ import (absolute_import, division, print_function)
+
+from qtpy import QtWidgets, QtCore
+
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
+
+from MultiPlotting.navigation_toolbar import myToolbar
+
+# use this to manage lines and workspaces directly
+
+# visualises multiple plots
+
+
+class subPlot(QtWidgets.QWidget):
+    quickEditSignal = QtCore.Signal(object)
+
+    def __init__(self, context):
+        super(subPlot, self).__init__()
+        self._context = context
+        self.figure = Figure()
+        self.figure.set_facecolor("none")
+        self.canvas = FigureCanvas(self.figure)
+        # update quick edit from tool bar
+        self.canvas.mpl_connect("draw_event", self.draw_event_callback)
+
+        grid = QtWidgets.QGridLayout()
+        # add toolbar
+        self.toolbar = myToolbar(self.canvas, self)
+        self.toolbar.update()
+        grid.addWidget(self.toolbar, 0, 0)
+        # add plot
+        self.plotObjects = {}
+        grid.addWidget(self.canvas, 1, 0)
+        self.setLayout(grid)
+
+    """ this is called when the zoom
+    or pan are used. We want to send a
+    signal to update the axis ranges """
+
+    def draw_event_callback(self, event):
+        self.figure.tight_layout()
+        for subplot in self.plotObjects.keys():
+            self.emit_subplot_range(subplot)
+
+    # plot a workspace, if a new subplot create it.
+    def plot(self, subplotName, workspace, specNum=1):
+        new = False
+        if subplotName not in self._context.subplots.keys():
+            self.add_subplot(subplotName)
+            new = True
+        self._add_plotted_line(subplotName, workspace, specNum=specNum)
+        if new:
+            self.emit_subplot_range(subplotName)
+
+    def change_errors(self, state, subplotNames):
+        for subplotName in subplotNames:
+            self._context.subplots[subplotName].change_errors(state)
+            self.canvas.draw()
+
+    # adds plotted line to context and updates GUI
+    def _add_plotted_line(self, subplotName, workspace, specNum):
+        """ Appends plotted lines to the related subplot list. """
+        self._context.addLine(subplotName, workspace, specNum)
+        self.canvas.draw()
+
+    def add_subplot(self, subplotName, code=111):
+        self.plotObjects[subplotName] = self.figure.add_subplot(code)
+        self.plotObjects[subplotName].set_title(subplotName)
+        self._context.addSubplot(subplotName, self.plotObjects[subplotName])
+        self.figure.tight_layout()
+
+    def emit_subplot_range(self, subplotName):
+        self.quickEditSignal.emit(subplotName)
+
+    def set_plot_x_range(self, subplotNames, range):
+        for subplotName in subplotNames:
+            # make a set method in context and set it there
+            self.plotObjects[subplotName].set_xlim(range)
+            self.canvas.draw()
+
+    def set_plot_y_range(self, subplotNames, range):
+        for subplotName in subplotNames:
+            self.plotObjects[subplotName].set_ylim(range)
+            self.canvas.draw()
+
+    def connect_quick_edit_signal(self, slot):
+        self.quickEditSignal.connect(slot)
+
+    def disconnect_quick_edit_signal(self, slot):
+        self.quickEditSignal.disconnect(slot)
+
+    def set_y_autoscale(self, subplotNames, state):
+        for subplotName in subplotNames:
+            self._context.subplots[subplotName].change_auto(state)
+            self.canvas.draw()
diff --git a/scripts/MultiPlotting/subplot/subPlot_context.py b/scripts/MultiPlotting/subplot/subPlot_context.py
new file mode 100644
index 0000000000000000000000000000000000000000..146f4ebdca6a85561e3ba827acbb667cb3ddf99f
--- /dev/null
+++ b/scripts/MultiPlotting/subplot/subPlot_context.py
@@ -0,0 +1,121 @@
+from __future__ import (absolute_import, division, print_function)
+from six import iteritems
+from mantid import plots
+from copy import copy
+
+# use this to manage lines and workspaces directly
+# store errors in here
+# to recover plot when using the plot selector
+
+
+class subPlotContext(object):
+
+    def __init__(self, name, subplot):
+        self.name = name
+        self._subplot = subplot
+        self._ws = {}
+        self._lines = {}
+        self._specNum = {}
+        self._errors = False
+
+    def addLine(self, ws, specNum=1):
+        # make plot/get label
+        line, = plots.plotfunctions.plot(self._subplot, ws, specNum=specNum)
+        label = line.get_label()
+        if self._errors:
+            line, cap_lines, bar_lines = plots.plotfunctions.errorbar(
+                self._subplot, ws, specNum=specNum, label=label)
+            all_lines = [line]
+            all_lines.extend(cap_lines)
+            all_lines.extend(bar_lines)
+            self._lines[label] = all_lines
+        else:
+            # line will be a list - will include error bars
+            self._lines[label] = [line]
+        self._specNum[label] = specNum
+        # store the ws as the key and have a list of labels that use that ws
+        if ws not in self._ws.keys():
+            self._ws[ws] = [label]
+        else:
+            self._ws[ws].append(label)
+
+    def redraw(self, label):
+        # get current colour and marker
+        colour = copy(self._lines[label][0].get_color())
+        marker = copy(self._lines[label][0].get_marker())
+        # delete old lines
+        for line in self._lines[label]:
+            line.remove()
+        del self._lines[label]
+        # replot the line
+        if self._errors:
+            line, cap_lines, bar_lines = plots.plotfunctions.errorbar(self._subplot, self.get_ws(
+                label), specNum=self.specNum[label], color=colour, marker=marker, label=label)
+            all_lines = [line]
+            all_lines.extend(cap_lines)
+            all_lines.extend(bar_lines)
+            self._lines[label] = all_lines
+        else:
+            line, = plots.plotfunctions.plot(self._subplot, self.get_ws(
+                label), specNum=self.specNum[label], color=colour, marker=marker, label=label)
+            self._lines[label] = [line]
+
+    def change_errors(self, state):
+        self._errors = state
+        for label in self._lines.keys():
+            self.redraw(label)
+
+    def change_auto(self, state):
+        for label in self._lines.keys():
+            self._subplot.autoscale(enable=state, axis="y")
+
+    @property
+    def lines(self):
+        return self._lines
+
+    @property
+    def ws(self):
+        return self._ws
+
+    @property
+    def specNum(self):
+        return self._specNum
+
+    @property
+    def errors(self):
+        return self._errors
+
+    @property
+    def xbounds(self):
+        return self._subplot.get_xlim()
+
+    @property
+    def ybounds(self):
+        return self._subplot.get_ylim()
+
+    def get_ws(self, label):
+        for ws in self._ws.keys():
+            if label in self._ws[ws]:
+                return ws
+
+    def removeLine(self, name):
+        lines = self._lines[name]
+        for line in lines:
+            line.remove()
+            del line
+        del self._lines[name]
+        del self._specNum[name]
+        # remove line for ws
+        to_delete = []
+        for key, list in iteritems(self._ws):
+            if name in list:
+                list.remove(name)
+                if len(list) == 0:
+                    to_delete.append(key)
+        for key in to_delete:
+            del self._ws[key]
+
+    def delete(self):
+        keys = self._lines.keys()
+        for label in keys:
+            self.removeLine(label)
diff --git a/scripts/Muon/GUI/Common/grouping_table_widget/__init__.py b/scripts/Muon/GUI/Common/grouping_table_widget/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_model.py b/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..43b3e822e824b09999d47b7df5577096e58023f1
--- /dev/null
+++ b/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_model.py
@@ -0,0 +1,41 @@
+from __future__ import (absolute_import, division, print_function)
+
+from Muon.GUI.Common.muon_data_context import MuonDataContext, construct_empty_group
+
+
+class GroupingTableModel(object):
+    """
+    Model used when grouping table is not part of the grouping tab. When the
+    grouping table presenter is embedded in the grouping tab, use the GroupingTabModel instead.
+
+    The model takes an instance of the MuonContext as an input, most of its functionality is simply
+    a callback to this class.
+    """
+
+    def __init__(self, data=MuonDataContext()):
+        self._data = data
+
+    @property
+    def groups(self):
+        return self._data.groups
+
+    @property
+    def group_names(self):
+        return list(self._data.group_names)
+
+    @property
+    def group_and_pair_names(self):
+        return list(self._data.group_names) + list(self._data.pair_names)
+
+    def add_group(self, group):
+        self._data.add_group(group)
+
+    def remove_groups_by_name(self, name_list):
+        for name in name_list:
+            del self._data.groups[name]
+
+    def construct_empty_group(self, group_index):
+        return construct_empty_group(self.group_names, group_index)
+
+    def clear_groups(self):
+        self._data.clear_groups()
diff --git a/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_presenter.py b/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..73a99a78a24b30fa72475a1c9c87a3e90fb8692c
--- /dev/null
+++ b/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_presenter.py
@@ -0,0 +1,136 @@
+from __future__ import (absolute_import, division, print_function)
+
+import re
+
+from Muon.GUI.Common.utilities import run_string_utils as run_utils
+from Muon.GUI.Common.muon_group import MuonGroup
+
+maximum_number_of_groups = 20
+
+
+class GroupingTablePresenter(object):
+
+    def __init__(self, view, model):
+        self._view = view
+        self._model = model
+
+        self._view.on_add_group_button_clicked(self.handle_add_group_button_clicked)
+        self._view.on_remove_group_button_clicked(self.handle_remove_group_button_clicked)
+
+        self._view.on_user_changes_group_name(self.validate_group_name)
+        self._view.on_user_changes_detector_IDs(self.validate_detector_ids)
+
+        self._view.on_table_data_changed(self.handle_data_change)
+
+        self._dataChangedNotifier = lambda: 0
+
+    def show(self):
+        self._view.show()
+
+    def on_data_changed(self, notifier):
+        self._dataChangedNotifier = notifier
+
+    def notify_data_changed(self):
+        self._dataChangedNotifier()
+
+    def _is_edited_name_duplicated(self, new_name):
+        is_name_column_being_edited = self._view.grouping_table.currentColumn() == 0
+        is_name_unique = True
+        if new_name in self._model.group_and_pair_names:
+            is_name_unique = False
+        return is_name_column_being_edited and not is_name_unique
+
+    def validate_group_name(self, text):
+        if self._is_edited_name_duplicated(text):
+            self._view.warning_popup("Groups and pairs must have unique names")
+            return False
+        if not re.match(run_utils.valid_name_regex, text):
+            self._view.warning_popup("Group names should only contain digits, characters and _")
+            return False
+        return True
+
+    def validate_detector_ids(self, text):
+        if re.match(run_utils.run_string_regex, text):
+            return True
+        self._view.warning_popup("Invalid detector list.")
+        return False
+
+    def disable_editing(self):
+        self._view.disable_editing()
+
+    def enable_editing(self):
+        self._view.enable_editing()
+
+    def add_group(self, group):
+        """Adds a group to the model and view"""
+        if self._view.num_rows() >= maximum_number_of_groups:
+            self._view.warning_popup("Cannot add more than {} groups.".format(maximum_number_of_groups))
+            return
+        self.add_group_to_view(group)
+        self.add_group_to_model(group)
+        self._view.notify_data_changed()
+        self.notify_data_changed()
+
+    def add_group_to_model(self, group):
+        self._model.add_group(group)
+
+    def add_group_to_view(self, group):
+        self._view.disable_updates()
+        assert isinstance(group, MuonGroup)
+        entry = [str(group.name), run_utils.run_list_to_string(group.detectors), str(group.n_detectors)]
+        self._view.add_entry_to_table(entry)
+        self._view.enable_updates()
+
+    def handle_add_group_button_clicked(self):
+        group = self._model.construct_empty_group(self._view.num_rows() + 1)
+        self.add_group(group)
+
+    def handle_remove_group_button_clicked(self):
+        group_names = self._view.get_selected_group_names()
+        if not group_names:
+            self.remove_last_row_in_view_and_model()
+        else:
+            self.remove_selected_rows_in_view_and_model(group_names)
+        self._view.notify_data_changed()
+        self.notify_data_changed()
+
+    def remove_selected_rows_in_view_and_model(self, group_names):
+        self._view.remove_selected_groups()
+        self._model.remove_groups_by_name(group_names)
+
+    def remove_last_row_in_view_and_model(self):
+        if self._view.num_rows() > 0:
+            name = self._view.get_table_contents()[-1][0]
+            self._view.remove_last_row()
+            self._model.remove_groups_by_name([name])
+
+    def handle_data_change(self, row, col):
+        changed_item = self._view.get_table_item_text(row, col)
+        update_model = True
+        if col == 0 and not self.validate_group_name(changed_item):
+            update_model = False
+        if col == 1 and not self.validate_detector_ids(changed_item):
+            update_model = False
+        if update_model:
+            self.update_model_from_view()
+
+        self.update_view_from_model()
+        self._view.notify_data_changed()
+        self.notify_data_changed()
+
+    def update_model_from_view(self):
+        table = self._view.get_table_contents()
+        self._model.clear_groups()
+        for entry in table:
+            detector_list = run_utils.run_string_to_list(str(entry[1]))
+            group = MuonGroup(group_name=str(entry[0]), detector_ids=detector_list)
+            self._model.add_group(group)
+
+    def update_view_from_model(self):
+        self._view.disable_updates()
+
+        self._view.clear()
+        for name, group in self._model.groups.items():
+            self.add_group_to_view(group)
+
+        self._view.enable_updates()
diff --git a/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_view.py b/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..5776537482da8af57b53c2aa0e40e91e6d1cea3a
--- /dev/null
+++ b/scripts/Muon/GUI/Common/grouping_table_widget/grouping_table_widget_view.py
@@ -0,0 +1,304 @@
+from __future__ import (absolute_import, division, print_function)
+
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import pyqtSignal as Signal
+
+from Muon.GUI.Common.utilities import table_utils
+from Muon.GUI.Common import message_box
+
+group_table_columns = {0: 'group_name', 1: 'detector_ids', 2: 'number_of_detectors'}
+
+
+class GroupingTableView(QtGui.QWidget):
+    # For use by parent widget
+    dataChanged = Signal()
+    addPairRequested = Signal(str, str)
+
+    @staticmethod
+    def warning_popup(message):
+        message_box.warning(str(message))
+
+    def __init__(self, parent=None):
+        super(GroupingTableView, self).__init__(parent)
+
+        self.grouping_table = QtGui.QTableWidget(self)
+        self.set_up_table()
+
+        self.setup_interface_layout()
+
+        self.grouping_table.cellChanged.connect(self.on_cell_changed)
+
+        self._validate_group_name_entry = lambda text: True
+        self._validate_detector_ID_entry = lambda text: True
+        self._on_table_data_changed = lambda: 0
+
+        # whether the table is updating and therefore we shouldn't respond to signals
+        self._updating = False
+        # whether the interface should be disabled
+        self._disabled = False
+
+    def setup_interface_layout(self):
+        self.setObjectName("GroupingTableView")
+        self.resize(500, 500)
+
+        self.add_group_button = QtGui.QToolButton()
+        self.remove_group_button = QtGui.QToolButton()
+
+        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+        size_policy.setHorizontalStretch(0)
+        size_policy.setVerticalStretch(0)
+        size_policy.setHeightForWidth(self.add_group_button.sizePolicy().hasHeightForWidth())
+        size_policy.setHeightForWidth(self.remove_group_button.sizePolicy().hasHeightForWidth())
+
+        self.add_group_button.setSizePolicy(size_policy)
+        self.add_group_button.setObjectName("addGroupButton")
+        self.add_group_button.setToolTip("Add a group to the end of the table")
+        self.add_group_button.setText("+")
+
+        self.remove_group_button.setSizePolicy(size_policy)
+        self.remove_group_button.setObjectName("removeGroupButton")
+        self.remove_group_button.setToolTip("Remove selected/last group(s) from the table")
+        self.remove_group_button.setText("-")
+
+        self.horizontal_layout = QtGui.QHBoxLayout()
+        self.horizontal_layout.setObjectName("horizontalLayout")
+        self.horizontal_layout.addWidget(self.add_group_button)
+        self.horizontal_layout.addWidget(self.remove_group_button)
+        self.spacer_item = QtGui.QSpacerItem(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
+        self.horizontal_layout.addItem(self.spacer_item)
+        self.horizontal_layout.setAlignment(QtCore.Qt.AlignLeft)
+
+        self.vertical_layout = QtGui.QVBoxLayout(self)
+        self.vertical_layout.setObjectName("verticalLayout")
+        self.vertical_layout.addWidget(self.grouping_table)
+        self.vertical_layout.addLayout(self.horizontal_layout)
+
+        self.setLayout(self.vertical_layout)
+
+    def set_up_table(self):
+        self.grouping_table.setColumnCount(3)
+        self.grouping_table.setHorizontalHeaderLabels(["Group Name", "Detector IDs", "N Detectors"])
+        header = self.grouping_table.horizontalHeader()
+        header.setResizeMode(0, QtGui.QHeaderView.Stretch)
+        header.setResizeMode(1, QtGui.QHeaderView.Stretch)
+        header.setResizeMode(2, QtGui.QHeaderView.ResizeToContents)
+        vertical_headers = self.grouping_table.verticalHeader()
+        vertical_headers.setMovable(False)
+        vertical_headers.setResizeMode(QtGui.QHeaderView.ResizeToContents)
+        vertical_headers.setVisible(True)
+
+        self.grouping_table.horizontalHeaderItem(0).setToolTip("The name of the group :"
+                                                               "\n    - The name must be unique across all groups/pairs"
+                                                               "\n    - The name can only use digits, characters and _")
+        self.grouping_table.horizontalHeaderItem(1).setToolTip("The sorted list of detectors :"
+                                                               "\n  - The list can only contain integers."
+                                                               "\n  - , is used to separate detectors or ranges."
+                                                               "\n  - \"-\" denotes a range, i,e \"1-5\" is the same as"
+                                                               " \"1,2,3,4,5\" ")
+        self.grouping_table.horizontalHeaderItem(2).setToolTip("The number of detectors in the group.")
+
+    def num_rows(self):
+        return self.grouping_table.rowCount()
+
+    def num_cols(self):
+        return self.grouping_table.columnCount()
+
+    def notify_data_changed(self):
+        if not self._updating:
+            self.dataChanged.emit()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Adding / removing table entries
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def add_entry_to_table(self, row_entries):
+        assert len(row_entries) == self.grouping_table.columnCount()
+
+        row_position = self.grouping_table.rowCount()
+        self.grouping_table.insertRow(row_position)
+        for i, entry in enumerate(row_entries):
+            item = QtGui.QTableWidgetItem(entry)
+            if group_table_columns[i] == group_table_columns[0]:
+                # column 0 : group name
+                group_name_widget = table_utils.ValidatedTableItem(self._validate_group_name_entry)
+                group_name_widget.setText(entry)
+                self.grouping_table.setItem(row_position, 0, group_name_widget)
+                self.grouping_table.item(row_position, 0).setToolTip(entry)
+            if group_table_columns[i] == group_table_columns[1]:
+                # column 1 : detector IDs
+                detector_widget = table_utils.ValidatedTableItem(self._validate_detector_ID_entry)
+                detector_widget.setText(entry)
+                self.grouping_table.setItem(row_position, 1, detector_widget)
+                self.grouping_table.item(row_position, 1).setToolTip(entry)
+            if group_table_columns[i] == group_table_columns[2]:
+                # column 2 : number of detectors
+                item.setFlags(QtCore.Qt.ItemIsEnabled)
+                item.setFlags(QtCore.Qt.ItemIsSelectable)
+            self.grouping_table.setItem(row_position, i, item)
+
+    def _get_selected_row_indices(self):
+        return list(set(index.row() for index in self.grouping_table.selectedIndexes()))
+
+    def get_selected_group_names(self):
+        indexes = self._get_selected_row_indices()
+        return [str(self.grouping_table.item(i, 0).text()) for i in indexes]
+
+    def remove_selected_groups(self):
+        indices = self._get_selected_row_indices()
+        for index in reversed(sorted(indices)):
+            self.grouping_table.removeRow(index)
+
+    def remove_last_row(self):
+        last_row = self.grouping_table.rowCount() - 1
+        if last_row >= 0:
+            self.grouping_table.removeRow(last_row)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Context menu on right-click in the table
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def _context_menu_add_group_action(self, slot):
+        add_group_action = QtGui.QAction('Add Group', self)
+        if len(self._get_selected_row_indices()) > 0:
+            add_group_action.setEnabled(False)
+        add_group_action.triggered.connect(slot)
+        return add_group_action
+
+    def _context_menu_remove_group_action(self, slot):
+        if len(self._get_selected_row_indices()) > 1:
+            # use plural if >1 item selected
+            remove_group_action = QtGui.QAction('Remove Groups', self)
+        else:
+            remove_group_action = QtGui.QAction('Remove Group', self)
+        if self.num_rows() == 0:
+            remove_group_action.setEnabled(False)
+        remove_group_action.triggered.connect(slot)
+        return remove_group_action
+
+    def _context_menu_add_pair_action(self, slot):
+        add_pair_action = QtGui.QAction('Add Pair', self)
+        if len(self._get_selected_row_indices()) != 2:
+            add_pair_action.setEnabled(False)
+        add_pair_action.triggered.connect(slot)
+        return add_pair_action
+
+    def contextMenuEvent(self, _event):
+        """Overridden method"""
+        self.menu = QtGui.QMenu(self)
+
+        self.add_group_action = self._context_menu_add_group_action(self.add_group_button.clicked.emit)
+        self.remove_group_action = self._context_menu_remove_group_action(self.remove_group_button.clicked.emit)
+        self.add_pair_action = self._context_menu_add_pair_action(self.add_pair_requested)
+
+        if self._disabled:
+            self.add_group_action.setEnabled(False)
+            self.remove_group_action.setEnabled(False)
+            self.add_pair_action.setEnabled(False)
+
+        self.menu.addAction(self.add_group_action)
+        self.menu.addAction(self.remove_group_action)
+        self.menu.addAction(self.add_pair_action)
+
+        self.menu.popup(QtGui.QCursor.pos())
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Slot connections
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def on_user_changes_group_name(self, slot):
+        self._validate_group_name_entry = slot
+
+    def on_user_changes_detector_IDs(self, slot):
+        self._validate_detector_ID_entry = slot
+
+    def on_add_group_button_clicked(self, slot):
+        self.add_group_button.clicked.connect(slot)
+
+    def on_remove_group_button_clicked(self, slot):
+        self.remove_group_button.clicked.connect(slot)
+
+    def on_table_data_changed(self, slot):
+        self._on_table_data_changed = slot
+
+    def add_pair_requested(self):
+        selected_names = self.get_selected_group_names()
+        self.addPairRequested.emit(selected_names[0], selected_names[1])
+
+    def on_cell_changed(self, _row, _col):
+        if not self._updating:
+            self._on_table_data_changed(_row, _col)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    #
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def get_table_item_text(self, row, col):
+        return self.grouping_table.item(row, col).text()
+
+    def get_table_contents(self):
+        if self._updating:
+            return []
+        ret = []
+        for row in range(self.num_rows()):
+            row_list = []
+            for col in range(self.num_cols()):
+                row_list.append(str(self.grouping_table.item(row, col).text()))
+            ret.append(row_list)
+        return ret
+
+    def clear(self):
+        # Go backwards to preserve indices
+        for row in reversed(range(self.num_rows())):
+            self.grouping_table.removeRow(row)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Enabling and disabling editing and updating of the widget
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def disable_updates(self):
+        """Usage : """
+        self._updating = True
+
+    def enable_updates(self):
+        """Usage : """
+        self._updating = False
+
+    def disable_editing(self):
+        self.disable_updates()
+        self._disabled = True
+        self._disable_buttons()
+        self._disable_all_table_items()
+        self.enable_updates()
+
+    def enable_editing(self):
+        self.disable_updates()
+        self._disabled = False
+        self._enable_buttons()
+        self._enable_all_table_items()
+        self.enable_updates()
+
+    def _enable_buttons(self):
+        self.add_group_button.setEnabled(True)
+        self.remove_group_button.setEnabled(True)
+
+    def _disable_buttons(self):
+        self.add_group_button.setEnabled(False)
+        self.remove_group_button.setEnabled(False)
+
+    def _disable_all_table_items(self):
+        for row in range(self.num_rows()):
+            for col in range(self.num_cols()):
+                item = self.grouping_table.item(row, col)
+                item.setFlags(QtCore.Qt.ItemIsSelectable)
+
+    def _enable_all_table_items(self):
+        for row in range(self.num_rows()):
+            for col in range(self.num_cols()):
+                item = self.grouping_table.item(row, col)
+                if group_table_columns[col] != 'number_of_detectors':
+                    item.setFlags(QtCore.Qt.ItemIsSelectable |
+                                  QtCore.Qt.ItemIsEditable |
+                                  QtCore.Qt.ItemIsEnabled)
+                else:
+                    # number of detectors should remain un-editable
+                    item.setFlags(QtCore.Qt.ItemIsSelectable)
diff --git a/scripts/Muon/GUI/Common/home_grouping_widget/__init__.py b/scripts/Muon/GUI/Common/home_grouping_widget/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_model.py b/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..a05a0cb865ebaaf81c4163ae9cf6bd26aa3ad4bd
--- /dev/null
+++ b/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_model.py
@@ -0,0 +1,65 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+from Muon.GUI.Common.muon_context import MuonContext
+
+
+class HomeGroupingWidgetModel(object):
+
+    def __init__(self, muon_data=MuonContext()):
+        self._data = muon_data
+
+    def get_group_names(self):
+        return self._data.groups.keys()
+
+    def get_pair_names(self):
+        return self._data.pairs.keys()
+
+    def is_group(self, name):
+        return name in self.get_group_names()
+
+    def is_pair(self, name):
+        return name in self.get_pair_names()
+
+    def update_pair_alpha(self, pair_name, alpha):
+        try:
+            self._data.pairs[pair_name].alpha = alpha
+        except Exception:
+            print("Exception in update_pair_alpha")
+
+    def get_alpha(self, pair_name):
+        pair = self._data.pairs[pair_name]
+        if pair:
+            return pair.alpha
+        else:
+            return None
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Periods
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def is_data_multi_period(self):
+        return self._data.is_multi_period()
+
+    def number_of_periods(self):
+        if self.is_data_multi_period():
+            return len(self._data.current_data["OutputWorkspace"])
+        else:
+            return 1
+
+    def update_summed_periods(self, summed_periods):
+        self._data.current_data["SummedPeriods"] = summed_periods
+
+    def update_subtracted_periods(self, subtracted_periods):
+        self._data.current_data["SubtractedPeriods"] = subtracted_periods
+
+    def get_summed_periods(self):
+        return self._data.current_data["SummedPeriods"]
+
+    def get_subtracted_periods(self):
+        return self._data.current_data["SubtractedPeriods"]
diff --git a/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_presenter.py b/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5f1c01465c185b8a31e228858d143a9bc88e4d1
--- /dev/null
+++ b/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_presenter.py
@@ -0,0 +1,109 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+from Muon.GUI.Common.home_tab.home_tab_presenter import HomeTabSubWidget
+from Muon.GUI.Common.observer_pattern import Observable
+from Muon.GUI.Common.run_string_utils import run_string_to_list
+
+
+class HomeGroupingWidgetPresenter(HomeTabSubWidget):
+
+    @staticmethod
+    def string_to_list(text):
+        # if text == "":
+        #     return []
+        # return [int(i) for i in text.split(",")]
+        return run_string_to_list(text)
+
+    def __init__(self, view, model):
+        self._view = view
+        self._model = model
+
+        self._view.on_grouppair_selection_changed(self.handle_grouppair_selector_changed)
+
+        self._view.on_alpha_changed(self.handle_user_changes_alpha)
+
+        self._view.on_summed_periods_changed(self.handle_periods_changed)
+        self._view.on_subtracted_periods_changed(self.handle_periods_changed)
+
+        self.pairAlphaNotifier = HomeGroupingWidgetPresenter.PairAlphaNotifier(self)
+
+    def show(self):
+        self._view.show()
+
+    def handle_user_changes_alpha(self):
+        alpha = self._view.get_current_alpha()
+        pair_name = str(self._view.get_currently_selected_group_pair())
+        self._model.update_pair_alpha(pair_name, alpha)
+        # notify any observers of the change
+        self.pairAlphaNotifier.notify_subscribers()
+
+    def update_group_pair_list(self):
+        group_names = self._model.get_group_names()
+        pair_names = self._model.get_pair_names()
+        self._view.populate_group_pair_selector(group_names, pair_names)
+
+    def hide_multiperiod_widget_if_data_single_period(self):
+        if self._model.is_data_multi_period():
+            self._view.multi_period_widget_hidden(False)
+        else:
+            self._view.multi_period_widget_hidden(True)
+
+    def handle_grouppair_selector_changed(self):
+        name = str(self._view.get_selected_group_or_pair_name())
+        if self._model.is_group(name):
+            self._view.alpha_hidden(True)
+        elif self._model.is_pair(name):
+            self._view.alpha_hidden(False)
+            alpha = self._model.get_alpha(name)
+            self._view.set_current_alpha(alpha)
+        else:
+            self._view.alpha_hidden(True)
+
+    def update_view_from_model(self):
+        self.update_group_pair_list()
+        self.hide_multiperiod_widget_if_data_single_period()
+
+        n_periods = self._model.number_of_periods()
+        self._view.set_period_number_in_period_label(n_periods)
+
+    def update_period_edits(self):
+        summed_periods = self._model.get_summed_periods()
+        subtracted_periods = self._model.get_subtracted_periods()
+
+        self._view.set_summed_periods(",".join([str(p) for p in summed_periods]))
+        self._view.set_subtracted_periods(",".join([str(p) for p in subtracted_periods]))
+
+    def handle_periods_changed(self):
+        summed = self.string_to_list(self._view.get_summed_periods())
+        subtracted = self.string_to_list(self._view.get_subtracted_periods())
+
+        subtracted = [i for i in subtracted if i not in summed]
+
+        n_periods = self._model.number_of_periods()
+        bad_periods = [period for period in summed if (period > n_periods) or period == 0] +\
+                      [period for period in subtracted if(period > n_periods) or period == 0]
+        if len(bad_periods) > 0:
+            self._view.warning_popup("The following periods are invalid : " + ",".join([str(period) for period in bad_periods]))
+
+        summed = [p for p in summed if (p <= n_periods) and p > 0]
+        subtracted = [p for p in subtracted if (p <= n_periods) and p > 0]
+
+        self._model.update_summed_periods(summed)
+        self._model.update_subtracted_periods(subtracted)
+
+        self.update_period_edits()
+
+    class PairAlphaNotifier(Observable):
+
+        def __init__(self, outer):
+            Observable.__init__(self)
+            self.outer = outer  # handle to containing class
+
+        def notify_subscribers(self, *args, **kwargs):
+            Observable.notify_subscribers(self, *args, **kwargs)
diff --git a/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_view.py b/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac563f1854a6e7b8bd492ec7296cd68e754db55c
--- /dev/null
+++ b/scripts/Muon/GUI/Common/home_grouping_widget/home_grouping_widget_view.py
@@ -0,0 +1,231 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+from PyQt4 import QtGui, QtCore
+from Muon.GUI.Common.run_string_utils import valid_alpha_regex
+from Muon.GUI.Common.message_box import warning
+
+
+class HomeGroupingWidgetView(QtGui.QWidget):
+
+    def __init__(self, parent=None):
+        super(HomeGroupingWidgetView, self).__init__(parent)
+        self.setup_interface()
+
+        self.alpha_hidden(True)
+        self.periods_hidden(False)
+
+    def setup_interface(self, show_checks=False):
+        self.setObjectName("GroupingWidget")
+        self.resize(500, 100)
+
+        self.grouppair_label = QtGui.QLabel(self)
+        self.grouppair_label.setObjectName("groupPairLabel")
+        self.grouppair_label.setText("Group / ")
+
+        self.pair_label = QtGui.QLabel(self)
+        self.pair_label.setObjectName("pairLabel")
+        font = QtGui.QFont()
+        font.setBold(True)
+        self.pair_label.setFont(font)
+        self.pair_label.setText("Pair : ")
+
+        self.grouppair_selector = QtGui.QComboBox(self)
+        self.grouppair_selector.setObjectName("groupPairSelector")
+        self.grouppair_selector.addItems(["fwd", "bwd"])
+
+        self.alpha_label_2 = QtGui.QLabel(self)
+        self.alpha_label_2.setObjectName("AlphaLabel")
+        self.alpha_label_2.setText("Alpha : ")
+
+        self.alpha_edit = QtGui.QLineEdit(self)
+        self.alpha_edit.setObjectName("alphaEdit")
+        self.alpha_edit.setText("1.0")
+
+        reg_ex = QtCore.QRegExp(valid_alpha_regex)
+        alpha_validator = QtGui.QRegExpValidator(reg_ex, self.alpha_edit)
+        self.alpha_edit.setValidator(alpha_validator)
+
+        self.horizontal_layout = QtGui.QHBoxLayout()
+        self.horizontal_layout.setObjectName("horizontalLayout3")
+        self.horizontal_layout.addWidget(self.grouppair_label)
+        self.horizontal_layout.addWidget(self.pair_label)
+        self.horizontal_layout.addWidget(self.grouppair_selector)
+        self.horizontal_layout.addStretch(0)
+        self.horizontal_layout.addWidget(self.alpha_label_2)
+        self.horizontal_layout.addWidget(self.alpha_edit)
+
+        self.period_label = QtGui.QLabel(self)
+        self.period_label.setObjectName("periodLabel")
+        self.period_label.setText("Data collected in n periods. Plot/analysis period(s) : ")
+
+        self.summed_period_edit = QtGui.QLineEdit(self)
+        self.summed_period_edit.setText("1")
+        reg_ex = QtCore.QRegExp("^[0-9]*([0-9]+[,-]{0,1})*[0-9]+$")
+        period_validator = QtGui.QRegExpValidator(reg_ex, self.summed_period_edit)
+        self.summed_period_edit.setValidator(period_validator)
+
+        self.minus_label = QtGui.QLabel(self)
+        self.minus_label.setObjectName("minusLabel")
+        self.minus_label.setText("-")
+
+        self.subtracted_period_edit = QtGui.QLineEdit(self)
+        self.subtracted_period_edit.setText("")
+        period_validator = QtGui.QRegExpValidator(reg_ex, self.subtracted_period_edit)
+        self.subtracted_period_edit.setValidator(period_validator)
+
+        self.horizontal_layout_2 = QtGui.QHBoxLayout()
+        self.horizontal_layout_2.setObjectName("horizontalLayout2")
+        self.horizontal_layout_2.addWidget(self.period_label)
+        self.horizontal_layout_2.addStretch(0)
+        self.horizontal_layout_2.addWidget(self.summed_period_edit)
+        self.horizontal_layout_2.addSpacing(10)
+        self.horizontal_layout_2.addWidget(self.minus_label)
+        self.horizontal_layout_2.addSpacing(10)
+        self.horizontal_layout_2.addWidget(self.subtracted_period_edit)
+
+        self.group = QtGui.QGroupBox("Groups and Pairs")
+        self.group.setFlat(False)
+        self.setStyleSheet("QGroupBox {border: 1px solid grey;border-radius: 10px;margin-top: 1ex; margin-right: 0ex}"
+                           "QGroupBox:title {"
+                           'subcontrol-origin: margin;'
+                           "padding: 0 3px;"
+                           'subcontrol-position: top center;'
+                           'padding-top: -10px;'
+                           'padding-bottom: 0px;'
+                           "padding-right: 10px;"
+                           ' color: grey; }')
+
+        self.vertical_layout = QtGui.QVBoxLayout(self)
+        self.vertical_layout.setObjectName("verticalLayout")
+        self.vertical_layout.addItem(self.horizontal_layout)
+        self.vertical_layout.addItem(self.horizontal_layout_2)
+        if show_checks:
+            self.all_groups_label = QtGui.QLabel(self)
+            self.all_groups_label.setText("Apply to all groups : ")
+            self.all_groups_checkbox = QtGui.QCheckBox(self)
+            self.all_pairs_label = QtGui.QLabel(self)
+            self.all_pairs_label.setText("Apply to all pairs (alpha will not be applied)  : ")
+            self.all_pairs_checkbox = QtGui.QCheckBox(self)
+
+            self.horizontal_layout_3 = QtGui.QHBoxLayout()
+            self.horizontal_layout_3.setObjectName("horizontalLayout2")
+            self.horizontal_layout_3.addWidget(self.all_groups_label)
+            self.horizontal_layout_3.addWidget(self.all_groups_checkbox)
+
+            self.horizontal_layout_4 = QtGui.QHBoxLayout()
+            self.horizontal_layout_4.addWidget(self.all_pairs_label)
+            self.horizontal_layout_4.addWidget(self.all_pairs_checkbox)
+            self.vertical_layout.addItem(self.horizontal_layout_3)
+            self.vertical_layout.addItem(self.horizontal_layout_4)
+
+        self.group.setLayout(self.vertical_layout)
+
+        self.widget_layout = QtGui.QVBoxLayout(self)
+        self.widget_layout.addWidget(self.group)
+        self.setLayout(self.widget_layout)
+
+    def warning_popup(self, message):
+        warning(message, parent=self)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Groups and Pairs
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def populate_group_pair_selector(self, group_names, pair_names):
+        self.grouppair_selector.clear()
+
+        model = self.grouppair_selector.model()
+        for name in group_names:
+            item = QtGui.QStandardItem(str(name))
+            font = item.font()
+            item.setFont(font)
+            model.appendRow(item)
+        for name in pair_names:
+            item = QtGui.QStandardItem(str(name))
+            #item.setForeground(QtGui.QColor('red'))
+            font = item.font()
+            font.setBold(True)
+            item.setFont(font)
+            model.appendRow(item)
+
+    def get_selected_group_or_pair_name(self):
+        return self.grouppair_selector.currentText()
+
+    def on_grouppair_selection_changed(self, slot):
+        self.grouppair_selector.currentIndexChanged.connect(slot)
+
+    def get_currently_selected_group_pair(self):
+        return self.grouppair_selector.currentText()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Alpha
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def on_alpha_changed(self, slot):
+        self.alpha_edit.editingFinished.connect(slot)
+
+    def set_current_alpha(self, alpha):
+        self.alpha_edit.setText(str(alpha))
+
+    def get_current_alpha(self):
+        return float(self.alpha_edit.text())
+
+    def alpha_hidden(self, hidden=True):
+        """
+        hide/show the functionality for the alpha parameter
+        """
+        if hidden:
+            self.alpha_label_2.hide()
+            self.alpha_edit.hide()
+        if not hidden:
+            self.alpha_label_2.setVisible(True)
+            self.alpha_edit.setVisible(True)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Periods
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def set_summed_periods(self, text):
+        self.summed_period_edit.setText(text)
+
+    def set_subtracted_periods(self, text):
+        self.subtracted_period_edit.setText(text)
+
+    def get_summed_periods(self):
+        return str(self.summed_period_edit.text())
+
+    def get_subtracted_periods(self):
+        return str(self.subtracted_period_edit.text())
+
+    def on_summed_periods_changed(self, slot):
+        self.summed_period_edit.editingFinished.connect(slot)
+
+    def on_subtracted_periods_changed(self, slot):
+        self.subtracted_period_edit.editingFinished.connect(slot)
+
+    def set_period_number_in_period_label(self, n_periods):
+        self.period_label.setText("Data collected in " + str(n_periods) + " periods. Plot/analysis period(s) : ")
+
+    def multi_period_widget_hidden(self, hidden=True):
+        self.periods_hidden(hidden)
+
+    def periods_hidden(self, hidden=True):
+        """
+        show/hide the multi-period data functionality.
+        """
+        if hidden:
+            self.period_label.hide()
+            self.summed_period_edit.hide()
+            self.minus_label.hide()
+            self.subtracted_period_edit.hide()
+        if not hidden:
+            self.period_label.setVisible(True)
+            self.summed_period_edit.setVisible(True)
+            self.minus_label.setVisible(True)
+            self.subtracted_period_edit.setVisible(True)
diff --git a/scripts/Muon/GUI/Common/home_instrument_widget/__init__.py b/scripts/Muon/GUI/Common/home_instrument_widget/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_model.py b/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..f865d122f2d7a170fc9ce269fe236ab524d90da2
--- /dev/null
+++ b/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_model.py
@@ -0,0 +1,108 @@
+from __future__ import (absolute_import, division, print_function)
+
+from Muon.GUI.Common.muon_context import MuonContext
+from mantid.api import ITableWorkspace, WorkspaceGroup
+from mantid import api
+
+
+class InstrumentWidgetModel(object):
+    """
+    The model holds the muon context and interacts with it, only able to modify the pre-processing parts of each
+    run.
+
+    The model should not take care of processing data, it should only interact with and modify the muon context data
+    so that when processing is done from elsewhere the parameters of the pre-processing are up-to-date with the
+    GUI.
+    """
+
+    def __init__(self, muon_data=MuonContext()):
+        self._data = muon_data
+
+    def clear_data(self):
+        """When e.g. instrument changed"""
+        self._data.clear()
+
+    def get_file_time_zero(self):
+        return self._data.loaded_data["TimeZero"]
+
+    def get_user_time_zero(self):
+        if "UserTimeZero" in self._data.loaded_data.keys():
+            time_zero = self._data.loaded_data["UserTimeZero"]
+        else:
+            # default to loaded value, keep a record of the data vaue
+            self._data.loaded_data["DataTimeZero"] = self._data.loaded_data["TimeZero"]
+            time_zero = self._data.loaded_data["TimeZero"]
+        return time_zero
+
+    def get_file_first_good_data(self):
+        return self._data.loaded_data["FirstGoodData"]
+
+    def get_user_first_good_data(self):
+        if "UserFirstGoodData" in self._data.loaded_data.keys():
+            first_good_data = self._data.loaded_data["UserFirstGoodData"]
+        else:
+            # Default to loaded value
+            self._data.loaded_data["FirstGoodData"] = self._data.loaded_data["FirstGoodData"]
+            first_good_data = self._data.loaded_data["FirstGoodData"]
+        return first_good_data
+
+    def set_user_time_zero(self, time_zero):
+        self._data.loaded_data["UserTimeZero"] = time_zero
+
+    def set_user_first_good_data(self, first_good_data):
+        self._data.loaded_data["UserFirstGoodData"] = first_good_data
+
+    def get_dead_time_table_from_data(self):
+        if self._data.is_multi_period():
+            return self._data.loaded_data["DataDeadTimeTable"][0]
+        else:
+            return self._data.loaded_data["DataDeadTimeTable"]
+
+    def get_dead_time_table(self):
+        return self._data.dead_time_table
+
+    def add_fixed_binning(self, fixed_bin_size):
+        self._data.loaded_data["Rebin"] = str(fixed_bin_size)
+
+    def add_variable_binning(self, rebin_params):
+        self._data.loaded_data["Rebin"] = str(rebin_params)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Dead Time
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def load_dead_time(self):
+        # TODO : Create this function
+        pass
+
+    def check_dead_time_file_selection(self, selection):
+        try:
+            table = api.AnalysisDataServiceImpl.Instance().retrieve(str(selection))
+        except Exception:
+            raise ValueError("Workspace " + str(selection) + " does not exist")
+        assert isinstance(table, ITableWorkspace)
+        # are column names correct?
+        col = table.getColumnNames()
+        if len(col) != 2:
+            raise ValueError("Expected 2 columns, found ", str(max(0, len(col))))
+        if col[0] != "spectrum" or col[1] != "dead-time":
+            raise ValueError("Columns have incorrect names")
+        rows = table.rowCount()
+        if rows != self._data.loaded_workspace.getNumberHistograms():
+            raise ValueError("Number of histograms do not match number of rows in dead time table")
+        return True
+
+    def set_dead_time_to_none(self):
+        self._data.loaded_data["DeadTimeTable"] = None
+
+    def set_dead_time_from_data(self):
+        data_dead_time = self._data.loaded_data["DataDeadTimeTable"]
+        if isinstance(data_dead_time, WorkspaceGroup):
+            self._data.loaded_data["DeadTimeTable"] = data_dead_time[0]
+        else:
+            self._data.loaded_data["DeadTimeTable"] = data_dead_time
+
+    def set_user_dead_time_from_ADS(self, name):
+        dtc = api.AnalysisDataServiceImpl.Instance().retrieve(str(name))
+        self._data.loaded_data["UserDeadTimeTable"] = dtc
+        self._data.loaded_data["DeadTimeTable"] = dtc
diff --git a/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_presenter.py b/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a7f474dae271ca66e42e1f8d272ca89bb9b3460
--- /dev/null
+++ b/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_presenter.py
@@ -0,0 +1,203 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+from Muon.GUI.Common.home_tab.home_tab_presenter import HomeTabSubWidget
+import Muon.GUI.Common.load_utils as load_utils
+from Muon.GUI.Common.muon_file_utils import filter_for_extensions
+from Muon.GUI.Common.observer_pattern import Observable
+
+
+class InstrumentWidgetPresenter(HomeTabSubWidget):
+
+    @staticmethod
+    def dead_time_from_data_text(dead_times):
+        mean = sum(dead_times) / len(dead_times)
+        label = "From {0:.3f} to {1:.3f} (ave. {2:.3f})".format(min(dead_times), max(dead_times), mean)
+        return label
+
+    def __init__(self, view, model):
+        self._view = view
+        self._model = model
+
+        self._view.on_time_zero_checkState_changed(self.handle_loaded_time_zero_checkState_change)
+        self._view.on_time_zero_changed(self.handle_user_changes_time_zero)
+
+        self._view.on_first_good_data_checkState_changed(self.handle_loaded_first_good_data_checkState_change)
+        self._view.on_first_good_data_changed(self.handle_user_changes_first_good_data)
+
+        self._view.on_fixed_rebin_edit_changed(self.handle_fixed_rebin_changed)
+        self._view.on_variable_rebin_edit_changed(self.handle_variable_rebin_changed)
+
+        self._view.on_dead_time_from_data_selected(self.handle_user_selects_dead_time_from_data)
+        self._view.on_dead_time_unselected(self.handle_dead_time_unselected)
+        self._view.on_dead_time_browse_clicked(self.handle_dead_time_browse_clicked)
+        self._view.on_dead_time_from_file_selected(self.handle_dead_time_from_file_selected)
+        self._view.on_dead_time_file_option_changed(self.handle_dead_time_from_file_changed)
+
+        self._view.on_instrument_changed(self.handle_instrument_changed)
+
+        # notifier for instrument changes
+        self.instrumentNotifier = InstrumentWidgetPresenter.InstrumentNotifier(self)
+
+    def show(self):
+        self._view.show()
+
+    def update_view_from_model(self):
+        self.handle_loaded_first_good_data_checkState_change()
+        self.handle_loaded_time_zero_checkState_change()
+        self._view.set_instrument(self._model._data.instrument)
+
+    def clear_view(self):
+        self._view.set_time_zero(0.0)
+        self._view.set_first_good_data(0.0)
+        self._view.set_combo_boxes_to_default()
+        self._view.set_checkboxes_to_defualt()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Time Zero
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def handle_user_changes_time_zero(self):
+        time_zero = self._view.get_time_zero()
+        self._model.set_user_time_zero(time_zero)
+
+    def handle_loaded_time_zero_checkState_change(self):
+        if self._view.time_zero_state():
+            time_zero = self._model.get_file_time_zero()
+            self._view.set_time_zero(time_zero)
+        else:
+            time_zero = self._model.get_user_time_zero()
+            self._view.set_time_zero(time_zero)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # First Good Data
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def handle_user_changes_first_good_data(self):
+        first_good_data = self._view.get_first_good_data()
+        self._model.set_user_first_good_data(first_good_data)
+
+    def handle_loaded_first_good_data_checkState_change(self):
+        if self._view.first_good_data_state():
+            first_good_data = self._model.get_file_first_good_data()
+            self._view.set_first_good_data(first_good_data)
+        else:
+            first_good_data = self._model.get_user_first_good_data()
+            self._view.set_first_good_data(first_good_data)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Rebin
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def handle_fixed_rebin_changed(self):
+        fixed_bin_size = float(self._view.get_fixed_bin_text())
+        self._model.add_fixed_binning(fixed_bin_size)
+
+    def handle_variable_rebin_changed(self):
+        variable_bin_size = float(self._view.get_fixed_bin_text())
+        self._model.add_variable_binning(variable_bin_size)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Instrument
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def handle_instrument_changed(self):
+        """User changes the selected instrument."""
+        instrument = self._view.get_instrument()
+        current_instrument = self._view.cached_instrument
+        if instrument != self._model._data.instrument:
+            # prompt user to continue or not
+            user_response = self._view.instrument_changed_warning()
+            if user_response == 1:
+                # User selects "Ok"
+                self._model.clear_data()
+                self.clear_view()
+                self._view.set_instrument(instrument, block=True)
+                self.instrumentNotifier.notify_subscribers(instrument)
+            else:
+                # User selects "Cancel", reset the instrument selector
+                self._view.set_instrument(current_instrument, block=True)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Dead Time
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def handle_dead_time_browse_clicked(self):
+        """User selects the option to Browse for a nexus file to load dead times from."""
+        filename = self._view.show_file_browser_and_return_selection(
+            filter_for_extensions(['nxs']), [''], multiple_files=False)[0]
+        name = load_utils.load_dead_time_from_filename(filename)
+        if name == "":
+            self._view.warning_popup("File does not appear to contain dead time data.")
+            return
+
+        # switch the view to the "from table workspace" option
+        self._view.set_dead_time_selection(2)
+        is_set = self._view.set_dead_time_file_selection_text(name)
+        if not is_set:
+            self._view.warning_popup("Dead time table cannot be loaded")
+
+    def handle_user_selects_dead_time_from_data(self):
+        """User chooses to load dead time from the currently loaded workspace."""
+        dtc = self._model.get_dead_time_table_from_data()
+        if dtc is not None:
+            self._model.set_dead_time_from_data()
+            dead_times = dtc.toDict()['dead-time']
+            dead_time_text = self.dead_time_from_data_text(dead_times)
+            self._view.set_dead_time_label(dead_time_text)
+        else:
+            self._view.set_dead_time_label("No loaded dead time")
+
+    def set_dead_time_text_to_default(self):
+        """by default the dead time text should onl contain 0.0."""
+        dead_time_text = self.dead_time_from_data_text([0.0])
+        self._view.set_dead_time_label(dead_time_text)
+
+    def handle_dead_time_from_file_selected(self):
+        """User has selected the dead time "from Table Workspace" option."""
+        table_names = load_utils.get_table_workspace_names_from_ADS()
+        self._view.populate_dead_time_combo(table_names)
+        self.set_dead_time_text_to_default()
+
+    def handle_dead_time_unselected(self):
+        """User has set dead time combo to 'None'."""
+        self.set_dead_time_text_to_default()
+        self._model.set_dead_time_to_none()
+
+    def handle_dead_time_from_file_changed(self):
+        """The user changes the selected Table Workspace to use as dead time."""
+        selection = self._view.get_dead_time_file_selection()
+        if selection == "None" or selection == "":
+            self.handle_dead_time_unselected()
+            return
+        try:
+            self._model.check_dead_time_file_selection(selection)
+            self._model.set_user_dead_time_from_ADS(selection)
+            dead_times = self._model.get_dead_time_table().toDict()['dead-time']
+            dead_time_text = self.dead_time_from_data_text(dead_times)
+            self._view.set_dead_time_label(dead_time_text)
+        except ValueError as error:
+            self._handle_selected_table_is_invalid()
+            self._view.warning_popup(error.args[0])
+
+    def _handle_selected_table_is_invalid(self):
+        self._model.set_dead_time_to_none()
+        self._view.set_dead_time_file_selection(0)
+        self.set_dead_time_text_to_default()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Observer / Observable
+    # ------------------------------------------------------------------------------------------------------------------
+
+    class InstrumentNotifier(Observable):
+        def __init__(self, outer):
+            Observable.__init__(self)
+            self.outer = outer  # handle to containing class
+
+        def notify_subscribers(self, *args, **kwargs):
+            Observable.notify_subscribers(self, *args)
diff --git a/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_view.py b/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..190a9442fafa98157a8897e12253f5953c008bf2
--- /dev/null
+++ b/scripts/Muon/GUI/Common/home_instrument_widget/home_instrument_widget_view.py
@@ -0,0 +1,569 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+# -*- coding: utf8 -*-
+from __future__ import (absolute_import, division, print_function)
+
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import pyqtSignal as Signal
+
+from Muon.GUI.Common.muon_file_utils import allowed_instruments
+from Muon.GUI.Common.run_string_utils import valid_float_regex
+from Muon.GUI.Common.message_box import warning
+
+
+class InstrumentWidgetView(QtGui.QWidget):
+    dataChanged = Signal()
+
+    def __init__(self, parent=None):
+        super(InstrumentWidgetView, self).__init__(parent)
+
+        self.layout = QtGui.QGridLayout(self)
+
+        self._button_height = 40
+        self._cached_instrument = ["None", "None"]
+
+        self.setup_interface()
+        self.apply_to_all_hidden(True)
+        self.dead_time_file_loader_hidden(True)
+        self.dead_time_other_file_hidden(True)
+
+        self.deadtime_selector.currentIndexChanged.connect(self.on_dead_time_combo_changed)
+        self.rebin_selector.currentIndexChanged.connect(self.on_rebin_combo_changed)
+        self.timezero_checkbox.stateChanged.connect(self.on_time_zero_checkbox_state_change)
+        self.firstgooddata_checkbox.stateChanged.connect(self.on_first_good_data_checkbox_state_change)
+
+        self._on_dead_time_from_data_selected = None
+        self._on_dead_time_from_other_file_selected = lambda: 0
+
+        self.firstgooddata_checkbox.setChecked(1)
+        self.timezero_checkbox.setChecked(1)
+        self.time_zero_edit_enabled(True)
+        self.first_good_data_edit_enabled(True)
+
+        self._on_time_zero_changed = lambda: 0
+        self._on_first_good_data_changed = lambda: 0
+        self._on_dead_time_from_file_selected = lambda: 0
+        self._on_dead_time_file_option_selected = lambda: 0
+        self._on_dead_time_unselected = lambda: 0
+
+        self.timezero_edit.editingFinished.connect(
+            lambda: self._on_time_zero_changed() if not self.is_time_zero_checked() else None)
+        self.firstgooddata_edit.editingFinished.connect(
+            lambda: self._on_first_good_data_changed() if not self.is_first_good_data_checked() else None)
+        self.deadtime_file_selector.currentIndexChanged.connect(self.on_dead_time_file_combo_changed)
+
+    def apply_to_all_hidden(self, hidden=True):
+        if hidden:
+            self.apply_all_label.hide()
+            self.apply_all_checkbox.hide()
+        if not hidden:
+            self.apply_all_label.setVisible(True)
+            self.apply_all_checkbox.setVisible(True)
+
+    def setup_filter_row(self):
+        self.apply_all_label = QtGui.QLabel(self)
+        self.apply_all_label.setObjectName("applyAllLabel")
+        self.apply_all_label.setText("Apply to all loaded data ")
+
+        self.apply_all_checkbox = QtGui.QCheckBox(self)
+
+        self.horizontal_layout_6 = QtGui.QHBoxLayout()
+        self.horizontal_layout_6.setObjectName("horizontalLayout6")
+        self.horizontal_layout_6.addWidget(self.apply_all_label)
+        self.horizontal_layout_6.addWidget(self.apply_all_checkbox)
+
+    def setup_interface(self):
+        self.setObjectName("InstrumentWidget")
+
+        self.setup_instrument_row()
+        self.setup_time_zero_row()
+        self.setup_first_good_data_row()
+        self.setup_dead_time_row()
+        self.setup_rebin_row()
+        self.setup_filter_row()
+
+        self.group = QtGui.QGroupBox("Run Pre-processing Parameters")
+        self.group.setFlat(False)
+        self.setStyleSheet("QGroupBox {border: 1px solid grey;border-radius: 10px;margin-top: 1ex; margin-right: 0ex}"
+                           "QGroupBox:title {"
+                           'subcontrol-origin: margin;'
+                           "padding: 0 3px;"
+                           'subcontrol-position: top center;'
+                           'padding-top: -10px;'
+                           'padding-bottom: 0px;'
+                           "padding-right: 10px;"
+                           ' color: grey; }')
+
+        self.group.setLayout(self.layout)
+
+        self.group2 = QtGui.QGroupBox("Rebin")
+        self.group2.setFlat(False)
+        self.vertical_layout2 = QtGui.QVBoxLayout()
+        self.vertical_layout2.addItem(self.horizontal_layout_5)
+        self.group2.setLayout(self.horizontal_layout_5)
+
+        self.widget_layout = QtGui.QVBoxLayout(self)
+        self.widget_layout.addWidget(self.group)
+        self.widget_layout.addWidget(self.group2)
+        self.setLayout(self.widget_layout)
+
+    def show_file_browser_and_return_selection(self, file_filter, search_directories, multiple_files=False):
+        default_directory = search_directories[0]
+        if multiple_files:
+            chosen_files = QtGui.QFileDialog.getOpenFileNames(self, "Select files", default_directory,
+                                                              file_filter)
+            return [str(chosen_file) for chosen_file in chosen_files]
+        else:
+            chosen_file = QtGui.QFileDialog.getOpenFileName(self, "Select file", default_directory,
+                                                            file_filter)
+            return [str(chosen_file)]
+
+    def set_combo_boxes_to_default(self):
+        self.rebin_selector.setCurrentIndex(0)
+        self.rebin_fixed_hidden(True)
+        self.rebin_variable_hidden(True)
+        self.deadtime_selector.setCurrentIndex(0)
+        self.dead_time_data_info_hidden(True)
+        self.dead_time_file_loader_hidden(True)
+
+    def set_checkboxes_to_defualt(self):
+        self.timezero_checkbox.setChecked(1)
+        self.firstgooddata_checkbox.setChecked(1)
+
+    def warning_popup(self, message):
+        warning(message, parent=self)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Instrument selection
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def _fixed_aspect_ratio_size_policy(self, widget):
+        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+        size_policy.setHorizontalStretch(0)
+        size_policy.setVerticalStretch(0)
+        size_policy.setHeightForWidth(widget.sizePolicy().hasHeightForWidth())
+        return size_policy
+
+    def setup_instrument_row(self):
+        self.instrument_selector = QtGui.QComboBox(self)
+        self.instrument_selector.setSizePolicy(self._fixed_aspect_ratio_size_policy(self.instrument_selector))
+        self.instrument_selector.setObjectName("instrumentSelector")
+        self.instrument_selector.addItems(["None"] + allowed_instruments)
+
+        self.instrument_label = QtGui.QLabel(self)
+        self.instrument_label.setObjectName("instrumentLabel")
+        self.instrument_label.setText("Instrument : ")
+
+        self.horizontal_layout = QtGui.QHBoxLayout()
+        self.horizontal_layout.setObjectName("horizontalLayout")
+        self.horizontal_layout.addWidget(self.instrument_label)
+        self.horizontal_layout.addWidget(self.instrument_selector)
+        self.horizontal_layout.addStretch(0)
+
+        self.layout.addWidget(self.instrument_label, 0, 0)
+        self.layout.addWidget(self.instrument_selector, 0, 1)
+
+    def get_instrument(self):
+        return self.instrument_selector.currentText()
+
+    def set_instrument(self, instrument, block=False):
+        index = self.instrument_selector.findText(instrument)
+        if index != -1:
+            self.instrument_selector.blockSignals(block)
+            self.instrument_selector.setCurrentIndex(index)
+            self.instrument_selector.blockSignals(False)
+
+    def on_instrument_changed(self, slot):
+        self.instrument_selector.currentIndexChanged.connect(slot)
+        self.instrument_selector.currentIndexChanged.connect(self.cache_instrument)
+
+    def cache_instrument(self):
+        self._cached_instrument.pop(0)
+        self._cached_instrument.append(str(self.instrument_selector.currentText()))
+
+    @property
+    def cached_instrument(self):
+        return self._cached_instrument[-1]
+
+    def instrument_changed_warning(self):
+        msg = QtGui.QMessageBox(self)
+        msg.setIcon(QtGui.QMessageBox.Warning)
+        msg.setText("Changing instrument will reset the interface, continue?")
+        msg.setWindowTitle("Changing Instrument")
+        msg.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
+        retval = msg.exec_()
+        if retval == 1024:
+            # The "OK" code
+            return 1
+        else:
+            return 0
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Time zero
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def setup_time_zero_row(self):
+        self.timezero_label = QtGui.QLabel(self)
+        self.timezero_label.setObjectName("timeZeroLabel")
+        self.timezero_label.setText("Time Zero : ")
+
+        self.timezero_edit = QtGui.QLineEdit(self)
+        timezero_validator = QtGui.QRegExpValidator(QtCore.QRegExp(valid_float_regex), self.timezero_edit)
+        self.timezero_edit.setValidator(timezero_validator)
+        self.timezero_edit.setObjectName("timeZeroEdit")
+        self.timezero_edit.setText("")
+
+        self.timezero_unit_label = QtGui.QLabel(self)
+        self.timezero_unit_label.setObjectName("timeZeroUnitLabel")
+        self.timezero_unit_label.setText(u" µs (From data file ")
+
+        self.timezero_checkbox = QtGui.QCheckBox(self)
+        self.timezero_checkbox.setObjectName("timeZeroCheckbox")
+        self.timezero_checkbox.setChecked(True)
+
+        self.timezero_label_2 = QtGui.QLabel(self)
+        self.timezero_label_2.setObjectName("timeZeroLabel")
+        self.timezero_label_2.setText(" )")
+
+        self.horizontal_layout_2 = QtGui.QHBoxLayout()
+        self.horizontal_layout_2.setObjectName("horizontalLayout2")
+        self.horizontal_layout_2.addSpacing(10)
+        self.horizontal_layout_2.addWidget(self.timezero_unit_label)
+        self.horizontal_layout_2.addWidget(self.timezero_checkbox)
+        self.horizontal_layout_2.addWidget(self.timezero_label_2)
+        self.horizontal_layout_2.addStretch(0)
+
+        self.layout.addWidget(self.timezero_label, 1, 0)
+        self.layout.addWidget(self.timezero_edit, 1, 1)
+        self.layout.addItem(self.horizontal_layout_2, 1, 2)
+
+    def set_time_zero(self, time_zero):
+        self.timezero_edit.setText("{0:.3f}".format(round(float(time_zero), 3)))
+
+    def get_time_zero(self):
+        return float(self.timezero_edit.text())
+
+    def time_zero_edit_enabled(self, enabled):
+        self.timezero_edit.setEnabled(not enabled)
+
+    def is_time_zero_checked(self):
+        return self.timezero_checkbox.checkState()
+
+    def on_time_zero_changed(self, slot):
+        self._on_time_zero_changed = slot
+
+    def on_time_zero_checkState_changed(self, slot):
+        self.timezero_checkbox.stateChanged.connect(slot)
+
+    def time_zero_state(self):
+        return self.timezero_checkbox.checkState()
+
+    def on_time_zero_checkbox_state_change(self):
+        self.time_zero_edit_enabled(self.timezero_checkbox.checkState())
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # First good data
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def setup_first_good_data_row(self):
+
+        self.firstgooddata_label = QtGui.QLabel(self)
+        self.firstgooddata_label.setObjectName("firstgooddataLabel")
+        self.firstgooddata_label.setText("First Good Data : ")
+
+        self.firstgooddata_edit = QtGui.QLineEdit(self)
+        firstgooddata_validator = QtGui.QRegExpValidator(QtCore.QRegExp(valid_float_regex), self.timezero_edit)
+        self.timezero_edit.setValidator(firstgooddata_validator)
+        self.firstgooddata_edit.setObjectName("firstgooddataEdit")
+        self.firstgooddata_edit.setText("")
+
+        self.firstgooddata_unit_label = QtGui.QLabel(self)
+        self.firstgooddata_unit_label.setObjectName("firstgooddataUnitLabel")
+        self.firstgooddata_unit_label.setText(u" U+03BCs (From data file ")
+
+        self.firstgooddata_checkbox = QtGui.QCheckBox(self)
+        self.firstgooddata_checkbox.setObjectName("firstgooddataCheckbox")
+        self.firstgooddata_checkbox.setChecked(True)
+
+        self.firstgooddata_label_2 = QtGui.QLabel(self)
+        self.firstgooddata_label_2.setObjectName("timeZeroLabel")
+        self.firstgooddata_label_2.setText(" )")
+
+        self.horizontal_layout_3 = QtGui.QHBoxLayout()
+        self.horizontal_layout_3.setObjectName("horizontalLayout3")
+        self.horizontal_layout_3.addSpacing(10)
+        self.horizontal_layout_3.addWidget(self.firstgooddata_unit_label)
+        self.horizontal_layout_3.addWidget(self.firstgooddata_checkbox)
+        self.horizontal_layout_3.addWidget(self.firstgooddata_label_2)
+        self.horizontal_layout_3.addStretch(0)
+
+        self.layout.addWidget(self.firstgooddata_label, 2, 0)
+        self.layout.addWidget(self.firstgooddata_edit, 2, 1)
+        self.layout.addItem(self.horizontal_layout_3, 2, 2)
+
+    def on_first_good_data_changed(self, slot):
+        self._on_first_good_data_changed = slot
+
+    def set_first_good_data(self, first_good_data):
+        self.firstgooddata_edit.setText("{0:.3f}".format(round(float(first_good_data), 3)))
+
+    def on_first_good_data_checkState_changed(self, slot):
+        self.firstgooddata_checkbox.stateChanged.connect(slot)
+
+    def first_good_data_state(self):
+        return self.firstgooddata_checkbox.checkState()
+
+    def is_first_good_data_checked(self):
+        return self.firstgooddata_checkbox.checkState()
+
+    def on_first_good_data_checkbox_state_change(self):
+        self.first_good_data_edit_enabled(self.firstgooddata_checkbox.checkState())
+
+    def first_good_data_edit_enabled(self, disabled):
+        self.firstgooddata_edit.setEnabled(not disabled)
+
+    def get_first_good_data(self):
+        return float(self.firstgooddata_edit.text())
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Dead time correction
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def setup_dead_time_row(self):
+        self.deadtime_label = QtGui.QLabel(self)
+        self.deadtime_label.setObjectName("deadTimeLabel")
+        self.deadtime_label.setText("Dead Time : ")
+
+        self.deadtime_selector = QtGui.QComboBox(self)
+        self.deadtime_selector.setObjectName("deadTimeSelector")
+        self.deadtime_selector.addItems(["None", "From data file", "From table workspace", "From other file"])
+
+        self.deadtime_label_2 = QtGui.QLabel(self)
+        self.deadtime_label_2.setObjectName("deadTimeFileLabel")
+        self.deadtime_label_2.setText("Dead Time Workspace : ")
+
+        self.deadtime_label_3 = QtGui.QLabel(self)
+        self.deadtime_label_3.setObjectName("deadTimeInfoLabel")
+        self.deadtime_label_3.setText("")
+
+        self.deadtime_file_selector = QtGui.QComboBox(self)
+        self.deadtime_file_selector.setObjectName("deadTimeCombo")
+        self.deadtime_file_selector.addItem("None")
+        self.deadtime_file_selector.setToolTip("Select a table which is loaded into the ADS.")
+
+        self.deadtime_browse_button = QtGui.QPushButton(self)
+        self.deadtime_browse_button.setObjectName("deadTimeBrowseButton")
+        self.deadtime_browse_button.setText("Browse")
+        self.deadtime_browse_button.setToolTip("Browse for a .nxs file to load dead times from. If valid, the "
+                                               "dead times will be saved as a table, and automatically selected "
+                                               "as the dead time for the current data.")
+
+        self.horizontal_layout_4 = QtGui.QHBoxLayout()
+        self.horizontal_layout_4.setObjectName("horizontalLayout3")
+        self.horizontal_layout_4.addSpacing(10)
+        self.horizontal_layout_4.addWidget(self.deadtime_label_3)
+
+        self.dead_time_file_layout = QtGui.QHBoxLayout()
+        self.dead_time_file_layout.addWidget(self.deadtime_browse_button)
+        self.dead_time_file_layout.addStretch(0)
+
+        self.dead_time_other_file_label = QtGui.QLabel(self)
+        self.dead_time_other_file_label.setText("From other file : ")
+
+        self.layout.addWidget(self.deadtime_label, 3, 0)
+        self.layout.addWidget(self.deadtime_selector, 3, 1)
+        self.layout.addItem(self.horizontal_layout_4, 3, 2)
+        self.layout.addWidget(self.deadtime_label_2, 4, 0)
+        self.layout.addWidget(self.deadtime_file_selector, 4, 1)
+        self.layout.addWidget(self.dead_time_other_file_label, 5, 0)
+        self.layout.addWidget(self.deadtime_browse_button, 5, 1)
+
+    def on_dead_time_file_option_changed(self, slot):
+        self._on_dead_time_file_option_selected = slot
+
+    def on_dead_time_from_data_selected(self, slot):
+        self._on_dead_time_from_data_selected = slot
+
+    def on_dead_time_unselected(self, slot):
+        self._on_dead_time_unselected = slot
+
+    def on_dead_time_browse_clicked(self, slot):
+        self.deadtime_browse_button.clicked.connect(slot)
+
+    def on_dead_time_from_file_selected(self, slot):
+        self._on_dead_time_from_file_selected = slot
+
+    def populate_dead_time_combo(self, names):
+        self.deadtime_file_selector.clear()
+        self.deadtime_file_selector.addItem("None")
+        for name in names:
+            self.deadtime_file_selector.addItem(name)
+
+    def get_dead_time_file_selection(self):
+        return self.deadtime_file_selector.currentText()
+
+    def set_dead_time_file_selection_text(self, text):
+        index = self.deadtime_file_selector.findText(text)
+        if index >= 0:
+            self.deadtime_file_selector.setCurrentIndex(index)
+            return True
+        return False
+
+    def set_dead_time_file_selection(self, index):
+        self.deadtime_file_selector.setCurrentIndex(index)
+
+    def set_dead_time_selection(self, index):
+        self.deadtime_selector.setCurrentIndex(index)
+
+    def dead_time_file_loader_hidden(self, hidden=True):
+        if hidden:
+            self.deadtime_file_selector.hide()
+
+            self.deadtime_label_2.hide()
+            self.dead_time_data_info_hidden(hidden)
+        if not hidden:
+            self.deadtime_file_selector.setVisible(True)
+            self.deadtime_label_2.setVisible(True)
+            self.dead_time_data_info_hidden(hidden)
+
+    def dead_time_other_file_hidden(self, hidden):
+        if hidden:
+            self.dead_time_other_file_label.hide()
+            self.deadtime_browse_button.hide()
+
+        if not hidden:
+            self.deadtime_browse_button.setVisible(True)
+            self.dead_time_other_file_label.setVisible(True)
+
+    def dead_time_data_info_hidden(self, hidden=True):
+        if hidden:
+            self.deadtime_label_3.hide()
+        if not hidden:
+            self.deadtime_label_3.setVisible(True)
+
+    def set_dead_time_label(self, text):
+        self.deadtime_label_3.setText(text)
+
+    def on_dead_time_combo_changed(self, index):
+        if index == 0:
+            self._on_dead_time_unselected()
+            self.dead_time_file_loader_hidden(True)
+            self.dead_time_data_info_hidden(True)
+            self.dead_time_other_file_hidden(True)
+        if index == 1:
+            self._on_dead_time_from_data_selected()
+            self.dead_time_file_loader_hidden(True)
+            self.dead_time_data_info_hidden(False)
+            self.dead_time_other_file_hidden(True)
+        if index == 2:
+            self._on_dead_time_from_file_selected()
+            self.dead_time_file_loader_hidden(False)
+            self.dead_time_data_info_hidden(False)
+            self.dead_time_other_file_hidden(True)
+        if index == 3:
+            self._on_dead_time_from_other_file_selected()
+            self.dead_time_file_loader_hidden(True)
+            self.dead_time_data_info_hidden(True)
+            self.dead_time_other_file_hidden(False)
+
+    def on_dead_time_from_other_file_selected(self, slot):
+        self._on_dead_time_from_other_file_selected = slot
+
+    def on_dead_time_file_combo_changed(self, index):
+        self._on_dead_time_file_option_selected()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Rebin row
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def setup_rebin_row(self):
+        self.rebin_label = QtGui.QLabel(self)
+        self.rebin_label.setObjectName("rebinLabel")
+        self.rebin_label.setText("Rebin : ")
+
+        self.rebin_selector = QtGui.QComboBox(self)
+        self.rebin_selector.setObjectName("rebinSelector")
+        self.rebin_selector.addItems(["None", "Fixed", "Variable"])
+
+        self.rebin_steps_label = QtGui.QLabel(self)
+        self.rebin_steps_label.setText("Steps : ")
+
+        self.rebin_steps_edit = QtGui.QLineEdit(self)
+
+        self.rebin_variable_label = QtGui.QLabel(self)
+        self.rebin_variable_label.setText("Bin Boundaries : ")
+        self.rebin_variable_edit = QtGui.QLineEdit(self)
+
+        self.rebin_help_button = QtGui.QPushButton(self)
+        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+        size_policy.setHorizontalStretch(0)
+        size_policy.setVerticalStretch(0)
+        size_policy.setHeightForWidth(self.rebin_help_button.sizePolicy().hasHeightForWidth())
+        self.rebin_help_button.setSizePolicy(size_policy)
+        self.rebin_help_button.setMinimumSize(QtCore.QSize(40, 40))
+        self.rebin_help_button.setMaximumSize(QtCore.QSize(40, 40))
+        self.rebin_help_button.setObjectName("rebinHelpButton")
+        self.rebin_help_button.setText("?")
+
+        self.horizontal_layout_5 = QtGui.QHBoxLayout()
+        self.horizontal_layout_5.setObjectName("horizontalLayout3")
+        self.horizontal_layout_5.addSpacing(10)
+
+        self.horizontal_layout_5.addWidget(self.rebin_label)
+        self.horizontal_layout_5.addWidget(self.rebin_selector)
+
+        self.horizontal_layout_5.addWidget(self.rebin_steps_label)
+        self.horizontal_layout_5.addWidget(self.rebin_steps_edit)
+        self.horizontal_layout_5.addWidget(self.rebin_variable_label)
+        self.horizontal_layout_5.addWidget(self.rebin_variable_edit)
+        self.horizontal_layout_5.addStretch(0)
+        self.horizontal_layout_5.addSpacing(10)
+        self.horizontal_layout_5.addWidget(self.rebin_help_button)
+
+        self.rebin_steps_label.hide()
+        self.rebin_steps_edit.hide()
+        self.rebin_variable_label.hide()
+        self.rebin_variable_edit.hide()
+
+    def rebin_fixed_hidden(self, hidden=True):
+        if hidden:
+            self.rebin_steps_label.hide()
+            self.rebin_steps_edit.hide()
+        if not hidden:
+            self.rebin_steps_label.setVisible(True)
+            self.rebin_steps_edit.setVisible(True)
+
+    def rebin_variable_hidden(self, hidden=True):
+        if hidden:
+            self.rebin_variable_label.hide()
+            self.rebin_variable_edit.hide()
+        if not hidden:
+            self.rebin_variable_label.setVisible(True)
+            self.rebin_variable_edit.setVisible(True)
+
+    def on_rebin_combo_changed(self, index):
+        if index == 0:
+            self.rebin_fixed_hidden(True)
+            self.rebin_variable_hidden(True)
+        if index == 1:
+            self.rebin_fixed_hidden(False)
+            self.rebin_variable_hidden(True)
+        if index == 2:
+            self.rebin_fixed_hidden(True)
+            self.rebin_variable_hidden(False)
+
+    def on_fixed_rebin_edit_changed(self, slot):
+        self.rebin_steps_edit.editingFinished.connect(slot)
+
+    def on_variable_rebin_edit_changed(self, slot):
+        self.rebin_variable_edit.editingFinished.connect(slot)
+
+    def get_fixed_bin_text(self):
+        return self.rebin_steps_edit.text()
+
+    def get_variable_bin_text(self):
+        return self.rebin_variable_edit.text()
diff --git a/scripts/Muon/GUI/Common/pairing_table_widget/__init__.py b/scripts/Muon/GUI/Common/pairing_table_widget/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_model.py b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..3214cf2b4507810a31b662e9d89d10282b67d2cb
--- /dev/null
+++ b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_model.py
@@ -0,0 +1,38 @@
+from __future__ import (absolute_import, division, print_function)
+
+from Muon.GUI.Common.muon_data_context import MuonDataContext, construct_empty_pair
+
+
+class PairingTableModel(object):
+
+    def __init__(self, data=MuonDataContext()):
+        self._data = data
+
+    @property
+    def pairs(self):
+        return self._data.pairs.values()
+
+    @property
+    def pair_names(self):
+        return list(self._data.pair_names)
+
+    @property
+    def group_names(self):
+        return list(self._data.group_names)
+
+    @property
+    def group_and_pair_names(self):
+        return list(self._data.group_names) + list(self._data.pair_names)
+
+    def add_pair(self, pair):
+        self._data.add_pair(pair)
+
+    def remove_pairs_by_name(self, name_list):
+        for name in name_list:
+            del self._data.pairs[name]
+
+    def construct_empty_pair(self, pair_index):
+        return construct_empty_pair(self.group_names, self.pair_names, pair_index)
+
+    def clear_pairs(self):
+        self._data.clear_pairs()
diff --git a/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_presenter.py b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ef6eb22d6f37c0d53afc16684d90376e2b267a8
--- /dev/null
+++ b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_presenter.py
@@ -0,0 +1,176 @@
+from __future__ import (absolute_import, division, print_function)
+
+import re
+
+from Muon.GUI.Common.muon_pair import MuonPair
+from Muon.GUI.Common.utilities.run_string_utils import valid_name_regex, valid_alpha_regex
+from Muon.GUI.Common.observer_pattern import Observable
+
+
+class PairingTablePresenter(object):
+
+    def __init__(self, view, model):
+        self._view = view
+        self._model = model
+
+        self._view.on_add_pair_button_clicked(self.handle_add_pair_button_clicked)
+        self._view.on_remove_pair_button_clicked(self.handle_remove_pair_button_clicked)
+
+        self._view.on_user_changes_pair_name(self.validate_pair_name)
+        self._view.on_user_changes_alpha(self.validate_alpha)
+        self._view.on_guess_alpha_clicked(self.handle_guess_alpha_clicked)
+        self._view.on_table_data_changed(self.handle_data_change)
+
+        self._dataChangedNotifier = lambda: 0
+        self._on_alpha_clicked = lambda: 0
+        self._on_guess_alpha_requested = lambda pair_name, group1, group2: 0
+
+        # notify if Guess Alpha clicked for any table entries
+        self.guessAlphaNotifier = PairingTablePresenter.GuessAlphaNotifier(self)
+
+    def show(self):
+        self._view.show()
+
+    def on_data_changed(self, notifier):
+        self._dataChangedNotifier = notifier
+
+    def notify_data_changed(self):
+        self._dataChangedNotifier()
+
+    def disable_editing(self):
+        self._view.disable_editing()
+
+    def enable_editing(self):
+        self._view.enable_editing()
+
+    def handle_guess_alpha_clicked(self, row):
+        table_row = self._view.get_table_contents()[row]
+        pair_name = table_row[0]
+        group1 = table_row[1]
+        group2 = table_row[2]
+        self.guessAlphaNotifier.notify_subscribers([pair_name, group1, group2])
+
+    def handle_data_change(self, row, col):
+        changed_item = self._view.get_table_item_text(row, col)
+        update_model = True
+        if col == 0 and not self.validate_pair_name(changed_item):
+            update_model = False
+        if col == 3:
+            if not self.validate_alpha(changed_item):
+                update_model = False
+            else:
+                self._view.pairing_table.blockSignals(True)
+                rounded_item = '{:.3f}'.format(float(changed_item)) if '{:.3f}'.format(float(changed_item)) != '0.000'\
+                    else '{:.3g}'.format(float(changed_item))
+
+                self._view.pairing_table.item(row, col).setText(rounded_item)
+                self._view.pairing_table.blockSignals(False)
+
+        if update_model:
+            self.update_model_from_view()
+
+        self.update_view_from_model()
+        self.notify_data_changed()
+
+    def update_model_from_view(self):
+        table = self._view.get_table_contents()
+        self._model.clear_pairs()
+        for entry in table:
+            pair = MuonPair(pair_name=str(entry[0]),
+                            backward_group_name=str(entry[1]),
+                            forward_group_name=str(entry[2]),
+                            alpha=float(entry[3]))
+            self._model.add_pair(pair)
+
+    def update_view_from_model(self):
+        self._view.disable_updates()
+
+        self._view.clear()
+        for pair in self._model.pairs:
+            self.add_pair_to_view(pair)
+
+        self._view.enable_updates()
+
+    def update_group_selections(self):
+        groups = self._model.group_names
+        self._view.update_group_selections(groups)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Add / Remove pairs
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def add_pair(self, pair):
+        """Add a pair to the model and view"""
+        if self._view.num_rows() > 19:
+            self._view.warning_popup("Cannot add more than 20 pairs.")
+            return
+        self.add_pair_to_view(pair)
+        self.add_pair_to_model(pair)
+
+    def add_pair_to_model(self, pair):
+        self._model.add_pair(pair)
+
+    def add_pair_to_view(self, pair):
+        self._view.disable_updates()
+        self.update_group_selections()
+        assert isinstance(pair, MuonPair)
+        entry = [str(pair.name), str(pair.forward_group), str(pair.backward_group), str(pair.alpha)]
+        self._view.add_entry_to_table(entry)
+        self._view.enable_updates()
+
+    def handle_add_pair_button_clicked(self):
+        pair = self._model.construct_empty_pair(self._view.num_rows() + 1)
+        self.add_pair(pair)
+        self.notify_data_changed()
+
+    def handle_remove_pair_button_clicked(self):
+        pair_names = self._view.get_selected_pair_names()
+        if not pair_names:
+            self.remove_last_row_in_view_and_model()
+        else:
+            self._view.remove_selected_pairs()
+            self._model.remove_pairs_by_name(pair_names)
+        self.notify_data_changed()
+
+    def remove_last_row_in_view_and_model(self):
+        if self._view.num_rows() > 0:
+            name = self._view.get_table_contents()[-1][0]
+            self._view.remove_last_row()
+            self._model.remove_pairs_by_name([name])
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Table entry validation
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def _is_edited_name_duplicated(self, new_name):
+        is_name_column_being_edited = self._view.pairing_table.currentColumn() == 0
+        is_name_unique = (sum(
+            [new_name == name for name in self._model.group_and_pair_names]) == 0)
+        return is_name_column_being_edited and not is_name_unique
+
+    def validate_pair_name(self, text):
+        if self._is_edited_name_duplicated(text):
+            self._view.warning_popup("Groups and pairs must have unique names")
+            return False
+        if not re.match(valid_name_regex, text):
+            self._view.warning_popup("Pair names should only contain digits, characters and _")
+            return False
+        return True
+
+    def validate_alpha(self, alpha_text):
+        if not re.match(valid_alpha_regex, alpha_text) or float(alpha_text) <= 0.0:
+            self._view.warning_popup("Alpha must be > 0")
+            return False
+        return True
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Observer / Observable
+    # ------------------------------------------------------------------------------------------------------------------
+
+    class GuessAlphaNotifier(Observable):
+        def __init__(self, outer):
+            Observable.__init__(self)
+            self.outer = outer  # handle to containing class
+
+        def notify_subscribers(self, arg=["", "", ""]):
+            Observable.notify_subscribers(self, arg)
diff --git a/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_view.py b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..70d573d4f38c40bcd99197bbd3d7d58bf2635b7e
--- /dev/null
+++ b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_view.py
@@ -0,0 +1,347 @@
+from __future__ import (absolute_import, division, print_function)
+
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import pyqtSignal as Signal
+
+from Muon.GUI.Common import message_box
+from Muon.GUI.Common.utilities import table_utils
+
+pair_columns = {0: 'pair_name', 1: 'group_1', 2: 'group_2', 3: 'alpha', 4: 'guess_alpha'}
+
+
+class PairingTableView(QtGui.QWidget):
+    dataChanged = Signal()
+
+    @staticmethod
+    def warning_popup(message):
+        message_box.warning(str(message))
+
+    def __init__(self, parent=None):
+        super(PairingTableView, self).__init__(parent)
+
+        self.pairing_table = QtGui.QTableWidget(self)
+        self.set_up_table()
+        self.setup_interface_layout()
+        self.pairing_table.itemChanged.connect(self.on_item_changed)
+        self.pairing_table.cellChanged.connect(self.on_cell_changed)
+
+        # Table entry validation
+        self._validate_pair_name_entry = lambda text: True
+        self._validate_alpha = lambda text: True
+
+        self._on_table_data_changed = lambda: 0
+        self._on_guess_alpha_clicked = lambda row: 0
+
+        # The active groups that can be selected from the group combo box
+        self._group_selections = []
+
+        # whether the table is updating and therefore we shouldn't respond to signals
+        self._updating = False
+
+        # the right-click context menu
+        self.menu = None
+        self._disabled = False
+        self.add_pair_action = None
+        self.remove_pair_action = None
+
+    def setup_interface_layout(self):
+        self.setObjectName("PairingTableView")
+        self.resize(500, 500)
+
+        self.add_pair_button = QtGui.QToolButton()
+        self.remove_pair_button = QtGui.QToolButton()
+
+        size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+        size_policy.setHorizontalStretch(0)
+        size_policy.setVerticalStretch(0)
+        size_policy.setHeightForWidth(self.add_pair_button.sizePolicy().hasHeightForWidth())
+        size_policy.setHeightForWidth(self.remove_pair_button.sizePolicy().hasHeightForWidth())
+
+        self.add_pair_button.setSizePolicy(size_policy)
+        self.add_pair_button.setObjectName("addGroupButton")
+        self.add_pair_button.setToolTip("Add a pair to the end of the table")
+        self.add_pair_button.setText("+")
+
+        self.remove_pair_button.setSizePolicy(size_policy)
+        self.remove_pair_button.setObjectName("removeGroupButton")
+        self.remove_pair_button.setToolTip("Remove selected/last pair(s) from the table")
+        self.remove_pair_button.setText("-")
+
+        self.horizontal_layout = QtGui.QHBoxLayout()
+        self.horizontal_layout.setObjectName("horizontalLayout")
+        self.horizontal_layout.addWidget(self.add_pair_button)
+        self.horizontal_layout.addWidget(self.remove_pair_button)
+        self.spacer_item = QtGui.QSpacerItem(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
+        self.horizontal_layout.addItem(self.spacer_item)
+        self.horizontal_layout.setAlignment(QtCore.Qt.AlignLeft)
+
+        self.vertical_layout = QtGui.QVBoxLayout(self)
+        self.vertical_layout.setObjectName("verticalLayout")
+        self.vertical_layout.addWidget(self.pairing_table)
+        self.vertical_layout.addLayout(self.horizontal_layout)
+
+        self.setLayout(self.vertical_layout)
+
+    def set_up_table(self):
+        self.pairing_table.setColumnCount(5)
+        self.pairing_table.setHorizontalHeaderLabels(["Pair Name", "Group 1", " Group 2", "Alpha", "Guess Alpha"])
+        header = self.pairing_table.horizontalHeader()
+        header.setResizeMode(0, QtGui.QHeaderView.Stretch)
+        header.setResizeMode(1, QtGui.QHeaderView.Stretch)
+        header.setResizeMode(2, QtGui.QHeaderView.Stretch)
+        header.setResizeMode(3, QtGui.QHeaderView.Stretch)
+        header.setResizeMode(4, QtGui.QHeaderView.ResizeToContents)
+        vertical_headers = self.pairing_table.verticalHeader()
+        vertical_headers.setMovable(False)
+        vertical_headers.setResizeMode(QtGui.QHeaderView.ResizeToContents)
+        vertical_headers.setVisible(True)
+
+        self.pairing_table.horizontalHeaderItem(0).setToolTip("The name of the pair :"
+                                                              "\n    - The name must be unique across all groups/pairs"
+                                                              "\n    - The name can only use digits, characters and _")
+        self.pairing_table.horizontalHeaderItem(1).setToolTip("Group 1 of the pair, selected from the grouping table")
+        self.pairing_table.horizontalHeaderItem(2).setToolTip("Group 2 of the pair, selected from the grouping table")
+        self.pairing_table.horizontalHeaderItem(3).setToolTip("The value of Alpha for the pair asymmetry:"
+                                                              "\n   - The number must be >= 0.0")
+        self.pairing_table.horizontalHeaderItem(4).setToolTip("Replace the current value of Alpha with one estimated"
+                                                              " from the data.")
+
+    def num_rows(self):
+        return self.pairing_table.rowCount()
+
+    def num_cols(self):
+        return self.pairing_table.columnCount()
+
+    def update_group_selections(self, group_name_list):
+        self._group_selections = group_name_list
+
+    def get_index_of_text(self, selector, text):
+        for i in range(selector.count()):
+            if str(selector.itemText(i)) == text:
+                return i
+        return 0
+
+    def clear(self):
+        # Go backwards to preserve indices
+        for row in reversed(range(self.num_rows())):
+            self.pairing_table.removeRow(row)
+
+    def notify_data_changed(self):
+        if not self._updating:
+            self.dataChanged.emit()
+
+    def add_entry_to_table(self, row_entries):
+        assert len(row_entries) == self.pairing_table.columnCount() - 1
+
+        row_position = self.pairing_table.rowCount()
+        self.pairing_table.insertRow(row_position)
+        for i, entry in enumerate(row_entries):
+            item = QtGui.QTableWidgetItem(entry)
+            if pair_columns[i] == 'pair_name':
+                pair_name_widget = table_utils.ValidatedTableItem(self._validate_pair_name_entry)
+                pair_name_widget.setText(entry)
+                self.pairing_table.setItem(row_position, i, pair_name_widget)
+            if pair_columns[i] == 'group_1':
+                group1_selector_widget = self._group_selection_cell_widget()
+                # ensure changing the selection sends an update signal
+                group1_selector_widget.currentIndexChanged.connect(lambda: self.on_cell_changed(row_position, 1))
+                index = self.get_index_of_text(group1_selector_widget, entry)
+                group1_selector_widget.setCurrentIndex(index)
+                self.pairing_table.setCellWidget(row_position, i, group1_selector_widget)
+            if pair_columns[i] == 'group_2':
+                group2_selector_widget = self._group_selection_cell_widget()
+                # ensure changing the selection sends an update signal
+                group2_selector_widget.currentIndexChanged.connect(lambda: self.on_cell_changed(row_position, 2))
+                index = self.get_index_of_text(group2_selector_widget, entry)
+                group2_selector_widget.setCurrentIndex(index)
+                self.pairing_table.setCellWidget(row_position, i, group2_selector_widget)
+            if pair_columns[i] == 'alpha':
+                alpha_widget = table_utils.ValidatedTableItem(self._validate_alpha)
+                alpha_widget.setText(entry)
+                self.pairing_table.setItem(row_position, i, alpha_widget)
+            self.pairing_table.setItem(row_position, i, item)
+        # guess alpha button
+        guess_alpha_widget = self._guess_alpha_button()
+        guess_alpha_widget.clicked.connect(lambda: self.guess_alpha_clicked_from_row(row_position))
+        self.pairing_table.setCellWidget(row_position, 4, guess_alpha_widget)
+
+    def _group_selection_cell_widget(self):
+        # The widget for the group selection columns
+        selector = QtGui.QComboBox(self)
+        selector.setToolTip("Select a group from the grouping table")
+        selector.addItems(self._group_selections)
+        return selector
+
+    def _guess_alpha_button(self):
+        # The widget for the guess alpha column
+        guess_alpha = QtGui.QPushButton(self)
+        guess_alpha.setToolTip("Estimate the alpha value for this pair")
+        guess_alpha.setText("Guess")
+        return guess_alpha
+
+    def guess_alpha_clicked_from_row(self, row):
+        self._on_guess_alpha_clicked(row)
+
+    def get_table_contents(self):
+        if self._updating:
+            return []
+        ret = [[None for _ in range(self.num_cols())] for _ in range(self.num_rows())]
+        for row in range(self.num_rows()):
+            for col in range(self.num_cols()):
+                if col == 1 or col == 2:
+                    # columns with widgets
+                    ret[row][col] = str(self.pairing_table.cellWidget(row, col).currentText())
+                elif col == 4:
+                    ret[row][col] = "Guess"
+                else:
+                    # columns without widgets
+                    ret[row][col] = str(self.pairing_table.item(row, col).text())
+        return ret
+
+    def get_table_item_text(self, row, col):
+        return self.pairing_table.item(row, col).text()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Signal / Slot connections
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def on_user_changes_pair_name(self, slot):
+        self._validate_pair_name_entry = slot
+
+    def on_user_changes_alpha(self, slot):
+        self._validate_alpha = slot
+
+    def on_guess_alpha_clicked(self, slot):
+        self._on_guess_alpha_clicked = slot
+
+    def on_add_pair_button_clicked(self, slot):
+        self.add_pair_button.clicked.connect(slot)
+
+    def on_remove_pair_button_clicked(self, slot):
+        self.remove_pair_button.clicked.connect(slot)
+
+    def on_table_data_changed(self, slot):
+        self._on_table_data_changed = slot
+
+    def on_item_changed(self):
+        """Not yet implemented."""
+        if not self._updating:
+            pass
+
+    def on_cell_changed(self, _row, _col):
+        if not self._updating:
+            self._on_table_data_changed(_row, _col)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Context Menu
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def contextMenuEvent(self, _event):
+        """Overridden method for dealing with the right-click context menu"""
+        self.menu = QtGui.QMenu(self)
+
+        self.add_pair_action = self._context_menu_add_pair_action(self.add_pair_button.clicked.emit)
+        self.remove_pair_action = self._context_menu_remove_pair_action(self.remove_pair_button.clicked.emit)
+
+        if self._disabled:
+            self.add_pair_action.setEnabled(False)
+            self.remove_pair_action.setEnabled(False)
+        # set-up the menu
+        self.menu.addAction(self.add_pair_action)
+        self.menu.addAction(self.remove_pair_action)
+        self.menu.popup(QtGui.QCursor.pos())
+
+    def _context_menu_add_pair_action(self, slot):
+        add_pair_action = QtGui.QAction('Add Pair', self)
+        if len(self._get_selected_row_indices()) > 0:
+            add_pair_action.setEnabled(False)
+        add_pair_action.triggered.connect(slot)
+        return add_pair_action
+
+    def _context_menu_remove_pair_action(self, slot):
+        if len(self._get_selected_row_indices()) > 1:
+            # use plural if >1 item selected
+            remove_pair_action = QtGui.QAction('Remove Pairs', self)
+        else:
+            remove_pair_action = QtGui.QAction('Remove Pair', self)
+        if self.num_rows() == 0:
+            remove_pair_action.setEnabled(False)
+        remove_pair_action.triggered.connect(slot)
+        return remove_pair_action
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Adding / Removing pairs
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def _get_selected_row_indices(self):
+        return list(set(index.row() for index in self.pairing_table.selectedIndexes()))
+
+    def get_selected_pair_names(self):
+        indexes = self._get_selected_row_indices()
+        return [str(self.pairing_table.item(i, 0).text()) for i in indexes]
+
+    def remove_selected_pairs(self):
+        indices = self._get_selected_row_indices()
+        for index in reversed(sorted(indices)):
+            self.pairing_table.removeRow(index)
+
+    def remove_last_row(self):
+        last_row = self.pairing_table.rowCount() - 1
+        if last_row >= 0:
+            self.pairing_table.removeRow(last_row)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Enabling / Disabling the table
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def enable_updates(self):
+        """Allow update signals to be sent."""
+        self._updating = False
+
+    def disable_updates(self):
+        """Prevent update signals being sent."""
+        self._updating = True
+
+    def enable_editing(self):
+        self.disable_updates()
+        self._disabled = False
+        self._enable_all_buttons()
+        self._enable_all_table_items()
+        self.enable_updates()
+
+    def disable_editing(self):
+        self.disable_updates()
+        self._disabled = True
+        self._disable_all_buttons()
+        self._disable_all_table_items()
+        self.enable_updates()
+
+    def _disable_all_table_items(self):
+        for row in range(self.num_rows()):
+            for col in range(self.num_cols()):
+                if col == 1 or col == 2 or col == 4:
+                    item = self.pairing_table.cellWidget(row, col)
+                    item.setEnabled(False)
+                else:
+                    item = self.pairing_table.item(row, col)
+                    item.setFlags(QtCore.Qt.ItemIsSelectable)
+
+    def _enable_all_table_items(self):
+        for row in range(self.num_rows()):
+            for col in range(self.num_cols()):
+                if col == 1 or col == 2 or col == 4:
+                    item = self.pairing_table.cellWidget(row, col)
+                    item.setEnabled(True)
+                else:
+                    item = self.pairing_table.item(row, col)
+                    item.setFlags(QtCore.Qt.ItemIsSelectable |
+                                  QtCore.Qt.ItemIsEditable |
+                                  QtCore.Qt.ItemIsEnabled)
+
+    def _enable_all_buttons(self):
+        self.add_pair_button.setEnabled(True)
+        self.remove_pair_button.setEnabled(True)
+
+    def _disable_all_buttons(self):
+        self.add_pair_button.setEnabled(False)
+        self.remove_pair_button.setEnabled(False)
diff --git a/scripts/Muon/GUI/Common/utilities/run_string_utils.py b/scripts/Muon/GUI/Common/utilities/run_string_utils.py
index 566fac649c164704ec2ca3d25f3fc5020a782f39..20c3bb6c8fba2cafb513b24e688a9aaebacac1b5 100644
--- a/scripts/Muon/GUI/Common/utilities/run_string_utils.py
+++ b/scripts/Muon/GUI/Common/utilities/run_string_utils.py
@@ -97,7 +97,7 @@ def run_string_to_list(run_string):
             max_length = len(range_max)
             min_length = len(range_min)
             if(max_length < min_length):
-                range_max = range_min[:max_length - min_length + 1] + range_max
+                range_max = range_min[:min_length - max_length] + range_max
 
             range_max = int(range_max)
             range_min = int(range_min)
diff --git a/scripts/Muon/GUI/MuonAnalysis/load_widget/load_widget_presenter.py b/scripts/Muon/GUI/MuonAnalysis/load_widget/load_widget_presenter.py
index e4ea4c43883e9e0a3c8d385e70ea442f9ee64f97..3f66fa8510c0a549b3bd87ed8ca142eb51495630 100644
--- a/scripts/Muon/GUI/MuonAnalysis/load_widget/load_widget_presenter.py
+++ b/scripts/Muon/GUI/MuonAnalysis/load_widget/load_widget_presenter.py
@@ -134,7 +134,6 @@ class LoadWidgetPresenter(object):
             self.outer = outer  # handle to containing class
 
         def notify_subscribers(self, arg=None):
-            # AnalysisDataService.clear()
             for workspace, run in zip(self.outer._model.workspaces, self.outer._model.runs):
                 for i, single_ws in enumerate(workspace['OutputWorkspace']):
                     name = str(run) + "_period_" + str(i)
diff --git a/scripts/SANS/SANSUtility.py b/scripts/SANS/SANSUtility.py
index fe6cc7e5dc7cab543955b471bb8e79f93b646079..2fa1b8e8a11f67e7c4ca1a75d3681bde65a0ae56 100644
--- a/scripts/SANS/SANSUtility.py
+++ b/scripts/SANS/SANSUtility.py
@@ -255,6 +255,8 @@ def getBinsBoundariesFromWorkspace(ws_reference):
 
 def getFilePathFromWorkspace(ws):
     ws_pointer = getWorkspaceReference(ws)
+    if isinstance(ws_pointer, WorkspaceGroup):
+        ws_pointer = ws_pointer[0]
     file_path = None
 
     try:
diff --git a/scripts/SANS/isis_reduction_steps.py b/scripts/SANS/isis_reduction_steps.py
index c19cdd28a4287295d7278f5c3a64b108938dd581..946ceedf515518c1f2dd8519b23e543c57dd1daf 100644
--- a/scripts/SANS/isis_reduction_steps.py
+++ b/scripts/SANS/isis_reduction_steps.py
@@ -219,8 +219,12 @@ class LoadRun(object):
                                                          monitor_appendix=appendix)
 
         loader_name = ''
+        if isinstance(outWs, WorkspaceGroup):
+            historyWs = outWs[0]
+        else:
+            historyWs = outWs
         try:
-            last_algorithm = outWs.getHistory().lastAlgorithm()
+            last_algorithm = historyWs.getHistory().lastAlgorithm()
             loader_name = last_algorithm.getProperty('LoaderName').value
         except RuntimeError as details:
             sanslog.warning(
diff --git a/scripts/SANS/sans/common/enums.py b/scripts/SANS/sans/common/enums.py
index 47d415468b45a1d4e48e8c807617b380b3075cde..c37c44259a55610fd142de46711f83b562c2c70f 100644
--- a/scripts/SANS/sans/common/enums.py
+++ b/scripts/SANS/sans/common/enums.py
@@ -11,7 +11,7 @@
 from __future__ import (absolute_import, division, print_function)
 from inspect import isclass
 from functools import partial
-from six import PY3
+from six import PY2
 
 
 # ----------------------------------------------------------------------------------------------------------------------
@@ -62,13 +62,21 @@ def string_convertible(cls):
         raise RuntimeError("Could not convert {0} to string. Unknown value.".format(convert_to_string))
 
     def from_string(elements, convert_from_string):
-        if PY3 and isinstance(convert_from_string, bytes):
+        if not PY2 and isinstance(convert_from_string, bytes):
             convert_from_string = convert_from_string.decode()
         for key, value in list(elements.items()):
             if convert_from_string == key:
                 return value
         raise RuntimeError("Could not convert {0} from string. Unknown value.".format(convert_from_string))
 
+    def has_member(elements, convert):
+        if not PY2 and isinstance(convert, bytes):
+            convert = convert.decode()
+        for key, value in list(elements.items()):
+            if convert == key or convert == value:
+                return True
+        return False
+
     # First get all enum/sub-class elements
     convertible_elements = {}
     for attribute_name, attribute_value in list(cls.__dict__.items()):
@@ -78,8 +86,10 @@ def string_convertible(cls):
     # Add the new static methods to the class
     partial_to_string = partial(to_string, convertible_elements)
     partial_from_string = partial(from_string, convertible_elements)
+    partial_has_member = partial(has_member, convertible_elements)
     setattr(cls, "to_string", staticmethod(partial_to_string))
     setattr(cls, "from_string", staticmethod(partial_from_string))
+    setattr(cls, "has_member", staticmethod(partial_has_member))
     return cls
 
 
diff --git a/scripts/SANS/sans/gui_logic/gui_common.py b/scripts/SANS/sans/gui_logic/gui_common.py
index 7bbaa0b6ad0e0eb5ee601823051e94cdf16aa60d..51bd235c7e8a15c8701ff3052ad31dd8c4af088b 100644
--- a/scripts/SANS/sans/gui_logic/gui_common.py
+++ b/scripts/SANS/sans/gui_logic/gui_common.py
@@ -214,3 +214,23 @@ def open_file_dialog(line_edit, filter_text, directory):
     if isinstance(file_name, tuple):
         file_name = file_name[0]
     line_edit.setText(file_name)
+
+
+def get_batch_file_dir_from_path(batch_file_path):
+    path, file = os.path.split(batch_file_path)
+    if path != "" and path[-1] != "/":
+        # Make string inline with other ConfigService paths
+        path += "/"
+    return path
+
+
+def add_dir_to_datasearch(batch_file_path, current_directories):
+    batch_file_directory = get_batch_file_dir_from_path(batch_file_path)
+    if batch_file_directory != "" and batch_file_directory not in current_directories:
+        current_directories = ";".join([current_directories, batch_file_directory])
+    return batch_file_directory, current_directories
+
+
+def remove_dir_from_datasearch(batch_file_path, directories):
+    new_dirs = ";".join([path for path in directories.split(";") if path != batch_file_path])
+    return new_dirs
diff --git a/scripts/SANS/sans/gui_logic/models/table_model.py b/scripts/SANS/sans/gui_logic/models/table_model.py
index 59faae476929db6204f6d5a8cc26aa04b78f4ae3..34c8318f44f973d97f259da45aa3008bc477f8e8 100644
--- a/scripts/SANS/sans/gui_logic/models/table_model.py
+++ b/scripts/SANS/sans/gui_logic/models/table_model.py
@@ -12,16 +12,16 @@ information regarding the custom output name and the information in the options
 
 from __future__ import (absolute_import, division, print_function)
 
+import functools
 import os
 import re
 
 from sans.common.constants import ALL_PERIODS
-from sans.gui_logic.models.basic_hint_strategy import BasicHintStrategy
 from sans.common.enums import RowState, SampleShape
-import functools
+from sans.common.file_information import SANSFileInformationFactory
+from sans.gui_logic.models.basic_hint_strategy import BasicHintStrategy
 from sans.gui_logic.presenter.create_file_information import create_file_information
 from ui.sans_isis.work_handler import WorkHandler
-from sans.common.file_information import SANSFileInformationFactory
 
 
 class TableModel(object):
@@ -308,6 +308,18 @@ class TableIndexModel(object):
                 self.sample_height, self.sample_width, self._convert_sample_shape_to_string(self.sample_shape),
                 self.options_column_model.get_options_string()]
 
+    def to_batch_list(self):
+        """
+        :return: a list of data in the order as would typically appear
+        in a batch file
+        """
+        return_list = [self.sample_scatter, self.output_name, self.sample_transmission,
+                       self.sample_direct, self.can_scatter, self.can_transmission,
+                       self.can_direct, self.user_file]
+        return_list = list(map(str, return_list))
+        return_list = list(map(str.strip, return_list))
+        return return_list
+
     def _convert_sample_shape_to_string(self, shape):
         if isinstance(shape, str):
             # TODO temporary fix to shape already being a str
diff --git a/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py b/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py
index 814ef10ebfb4a8275409d0d4ea44df26036874f0..1e276305b557402b467850a104d398f86f38ea85 100644
--- a/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py
+++ b/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py
@@ -12,33 +12,37 @@ for presenting and generating the reduction settings.
 
 from __future__ import (absolute_import, division, print_function)
 
-import os
 import copy
+import csv
+import os
+import sys
 import time
-from mantid.kernel import Logger, ConfigService
-from mantid.api import (FileFinder)
+import traceback
 
-from ui.sans_isis.sans_data_processor_gui import SANSDataProcessorGui
-from sans.gui_logic.models.state_gui_model import StateGuiModel
-from sans.gui_logic.models.batch_process_runner import BatchProcessRunner
-from sans.gui_logic.models.table_model import TableModel, TableIndexModel
-from sans.gui_logic.presenter.settings_diagnostic_presenter import (SettingsDiagnosticPresenter)
-from sans.gui_logic.presenter.masking_table_presenter import (MaskingTablePresenter)
-from sans.gui_logic.presenter.beam_centre_presenter import BeamCentrePresenter
-from sans.gui_logic.presenter.add_runs_presenter import OutputDirectoryObserver as SaveDirectoryObserver
-from sans.gui_logic.gui_common import (get_reduction_mode_strings_for_gui, get_string_for_gui_from_instrument)
-from sans.common.enums import (BatchReductionEntry, RangeStepType, SampleShape, FitType, RowState, SANSInstrument)
-from sans.user_file.user_file_reader import UserFileReader
+from mantid.api import (FileFinder)
+from mantid.kernel import Logger, ConfigService
 from sans.command_interface.batch_csv_file_parser import BatchCsvParser
 from sans.common.constants import ALL_PERIODS
+from sans.common.enums import (BatchReductionEntry, RangeStepType, SampleShape, FitType, RowState, SANSInstrument)
+from sans.gui_logic.gui_common import (get_reduction_mode_strings_for_gui, get_string_for_gui_from_instrument,
+                                       add_dir_to_datasearch, remove_dir_from_datasearch)
+from sans.gui_logic.models.batch_process_runner import BatchProcessRunner
 from sans.gui_logic.models.beam_centre_model import BeamCentreModel
-from sans.gui_logic.presenter.diagnostic_presenter import DiagnosticsPagePresenter
+from sans.gui_logic.models.create_state import create_states
 from sans.gui_logic.models.diagnostics_page_model import run_integral, create_state
+from sans.gui_logic.models.state_gui_model import StateGuiModel
+from sans.gui_logic.models.table_model import TableModel, TableIndexModel
+from sans.gui_logic.presenter.add_runs_presenter import OutputDirectoryObserver as SaveDirectoryObserver
+from sans.gui_logic.presenter.beam_centre_presenter import BeamCentrePresenter
+from sans.gui_logic.presenter.diagnostic_presenter import DiagnosticsPagePresenter
+from sans.gui_logic.presenter.masking_table_presenter import (MaskingTablePresenter)
+from sans.gui_logic.presenter.save_other_presenter import SaveOtherPresenter
+from sans.gui_logic.presenter.settings_diagnostic_presenter import (SettingsDiagnosticPresenter)
 from sans.sans_batch import SANSCentreFinder
-from sans.gui_logic.models.create_state import create_states
-from ui.sans_isis.work_handler import WorkHandler
+from sans.user_file.user_file_reader import UserFileReader
 from ui.sans_isis import SANSSaveOtherWindow
-from sans.gui_logic.presenter.save_other_presenter import SaveOtherPresenter
+from ui.sans_isis.sans_data_processor_gui import SANSDataProcessorGui
+from ui.sans_isis.work_handler import WorkHandler
 
 try:
     import mantidplot
@@ -93,6 +97,9 @@ class RunTabPresenter(object):
         def on_load_clicked(self):
             self._presenter.on_load_clicked()
 
+        def on_export_table_clicked(self):
+            self._presenter.on_export_table_clicked()
+
         def on_multi_period_selection(self, show_periods):
             self._presenter.on_multiperiod_changed(show_periods)
 
@@ -282,6 +289,7 @@ class RunTabPresenter(object):
         """
         Loads the user file. Populates the models and the view.
         """
+        error_msg = "Loading of the user file failed"
         try:
             # 1. Get the user file path from the view
             user_file_path = self._view.get_user_file_path()
@@ -294,31 +302,44 @@ class RunTabPresenter(object):
                 raise RuntimeError(
                     "The user path {} does not exist. Make sure a valid user file path"
                     " has been specified.".format(user_file_path))
-            self._table_model.user_file = user_file_path
-            # Clear out the current view
-            self._view.reset_all_fields_to_default()
-
-            # 3. Read and parse the user file
-            user_file_reader = UserFileReader(user_file_path)
-            user_file_items = user_file_reader.read_user_file()
-
-            # 4. Populate the model
-            self._state_model = StateGuiModel(user_file_items)
-            # 5. Update the views.
-            self._update_view_from_state_model()
-            self._beam_centre_presenter.update_centre_positions(self._state_model)
-
-            self._beam_centre_presenter.on_update_rows()
-            self._masking_table_presenter.on_update_rows()
-            self._workspace_diagnostic_presenter.on_user_file_load(user_file_path)
-
-            # 6. Warning if user file did not contain a recognised instrument
-            if self._view.instrument == SANSInstrument.NoInstrument:
-                raise RuntimeError("User file did not contain a SANS Instrument.")
-
-        except Exception as e:
-            self.sans_logger.error("Loading of the user file failed. {}".format(str(e)))
-            self.display_warning_box('Warning', 'Loading of the user file failed.', str(e))
+        except RuntimeError as path_error:
+            self.display_errors(path_error, error_msg + " when finding file.")
+        else:
+            try:
+                self._table_model.user_file = user_file_path
+                # Clear out the current view
+                self._view.reset_all_fields_to_default()
+
+                # 3. Read and parse the user file
+                user_file_reader = UserFileReader(user_file_path)
+                user_file_items = user_file_reader.read_user_file()
+            except (RuntimeError, ValueError) as e:
+                self.display_errors(e, error_msg + " when reading file.", use_error_name=True)
+            else:
+                try:
+                    # 4. Populate the model
+                    self._state_model = StateGuiModel(user_file_items)
+                    # 5. Update the views.
+                    self._update_view_from_state_model()
+                    self._beam_centre_presenter.update_centre_positions(self._state_model)
+
+                    self._beam_centre_presenter.on_update_rows()
+                    self._masking_table_presenter.on_update_rows()
+                    self._workspace_diagnostic_presenter.on_user_file_load(user_file_path)
+
+                    # 6. Warning if user file did not contain a recognised instrument
+                    if self._view.instrument == SANSInstrument.NoInstrument:
+                        raise RuntimeError("User file did not contain a SANS Instrument.")
+
+                except RuntimeError as instrument_e:
+                    # Only catch the error we know about
+                    # If a new exception is caused, we can now see the stack trace
+                    self.display_errors(instrument_e, error_msg + " when reading instrument.")
+                except Exception as other_error:
+                    # If we don't catch all exceptions, SANS can fail to open if last loaded
+                    # user file contains an error
+                    traceback.print_exc()
+                    self.display_errors(other_error, "Unknown error in loading user file.", use_error_name=True)
 
     def on_batch_file_load(self):
         """
@@ -331,6 +352,10 @@ class RunTabPresenter(object):
             if not batch_file_path:
                 return
 
+            datasearch_dirs = ConfigService["datasearch.directories"]
+            batch_file_directory, datasearch_dirs = add_dir_to_datasearch(batch_file_path, datasearch_dirs)
+            ConfigService["datasearch.directories"] = datasearch_dirs
+
             if not os.path.exists(batch_file_path):
                 raise RuntimeError(
                     "The batch file path {} does not exist. Make sure a valid batch file path"
@@ -348,6 +373,10 @@ class RunTabPresenter(object):
                 self._add_row_to_table_model(row, index)
             self._table_model.remove_table_entries([len(parsed_rows)])
         except RuntimeError as e:
+            if batch_file_directory:
+                # Remove added directory from datasearch.directories
+                ConfigService["datasearch.directories"] = remove_dir_from_datasearch(batch_file_directory, datasearch_dirs)
+
             self.sans_logger.error("Loading of the batch file failed. {}".format(str(e)))
             self.display_warning_box('Warning', 'Loading of the batch file failed', str(e))
 
@@ -544,15 +573,70 @@ class RunTabPresenter(object):
             self.sans_logger.error("Process halted due to: {}".format(str(e)))
             self.display_warning_box("Warning", "Process halted", str(e))
 
+    def on_export_table_clicked(self):
+        non_empty_rows = self.get_row_indices()
+        if len(non_empty_rows) == 0:
+            self.sans_logger.notice("Cannot export table as it is empty.")
+            return
+
+        # Python 2 and 3 take input in different modes for writing lists to csv files
+        if sys.version_info[0] == 2:
+            open_type = 'wb'
+        else:
+            open_type = 'w'
+
+        try:
+            self._view.disable_buttons()
+
+            default_filename = self._table_model.batch_file
+            filename = self.display_save_file_box("Save table as", default_filename, "*.csv")
+
+            if filename:
+                self.sans_logger.notice("Starting export of table.")
+                if filename[-4:] != '.csv':
+                    filename += '.csv'
+
+                with open(filename, open_type) as outfile:
+                    # Pass filewriting object rather than filename to make testing easier
+                    writer = csv.writer(outfile)
+                    self._export_table(writer, non_empty_rows)
+                    self.sans_logger.notice("Table exporting finished.")
+
+            self._view.enable_buttons()
+        except Exception as e:
+            self._view.enable_buttons()
+            self.sans_logger.error("Export halted due to : {}".format(str(e)))
+            self.display_warning_box("Warning", "Export halted", str(e))
+
     def on_multiperiod_changed(self, show_periods):
         if show_periods:
             self._view.show_period_columns()
         else:
             self._view.hide_period_columns()
 
+    def display_errors(self, error, context_msg, use_error_name=False):
+        """
+        Code for alerting the user to a caught error
+        :param error: a caught exception
+        :param context_msg: string. Text to explain what SANS was trying to do
+                            when the error occurred. e.g. 'Loading of the user file failed'.
+        :param use_error_name: bool. If True, append type of error (e.g. RuntimeError) to context_msg
+        :return:
+        """
+        logger_msg = context_msg
+        if use_error_name:
+            logger_msg += " {}:".format(type(error).__name__)
+        logger_msg += " {}"
+        self.sans_logger.error(logger_msg.format(str(error)))
+        self.display_warning_box('Warning', context_msg, str(error))
+
     def display_warning_box(self, title, text, detailed_text):
         self._view.display_message_box(title, text, detailed_text)
 
+    def display_save_file_box(self, title, default_path, file_filter):
+        filename = self._view.display_save_file_box(title, default_path, file_filter)
+        return filename
+
     def notify_progress(self, row, out_shift_factors, out_scale_factors):
         self.increment_progress()
         if out_scale_factors and out_shift_factors:
@@ -1123,6 +1207,40 @@ class RunTabPresenter(object):
     def get_cell_value(self, row, column):
         return self._view.get_cell(row=row, column=self.table_index[column], convert_to=str)
 
+    def _export_table(self, filewriter, rows):
+        """
+        Take the current table model, and create a comma delimited csv file
+        :param filewriter: File object to be written to
+        :param rows: list of indices for non-empty rows
+        :return: Nothing
+        """
+        for row in rows:
+                table_row = self._table_model.get_table_entry(row).to_batch_list()
+                batch_file_row = self._create_batch_entry_from_row(table_row)
+                filewriter.writerow(batch_file_row)
+
+    @staticmethod
+    def _create_batch_entry_from_row(row):
+        batch_file_keywords = ["sample_sans",
+                               "output_as",
+                               "sample_trans",
+                               "sample_direct_beam",
+                               "can_sans",
+                               "can_trans",
+                               "can_direct_beam",
+                               "user_file"]
+
+        loop_range = min(len(row), len(batch_file_keywords))
+        new_row = [''] * (2 * loop_range)
+
+        for i in range(loop_range):
+            key = batch_file_keywords[i]
+            value = row[i]
+            new_row[2*i] = key
+            new_row[2*i + 1] = value
+
+        return new_row
+
     # ------------------------------------------------------------------------------------------------------------------
     # Settings
     # ------------------------------------------------------------------------------------------------------------------
diff --git a/scripts/SANS/sans/user_file/user_file_parser.py b/scripts/SANS/sans/user_file/user_file_parser.py
index 153757f9fb3f0962402719b5d5a352b3c3bf1932..88c94f89f938a5555e182e08ca40acae3f9d1813 100644
--- a/scripts/SANS/sans/user_file/user_file_parser.py
+++ b/scripts/SANS/sans/user_file/user_file_parser.py
@@ -60,7 +60,8 @@ def extract_int_range(to_extract):
 def extract_list(to_extract, separator, converter):
     to_extract = to_extract.strip()
     to_extract = ' '.join(to_extract.split())
-    string_list = [element.replace(" ", "") for element in to_extract.split(separator)]
+    string_list = [element.replace(" ", "") for element in re.split(separator, to_extract)]
+    string_list = [element for element in string_list if element != ""]
     return [converter(element) for element in string_list]
 
 
@@ -75,7 +76,8 @@ def extract_string_list(to_extract, separator=","):
 def extract_float_range_midpoint_and_steps(to_extract, separator):
     to_extract = ' '.join(to_extract.split())
 
-    entries_string = to_extract.split(separator)
+    entries_string = re.split(separator, to_extract)
+    entries_string = [element for element in entries_string if element != ""]
     number_of_entries = len(entries_string)
     if number_of_entries != 5:
         raise RuntimeError("Expected a range defined by 5 numbers,"
@@ -113,6 +115,7 @@ start_string = "^\\s*"
 end_string = "\\s*$"
 space_string = "\\s+"
 rebin_string = "(\\s*[-+]?\\d+(\\.\\d+)?)(\\s*,\\s*[-+]?\\d+(\\.\\d+)?)*"
+comma_or_space_separator_string = ",? *"  # Will split the input string if commas OR spaces separate values
 
 
 # ----------------------------------------------------------------
@@ -508,7 +511,7 @@ class LimitParser(UserFileComponentParser):
         L/PHI[/NOMIRROR] d1 d2
 
         L/Q/ q1 q2 [dq[/LIN]]  or  L/Q q1 q2 [dq[/LOG]]
-        L/Q q1,dq1,q3,dq2,q2 [/LIN]]  or  L/Q q1,dq1,q3,dq2,q2 [/LOG]]
+        L/Q q1,dq1,q2,dq2,q3 [/LIN]]  or  L/Q q1,dq1,q2,dq2,q3 [/LOG]]
         but apparently also L/Q q1, dq1, q2, dq2, q3, dq3, ... [/LOG | /LIN] is allowed
 
         L/Q/RCut c
@@ -565,8 +568,12 @@ class LimitParser(UserFileComponentParser):
         self._q = "\\s*Q\\s*"
         self._q_simple_pattern = re.compile(start_string + self._q + space_string +
                                             self._simple_range + end_string)
-        self._q_complex_pattern = re.compile(start_string + self._q + space_string + self._complex_range + end_string)
-        self._q_complex_pattern_2 = re.compile(start_string + self._q + space_string + self._complex_range_2 +
+
+        q_complex_range = ",?".join(self._complex_range.split(","))  # allow comma and space separation for q ranges
+        q_complex_range_2 = ",?".join(self._complex_range_2.split(","))
+
+        self._q_complex_pattern = re.compile(start_string + self._q + space_string + q_complex_range + end_string)
+        self._q_complex_pattern_2 = re.compile(start_string + self._q + space_string + q_complex_range_2 +
                                                end_string)
 
         # Qxy limits
@@ -651,7 +658,7 @@ class LimitParser(UserFileComponentParser):
             # We have to make sure that there is an odd number of elements
             range_with_steps_string = re.sub(self._q, "", line)
             range_with_steps_string = re.sub(self._lin_or_log, "", range_with_steps_string)
-            range_with_steps = extract_float_list(range_with_steps_string, ",")
+            range_with_steps = extract_float_list(range_with_steps_string, comma_or_space_separator_string)
             pattern_matches = len(range_with_steps) > 5 and (len(range_with_steps) % 2 == 1)
         return pattern_matches
 
@@ -767,7 +774,8 @@ class LimitParser(UserFileComponentParser):
 
         # Remove the step type
         range_with_steps_string = re.sub(self._lin_or_log, "", complex_range_input)
-        range_with_steps = extract_float_range_midpoint_and_steps(range_with_steps_string, ",")
+        range_with_steps = extract_float_range_midpoint_and_steps(range_with_steps_string,
+                                                                  comma_or_space_separator_string)
 
         # Check if there is a sign on the individual steps, this shows if something had been marked as linear or log.
         # If there is an explicit LOG/LIN command, then this overwrites the sign
@@ -791,7 +799,7 @@ class LimitParser(UserFileComponentParser):
 
         # Remove the step type
         range_with_steps_string = re.sub(self._lin_or_log, "", complex_range_input)
-        range_with_steps = extract_float_list(range_with_steps_string, ",")
+        range_with_steps = extract_float_list(range_with_steps_string, comma_or_space_separator_string)
 
         if step_type is not None:
             prefix = -1.0 if step_type is RangeStepType.Log else 1.0
diff --git a/scripts/plotting_test.py b/scripts/plotting_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7001d06897dd0cce9ca29dc94a4ae88af390466
--- /dev/null
+++ b/scripts/plotting_test.py
@@ -0,0 +1,61 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import absolute_import, print_function
+
+from PyQt4 import QtGui
+
+import sys
+
+from Muon.GUI.Common import message_box
+
+from MultiPlotting.multiPlotting_widget import MultiPlotWidget
+from MultiPlotting.multiPlotting_context import *
+
+import mantid.simpleapi as mantid
+
+
+class plotTestGui(QtGui.QMainWindow):
+
+    def __init__(self, parent=None):
+        super(plotTestGui, self).__init__(parent)
+        self._context = PlottingContext()
+        self.test = MultiPlotWidget(self._context, self)
+        ws = setUpSubplot()
+        self.test.add_subplot("test", 221)
+        self.test.add_subplot("bob", 222)
+        self.test.add_subplot("moo", 223)
+        self.test.add_subplot("baa", 224)
+        self.test.plot("test", ws, specNum=26)
+        self.test.plot("bob", ws, specNum=1)
+        self.test.plot("moo", ws, specNum=42)
+        self.test.plot("baa", ws, specNum=2)
+        self.test.set_all_values()
+        self.setCentralWidget(self.test)
+
+        self.setWindowTitle("plot test")
+
+
+def setUpSubplot():
+    ws = mantid.Load("MUSR00015089", OutputWorkspace="ws")
+    return ws
+
+
+def qapp():
+    if QtGui.QApplication.instance():
+        _app = QtGui.QApplication.instance()
+    else:
+        _app = QtGui.QApplication(sys.argv)
+    return _app
+
+
+app = qapp()
+try:
+    window = plotTestGui()
+    window.show()
+    app.exec_()
+except RuntimeError as error:
+    message_box.warning(str(error))
diff --git a/scripts/test/ErrorReportPresenterTest.py b/scripts/test/ErrorReportPresenterTest.py
index 3f1a89e1e8ccc1638b4752f21304c9f13cb0fd4d..66d4413a5681faf37bf463858c03d80d40d70b8f 100644
--- a/scripts/test/ErrorReportPresenterTest.py
+++ b/scripts/test/ErrorReportPresenterTest.py
@@ -26,10 +26,6 @@ class ErrorReportPresenterTest(unittest.TestCase):
         self.zip_recovery_mock = zip_recovery_patcher.start()
         self.zip_recovery_mock.return_value = ('zipped_file', 'file_hash')
 
-        file_removal_patcher = mock.patch('ErrorReporter.error_report_presenter.remove_recovery_file')
-        self.addCleanup(file_removal_patcher.stop)
-        self.file_removal_mock = file_removal_patcher.start()
-
         self.view = mock.MagicMock()
         self.exit_code = 255
         self.error_report_presenter = ErrorReporterPresenter(self.view, self.exit_code)
@@ -97,7 +93,7 @@ class ErrorReportPresenterTest(unittest.TestCase):
         self.error_report_presenter._send_report_to_server = mock.MagicMock(return_value=201)
         self.error_report_presenter._upload_recovery_file = mock.MagicMock()
         self.error_report_presenter._handle_exit = mock.MagicMock()
-        
+
         self.error_report_presenter.error_handler(continue_working, share, name, email, text_box)
 
         self.error_report_presenter._send_report_to_server.called_once_with(share_identifiable=True, name=name, email=email,
diff --git a/scripts/test/MultiPlotting/AxisChangerTwoPresenter_test.py b/scripts/test/MultiPlotting/AxisChangerTwoPresenter_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..38a5652a8a7490d59bf7242233b05b9f6ff8949b
--- /dev/null
+++ b/scripts/test/MultiPlotting/AxisChangerTwoPresenter_test.py
@@ -0,0 +1,49 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+
+from MultiPlotting.AxisChanger.axis_changer_presenter import AxisChangerPresenter
+from MultiPlotting.AxisChanger.axis_changer_view import AxisChangerView
+
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class AxisChangerTwoPresenterTest(unittest.TestCase):
+    def setUp(self):
+        view = mock.create_autospec(AxisChangerView)
+        self.acp = AxisChangerPresenter(view)
+        self.view = self.acp.view
+        self.slot = mock.Mock()
+        self.bounds = mock.Mock()
+
+    def test_get_bounds(self):
+        self.acp.get_bounds()
+        self.assertEquals(self.view.get_bounds.call_count, 1)
+
+    def test_set_bounds(self):
+        self.acp.set_bounds(self.bounds)
+        self.view.set_bounds.assert_called_with(self.bounds)
+
+    def test_clear_bounds(self):
+        self.acp.clear_bounds()
+        self.assertEquals(self.view.clear_bounds.call_count, 1)
+
+    def test_on_lower_bound_changed(self):
+        self.acp.on_bound_changed(self.slot)
+        self.view.on_bound_changed.assert_called_with(self.slot)
+
+    def test_unreg_bound_changed(self):
+        self.acp.unreg_on_bound_changed(self.slot)
+        self.view.unreg_bound_changed.assert_called_with(self.slot)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/scripts/test/MultiPlotting/AxisChangerTwoView_test.py b/scripts/test/MultiPlotting/AxisChangerTwoView_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..202fa62e69aaa085c8d58a5d0057748103c138d4
--- /dev/null
+++ b/scripts/test/MultiPlotting/AxisChangerTwoView_test.py
@@ -0,0 +1,76 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+
+from MultiPlotting.AxisChanger.axis_changer_view import AxisChangerView
+
+from Muon.GUI.Common import mock_widget
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class AxisChangerTwoViewTest(unittest.TestCase):
+    def setUp(self):
+        self._qapp = mock_widget.mockQapp()
+
+        label = "test"
+        self.view = AxisChangerView(label)
+        self.test_bounds = ["10", "20"]
+
+        self.view.lower_bound.text = mock.Mock(
+            return_value=self.test_bounds[0])
+        self.view.upper_bound.text = mock.Mock(
+            return_value=self.test_bounds[1])
+
+        self.view.lower_bound.setText = mock.Mock()
+        self.view.upper_bound.setText = mock.Mock()
+
+        self.view.lower_bound.clear = mock.Mock()
+        self.view.upper_bound.clear = mock.Mock()
+
+        self.view.sig_bound_changed = mock.Mock()
+        self.view.sig_bound_changed.emit = mock.Mock()
+        self.view.sig_bound_changed.connect = mock.Mock()
+        self.view.sig_bound_changed.disconnect = mock.Mock()
+
+        self.slot = mock.Mock()
+
+    def test_get_bounds(self):
+        self.assertEqual(
+            self.view.get_bounds(), [
+                int(bound) for bound in self.test_bounds])
+
+    def test_set_bounds(self):
+        self.view.set_bounds(self.test_bounds)
+        self.view.lower_bound.setText.assert_called_with(self.test_bounds[0])
+        self.view.upper_bound.setText.assert_called_with(self.test_bounds[1])
+
+    def test_clear_bounds(self):
+        self.view.clear_bounds()
+        self.assertEquals(self.view.lower_bound.clear.call_count, 1)
+        self.assertEquals(self.view.upper_bound.clear.call_count, 1)
+
+    def test_bound_changed(self):
+        self.view._bound_changed()
+        self.view.sig_bound_changed.emit.assert_called_with(
+            list(self.view.get_bounds()))
+
+    def test_on_bound_changed(self):
+        self.view.on_bound_changed(self.slot)
+        self.view.sig_bound_changed.connect.assert_called_with(self.slot)
+
+    def test_unreg_on_bound_changed(self):
+        self.view.unreg_bound_changed(self.slot)
+        self.view.sig_bound_changed.disconnect.assert_called_with(
+            self.slot)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/scripts/test/MultiPlotting/CMakeLists.txt b/scripts/test/MultiPlotting/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7cc5fb72f926c0fecfd87068fbe7b60ae67ee4ab
--- /dev/null
+++ b/scripts/test/MultiPlotting/CMakeLists.txt
@@ -0,0 +1,20 @@
+#
+## Tests for Muon GUIs
+##
+
+set ( TEST_PY_FILES
+   AxisChangerTwoPresenter_test.py
+   AxisChangerTwoView_test.py
+   QuickEditPresenter_test.py
+   QuickEditWidget_test.py
+   MultiPlottingContext_test.py
+   MultiPlotWidget_test.py
+   SubPlotContext_test.py
+)
+
+check_tests_valid ( ${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES} )
+
+# Prefix for test name=PythonAlgorithms
+set ( PYUNITTEST_QT_API pyqt ) # force to use qt4
+pyunittest_add_test ( ${CMAKE_CURRENT_SOURCE_DIR} python.MuonQt4 ${TEST_PY_FILES} )
+unset ( PYUNITTEST_QT_API )
diff --git a/scripts/test/MultiPlotting/MultiPlotWidget_test.py b/scripts/test/MultiPlotting/MultiPlotWidget_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..65d8a85f5d52a39792aef3ab9247a11b9ac8f869
--- /dev/null
+++ b/scripts/test/MultiPlotting/MultiPlotWidget_test.py
@@ -0,0 +1,215 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+
+from Muon.GUI.Common import mock_widget
+
+from MultiPlotting.multiPlotting_widget import MultiPlotWidget
+from MultiPlotting.QuickEdit.quickEdit_widget import QuickEditWidget
+from MultiPlotting.subplot.subPlot import subPlot
+from MultiPlotting.multiPlotting_context import PlottingContext
+
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+class bounds(object):
+    def __init__(self,x,y):
+        self.x = x
+        self.y =y
+        self.error = False
+
+    @property
+    def xbounds(self):
+       return self.x
+
+    @property
+    def ybounds(self):
+       return self.y
+
+    @property
+    def errors(self):
+        return self.error
+
+def data():
+    values = {}
+    values["one"] = bounds([5,20],[5,10])
+    values["two"] = bounds([6,10],[0,9])
+    values["three"] = bounds([-1,11],[7,8])
+    values["four"] = bounds([4,12],[4,50])
+    return values
+
+class MultiPlotWidgetTest(unittest.TestCase):
+
+    def setUp(self):
+        self._qapp = mock_widget.mockQapp()
+        context = PlottingContext()
+        self.widget = MultiPlotWidget(context)
+ 
+    def test_add_subplot(self):
+        #with mock.patch("MultiPlotting.subplot.subPlot_context.subPlotContext.addLine") as patch:
+        with mock.patch("MultiPlotting.QuickEdit.quickEdit_widget.QuickEditWidget.add_subplot") as qe_patch:
+            self.widget.add_subplot("test",111)
+            self.assertEquals(qe_patch.call_count,1)
+
+    def test_plot(self):
+        with mock.patch("MultiPlotting.subplot.subPlot.subPlot.plot") as patch:
+             ws = mock.MagicMock()
+             subplotName = "test"
+             specNum = 4
+             self.widget.plot(subplotName, ws, specNum)
+             patch.assert_called_with(subplotName, ws, specNum=specNum)
+             self.assertEquals(patch.call_count,1)
+
+    def test_setAllValues(self):
+        self.widget._context.subplots = data()
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value = list(data().keys()))
+        self.widget._x_range_changed = mock.MagicMock()
+        self.widget._y_range_changed = mock.MagicMock()
+        self.widget._check_all_errors = mock.MagicMock(return_value = False)
+        self.widget._change_errors = mock.MagicMock()
+
+        self.widget.set_all_values()
+        self.widget._x_range_changed.assert_called_with([-1,20])
+        self.widget._y_range_changed.assert_called_with([0,50])
+
+    def test_updateQuickEdit1Match(self):
+        self.widget._context.subplots = data()
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =["two"])
+        self.widget.quickEdit.set_plot_x_range = mock.MagicMock()
+        self.widget.quickEdit.set_plot_y_range = mock.MagicMock()
+
+        self.widget._update_quick_edit("two")
+        self.widget.quickEdit.set_plot_x_range.assert_called_with([6,10])
+        self.widget.quickEdit.set_plot_y_range.assert_called_with([0,9])
+
+    def test_updateQuickEdit1NoMatch(self):
+        self.widget._context.subplots = data()
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =["two"])
+        self.widget.quickEdit.set_plot_x_range = mock.MagicMock()
+        self.widget.quickEdit.set_plot_y_range = mock.MagicMock()
+
+        self.widget._update_quick_edit("three")
+        self.assertEquals(self.widget.quickEdit.set_plot_x_range.call_count,0)
+        self.assertEquals(self.widget.quickEdit.set_plot_y_range.call_count,0)
+
+    def test_updateQuickEditMany(self):
+        self.widget._context.subplots = data()
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =["two","three"])
+        self.widget.quickEdit.set_plot_x_range = mock.MagicMock()
+        self.widget.quickEdit.set_plot_y_range = mock.MagicMock()
+
+        self.widget._update_quick_edit("two")
+        self.widget.quickEdit.set_plot_x_range.assert_called_with([6,10])
+        self.widget.quickEdit.set_plot_y_range.assert_called_with([0,9])
+
+    def test_selectionChanged(self):
+        self.widget._context.subplots = data()
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =["two"])
+        self.widget._check_all_errors = mock.MagicMock(return_value = False)
+        self.widget._change_errors = mock.MagicMock()
+        self.widget.quickEdit.set_plot_x_range = mock.MagicMock()
+        self.widget.quickEdit.set_plot_y_range = mock.MagicMock()
+
+        self.widget._selection_changed(1)
+        self.widget.quickEdit.set_plot_x_range.assert_called_with([6,10])
+        self.widget.quickEdit.set_plot_y_range.assert_called_with([0,9])
+
+  
+    def test_selectionChangedAll(self):
+        self.widget._context.subplots = data()
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =["two","three"])
+        xbounds = [-1,2]
+        ybounds = [-10,20]
+        self.widget._context.get_xBounds = mock.MagicMock(return_value = xbounds)
+        self.widget._context.get_yBounds = mock.MagicMock(return_value = ybounds)
+        self.widget._check_all_errors = mock.MagicMock(return_value = False)
+        self.widget._change_errors = mock.MagicMock()
+        self.widget.quickEdit.set_plot_x_range = mock.MagicMock()
+        self.widget.quickEdit.set_plot_y_range = mock.MagicMock()
+        self.widget._x_range_changed = mock.MagicMock()
+        self.widget._y_range_changed = mock.MagicMock()
+
+        self.widget._selection_changed(1)
+        self.widget.quickEdit.set_plot_x_range.assert_called_with(xbounds)
+        self.widget.quickEdit.set_plot_y_range.assert_called_with(ybounds)
+        self.widget._x_range_changed.assert_called_with(xbounds)
+        self.widget._y_range_changed.assert_called_with(ybounds)
+
+    def test_xRangeChanged(self):
+        names = ["two","three"]
+        xbounds = [9,18]
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =names)
+        self.widget._context.set_xBounds = mock.MagicMock()
+        self.widget.plots.set_plot_x_range = mock.MagicMock()
+        self.widget._x_range_changed(xbounds)
+        self.widget._context.set_xBounds.assert_called_with(xbounds)
+        self.widget.plots.set_plot_x_range.assert_called_with(names,xbounds)
+
+    def test_xRangeChanged1(self):
+        names = ["two"]
+        xbounds = [9,18]
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =names)
+        self.widget._context.set_xBounds = mock.MagicMock()
+        self.widget.plots.set_plot_x_range = mock.MagicMock()
+        self.widget._x_range_changed(xbounds)
+        self.assertEquals(self.widget._context.set_xBounds.call_count, 0)
+        self.widget.plots.set_plot_x_range.assert_called_with(names,xbounds)
+
+    def test_yRangeChanged(self):
+        names = ["two","three"]
+        ybounds = [9,18]
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =names)
+        self.widget._context.set_yBounds = mock.MagicMock()
+        self.widget.plots.set_plot_y_range = mock.MagicMock()
+        self.widget._y_range_changed(ybounds)
+        self.widget._context.set_yBounds.assert_called_with(ybounds)
+        self.widget.plots.set_plot_y_range.assert_called_with(names,ybounds)
+
+    def test_yRangeChanged1(self):
+        names = ["two"]
+        ybounds = [9,18]
+        # mocks as we only want to test logic
+        self.widget.quickEdit.get_selection = mock.MagicMock(return_value =names)
+        self.widget._context.set_yBounds = mock.MagicMock()
+        self.widget.plots.set_plot_y_range = mock.MagicMock()
+        self.widget._y_range_changed(ybounds)
+        self.assertEquals(self.widget._context.set_yBounds.call_count, 0)
+        self.widget.plots.set_plot_y_range.assert_called_with(names,ybounds)
+
+    def test_checkAllErrorsFalse(self):
+        context = data()
+        self.widget._context.subplots = context
+        self.assertEquals(self.widget._check_all_errors(context.keys()),False)
+
+    def test_checkAllErrorsTrue(self):
+        context = data()
+        for name in context.keys():
+            context[name].error = True
+        self.widget._context.subplots = context
+        self.assertEquals(self.widget._check_all_errors(context.keys()),True)
+
+    def test_checkAllErrors1True(self):
+        context = data()
+        context["two"].error = True
+        self.widget._context.subplots = context
+        self.assertEquals(self.widget._check_all_errors(context.keys()),False)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/scripts/test/MultiPlotting/MultiPlottingContext_test.py b/scripts/test/MultiPlotting/MultiPlottingContext_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac143445ac85c00ffe46d5182b3ba1c6bf69ec37
--- /dev/null
+++ b/scripts/test/MultiPlotting/MultiPlottingContext_test.py
@@ -0,0 +1,61 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+
+from MultiPlotting.multiPlotting_context import PlottingContext
+from MultiPlotting.subplot.subPlot_context import subPlotContext
+
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+class gen_ws(object):
+    def __init__(self,mock):
+       self._input = "in"
+       self._OutputWorkspace = mock
+
+    def __len__(self):
+        return 2
+
+    @property
+    def OutputWorkspace(self):
+        return self._OutputWorkspace
+
+class MultiPlottingContextTest(unittest.TestCase):
+    def setUp(self):
+        self.context = PlottingContext()
+ 
+    def test_add_line_1(self):
+        specNum = 4
+        ws = mock.MagicMock()
+        # add mock subplot
+        subplot = mock.MagicMock()
+        self.subplot = mock.create_autospec(subPlotContext)
+        with mock.patch("MultiPlotting.subplot.subPlot_context.subPlotContext.addLine") as patch:
+            self.context.addSubplot("one",subplot) 
+            self.context.addLine("one",ws,specNum)
+            self.assertEquals(patch.call_count,1)
+            patch.assert_called_with(ws,specNum)
+
+    def test_add_line_2(self):
+        specNum = 4
+        mockWS = mock.MagicMock()
+        ws = gen_ws(mockWS)
+        # add mock subplot
+        subplot = mock.MagicMock()
+        self.subplot = mock.create_autospec(subPlotContext)
+        with mock.patch("MultiPlotting.subplot.subPlot_context.subPlotContext.addLine") as patch:
+            self.context.addSubplot("one",subplot) 
+            self.context.addLine("one",ws,specNum)
+            self.assertEquals(patch.call_count,1)
+            patch.assert_called_with(mockWS,specNum)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/scripts/test/MultiPlotting/QuickEditPresenter_test.py b/scripts/test/MultiPlotting/QuickEditPresenter_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..81a0095ebab7ee36ad1a1af385b88cf1b7c0a151
--- /dev/null
+++ b/scripts/test/MultiPlotting/QuickEditPresenter_test.py
@@ -0,0 +1,94 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+
+from MultiPlotting.QuickEdit.quickEdit_presenter import QuickEditPresenter
+from MultiPlotting.QuickEdit.quickEdit_view import QuickEditView
+
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class QuickEditPresenterTest(unittest.TestCase):
+    def setUp(self):
+        self.view = mock.create_autospec(QuickEditView)
+        self.pres = QuickEditPresenter(self.view)
+        self.slot = mock.Mock()
+
+    def test_add_subplot(self):
+        name = "new plot"
+        self.pres.add_subplot(name)
+        self.assertEquals(self.view.add_subplot.call_count, 1)
+        self.assertEquals(self.view.find_index.call_count, 1)
+        self.assertEquals(self.view.set_index.call_count, 1)
+
+    def test_connect_autoscale(self):
+        self.pres.connect_autoscale_changed(self.slot)
+        self.view.connect_autoscale_changed.assert_called_with(self.slot)
+
+    def test_connect_errors(self):
+        self.pres.connect_errors_changed(self.slot)
+        self.view.connect_errors_changed.assert_called_with(self.slot)
+
+    def test_connect_x_range(self):
+        self.pres.connect_x_range_changed(self.slot)
+        self.view.connect_x_range_changed.assert_called_with(self.slot)
+ 
+    def test_connect_y_range(self):
+        self.pres.connect_y_range_changed(self.slot)
+        self.view.connect_y_range_changed.assert_called_with(self.slot)
+
+    def test_connect_plot_selection(self):
+        self.pres.connect_plot_selection(self.slot)
+        self.view.connect_plot_selection.assert_called_with(self.slot)
+
+    def test_all(self):
+        names = ["all","one","two"]
+        def plots(index):
+            return names[index]
+        self.view.number_of_plots = mock.MagicMock(return_value = len(names))
+        self.view.plot_at_index = mock.MagicMock(side_effect = plots)
+        output = self.pres.all()
+        # should miss out all
+        for k in range(len(output)):
+            self.assertEquals(names[k+1], output[k])
+
+    def test_set_plot_x_range(self):
+        self.pres.set_plot_x_range([0,1])
+        self.view.set_plot_x_range.assert_called_with([0,1])
+        
+    def test_set_plot_y_range(self):
+        self.pres.set_plot_y_range([0,1])
+        self.view.set_plot_y_range.assert_called_with([0,1])
+           
+    def test_set_errors_TT(self):
+        self.view.get_errors = mock.MagicMock(return_value = True)
+        self.pres.set_errors(True)
+        self.assertEquals(self.view.set_errors.call_count,0)
+       
+    def test_set_errors_TF(self):
+        self.view.get_errors = mock.MagicMock(return_value = True)
+        self.pres.set_errors(False)
+        self.assertEquals(self.view.set_errors.call_count,1)
+        self.view.set_errors.assert_called_with(False)
+
+    def test_set_errors_FT(self):
+        self.view.get_errors = mock.MagicMock(return_value = False)
+        self.pres.set_errors(True)
+        self.assertEquals(self.view.set_errors.call_count,1)
+        self.view.set_errors.assert_called_with(True)
+
+    def test_set_errors_FF(self):
+        self.view.get_errors = mock.MagicMock(return_value = False)
+        self.pres.set_errors(False)
+        self.assertEquals(self.view.set_errors.call_count,0)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/scripts/test/MultiPlotting/QuickEditWidget_test.py b/scripts/test/MultiPlotting/QuickEditWidget_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..42c528346a5e108a4b371199fbe08655e26ed11f
--- /dev/null
+++ b/scripts/test/MultiPlotting/QuickEditWidget_test.py
@@ -0,0 +1,80 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+
+from MultiPlotting.QuickEdit.quickEdit_presenter import QuickEditPresenter
+from MultiPlotting.QuickEdit.quickEdit_widget import QuickEditWidget
+
+from Muon.GUI.Common import mock_widget
+
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class QuickEditWidgetTest(unittest.TestCase):
+    def setUp(self):
+        self._qapp = mock_widget.mockQapp()
+        self.pres = mock.create_autospec(QuickEditPresenter)
+        self.widget = QuickEditWidget()
+        self.slot = mock.Mock()
+        self.widget.set_mock(self.pres)
+
+    def test_connect_autoscale(self):
+        self.widget.connect_autoscale_changed(self.slot)
+        self.pres.connect_autoscale_changed.assert_called_with(self.slot)
+
+    def test_connect_errors(self):
+        self.widget.connect_errors_changed(self.slot)
+        self.pres.connect_errors_changed.assert_called_with(self.slot)
+
+    def test_connect_x_range(self):
+        self.widget.connect_x_range_changed(self.slot)
+        self.pres.connect_x_range_changed.assert_called_with(self.slot)
+ 
+    def test_connect_y_range(self):
+        self.widget.connect_y_range_changed(self.slot)
+        self.pres.connect_y_range_changed.assert_called_with(self.slot)
+
+    def test_connect_plot_selection(self):
+        self.widget.connect_plot_selection(self.slot)
+        self.pres.connect_plot_selection.assert_called_with(self.slot)
+
+    def test_add_subplot(self):
+        name = "new plot"
+        self.widget.add_subplot(name)
+        self.assertEquals(self.pres.add_subplot.call_count, 1)
+        self.pres.add_subplot.assert_called_with(name)
+
+    def test_get_selection_one(self):
+        name = "one plot"
+        self.pres.widget.current_selection = mock.MagicMock(return_value = name)
+        output = self.widget.get_selection()
+        self.assertEquals([name], output)
+
+    def test_get_selection_all(self):
+        name = "All"
+        self.pres.widget.current_selection = mock.MagicMock(return_value = name)
+        output = self.widget.get_selection()
+        self.assertEquals(self.pres.all.call_count, 1)
+
+    def test_set_plot_x_range(self):
+        self.widget.set_plot_x_range([0,1])
+        self.pres.set_plot_x_range.assert_called_with([0,1])
+        
+    def test_set_plot_y_range(self):
+        self.widget.set_plot_y_range([0,1])
+        self.pres.set_plot_y_range.assert_called_with([0,1])
+           
+    def test_set_errors_TT(self):
+        self.widget.set_errors(True)
+        self.pres.set_errors.assert_called_with(True)
+ 
+if __name__ == "__main__":
+    unittest.main()
diff --git a/scripts/test/MultiPlotting/SubPlotContext_test.py b/scripts/test/MultiPlotting/SubPlotContext_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2c808eefa252f0c191ca61b2d13768ead2ed6c8
--- /dev/null
+++ b/scripts/test/MultiPlotting/SubPlotContext_test.py
@@ -0,0 +1,110 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+
+from MultiPlotting.subplot.subPlot_context import subPlotContext
+from mantid import plots
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class line(object):
+    def __init__(self):
+       self.label = "test"
+
+    def get_label(self):
+        return self.label
+
+    def get_color(self):
+        return "red"
+
+    def get_marker(self):
+        return "star"
+
+    def remove(self):
+        return
+
+def errors():
+    return tuple([line(),[line()],[line()]])
+
+class SubPlotContextTest(unittest.TestCase):
+    def setUp(self):
+        name = "test"
+        self.subplot = mock.MagicMock()
+        self.context = subPlotContext(name,self.subplot)
+ 
+    def test_add_line_no_erros(self):
+        ws = mock.MagicMock()
+        with mock.patch("mantid.plots.plotfunctions.plot") as patch:
+             patch.return_value = tuple([line()])
+             self.context.addLine(ws,3)
+             self.assertEquals(patch.call_count,1)
+             patch.assert_called_with(self.subplot,ws,specNum=3)
+
+    def test_add_line_errors(self): 
+        ws = mock.MagicMock()
+        self.context.change_errors(True)
+        lines = line()
+        with mock.patch("mantid.plots.plotfunctions.plot") as plot:
+             plot.return_value = tuple([lines])
+             with mock.patch("mantid.plots.plotfunctions.errorbar") as patch:
+                  patch.return_value = errors()
+                  self.context.addLine(ws,3)
+                  self.assertEquals(plot.call_count,1)
+                  self.assertEquals(patch.call_count,1)
+                  patch.assert_called_with(self.subplot,ws,specNum=3,label=lines.get_label())
+
+    def test_redraw_errors(self):
+        ws = mock.MagicMock()
+        self.context.change_errors(True)
+        lines = line()
+        # add first line
+        with mock.patch("mantid.plots.plotfunctions.plot") as plot:
+             plot.return_value = tuple([lines])
+             with mock.patch("mantid.plots.plotfunctions.errorbar") as patch:
+                  patch.return_value = errors()
+                  self.context.addLine(ws,3)
+                  self.assertEquals(plot.call_count,1)
+                  self.assertEquals(patch.call_count,1)
+                  # redraw
+                  self.context.redraw(lines.get_label())
+                  self.assertEquals(patch.call_count,2)
+                  patch.assert_called_with(self.subplot,ws,specNum=3,color=lines.get_color(),marker=lines.get_marker(),label=lines.get_label())
+
+
+    def test_redraw_no_errors(self):
+        ws = mock.MagicMock()
+        with mock.patch("mantid.plots.plotfunctions.plot") as patch:
+             lines = line()
+             patch.return_value = tuple([lines])
+             self.context.addLine(ws,3)
+             self.assertEquals(patch.call_count,1)
+             patch.assert_called_with(self.subplot,ws,specNum=3)
+             # redraw
+             self.context.redraw(lines.get_label())
+             self.assertEquals(patch.call_count,2)
+             patch.assert_called_with(self.subplot,ws,specNum=3,color=lines.get_color(),marker=lines.get_marker(),label=lines.get_label())
+    
+
+    def test_change_errors(self): 
+        self.context._lines ={"one":1,"two":2,"three":3}
+        self.context.redraw = mock.MagicMock()
+        self.context.change_errors(True)
+        self.assertEquals(self.context.redraw.call_count,3)
+
+    def test_change_auto(self):
+        self.context._lines ={"one":1,"two":2,"three":3}
+        self.context._subplot.autoscale = mock.MagicMock()
+        self.context.change_auto(True)
+        self.assertEquals(self.context._subplot.autoscale.call_count,3)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/scripts/test/Muon/CMakeLists.txt b/scripts/test/Muon/CMakeLists.txt
index 2b13d550c8fd88fcd592e470b7cf9e22b8a96c4d..0db0aa20a5d73c1a9d0935828e9b0f3a0afac808 100644
--- a/scripts/test/Muon/CMakeLists.txt
+++ b/scripts/test/Muon/CMakeLists.txt
@@ -20,6 +20,10 @@ set ( TEST_PY_FILES
    loading_tab/loadwidget_presenter_test.py
    loading_tab/loadwidget_presenter_multiple_file_test.py
    loading_tab/loadwidget_presenter_failure_test.py
+   grouping_tab/grouping_table_presenter_test.py
+   grouping_tab/pairing_table_presenter_test.py
+   grouping_tab/pairing_table_group_selector_test.py
+   grouping_tab/pairing_table_alpha_test.py
    LoadWidgetModel_test.py
    LoadWidgetPresenter_test.py
    LoadWidgetView_test.py
diff --git a/scripts/test/Muon/grouping_tab/grouping_table_presenter_test.py b/scripts/test/Muon/grouping_tab/grouping_table_presenter_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..e999fb2fa2de06fa0305b8f7cb7ce497da360739
--- /dev/null
+++ b/scripts/test/Muon/grouping_tab/grouping_table_presenter_test.py
@@ -0,0 +1,300 @@
+import unittest
+import sys
+import six
+from PyQt4 import QtGui
+from Muon.GUI.Common.grouping_table_widget.grouping_table_widget_model import GroupingTableModel
+from Muon.GUI.Common.grouping_table_widget.grouping_table_widget_view import GroupingTableView
+from Muon.GUI.Common.grouping_table_widget.grouping_table_widget_presenter import GroupingTablePresenter
+from Muon.GUI.Common.muon_group import MuonGroup
+from Muon.GUI.Common.muon_data_context import MuonDataContext
+from Muon.GUI.Common import mock_widget
+
+if sys.version_info.major > 2:
+    from unittest import mock
+else:
+    import mock
+
+maximum_number_of_groups = 20
+
+
+class GroupingTablePresenterTest(unittest.TestCase):
+
+    def setUp(self):
+        self._qapp = mock_widget.mockQapp()
+        # Store an empty widget to parent all the views, and ensure they are deleted correctly
+        self.obj = QtGui.QWidget()
+
+        self.data = MuonDataContext()
+        self.model = GroupingTableModel(data=self.data)
+        self.view = GroupingTableView(parent=self.obj)
+        self.presenter = GroupingTablePresenter(self.view, self.model)
+
+        self.view.warning_popup = mock.Mock()
+
+    def tearDown(self):
+        self.obj = None
+
+    def assert_model_empty(self):
+        self.assertEqual(len(self.model.group_names), 0)
+        self.assertEqual(len(self.model.groups), 0)
+
+    def assert_view_empty(self):
+        self.assertEqual(self.view.num_rows(), 0)
+
+    def add_three_groups_to_table(self):
+        group1 = MuonGroup(group_name="my_group_0", detector_ids=[1])
+        group2 = MuonGroup(group_name="my_group_1", detector_ids=[2])
+        group3 = MuonGroup(group_name="my_group_2", detector_ids=[3])
+        self.presenter.add_group(group1)
+        self.presenter.add_group(group2)
+        self.presenter.add_group(group3)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : Initialization
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_table_has_three_columns_when_initialized(self):
+        self.assertEqual(self.view.num_cols(), 3)
+
+    def test_that_model_is_initialized_as_empty(self):
+        self.assert_model_empty()
+
+    def test_that_view_is_initialized_as_empty(self):
+        self.assert_view_empty()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : Adding and removing groups
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_add_group_button_adds_group(self):
+        self.presenter.handle_add_group_button_clicked()
+        self.assertEqual(self.view.num_rows(), 1)
+        self.assertEqual(len(self.model.groups), 1)
+
+    def test_that_add_three_groups_function_adds_three_groups(self):
+        self.add_three_groups_to_table()
+        self.assertEqual(self.view.num_rows(), 3)
+        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_0")
+        self.assertEqual(self.view.get_table_item_text(1, 0), "my_group_1")
+        self.assertEqual(self.view.get_table_item_text(2, 0), "my_group_2")
+
+    def test_that_remove_group_button_removes_group(self):
+        self.add_three_groups_to_table()
+        self.presenter.handle_remove_group_button_clicked()
+        self.assertEqual(self.view.num_rows(), 2)
+
+    def test_that_add_group_button_adds_group_to_end_of_table(self):
+        self.add_three_groups_to_table()
+
+        self.presenter.add_group(MuonGroup(group_name="new", detector_ids=[4]))
+
+        self.assertEqual(self.view.get_table_item_text(self.view.num_rows() - 1, 0), "new")
+
+    def test_that_remove_group_button_removes_group_from_end_of_table(self):
+        self.add_three_groups_to_table()
+
+        self.presenter.handle_remove_group_button_clicked()
+
+        self.assertEqual(self.view.get_table_item_text(self.view.num_rows() - 1, 0), "my_group_1")
+
+    def test_that_highlighting_rows_and_clicking_remove_group_removes_the_selected_rows(self):
+        self.add_three_groups_to_table()
+        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 2])
+
+        self.presenter.handle_remove_group_button_clicked()
+
+        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_1")
+        self.assertEqual(self.model.group_names, ["my_group_1"])
+
+    def test_that_cannot_add_more_than_20_rows(self):
+        for i in range(maximum_number_of_groups + 1):
+            self.presenter.handle_add_group_button_clicked()
+
+        self.assertEqual(self.view.num_rows(), maximum_number_of_groups)
+        self.assertEqual(len(self.model.groups), maximum_number_of_groups)
+
+    def test_that_trying_to_add_a_20th_row_gives_warning_message(self):
+        for i in range(maximum_number_of_groups + 1):
+            self.presenter.handle_add_group_button_clicked()
+
+        self.assertEqual(self.view.warning_popup.call_count, 1)
+
+    def test_that_remove_group_when_table_is_empty_does_not_throw(self):
+
+        self.presenter.handle_remove_group_button_clicked()
+        self.assertEqual(self.view.warning_popup.call_count, 0)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Testing context menu
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_context_menu_add_grouping_with_no_rows_selected_adds_group_to_end_of_table(self):
+        self.presenter.handle_add_group_button_clicked()
+
+        self.view.contextMenuEvent(0)
+        self.view.add_group_action.triggered.emit(True)
+
+        self.assertEqual(len(self.model.groups), 2)
+        self.assertEqual(self.view.num_rows(), 2)
+        self.assertEqual(self.view.get_table_item_text(1, 0), "group_2")
+
+    def test_context_menu_add_grouping_with_rows_selected_does_not_add_group(self):
+        self.presenter.handle_add_group_button_clicked()
+        self.view._get_selected_row_indices = mock.Mock(return_value=[0])
+
+        self.view.contextMenuEvent(0)
+
+        self.assertFalse(self.view.add_group_action.isEnabled())
+
+    def test_context_menu_remove_grouping_with_no_rows_selected_removes_last_row(self):
+        self.add_three_groups_to_table()
+
+        self.view.contextMenuEvent(0)
+        self.view.remove_group_action.triggered.emit(True)
+
+        self.assertEqual(len(self.model.groups), 2)
+        self.assertEqual(self.view.num_rows(), 2)
+        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_0")
+        self.assertEqual(self.view.get_table_item_text(1, 0), "my_group_1")
+
+    def test_context_menu_remove_grouping_removes_selected_rows(self):
+        self.add_three_groups_to_table()
+        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 2])
+
+        self.view.contextMenuEvent(0)
+        self.view.remove_group_action.triggered.emit(True)
+
+        self.assertEqual(len(self.model.groups), 1)
+        self.assertEqual(self.view.num_rows(), 1)
+        self.assertEqual(self.view.get_table_item_text(0, 0), "my_group_1")
+
+    def test_context_menu_remove_grouping_disabled_if_no_groups_in_table(self):
+        self.view.contextMenuEvent(0)
+
+        self.assertFalse(self.view.remove_group_action.isEnabled())
+
+    def test_context_menu_add_pair_disabled_unless_two_groups_selected(self):
+        self.add_three_groups_to_table()
+
+        for selected_rows in [[], [0], [0, 1, 2]]:
+            self.view._get_selected_row_indices = mock.Mock(return_value=selected_rows)
+            self.view.contextMenuEvent(0)
+
+            self.assertFalse(self.view.add_pair_action.isEnabled())
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # Group name validation
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_can_change_group_name_to_valid_name_and_update_view_and_model(self):
+        self.add_three_groups_to_table()
+
+        self.view.grouping_table.item(0, 0).setText("new_name")
+
+        self.assertEqual(self.view.get_table_item_text(0, 0), "new_name")
+        self.assertIn("new_name", self.model.group_names)
+
+    def test_that_if_invalid_name_given_warning_message_is_shown(self):
+        self.add_three_groups_to_table()
+        invalid_names = ["", "@", "name!", "+-"]
+
+        for invalid_name in invalid_names:
+            self.view.grouping_table.item(0, 0).setText(invalid_name)
+
+            self.assertEqual(str(self.view.get_table_item_text(0, 0)), "my_group_0")
+            self.assertIn("my_group_0", self.model.group_names)
+
+    def test_that_group_names_with_numbers_and_letters_and_underscores_are_valid(self):
+        self.add_three_groups_to_table()
+
+        valid_names = ["fwd", "fwd_1", "1234", "FWD0001", "_fwd"]
+
+        for valid_name in valid_names:
+            self.view.grouping_table.item(0, 0).setText(valid_name)
+
+            self.assertEqual(str(self.view.get_table_item_text(0, 0)), valid_name)
+            self.assertIn(valid_name, self.model.group_names)
+
+    def test_that_renaming_group_to_duplicate_fails_and_reverts_to_previous_value(self):
+        self.add_three_groups_to_table()
+
+        self.view.grouping_table.setCurrentCell(0, 0)
+        self.view.grouping_table.item(0, 0).setText("my_group_1")
+
+        self.assertEqual(str(self.view.get_table_item_text(0, 0)), "my_group_0")
+        self.assertIn("my_group_0", self.model.group_names)
+
+    def test_that_warning_shown_if_duplicated_group_name_used(self):
+        self.add_three_groups_to_table()
+
+        self.view.grouping_table.setCurrentCell(0, 0)
+        self.view.grouping_table.item(0, 0).setText("my_group_1")
+
+        self.assertEqual(self.view.warning_popup.call_count, 1)
+
+    def test_that_default_group_name_is_group_1(self):
+        self.presenter.handle_add_group_button_clicked()
+
+        self.assertEqual(str(self.view.get_table_item_text(0, 0)), "group_1")
+        self.assertIn("group_1", self.model.group_names)
+
+    def test_that_adding_new_group_creates_incremented_default_name(self):
+        self.presenter.handle_add_group_button_clicked()
+        self.presenter.handle_add_group_button_clicked()
+        self.presenter.handle_add_group_button_clicked()
+
+        self.assertEqual(str(self.view.get_table_item_text(0, 0)), "group_1")
+        self.assertEqual(str(self.view.get_table_item_text(1, 0)), "group_2")
+        self.assertEqual(str(self.view.get_table_item_text(2, 0)), "group_3")
+        six.assertCountEqual(self, self.model.group_names, ["group_1", "group_2", "group_3"])
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # detector ID validation
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_if_not_entering_numbers_into_detector_IDs_the_changes_are_rejected(self):
+        self.add_three_groups_to_table()
+
+        invalid_id_lists = ["fwd", "a", "A", "!", "_", "(1)", "11a22"]
+
+        call_count = 0
+        for invalid_ids in invalid_id_lists:
+            call_count += 1
+
+            self.view.grouping_table.setCurrentCell(0, 1)
+            self.view.grouping_table.item(0, 1).setText(invalid_ids)
+
+            self.assertEqual(self.view.warning_popup.call_count, call_count)
+
+            self.assertEqual(self.view.get_table_item_text(0, 1), "1")
+            self.assertEqual(self.model._data._groups["my_group_0"].detectors, [1])
+
+    def test_that_displayed_values_are_simplified_to_least_verbose_form(self):
+        self.presenter.handle_add_group_button_clicked()
+
+        self.view.grouping_table.setCurrentCell(0, 1)
+        self.view.grouping_table.item(0, 1).setText("20-25,10,5,4,3,2,1")
+
+        self.assertEqual(self.view.get_table_item_text(0, 1), "1-5,10,20-25")
+        self.assertEqual(self.model._data._groups["group_1"].detectors, [1, 2, 3, 4, 5, 10, 20, 21, 22, 23, 24, 25])
+
+    def test_that_if_detector_list_changed_that_number_of_detectors_updates(self):
+        self.presenter.handle_add_group_button_clicked()
+        self.assertEqual(self.view.get_table_item_text(0, 2), "1")
+
+        self.view.grouping_table.setCurrentCell(0, 1)
+        self.view.grouping_table.item(0, 1).setText("1-10")
+
+        self.assertEqual(self.view.get_table_item_text(0, 2), "10")
+
+    def test_that_detector_numbers_cannot_be_edited(self):
+        self.presenter.handle_add_group_button_clicked()
+
+        self.view.grouping_table.setCurrentCell(0, 2)
+        self.view.grouping_table.item(0, 2).setText("25")
+
+        self.assertEqual(self.view.get_table_item_text(0, 2), "1")
+
+
+if __name__ == '__main__':
+    unittest.main(buffer=False, verbosity=2)
diff --git a/scripts/test/Muon/grouping_tab/pairing_table_alpha_test.py b/scripts/test/Muon/grouping_tab/pairing_table_alpha_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a704261009f3b655cf16d5abaaef7da84560295
--- /dev/null
+++ b/scripts/test/Muon/grouping_tab/pairing_table_alpha_test.py
@@ -0,0 +1,155 @@
+import unittest
+import sys
+
+if sys.version_info.major == 3:
+    from unittest import mock
+else:
+    import mock
+
+from PyQt4 import QtGui
+
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_model import PairingTableModel
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_view import PairingTableView
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_presenter import PairingTablePresenter
+
+from Muon.GUI.Common.muon_group import MuonGroup
+from Muon.GUI.Common.muon_pair import MuonPair
+from Muon.GUI.Common.muon_data_context import MuonDataContext
+from Muon.GUI.Common import mock_widget
+
+
+class AlphaTest(unittest.TestCase):
+
+    def setUp(self):
+        self._qapp = mock_widget.mockQapp()
+        # Store an empty widget to parent all the views, and ensure they are deleted correctly
+        self.obj = QtGui.QWidget()
+
+        self.data = MuonDataContext()
+
+        self.model = PairingTableModel(data=self.data)
+        self.view = PairingTableView(parent=self.obj)
+        self.presenter = PairingTablePresenter(self.view, self.model)
+
+        self.view.warning_popup = mock.Mock()
+
+    def tearDown(self):
+        self.obj = None
+
+    def assert_model_empty(self):
+        self.assertEqual(len(self.model.pair_names), 0)
+        self.assertEqual(len(self.model.pairs), 0)
+
+    def assert_view_empty(self):
+        self.assertEqual(self.view.num_rows(), 0)
+
+    def add_three_groups_to_model(self):
+        group1 = MuonGroup(group_name="my_group_0", detector_ids=[1])
+        group2 = MuonGroup(group_name="my_group_1", detector_ids=[2])
+        group3 = MuonGroup(group_name="my_group_2", detector_ids=[3])
+        self.data.add_group(group1)
+        self.data.add_group(group2)
+        self.data.add_group(group3)
+
+    def add_two_pairs_to_table(self):
+        pair1 = MuonPair(pair_name="my_pair_0", forward_group_name="my_group_0", backward_group_name="my_group_1", alpha=1.0)
+        pair2 = MuonPair(pair_name="my_pair_1", forward_group_name="my_group_1", backward_group_name="my_group_2", alpha=1.0)
+        self.presenter.add_pair(pair1)
+        self.presenter.add_pair(pair2)
+
+    def get_group_1_selector(self, row):
+        return self.view.pairing_table.cellWidget(row, 1)
+
+    def get_group_2_selector(self, row):
+        return self.view.pairing_table.cellWidget(row, 2)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : test the functionality around alpha.
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_alpha_defaults_to_1(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(self.view.get_table_item_text(0, 3), "1.0")
+
+    def test_that_table_reverts_to_previous_value_when_adding_values_which_arent_numbers_to_alpha_column(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        non_numeric_alphas = ["", "a", "long", "!", "_", "1+2"]
+
+        default_value = self.view.get_table_item_text(0, 3)
+        for invalid_alpha in non_numeric_alphas:
+            self.view.pairing_table.setCurrentCell(0, 3)
+            self.view.pairing_table.item(0, 3).setText(invalid_alpha)
+
+            self.assertEqual(self.view.get_table_item_text(0, 3), default_value)
+
+    def test_that_warning_displayed_when_adding_invalid_alpha_values(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        non_numeric_alphas = ["", "a", "long", "!", "_", "1+2"]
+
+        call_count = 0
+        for invalid_alpha in non_numeric_alphas:
+            call_count += 1
+            self.view.pairing_table.setCurrentCell(0, 3)
+            self.view.pairing_table.item(0, 3).setText(invalid_alpha)
+
+            self.assertEqual(self.view.warning_popup.call_count, call_count)
+
+    def test_that_alpha_values_stored_to_three_decimal_places(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.view.pairing_table.setCurrentCell(0, 3)
+        # test that rounds correctly
+        self.view.pairing_table.item(0, 3).setText("1.1239")
+
+        self.assertEqual(self.view.get_table_item_text(0, 3), "1.124")
+
+    def test_that_alpha_values_stored_to_three_decimal_places_when_rounding_down(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.view.pairing_table.setCurrentCell(0, 3)
+        # test that rounds correctly
+        self.view.pairing_table.item(0, 3).setText("1.1244")
+
+        self.assertEqual(self.view.get_table_item_text(0, 3), "1.124")
+
+    def test_that_valid_alpha_values_are_added_correctly(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        valid_inputs = ["1.0", "12", ".123", "0.00001", "0.0005"]
+        expected_output = ["1.0", "12.0", "0.123", "1e-05", "0.001"]
+
+        for valid_alpha, expected_alpha in iter(zip(valid_inputs, expected_output)):
+            self.view.pairing_table.setCurrentCell(0, 3)
+            self.view.pairing_table.item(0, 3).setText(valid_alpha)
+
+            self.assertEqual(self.view.get_table_item_text(0, 3), expected_alpha)
+
+    def test_that_negative_alpha_is_not_allowed(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.view.pairing_table.setCurrentCell(0, 3)
+        default_value = self.view.get_table_item_text(0, 3)
+        self.view.pairing_table.item(0, 3).setText("-1.0")
+
+        self.assertEqual(self.view.get_table_item_text(0, 3), default_value)
+        self.assertEqual(self.view.warning_popup.call_count, 1)
+
+    def test_that_clicking_guess_alpha_triggers_correct_slot_with_correct_row_supplied(self):
+        # Guess alpha functionality must be implemented by parent widgets. So we just check that the
+        # design for implementing this works (via an Observable in the presenter)
+        self.presenter.handle_add_pair_button_clicked()
+        self.presenter.handle_add_pair_button_clicked()
+        self.presenter.guessAlphaNotifier.notify_subscribers = mock.Mock()
+
+        self.view.pairing_table.cellWidget(1, 4).clicked.emit(True)
+
+        self.assertEqual(self.presenter.guessAlphaNotifier.notify_subscribers.call_count, 1)
+        self.assertEqual(self.presenter.guessAlphaNotifier.notify_subscribers.call_args_list[0][0][0],
+                         ["pair_2", "", ""])
+
+
+if __name__ == '__main__':
+    unittest.main(buffer=False, verbosity=2)
diff --git a/scripts/test/Muon/grouping_tab/pairing_table_group_selector_test.py b/scripts/test/Muon/grouping_tab/pairing_table_group_selector_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..99df885e022408465ad17947a004bec31b68a606
--- /dev/null
+++ b/scripts/test/Muon/grouping_tab/pairing_table_group_selector_test.py
@@ -0,0 +1,193 @@
+import unittest
+import sys
+from PyQt4 import QtGui
+
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_model import PairingTableModel
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_view import PairingTableView
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_presenter import PairingTablePresenter
+
+from Muon.GUI.Common.muon_group import MuonGroup
+from Muon.GUI.Common.muon_pair import MuonPair
+from Muon.GUI.Common.muon_data_context import MuonDataContext
+from Muon.GUI.Common import mock_widget
+
+if sys.version_info.major > 2:
+    from unittest import mock
+else:
+    import mock
+
+
+class GroupSelectorTest(unittest.TestCase):
+
+    def setUp(self):
+        self._qapp = mock_widget.mockQapp()
+        # Store an empty widget to parent all the views, and ensure they are deleted correctly
+        self.obj = QtGui.QWidget()
+
+        self.data = MuonDataContext()
+
+        self.model = PairingTableModel(data=self.data)
+        self.view = PairingTableView(parent=self.obj)
+        self.presenter = PairingTablePresenter(self.view, self.model)
+
+        self.view.warning_popup = mock.Mock()
+
+    def tearDown(self):
+        self.obj = None
+
+    def assert_model_empty(self):
+        self.assertEqual(len(self.model.pair_names), 0)
+        self.assertEqual(len(self.model.pairs), 0)
+
+    def assert_view_empty(self):
+        self.assertEqual(self.view.num_rows(), 0)
+
+    def add_three_groups_to_model(self):
+        group1 = MuonGroup(group_name="my_group_0", detector_ids=[1])
+        group2 = MuonGroup(group_name="my_group_1", detector_ids=[2])
+        group3 = MuonGroup(group_name="my_group_2", detector_ids=[3])
+        self.data.add_group(group1)
+        self.data.add_group(group2)
+        self.data.add_group(group3)
+
+    def add_two_pairs_to_table(self):
+        pair1 = MuonPair(pair_name="my_pair_0", forward_group_name="my_group_0", backward_group_name="my_group_1", alpha=1.0)
+        pair2 = MuonPair(pair_name="my_pair_1", forward_group_name="my_group_1", backward_group_name="my_group_2", alpha=1.0)
+        self.presenter.add_pair(pair1)
+        self.presenter.add_pair(pair2)
+
+    def get_group_1_selector_from_pair(self, row):
+        return self.view.pairing_table.cellWidget(row, 1)
+
+    def get_group_2_selector_from_pair(self, row):
+        return self.view.pairing_table.cellWidget(row, 2)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : test the functionality around the combo boxes which allow the user to select the two groups that
+    #         together make the muon pair.
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_adding_pair_when_no_groups_exist_leaves_combo_boxes_empty(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 0)
+        self.assertEqual(self.get_group_2_selector_from_pair(0).count(), 0)
+        self.assertEqual(self.get_group_1_selector_from_pair(0).currentText(), "")
+        self.assertEqual(self.get_group_2_selector_from_pair(0).currentText(), "")
+
+    def test_that_adding_pair_then_adding_group_puts_group_in_combos(self):
+        self.presenter.handle_add_pair_button_clicked()
+        self.data.add_group(MuonGroup(group_name="my_group_0", detector_ids=[1]))
+        self.presenter.update_view_from_model()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 1)
+        self.assertEqual(self.get_group_2_selector_from_pair(0).count(), 1)
+
+    def test_that_adding_pair_then_adding_group_sets_combo_to_added_group(self):
+        self.presenter.handle_add_pair_button_clicked()
+        self.data.add_group(MuonGroup(group_name="my_group_0", detector_ids=[1]))
+        self.presenter.update_view_from_model()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).currentText(), "my_group_0")
+        self.assertEqual(self.get_group_2_selector_from_pair(0).currentText(), "my_group_0")
+
+    def test_that_adding_two_groups_and_then_pair_sets_combo_to_added_groups(self):
+        self.data.add_group(MuonGroup(group_name="my_group_0", detector_ids=[1]))
+        self.data.add_group(MuonGroup(group_name="my_group_1", detector_ids=[2]))
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).currentText(), "my_group_0")
+        self.assertEqual(self.get_group_2_selector_from_pair(0).currentText(), "my_group_1")
+
+    def test_that_added_groups_appear_in_group_combo_boxes_in_new_pairs(self):
+        self.add_three_groups_to_model()
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 3)
+        self.assertNotEqual(self.get_group_1_selector_from_pair(0).findText("my_group_0"), -1)
+        self.assertNotEqual(self.get_group_1_selector_from_pair(0).findText("my_group_1"), -1)
+        self.assertNotEqual(self.get_group_1_selector_from_pair(0).findText("my_group_2"), -1)
+
+    def test_that_get_index_of_text_returns_correct_index_if_text_exists(self):
+        self.add_three_groups_to_model()
+        self.presenter.handle_add_pair_button_clicked()
+
+        index = self.view.get_index_of_text(self.get_group_1_selector_from_pair(0), 'my_group_1')
+
+        self.assertEqual(index, 1)
+
+    def test_that_get_index_of_text_returns_0_if_text_does_not_exists(self):
+        self.add_three_groups_to_model()
+        self.presenter.handle_add_pair_button_clicked()
+
+        index = self.view.get_index_of_text(self.get_group_1_selector_from_pair(0), 'random string')
+
+        self.assertEqual(index, 0)
+
+    def test_that_added_groups_appear_in_group_combo_boxes_in_existing_pairs_if_update_called(self):
+        self.presenter.handle_add_pair_button_clicked()
+        self.add_three_groups_to_model()
+        # the following method must be called
+        self.presenter.update_view_from_model()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 3)
+        self.assertNotEqual(self.get_group_1_selector_from_pair(0).findText("my_group_0"), -1)
+        self.assertNotEqual(self.get_group_1_selector_from_pair(0).findText("my_group_1"), -1)
+        self.assertNotEqual(self.get_group_1_selector_from_pair(0).findText("my_group_2"), -1)
+
+    def test_that_changing_group_selection_triggers_cell_changed_method_in_view(self):
+        self.add_three_groups_to_model()
+        self.presenter.handle_add_pair_button_clicked()
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.view.on_cell_changed = mock.Mock()
+        self.get_group_1_selector_from_pair(0).setCurrentIndex(1)
+
+        self.assertEqual(self.view.on_cell_changed.call_count, 1)
+        self.assertEqual(self.view.on_cell_changed.call_args_list[0][0], (0, 1))
+
+    def test_that_removing_groups_and_then_calling_update_removes_groups_from_selections(self):
+        self.add_three_groups_to_model()
+        self.presenter.handle_add_pair_button_clicked()
+        del self.data.groups["my_group_1"]
+        del self.data.groups["my_group_2"]
+        self.presenter.update_view_from_model()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 1)
+        self.assertEqual(self.get_group_2_selector_from_pair(0).count(), 1)
+        self.assertNotEqual(self.get_group_1_selector_from_pair(0).findText("my_group_0"), -1)
+        self.assertNotEqual(self.get_group_2_selector_from_pair(0).findText("my_group_0"), -1)
+
+    def test_adding_new_group_does_not_change_pair_slection(self):
+        self.add_three_groups_to_model()
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 3)
+        self.assertEqual(self.get_group_2_selector_from_pair(0).count(), 3)
+        self.assertEqual(self.get_group_1_selector_from_pair(0).currentText(), 'my_group_0')
+        self.assertEqual(self.get_group_2_selector_from_pair(0).currentText(), 'my_group_1')
+
+        group4 = MuonGroup(group_name="my_group_4", detector_ids=[4])
+        self.data.add_group(group4)
+        self.presenter.update_view_from_model()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 4)
+        self.assertEqual(self.get_group_2_selector_from_pair(0).count(), 4)
+        self.assertEqual(self.get_group_1_selector_from_pair(0).currentText(), 'my_group_0')
+        self.assertEqual(self.get_group_2_selector_from_pair(0).currentText(), 'my_group_1')
+
+    def test_removing_group_used_in_pair_handled_gracefully(self):
+        self.add_three_groups_to_model()
+        self.presenter.handle_add_pair_button_clicked()
+
+        del self.data.groups["my_group_0"]
+        self.presenter.update_view_from_model()
+
+        self.assertEqual(self.get_group_1_selector_from_pair(0).count(), 2)
+        self.assertEqual(self.get_group_2_selector_from_pair(0).count(), 2)
+        self.assertEqual(self.get_group_1_selector_from_pair(0).currentText(), 'my_group_1')
+        self.assertEqual(self.get_group_2_selector_from_pair(0).currentText(), 'my_group_1')
+
+
+if __name__ == '__main__':
+    unittest.main(buffer=False, verbosity=2)
diff --git a/scripts/test/Muon/grouping_tab/pairing_table_presenter_test.py b/scripts/test/Muon/grouping_tab/pairing_table_presenter_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2ce198d2f1fbefff3c6ffff6c882f0343c51e22
--- /dev/null
+++ b/scripts/test/Muon/grouping_tab/pairing_table_presenter_test.py
@@ -0,0 +1,267 @@
+import unittest
+import sys
+import six
+
+if sys.version_info.major == 3:
+    from unittest import mock
+else:
+    import mock
+
+from PyQt4 import QtGui
+
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_model import PairingTableModel
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_view import PairingTableView
+from Muon.GUI.Common.pairing_table_widget.pairing_table_widget_presenter import PairingTablePresenter
+
+from Muon.GUI.Common.muon_group import MuonGroup
+from Muon.GUI.Common.muon_pair import MuonPair
+from Muon.GUI.Common.muon_data_context import MuonDataContext
+from Muon.GUI.Common import mock_widget
+
+
+class PairingTablePresenterTest(unittest.TestCase):
+
+    def setUp(self):
+        self._qapp = mock_widget.mockQapp()
+        # Store an empty widget to parent all the views, and ensure they are deleted correctly
+        self.obj = QtGui.QWidget()
+
+        self.data = MuonDataContext()
+        self.add_three_groups_to_model()
+
+        self.model = PairingTableModel(data=self.data)
+        self.view = PairingTableView(parent=self.obj)
+        self.presenter = PairingTablePresenter(self.view, self.model)
+
+        self.view.warning_popup = mock.Mock()
+
+    def tearDown(self):
+        self.obj = None
+
+    def assert_model_empty(self):
+        self.assertEqual(len(self.model.pair_names), 0)
+        self.assertEqual(len(self.model.pairs), 0)
+
+    def assert_view_empty(self):
+        self.assertEqual(self.view.num_rows(), 0)
+
+    def add_three_groups_to_model(self):
+        group1 = MuonGroup(group_name="my_group_0", detector_ids=[1])
+        group2 = MuonGroup(group_name="my_group_1", detector_ids=[2])
+        group3 = MuonGroup(group_name="my_group_2", detector_ids=[3])
+        self.data.add_group(group1)
+        self.data.add_group(group2)
+        self.data.add_group(group3)
+
+    def add_two_pairs_to_table(self):
+        pair1 = MuonPair(pair_name="my_pair_0", forward_group_name="my_group_0", backward_group_name="my_group_1", alpha=1.0)
+        pair2 = MuonPair(pair_name="my_pair_1", forward_group_name="my_group_1", backward_group_name="my_group_2", alpha=1.0)
+        self.presenter.add_pair(pair1)
+        self.presenter.add_pair(pair2)
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : Initialization
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_table_has_five_columns_when_initialized(self):
+        # these are : pair name, group 1, group 2, alpha, guess alpha
+        self.assertEqual(self.view.num_cols(), 5)
+
+    def test_that_model_is_initialized_as_empty(self):
+        self.assert_model_empty()
+
+    def test_that_view_is_initialized_as_empty(self):
+        self.assert_view_empty()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : Adding and removing groups
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_add_pair_button_adds_pair(self):
+        self.presenter.handle_add_pair_button_clicked()
+        self.assertEqual(self.view.num_rows(), 1)
+        self.assertEqual(len(self.model.pairs), 1)
+
+    def test_that_remove_pair_button_removes_group(self):
+        self.add_two_pairs_to_table()
+        self.presenter.handle_remove_pair_button_clicked()
+        self.assertEqual(self.view.num_rows(), 1)
+
+    def test_that_add_pair_button_adds_pair_to_end_of_table(self):
+        self.add_two_pairs_to_table()
+
+        self.presenter.add_pair(MuonPair(pair_name="new"))
+
+        self.assertEqual(self.view.get_table_item_text(self.view.num_rows() - 1, 0), "new")
+
+    def test_that_remove_pair_button_removes_pair_from_end_of_table(self):
+        self.add_two_pairs_to_table()
+
+        self.presenter.handle_remove_pair_button_clicked()
+
+        self.assertEqual(self.view.get_table_item_text(self.view.num_rows() - 1, 0), "my_pair_0")
+
+    def test_that_highlighting_rows_and_clicking_remove_pair_removes_the_selected_rows(self):
+        self.add_two_pairs_to_table()
+        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 1])
+
+        self.presenter.handle_remove_pair_button_clicked()
+
+        self.assert_model_empty()
+        self.assert_view_empty()
+
+    def test_that_cannot_add_more_than_20_rows(self):
+        for i in range(21):
+            self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(self.view.num_rows(), 20)
+        self.assertEqual(len(self.model.pairs), 20)
+
+    def test_that_trying_to_add_a_20th_row_gives_warning_message(self):
+        for i in range(21):
+            self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(self.view.warning_popup.call_count, 1)
+
+    def test_that_remove_group_when_table_is_empty_does_not_throw(self):
+        for i in range(3):
+            self.presenter.handle_remove_pair_button_clicked()
+        self.view.warning_popup.assert_not_called()
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : Context menu has "add pair" and "remove pair" functionality
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_context_menu_add_pairing_with_no_rows_selected_adds_pair_to_end_of_table(self):
+        self.view.contextMenuEvent(0)
+        self.view.add_pair_action.triggered.emit(True)
+
+        self.assertEqual(len(self.model.pairs), 1)
+        self.assertEqual(self.view.num_rows(), 1)
+        self.assertEqual(self.view.get_table_item_text(0, 0), "pair_1")
+
+    def test_context_menu_add_pairing_with_rows_selected_does_not_add_pair(self):
+        self.add_two_pairs_to_table()
+        self.view._get_selected_row_indices = mock.Mock(return_value=[0])
+
+        self.view.contextMenuEvent(0)
+
+        self.assertFalse(self.view.add_pair_action.isEnabled())
+
+    def test_context_menu_remove_pairing_with_no_rows_selected_removes_last_row(self):
+        for i in range(3):
+            # names : pair_1, pair_2, pair_3
+            self.presenter.handle_add_pair_button_clicked()
+
+        self.view.contextMenuEvent(0)
+        self.view.remove_pair_action.triggered.emit(True)
+
+        self.assertEqual(len(self.model.pairs), 2)
+        self.assertEqual(self.view.num_rows(), 2)
+        self.assertEqual(self.view.get_table_item_text(0, 0), "pair_1")
+        self.assertEqual(self.view.get_table_item_text(1, 0), "pair_2")
+
+    def test_context_menu_remove_pairing_removes_selected_rows(self):
+        for i in range(3):
+            # names : pair_0, pair_1, pair_2
+            self.presenter.handle_add_pair_button_clicked()
+        self.view._get_selected_row_indices = mock.Mock(return_value=[0, 2])
+
+        self.view.contextMenuEvent(0)
+        self.view.remove_pair_action.triggered.emit(True)
+
+        self.assertEqual(len(self.model.pairs), 1)
+        self.assertEqual(self.view.num_rows(), 1)
+        self.assertEqual(self.view.get_table_item_text(0, 0), "pair_2")
+
+    def test_context_menu_remove_pairing_disabled_if_no_pairs_in_table(self):
+        self.view.contextMenuEvent(0)
+
+        self.assertFalse(self.view.remove_pair_action.isEnabled())
+
+    # ------------------------------------------------------------------------------------------------------------------
+    # TESTS : Pair name validation
+    # ------------------------------------------------------------------------------------------------------------------
+
+    def test_that_can_change_pair_name_to_valid_name_and_update_view_and_model(self):
+        self.add_two_pairs_to_table()
+        self.view.pairing_table.setCurrentCell(0, 0)
+        self.view.pairing_table.item(0, 0).setText("new_name")
+
+        self.assertEqual(self.view.get_table_item_text(0, 0), "new_name")
+        self.assertIn("new_name", self.model.pair_names)
+
+    def test_that_if_invalid_name_given_warning_message_is_shown(self):
+        self.add_two_pairs_to_table()
+
+        invalid_names = ["", "@", "name!", "+-"]
+        call_count = self.view.warning_popup.call_count
+        for invalid_name in invalid_names:
+            call_count += 1
+            self.view.pairing_table.setCurrentCell(0, 0)
+            self.view.pairing_table.item(0, 0).setText(invalid_name)
+
+            self.assertEqual(self.view.warning_popup.call_count, call_count)
+
+    def test_that_if_invalid_name_given_name_reverts_to_its_previous_value(self):
+        self.add_two_pairs_to_table()
+
+        invalid_names = ["", "@", "name!", "+-"]
+
+        for invalid_name in invalid_names:
+            print(self.view.get_table_contents())
+            self.view.pairing_table.setCurrentCell(0, 0)
+            self.view.pairing_table.item(0, 0).setText(invalid_name)
+            print(self.view.get_table_contents())
+
+            self.assertEqual(str(self.view.get_table_item_text(0, 0)), "my_pair_0")
+            self.assertIn("my_pair_0", self.model.pair_names)
+
+    def test_that_pair_names_with_numbers_and_letters_and_underscores_are_valid(self):
+        self.add_two_pairs_to_table()
+
+        valid_names = ["fwd", "fwd_1", "1234", "FWD0001", "_fwd"]
+
+        for valid_name in valid_names:
+            self.view.pairing_table.setCurrentCell(0, 0)
+            self.view.pairing_table.item(0, 0).setText(valid_name)
+
+            self.assertEqual(str(self.view.get_table_item_text(0, 0)), valid_name)
+            self.assertIn(valid_name, self.model.pair_names)
+
+    def test_that_renaming_group_to_duplicate_fails_and_reverts_to_previous_value(self):
+        self.add_two_pairs_to_table()
+
+        self.view.pairing_table.setCurrentCell(0, 0)
+        self.view.pairing_table.item(0, 0).setText("my_pair_1")
+
+        self.assertEqual(str(self.view.get_table_item_text(0, 0)), "my_pair_0")
+        self.assertIn("my_pair_0", self.model.pair_names)
+
+    def test_that_warning_shown_if_duplicated_pair_name_used(self):
+        self.add_two_pairs_to_table()
+
+        self.view.pairing_table.setCurrentCell(0, 0)
+        self.view.pairing_table.item(0, 0).setText("my_group_1")
+
+        self.assertEqual(self.view.warning_popup.call_count, 1)
+
+    def test_that_default_pair_name_is_pair_0(self):
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(str(self.view.get_table_item_text(0, 0)), "pair_1")
+        self.assertIn("pair_1", self.model.pair_names)
+
+    def test_that_adding_new_pair_creates_incremented_default_name(self):
+        self.presenter.handle_add_pair_button_clicked()
+        self.presenter.handle_add_pair_button_clicked()
+        self.presenter.handle_add_pair_button_clicked()
+
+        self.assertEqual(str(self.view.get_table_item_text(0, 0)), "pair_1")
+        self.assertEqual(str(self.view.get_table_item_text(1, 0)), "pair_2")
+        self.assertEqual(str(self.view.get_table_item_text(2, 0)), "pair_3")
+        six.assertCountEqual(self, self.model.pair_names, ["pair_1", "pair_2", "pair_3"])
+
+
+if __name__ == '__main__':
+    unittest.main(buffer=False, verbosity=2)
diff --git a/scripts/test/SANS/common/enums_test.py b/scripts/test/SANS/common/enums_test.py
index b18295cc77dacd43cb60a21e13f7c41a6599bdac..57ab5c1a7763a59773b26a37919ecbe1afeda188 100644
--- a/scripts/test/SANS/common/enums_test.py
+++ b/scripts/test/SANS/common/enums_test.py
@@ -17,6 +17,12 @@ class DummyClass(object):
     pass
 
 
+@string_convertible
+@serializable_enum("TypeA", "TypeB", "TypeC")
+class IncorrectClass(object):
+    pass
+
+
 class SANSFileInformationTest(unittest.TestCase):
     def test_that_can_create_enum_value_and_is_sub_class_of_base_type(self):
         type_a = DummyClass.TypeA
@@ -35,5 +41,17 @@ class SANSFileInformationTest(unittest.TestCase):
     def test_that_raises_run_time_error_if_string_is_not_known(self):
         self.assertRaises(RuntimeError, DummyClass.from_string, "TypeD")
 
+    def test_that_has_member_handles_strings(self):
+        self.assertTrue(DummyClass.has_member("TypeA"))
+        self.assertFalse(DummyClass.has_member("TypeD"))
+
+    def test_that_has_member_handles_enums(self):
+        a_variable = DummyClass.TypeA
+        incorrect_variable = IncorrectClass.TypeA
+
+        self.assertTrue(DummyClass.has_member(a_variable))
+        self.assertFalse(DummyClass.has_member(incorrect_variable))
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/scripts/test/SANS/gui_logic/gui_common_test.py b/scripts/test/SANS/gui_logic/gui_common_test.py
index c4ae5de868315a6230444e080bbc24afb74a6842..cad876b4458639ea907ab322316e982366cd472f 100644
--- a/scripts/test/SANS/gui_logic/gui_common_test.py
+++ b/scripts/test/SANS/gui_logic/gui_common_test.py
@@ -9,10 +9,15 @@ from __future__ import (absolute_import, division, print_function)
 import unittest
 
 import mantid
+from mantid.kernel import config
 
 from sans.gui_logic.gui_common import (get_reduction_mode_strings_for_gui, get_reduction_selection,
-                                       get_string_for_gui_from_reduction_mode)
+                                       get_string_for_gui_from_reduction_mode,
+                                       get_batch_file_dir_from_path,
+                                       add_dir_to_datasearch,
+                                       remove_dir_from_datasearch)
 from sans.common.enums import (SANSInstrument, ISISReductionMode)
+import os
 
 
 class GuiCommonTest(unittest.TestCase):
@@ -67,6 +72,58 @@ class GuiCommonTest(unittest.TestCase):
         self.do_test_reduction_mode_string(SANSInstrument.NoInstrument, ISISReductionMode.LAB, "LAB")
         self.do_test_reduction_mode_string(SANSInstrument.NoInstrument, ISISReductionMode.HAB, "HAB")
 
+    def test_that_batch_file_dir_returns_none_if_no_forwardslash(self):
+        a_path = "test_batch_file_path.csv"
+        result = get_batch_file_dir_from_path(a_path)
+        self.assertEqual("", result, "Expected empty string. Returned value was {}".format(result))
+
+    def test_correct_batch_file_dir_returned(self):
+        a_path = "A/Test/Path/batch_file.csv"
+        result = get_batch_file_dir_from_path(a_path)
+
+        expected_result = "A/Test/Path/"
+        self.assertEqual(result, expected_result)
+
+    def test_datasearch_directories_updated(self):
+        current_dirs = "A/Path/"
+        batch_file = "A/Path/To/Batch/File/batch_file.csv"
+        _, result = add_dir_to_datasearch(batch_file, current_dirs)
+
+        expected_result = "A/Path/;A/Path/To/Batch/File/"
+        self.assertEqual(expected_result, result)
+
+    def test_empty_string_not_added_to_datasearch_directories(self):
+        current_dirs = "A/Path/"
+        batch_file = "batch_file.csv"
+        _, result = add_dir_to_datasearch(batch_file, current_dirs)
+
+        expected_result = "A/Path/"
+        self.assertEqual(expected_result, result)
+
+    def test_existing_directory_not_added_to_datasearch_directories(self):
+        current_dirs = "A/Path/;A/Path/Already/Added/"
+        batch_file = "A/Path/Already/Added/batch_file.csv"
+        _, result = add_dir_to_datasearch(batch_file, current_dirs)
+
+        expected_result = "A/Path/;A/Path/Already/Added/"
+        self.assertEqual(expected_result, result)
+
+    def test_directories_unchanged_when_removing_empty_string(self):
+        current_dirs = "A/Path/;Another/Path/"
+        file_to_remove = ""
+        result = remove_dir_from_datasearch(file_to_remove, current_dirs)
+
+        expected_result = "A/Path/;Another/Path/"
+        self.assertEqual(result, expected_result)
+
+    def test_correct_directory_removed(self):
+        current_dirs = "A/Path/;Another/Path/;A/Final/Path/"
+        file_to_remove = "Another/Path/"
+        result = remove_dir_from_datasearch(file_to_remove, current_dirs)
+
+        expected_result = "A/Path/;A/Final/Path/"
+        self.assertEqual(expected_result, result)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/scripts/test/SANS/gui_logic/run_tab_presenter_test.py b/scripts/test/SANS/gui_logic/run_tab_presenter_test.py
index 570d3430a3e6c54d38ce9233cd2dcb34c4361c5a..0be1749b60aeb1c9708d99ab699bc72b5cd3e960 100644
--- a/scripts/test/SANS/gui_logic/run_tab_presenter_test.py
+++ b/scripts/test/SANS/gui_logic/run_tab_presenter_test.py
@@ -267,6 +267,19 @@ class RunTabPresenterTest(unittest.TestCase):
         self._remove_files(user_file_path=user_file_path)
         self.os_patcher.start()
 
+    def test_batch_file_dir_not_added_to_config_if_batch_file_load_fails(self):
+        presenter = RunTabPresenter(SANSFacility.ISIS)
+        user_file_path = create_user_file(sample_user_file)
+        view, settings_diagnostic_tab, masking_table = create_mock_view(user_file_path, "A/Path/batch_file.csv")
+        presenter.set_view(view)
+
+        presenter.on_batch_file_load()
+        config_dirs = config["datasearch.directories"]
+        result = "A/Path/" in config_dirs
+
+        self.assertFalse(result, "We do not expect A/Path/ to be added to config, "
+                                 "datasearch.directories is now {}".format(config_dirs))
+
     def test_that_gets_states_from_view(self):
         # Arrange
         batch_file_path, user_file_path, presenter, _ = self._get_files_and_mock_presenter(BATCH_FILE_TEST_CONTENT_2)
@@ -872,6 +885,54 @@ class RunTabPresenterTest(unittest.TestCase):
             "Expected reset_row_state to have been called 7 times. Called {} times.".format(
                 presenter._table_model.reset_row_state.call_count))
 
+    def test_that_table_not_exported_if_table_is_empty(self):
+        presenter = RunTabPresenter(SANSFacility.ISIS)
+        view = mock.MagicMock()
+        presenter.set_view(view)
+
+        presenter._export_table = mock.MagicMock()
+
+        presenter.on_export_table_clicked()
+        self.assertEqual(presenter._export_table.call_count, 0,
+                         "_export table should not have been called."
+                         " It was called {} times.".format(presenter._export_table.call_count))
+
+    def test_row_created_for_batch_file_correctly(self):
+        presenter = RunTabPresenter(SANSFacility.ISIS)
+        view = mock.MagicMock()
+        presenter.set_view(view)
+
+        test_row = ["SANS2D00022025", "another_file", "SANS2D00022052", "SANS2D00022022",
+                    "", "", "", "a_user_file.txt"]
+
+        expected_list = ["sample_sans", "SANS2D00022025", "output_as", "another_file",
+                         "sample_trans", "SANS2D00022052", "sample_direct_beam", "SANS2D00022022",
+                         "can_sans", "", "can_trans", "", "can_direct_beam", "",
+                         "user_file", "a_user_file.txt"]
+
+        actual_list = presenter._create_batch_entry_from_row(test_row)
+
+        self.assertEqual(actual_list, expected_list)
+
+    def test_buttons_enabled_after_export_table_fails(self):
+        presenter = RunTabPresenter(SANSFacility.ISIS)
+        view = mock.MagicMock()
+        presenter.set_view(view)
+
+        presenter.get_row_indices = mock.MagicMock(return_value=[0, 1, 2])
+        presenter._view.enable_buttons = mock.MagicMock()
+        # Mock error throw on disable buttons so export fails
+        presenter._view.disable_buttons = mock.MagicMock(side_effect=RuntimeError("A test exception"))
+        try:
+            presenter.on_export_table_clicked()
+        except Exception as e:
+            self.assertTrue(False, "Exceptions should have been caught in the method. "
+                                   "Exception thrown is {}".format(str(e)))
+        else:
+            self.assertEqual(presenter._view.enable_buttons.call_count, 1,
+                             "Expected enable buttons to be called once, "
+                             "was called {} times.".format(presenter._view.enable_buttons.call_count))
+
     @staticmethod
     def _clear_property_manager_data_service():
         for element in PropertyManagerDataService.getObjectNames():
diff --git a/scripts/test/SANS/gui_logic/table_model_test.py b/scripts/test/SANS/gui_logic/table_model_test.py
index 32fac32fb73479e3f3d2a22571d6d505b0ea78a8..8540aa177a582029f68101a7ce59c61e7fb55d49 100644
--- a/scripts/test/SANS/gui_logic/table_model_test.py
+++ b/scripts/test/SANS/gui_logic/table_model_test.py
@@ -84,7 +84,7 @@ class TableModelTest(unittest.TestCase):
 
     def test_that_parse_string_returns_correctly(self):
         string_to_parse = 'EventSlices=1-6,5-9,4:5:89 , WavelengthMax=78 , WavelengthMin=9'
-        expected_dict = {'EventSlices':'1-6,5-9,4:5:89', 'WavelengthMax':'78', 'WavelengthMin':'9'}
+        expected_dict = {'EventSlices': '1-6,5-9,4:5:89', 'WavelengthMax': '78', 'WavelengthMin': '9'}
 
         parsed_dict = OptionsColumnModel._parse_string(string_to_parse)
 
@@ -138,7 +138,7 @@ class TableModelTest(unittest.TestCase):
     def test_that_OptionsColumnModel_get_permissable_properties_returns_correct_properties(self):
         permissable_properties = OptionsColumnModel._get_permissible_properties()
 
-        self.assertEqual(permissable_properties, {"WavelengthMin":float, "WavelengthMax": float, "EventSlices": str,
+        self.assertEqual(permissable_properties, {"WavelengthMin": float, "WavelengthMax": float, "EventSlices": str,
                                                   "MergeScale": float, "MergeShift": float})
 
     def test_that_OptionsColumnModel_get_hint_strategy(self):
@@ -210,6 +210,17 @@ class TableModelTest(unittest.TestCase):
         self.assertEqual(options_string, 'EventSlices=1-6,5-9,4:5:89, MergeScale=1.5,'
                                          ' WavelengthMax=78.0, WavelengthMin=9.0')
 
+    def test_that_to_batch_list_is_correct_format(self):
+        test_row = ['SANS2D00022024  ', '', 'SANS2D00022025 ', '', '   SANS2D00022026 ', '', '', '', '', '', '', '',
+                    '    out_file', 'a_user_file ', 1.0, '', '', 'Disc', 'WavelengthMax=5.0']
+        table_index_model = TableIndexModel(*test_row)
+
+        actual_list = table_index_model.to_batch_list()
+        expected_list = ["SANS2D00022024", "out_file", "SANS2D00022025", "SANS2D00022026",
+                         "", "", "", "a_user_file"]
+
+        self.assertEqual(actual_list, expected_list)
+
     def _do_test_file_setting(self, func, prop):
         # Test that can set to empty string
         table_model = TableModel()
diff --git a/scripts/test/SANS/user_file/user_file_parser_test.py b/scripts/test/SANS/user_file/user_file_parser_test.py
index 643b6eac76f8e1279b4bfbe8dfec3112b3a732a5..e054cb5923471a37256c83cda9e522cfb9df9cb7 100644
--- a/scripts/test/SANS/user_file/user_file_parser_test.py
+++ b/scripts/test/SANS/user_file/user_file_parser_test.py
@@ -32,7 +32,8 @@ def assert_valid_result(result, expected, assert_true):
     for key in keys_result:
         assert_true(key in keys_expected)
         if result[key] != expected[key]:
-            assert_true(result[key] == expected[key])
+            assert_true(result[key] == expected[key], "For key {}, {} does not equal {}".format(key, result[key],
+                                                                                                expected[key]))
 
 
 def assert_valid_parse(parser, to_parse, expected, assert_true):
@@ -245,25 +246,29 @@ class LimitParserTest(unittest.TestCase):
                                                                               rebin_string="-12.0,-2.7,34.6")},
                           "L/q -12 3.6 2 /LIN": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
                                                                             rebin_string="-12.0,2.0,3.6")},
-                          "L/q -12 ,  0.4  ,23 ,-34.8, 3.6": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
-                                                              rebin_string="-12.0,0.4,23.0,-34.8,3.6")},  # noqa
-                          "L/q -12  , 0.4 , 23 ,-34.8 ,3.6 /LIn": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
-                                                                   rebin_string="-12.0,0.4,23.0,34.8,3.6")},
-                          "L/q -12  , 0.4 , 23  ,34.8 ,3.6  /Log": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
-                                                                    rebin_string="-12.0,-0.4,23.0,-34.8,3.6")},
-                          "L/q -12  , 0.4 , 23  ,34.8 ,3.6, .123, 5.6  /Log": {LimitsId.q: q_rebin_values(min=-12.,
+                          "L/q -12 ,  0.41  ,23 ,-34.8, 3.6": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
+                                                              rebin_string="-12.0,0.41,23.0,-34.8,3.6")},  # noqa
+                          "L/q -12  , 0.42 , 23 ,-34.8 ,3.6 /LIn": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
+                                                                   rebin_string="-12.0,0.42,23.0,34.8,3.6")},
+                          "L/q -12   0.43     23 -34.8 3.6": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
+                                                                            rebin_string="-12.0,0.43,23.0,-34.8,3.6")},
+                          "L/q -12  , 0.44 , 23  ,34.8,3.6  /Log": {LimitsId.q: q_rebin_values(min=-12., max=3.6,
+                                                                    rebin_string="-12.0,-0.44,23.0,-34.8,3.6")},
+                          "L/q -12  , 0.45 , 23  ,34.8 ,3.6, .123, 5.6  /Log": {LimitsId.q: q_rebin_values(min=-12.,
                                                                                                           max=5.6,
-                                                                    rebin_string="-12.0,-0.4,23.0,-34.8,3.6,"
+                                                                    rebin_string="-12.0,-0.45,23.0,-34.8,3.6,"
                                                                                  "-0.123,5.6")},
-                          "L/q -12  , 0.4 , 23  ,34.8 ,3.6, -.123, 5.6": {LimitsId.q: q_rebin_values(min=-12.,
+                          "L/q -12  , 0.46 , 23  ,34.8 ,3.6, -.123, 5.6": {LimitsId.q: q_rebin_values(min=-12.,
                                                                                                      max=5.6,
-                                                                          rebin_string="-12.0,0.4,23.0,34.8,3.6,"
-                                                                                       "-0.123,5.6")}
+                                                                          rebin_string="-12.0,0.46,23.0,34.8,3.6,"
+                                                                                       "-0.123,5.6")},
+                          "L/q -12   0.47   23 34.8  3.6, -.123    5.6": {LimitsId.q: q_rebin_values(min=-12.,
+                                                                            max=5.6,
+                                                                            rebin_string="-12.0,0.47,23.0,34.8,3.6,"
+                                                                                            "-0.123,5.6")}
                           }
 
-        invalid_settings = {"L/Q 12 2 3 4": RuntimeError,
-                            "L/Q 12 2 3 4 23 3": RuntimeError,
-                            "L/Q 12 2 3 4 5/LUG": RuntimeError,
+        invalid_settings = {"L/Q 12 2 3 4 5/LUG": RuntimeError,
                             "L/Q 12 2 /LIN": RuntimeError,
                             "L/Q ": RuntimeError,
                             "L/Q a 1 2 3 4 /LIN": RuntimeError}
diff --git a/scripts/wish/reduce.py b/scripts/wish/reduce.py
deleted file mode 100644
index 31d1868d68056fcf15c819a968ea692999fef0bc..0000000000000000000000000000000000000000
--- a/scripts/wish/reduce.py
+++ /dev/null
@@ -1,919 +0,0 @@
-# flake8: noqa
-import sys
-from mantid.simpleapi import *
-import numpy as n
-import mantid.simpleapi as mantid
-
-sys.path.insert(0, "/opt/Mantid/bin")
-sys.path.append("/isis/NDXWISH/user/scripts/autoreduction")
-
-
-def Wish_Run(input, cal_directory, user_directory, outputfolder, deleteWorkspace):
-    __name__ = input
-    print("Running")
-    cal_dir = cal_directory
-    use_folder = user_directory
-    out_fold = outputfolder
-
-    def validate(input_file, output_dir):
-        """
-        Autoreduction validate Function
-        -------------------------------
-
-        Function to ensure that the files we want to use in reduction exist.
-        Please add any files/directories to the required_files/dirs lists.
-        """
-        print("Running validation")
-        required_files = [input_file]
-        required_dirs = [output_dir]
-        for file_path in required_files:
-            if not os.path.isfile(file_path):
-                raise RuntimeError("Unable to find file: {}".format(file_path))
-        for dir in required_dirs:
-            if not os.path.isdir(dir):
-                raise RuntimeError("Unable to find directory: {}".format(dir))
-        print("Validation successful")
-
-    # Get the run number from the input data path
-    def get_run_number(data_path):
-        data_path = data_path.split('/')[-1]  # Get the file name
-        data_path = data_path.split('.')[0]  # Remove the extension
-        data_path = data_path[4:]  # Remove the WISH prefix
-        return int(data_path)
-
-    # Get the valid wavelength range, i.e. excluding regions where choppers cut
-    def WISH_getlambdarange():
-        return 0.7, 10.35
-
-    def WISH_setuser(usern):
-        global username
-        username = usern
-
-    def WISH_setdatadir(directory="/archive/ndxwish/Instrument/data/cycle_09_5/"):
-        global wish_datadir
-        wish_datadir = directory
-
-    def WISH_setuserdir(directory):
-        global wish_userdir
-        wish_userdir = directory
-
-    def WISH_setdatafile(filename):
-        global wish_datafile
-        wish_datafile = filename
-
-    def WISH_getdatafile():
-        return wish_datafile
-
-    def WISH_datadir():
-        return wish_datadir
-
-    def WISH_userdir(cycle='cycle_10_1'):
-        return wish_userdir
-
-    def WISH_getcalibration(cycle="11_4"):
-        return cal_dir
-
-    # def WISH_setcalibration(directory):
-        # wish_userdir = cal_dir
-
-    #   This is no longer needed unless run manually
-    def WISH_startup(usern, cycle='14_3'):
-        global userdatadir
-        global userdataprocessed
-        global mtdplt
-        import sys
-        sys.path.append('/home/' + usern + '/Scripts/')
-        # import Mantid_plotting as mtdplt
-        # userdatadir="/home/rawdata/"
-        # userdatadir = "/archive/ndxwish/Instrument/data/cycle_" + cycle + '/'
-        userdatadir = use_folder
-        WISH_setdatadir(userdatadir)
-        print "Raw Data in :   ", userdatadir
-        # userdataprocessed =  r"/home/sjenkins/Documents/WISH"
-        userdataprocessed = out_fold
-        WISH_setuserdir(directory=userdataprocessed)
-        print "Processed Data in :   ", userdataprocessed
-        return
-
-    # Returns the calibration filename
-    def WISH_cal(panel):
-        return WISH_getcalibration() + "WISH_cycle_10_3_noends_10to10.cal"
-        # return "/home/mp43/Desktop/Si_Mar15/test_detOffsets_SiMar15_noends.cal"
-        # return "/home/ryb18365/Desktop/WISH_cycle_10_3_noends_10to10_dodgytubesremoved.cal"
-
-    # Returns the grouping filename
-    def WISH_group():
-        return WISH_getcalibration() + "WISH_cycle_10_3_noends_10to10.cal"
-        # return "/home/mp43/Desktop/Si_Mar15/test_detOffsets_SiMar15_noends.cal"
-        # return "/home/ryb18365/Desktop/WISH_cycle_10_3_noends_10to10_dodgytubesremoved.cal"
-
-    def WISH_getvana(panel, SE="candlestick", cycle="09_4"):
-        if (SE == "candlestick"):
-            if (cycle == "09_2"):
-                return WISH_getcalibration() + "vana318-" + str(panel) + "foc-rmbins-smooth50.nx5"
-            if (cycle == "09_3"):
-                return WISH_getcalibration(cycle) + "vana935-" + str(panel) + "foc-SS.nx5"
-            if (cycle == "09_4"):
-                return WISH_getcalibration(cycle) + "vana3123-" + str(panel) + "foc-SS.nx5"
-            if (cycle == "09_5"):
-                return WISH_getcalibration(cycle) + "vana3123-" + str(panel) + "foc-SS.nx5"
-            if (cycle == "11_4"):
-                return WISH_getcalibration(cycle) + "vana38428-" + str(panel) + "foc-SF-SS.nxs"
-            if (cycle == "18_2"):
-                return WISH_getcalibration(cycle) + "WISHvana41865-" + str(panel) + "foc.nxs"
-        if (SE == "WISHcryo"):
-            if (cycle == "09_2"):
-                return WISH_getcalibration() + "vana318-" + str(panel) + "foc-rmbins-smooth50.nx5"
-            if (cycle == "09_3"):
-                return WISH_getcalibration(cycle) + "vana935-" + str(panel) + "foc-SS.nx5"
-            if (cycle == "09_4"):
-                return WISH_getcalibration(cycle) + "vana3123-" + str(panel) + "foc-SS.nx5"
-            if (cycle == "11_1"):
-                return WISH_getcalibration(cycle) + "vana17718-" + str(panel) + "foc-SS.nxs"
-            if (cycle == "11_2"):
-                return WISH_getcalibration(cycle) + "vana16812-" + str(panel) + "foc-SS.nx5"
-            if (cycle == "11_3"):
-                return WISH_getcalibration(cycle) + "vana18590-" + str(panel) + "foc-SS-new.nxs"
-            if (cycle == "18_2"):
-                return WISH_getcalibration(cycle) + "WISHvana41865-" + str(panel) + "foc.nxs"
-
-    def split_string(t):
-        indxp = 0
-        for i in range(0, len(t)):
-            if (t[i] == "+"):
-                indxp = i
-        if (indxp != 0):
-            return int(t[0:indxp]), int(t[indxp + 1:len(t)])
-
-    def WISH_getemptyinstrument(panel, cycle="09_4"):
-        if (cycle == "09_4"):
-            return WISH_getcalibration(cycle) + "emptyinst3120-" + str(panel) + "foc.nx5"
-
-    def WISH_getempty(panel, SE="WISHcryo", cycle="09_4"):
-        if (SE == "WISHcryo"):
-            if (cycle == "09_2"):
-                return WISH_getcalibration(cycle) + "emptycryo322-" + str(panel) + "-smooth50.nx5"
-            if (cycle == "09_3"):
-                return WISH_getcalibration(cycle) + "emptycryo1725-" + str(panel) + "foc.nx5"
-            if (cycle == "09_4"):
-                return WISH_getcalibration(cycle) + "emptycryo3307-" + str(panel) + "foc.nx5"
-            if (cycle == "09_5"):
-                return WISH_getcalibration(cycle) + "emptycryo16759-" + str(panel) + "foc.nx5"
-            if (cycle == "11_1"):
-                return WISH_getcalibration(cycle) + "emptycryo17712-" + str(panel) + "foc-SS.nxs"
-            if (cycle == "11_2"):
-                return WISH_getcalibration(cycle) + "emptycryo16759-" + str(panel) + "foc-SS.nx5"
-            if (cycle == "11_3"):
-                return WISH_getcalibration(cycle) + "emptycryo17712-" + str(panel) + "foc-SS-new.nxs"
-            if (cycle == "11_4"):
-                return WISH_getcalibration(cycle) + "empty_mag20620-" + str(panel) + "foc-HR-SF.nxs"
-        if (SE == "candlestick"):
-            if (cycle == "09_4"):
-                return WISH_getcalibration(cycle) + "emptyinst3120-" + str(panel) + "foc.nxs"
-            if (cycle == "09_3"):
-                return WISH_getcalibration(cycle) + "emptyinst1726-" + str(panel) + "foc-monitor.nxs"
-            if (cycle == "11_4"):
-                return WISH_getcalibration(cycle) + "emptyinst19618-" + str(panel) + "foc-SF-S.nxs"
-            if (cycle == "17_1"):
-                return WISH_getcalibration(cycle) + "emptyinst38581-" + str(panel) + "foc.nxs"
-
-    def WISH_getfilename(run_number, ext):
-        if (ext[0] != 's'):
-            data_dir = WISH_datadir()
-        else:
-            # data_dir="/datad/ndxwish/"
-            data_dir = WISH_datadir()
-        digit = len(str(run_number))
-        filename = os.path.join(data_dir, "WISH")
-        for i in range(0, 8 - digit):
-            filename = filename + "0"
-        filename += str(run_number) + "." + ext
-        return filename
-
-    def WISH_returnpanel(panel):
-        if (panel == 1):
-            min = 6
-            max = 19461
-        elif (panel == 2):
-            min = 19462
-            max = 38917
-        elif (panel == 3):
-            min = 38918
-            max = 58373
-        elif (panel == 4):
-            min = 58374
-            max = 77829
-        elif (panel == 5):
-            min = 77830
-            max = 97285
-        elif (panel == 6):
-            min = 97286
-            max = 116741
-        elif (panel == 7):
-            min = 116742
-            max = 136197
-        elif (panel == 8):
-            min = 136198
-            max = 155653
-        elif (panel == 9):
-            min = 155654
-            max = 175109
-        elif (panel == 10):
-            min = 175110
-            max = 194565
-        elif (panel == 0):
-            min = 6
-            max = 194565
-        print panel
-        return min, max
-
-    # This function no longer works for a list of numbers
-    # Reads a wish data file return a workspace with a short name
-    def WISH_read(number, panel, ext):
-        if type(number) is int:
-            filename = WISH_getdatafile()  # Changed as full path is set in main now
-            ext = filename.split('.')[-1]  # Get the extension from the inputted filename
-            print "Extension is: " + ext
-            # if (ext[0:10]=="nxs_event"):
-            #    filename=WISH_getfilename(number,"nxs")
-            print "will be reading filename..." + filename
-            min, max = WISH_returnpanel(panel)
-            if (panel != 0):
-                output = "w" + str(number) + "-" + str(panel)
-            else:
-                output = "w" + str(number)
-            if (ext == "raw"):
-                mantid.LoadRaw(Filename=filename, OutputWorkspace=output, SpectrumMin=str(min), SpectrumMax=str(max),
-                               LoadLogFiles="0")
-                mantid.MaskBins(InputWorkspace=output, OutputWorkspace=output, XMin=99900, XMax=106000)
-                print "standard raw file loaded"
-            if (ext[0] == "s"):
-                mantid.LoadRaw(Filename=filename, OutputWorkspace=output, SpectrumMin=str(min), SpectrumMax=str(max),
-                               LoadLogFiles="0")
-                mantid.MaskBins(InputWorkspace=output, OutputWorkspace=output, XMin=99900, XMax=106000)
-                print "sav file loaded"
-            if (ext == "nxs_event"):
-                mantid.LoadEventNexus(Filename=filename, OutputWorkspace=output, LoadMonitors='1')
-                mantid.RenameWorkspace(output + "_monitors", "w" + str(number) + "_monitors")
-                mantid.Rebin(InputWorkspace=output, OutputWorkspace=output, Params='6000,-0.00063,110000')
-                mantid.ConvertToMatrixWorkspace(output, output)
-                min, max = WISH_returnpanel(panel)
-                mantid.CropWorkspace(InputWorkspace=output, OutputWorkspace=output, StartWorkspaceIndex=min - 6,
-                                     EndWorkspaceIndex=max - 6)
-                mantid.MaskBins(InputWorkspace=output, OutputWorkspace=output, XMin=99900, XMax=106000)
-                print "full nexus eventfile loaded"
-            if (ext[0:10] == "nxs_event_"):
-                label, tmin, tmax = split_string_event(ext)
-                output = output + "_" + label
-                if (tmax == "end"):
-                    mantid.LoadEventNexus(Filename=filename, OutputWorkspace=output, FilterByTimeStart=tmin,
-                                          LoadMonitors='1',
-                                          MonitorsAsEvents='1', FilterMonByTimeStart=tmin)
-                else:
-                    mantid.LoadEventNexus(Filename=filename, OutputWorkspace=output, FilterByTimeStart=tmin,
-                                          FilterByTimeStop=tmax,
-                                          LoadMonitors='1', MonitorsAsEvents='1', FilterMonByTimeStart=tmin,
-                                          FilterMonByTimeStop=tmax)
-                    mantid.RenameWorkspace(output + "_monitors", "w" + str(number) + "_monitors")
-                print "renaming monitors done!"
-                mantid.Rebin(InputWorkspace=output, OutputWorkspace=output, Params='6000,-0.00063,110000')
-                mantid.ConvertToMatrixWorkspace(output, output)
-                min, max = WISH_returnpanel(panel)
-                mantid.CropWorkspace(InputWorkspace=output, OutputWorkspace=output, StartWorkspaceIndex=min - 6,
-                                     EndWorkspaceIndex=max - 6)
-                mantid.MaskBins(output, output, XMin=99900, XMax=106000)
-                print "nexus event file chopped"
-            if (ext == "nxs"):
-                mantid.LoadNexus(Filename=filename, OutputWorkspace=output, SpectrumMin=str(min), SpectrumMax=str(max))
-                mantid.MaskBins(InputWorkspace=output, OutputWorkspace=output, XMin=99900, XMax=106000)
-                mantid.ConvertUnits(InputWorkspace=output, OutputWorkspace=output, Target="Wavelength", Emode="Elastic")
-                print "standard histo nxs file loaded"
-        else:
-
-            n1, n2 = split_string(number)
-            output = "w" + str(n1) + "_" + str(n2) + "-" + str(panel)
-            filename = WISH_getfilename(n1, ext)
-            print "reading filename..." + filename
-            min, max = WISH_returnpanel(panel)
-            output1 = "w" + str(n1) + "-" + str(panel)
-            mantid.LoadRaw(Filename=filename, OutputWorkspace=output1, SpectrumMin=str(min), SpectrumMax=str(max),
-                           LoadLogFiles="0")
-            filename = WISH_getfilename(n2, ext)
-            print "reading filename..." + filename
-            min, max = WISH_returnpanel(panel)
-            output2 = "w" + str(n2) + "-" + str(panel)
-            mantid.LoadRaw(Filename=filename, OutputWorkspace=output2, SpectrumMin=str(min), SpectrumMax=str(max),
-                           LoadLogFiles="0")
-            mantid.MergeRuns(output1 + "," + output2, output)
-            mantid.DeleteWorkspace(output1)
-            mantid.DeleteWorkspace(output2)
-            mantid.ConvertUnits(InputWorkspace=output, OutputWorkspace=output, Target="Wavelength", Emode="Elastic")
-        lmin, lmax = WISH_getlambdarange()
-        mantid.CropWorkspace(InputWorkspace=output, OutputWorkspace=output, XMin=lmin, XMax=lmax)
-        monitor = WISH_process_incidentmon(number, ext, spline_terms=70, debug=False)
-        print "first norm to be done"
-        mantid.NormaliseToMonitor(InputWorkspace=output, OutputWorkspace=output + "norm1", MonitorWorkspace=monitor)
-        print "second norm to be done"
-        mantid.NormaliseToMonitor(InputWorkspace=output + "norm1", OutputWorkspace=output + "norm2",
-                                  MonitorWorkspace=monitor,
-                                  IntegrationRangeMin=0.7, IntegrationRangeMax=10.35)
-        mantid.DeleteWorkspace(output)
-        mantid.DeleteWorkspace(output + "norm1")
-        mantid.RenameWorkspace(InputWorkspace=output + "norm2", OutputWorkspace=output)
-        mantid.ConvertUnits(InputWorkspace=output, OutputWorkspace=output, Target="TOF", EMode="Elastic")
-        mantid.ReplaceSpecialValues(InputWorkspace=output, OutputWorkspace=output, NaNValue=0.0, NaNError=0.0,
-                                    InfinityValue=0.0,
-                                    InfinityError=0.0)
-        return output
-
-    # Focus dataset for a given panel and return the workspace
-    def WISH_focus_onepanel(work, focus, panel):
-        mantid.AlignDetectors(InputWorkspace=work, OutputWorkspace=work, CalibrationFile=WISH_cal(panel))
-        mantid.DiffractionFocussing(InputWorkspace=work, OutputWorkspace=focus, GroupingFileName=WISH_group())
-        if (panel == 5 or panel == 6):
-            mantid.CropWorkspace(InputWorkspace=focus, OutputWorkspace=focus, XMin=0.3)
-        return focus
-
-    def WISH_split(focus):
-        for i in range(0, 11):
-            out = focus[0:len(focus) - 3] + "-" + str(i + 1) + "foc"
-            mantid.ExtractSingleSpectrum(InputWorkspace=focus, OutputWorkspace=out, WorkspaceIndex=i)
-            mantid.DeleteWorkspace(focus)
-        return
-
-    def WISH_focus(work, panel):
-        focus = work + "foc"
-        if (panel != 0):
-            WISH_focus_onepanel(work, focus, panel)
-        else:
-            WISH_focus_onepanel(work, focus, panel)
-            WISH_split(focus)
-
-    def WISH_process(number, panel, ext, SEsample="WISHcryo", emptySEcycle="09_4", SEvana="candlestick",
-                     cyclevana="09_4",
-                     absorb=False, nd=0.0, Xs=0.0, Xa=0.0, h=0.0, r=0.0):
-        w = WISH_read(number, panel, ext)
-        print "file read and normalized"
-        if (absorb):
-            mantid.ConvertUnits(InputWorkspace=w, OutputWorkspace=w, Target="Wavelength", EMode="Elastic")
-            mantid.CylinderAbsorption(InputWorkspace=w, OutputWorkspace="T",
-                                      CylinderSampleHeight=h, CylinderSampleRadius=r, AttenuationXSection=Xa,
-                                      ScatteringXSection=Xs, SampleNumberDensity=nd,
-                                      NumberOfSlices="10", NumberOfAnnuli="10", NumberOfWavelengthPoints="25",
-                                      ExpMethod="Normal")
-            mantid.Divide(LHSWorkspace=w, RHSWorkspace="T", OutputWorkspace=w)
-            mantid.DeleteWorkspace("T")
-            mantid.ConvertUnits(InputWorkspace=w, OutputWorkspace=w, Target="TOF", EMode="Elastic")
-        wfoc = WISH_focus(w, panel)
-        print "focussing done!"
-        if type(number) is int:
-            wfocname = "w" + str(number) + "-" + str(panel) + "foc"
-            if (len(ext) > 9):
-                label, tmin, tmax = split_string_event(ext)
-                wfocname = "w" + str(number) + "-" + str(panel) + "_" + label + "foc"
-        else:
-            n1, n2 = split_string(number)
-            wfocname = "w" + str(n1) + "_" + str(n2) + "-" + str(panel) + "foc"
-        if deleteWorkspace:
-            mantid.DeleteWorkspace(w)
-        if (panel == 1):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.80, XMax=53.3)
-        elif (panel == 2):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=13.1)
-        elif (panel == 3):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=7.77)
-        elif (panel == 4):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.40, XMax=5.86)
-        elif (panel == 5):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.35, XMax=4.99)
-        elif (panel == 6):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.35, XMax=4.99)
-        elif (panel == 7):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.40, XMax=5.86)
-        elif (panel == 8):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=7.77)
-        elif (panel == 9):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=13.1)
-        elif (panel == 10):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.80, XMax=53.3)
-
-        if (panel == 0):
-            for i in range(1, 11):
-                wfocname = "w" + str(number) + "-" + str(i) + "foc"
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.80, XMax=53.3)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=13.1)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=7.77)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.40, XMax=5.86)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.35, XMax=4.99)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.35, XMax=4.99)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.40, XMax=5.86)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=7.77)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=13.1)
-                mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.80, XMax=53.3)
-        # print "will try to load an empty with the name:"
-        print panel
-        print SEsample
-        print emptySEcycle
-        print WISH_getempty(panel, SEsample, emptySEcycle)
-        if (panel == 0):
-            for i in range(1, 11):
-                wfocname = "w" + str(number) + "-" + str(i) + "foc"
-                mantid.LoadNexusProcessed(Filename=WISH_getempty(i, SEsample, emptySEcycle), OutputWorkspace="empty")
-                mantid.RebinToWorkspace(WorkspaceToRebin="empty", WorkspaceToMatch=wfocname, OutputWorkspace="empty")
-                mantid.Minus(LHSWorkspace=wfocname, RHSWorkspace="empty", OutputWorkspace=wfocname)
-                mantid.DeleteWorkspace("empty")
-                print "will try to load a vanadium with the name:" + WISH_getvana(i, SEvana, cyclevana)
-                mantid.LoadNexusProcessed(Filename=WISH_getvana(i, SEvana, cyclevana), OutputWorkspace="vana")
-                mantid.RebinToWorkspace(WorkspaceToRebin="vana", WorkspaceToMatch=wfocname, OutputWorkspace="vana")
-                mantid.Divide(LHSWorkspace=wfocname, RHSWorkspace="vana", OutputWorkspace=wfocname)
-                mantid.DeleteWorkspace("vana")
-                mantid.ConvertUnits(InputWorkspace=wfocname, OutputWorkspace=wfocname, Target="TOF", EMode="Elastic")
-                mantid.ReplaceSpecialValues(InputWorkspace=wfocname, OutputWorkspace=wfocname, NaNValue=0.0,
-                                            NaNError=0.0,
-                                            InfinityValue=0.0, InfinityError=0.0)
-                mantid.SaveGSS(InputWorkspace=wfocname,
-                               Filename=os.path.join(WISH_userdir(), (str(number) + "-" + str(i) + ext + ".gss")),
-                               Append=False, Bank=1)
-                mantid.SaveFocusedXYE(wfocname,
-                                      os.path.join(WISH_userdir(), (str(number) + "-" + str(i) + ext + ".dat")))
-                mantid.SaveNexusProcessed(wfocname,
-                                          os.path.join(WISH_userdir(), (str(number) + "-" + str(i) + ext + ".nxs")))
-        else:
-            mantid.LoadNexusProcessed(Filename=WISH_getempty(panel, SEsample, emptySEcycle), OutputWorkspace="empty")
-            mantid.RebinToWorkspace(WorkspaceToRebin="empty", WorkspaceToMatch=wfocname, OutputWorkspace="empty")
-            mantid.Minus(LHSWorkspace=wfocname, RHSWorkspace="empty", OutputWorkspace=wfocname)
-            mantid.DeleteWorkspace("empty")
-            print "will try to load a vanadium with the name:" + WISH_getvana(panel, SEvana, cyclevana)
-            mantid.LoadNexusProcessed(Filename=WISH_getvana(panel, SEvana, cyclevana), OutputWorkspace="vana")
-            mantid.RebinToWorkspace(WorkspaceToRebin="vana", WorkspaceToMatch=wfocname, OutputWorkspace="vana")
-            mantid.Divide(LHSWorkspace=wfocname, RHSWorkspace="vana", OutputWorkspace=wfocname)
-            mantid.DeleteWorkspace("vana")
-            mantid.ConvertUnits(InputWorkspace=wfocname, OutputWorkspace=wfocname, Target="TOF", EMode="Elastic")
-            mantid.ReplaceSpecialValues(InputWorkspace=wfocname, OutputWorkspace=wfocname, NaNValue=0.0, NaNError=0.0,
-                                        InfinityValue=0.0, InfinityError=0.0)
-            mantid.SaveGSS(InputWorkspace=wfocname,
-                           Filename=os.path.join(WISH_userdir(), (str(number) + "-" + str(panel) + ext + ".gss")),
-                           Append=False, Bank=1)
-            mantid.SaveFocusedXYE(wfocname,
-                                  os.path.join(WISH_userdir(), (str(number) + "-" + str(panel) + ext + ".dat")))
-            mantid.SaveNexusProcessed(wfocname,
-                                      os.path.join(WISH_userdir(), (str(number) + "-" + str(panel) + ext + ".nxs")))
-        return wfocname
-
-    # Create a corrected vanadium (normalise,corrected for attenuation and empty, strip peaks) and
-    # save a a nexus processed file.
-    # It looks like smoothing of 100 works quite well
-    def WISH_createvan(van, empty, panel, smoothing, vh, vr, cycle_van="18_2", cycle_empty="17_1"):
-        WISH_startup("ffv81422", cycle_van)
-        WISH_setdatafile(WISH_getfilename(41870, "nxs"))
-        WISH_setdatadir("/archive/ndxwish/Instrument/data/cycle_" + cycle_van + "/")
-        wvan = WISH_read(van, panel, "nxs_event")
-        WISH_startup("ffv81422", cycle_empty)
-        WISH_setdatafile(WISH_getfilename(38581, "nxs"))
-        WISH_setdatadir("/archive/ndxwish/Instrument/data/cycle_" + cycle_empty + "/")
-        wempty = WISH_read(empty, panel, "nxs_event")
-        mantid.Minus(LHSWorkspace=wvan, RHSWorkspace=wempty, OutputWorkspace=wvan)
-        print "read van and empty"
-        mantid.DeleteWorkspace(wempty)
-        mantid.ConvertUnits(InputWorkspace=wvan, OutputWorkspace=wvan, Target="Wavelength", EMode="Elastic")
-        mantid.CylinderAbsorption(InputWorkspace=wvan, OutputWorkspace="T",
-                                  CylinderSampleHeight=str(vh), CylinderSampleRadius=str(vr),
-                                  AttenuationXSection="4.8756",
-                                  ScatteringXSection="5.16", SampleNumberDensity="0.07118",
-                                  NumberOfSlices="10", NumberOfAnnuli="10", NumberOfWavelengthPoints="25",
-                                  ExpMethod="Normal")
-        mantid.Divide(LHSWorkspace=wvan, RHSWorkspace="T", OutputWorkspace=wvan)
-        mantid.DeleteWorkspace("T")
-        mantid.ConvertUnits(InputWorkspace=wvan, OutputWorkspace=wvan, Target="TOF", EMode="Elastic")
-        # vanfoc = WISH_focus(wvan, panel)
-        mantid.DeleteWorkspace(wvan)
-        # StripPeaks(vanfoc,vanfoc)
-        # SmoothData(vanfoc,vanfoc,str(smoothing))
-        return
-
-    def WISH_createempty(empty, panel):
-        wempty = WISH_read(empty, panel, "raw")
-        emptyfoc = WISH_focus(wempty, panel)
-        return emptyfoc
-
-    # Have made no changes here as not called (may not work in future though)
-    def WISH_monitors(rb, ext):
-        # data_dir = WISH_dir()
-        file = WISH_getfilename(rb, ext)
-        wout = "w" + str(rb)
-        print "reading File..." + file
-        mantid.LoadRaw(Filename=file, OutputWorkspace=wout, SpectrumMin=str(1), SpectrumMax=str(5), LoadLogFiles="0")
-        mantid.NormaliseByCurrent(InputWorkspace=wout, OutputWorkspace=wout)
-        mantid.ConvertToDistribution(wout)
-        return wout
-
-    # Have made no changes here as not called (may not work in future though)
-    def WISH_PH_TOF(runnumber, tubemin):
-        min = 6 + (tubenumber - 1) * 128
-        max = min + 128
-        file = WISH_getfilename(runnumber, tubemin)
-        output = "Run" + str(runnumber) + "tube" + str(tubenumber)
-        w = WISH_read(number, panel, ext)
-        print "file read and normalized"
-        if (absorb):
-            mantid.ConvertUnits(w, w, "Wavelength")
-            mantid.CylinderAbsorption(InputWorkspace=w, OutputWorkspace="T",
-                                      CylinderSampleHeight=h, CylinderSampleRadius=r, AttenuationXSection=Xa,
-                                      ScatteringXSection=Xs, SampleNumberDensity=nd,
-                                      NumberOfSlices="10", NumberOfAnnuli="10", NumberOfWavelengthPoints="25",
-                                      ExpMethod="Normal")
-            mantid.Divide(w, "T", w)
-            mantid.DeleteWorkspace("T")
-            mantid.ConvertUnits(InputWorkspace=w, OutputWorkspace=w, Target="TOF", EMode="Elastic")
-        # wfoc = WISH_focus(w, panel)
-        print "focussing done!"
-        if type(number) is int:
-            wfocname = "w" + str(number) + "-" + str(panel) + "foc"
-            if (len(ext) > 9):
-                label, tmin, tmax = split_string_event(ext)
-                wfocname = "w" + str(number) + "-" + str(panel) + "_" + label + "foc"
-        else:
-            n1, n2 = split_string(number)
-            wfocname = "w" + str(n1) + "_" + str(n2) + "-" + str(panel) + "foc"
-        if (panel == 1):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.80, XMax=53.3)
-        elif (panel == 2):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=13.1)
-        elif (panel == 3):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=7.77)
-        elif (panel == 4):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.40, XMax=5.86)
-        elif (panel == 5):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.35, XMax=4.99)
-        if (panel == 10):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.80, XMax=53.3)
-        elif (panel == 9):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=13.1)
-        elif (panel == 8):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.50, XMax=7.77)
-        elif (panel == 7):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.40, XMax=5.86)
-        elif (panel == 6):
-            mantid.CropWorkspace(InputWorkspace=wfocname, OutputWorkspace=wfocname, XMin=0.35, XMax=4.99)
-        # print "will try to load an empty with the name:"
-        print WISH_getempty(panel, SEsample, emptySEcycle)
-        if (panel == 0):
-            for i in range(1, 11):
-                mantid.LoadNexusProcessed(Filename=WISH_getempty(i, SEsample, emptySEcycle), OutputWorkspace="empty")
-                mantid.RebinToWorkspace(WorkspaceToRebin="empty", WorkspaceToMatch=wfocname, OutputWorkspace="empty")
-                mantid.Minus(LHSWorkspace=wfocname, RHSWorkspace="empty", OutputWorkspace=wfocname)
-                mantid.DeleteWorkspace("empty")
-                print "will try to load a vanadium with the name:" + WISH_getvana(i, SEvana, cyclevana)
-                mantid.LoadNexusProcessed(Filename=WISH_getvana(i, SEvana, cyclevana), OutputWorkspace="vana")
-                mantid.RebinToWorkspace(WorkspaceToRebin="vana", WorkspaceToMatch=wfocname, OutputWorkspace="vana")
-                mantid.Divide(LHSWorkspace=wfocname, RHSWorkspace="vana", OutputWorkspace=wfocname)
-                mantid.DeleteWorkspace("vana")
-                mantid.ConvertUnits(InputWorkspace=wfocname, OutputWorkspace=wfocname, Target="TOF", EMode="Elastic")
-        #			SaveGSS(InputWorkspace=wfocname,Filename=WISH_userdir()+str(number)+"-"+str(i)+ext+".gss",Append=False,Bank=1)
-        #			SaveFocusedXYE(wfocname,WISH_userdir()+str(number)+"-"+str(i)+ext+".dat")
-        else:
-            mantid.LoadNexusProcessed(Filename=WISH_getempty(panel, SEsample, emptySEcycle), OutputWorkspace="empty")
-            mantid.RebinToWorkspace(WorkspaceToRebin="empty", WorkspaceToMatch=wfocname, OutputWorkspace="empty")
-            mantid.Minus(LHSWorkspace=wfocname, RHSWorkspace="empty", OutputWorkspace=wfocname)
-            mantid.DeleteWorkspace("empty")
-            print "will try to load a vanadium with the name:" + WISH_getvana(panel, SEvana, cyclevana)
-            mantid.LoadNexusProcessed(Filename=WISH_getvana(panel, SEvana, cyclevana), OutputWorkspace="vana")
-            mantid.RebinToWorkspace(WorkspaceToRebin="vana", WorkspaceToMatch=wfocname, OutputWorkspace="vana")
-            mantid.Divide(LHSWorkspace=wfocname, RHSWorkspace="vana", OutputWorkspace=wfocname)
-            mantid.DeleteWorkspace("vana")
-            mantid.ConvertUnits(InputWorkspace=wfocname, OutputWorkspace=wfocname, Target="TOF", EMode="Elastic")
-        # SaveGSS(InputWorkspace=wfocname,Filename=WISH_userdir()+str(number)+"-"+str(panel)+ext+".gss",
-        # Append=False,Bank=1)
-        # SaveFocusedXYE(wfocname,WISH_userdir()+str(number)+"-"+str(panel)+ext+".dat")
-        return wfocname
-        mantid.LoadRaw(Filename=file, OutputWorkspace=output, spectrummin=str(min), spectrummax=str(max),
-                       LoadLogFiles="0")
-        mantid.Integration(InputWorkspace=output, OutputWorkspace=output + "int")
-        # g = plotTimeBin(output + "int", 0)
-
-    # Smoothing the incident beam monitor using a spline function.
-    # Regions around Bragg edges are masked, before fitting with a  spline function.
-    # Returns a smooth monitor spectrum
-
-    def WISH_process_incidentmon(number, ext, spline_terms=20, debug=False):
-        if type(number) is int:
-            fname = WISH_getdatafile()
-            works = "monitor" + str(number)
-            if (ext == "raw"):
-                works = "monitor" + str(number)
-                mantid.LoadRaw(Filename=fname, OutputWorkspace=works, SpectrumMin=4, SpectrumMax=4, LoadLogFiles="0")
-            if (ext[0] == "s"):
-                works = "monitor" + str(number)
-                mantid.LoadRaw(Filename=fname, OutputWorkspace=works, SpectrumMin=4, SpectrumMax=4, LoadLogFiles="0")
-            if (ext == "nxs"):
-                works = "monitor" + str(number)
-                mantid.LoadNexus(Filename=fname, OutputWorkspace=works, SpectrumMin=4, SpectrumMax=4)
-                mantid.ConvertUnits(InputWorkspace=works, OutputWorkspace=works, Target="Wavelength", Emode="Elastic")
-            if (ext[0:9] == "nxs_event"):
-                temp = "w" + str(number) + "_monitors"
-                works = "w" + str(number) + "_monitor4"
-                mantid.Rebin(InputWorkspace=temp, OutputWorkspace=temp, Params='6000,-0.00063,110000',
-                             PreserveEvents=False)
-                mantid.ExtractSingleSpectrum(InputWorkspace=temp, OutputWorkspace=works, WorkspaceIndex=3)
-        else:
-            n1, n2 = split_string(number)
-            works = "monitor" + str(n1) + "_" + str(n2)
-            fname = WISH_getfilename(n1, ext)
-            works1 = "monitor" + str(n1)
-            mantid.LoadRaw(Filename=fname, OutputWorkspace=works1, SpectrumMin=4, SpectrumMax=4, LoadLogFiles="0")
-            fname = WISH_getfilename(n2, ext)
-            works2 = "monitor" + str(n2)
-            mantid.LoadRaw(Filename=fname, OutputWorkspace=works2, SpectrumMin=4, SpectrumMax=4, LoadLogFiles="0")
-            mantid.MergeRuns(InputWorkspaces=works1 + "," + works2, OutputWorkspace=works)
-            mantid.DeleteWorkspace(works1)
-            mantid.DeleteWorkspace(works2)
-            mantid.ConvertUnits(InputWorkspace=works, OutputWorkspace=works, Target="Wavelength", Emode="Elastic")
-        lmin, lmax = WISH_getlambdarange()
-        mantid.CropWorkspace(InputWorkspace=works, OutputWorkspace=works, XMin=lmin, XMax=lmax)
-        ex_regions = n.zeros((2, 4))
-        ex_regions[:, 0] = [4.57, 4.76]
-        ex_regions[:, 1] = [3.87, 4.12]
-        ex_regions[:, 2] = [2.75, 2.91]
-        ex_regions[:, 3] = [2.24, 2.50]
-        mantid.ConvertToDistribution(works)
-        if (debug):
-            x, y, z = mtdplt.getnarray(works, 0)
-            p.plot(x, y)
-        for reg in range(0, 4):
-            mantid.MaskBins(InputWorkspace=works, OutputWorkspace=works, XMin=ex_regions[0, reg],
-                            XMax=ex_regions[1, reg])
-        if (debug):
-            x, y, z = mtdplt.getnarray(works, LoadRaw0)
-            p.plot(x, y)
-            mantid.SplineBackground(InputWorkspace=works, OutputWorkspace=works, WorkspaceIndex=0, NCoeff=spline_terms)
-        if (debug):
-            x, y, z = mtdplt.getnarray(works, 0)
-            p.plot(x, y)
-            p.show()
-            mantid.SmoothData(InputWorkspace=works, OutputWorkspace=works, NPoints=40)
-        mantid.ConvertFromDistribution(works)
-        return works
-
-    # removes the peaks in a vanadium  run, then performs a spline and a smooth
-    def Removepeaks_spline_smooth_vana(works, panel, debug=False):
-        splineterms = 0
-        smoothterms = 0
-        if (panel == 1):
-            splineterms = 0
-            smoothterms = 30
-        if (panel == 2):
-            splineterms = 0
-            smoothterms = 10
-        if (panel == 3):
-            splineterms = 0
-            smoothterms = 15
-        if (panel == 4):
-            splineterms = 0
-            smoothterms = 15
-        if (panel == 5):
-            splineterms = 0
-            smoothterms = 10
-        if (debug):
-            x, y, z = mantid.getnarray(works, 0)
-            p.plot(x, y)
-        if (splineterms != 0):
-            mantid.SplineBackground(InputWorkspace=works, OutputWorkspace=works, WorkspaceIndex=0, NCoeff=splineterms)
-        if (debug):
-            x, y, z = mantid.getnarray(works, 0)
-            p.plot(x, y)
-        if not (smoothterms == 0):
-            mantid.SmoothData(InputWorkspace=works, OutputWorkspace=works, NPoints=smoothterms)
-        if (debug):
-            x, y, z = mantid.getnarray(works, 0)
-            p.plot(x, y)
-            p.show()
-        return works
-
-    def split_string_event(t):
-        # this assumes the form nxs_event_label_tmin_tmax
-        indx_ = []
-        for i in range(10, len(t)):
-            if (t[i] == "_"):
-                indx_.append(i)
-        label = t[10:indx_[0]]
-        tmin = t[indx_[0] + 1:indx_[1]]
-        tmax = t[indx_[1] + 1:len(t)]
-        return label, tmin, tmax
-
-    def minus_emptycans(runno, empty):
-        panel_list = ['-1foc', '-2foc', '-3foc', '-4foc', '-5foc', '-6foc', '-7foc', '-8foc', '-9foc', '-10foc',
-                      '-1_10foc',
-                      '-2_9foc', '-3_8foc', '-4_7foc', '-5_6foc']
-        for p in panel_list:
-            mantid.Minus(LHSWorkspace='w' + str(runno) + p, RHSWorkspace='w' + str(empty) + p,
-                         OutputWorkspace='w' + str(runno) + 'minus' + str(empty) + p)
-            mantid.ConvertUnits(InputWorkspace='w' + str(runno) + 'minus' + str(empty) + p,
-                                OutputWorkspace='w' + str(runno) + 'minus' + str(empty) + p + '-d', Target='dSpacing')
-            mantid.SaveGSS("w" + str(runno) + 'minus' + str(empty) + p,
-                           os.path.join(WISH_userdir(), (str(i) + p + ".gss")), Append=False, Bank=1)
-
-    def main(input_file, output_dir):
-        # Check files can be found
-        validate(input_file, output_dir)
-
-        # test="nxs_event_1_300.00_600.00"
-        # print split_string_event(test)
-
-        # #####################################################################
-        # #####     			USER SPECIFIC PART STARTS BELOW 									   ##
-        # #####     			IN CASE LINES ABOVE HAVE BEEN EDITED AND SCRIPTS NO LONGER WORK   ##
-        # #####     			LOG OUT AND BACK IN TO THE MACHINE								   ##
-        # #####################################################################
-        # ########### SETTING the paths automatically : WISH_startup(username,cycle_name) ##############
-        # WISH_startup("ryb18365","15_1")
-
-        WISH_setuserdir(output_dir)
-        WISH_setdatafile(input_file)
-        print(output_dir)
-        print(input_file)
-        # #        WISH PROCESS ROUTINES TO EDIT   (penultimate line optional but useful for recovering data later on)  #
-        # To add two raw files together, replace runno (integer, eg. 16800) by a string "int1+int2" (eg "16800+16801" note quotes)
-        # ##############################################################################################
-        # beg=0
-        # end=1800
-        # nbslices=int(end/180)
-        # suffix=[]
-        # for k in range(0,nbslices):
-        #	suffix.append("nxs_event_slice"+str(k)+"_"+str(int(k*180))+"_"+str((k+1)*180))
-
-        # print len(suffix)
-        # print suffix[0], suffix[k]
-
-        # for i in range(24901,24902):
-        #	for j in range(2,3):
-        #		for k in range(0,len(suffix)):
-        #			wout=WISH_process(i,j,suffix[k],"candlestick","11_4","candlestick","11_4",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #			ConvertUnits(wout,wout+"-d","dSpacing")
-        # ##############################################################################################
-        # for i in range(24895,24896):
-        #	for j in range(5,6):
-        #		wout=WISH_process(i,j,"nxs_event_slice1_0_300","candlestick","11_4","candlestick","11_4",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice2_300_600","candlestick","11_4","candlestick","11_4",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice3_600_900","candlestick","11_4","candlestick","11_4",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice4_900_1200","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice5_1200_1500","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice6_1500_1800","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")-4foc
-        #		wout=WISH_process(i,j,"nxs_event_slice7_1800_2100","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice8_2100_2400","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice9_2400_2700","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice10_2700_3000","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice11_3000_3300","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-        #		wout=WISH_process(i,j,"nxs_event_slice12_3300_end","WISHcryo","11_3","WISHcryo","11_3",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-
-        i = get_run_number(input_file)
-        for j in range(1, 11):
-            WISH_process(i, j, "raw", "candlestick", "17_1", "candlestick", "18_2", absorb=False, nd=0.0, Xs=0.0,
-                         Xa=0.0, h=4.0, r=0.4)
-        for j in range(1, 11):
-            wout = WISH_process(i, j, "raw", "candlestick", "17_1", "candlestick", "18_2", absorb=False, nd=0.0, Xs=0.0,
-                                Xa=0.0, h=4.0, r=0.4)
-            mantid.ConvertUnits(InputWorkspace=wout, OutputWorkspace=wout + "-d", Target="dSpacing", EMode="Elastic")
-            #	SaveGSS("w"+str(i)+"-1foc",WISH_userdir()+str(i)+"-1foc"+".gss",Append=False,Bank=1)
-            #	SaveFocusedXYE("w"+str(i)+"-1foc",WISH_userdir()+str(i)+"-1foc"+".dat")
-            #	SaveGSS("w"+str(i)+"-2foc",WISH_userdir()+str(i)+"-2foc"+".gss",Append=False,Bank=1)
-            #	SaveFocusedXYE("w"+str(i)+"-2foc",WISH_userdir()+str(i)+"-2foc"+".dat")
-            mantid.RebinToWorkspace(WorkspaceToRebin="w" + str(i) + "-6foc", WorkspaceToMatch="w" + str(i) + "-5foc",
-                                    OutputWorkspace="w" + str(i) + "-6foc", PreserveEvents='0')
-            mantid.Plus(LHSWorkspace="w" + str(i) + "-5foc", RHSWorkspace="w" + str(i) + "-6foc",
-                        OutputWorkspace="w" + str(i) + "-5_6foc")
-            mantid.ConvertUnits(InputWorkspace="w" + str(i) + "-5_6foc",
-                                OutputWorkspace="w" + str(i) + "-5_6foc" + "-d",
-                                Target="dSpacing", EMode="Elastic")
-            mantid.SaveGSS("w" + str(i) + "-5_6foc", os.path.join(WISH_userdir(), (str(i) + "-5_6raw" + ".gss")),
-                           Append=False, Bank=1)
-            mantid.SaveFocusedXYE("w" + str(i) + "-5_6foc", os.path.join(WISH_userdir(), (str(i) + "-5_6raw" + ".dat")))
-            mantid.SaveNexusProcessed("w" + str(i) + "-5_6foc",
-                                      os.path.join(WISH_userdir(), (str(i) + "-5_6raw" + ".nxs")))
-            mantid.RebinToWorkspace(WorkspaceToRebin="w" + str(i) + "-7foc", WorkspaceToMatch="w" + str(i) + "-4foc",
-                                    OutputWorkspace="w" + str(i) + "-7foc", PreserveEvents='0')
-            mantid.Plus(LHSWorkspace="w" + str(i) + "-4foc", RHSWorkspace="w" + str(i) + "-7foc",
-                        OutputWorkspace="w" + str(i) + "-4_7foc")
-            mantid.ConvertUnits(InputWorkspace="w" + str(i) + "-4_7foc",
-                                OutputWorkspace="w" + str(i) + "-4_7foc" + "-d",
-                                Target="dSpacing", EMode="Elastic")
-            mantid.SaveGSS("w" + str(i) + "-4_7foc", os.path.join(WISH_userdir(), (str(i) + "-4_7raw" + ".gss")),
-                           Append=False, Bank=1)
-            mantid.SaveFocusedXYE("w" + str(i) + "-4_7foc", os.path.join(WISH_userdir(), (str(i) + "-4_7raw" + ".dat")))
-            mantid.SaveNexusProcessed("w" + str(i) + "-4_7foc",
-                                      os.path.join(WISH_userdir(), (str(i) + "-4_7raw" + ".nxs")))
-        mantid.RebinToWorkspace(WorkspaceToRebin="w" + str(i) + "-8foc", WorkspaceToMatch="w" + str(i) + "-3foc",
-                                OutputWorkspace="w" + str(i) + "-8foc", PreserveEvents='0')
-        mantid.Plus(LHSWorkspace="w" + str(i) + "-3foc", RHSWorkspace="w" + str(i) + "-8foc",
-                    OutputWorkspace="w" + str(i) + "-3_8foc")
-        mantid.ConvertUnits(InputWorkspace="w" + str(i) + "-3_8foc", OutputWorkspace="w" + str(i) + "-3_8foc" + "-d",
-                            Target="dSpacing", EMode="Elastic")
-        mantid.SaveGSS("w" + str(i) + "-3_8foc", os.path.join(WISH_userdir(), (str(i) + "-3_8raw" + ".gss")),
-                       Append=False, Bank=1)
-        mantid.SaveFocusedXYE("w" + str(i) + "-3_8foc", os.path.join(WISH_userdir(), (str(i) + "-3_8raw" + ".dat")))
-        mantid.SaveNexusProcessed("w" + str(i) + "-3_8foc", os.path.join(WISH_userdir(), (str(i) + "-3_8raw" + ".nxs")))
-        mantid.RebinToWorkspace(WorkspaceToRebin="w" + str(i) + "-9foc", WorkspaceToMatch="w" + str(i) + "-2foc",
-                                OutputWorkspace="w" + str(i) + "-9foc", PreserveEvents='0')
-        mantid.Plus(LHSWorkspace="w" + str(i) + "-2foc", RHSWorkspace="w" + str(i) + "-9foc",
-                    OutputWorkspace="w" + str(i) + "-2_9foc")
-        mantid.ConvertUnits(InputWorkspace="w" + str(i) + "-2_9foc", OutputWorkspace="w" + str(i) + "-2_9foc" + "-d",
-                            Target="dSpacing", EMode="Elastic")
-        mantid.SaveGSS("w" + str(i) + "-2_9foc", os.path.join(WISH_userdir(), (str(i) + "-2_9raw" + ".gss")),
-                       Append=False, Bank=1)
-        mantid.SaveFocusedXYE("w" + str(i) + "-2_9foc", os.path.join(WISH_userdir(), (str(i) + "-2_9raw" + ".dat")))
-        mantid.SaveNexusProcessed("w" + str(i) + "-2_9foc", os.path.join(WISH_userdir(), (str(i) + "-2_9raw" + ".nxs")))
-        mantid.RebinToWorkspace(WorkspaceToRebin="w" + str(i) + "-10foc", WorkspaceToMatch="w" + str(i) + "-1foc",
-                                OutputWorkspace="w" + str(i) + "-10foc", PreserveEvents='0')
-        mantid.Plus(LHSWorkspace="w" + str(i) + "-1foc", RHSWorkspace="w" + str(i) + "-10foc",
-                    OutputWorkspace="w" + str(i) + "-1_10foc")
-        mantid.ConvertUnits(InputWorkspace="w" + str(i) + "-1_10foc", OutputWorkspace="w" + str(i) + "-1_10foc" + "-d",
-                            Target="dSpacing", EMode="Elastic")
-        mantid.SaveGSS("w" + str(i) + "-1_10foc", os.path.join(WISH_userdir(), (str(i) + "-1_10raw" + ".gss")),
-                       Append=False, Bank=1)
-        mantid.SaveFocusedXYE("w" + str(i) + "-1_10foc", os.path.join(WISH_userdir(), (str(i) + "-1_10raw" + ".dat")))
-        mantid.SaveNexusProcessed("w" + str(i) + "-1_10foc",
-                                  os.path.join(WISH_userdir(), (str(i) + "-1_10raw" + ".nxs")))
-
-        # minus_emptycans(26977,26969)
-        # #############################################################################################
-        # for i in range(23840,23841):
-        #	for j in range(5,0,-1):
-        #		wout=WISH_process(i,j,"raw","candlestick","11_4","candlestick","11_4",absorb=False,nd=0.0,Xs=0.0,Xa=0.0,h=0.0,r=0.0)
-        #		SaveNexusProcessed(wout,WISH_userdir()+wout+".nxs")
-        #		ConvertUnits(wout,wout+"-d","dSpacing")
-
-        # ###########################################################################################
-        # for i in range(18880,18881):
-        #	for j in range(1,6):
-        #		wout=WISH_process(i,j,"raw","WISHcryo","11_3","WISHcryo","11_3",absorb=True,nd=0.0035,Xs=62.27,Xa=429.95,h=4.0,r=0.15)
-        # ###########################################################################################
-        # #########               END OF WISH PROCESS ROUTINES                               ##################
-
-        # #####################################################################
-        # How to retrieve already processed Data without having to reprocess it  (in case Mantid session has been closed #
-        # list_overlap=['5_6','4_7','3_8','2_9','1_10']
-        # for i in range(27739,27740):
-        # for j in range(1,11):
-        #    wr=str(i)+"-"+str(j)+"raw"
-        #    LoadNexusProcessed(Filename=WISH_userdir()+wr+".nxs",OutputWorkspace=wr)
-        #    ConvertUnits(InputWorkspace=wr,OutputWorkspace=wr+"-d",Target="dSpacing")
-        # for k in list_overlap:
-        #   wr=str(i)+"-"+k+"raw"
-        #   LoadNexusProcessed(Filename=WISH_userdir()+wr+".nxs",OutputWorkspace=wr)
-        #  ConvertUnits(InputWorkspace=wr,OutputWorkspace=wr+"-d",Target="dSpacing")
-        #####################################################################
-        # for runno in range(22678,22686):
-        # for j in range(1,6):
-        # wr="w"+str(runno)+"-"+str(j)+"foc"
-
-        # for j in range(1,6):
-        #	Plus("w22663"+"-"+str(j)+"foc","w22664"+"-"+str(j)+"foc","vancan"+"-"+str(j)+"foc")
-        #	SaveGSS(InputWorkspace="vancan"+"-"+str(j)+"foc",Filename=WISH_userdir()+"vancan"+"-"+str(j)+"foc.gss",Append=False,Bank=1)
-
-        # ####################################################################
-        # If you don't have a correct empty, either use the most suitable one or use the lines below ####
-        # for i in range(1197,1410):
-        #	for j in range(1,6):
-        #		wout=WISH_read(i,j,"raw")
-        #		wfoc=WISH_focus(wout,j)
-        # ####################################################################
-        # use the lines below to manually set the paths if needed
-
-        # ########  use the lines below to create a processed empty (instrument, cryostat, can..) run     ########
-        # for i in range(4,5):
-        #	WISH_createempty(20620,i)
-        # SaveNexusProcessed("w16748-"+str(i)+"foc",WISH_userdir()+"emptyinst16748-"+str(i)+"foc.nx5")
-
-    def create_vanadium():
-
-        # ######### use the lines below to process a LoadRawvanadium run                               #################
-        for j in range(1, 11):
-            WISH_createvan(41865, 38581, j, 100, 4.0, 0.15, cycle_van="18_2", cycle_empty="17_1")
-            mantid.CropWorkspace(InputWorkspace="w41865-" + str(j) + "foc", OutputWorkspace="w41865-" + str(j) + "foc",
-                                 XMin='0.35',
-                                 XMax='5.0')
-            Removepeaks_spline_smooth_vana("w41865-" + str(j) + "foc", j, debug=False)
-            mantid.SaveNexusProcessed("w41865-" + str(j) + "foc",
-                                      os.path.join(WISH_userdir(), ("vana41865-" + str(j) + "foc.nxs")))
-
-    if __name__ == "__main__":
-        # WISH_startup("ffv81422", "17_1")
-        # WISH_setdatafile(WISH_getfilename(38581, "nxs"))
-        # for i in range(1, 11):
-        #    print "loop"
-        #    wout = WISH_read(38581, i, "raw")
-        #    wfoc=WISH_focus(wout,i)
-        WISH_startup("ffv81422", "18_2")
-        WISH_setdatafile(WISH_getfilename(41870, "nxs"))
-
-        main(WISH_getdatafile(), WISH_userdir())