diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9600d33067116a8a7ae5812a29f676e5a7c5d20e..6813d520d89e3de45578a2e634a5d274f6e312b1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -120,7 +120,7 @@ if (ENABLE_MANTIDPLOT)
 endif()
 
 if ( ENABLE_WORKBENCH )
-  find_package ( Qt5 COMPONENTS Core Gui Widgets REQUIRED )
+  find_package ( Qt5 COMPONENTS Core Gui Widgets OpenGL REQUIRED )
   if ( Qt5_FOUND )
     message ( STATUS "Found Qt ${Qt5_VERSION}: ${Qt5_DIR}" )
   endif()
diff --git a/Framework/API/inc/MantidAPI/DataProcessorAlgorithm.h b/Framework/API/inc/MantidAPI/DataProcessorAlgorithm.h
index 567f75e9289464d928f0527f9f034246fdb01473..47b296679e03914a5995f59be180369bd105bd6b 100644
--- a/Framework/API/inc/MantidAPI/DataProcessorAlgorithm.h
+++ b/Framework/API/inc/MantidAPI/DataProcessorAlgorithm.h
@@ -4,8 +4,11 @@
 #include "MantidKernel/System.h"
 #include "MantidAPI/Algorithm.h"
 #include "MantidAPI/AlgorithmManager.h"
-#include "MantidAPI/ITableWorkspace_fwd.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 #include "MantidAPI/IEventWorkspace_fwd.h"
+#include "MantidAPI/ITableWorkspace_fwd.h"
+#include "MantidAPI/ParallelAlgorithm.h"
+#include "MantidAPI/SerialAlgorithm.h"
 #include "MantidKernel/PropertyManager.h"
 #include <vector>
 
@@ -39,11 +42,13 @@ namespace API {
    File change history is stored at: <https://github.com/mantidproject/mantid>
    Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
-class DLLExport DataProcessorAlgorithm : public Algorithm {
+template <class Base>
+class DLLExport GenericDataProcessorAlgorithm : public Base {
 public:
-  DataProcessorAlgorithm();
+  GenericDataProcessorAlgorithm();
   std::string getPropertyValue(const std::string &name) const override;
-  TypedValue getProperty(const std::string &name) const override;
+  Kernel::PropertyManagerOwner::TypedValue
+  getProperty(const std::string &name) const override;
 
 protected:
   boost::shared_ptr<Algorithm> createChildAlgorithm(
@@ -110,8 +115,8 @@ private:
     auto alg = createChildAlgorithm(algorithmName);
     alg->initialize();
 
-    alg->setProperty<LHSType>("LHSWorkspace", lhs);
-    alg->setProperty<RHSType>("RHSWorkspace", rhs);
+    alg->template setProperty<LHSType>("LHSWorkspace", lhs);
+    alg->template setProperty<RHSType>("RHSWorkspace", rhs);
     alg->execute();
 
     if (alg->isExecuted()) {
@@ -137,8 +142,28 @@ private:
   std::string m_propertyManagerPropertyName;
   /// Map property names to names in supplied properties manager
   std::map<std::string, std::string> m_nameToPMName;
+
+  // This method is a workaround for the C4661 compiler warning in visual
+  // studio. This allows the template declaration and definition to be separated
+  // in different files. See stack overflow article for a more detailed
+  // explanation:
+  // https://stackoverflow.com/questions/44160467/warning-c4661no-suitable-definition-provided-for-explicit-template-instantiatio
+  // https://stackoverflow.com/questions/33517902/how-to-export-a-class-derived-from-an-explicitly-instantiated-template-in-visual
+  void visualStudioC4661Workaround();
 };
 
+template <>
+MANTID_API_DLL void
+GenericDataProcessorAlgorithm<Algorithm>::visualStudioC4661Workaround();
+
+using DataProcessorAlgorithm = GenericDataProcessorAlgorithm<Algorithm>;
+using SerialDataProcessorAlgorithm =
+    GenericDataProcessorAlgorithm<SerialAlgorithm>;
+using ParallelDataProcessorAlgorithm =
+    GenericDataProcessorAlgorithm<ParallelAlgorithm>;
+using DistributedDataProcessorAlgorithm =
+    GenericDataProcessorAlgorithm<DistributedAlgorithm>;
+
 } // namespace API
 } // namespace Mantid
 
diff --git a/Framework/API/inc/MantidAPI/DistributedAlgorithm.h b/Framework/API/inc/MantidAPI/DistributedAlgorithm.h
index 0dfebbf6ff55ec8f7468a32e4a507878c47f4f84..0d9351062e8857dea6815f15a953af76bc4a0f0d 100644
--- a/Framework/API/inc/MantidAPI/DistributedAlgorithm.h
+++ b/Framework/API/inc/MantidAPI/DistributedAlgorithm.h
@@ -16,7 +16,9 @@ namespace API {
   from DistributedAlgorithm instead of from Algorithm provides the necessary
   overriden method(s) to allow running an algorithm with MPI. This works under
   the following conditions:
-  1. The algorithm must have a single input and a single output workspace.
+  1. The algorithm must input workspaces with compatible storage modes.
+  StorageMode::Distributed is not compatible with StorageMode::MasterOnly, but
+  all combinations with StorageMode::Cloned are considered compatible.
   2. No output files may be written since filenames would clash.
   Algorithms that do not modify spectra in a workspace may also use this base
   class to support MPI. For example, modifications of the instrument are handled
diff --git a/Framework/API/inc/MantidAPI/WorkspaceGroup.h b/Framework/API/inc/MantidAPI/WorkspaceGroup.h
index a3ab7f468295acfbb1d088c60a5971a547ed5b93..1235c04c8cf11e13f0834be867a981fd8b7cad36 100644
--- a/Framework/API/inc/MantidAPI/WorkspaceGroup.h
+++ b/Framework/API/inc/MantidAPI/WorkspaceGroup.h
@@ -53,7 +53,8 @@ class Algorithm;
 class MANTID_API_DLL WorkspaceGroup : public Workspace {
 public:
   /// Default constructor.
-  WorkspaceGroup();
+  WorkspaceGroup(
+      const Parallel::StorageMode storageMode = Parallel::StorageMode::Cloned);
   /// Destructor
   ~WorkspaceGroup() override;
   /// Return a string ID of the class
diff --git a/Framework/API/src/Algorithm.cpp b/Framework/API/src/Algorithm.cpp
index 3a466777a55a2e32a1fd705294a053bd05b29acb..0e4c36bb38e985db7b7bace28b978784ea5bcd1f 100644
--- a/Framework/API/src/Algorithm.cpp
+++ b/Framework/API/src/Algorithm.cpp
@@ -502,10 +502,15 @@ bool Algorithm::execute() {
     return false;
   }
 
+  const auto executionMode = getExecutionMode();
+
   timingInit += timer.elapsed(resetTimer);
   // ----- Perform validation of the whole set of properties -------------
-  if (!callProcessGroups) // for groups this is called on each workspace
-                          // separately
+  if ((!callProcessGroups) &&
+      (executionMode != Parallel::ExecutionMode::MasterOnly ||
+       communicator().rank() ==
+           0)) // for groups this is called on each workspace
+               // separately
   {
     std::map<std::string, std::string> errors = this->validateInputs();
     if (!errors.empty()) {
@@ -566,7 +571,7 @@ bool Algorithm::execute() {
 
       startTime = Mantid::Types::Core::DateAndTime::getCurrentTime();
       // Call the concrete algorithm's exec method
-      this->exec(getExecutionMode());
+      this->exec(executionMode);
       registerFeatureUsage();
       // Check for a cancellation request in case the concrete algorithm doesn't
       interruption_point();
@@ -1773,6 +1778,9 @@ Parallel::ExecutionMode Algorithm::getExecutionMode() const {
     getLogger().error() << error << "\n";
     throw(std::runtime_error(error));
   }
+  getLogger().information() << "MPI Rank " << communicator().rank()
+                            << " running with "
+                            << Parallel::toString(executionMode) << '\n';
   return executionMode;
 }
 
@@ -1792,6 +1800,11 @@ Algorithm::getInputWorkspaceStorageModes() const {
     else if (!wsProp->isOptional())
       map.emplace(prop.name(), Parallel::StorageMode::MasterOnly);
   }
+  getLogger().information()
+      << "Input workspaces for determining execution mode:\n";
+  for (const auto &item : map)
+    getLogger().information() << "  " << item.first << " --- "
+                              << Parallel::toString(item.second) << '\n';
   return map;
 }
 
diff --git a/Framework/API/src/DataProcessorAlgorithm.cpp b/Framework/API/src/DataProcessorAlgorithm.cpp
index 1e61e78ce112c5048be2eba7f2840447fa7e7f85..053676057f9525dcd205b10d434f08b409cac6c5 100644
--- a/Framework/API/src/DataProcessorAlgorithm.cpp
+++ b/Framework/API/src/DataProcessorAlgorithm.cpp
@@ -26,11 +26,12 @@ namespace API {
 //----------------------------------------------------------------------------------------------
 /** Constructor
  */
-DataProcessorAlgorithm::DataProcessorAlgorithm()
-    : API::Algorithm(), m_useMPI(false), m_loadAlg("Load"),
-      m_accumulateAlg("Plus"), m_loadAlgFileProp("Filename"),
+template <class Base>
+GenericDataProcessorAlgorithm<Base>::GenericDataProcessorAlgorithm()
+    : m_useMPI(false), m_loadAlg("Load"), m_accumulateAlg("Plus"),
+      m_loadAlgFileProp("Filename"),
       m_propertyManagerPropertyName("ReductionProperties") {
-  enableHistoryRecordingForChild(true);
+  Base::enableHistoryRecordingForChild(true);
 }
 
 //---------------------------------------------------------------------------------------------
@@ -54,7 +55,9 @@ DataProcessorAlgorithm::DataProcessorAlgorithm()
 *default gives the latest version.
 *  @return shared pointer to the newly created algorithm object
 */
-boost::shared_ptr<Algorithm> DataProcessorAlgorithm::createChildAlgorithm(
+template <class Base>
+boost::shared_ptr<Algorithm>
+GenericDataProcessorAlgorithm<Base>::createChildAlgorithm(
     const std::string &name, const double startProgress,
     const double endProgress, const bool enableLogging, const int &version) {
   // call parent method to create the child algorithm
@@ -63,18 +66,20 @@ boost::shared_ptr<Algorithm> DataProcessorAlgorithm::createChildAlgorithm(
   alg->enableHistoryRecordingForChild(this->isRecordingHistoryForChild());
   if (this->isRecordingHistoryForChild()) {
     // pass pointer to the history object created in Algorithm to the child
-    alg->trackAlgorithmHistory(m_history);
+    alg->trackAlgorithmHistory(Base::m_history);
   }
   return alg;
 }
 
-void DataProcessorAlgorithm::setLoadAlg(const std::string &alg) {
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::setLoadAlg(const std::string &alg) {
   if (alg.empty())
     throw std::invalid_argument("Cannot set load algorithm to empty string");
   m_loadAlg = alg;
 }
 
-void DataProcessorAlgorithm::setLoadAlgFileProp(
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::setLoadAlgFileProp(
     const std::string &filePropName) {
   if (filePropName.empty()) {
     throw std::invalid_argument(
@@ -83,14 +88,16 @@ void DataProcessorAlgorithm::setLoadAlgFileProp(
   m_loadAlgFileProp = filePropName;
 }
 
-void DataProcessorAlgorithm::setAccumAlg(const std::string &alg) {
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::setAccumAlg(const std::string &alg) {
   if (alg.empty())
     throw std::invalid_argument(
         "Cannot set accumulate algorithm to empty string");
   m_accumulateAlg = alg;
 }
 
-void DataProcessorAlgorithm::setPropManagerPropName(
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::setPropManagerPropName(
     const std::string &propName) {
   m_propertyManagerPropertyName = propName;
 }
@@ -103,7 +110,8 @@ void DataProcessorAlgorithm::setPropManagerPropName(
  * @param nameInProp Name of the property as declared in Algorithm::init().
  * @param nameInPropManager Name of the property in the PropertyManager.
  */
-void DataProcessorAlgorithm::mapPropertyName(
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::mapPropertyName(
     const std::string &nameInProp, const std::string &nameInPropManager) {
   m_nameToPMName[nameInProp] = nameInPropManager;
 }
@@ -119,8 +127,9 @@ void DataProcessorAlgorithm::mapPropertyName(
  *
  * @throws std::runtime_error If you ask to copy a non-existent property
  */
-void DataProcessorAlgorithm::copyProperty(API::Algorithm_sptr alg,
-                                          const std::string &name) {
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::copyProperty(
+    API::Algorithm_sptr alg, const std::string &name) {
   if (!alg->existsProperty(name)) {
     std::stringstream msg;
     msg << "Algorithm \"" << alg->name() << "\" does not have property \""
@@ -129,8 +138,8 @@ void DataProcessorAlgorithm::copyProperty(API::Algorithm_sptr alg,
   }
 
   auto prop = alg->getPointerToProperty(name);
-  declareProperty(std::unique_ptr<Property>(prop->clone()),
-                  prop->documentation());
+  Base::declareProperty(std::unique_ptr<Property>(prop->clone()),
+                        prop->documentation());
 }
 
 /**
@@ -141,10 +150,11 @@ void DataProcessorAlgorithm::copyProperty(API::Algorithm_sptr alg,
  * @param name
  * @return
  */
-std::string
-DataProcessorAlgorithm::getPropertyValue(const std::string &name) const {
+template <class Base>
+std::string GenericDataProcessorAlgorithm<Base>::getPropertyValue(
+    const std::string &name) const {
   // explicitly specifying a property wins
-  if (!isDefault(name)) {
+  if (!Base::isDefault(name)) {
     return Algorithm::getPropertyValue(name);
   }
 
@@ -168,11 +178,13 @@ DataProcessorAlgorithm::getPropertyValue(const std::string &name) const {
  * @param name
  * @return
  */
+template <class Base>
 PropertyManagerOwner::TypedValue
-DataProcessorAlgorithm::getProperty(const std::string &name) const {
+GenericDataProcessorAlgorithm<Base>::getProperty(
+    const std::string &name) const {
   // explicitely specifying a property wins
-  if (!isDefault(name)) {
-    return Algorithm::getProperty(name);
+  if (!Base::isDefault(name)) {
+    return Base::getProperty(name);
   }
 
   // return it if it is in the held property manager
@@ -188,15 +200,18 @@ DataProcessorAlgorithm::getProperty(const std::string &name) const {
   return Algorithm::getProperty(name);
 }
 
-ITableWorkspace_sptr
-DataProcessorAlgorithm::determineChunk(const std::string &filename) {
+template <class Base>
+ITableWorkspace_sptr GenericDataProcessorAlgorithm<Base>::determineChunk(
+    const std::string &filename) {
   UNUSED_ARG(filename);
 
   throw std::runtime_error(
       "DataProcessorAlgorithm::determineChunk is not implemented");
 }
 
-MatrixWorkspace_sptr DataProcessorAlgorithm::loadChunk(const size_t rowIndex) {
+template <class Base>
+MatrixWorkspace_sptr
+GenericDataProcessorAlgorithm<Base>::loadChunk(const size_t rowIndex) {
   UNUSED_ARG(rowIndex);
 
   throw std::runtime_error(
@@ -208,7 +223,9 @@ MatrixWorkspace_sptr DataProcessorAlgorithm::loadChunk(const size_t rowIndex) {
  * @param partialWS :: workspace to assemble
  * thread only)
  */
-Workspace_sptr DataProcessorAlgorithm::assemble(Workspace_sptr partialWS) {
+template <class Base>
+Workspace_sptr
+GenericDataProcessorAlgorithm<Base>::assemble(Workspace_sptr partialWS) {
   Workspace_sptr outputWS = partialWS;
 #ifdef MPI_BUILD
   IAlgorithm_sptr gatherAlg = createChildAlgorithm("GatherWorkspaces");
@@ -233,9 +250,10 @@ Workspace_sptr DataProcessorAlgorithm::assemble(Workspace_sptr partialWS) {
  * @param outputWSName :: Name of the assembled workspace (available in main
  * thread only)
  */
+template <class Base>
 Workspace_sptr
-DataProcessorAlgorithm::assemble(const std::string &partialWSName,
-                                 const std::string &outputWSName) {
+GenericDataProcessorAlgorithm<Base>::assemble(const std::string &partialWSName,
+                                              const std::string &outputWSName) {
 #ifdef MPI_BUILD
   std::string threadOutput = partialWSName;
   Workspace_sptr partialWS =
@@ -266,8 +284,9 @@ DataProcessorAlgorithm::assemble(const std::string &partialWSName,
  * @param outputWSName :: Name of the workspace to save
  * @param outputFile :: Path to the Nexus file to save
  */
-void DataProcessorAlgorithm::saveNexus(const std::string &outputWSName,
-                                       const std::string &outputFile) {
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::saveNexus(
+    const std::string &outputWSName, const std::string &outputFile) {
   bool saveOutput = true;
 #ifdef MPI_BUILD
   if (boost::mpi::communicator().rank() > 0)
@@ -283,7 +302,7 @@ void DataProcessorAlgorithm::saveNexus(const std::string &outputWSName,
 }
 
 /// Return true if we are running on the main thread
-bool DataProcessorAlgorithm::isMainThread() {
+template <class Base> bool GenericDataProcessorAlgorithm<Base>::isMainThread() {
   bool mainThread;
 #ifdef MPI_BUILD
   mainThread = (boost::mpi::communicator().rank() == 0);
@@ -294,7 +313,7 @@ bool DataProcessorAlgorithm::isMainThread() {
 }
 
 /// Return the number of MPI processes running
-int DataProcessorAlgorithm::getNThreads() {
+template <class Base> int GenericDataProcessorAlgorithm<Base>::getNThreads() {
 #ifdef MPI_BUILD
   return boost::mpi::communicator().size();
 #else
@@ -307,8 +326,10 @@ int DataProcessorAlgorithm::getNThreads() {
  * @param inputData :: File path or workspace name
  * @param loadQuiet :: If true then the output is not stored in the ADS
  */
-Workspace_sptr DataProcessorAlgorithm::load(const std::string &inputData,
-                                            const bool loadQuiet) {
+template <class Base>
+Workspace_sptr
+GenericDataProcessorAlgorithm<Base>::load(const std::string &inputData,
+                                          const bool loadQuiet) {
   Workspace_sptr inputWS;
 
   // First, check whether we have the name of an existing workspace
@@ -370,11 +391,13 @@ Workspace_sptr DataProcessorAlgorithm::load(const std::string &inputData,
  *
  * @param propertyManager :: Name of the property manager to retrieve.
  */
-boost::shared_ptr<PropertyManager> DataProcessorAlgorithm::getProcessProperties(
+template <class Base>
+boost::shared_ptr<PropertyManager>
+GenericDataProcessorAlgorithm<Base>::getProcessProperties(
     const std::string &propertyManager) const {
   std::string propertyManagerName(propertyManager);
   if (propertyManager.empty() && (!m_propertyManagerPropertyName.empty())) {
-    if (!existsProperty(m_propertyManagerPropertyName)) {
+    if (!Base::existsProperty(m_propertyManagerPropertyName)) {
       std::stringstream msg;
       msg << "Failed to find property \"" << m_propertyManagerPropertyName
           << "\"";
@@ -388,7 +411,7 @@ boost::shared_ptr<PropertyManager> DataProcessorAlgorithm::getProcessProperties(
     processProperties =
         PropertyManagerDataService::Instance().retrieve(propertyManagerName);
   } else {
-    getLogger().notice() << "Could not find property manager\n";
+    Base::getLogger().notice() << "Could not find property manager\n";
     processProperties = boost::make_shared<PropertyManager>();
     PropertyManagerDataService::Instance().addOrReplace(propertyManagerName,
                                                         processProperties);
@@ -396,14 +419,16 @@ boost::shared_ptr<PropertyManager> DataProcessorAlgorithm::getProcessProperties(
   return processProperties;
 }
 
+template <class Base>
 std::vector<std::string>
-DataProcessorAlgorithm::splitInput(const std::string &input) {
+GenericDataProcessorAlgorithm<Base>::splitInput(const std::string &input) {
   UNUSED_ARG(input);
   throw std::runtime_error(
       "DataProcessorAlgorithm::splitInput is not implemented");
 }
 
-void DataProcessorAlgorithm::forwardProperties() {
+template <class Base>
+void GenericDataProcessorAlgorithm<Base>::forwardProperties() {
   throw std::runtime_error(
       "DataProcessorAlgorithm::forwardProperties is not implemented");
 }
@@ -418,9 +443,10 @@ void DataProcessorAlgorithm::forwardProperties() {
  * @param rhs :: the workspace on the right hand side of the divide symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::divide(const MatrixWorkspace_sptr lhs,
-                               const MatrixWorkspace_sptr rhs) {
+GenericDataProcessorAlgorithm<Base>::divide(const MatrixWorkspace_sptr lhs,
+                                            const MatrixWorkspace_sptr rhs) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Divide", lhs, rhs);
@@ -432,9 +458,10 @@ DataProcessorAlgorithm::divide(const MatrixWorkspace_sptr lhs,
  * @param rhsValue :: the value on the right hand side of the divide symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::divide(const MatrixWorkspace_sptr lhs,
-                               const double &rhsValue) {
+GenericDataProcessorAlgorithm<Base>::divide(const MatrixWorkspace_sptr lhs,
+                                            const double &rhsValue) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Divide", lhs, createWorkspaceSingleValue(rhsValue));
@@ -448,9 +475,10 @@ DataProcessorAlgorithm::divide(const MatrixWorkspace_sptr lhs,
  * symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::multiply(const MatrixWorkspace_sptr lhs,
-                                 const MatrixWorkspace_sptr rhs) {
+GenericDataProcessorAlgorithm<Base>::multiply(const MatrixWorkspace_sptr lhs,
+                                              const MatrixWorkspace_sptr rhs) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Divide", lhs, rhs);
@@ -464,9 +492,10 @@ DataProcessorAlgorithm::multiply(const MatrixWorkspace_sptr lhs,
  * symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::multiply(const MatrixWorkspace_sptr lhs,
-                                 const double &rhsValue) {
+GenericDataProcessorAlgorithm<Base>::multiply(const MatrixWorkspace_sptr lhs,
+                                              const double &rhsValue) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Multiply", lhs, createWorkspaceSingleValue(rhsValue));
@@ -478,9 +507,10 @@ DataProcessorAlgorithm::multiply(const MatrixWorkspace_sptr lhs,
  * @param rhs :: the workspace on the right hand side of the addition symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::plus(const MatrixWorkspace_sptr lhs,
-                             const MatrixWorkspace_sptr rhs) {
+GenericDataProcessorAlgorithm<Base>::plus(const MatrixWorkspace_sptr lhs,
+                                          const MatrixWorkspace_sptr rhs) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Plus", lhs, rhs);
@@ -492,9 +522,10 @@ DataProcessorAlgorithm::plus(const MatrixWorkspace_sptr lhs,
  * @param rhsValue :: the value on the right hand side of the addition symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::plus(const MatrixWorkspace_sptr lhs,
-                             const double &rhsValue) {
+GenericDataProcessorAlgorithm<Base>::plus(const MatrixWorkspace_sptr lhs,
+                                          const double &rhsValue) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Plus", lhs, createWorkspaceSingleValue(rhsValue));
@@ -506,9 +537,10 @@ DataProcessorAlgorithm::plus(const MatrixWorkspace_sptr lhs,
  * @param rhs :: the workspace on the right hand side of the subtraction symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::minus(const MatrixWorkspace_sptr lhs,
-                              const MatrixWorkspace_sptr rhs) {
+GenericDataProcessorAlgorithm<Base>::minus(const MatrixWorkspace_sptr lhs,
+                                           const MatrixWorkspace_sptr rhs) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Minus", lhs, rhs);
@@ -521,9 +553,10 @@ DataProcessorAlgorithm::minus(const MatrixWorkspace_sptr lhs,
  * symbol
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::minus(const MatrixWorkspace_sptr lhs,
-                              const double &rhsValue) {
+GenericDataProcessorAlgorithm<Base>::minus(const MatrixWorkspace_sptr lhs,
+                                           const double &rhsValue) {
   return this->executeBinaryAlgorithm<
       MatrixWorkspace_sptr, MatrixWorkspace_sptr, MatrixWorkspace_sptr>(
       "Minus", lhs, createWorkspaceSingleValue(rhsValue));
@@ -534,8 +567,10 @@ DataProcessorAlgorithm::minus(const MatrixWorkspace_sptr lhs,
  * @param rhsValue :: the value to convert to a single value matrix workspace
  * @return matrix workspace resulting from the operation
  */
+template <class Base>
 MatrixWorkspace_sptr
-DataProcessorAlgorithm::createWorkspaceSingleValue(const double &rhsValue) {
+GenericDataProcessorAlgorithm<Base>::createWorkspaceSingleValue(
+    const double &rhsValue) {
   MatrixWorkspace_sptr retVal =
       WorkspaceFactory::Instance().create("WorkspaceSingleValue", 1, 1, 1);
   retVal->dataY(0)[0] = rhsValue;
@@ -543,5 +578,18 @@ DataProcessorAlgorithm::createWorkspaceSingleValue(const double &rhsValue) {
   return retVal;
 }
 
+template <typename T>
+void GenericDataProcessorAlgorithm<T>::visualStudioC4661Workaround() {}
+
+template class GenericDataProcessorAlgorithm<Algorithm>;
+template class MANTID_API_DLL GenericDataProcessorAlgorithm<SerialAlgorithm>;
+template class MANTID_API_DLL GenericDataProcessorAlgorithm<ParallelAlgorithm>;
+template class MANTID_API_DLL
+    GenericDataProcessorAlgorithm<DistributedAlgorithm>;
+
+template <>
+MANTID_API_DLL void
+GenericDataProcessorAlgorithm<Algorithm>::visualStudioC4661Workaround() {}
+
 } // namespace Mantid
 } // namespace API
diff --git a/Framework/API/src/DistributedAlgorithm.cpp b/Framework/API/src/DistributedAlgorithm.cpp
index a9c0703e463f1c51675b4eaa9ff69fd724a2bf1c..69688b55ac7d9264b477766bb02b35b2bcf62782 100644
--- a/Framework/API/src/DistributedAlgorithm.cpp
+++ b/Framework/API/src/DistributedAlgorithm.cpp
@@ -1,11 +1,33 @@
 #include "MantidAPI/DistributedAlgorithm.h"
 
+#include <algorithm>
+
 namespace Mantid {
 namespace API {
 
 Parallel::ExecutionMode DistributedAlgorithm::getParallelExecutionMode(
     const std::map<std::string, Parallel::StorageMode> &storageModes) const {
-  return Parallel::getCorrespondingExecutionMode(storageModes.begin()->second);
+  using namespace Parallel;
+  if (std::any_of(
+          storageModes.begin(), storageModes.end(),
+          [](const std::pair<std::string, Parallel::StorageMode> &item) {
+            return item.second == StorageMode::Distributed;
+          })) {
+    if (std::any_of(
+            storageModes.begin(), storageModes.end(),
+            [](const std::pair<std::string, Parallel::StorageMode> &item) {
+              return item.second == StorageMode::MasterOnly;
+            }))
+      return ExecutionMode::Invalid;
+    return ExecutionMode::Distributed;
+  }
+  if (std::any_of(
+          storageModes.begin(), storageModes.end(),
+          [](const std::pair<std::string, Parallel::StorageMode> &item) {
+            return item.second == StorageMode::MasterOnly;
+          }))
+    return ExecutionMode::MasterOnly;
+  return ExecutionMode::Identical;
 }
 
 } // namespace API
diff --git a/Framework/API/src/WorkspaceGroup.cpp b/Framework/API/src/WorkspaceGroup.cpp
index 1fe9323e23858b64485d49e2279951071149dde6..172d62382ad26aa4fa325c8a76ff3561f6141e50 100644
--- a/Framework/API/src/WorkspaceGroup.cpp
+++ b/Framework/API/src/WorkspaceGroup.cpp
@@ -15,8 +15,8 @@ size_t MAXIMUM_DEPTH = 100;
 Kernel::Logger g_log("WorkspaceGroup");
 }
 
-WorkspaceGroup::WorkspaceGroup()
-    : Workspace(),
+WorkspaceGroup::WorkspaceGroup(const Parallel::StorageMode storageMode)
+    : Workspace(storageMode),
       m_deleteObserver(*this, &WorkspaceGroup::workspaceDeleteHandle),
       m_beforeReplaceObserver(*this,
                               &WorkspaceGroup::workspaceBeforeReplaceHandle),
diff --git a/Framework/Algorithms/CMakeLists.txt b/Framework/Algorithms/CMakeLists.txt
index 8cffdf1cb499559851ec004d9e8ba9cadf15248b..39fcebb6676aaa9c84544e4d9c50bb80cbac5bc9 100644
--- a/Framework/Algorithms/CMakeLists.txt
+++ b/Framework/Algorithms/CMakeLists.txt
@@ -42,6 +42,7 @@ set ( SRC_FILES
 	src/ClearCache.cpp
 	src/ClearInstrumentParameters.cpp
 	src/ClearMaskFlag.cpp
+	src/ClearMaskedSpectra.cpp
 	src/CloneWorkspace.cpp
 	src/Comment.cpp
 	src/CommutativeBinaryOperation.cpp
@@ -176,6 +177,7 @@ set ( SRC_FILES
 	src/MaskBins.cpp
 	src/MaskBinsFromTable.cpp
 	src/MaskDetectorsIf.cpp
+	src/MaskInstrument.cpp
 	src/MatrixWorkspaceAccess.cpp
 	src/Max.cpp
 	src/MaxEnt.cpp
@@ -372,6 +374,7 @@ set ( INC_FILES
 	inc/MantidAlgorithms/ClearCache.h
 	inc/MantidAlgorithms/ClearInstrumentParameters.h
 	inc/MantidAlgorithms/ClearMaskFlag.h
+	inc/MantidAlgorithms/ClearMaskedSpectra.h
 	inc/MantidAlgorithms/CloneWorkspace.h
 	inc/MantidAlgorithms/Comment.h
 	inc/MantidAlgorithms/CommutativeBinaryOperation.h
@@ -507,6 +510,7 @@ set ( INC_FILES
 	inc/MantidAlgorithms/MaskBins.h
 	inc/MantidAlgorithms/MaskBinsFromTable.h
 	inc/MantidAlgorithms/MaskDetectorsIf.h
+	inc/MantidAlgorithms/MaskInstrument.h
 	inc/MantidAlgorithms/MatrixWorkspaceAccess.h
 	inc/MantidAlgorithms/Max.h
 	inc/MantidAlgorithms/MaxEnt.h
@@ -718,6 +722,7 @@ set ( TEST_FILES
 	ClearCacheTest.h
 	ClearInstrumentParametersTest.h
 	ClearMaskFlagTest.h
+	ClearMaskedSpectraTest.h
 	CloneWorkspaceTest.h
 	CommentTest.h
 	CommutativeBinaryOperationTest.h
@@ -849,6 +854,7 @@ set ( TEST_FILES
 	MaskBinsFromTableTest.h
 	MaskBinsTest.h
 	MaskDetectorsIfTest.h
+	MaskInstrumentTest.h
 	MaxEnt/MaxentCalculatorTest.h
 	MaxEnt/MaxentEntropyNegativeValuesTest.h
 	MaxEnt/MaxentEntropyPositiveValuesTest.h
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ClearMaskedSpectra.h b/Framework/Algorithms/inc/MantidAlgorithms/ClearMaskedSpectra.h
new file mode 100644
index 0000000000000000000000000000000000000000..fa27e65da2241cd37e2829ac752ddadb2cd43e28
--- /dev/null
+++ b/Framework/Algorithms/inc/MantidAlgorithms/ClearMaskedSpectra.h
@@ -0,0 +1,54 @@
+#ifndef MANTID_ALGORITHMS_CLEARMASKEDSPECTRA_H_
+#define MANTID_ALGORITHMS_CLEARMASKEDSPECTRA_H_
+
+#include "MantidAlgorithms/DllConfig.h"
+#include "MantidAPI/DistributedAlgorithm.h"
+
+namespace Mantid {
+namespace Algorithms {
+
+/** Clear counts (or events, if applicable) on all spectra that are fully
+  masked.  A spectrum is fully masked if all of its associated detectors are
+  masked, e.g., from a call to `MaskInstrument`.
+
+  @author Simon Heybrock
+  @date 2017
+
+  Copyright &copy; 2017 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge
+  National Laboratory & European Spallation Source
+
+  This file is part of Mantid.
+
+  Mantid is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 3 of the License, or
+  (at your option) any later version.
+
+  Mantid is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  File change history is stored at: <https://github.com/mantidproject/mantid>
+  Code Documentation is available at: <http://doxygen.mantidproject.org>
+*/
+class MANTID_ALGORITHMS_DLL ClearMaskedSpectra
+    : public API::DistributedAlgorithm {
+public:
+  const std::string name() const override;
+  int version() const override;
+  const std::string category() const override;
+  const std::string summary() const override;
+
+private:
+  void init() override;
+  void exec() override;
+};
+
+} // namespace Algorithms
+} // namespace Mantid
+
+#endif /* MANTID_ALGORITHMS_CLEARMASKEDSPECTRA_H_ */
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Comment.h b/Framework/Algorithms/inc/MantidAlgorithms/Comment.h
index 5d3db76cf3f7cab8e444c7d00d710a9bf4038405..6db408913c375d7497e58e41431b4180075ab000 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Comment.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Comment.h
@@ -2,7 +2,7 @@
 #define MANTID_ALGORITHMS_COMMENT_H_
 
 #include "MantidAlgorithms/DllConfig.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 namespace Mantid {
 namespace Algorithms {
 
@@ -29,7 +29,7 @@ namespace Algorithms {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport Comment : public API::Algorithm {
+class DLLExport Comment : public API::DistributedAlgorithm {
 public:
   const std::string name() const override;
   int version() const override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h b/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h
index daaf7336892038d73107117f7d3af41deb170039..88b4ec69a721b78b49cb1bca33d67ba1e1acd7f8 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h
@@ -91,6 +91,7 @@ protected:
   Parallel::ExecutionMode getParallelExecutionMode(
       const std::map<std::string, Parallel::StorageMode> &storageModes)
       const override;
+  void execMasterOnly() override;
 
 private:
   /// Initialise algorithm
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CopyInstrumentParameters.h b/Framework/Algorithms/inc/MantidAlgorithms/CopyInstrumentParameters.h
index 474dafe097435597ab160d355d12e344d64b1530..5740e3cae41ea0a185d546a855f46748172558e0 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/CopyInstrumentParameters.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/CopyInstrumentParameters.h
@@ -75,6 +75,11 @@ public:
   /// base target instrument (mainly used in testing)
   bool isInstrumentDifferent() const { return m_different_instrument_sp; }
 
+protected:
+  Parallel::ExecutionMode getParallelExecutionMode(
+      const std::map<std::string, Parallel::StorageMode> &storageModes)
+      const override;
+
 private:
   /// Initialisation code
   void init() override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/DeleteWorkspace.h b/Framework/Algorithms/inc/MantidAlgorithms/DeleteWorkspace.h
index 91890dbf8d5a8ad29a99e245cbc6ef3074a51815..045f2df92cbd500891068a2589dfe8a70fff2797 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/DeleteWorkspace.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/DeleteWorkspace.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHMS_DELETEWORKSPACE_H_
 #define MANTID_ALGORITHMS_DELETEWORKSPACE_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -37,7 +34,7 @@ namespace Algorithms {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport DeleteWorkspace : public API::Algorithm {
+class DLLExport DeleteWorkspace : public API::DistributedAlgorithm {
 public:
   /// Algorithm's name
   const std::string name() const override { return "DeleteWorkspace"; }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h b/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h
index 1b50e2ba57f8ba0146bbd4bdb543b14a132375c0..97a25bb314f68d4ae5767296cf0052384e957aff 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h
@@ -1,7 +1,7 @@
 #ifndef MANTID_ALGORITHM_GROUP_H_
 #define MANTID_ALGORITHM_GROUP_H_
 
-#include "MantidAPI/DistributedAlgorithm.h"
+#include "MantidAPI/Algorithm.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidAPI/WorkspaceProperty.h"
 #include "MantidAPI/WorkspaceGroup_fwd.h"
@@ -38,7 +38,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 File change history is stored at: <https://github.com/mantidproject/mantid>
 Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
-class DLLExport GroupWorkspaces : public API::DistributedAlgorithm {
+class DLLExport GroupWorkspaces : public API::Algorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "GroupWorkspaces"; }
@@ -54,6 +54,11 @@ public:
     return "Transforms\\Grouping;Utility\\Workspaces";
   }
 
+protected:
+  Parallel::ExecutionMode getParallelExecutionMode(
+      const std::map<std::string, Parallel::StorageMode> &storageModes)
+      const override;
+
 private:
   /// Overridden Init method
   void init() override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/MaskInstrument.h b/Framework/Algorithms/inc/MantidAlgorithms/MaskInstrument.h
new file mode 100644
index 0000000000000000000000000000000000000000..ef6837232280d79e99b2ce84335c6890cc1aecdb
--- /dev/null
+++ b/Framework/Algorithms/inc/MantidAlgorithms/MaskInstrument.h
@@ -0,0 +1,53 @@
+#ifndef MANTID_ALGORITHMS_MASKINSTRUMENT_H_
+#define MANTID_ALGORITHMS_MASKINSTRUMENT_H_
+
+#include "MantidAlgorithms/DllConfig.h"
+#include "MantidAPI/DistributedAlgorithm.h"
+
+namespace Mantid {
+namespace Algorithms {
+
+/** Mask specified detectors in an instrument. This is does NOT clear the data
+  in associated spectra in the workspace.  To clear the data manually
+  `ClearMaskedSpectra` can be called.
+
+  @author Simon Heybrock
+  @date 2017
+
+  Copyright &copy; 2017 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge
+  National Laboratory & European Spallation Source
+
+  This file is part of Mantid.
+
+  Mantid is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 3 of the License, or
+  (at your option) any later version.
+
+  Mantid is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  File change history is stored at: <https://github.com/mantidproject/mantid>
+  Code Documentation is available at: <http://doxygen.mantidproject.org>
+*/
+class MANTID_ALGORITHMS_DLL MaskInstrument : public API::DistributedAlgorithm {
+public:
+  const std::string name() const override;
+  int version() const override;
+  const std::string category() const override;
+  const std::string summary() const override;
+
+private:
+  void init() override;
+  void exec() override;
+};
+
+} // namespace Algorithms
+} // namespace Mantid
+
+#endif /* MANTID_ALGORITHMS_MASKINSTRUMENT_H_ */
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Q1D2.h b/Framework/Algorithms/inc/MantidAlgorithms/Q1D2.h
index 06e40f7c178e16d8bc324137eb5b1e5b589c24fc..e0779024764f9147a15978c86755ba7033597e43 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Q1D2.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Q1D2.h
@@ -55,6 +55,11 @@ public:
   /// Algorithm's category for identification
   const std::string category() const override { return "SANS"; }
 
+protected:
+  Parallel::ExecutionMode getParallelExecutionMode(
+      const std::map<std::string, Parallel::StorageMode> &storageModes)
+      const override;
+
 private:
   /// the experimental workspace with counts across the detector
   API::MatrixWorkspace_const_sptr m_dataWS;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Scale.h b/Framework/Algorithms/inc/MantidAlgorithms/Scale.h
index 6ba8290a7106ca0c1bea18fd137f78d879c5639b..5c451b14fcb7b6895775c1e3e2c7f039f50c2ce8 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/Scale.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/Scale.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHMS_SCALE_H_
 #define MANTID_ALGORITHMS_SCALE_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -46,7 +43,7 @@ namespace Algorithms {
     File change history is stored at: <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport Scale : public API::Algorithm {
+class DLLExport Scale : public API::DistributedAlgorithm {
 public:
   /// Algorithm's name
   const std::string name() const override { return "Scale"; }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SumOverlappingTubes.h b/Framework/Algorithms/inc/MantidAlgorithms/SumOverlappingTubes.h
index 8cf9f908ce2c2425966d63558836f084e79544f3..17115c98e2e2f0116e00c41b3985554f5fc5b81a 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/SumOverlappingTubes.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/SumOverlappingTubes.h
@@ -44,7 +44,6 @@ public:
            "scattering angle. Detector scans with overlapping tubes are "
            "supported.";
   }
-  std::map<std::string, std::string> validateInputs() override;
   int version() const override { return 1; }
 
 private:
@@ -67,7 +66,7 @@ private:
 
   void getInputParameters();
   void getScatteringAngleBinning();
-  void getHeightAxis();
+  void getHeightAxis(const std::string &componentName);
   std::vector<std::vector<double>>
   performBinning(API::MatrixWorkspace_sptr &outputWS);
 
diff --git a/Framework/Algorithms/src/ClearMaskedSpectra.cpp b/Framework/Algorithms/src/ClearMaskedSpectra.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0815099a4ff6a863a4156712a75d32e36ad9eb96
--- /dev/null
+++ b/Framework/Algorithms/src/ClearMaskedSpectra.cpp
@@ -0,0 +1,60 @@
+#include "MantidDataObjects/EventWorkspace.h"
+#include "MantidAlgorithms/ClearMaskedSpectra.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/SpectrumInfo.h"
+
+using namespace Mantid::Kernel;
+using namespace Mantid::API;
+
+namespace Mantid {
+namespace Algorithms {
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(ClearMaskedSpectra)
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string ClearMaskedSpectra::name() const {
+  return "ClearMaskedSpectra";
+}
+
+/// Algorithm's version for identification. @see Algorithm::version
+int ClearMaskedSpectra::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string ClearMaskedSpectra::category() const {
+  return "Transforms\\Masking";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string ClearMaskedSpectra::summary() const {
+  return "Clear counts and/or events in all fully masked spectra.";
+}
+
+void ClearMaskedSpectra::init() {
+  declareProperty(Kernel::make_unique<WorkspaceProperty<>>(
+      "InputWorkspace", "The input workspace", Direction::Input));
+  declareProperty(
+      Kernel::make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
+                                               Direction::Output),
+      "Name of the output workspace (can be same as InputWorkspace)");
+}
+
+void ClearMaskedSpectra::exec() {
+  MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
+  MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
+  if (outputWS != inputWS) {
+    outputWS = inputWS->clone();
+    setProperty("OutputWorkspace", outputWS);
+  }
+
+  const auto &spectrumInfo = outputWS->spectrumInfo();
+  for (size_t i = 0; i < spectrumInfo.size(); ++i)
+    if (spectrumInfo.hasDetectors(i) && spectrumInfo.isMasked(i))
+      outputWS->getSpectrum(i).clearData();
+
+  if (auto event = dynamic_cast<DataObjects::EventWorkspace *>(outputWS.get()))
+    event->clearMRU();
+}
+
+} // namespace Algorithms
+} // namespace Mantid
diff --git a/Framework/Algorithms/src/CompareWorkspaces.cpp b/Framework/Algorithms/src/CompareWorkspaces.cpp
index f2c93adde18bbdcbc656edbe271cb31e0064f776..5724a852f34bbdbd698a9f73e863f23059a8ed7f 100644
--- a/Framework/Algorithms/src/CompareWorkspaces.cpp
+++ b/Framework/Algorithms/src/CompareWorkspaces.cpp
@@ -15,6 +15,7 @@
 #include "MantidDataObjects/TableWorkspace.h"
 #include "MantidGeometry/Crystal/IPeak.h"
 #include "MantidKernel/Unit.h"
+#include "MantidParallel/Communicator.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -1196,5 +1197,12 @@ Parallel::ExecutionMode CompareWorkspaces::getParallelExecutionMode(
   return ExecutionMode::Invalid;
 }
 
+void CompareWorkspaces::execMasterOnly() {
+  if (communicator().rank() == 0)
+    exec();
+  else
+    setProperty("Result", true);
+}
+
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/CopyInstrumentParameters.cpp b/Framework/Algorithms/src/CopyInstrumentParameters.cpp
index 132f59f235eeac250883fd767e48ad467dbd60ce..5ed2d53818cf6a85e35278caf4a2ffad8d5d55c7 100644
--- a/Framework/Algorithms/src/CopyInstrumentParameters.cpp
+++ b/Framework/Algorithms/src/CopyInstrumentParameters.cpp
@@ -135,5 +135,16 @@ void CopyInstrumentParameters::checkProperties() {
   }
 }
 
+Parallel::ExecutionMode CopyInstrumentParameters::getParallelExecutionMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes) const {
+  const auto in = storageModes.at("InputWorkspace");
+  const auto out = storageModes.at("InputWorkspace");
+  // Source instrument avaible only on master rank, so copying not possible if
+  // target requires it on non-master ranks.
+  if (in == Parallel::StorageMode::MasterOnly && in != out)
+    return Parallel::ExecutionMode::Invalid;
+  return Parallel::getCorrespondingExecutionMode(out);
+}
+
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/GroupWorkspaces.cpp b/Framework/Algorithms/src/GroupWorkspaces.cpp
index ed871ee002469922bdba86cbc9aeac3ac3ad8a3d..6d6f839880f4223a57c6e767e1c905a755f39781 100644
--- a/Framework/Algorithms/src/GroupWorkspaces.cpp
+++ b/Framework/Algorithms/src/GroupWorkspaces.cpp
@@ -3,6 +3,7 @@
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidKernel/ArrayProperty.h"
+#include "MantidParallel/Communicator.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -31,7 +32,8 @@ void GroupWorkspaces::exec() {
   const std::vector<std::string> inputWorkspaces =
       getProperty("InputWorkspaces");
 
-  m_group = boost::make_shared<WorkspaceGroup>();
+  // Clear WorkspaceGroup in case algorithm instance is reused.
+  m_group = nullptr;
   addToGroup(inputWorkspaces);
 
   setProperty("OutputWorkspace", m_group);
@@ -63,8 +65,22 @@ void GroupWorkspaces::addToGroup(const API::Workspace_sptr &workspace) {
     // Remove the group from the ADS
     AnalysisDataService::Instance().remove(workspace->getName());
   } else {
+    if (!m_group)
+      m_group = boost::make_shared<WorkspaceGroup>(workspace->storageMode());
+    else if (communicator().size() != 1 &&
+             m_group->storageMode() != workspace->storageMode())
+      throw std::runtime_error(
+          "WorkspaceGroup with mixed Parallel::Storage mode is not supported.");
     m_group->addWorkspace(workspace);
   }
 }
+
+Parallel::ExecutionMode GroupWorkspaces::getParallelExecutionMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes) const {
+  static_cast<void>(storageModes);
+  const std::vector<std::string> names = getProperty("InputWorkspaces");
+  const auto ws = AnalysisDataService::Instance().retrieve(names.front());
+  return Parallel::getCorrespondingExecutionMode(ws->storageMode());
+}
 }
 }
diff --git a/Framework/Algorithms/src/MaskInstrument.cpp b/Framework/Algorithms/src/MaskInstrument.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3cc1f6328eddd0eb65487401ee62f615c19bc347
--- /dev/null
+++ b/Framework/Algorithms/src/MaskInstrument.cpp
@@ -0,0 +1,58 @@
+#include "MantidAlgorithms/MaskInstrument.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidKernel/ArrayProperty.h"
+
+using namespace Mantid::Kernel;
+using namespace Mantid::API;
+
+namespace Mantid {
+namespace Algorithms {
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(MaskInstrument)
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string MaskInstrument::name() const { return "MaskInstrument"; }
+
+/// Algorithm's version for identification. @see Algorithm::version
+int MaskInstrument::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string MaskInstrument::category() const {
+  return "Transforms\\Masking";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string MaskInstrument::summary() const {
+  return "Mask detectors in the instrument WITHOUT clearing data in associated "
+         "spectra.";
+}
+
+void MaskInstrument::init() {
+  declareProperty(Kernel::make_unique<WorkspaceProperty<>>(
+      "InputWorkspace", "The input workspace", Direction::Input));
+  declareProperty(
+      Kernel::make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
+                                               Direction::Output),
+      "Name of the output workspace (can be same as InputWorkspace)");
+  declareProperty(make_unique<ArrayProperty<detid_t>>("DetectorIDs"),
+                  "List of detector IDs to mask");
+}
+
+void MaskInstrument::exec() {
+  MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
+  MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
+  if (outputWS != inputWS) {
+    outputWS = inputWS->clone();
+    setProperty("OutputWorkspace", outputWS);
+  }
+
+  const std::vector<detid_t> detectorIds = getProperty("DetectorIDs");
+  auto &detectorInfo = outputWS->mutableDetectorInfo();
+  for (const auto &id : detectorIds)
+    detectorInfo.setMasked(detectorInfo.indexOf(id), true);
+}
+
+} // namespace Algorithms
+} // namespace Mantid
diff --git a/Framework/Algorithms/src/Q1D2.cpp b/Framework/Algorithms/src/Q1D2.cpp
index 66a569e126fcfedb5d1ed92f9cda483fffb6535d..92c5634276e403003c7ec9e949d78a239c136830 100644
--- a/Framework/Algorithms/src/Q1D2.cpp
+++ b/Framework/Algorithms/src/Q1D2.cpp
@@ -11,6 +11,8 @@
 #include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidDataObjects/Histogram1D.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
@@ -18,6 +20,9 @@
 #include "MantidKernel/RebinParamsValidator.h"
 #include "MantidKernel/UnitFactory.h"
 #include "MantidKernel/VectorHelper.h"
+#include "MantidIndexing/IndexInfo.h"
+#include "MantidParallel/Communicator.h"
+#include "MantidTypes/SpectrumDefinition.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -28,6 +33,7 @@ DECLARE_ALGORITHM(Q1D2)
 using namespace Kernel;
 using namespace API;
 using namespace Geometry;
+using namespace DataObjects;
 
 Q1D2::Q1D2() : API::Algorithm(), m_dataWS(), m_doSolidAngle(false) {}
 
@@ -263,6 +269,40 @@ void Q1D2::exec() {
   }
   PARALLEL_CHECK_INTERUPT_REGION
 
+  if (communicator().size() > 1) {
+    int tag = 0;
+    auto size = static_cast<int>(YOut.size());
+    if (communicator().rank() == 0) {
+      for (int rank = 1; rank < communicator().size(); ++rank) {
+        HistogramData::HistogramY y(YOut.size());
+        HistogramData::HistogramE e2(YOut.size());
+        communicator().recv(rank, tag, &y[0], size);
+        YOut += y;
+        communicator().recv(rank, tag, &e2[0], size);
+        EOutTo2 += e2;
+        communicator().recv(rank, tag, &y[0], size);
+        normSum += y;
+        communicator().recv(rank, tag, &e2[0], size);
+        normError2 += e2;
+        int detCount;
+        communicator().recv(rank, tag, detCount);
+        std::vector<detid_t> detIds(detCount);
+        communicator().recv(rank, tag, detIds.data(), detCount);
+        outputWS->getSpectrum(0).addDetectorIDs(detIds);
+      }
+    } else {
+      communicator().send(0, tag, YOut.rawData().data(), size);
+      communicator().send(0, tag, EOutTo2.rawData().data(), size);
+      communicator().send(0, tag, normSum.rawData().data(), size);
+      communicator().send(0, tag, normError2.rawData().data(), size);
+      const auto detIdSet = outputWS->getSpectrum(0).getDetectorIDs();
+      std::vector<detid_t> detIds(detIdSet.begin(), detIdSet.end());
+      auto size = static_cast<int>(detIds.size());
+      communicator().send(0, tag, size);
+      communicator().send(0, tag, detIds.data(), size);
+    }
+  }
+
   if (useQResolution) {
     // The number of Q (x)_ values is N, while the number of DeltaQ values is
     // N-1,
@@ -281,7 +321,7 @@ void Q1D2::exec() {
   }
 
   bool doOutputParts = getProperty("OutputParts");
-  if (doOutputParts) {
+  if (doOutputParts && communicator().rank() == 0) {
     MatrixWorkspace_sptr ws_sumOfCounts =
         WorkspaceFactory::Instance().create(outputWS);
     ws_sumOfCounts->setSharedX(0, outputWS->sharedX(0));
@@ -298,13 +338,17 @@ void Q1D2::exec() {
     ws_sumOfNormFactors->setFrequencyVariances(0, normError2);
 
     helper.outputParts(this, ws_sumOfCounts, ws_sumOfNormFactors);
+  } else if (doOutputParts) {
+    helper.outputParts(this, nullptr, nullptr);
   }
 
   progress.report("Normalizing I(Q)");
   // finally divide the number of counts in each output Q bin by its weighting
   normalize(normSum, normError2, YOut, EOutTo2);
 
-  setProperty("OutputWorkspace", outputWS);
+  if (communicator().rank() == 0) {
+    setProperty("OutputWorkspace", outputWS);
+  }
 }
 
 /** Creates the output workspace, its size, units, etc.
@@ -316,24 +360,26 @@ API::MatrixWorkspace_sptr
 Q1D2::setUpOutputWorkspace(const std::vector<double> &binParams) const {
   // Calculate the output binning
   HistogramData::BinEdges XOut(0);
-  size_t sizeOut = static_cast<size_t>(VectorHelper::createAxisFromRebinParams(
+  static_cast<void>(VectorHelper::createAxisFromRebinParams(
       binParams, XOut.mutableRawData()));
 
-  // Now create the output workspace
-  MatrixWorkspace_sptr outputWS =
-      WorkspaceFactory::Instance().create(m_dataWS, 1, sizeOut, sizeOut - 1);
+  // Create output workspace. On all but rank 0 this is a temporary workspace.
+  Indexing::IndexInfo indexInfo(1, communicator().rank() == 0
+                                       ? Parallel::StorageMode::MasterOnly
+                                       : Parallel::StorageMode::Cloned,
+                                communicator());
+  indexInfo.setSpectrumDefinitions(std::vector<SpectrumDefinition>(1));
+  auto outputWS = create<MatrixWorkspace>(*m_dataWS, indexInfo, XOut);
   outputWS->getAxis(0)->unit() =
       UnitFactory::Instance().create("MomentumTransfer");
   outputWS->setYUnitLabel("1/cm");
 
-  // Set the X vector for the output workspace
-  outputWS->setBinEdges(0, XOut);
   outputWS->setDistribution(true);
 
   outputWS->getSpectrum(0).clearDetectorIDs();
   outputWS->getSpectrum(0).setSpectrumNo(1);
 
-  return outputWS;
+  return std::move(outputWS);
 }
 
 /** Calculate the normalization term for each output bin
@@ -664,5 +710,28 @@ void Q1D2::normalize(const HistogramData::HistogramY &normSum,
   }
 }
 
+namespace {
+void checkStorageMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes,
+    const std::string &name) {
+  if (storageModes.count(name) &&
+      storageModes.at(name) != Parallel::StorageMode::Cloned)
+    throw std::runtime_error(name + " must have " +
+                             Parallel::toString(Parallel::StorageMode::Cloned));
+}
+}
+
+Parallel::ExecutionMode Q1D2::getParallelExecutionMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes) const {
+  if (storageModes.count("PixelAdj") || storageModes.count("WavePixelAdj") ||
+      storageModes.count("QResolution"))
+    throw std::runtime_error(
+        "Using in PixelAdj, WavePixelAdj, or QResolution in an MPI run of " +
+        name() + " is currently not supported.");
+  checkStorageMode(storageModes, "WavelengthAdj");
+  return Parallel::getCorrespondingExecutionMode(
+      storageModes.at("DetBankWorkspace"));
+}
+
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/Scale.cpp b/Framework/Algorithms/src/Scale.cpp
index 3c0a067f74076dd9e26038f97618118f9a738f1d..d5257ab24d3653e124db075f34034524a5be5ee1 100644
--- a/Framework/Algorithms/src/Scale.cpp
+++ b/Framework/Algorithms/src/Scale.cpp
@@ -1,6 +1,3 @@
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/Scale.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidKernel/ListValidator.h"
diff --git a/Framework/Algorithms/src/SumOverlappingTubes.cpp b/Framework/Algorithms/src/SumOverlappingTubes.cpp
index e566054dad80f34e98aa869869d013688080c872..d19efba00557d0ebec57a217d5f619205e5cccda 100644
--- a/Framework/Algorithms/src/SumOverlappingTubes.cpp
+++ b/Framework/Algorithms/src/SumOverlappingTubes.cpp
@@ -40,11 +40,11 @@ void SumOverlappingTubes::init() {
   declareProperty(make_unique<WorkspaceProperty<MatrixWorkspace>>(
                       "OutputWorkspace", "", Direction::Output),
                   "Name of the output workspace.");
-  std::vector<std::string> outputTypes{"2D", "2DStraight", "1DStraight"};
+  std::vector<std::string> outputTypes{"2DTubes", "2D", "1D"};
   declareProperty("OutputType", "2D",
                   boost::make_shared<StringListValidator>(outputTypes),
-                  "Whether to have the output in 2D, 2D with straightened "
-                  "Debye-Scherrer cones, or 1D.");
+                  "Whether to have the output in raw 2D, with no "
+                  "Debye-Scherrer cone correction, 2D or 1D.");
   declareProperty(
       make_unique<ArrayProperty<double>>(
           "ScatteringAngleBinning", "0.05",
@@ -58,12 +58,6 @@ void SumOverlappingTubes::init() {
       make_unique<PropertyWithValue<bool>>("CropNegativeScatteringAngles",
                                            false, Direction::Input),
       "If true the negative scattering angles are cropped (ignored).");
-  declareProperty(make_unique<PropertyWithValue<std::string>>(
-                      "ComponentForHeightAxis", "tube_1", Direction::Input),
-                  "The name of the component to use for the height axis, that "
-                  "is the name of a PSD tube to be used. If specifying this "
-                  "then there is no need to give a value for the HeightBinning "
-                  "option.");
   declareProperty(
       make_unique<ArrayProperty<double>>(
           "HeightAxis", boost::make_shared<RebinParamsValidator>(true, true)),
@@ -71,8 +65,7 @@ void SumOverlappingTubes::init() {
       "the final y value. This can also be a single number, which "
       "is the y value step size. In this case, the boundary of binning will "
       "be determined by minimum and maximum y values present in the "
-      "workspaces. For the 1DStraight case only this can also be two numbers, "
-      "to give the range desired.");
+      "workspaces. This can also be two numbers to give the range desired.");
   declareProperty(
       make_unique<PropertyWithValue<bool>>("Normalise", true, Direction::Input),
       "If true normalise to the number of entries added for a particular "
@@ -87,24 +80,6 @@ void SumOverlappingTubes::init() {
                   "counts are split.");
 }
 
-std::map<std::string, std::string> SumOverlappingTubes::validateInputs() {
-  std::map<std::string, std::string> result;
-
-  const std::string componentForHeightAxis =
-      getProperty("ComponentForHeightAxis");
-  const std::string heightAxis = getProperty("HeightAxis");
-
-  if (componentForHeightAxis.empty() && heightAxis.empty()) {
-    std::string message =
-        "Either a component, such as a tube, must be specified "
-        "to get the height axis, or the binning given explicitly.";
-    result["ComponentForHeightAxis"] = message;
-    result["HeightBinning"] = message;
-  }
-
-  return result;
-}
-
 void SumOverlappingTubes::exec() {
   getInputParameters();
 
@@ -155,20 +130,25 @@ void SumOverlappingTubes::getInputParameters() {
   m_workspaceList = combHelper.validateInputWorkspaces(workspaces, g_log);
 
   m_outputType = getPropertyValue("OutputType");
+  const auto &instrument = m_workspaceList.front()->getInstrument();
 
   // For D2B at the ILL the detectors are flipped when comparing with other
   // powder diffraction instruments such as D20. It is still desired to show
   // angles as positive however, so here we check if we need to multiple angle
   // calculations by -1.
   m_mirrorDetectors = 1;
-  auto mirrorDetectors =
-      m_workspaceList.front()->getInstrument()->getBoolParameter(
-          "mirror_detector_angles");
+  auto mirrorDetectors = instrument->getBoolParameter("mirror_detector_angles");
   if (!mirrorDetectors.empty() && mirrorDetectors[0])
     m_mirrorDetectors = -1;
 
+  std::string componentName = "";
+  auto componentNameParam =
+      instrument->getStringParameter("detector_for_height_axis");
+  if (!componentNameParam.empty())
+    componentName = componentNameParam[0];
+
   getScatteringAngleBinning();
-  getHeightAxis();
+  getHeightAxis(componentName);
 }
 
 void SumOverlappingTubes::getScatteringAngleBinning() {
@@ -222,10 +202,14 @@ void SumOverlappingTubes::getScatteringAngleBinning() {
                       << m_endScatteringAngle << "\n";
 }
 
-void SumOverlappingTubes::getHeightAxis() {
-  const std::string componentName = getProperty("ComponentForHeightAxis");
+void SumOverlappingTubes::getHeightAxis(const std::string &componentName) {
   std::vector<double> heightBinning = getProperty("HeightAxis");
-  if (componentName.length() > 0 && heightBinning.empty()) {
+  if (componentName.length() == 0 && heightBinning.empty())
+    throw std::runtime_error("No detector_for_height_axis parameter for this "
+                             "instrument. Please enter a value for the "
+                             "HeightAxis parameter.");
+  if ((componentName.length() > 0 && heightBinning.empty()) ||
+      (m_outputType != "1D" && heightBinning.size() == 2)) {
     // Try to get the component. It should be a tube with pixels in the
     // y-direction, the height bins are then taken as the detector positions.
     const auto &ws = m_workspaceList.front();
@@ -237,17 +221,22 @@ void SumOverlappingTubes::getHeightAxis() {
     const auto &compAss = dynamic_cast<const ICompAssembly &>(*comp);
     std::vector<IComponent_const_sptr> children;
     compAss.getChildren(children, false);
-    for (const auto &thing : children)
-      m_heightAxis.push_back(thing->getPos().Y());
+    for (const auto &child : children) {
+      const auto posY = child->getPos().Y();
+      if (heightBinning.size() == 2 &&
+          (posY < heightBinning[0] || posY > heightBinning[1]))
+        continue;
+      m_heightAxis.push_back(child->getPos().Y());
+    }
   } else {
     if (heightBinning.size() != 3) {
-      if (heightBinning.size() == 2 && m_outputType == "1DStraight") {
+      if (heightBinning.size() == 2 && m_outputType == "1D") {
         m_heightAxis.push_back(heightBinning[0]);
         m_heightAxis.push_back(heightBinning[1]);
       } else
         throw std::runtime_error("Height binning must have start, step and end "
-                                 "values (except for 1DStraight option).");
-    } else if (m_outputType == "1DStraight") {
+                                 "values (except for 1D option).");
+    } else if (m_outputType == "1D") {
       m_heightAxis.push_back(heightBinning[0]);
       m_heightAxis.push_back(heightBinning[2]);
     } else {
@@ -262,7 +251,7 @@ void SumOverlappingTubes::getHeightAxis() {
   m_startHeight = *min_element(m_heightAxis.begin(), m_heightAxis.end());
   m_endHeight = *max_element(m_heightAxis.begin(), m_heightAxis.end());
 
-  if (m_outputType == "1DStraight")
+  if (m_outputType == "1D")
     m_heightAxis = {(m_heightAxis.front() + m_heightAxis.back()) * 0.5};
 
   m_numHistograms = m_heightAxis.size();
@@ -307,7 +296,7 @@ SumOverlappingTubes::performBinning(MatrixWorkspace_sptr &outputWS) {
       }
 
       double angle;
-      if (m_outputType == "2D")
+      if (m_outputType == "2DTubes")
         angle = atan2(pos.X(), pos.Z());
       else
         angle = specInfo.signedTwoTheta(i);
diff --git a/Framework/Algorithms/test/ClearMaskedSpectraTest.h b/Framework/Algorithms/test/ClearMaskedSpectraTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..23c394fb8411b24812dceea253cc514ed3d73eae
--- /dev/null
+++ b/Framework/Algorithms/test/ClearMaskedSpectraTest.h
@@ -0,0 +1,134 @@
+#ifndef MANTID_ALGORITHMS_CLEARMASKEDSPECTRATEST_H_
+#define MANTID_ALGORITHMS_CLEARMASKEDSPECTRATEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAlgorithms/ClearMaskedSpectra.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+
+#include "MantidTestHelpers/InstrumentCreationHelper.h"
+
+using namespace Mantid;
+using namespace Algorithms;
+using namespace API;
+using namespace DataObjects;
+using namespace HistogramData;
+
+namespace {
+MatrixWorkspace_sptr makeWorkspace() {
+  MatrixWorkspace_sptr ws =
+      create<Workspace2D>(4, Histogram(Points(1), Counts{1.2}));
+  InstrumentCreationHelper::addFullInstrumentToWorkspace(*ws, false, false, "");
+  return ws;
+}
+
+MatrixWorkspace_sptr run(const MatrixWorkspace_sptr &ws) {
+  ClearMaskedSpectra alg;
+  alg.setChild(true);
+  alg.initialize();
+  alg.setProperty("InputWorkspace", ws);
+  alg.setPropertyValue("OutputWorkspace", "dummy");
+  alg.execute();
+  return alg.getProperty("OutputWorkspace");
+}
+
+MatrixWorkspace_sptr runInplace(const MatrixWorkspace_sptr &ws) {
+  ClearMaskedSpectra alg;
+  alg.setChild(true);
+  alg.initialize();
+  alg.setProperty("InputWorkspace", ws);
+  alg.setPropertyValue("OutputWorkspace", "dummy");
+  alg.setProperty("OutputWorkspace", ws);
+  alg.execute();
+  return ws;
+}
+}
+
+class ClearMaskedSpectraTest : 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 ClearMaskedSpectraTest *createSuite() {
+    return new ClearMaskedSpectraTest();
+  }
+  static void destroySuite(ClearMaskedSpectraTest *suite) { delete suite; }
+
+  void test_no_instrument_leaves_data_unchanged() {
+    MatrixWorkspace_sptr ws =
+        create<Workspace2D>(4, Histogram(Points(1), Counts{1.2}));
+    ClearMaskedSpectra alg;
+    alg.initialize();
+    alg.setProperty("InputWorkspace", ws);
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", "_dummy_for_inplace"));
+    alg.setProperty("OutputWorkspace", ws);
+    alg.execute();
+    TS_ASSERT(alg.isExecuted());
+    TS_ASSERT_EQUALS(ws->y(0)[0], 1.2);
+    TS_ASSERT_EQUALS(ws->y(1)[0], 1.2);
+    TS_ASSERT_EQUALS(ws->y(2)[0], 1.2);
+    TS_ASSERT_EQUALS(ws->y(3)[0], 1.2);
+  }
+
+  void test_no_masking() {
+    auto in = makeWorkspace();
+    auto out = run(in);
+    TS_ASSERT_DIFFERS(in, out);
+    TS_ASSERT_EQUALS(out->y(0)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(1)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(2)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(3)[0], 1.2);
+  }
+
+  void test_no_masking_inplace() {
+    auto in = makeWorkspace();
+    auto out = runInplace(in);
+    TS_ASSERT_EQUALS(in, out);
+    TS_ASSERT_EQUALS(out->y(0)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(1)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(2)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(3)[0], 1.2);
+  }
+
+  void test_masking() {
+    auto in = makeWorkspace();
+    auto &detInfo = in->mutableDetectorInfo();
+    detInfo.setMasked(1, true);
+    auto out = run(in);
+    TS_ASSERT_DIFFERS(in, out);
+    TS_ASSERT_EQUALS(out->y(0)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(1)[0], 0.0);
+    TS_ASSERT_EQUALS(out->y(2)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(3)[0], 1.2);
+  }
+
+  void test_masking_inplace() {
+    auto in = makeWorkspace();
+    auto &detInfo = in->mutableDetectorInfo();
+    detInfo.setMasked(1, true);
+    auto out = runInplace(in);
+    TS_ASSERT_EQUALS(in, out);
+    TS_ASSERT_EQUALS(out->y(0)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(1)[0], 0.0);
+    TS_ASSERT_EQUALS(out->y(2)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(3)[0], 1.2);
+  }
+
+  void test_does_not_clear_partially_masked() {
+    auto in = makeWorkspace();
+    in->getSpectrum(1).addDetectorID(3);
+    auto &detInfo = in->mutableDetectorInfo();
+    detInfo.setMasked(1, true);
+    auto out = run(in);
+    TS_ASSERT_DIFFERS(in, out);
+    TS_ASSERT_EQUALS(out->y(0)[0], 1.2);
+    // Only one of the associated detector IDs is masked, data preserved.
+    TS_ASSERT_EQUALS(out->y(1)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(2)[0], 1.2);
+    TS_ASSERT_EQUALS(out->y(3)[0], 1.2);
+  }
+};
+
+#endif /* MANTID_ALGORITHMS_CLEARMASKEDSPECTRATEST_H_ */
diff --git a/Framework/Algorithms/test/MaskInstrumentTest.h b/Framework/Algorithms/test/MaskInstrumentTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..8715bcf8dda90560de1db46349fb5e74f07bf018
--- /dev/null
+++ b/Framework/Algorithms/test/MaskInstrumentTest.h
@@ -0,0 +1,121 @@
+#ifndef MANTID_ALGORITHMS_MASKINSTRUMENTTEST_H_
+#define MANTID_ALGORITHMS_MASKINSTRUMENTTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAlgorithms/MaskInstrument.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+
+#include "MantidTestHelpers/InstrumentCreationHelper.h"
+
+using namespace Mantid;
+using namespace Algorithms;
+using namespace API;
+using namespace DataObjects;
+
+namespace {
+MatrixWorkspace_sptr makeWorkspace() {
+  MatrixWorkspace_sptr ws = create<Workspace2D>(4, HistogramData::Points(1));
+  InstrumentCreationHelper::addFullInstrumentToWorkspace(*ws, false, false, "");
+  return ws;
+}
+
+MatrixWorkspace_sptr maskInstrument(const MatrixWorkspace_sptr &ws,
+                                    const std::vector<int> &detectorIDs) {
+  MaskInstrument alg;
+  alg.setRethrows(true);
+  alg.initialize();
+  alg.setProperty("InputWorkspace", ws);
+  alg.setPropertyValue("OutputWorkspace", "out");
+  alg.setProperty("DetectorIDs", detectorIDs);
+  alg.execute();
+  auto out = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>("out");
+  AnalysisDataService::Instance().remove("out");
+  return out;
+}
+
+MatrixWorkspace_sptr
+maskInstrumentInplace(const MatrixWorkspace_sptr &ws,
+                      const std::vector<int> &detectorIDs) {
+  MaskInstrument alg;
+  alg.setRethrows(true);
+  alg.initialize();
+  alg.setProperty("InputWorkspace", ws);
+  TS_ASSERT_THROWS_NOTHING(
+      alg.setPropertyValue("OutputWorkspace", "_dummy_for_inplace"));
+  alg.setProperty("OutputWorkspace", ws);
+  alg.setProperty("DetectorIDs", detectorIDs);
+  alg.execute();
+  return ws;
+}
+}
+
+class MaskInstrumentTest : 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 MaskInstrumentTest *createSuite() { return new MaskInstrumentTest(); }
+  static void destroySuite(MaskInstrumentTest *suite) { delete suite; }
+
+  void test_masking() {
+    const auto in = makeWorkspace();
+    const auto ws = maskInstrument(in, {1, 3});
+    TS_ASSERT_DIFFERS(in, ws);
+    const auto &detInfo = ws->detectorInfo();
+    // Note that detector IDs in workspace start at 1, so there is an offset of
+    // 1 compared to the detector indices checked here.
+    TS_ASSERT_EQUALS(detInfo.isMasked(0), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(1), false);
+    TS_ASSERT_EQUALS(detInfo.isMasked(2), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(3), false);
+  }
+
+  void test_masking_cummulative() {
+    const auto in = makeWorkspace();
+    const auto ws = maskInstrument(in, {1, 3});
+    const auto ws2 = maskInstrument(ws, {1, 2});
+    const auto &detInfo = ws->detectorInfo();
+    TS_ASSERT_EQUALS(detInfo.isMasked(0), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(1), false);
+    TS_ASSERT_EQUALS(detInfo.isMasked(2), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(3), false);
+    const auto &detInfo2 = ws2->detectorInfo();
+    TS_ASSERT_EQUALS(detInfo2.isMasked(0), true);
+    TS_ASSERT_EQUALS(detInfo2.isMasked(1), true);
+    TS_ASSERT_EQUALS(detInfo2.isMasked(2), true);
+    TS_ASSERT_EQUALS(detInfo2.isMasked(3), false);
+  }
+
+  void test_masking_inplace() {
+    const auto in = makeWorkspace();
+    const auto ws = maskInstrumentInplace(in, {1, 3});
+    TS_ASSERT_EQUALS(in, ws);
+    const auto &detInfo = ws->detectorInfo();
+    TS_ASSERT_EQUALS(detInfo.isMasked(0), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(1), false);
+    TS_ASSERT_EQUALS(detInfo.isMasked(2), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(3), false);
+  }
+
+  void test_masking_inplace_cummulative() {
+    const auto in = makeWorkspace();
+    const auto ws = maskInstrumentInplace(in, {1, 3});
+    const auto ws2 = maskInstrumentInplace(in, {1, 2});
+    const auto &detInfo = ws2->detectorInfo();
+    TS_ASSERT_EQUALS(detInfo.isMasked(0), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(1), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(2), true);
+    TS_ASSERT_EQUALS(detInfo.isMasked(3), false);
+  }
+
+  void test_out_of_range() {
+    const auto in = makeWorkspace();
+    TS_ASSERT_THROWS(maskInstrumentInplace(in, {0}), std::out_of_range);
+    TS_ASSERT_THROWS(maskInstrumentInplace(in, {5}), std::out_of_range);
+  }
+};
+
+#endif /* MANTID_ALGORITHMS_MASKINSTRUMENTTEST_H_ */
diff --git a/Framework/Algorithms/test/SumOverlappingTubesTest.h b/Framework/Algorithms/test/SumOverlappingTubesTest.h
index b404a04e0124e620961141bedcdc1f78a1576323..ddb197e06850215b660e509df1f87f47cba537c4 100644
--- a/Framework/Algorithms/test/SumOverlappingTubesTest.h
+++ b/Framework/Algorithms/test/SumOverlappingTubesTest.h
@@ -54,9 +54,11 @@ public:
     // validator used in the algorithm.
     AnalysisDataService::Instance().add("testWS", testWS);
 
-    testWS->getInstrument()->getParameterMap()->addBool(
-        testWS->getInstrument()->getBaseComponent(), "mirror_detector_angles",
-        mirrorOutput);
+    auto parameterMap = testWS->getInstrument()->getParameterMap();
+    parameterMap->addBool(testWS->getInstrument()->getBaseComponent(),
+                          "mirror_detector_angles", mirrorOutput);
+    parameterMap->addString(testWS->getInstrument()->getBaseComponent(),
+                            "detector_for_height_axis", "tube-1");
     return testWS;
   }
 
@@ -86,6 +88,10 @@ public:
     // validator used in the algorithm.
     AnalysisDataService::Instance().add("testWS", testWS);
 
+    auto parameterMap = testWS->getInstrument()->getParameterMap();
+    parameterMap->addString(testWS->getInstrument()->getBaseComponent(),
+                            "detector_for_height_axis", "tube-1");
+
     return testWS;
   }
 
@@ -160,15 +166,16 @@ public:
     }
   }
 
-  void test_normal_operation_with_component_specified() {
+  void
+  test_normal_operation_with_component_specified_in_instrument_parameters() {
     auto testWS = createTestWS(N_TUBES, N_PIXELS_PER_TUBE);
 
     SumOverlappingTubes alg;
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
     verifySuccessCase();
@@ -184,8 +191,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
     MatrixWorkspace_sptr outWS =
@@ -212,6 +219,7 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
     alg.setProperty("HeightAxis", "0.0, 0.003, 0.027");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
@@ -229,8 +237,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "-90.0, 22.5, 0.0");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
     verifySuccessCase();
@@ -242,13 +250,17 @@ public:
   void test_non_existent_component() {
     auto testWS = createTestWS(N_TUBES, N_PIXELS_PER_TUBE);
 
+    auto parameterMap = testWS->getInstrument()->getParameterMap();
+    parameterMap->addString(testWS->getInstrument()->getBaseComponent(),
+                            "detector_for_height_axis", "not_a_component");
+
     SumOverlappingTubes alg;
     alg.initialize();
     alg.setChild(true);
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "not_a_component");
     TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error & e,
                             std::string(e.what()),
                             "Component not_a_component could not be found.");
@@ -263,12 +275,13 @@ public:
     alg.setChild(true);
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
     alg.setProperty("HeightAxis", "0.003");
     TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error & e,
                             std::string(e.what()),
                             "Height binning must have start, step and end "
-                            "values (except for 1DStraight option).");
+                            "values (except for 1D option).");
     AnalysisDataService::Instance().remove("testWS");
   }
 
@@ -280,12 +293,13 @@ public:
     alg.setChild(true);
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
     alg.setProperty("HeightAxis", "0.003");
     TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error & e,
                             std::string(e.what()),
                             "Height binning must have start, step and end "
-                            "values (except for 1DStraight option).");
+                            "values (except for 1D option).");
     AnalysisDataService::Instance().remove("testWS");
   }
 
@@ -297,8 +311,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
     verifySuccessCase(6.0);
@@ -315,8 +329,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     alg.setProperty("Normalise", false);
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
@@ -341,9 +355,9 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
     alg.setProperty("CropNegativeScatteringAngles", true);
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     alg.setProperty("Normalise", false);
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
@@ -390,8 +404,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     alg.setProperty("Normalise", false);
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
@@ -435,8 +449,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     alg.setProperty("ScatteringAngleTolerance", "0.3");
     alg.setProperty("Normalise", false);
     TS_ASSERT_THROWS_NOTHING(alg.execute());
@@ -462,8 +476,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
     MatrixWorkspace_sptr outWS =
@@ -488,8 +502,8 @@ public:
     alg.initialize();
     alg.setProperty("InputWorkspaces", "testWS");
     alg.setProperty("OutputWorkspace", "outWS");
+    alg.setProperty("OutputType", "2DTubes");
     alg.setProperty("ScatteringAngleBinning", "22.5");
-    alg.setProperty("ComponentForHeightAxis", "tube-1");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
     MatrixWorkspace_sptr outWS =
@@ -536,7 +550,7 @@ public:
     AnalysisDataService::Instance().remove("outWS");
   }
 
-  MatrixWorkspace_sptr do_straight_option(bool oneDimensional = false,
+  MatrixWorkspace_sptr do_standard_option(bool oneDimensional = false,
                                           bool explicitHeightAxis = false) {
     auto testWS = createTestWS(N_TUBES, N_PIXELS_PER_TUBE);
 
@@ -547,13 +561,9 @@ public:
     alg.setProperty("ScatteringAngleBinning", "22.5");
     if (explicitHeightAxis)
       alg.setProperty("HeightAxis", "0.0, 0.0135");
-    else
-      alg.setProperty("ComponentForHeightAxis", "tube-1");
     alg.setProperty("Normalise", false);
     if (oneDimensional)
-      alg.setProperty("OutputType", "1DStraight");
-    else
-      alg.setProperty("OutputType", "2DStraight");
+      alg.setProperty("OutputType", "1D");
     TS_ASSERT_THROWS_NOTHING(alg.execute());
 
     auto outWS = boost::dynamic_pointer_cast<Mantid::API::MatrixWorkspace>(
@@ -568,8 +578,8 @@ public:
     return outWS;
   }
 
-  void test_normal_operation_with_2d_straight_option() {
-    auto outWS = do_straight_option();
+  void test_normal_operation_with_2d_option() {
+    auto outWS = do_standard_option();
 
     verifyHeightAxis(outWS);
 
@@ -593,8 +603,8 @@ public:
     AnalysisDataService::Instance().remove("outWS");
   }
 
-  void test_normal_operation_with_1d_straight_option() {
-    auto outWS = do_straight_option(true);
+  void test_normal_operation_with_1d_option() {
+    auto outWS = do_standard_option(true);
 
     const auto &yAxis = outWS->getAxis(1);
     TS_ASSERT_EQUALS(yAxis->length(), 1)
@@ -619,8 +629,8 @@ public:
     AnalysisDataService::Instance().remove("outWS");
   }
 
-  void test_normal_operation_with_1d_straight_option_with_height_range() {
-    auto outWS = do_straight_option(true, true);
+  void test_normal_operation_with_1d_option_with_height_range() {
+    auto outWS = do_standard_option(true, true);
 
     const auto &yAxis = outWS->getAxis(1);
     TS_ASSERT_EQUALS(yAxis->length(), 1)
@@ -644,6 +654,32 @@ public:
     AnalysisDataService::Instance().remove("testWS");
     AnalysisDataService::Instance().remove("outWS");
   }
+
+  void test_normal_operation_with_2d_option_with_height_range() {
+    auto outWS = do_standard_option(false, true);
+
+    const auto &yAxis = outWS->getAxis(1);
+    TS_ASSERT_EQUALS(yAxis->length(), 5)
+    for (size_t i = 0; i < 5; ++i)
+      TS_ASSERT_DELTA(yAxis->getValue(i), 0.003 * double(i), 1e-6)
+
+    double totalCounts = 0.0;
+    for (size_t i = 0; i < N_TUBES; ++i) {
+      auto counts = outWS->getSpectrum(0).y()[i];
+      // Tolerance on error is quite large, due to repeated rounding
+      TS_ASSERT_DELTA(outWS->getSpectrum(0).e()[i], sqrt(counts), 0.1)
+      totalCounts += counts;
+    }
+
+    TS_ASSERT_DELTA(totalCounts, 10.0, 1e-6)
+
+    // An analytic comparison is a little harder for this case, do a quick check
+    // of an arbitary value
+    TS_ASSERT_DELTA(outWS->getSpectrum(0).y()[2], 2.0, 1e-6)
+
+    AnalysisDataService::Instance().remove("testWS");
+    AnalysisDataService::Instance().remove("outWS");
+  }
 };
 
 #endif /* MANTID_ALGORITHMS_SUMOVERLAPPINGTUBESTEST_H_ */
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadMask.h b/Framework/DataHandling/inc/MantidDataHandling/LoadMask.h
index 5e672678216c7f9342fc5926d903f0a9116813ab..3ea1b44215762d6fbcd497382dc2cc7d7233c6a7 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadMask.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadMask.h
@@ -2,7 +2,7 @@
 #define MANTID_DATAHANDLING_LOADMASK_H_
 
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidAPI/MatrixWorkspace_fwd.h"
 #include "MantidDataObjects/MaskWorkspace.h"
 #include "MantidGeometry/IDTypes.h"
@@ -47,7 +47,7 @@ namespace DataHandling {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport LoadMask : public API::Algorithm {
+class DLLExport LoadMask : public API::ParallelAlgorithm {
 public:
   /// Algorithm's name for identification
   const std::string name() const override { return "LoadMask"; };
diff --git a/Framework/DataHandling/inc/MantidDataHandling/ParallelEventLoader.h b/Framework/DataHandling/inc/MantidDataHandling/ParallelEventLoader.h
index 1af7bf563001db7dc80a497713b313f1658cd856..40af3f0de335005f6059c1c5a76270f7377a6a95 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/ParallelEventLoader.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/ParallelEventLoader.h
@@ -45,7 +45,8 @@ class MANTID_DATAHANDLING_DLL ParallelEventLoader {
 public:
   static void load(DataObjects::EventWorkspace &ws, const std::string &filename,
                    const std::string &groupName,
-                   const std::vector<std::string> &bankNames);
+                   const std::vector<std::string> &bankNames,
+                   const bool eventIDIsSpectrumNumber);
 };
 
 } // namespace DataHandling
diff --git a/Framework/DataHandling/src/LoadEventNexus.cpp b/Framework/DataHandling/src/LoadEventNexus.cpp
index a2a1d08e44f69ba32b214287d1a6c40968d3c909..0162587699a6ea300fe9cf5f04aaaaaca3046866 100644
--- a/Framework/DataHandling/src/LoadEventNexus.cpp
+++ b/Framework/DataHandling/src/LoadEventNexus.cpp
@@ -861,11 +861,15 @@ void LoadEventNexus::loadEvents(API::Progress *const prog,
     auto ws = m_ws->getSingleHeldWorkspace();
     m_file->close();
     try {
-      ParallelEventLoader::load(*ws, m_filename, m_top_entry_name, bankNames);
+      ParallelEventLoader::load(*ws, m_filename, m_top_entry_name, bankNames,
+                                event_id_is_spec);
+      g_log.information() << "Used ParallelEventLoader.\n";
       loaded = true;
       shortest_tof = 0.0;
       longest_tof = 1e10;
     } catch (const std::runtime_error &) {
+      g_log.warning()
+          << "ParallelEventLoader failed, falling back to default loader.\n";
     }
     safeOpenFile(m_filename);
   }
@@ -1681,8 +1685,6 @@ bool LoadEventNexus::canUseParallelLoader(const bool haveWeights,
     return false;
   if (haveWeights)
     return false;
-  if (event_id_is_spec)
-    return false;
   if (oldNeXusFileNames)
     return false;
   if (filter_tof_min != -1e20 || filter_tof_max != 1e20)
diff --git a/Framework/DataHandling/src/ParallelEventLoader.cpp b/Framework/DataHandling/src/ParallelEventLoader.cpp
index b218ec669da520657e582aea68e85971ffa10694..90a7522fa793413a633f828dfe2691d64958d9ef 100644
--- a/Framework/DataHandling/src/ParallelEventLoader.cpp
+++ b/Framework/DataHandling/src/ParallelEventLoader.cpp
@@ -14,9 +14,9 @@ std::vector<int32_t> bankOffsets(const API::ExperimentInfo &ws,
                                  const std::string &filename,
                                  const std::string &groupName,
                                  const std::vector<std::string> &bankNames) {
-  // Read an event ID for each bank. This is always a detector ID since the
-  // parallel loader is disabled otherwise. It is assumed that detector IDs
-  // within a bank are contiguous.
+  // Read an event ID for each bank. This is always a detector ID since
+  // bankOffsetsSpectrumNumbers is used otherwise. It is assumed that detector
+  // IDs within a bank are contiguous.
   const auto &idToBank = Parallel::IO::EventLoader::makeAnyEventIdToBankMap(
       filename, groupName, bankNames);
 
@@ -44,19 +44,54 @@ std::vector<int32_t> bankOffsets(const API::ExperimentInfo &ws,
   return bankOffsets;
 }
 
+/// Return offset between global spectrum index and spectrum number for given
+/// banks.
+std::vector<int32_t> bankOffsetsSpectrumNumbers(
+    const API::MatrixWorkspace &ws, const std::string &filename,
+    const std::string &groupName, const std::vector<std::string> &bankNames) {
+  // Read an event ID for each bank. This is always a spectrum number since
+  // bankOffsets is used otherwise. It is assumed that spectrum numbers within a
+  // bank are contiguous.
+  const auto &idToBank = Parallel::IO::EventLoader::makeAnyEventIdToBankMap(
+      filename, groupName, bankNames);
+
+  // *Global* vector of spectrum numbers.
+  const auto &specNums = ws.indexInfo().spectrumNumbers();
+  int32_t spectrumIndex{0}; // *global* index
+  std::vector<int32_t> bankOffsets(bankNames.size(), 0);
+  for (size_t i = 0; i < specNums.size(); ++i) {
+    // In contrast to the case of event ID = detector ID we know that any
+    // spectrum number has a corresponding event ID, i.e., we do not need
+    // special handling for monitors.
+    specnum_t specNum = static_cast<specnum_t>(specNums[i]);
+    // See comment in bankOffsets regarding this offset computation.
+    if (idToBank.count(specNum) == 1) {
+      size_t bank = idToBank.at(specNum);
+      bankOffsets[bank] = specNum - spectrumIndex;
+    }
+    spectrumIndex++;
+  }
+  return bankOffsets;
+}
+
 /// Load events from given banks into given EventWorkspace.
 void ParallelEventLoader::load(DataObjects::EventWorkspace &ws,
                                const std::string &filename,
                                const std::string &groupName,
-                               const std::vector<std::string> &bankNames) {
+                               const std::vector<std::string> &bankNames,
+                               const bool eventIDIsSpectrumNumber) {
   const size_t size = ws.getNumberHistograms();
   std::vector<std::vector<Types::Event::TofEvent> *> eventLists(size, nullptr);
   for (size_t i = 0; i < size; ++i)
     DataObjects::getEventsFrom(ws.getSpectrum(i), eventLists[i]);
+  const auto offsets =
+      eventIDIsSpectrumNumber
+          ? bankOffsetsSpectrumNumbers(ws, filename, groupName, bankNames)
+          : bankOffsets(ws, filename, groupName, bankNames);
 
-  Parallel::IO::EventLoader::load(
-      ws.indexInfo().communicator(), filename, groupName, bankNames,
-      bankOffsets(ws, filename, groupName, bankNames), std::move(eventLists));
+  Parallel::IO::EventLoader::load(ws.indexInfo().communicator(), filename,
+                                  groupName, bankNames, offsets,
+                                  std::move(eventLists));
 }
 
 } // namespace DataHandling
diff --git a/Framework/DataHandling/test/LoadEventNexusTest.h b/Framework/DataHandling/test/LoadEventNexusTest.h
index 026bc70a5c0c75112fb6c819033a7b6d3b2e2da0..5dc3e8407b32d71270e186b3b8d67b879c523b54 100644
--- a/Framework/DataHandling/test/LoadEventNexusTest.h
+++ b/Framework/DataHandling/test/LoadEventNexusTest.h
@@ -49,12 +49,12 @@ load_reference_workspace(const std::string &filename) {
 }
 
 void run_MPI_load(const Parallel::Communicator &comm,
-                  boost::shared_ptr<std::mutex> mutex) {
+                  boost::shared_ptr<std::mutex> mutex,
+                  const std::string &filename) {
   boost::shared_ptr<const EventWorkspace> reference;
   boost::shared_ptr<const EventWorkspace> eventWS;
   {
     std::lock_guard<std::mutex> lock(*mutex);
-    const std::string filename("CNCS_7860_event.nxs");
     reference = load_reference_workspace(filename);
     auto alg = ParallelTestHelpers::create<LoadEventNexus>(comm);
     alg->setProperty("Filename", filename);
@@ -76,11 +76,11 @@ void run_MPI_load(const Parallel::Communicator &comm,
   if (comm.rank() == 0) {
     TS_ASSERT_EQUALS(std::accumulate(localSizes.begin(), localSizes.end(),
                                      static_cast<size_t>(0)),
-                     static_cast<size_t>(51200));
+                     reference->getNumberHistograms());
     TS_ASSERT_EQUALS(std::accumulate(localEventCounts.begin(),
                                      localEventCounts.end(),
                                      static_cast<size_t>(0)),
-                     static_cast<size_t>(112266));
+                     reference->getNumberEvents());
   }
 
   const auto &indexInfo = eventWS->indexInfo();
@@ -836,12 +836,28 @@ public:
   }
 
   void test_MPI_load() {
+    // Note that this and other MPI tests currently work only in non-MPI builds
+    // with the default event loader, i.e., ParallelEventLoader is not
+    // supported. The reason is the locking we need in the test for HDF5 access,
+    // which implies that the communication within ParallelEventLoader will
+    // simply get stuck. Additionally, it will fail for the CNCS file since
+    // empty banks contain a dummy event with an invalid event ID, which
+    // ParallelEventLoader does not support.
+    int threads = 3; // Limited number of threads to avoid long running test.
+    ParallelTestHelpers::ParallelRunner runner(threads);
+    // Test reads from multiple threads, which is not supported by our HDF5
+    // libraries, so we need a mutex.
+    auto hdf5Mutex = boost::make_shared<std::mutex>();
+    runner.run(run_MPI_load, hdf5Mutex, "CNCS_7860_event.nxs");
+  }
+
+  void test_MPI_load_ISIS() {
     int threads = 3; // Limited number of threads to avoid long running test.
     ParallelTestHelpers::ParallelRunner runner(threads);
     // Test reads from multiple threads, which is not supported by our HDF5
     // libraries, so we need a mutex.
     auto hdf5Mutex = boost::make_shared<std::mutex>();
-    runner.run(run_MPI_load, hdf5Mutex);
+    runner.run(run_MPI_load, hdf5Mutex, "SANS2D00022048.nxs");
   }
 
 private:
diff --git a/Framework/DataObjects/src/Peak.cpp b/Framework/DataObjects/src/Peak.cpp
index 06f5a27d4a5850719eb98858f124066325096f76..f0607804cd6d7735c60db7e329c1c3fcfcb87c95 100644
--- a/Framework/DataObjects/src/Peak.cpp
+++ b/Framework/DataObjects/src/Peak.cpp
@@ -848,7 +848,7 @@ void Peak::setGoniometerMatrix(
   m_InverseGoniometerMatrix = m_GoniometerMatrix;
   if (fabs(m_InverseGoniometerMatrix.Invert()) < 1e-8)
     throw std::invalid_argument(
-        "Peak::setGoniometerMatrix(): Goniometer matrix must non-singular.");
+        "Peak::setGoniometerMatrix(): Goniometer matrix must be non-singular.");
 }
 
 // -------------------------------------------------------------------------------------
diff --git a/Framework/Indexing/inc/MantidIndexing/IndexInfo.h b/Framework/Indexing/inc/MantidIndexing/IndexInfo.h
index b96a988ca0a99235f8c381acc52fbf0695404f16..73db808d8e30b9b5862a6eee40a82f8db8dff220 100644
--- a/Framework/Indexing/inc/MantidIndexing/IndexInfo.h
+++ b/Framework/Indexing/inc/MantidIndexing/IndexInfo.h
@@ -96,6 +96,7 @@ public:
   size_t globalSize() const;
 
   SpectrumNumber spectrumNumber(const size_t index) const;
+  const std::vector<SpectrumNumber> &spectrumNumbers() const;
 
   void setSpectrumNumbers(std::vector<SpectrumNumber> &&spectrumNumbers);
   void setSpectrumNumbers(const SpectrumNumber min, const SpectrumNumber max);
diff --git a/Framework/Indexing/inc/MantidIndexing/SpectrumNumberTranslator.h b/Framework/Indexing/inc/MantidIndexing/SpectrumNumberTranslator.h
index 486e19ed5fc630603d5f73a76481468e9368369f..0002ebaf0dd76850748799b7193b79922a425053 100644
--- a/Framework/Indexing/inc/MantidIndexing/SpectrumNumberTranslator.h
+++ b/Framework/Indexing/inc/MantidIndexing/SpectrumNumberTranslator.h
@@ -60,6 +60,7 @@ public:
   size_t localSize() const;
 
   SpectrumNumber spectrumNumber(const size_t index) const;
+  const std::vector<SpectrumNumber> &globalSpectrumNumbers() const;
 
   SpectrumIndexSet makeIndexSet() const;
   SpectrumIndexSet makeIndexSet(SpectrumNumber min, SpectrumNumber max) const;
diff --git a/Framework/Indexing/src/IndexInfo.cpp b/Framework/Indexing/src/IndexInfo.cpp
index a9ac6b11c00b98e372b3945b643191a6a90f719d..f226f2136efbbec80bf57e52a2f76e195e9ae0f1 100644
--- a/Framework/Indexing/src/IndexInfo.cpp
+++ b/Framework/Indexing/src/IndexInfo.cpp
@@ -109,11 +109,18 @@ size_t IndexInfo::globalSize() const {
   return m_spectrumNumberTranslator->globalSize();
 }
 
-/// Returns the spectrum number for given index.
+/// Returns the spectrum number for given *local* index, i.e., spectrum numbers
+/// for spectra in this partition.
 SpectrumNumber IndexInfo::spectrumNumber(const size_t index) const {
   return m_spectrumNumberTranslator->spectrumNumber(index);
 }
 
+/// Returns a reference to the *global* vector of spectrum numbers, i.e., the
+/// spectrum numbers of spectra across all partitions.
+const std::vector<SpectrumNumber> &IndexInfo::spectrumNumbers() const {
+  return m_spectrumNumberTranslator->globalSpectrumNumbers();
+}
+
 /// Set a spectrum number for each index.
 void IndexInfo::setSpectrumNumbers(
     std::vector<SpectrumNumber> &&spectrumNumbers) {
diff --git a/Framework/Indexing/src/SpectrumNumberTranslator.cpp b/Framework/Indexing/src/SpectrumNumberTranslator.cpp
index 8802e2212e188b42c6613cf9ab1479539802b26a..30ff5101a1b51ff3cffffe07eae13fa914d5416f 100644
--- a/Framework/Indexing/src/SpectrumNumberTranslator.cpp
+++ b/Framework/Indexing/src/SpectrumNumberTranslator.cpp
@@ -83,6 +83,11 @@ SpectrumNumberTranslator::SpectrumNumberTranslator(
   }
 }
 
+const std::vector<SpectrumNumber> &
+SpectrumNumberTranslator::globalSpectrumNumbers() const {
+  return m_globalSpectrumNumbers;
+}
+
 SpectrumNumberTranslator::SpectrumNumberTranslator(
     const std::vector<GlobalSpectrumIndex> &globalIndices,
     const SpectrumNumberTranslator &parent)
diff --git a/Framework/Indexing/test/IndexInfoTest.h b/Framework/Indexing/test/IndexInfoTest.h
index 7c48bfb57a25280c92a9a018a700c64aecf48264..b93e0660da5f062d06737e377ba7c207303772d8 100644
--- a/Framework/Indexing/test/IndexInfoTest.h
+++ b/Framework/Indexing/test/IndexInfoTest.h
@@ -31,12 +31,14 @@ void run_StorageMode_Cloned(const Parallel::Communicator &comm) {
 void run_StorageMode_Distributed(const Parallel::Communicator &comm) {
   IndexInfo i(47, Parallel::StorageMode::Distributed, comm);
   TS_ASSERT_EQUALS(i.globalSize(), 47);
+  TS_ASSERT_EQUALS(i.spectrumNumbers().size(), 47);
   size_t expectedSize = 0;
   for (size_t globalIndex = 0; globalIndex < i.globalSize(); ++globalIndex) {
+    SpectrumNumber specNum = static_cast<int>(globalIndex) + 1;
+    TS_ASSERT_EQUALS(i.spectrumNumbers()[globalIndex], specNum);
     // Current default is RoundRobinPartitioner
     if (static_cast<int>(globalIndex) % comm.size() == comm.rank()) {
-      TS_ASSERT_EQUALS(i.spectrumNumber(expectedSize),
-                       static_cast<int>(globalIndex) + 1);
+      TS_ASSERT_EQUALS(i.spectrumNumber(expectedSize), specNum);
       ++expectedSize;
     }
   }
@@ -163,6 +165,9 @@ public:
     TS_ASSERT_EQUALS(info.spectrumNumber(0), 3);
     TS_ASSERT_EQUALS(info.spectrumNumber(1), 2);
     TS_ASSERT_EQUALS(info.spectrumNumber(2), 1);
+    TS_ASSERT_EQUALS(info.spectrumNumbers()[0], 3);
+    TS_ASSERT_EQUALS(info.spectrumNumbers()[1], 2);
+    TS_ASSERT_EQUALS(info.spectrumNumbers()[2], 1);
   }
 
   void test_construct_from_parent_reorder() {
diff --git a/Framework/MDAlgorithms/src/Integrate3DEvents.cpp b/Framework/MDAlgorithms/src/Integrate3DEvents.cpp
index 42cd8f1ea47d35d544c788427f337808e737111f..e74c1c2d1c452273780dcf8febdb0ee39e5bc00b 100644
--- a/Framework/MDAlgorithms/src/Integrate3DEvents.cpp
+++ b/Framework/MDAlgorithms/src/Integrate3DEvents.cpp
@@ -147,11 +147,6 @@ Integrate3DEvents::integrateStrongPeak(const IntegrationParameters &params,
   inti = peak - ratio * backgrd;
   sigi = sqrt(peak + ratio * ratio * backgrd);
 
-  if (inti < 0) {
-    inti = 0;
-    sigi = 0;
-  }
-
   // compute the fraction of peak within the standard core
   const auto total = (core + peak) - ratio * backgrd;
   const auto frac = std::min(1.0, std::abs(inti / total));
@@ -186,9 +181,9 @@ Integrate3DEvents::integrateWeakPeak(
   const auto &events = *result;
 
   const auto &directions = shape->directions();
-  const auto &abcBackgroundInnerRadii = shape->abcRadiiBackgroundInner();
-  const auto &abcBackgroundOuterRadii = shape->abcRadiiBackgroundOuter();
-  const auto &abcRadii = shape->abcRadii();
+  auto abcBackgroundInnerRadii = shape->abcRadiiBackgroundInner();
+  auto abcBackgroundOuterRadii = shape->abcRadiiBackgroundOuter();
+  auto abcRadii = shape->abcRadii();
 
   const auto max_sigma = std::get<2>(libPeak);
   auto rValues = calculateRadiusFactors(params, max_sigma);
@@ -214,7 +209,6 @@ Integrate3DEvents::integrateWeakPeak(
   const auto fracError = std::get<1>(libPeak);
 
   inti = peak_w_back - ratio * backgrd;
-  sigi = inti + ratio * ratio * backgrd;
 
   // correct for fractional intensity
   sigi = sigi / pow(inti, 2);
@@ -223,12 +217,17 @@ Integrate3DEvents::integrateWeakPeak(
   inti = inti * frac;
   sigi = sqrt(sigi) * inti;
 
-  if (inti < 0) {
-    inti = 0;
-    sigi = 0;
+  // scale integration shape by fractional amount
+  for (size_t i = 0; i < abcRadii.size(); ++i) {
+    abcRadii[i] *= frac;
+    abcBackgroundInnerRadii[i] *= frac;
+    abcBackgroundOuterRadii[i] *= frac;
   }
 
-  return shape;
+  return boost::make_shared<const PeakShapeEllipsoid>(
+      shape->directions(), abcRadii, abcBackgroundInnerRadii,
+      abcBackgroundOuterRadii, Mantid::Kernel::QLab,
+      "IntegrateEllipsoidsTwoStep");
 }
 
 double Integrate3DEvents::estimateSignalToNoiseRatio(
@@ -277,9 +276,10 @@ double Integrate3DEvents::estimateSignalToNoiseRatio(
   double peak_w_back = numInEllipsoid(events, eigen_vectors, peakRadii);
 
   double ratio = pow(r1, 3) / (pow(r3, 3) - pow(r2, 3));
-  double inti = peak_w_back - ratio * backgrd;
+  auto inti = peak_w_back - ratio * backgrd;
+  auto sigi = sqrt(peak_w_back + ratio * ratio * backgrd);
 
-  return inti / std::max(1.0, (ratio * backgrd));
+  return inti / sigi;
 }
 
 const std::vector<std::pair<double, V3D>> *
diff --git a/Framework/MDAlgorithms/test/Integrate3DEventsTest.h b/Framework/MDAlgorithms/test/Integrate3DEventsTest.h
index a15a2ba92f490400c8d1dc442fc24027590f0178..0b0bbfc1bd47cd45a84c06b8666e61650094b7ee 100644
--- a/Framework/MDAlgorithms/test/Integrate3DEventsTest.h
+++ b/Framework/MDAlgorithms/test/Integrate3DEventsTest.h
@@ -132,7 +132,9 @@ public:
     // synthesize two peaks
     V3D peak_1(20, 0, 0);
     V3D peak_2(0, 20, 0);
-    std::vector<std::pair<double, V3D>> peak_q_list{{1., peak_1}, {1., peak_2}};
+    V3D peak_3(0, 0, 20);
+    std::vector<std::pair<double, V3D>> peak_q_list{
+        {1., peak_1}, {1., peak_2}, {1., peak_3}};
 
     // synthesize a UB-inverse to map
     DblMatrix UBinv(3, 3, false); // Q to h,k,l
@@ -145,6 +147,7 @@ public:
     const int numWeakEvents = 100;
     generatePeak(event_Qs, peak_1, 0.1, numStrongEvents, 1); // strong peak
     generatePeak(event_Qs, peak_2, 0.1, numWeakEvents, 1);   // weak peak
+    generatePeak(event_Qs, peak_2, 0.1, 0, 1); // non-existant peak
 
     IntegrationParameters params;
     params.peakRadius = 1.0;
@@ -182,8 +185,20 @@ public:
     // to be weighted by the fraction of strong peak contained in a standard
     // core. This is not exactly the same because of the weighting from the
     // strong peak
-    TS_ASSERT_DELTA(weak_inti, 83.6960, 0.5);
-    TS_ASSERT_DELTA(weak_sigi, 8.37, 0.1);
+    TS_ASSERT_DELTA(weak_inti, 83.696, 0.5);
+    TS_ASSERT_DELTA(weak_sigi, 0.403, 0.1);
+
+    weak_inti = 0;
+    weak_sigi = 0;
+    integrator.integrateWeakPeak(params, shape, result.second, peak_3,
+                                 weak_inti, weak_sigi);
+
+    // Check the integrated intensity for a weak peak is exactly what we set it
+    // to be weighted by the fraction of strong peak contained in a standard
+    // core. This is not exactly the same because of the weighting from the
+    // strong peak
+    TS_ASSERT_DELTA(weak_inti, 0, 0.5);
+    TS_ASSERT_DELTA(weak_sigi, 0, 0.1);
   }
 
   void test_integrateWeakPeakWithBackground() {
@@ -245,7 +260,7 @@ public:
     // core. This is not exactly the same because of the weighting from the
     // strong peak
     TS_ASSERT_DELTA(weak_inti, numWeakEvents, 35);
-    TS_ASSERT_DELTA(weak_sigi, 8.62, 0.2);
+    TS_ASSERT_DELTA(weak_sigi, 0.445, 0.2);
   }
 
   void test_estimateSignalToNoiseRatioInPerfectCase() {
@@ -283,17 +298,17 @@ public:
     const auto ratio2 = integrator.estimateSignalToNoiseRatio(params, peak_2);
     const auto ratio3 = integrator.estimateSignalToNoiseRatio(params, peak_3);
 
-    TS_ASSERT_DELTA(ratio1, numStrongEvents, 0.0001);
-    TS_ASSERT_DELTA(ratio2, numWeakEvents, 0.0001);
-    TS_ASSERT_DELTA(ratio3, numWeakEvents / 2, 0.0001);
+    TS_ASSERT_DELTA(ratio1, numStrongEvents / 100, 1e-4);
+    TS_ASSERT_DELTA(ratio2, numWeakEvents / 10, 1e-4);
+    TS_ASSERT_DELTA(ratio3, 7.071, 1e-4);
   }
 
   void test_estimateSignalToNoiseRatioWithBackgroundAndOnePercentCulling() {
-    doTestSignalToNoiseRatio(true, 171.90, 1.2632, 0.1824);
+    doTestSignalToNoiseRatio(true, 99.3898, 5.4788, 1.0597);
   }
 
   void test_estimateSignalToNoiseRatioWithBackgroundAndNoOnePercentCulling() {
-    doTestSignalToNoiseRatio(false, 160.33, 1.088, 0.094);
+    doTestSignalToNoiseRatio(false, 99.3417, 5.0972, 0.5821);
   }
 
 private:
diff --git a/Framework/MDAlgorithms/test/IntegrateEllipsoidsTwoStepTest.h b/Framework/MDAlgorithms/test/IntegrateEllipsoidsTwoStepTest.h
index f7c4b4d2febf4e6689d931d026c7c61ae79d7321..710f88178378bda156ffdc53f060de652d54b298 100644
--- a/Framework/MDAlgorithms/test/IntegrateEllipsoidsTwoStepTest.h
+++ b/Framework/MDAlgorithms/test/IntegrateEllipsoidsTwoStepTest.h
@@ -350,6 +350,9 @@ public:
     builder.addPeakByHKL(V3D(1, -4, 0), numEventsPerStrongPeak, sigmas);
     builder.addPeakByHKL(V3D(2, -3, -4), numEventsPerStrongPeak, sigmas);
 
+    // weak peak with zero intensity
+    builder.addPeakByHKL(V3D(2, -5, -5), 0, sigmas);
+
     auto data = builder.build();
     auto eventWS = std::get<0>(data);
     auto peaksWS = std::get<1>(data);
@@ -402,6 +405,8 @@ public:
     TSM_ASSERT_DELTA("Wrong intensity for peak " + std::to_string(5),
                      integratedPeaksWS->getPeak(5).getIntensity(),
                      numEventsPerStrongPeak, 800);
+    TSM_ASSERT_DELTA("Wrong intensity for peak " + std::to_string(6),
+                     integratedPeaksWS->getPeak(6).getIntensity(), 100, 10);
   }
 
   void test_exec_events_with_adaptive_q() {
@@ -467,10 +472,10 @@ public:
 
     TSM_ASSERT_DELTA("Wrong intensity for peak " + std::to_string(0),
                      integratedPeaksWS->getPeak(0).getIntensity(),
-                     numEventsPerStrongPeak, 150);
+                     numEventsPerStrongPeak, 5100);
     TSM_ASSERT_DELTA("Wrong intensity for peak " + std::to_string(1),
                      integratedPeaksWS->getPeak(1).getIntensity(),
-                     numEventsPerStrongPeak, 150);
+                     numEventsPerStrongPeak, 5100);
     TSM_ASSERT_DELTA("Wrong intensity for peak " + std::to_string(2),
                      integratedPeaksWS->getPeak(2).getIntensity(),
                      numEventsPerStrongPeak, 900);
diff --git a/Framework/Parallel/inc/MantidParallel/IO/EventLoaderHelpers.h b/Framework/Parallel/inc/MantidParallel/IO/EventLoaderHelpers.h
index bf02c371c9373e1a7cfd953ccc1b0820a6f90e43..778feed11e55ecd4a3bbddb0481595751aa71ebc 100644
--- a/Framework/Parallel/inc/MantidParallel/IO/EventLoaderHelpers.h
+++ b/Framework/Parallel/inc/MantidParallel/IO/EventLoaderHelpers.h
@@ -58,6 +58,15 @@ H5::DataType readDataType(const H5::Group &group,
   return group.openDataSet(bankNames.front() + "/" + name).getDataType();
 }
 
+template <class T> class ThreadWaiter {
+public:
+  ThreadWaiter(T &thread) : m_thread(thread) {}
+  ~ThreadWaiter() { m_thread.wait(); }
+
+private:
+  T &m_thread;
+};
+
 template <class TimeOffsetType>
 void load(const Chunker &chunker, NXEventDataSource<TimeOffsetType> &dataSource,
           EventParser<TimeOffsetType> &dataSink) {
@@ -65,6 +74,9 @@ void load(const Chunker &chunker, NXEventDataSource<TimeOffsetType> &dataSource,
   const auto &ranges = chunker.makeLoadRanges();
   std::vector<int32_t> event_id(2 * chunkSize);
   std::vector<TimeOffsetType> event_time_offset(2 * chunkSize);
+  // Wait for thread completion before exit. Wrapped in struct in case of
+  // exceptions.
+  ThreadWaiter<EventParser<TimeOffsetType>> threadCleanup(dataSink);
 
   int64_t previousBank = -1;
   size_t bufferOffset{0};
@@ -88,7 +100,6 @@ void load(const Chunker &chunker, NXEventDataSource<TimeOffsetType> &dataSource,
                         event_time_offset.data() + bufferOffset, range);
     bufferOffset = (bufferOffset + chunkSize) % (2 * chunkSize);
   }
-  dataSink.wait();
 }
 
 template <class TimeOffsetType>
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/api/PythonAlgorithm/DataProcessorAdapter.h b/Framework/PythonInterface/inc/MantidPythonInterface/api/PythonAlgorithm/DataProcessorAdapter.h
index 9dcb33285dc3621a7d1f50560bdd0cac145e8cb7..38286ac53b0dd23e4b9df1df0e099b22dc701069 100644
--- a/Framework/PythonInterface/inc/MantidPythonInterface/api/PythonAlgorithm/DataProcessorAdapter.h
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/api/PythonAlgorithm/DataProcessorAdapter.h
@@ -41,10 +41,9 @@ namespace PythonInterface {
  * It also provides access to the protected methods on DataProcessorAlgorithm
  * from the type exported to Python
  */
+template <class Base>
 class DataProcessorAdapter
-    : public AlgorithmAdapter<API::DataProcessorAlgorithm> {
-  typedef AlgorithmAdapter<API::DataProcessorAlgorithm> SuperClass;
-
+    : public AlgorithmAdapter<API::GenericDataProcessorAlgorithm<Base>> {
 public:
   /// A constructor that looks like a Python __init__ method
   DataProcessorAdapter(PyObject *self);
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/PyObjectToString.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/PyObjectToString.h
new file mode 100644
index 0000000000000000000000000000000000000000..013f2460ef47f611b612e460a35d1e6e6738a42d
--- /dev/null
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/PyObjectToString.h
@@ -0,0 +1,42 @@
+#ifndef MANTID_PYTHONINERFACE_CONVERTERS_PYOBJECTTOSTRING_H_
+#define MANTID_PYTHONINERFACE_CONVERTERS_PYOBJECTTOSTRING_H_
+/**
+    Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge
+   National Laboratory & European Spallation Source
+
+    This file is part of Mantid.
+
+    Mantid is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    Mantid is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+    File change history is stored at: <https://github.com/mantidproject/mantid>
+    Code Documentation is available at: <http://doxygen.mantidproject.org>
+ */
+#include "MantidKernel/System.h"
+#include <boost/python/object.hpp>
+
+namespace Mantid {
+namespace PythonInterface {
+namespace Converters {
+
+/**
+ * Convert a python object to a string or throw an exception. This will convert
+ * unicode strings in python2 via utf8.
+ */
+DLLExport std::string pyObjToStr(const boost::python::object &value);
+
+} // namespace Converters
+} // namespace PythonInterface
+} // namespace Mantid
+
+#endif /* MANTID_PYTHONINERFACE_CONVERTERS_PYOBJECTTOSTRING_H_ */
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/DataServiceExporter.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/DataServiceExporter.h
index 5155dbb1437eba75c8bfbba0a8acd9bca2a77749..7657c6abae07b9949eb29ff68323d94b5bf876cf 100644
--- a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/DataServiceExporter.h
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/DataServiceExporter.h
@@ -24,11 +24,13 @@
     Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
 #include "MantidKernel/Exception.h"
+#include "MantidPythonInterface/kernel/Converters/PyObjectToString.h"
 #include "MantidPythonInterface/kernel/WeakPtr.h"
 
 #include <boost/python/class.hpp>
-#include <boost/python/list.hpp>
 #include <boost/python/extract.hpp>
+#include <boost/python/list.hpp>
+#include <boost/python/str.hpp>
 
 #include <set>
 
@@ -108,9 +110,14 @@ template <typename SvcType, typename SvcPtrType> struct DataServiceExporter {
    * @param name The name to assign to this in the service
    * @param item A boost.python wrapped SvcHeldType object
    */
-  static void addItem(SvcType &self, const std::string &name,
+  static void addItem(SvcType &self, const boost::python::object &name,
                       const boost::python::object &item) {
-    self.add(name, extractCppValue(item));
+    try {
+      std::string namestr = PythonInterface::Converters::pyObjToStr(name);
+      self.add(namestr, extractCppValue(item));
+    } catch (std::invalid_argument &) {
+      throw std::invalid_argument("Failed to convert name to a string");
+    }
   }
 
   /**
@@ -120,9 +127,14 @@ template <typename SvcType, typename SvcPtrType> struct DataServiceExporter {
    * @param name The name to assign to this in the service
    * @param item A boost.python wrapped SvcHeldType object
    */
-  static void addOrReplaceItem(SvcType &self, const std::string &name,
+  static void addOrReplaceItem(SvcType &self, const boost::python::object &name,
                                const boost::python::object &item) {
-    self.addOrReplace(name, extractCppValue(item));
+    try {
+      std::string namestr = PythonInterface::Converters::pyObjToStr(name);
+      self.addOrReplace(namestr, extractCppValue(item));
+    } catch (std::invalid_argument &) {
+      throw std::invalid_argument("Failed to convert name to a string");
+    }
   }
 
   /**
@@ -155,14 +167,23 @@ template <typename SvcType, typename SvcPtrType> struct DataServiceExporter {
    * @return A shared_ptr to the named object. If the name does not exist it
    * sets a KeyError error indicator.
    */
-  static WeakPtr retrieveOrKeyError(SvcType &self, const std::string &name) {
+  static WeakPtr retrieveOrKeyError(SvcType &self,
+                                    const boost::python::object &name) {
     using namespace Mantid::Kernel;
+
+    std::string namestr;
+    try {
+      namestr = PythonInterface::Converters::pyObjToStr(name);
+    } catch (std::invalid_argument &) {
+      throw std::invalid_argument("Failed to convert name to a string");
+    }
+
     SvcPtrType item;
     try {
-      item = self.retrieve(name);
+      item = self.retrieve(namestr);
     } catch (Exception::NotFoundError &) {
       // Translate into a Python KeyError
-      std::string err = "'" + name + "' does not exist.";
+      std::string err = "'" + namestr + "' does not exist.";
       PyErr_SetString(PyExc_KeyError, err.c_str());
       throw boost::python::error_already_set();
     }
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/MatrixToNumpy.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/MatrixToNumpy.h
index 71151a5329c941e598272f847c17e66cb234a4a8..173f5a1c319f9ae920ad3a7e52c30f0c737cefe4 100644
--- a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/MatrixToNumpy.h
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/MatrixToNumpy.h
@@ -22,10 +22,11 @@
     File change history is stored at: <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
+#include "MantidKernel/Matrix.h"
 #include "MantidKernel/System.h"
-#include "MantidPythonInterface/kernel/Converters/VectorToNDArray.h"
+#include "MantidPythonInterface/kernel/Converters/CloneToNumpy.h"
+#include "MantidPythonInterface/kernel/Converters/MatrixToNDArray.h"
 #include "MantidPythonInterface/kernel/Converters/PyArrayType.h"
-#include "MantidKernel/Matrix.h"
 
 #include <type_traits>
 
@@ -38,12 +39,26 @@ namespace Policies {
 
 namespace // anonymous
     {
+//-----------------------------------------------------------------------
+// MPL helper structs
+//-----------------------------------------------------------------------
+/// MPL struct to figure out if a type is a std::vector
+/// The general one inherits from boost::false_type
+template <typename T> struct is_matrix : boost::false_type {};
+
+/// Specialization for std::vector types to inherit from
+/// boost::true_type
+template <typename T> struct is_matrix<Kernel::Matrix<T>> : boost::true_type {};
+
+//-----------------------------------------------------------------------
+// MatrixRefToNumpyImpl - Policy for reference returns
+//-----------------------------------------------------------------------
 /**
  * Helper struct that implements the conversion
  * policy.
  */
 template <typename MatrixType, typename ConversionPolicy>
-struct ConvertMatrixToNDArray {
+struct MatrixRefToNumpyImpl {
   inline PyObject *operator()(const MatrixType &cmatrix) const {
     return Converters::MatrixToNDArray<typename MatrixType::value_type,
                                        ConversionPolicy>()(cmatrix);
@@ -53,23 +68,11 @@ struct ConvertMatrixToNDArray {
     return Converters::getNDArrayType();
   }
 };
-}
-
-//-----------------------------------------------------------------------
-// return_value_policy
-//-----------------------------------------------------------------------
-namespace {
-/// MPL struct to figure out if a type is a std::vector
-/// The general one inherits from boost::false_type
-template <typename T> struct is_matrix : boost::false_type {};
-
-/// Specialization for std::vector types to inherit from
-/// boost::true_type
-template <typename T> struct is_matrix<Kernel::Matrix<T>> : boost::true_type {};
 
 template <typename T>
-struct MatrixToNumpy_Requires_Reference_To_Matrix_Return_Type {};
+struct MatrixRefToNumpy_Requires_Reference_To_Matrix_Return_Type {};
 }
+
 /**
  * Implements a return value policy that
  * returns a numpy array from a Matrix
@@ -80,7 +83,7 @@ struct MatrixToNumpy_Requires_Reference_To_Matrix_Return_Type {};
  * (2) WrapReadWrite - Creates a read-write array around the original data (no
  *copy is performed)
  */
-template <typename ConversionPolicy> struct MatrixToNumpy {
+template <typename ConversionPolicy> struct MatrixRefToNumpy {
   // The boost::python framework calls return_value_policy::apply<T>::type
   template <class T> struct apply {
     // Typedef that removes and const or reference qualifiers from the return
@@ -91,8 +94,50 @@ template <typename ConversionPolicy> struct MatrixToNumpy {
     typedef typename boost::mpl::if_c<
         boost::mpl::and_<std::is_reference<T>,
                          is_matrix<non_const_type>>::value,
-        ConvertMatrixToNDArray<non_const_type, ConversionPolicy>,
-        MatrixToNumpy_Requires_Reference_To_Matrix_Return_Type<T>>::type type;
+        MatrixRefToNumpyImpl<non_const_type, ConversionPolicy>,
+        MatrixRefToNumpy_Requires_Reference_To_Matrix_Return_Type<T>>::type
+        type;
+  };
+};
+
+//-----------------------------------------------------------------------
+// MatrixToNumpy return_value_policy
+//-----------------------------------------------------------------------
+namespace {
+/**
+ * Helper struct that implements the conversion policy. This can only clone
+ * as wrapping would wrap a temporary
+ */
+template <typename MatrixType> struct MatrixToNumpyImpl {
+  inline PyObject *operator()(const MatrixType &cvector) const {
+    return Converters::MatrixToNDArray<typename MatrixType::value_type,
+                                       Converters::Clone>()(cvector);
+  }
+
+  inline PyTypeObject const *get_pytype() const {
+    return Converters::getNDArrayType();
+  }
+};
+
+template <typename T> struct MatrixToNumpy_Requires_Matrix_Return_By_Value {};
+} // namespace
+
+/**
+ * Implements a return value policy that
+ * returns a numpy array from a function returning a std::vector by value
+ *
+ * It is only possible to clone these types since a wrapper would wrap temporary
+ */
+struct MatrixToNumpy {
+  // The boost::python framework calls return_value_policy::apply<T>::type
+  template <class T> struct apply {
+    // Typedef that removes any const from the type
+    typedef typename std::remove_const<T>::type non_const_type;
+    // MPL compile-time check that T is a std::vector
+    typedef typename boost::mpl::if_c<
+        is_matrix<non_const_type>::value,
+        MatrixRefToNumpyImpl<non_const_type, Converters::Clone>,
+        MatrixToNumpy_Requires_Matrix_Return_By_Value<T>>::type type;
   };
 };
 }
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Registry/TypedPropertyValueHandler.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Registry/TypedPropertyValueHandler.h
index 17cc262a16b9cecf96245cb465345799aa6639a8..73b19f29ecb61bfc7622531af52e0925c031bead 100644
--- a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Registry/TypedPropertyValueHandler.h
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Registry/TypedPropertyValueHandler.h
@@ -116,8 +116,11 @@ struct DLLExport TypedPropertyValueHandler<
    */
   void set(Kernel::IPropertyManager *alg, const std::string &name,
            const boost::python::object &value) const override {
-    alg->setProperty<HeldType>(
-        name, boost::dynamic_pointer_cast<T>(ExtractWorkspace(value)()));
+    if (value == boost::python::object())
+      alg->setProperty<HeldType>(name, boost::shared_ptr<T>(nullptr));
+    else
+      alg->setProperty<HeldType>(
+          name, boost::dynamic_pointer_cast<T>(ExtractWorkspace(value)()));
   }
 
   /**
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/Algorithm.cpp b/Framework/PythonInterface/mantid/api/src/Exports/Algorithm.cpp
index c638c8db879e262202be14334145548851995a2a..9331a1548ac8c27fc8d9b0c303f3c56535d858d1 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/Algorithm.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/Algorithm.cpp
@@ -4,6 +4,9 @@
                                 // design
 #endif
 #include "MantidKernel/WarningSuppressions.h"
+#include "MantidAPI/SerialAlgorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 #include "MantidPythonInterface/api/PythonAlgorithm/AlgorithmAdapter.h"
 #ifdef _MSC_VER
 #pragma warning(default : 4250)
@@ -18,14 +21,23 @@
 #include <boost/python/scope.hpp>
 
 using Mantid::API::Algorithm;
+using Mantid::API::SerialAlgorithm;
+using Mantid::API::ParallelAlgorithm;
+using Mantid::API::DistributedAlgorithm;
 using Mantid::PythonInterface::AlgorithmAdapter;
 using Mantid::Kernel::Direction;
 using namespace boost::python;
 
 GET_POINTER_SPECIALIZATION(Algorithm)
+GET_POINTER_SPECIALIZATION(SerialAlgorithm)
+GET_POINTER_SPECIALIZATION(ParallelAlgorithm)
+GET_POINTER_SPECIALIZATION(DistributedAlgorithm)
 
 namespace {
 typedef AlgorithmAdapter<Algorithm> PythonAlgorithm;
+typedef AlgorithmAdapter<SerialAlgorithm> PythonSerialAlgorithm;
+typedef AlgorithmAdapter<ParallelAlgorithm> PythonParallelAlgorithm;
+typedef AlgorithmAdapter<DistributedAlgorithm> PythonDistributedAlgorithm;
 
 // declarePyAlgProperty(property*,doc)
 typedef void (*declarePropertyType1)(boost::python::object &self,
@@ -148,3 +160,34 @@ void export_leaf_classes() {
   // Python so we simply add an alias of the Algorithm name to PythonAlgorithm
   scope().attr("PythonAlgorithm") = scope().attr("Algorithm");
 }
+
+void export_SerialAlgorithm() {
+  register_ptr_to_python<boost::shared_ptr<SerialAlgorithm>>();
+  register_exception_translator<SerialAlgorithm::CancelException>(
+      &translateCancel);
+  class_<SerialAlgorithm, bases<Mantid::API::Algorithm>,
+         boost::shared_ptr<PythonSerialAlgorithm>, boost::noncopyable>(
+      "SerialAlgorithm", "Base class for simple serial algorithms");
+  scope().attr("PythonSerialAlgorithm") = scope().attr("SerialAlgorithm");
+}
+
+void export_ParallelAlgorithm() {
+  register_ptr_to_python<boost::shared_ptr<ParallelAlgorithm>>();
+  register_exception_translator<ParallelAlgorithm::CancelException>(
+      &translateCancel);
+  class_<ParallelAlgorithm, bases<Mantid::API::Algorithm>,
+         boost::shared_ptr<PythonParallelAlgorithm>, boost::noncopyable>(
+      "ParallelAlgorithm", "Base class for simple parallel algorithms");
+  scope().attr("PythonParallelAlgorithm") = scope().attr("ParallelAlgorithm");
+}
+
+void export_DistributedAlgorithm() {
+  register_ptr_to_python<boost::shared_ptr<DistributedAlgorithm>>();
+  register_exception_translator<DistributedAlgorithm::CancelException>(
+      &translateCancel);
+  class_<DistributedAlgorithm, bases<Mantid::API::Algorithm>,
+         boost::shared_ptr<PythonDistributedAlgorithm>, boost::noncopyable>(
+      "DistributedAlgorithm", "Base class for simple distributed algorithms");
+  scope().attr("PythonDistributedAlgorithm") =
+      scope().attr("DistributedAlgorithm");
+}
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/DataProcessorAlgorithm.cpp b/Framework/PythonInterface/mantid/api/src/Exports/DataProcessorAlgorithm.cpp
index 6532b0e3de5fdb8d468d1258222436b986435806..fa70073058a6c8f1731dc3b643c3e9985df88d8a 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/DataProcessorAlgorithm.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/DataProcessorAlgorithm.cpp
@@ -9,93 +9,104 @@ using Mantid::PythonInterface::DataProcessorAdapter;
 using namespace boost::python;
 
 namespace {
-typedef Workspace_sptr (DataProcessorAdapter::*loadOverload1)(
-    const std::string &);
-typedef Workspace_sptr (DataProcessorAdapter::*loadOverload2)(
+template <class Base>
+using loadOverload1 =
+    Workspace_sptr (DataProcessorAdapter<Base>::*)(const std::string &);
+template <class Base>
+using loadOverload2 = Workspace_sptr (DataProcessorAdapter<Base>::*)(
     const std::string &, const bool);
-}
 
-void export_DataProcessorAlgorithm() {
+template <class Base> void do_export(const std::string &name) {
   // for strings will actually create a list
   using Mantid::PythonInterface::Policies::VectorToNumpy;
+  using Adapter = DataProcessorAdapter<Base>;
 
-  class_<DataProcessorAlgorithm, bases<Algorithm>,
-         boost::shared_ptr<DataProcessorAdapter>, boost::noncopyable>(
-      "DataProcessorAlgorithm", "Base class workflow-type algorithms")
+  class_<GenericDataProcessorAlgorithm<Base>, bases<Base>,
+         boost::shared_ptr<Adapter>, boost::noncopyable>(
+      name.c_str(), "Base class workflow-type algorithms")
 
-      .def("setLoadAlg", &DataProcessorAdapter::setLoadAlgProxy,
-           (arg("self"), arg("alg")),
+      .def("setLoadAlg", &Adapter::setLoadAlgProxy, (arg("self"), arg("alg")),
            "Set the name of the algorithm called using the load() method "
            "[Default=Load]")
 
-      .def("setLoadAlgFileProp", &DataProcessorAdapter::setLoadAlgFilePropProxy,
+      .def("setLoadAlgFileProp", &Adapter::setLoadAlgFilePropProxy,
            (arg("self"), arg("file_prop_name")),
            "Set the name of the file property for the load algorithm when "
            "using "
            "the load() method [Default=Filename]")
 
-      .def("setAccumAlg", &DataProcessorAdapter::setAccumAlgProxy,
-           (arg("self"), arg("alg")),
+      .def("setAccumAlg", &Adapter::setAccumAlgProxy, (arg("self"), arg("alg")),
            "Set the name of the algorithm called to accumulate a chunk of "
            "processed data [Default=Plus]")
 
-      .def("copyProperties", &DataProcessorAdapter::copyPropertiesProxy,
+      .def("copyProperties", &Adapter::copyPropertiesProxy,
            (arg("self"), arg("alg"),
             arg("properties") = boost::python::object(), arg("version") = -1),
            "Copy properties from another algorithm")
 
-      .def("determineChunk", &DataProcessorAdapter::determineChunkProxy,
+      .def("determineChunk", &Adapter::determineChunkProxy,
            (arg("self"), arg("file_name")),
            "Return a TableWorkspace containing the information on how to split "
            "the "
            "input file when processing in chunks")
 
-      .def("loadChunk", &DataProcessorAdapter::loadChunkProxy,
+      .def("loadChunk", &Adapter::loadChunkProxy,
            (arg("self"), arg("row_index")), "Load a chunk of data")
 
-      .def("load", (loadOverload1)&DataProcessorAdapter::loadProxy,
+      .def("load", (loadOverload1<Base>)&Adapter::loadProxy,
            (arg("self"), arg("input_data")),
            "Loads the given file or workspace data and returns the workspace. "
            "The output is not stored in the AnalysisDataService.")
 
-      .def("load", (loadOverload2)&DataProcessorAdapter::loadProxy,
+      .def("load", (loadOverload2<Base>)&Adapter::loadProxy,
            (arg("self"), arg("input_data"), arg("load_quite")),
            "Loads the given file or workspace data and returns the workspace. "
            "If loadQuiet=True then output is not stored in the "
            "AnalysisDataService.")
 
-      .def("splitInput", &DataProcessorAdapter::splitInputProxy,
-           (arg("self"), arg("input")), return_value_policy<VectorToNumpy>())
+      .def("splitInput", &Adapter::splitInputProxy, (arg("self"), arg("input")),
+           return_value_policy<VectorToNumpy>())
 
-      .def("forwardProperties", &DataProcessorAdapter::forwardPropertiesProxy,
-           arg("self"))
+      .def("forwardProperties", &Adapter::forwardPropertiesProxy, arg("self"))
 
-      .def("getProcessProperties",
-           &DataProcessorAdapter::getProcessPropertiesProxy,
+      .def("getProcessProperties", &Adapter::getProcessPropertiesProxy,
            (arg("self"), arg("property_manager")),
            "Returns the named property manager from the service or creates "
            "a new one if it does not exist")
 
-      .def("assemble", &DataProcessorAdapter::assembleProxy,
+      .def("assemble", &Adapter::assembleProxy,
            (arg("self"), arg("partial_wsname"), arg("output_wsname")),
            "If an MPI build, assemble the partial workspaces from all MPI "
            "processes. "
            "Otherwise, simply returns the input workspace")
 
-      .def("saveNexus", &DataProcessorAdapter::saveNexusProxy,
+      .def("saveNexus", &Adapter::saveNexusProxy,
            (arg("self"), arg("output_wsname"), arg("output_filename")),
            "Save a workspace as a nexus file. If this is an MPI build then "
            "saving only "
            "happens for the main thread.")
 
-      .def("isMainThread", &DataProcessorAdapter::isMainThreadProxy,
-           arg("self"),
+      .def("isMainThread", &Adapter::isMainThreadProxy, arg("self"),
            "Returns true if this algorithm is the main thread for an MPI "
            "build. For "
            "non-MPI build it always returns true")
 
-      .def("getNThreads", &DataProcessorAdapter::getNThreadsProxy, arg("self"),
+      .def("getNThreads", &Adapter::getNThreadsProxy, arg("self"),
            "Returns the number of running MPI processes in an MPI build or 1 "
            "for "
            "a non-MPI build");
 }
+}
+
+void export_DataProcessorAlgorithm() {
+  do_export<Algorithm>("DataProcessorAlgorithm");
+}
+void export_SerialDataProcessorAlgorithm() {
+  do_export<SerialAlgorithm>("SerialDataProcessorAlgorithm");
+}
+void export_ParallelDataProcessorAlgorithm() {
+  do_export<ParallelAlgorithm>("ParallelDataProcessorAlgorithm");
+}
+void export_DistributedDataProcessorAlgorithm() {
+  do_export<DistributedAlgorithm>("DistributedDataProcessorAlgorithm");
+}
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
index 65aef8dfa1f90e9bfbf196b4d323bfcb9e5d250f..ecd32b1cad97572234669f688083f1bf099cb4dd 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
@@ -1,11 +1,14 @@
 #include "MantidGeometry/Crystal/IPeak.h"
+#include "MantidPythonInterface/kernel/Converters/CloneToNumpy.h"
 #include "MantidPythonInterface/kernel/Converters/PyObjectToMatrix.h"
 #include "MantidPythonInterface/kernel/GetPointer.h"
+#include "MantidPythonInterface/kernel/Policies/MatrixToNumpy.h"
 #include <boost/optional.hpp>
 #include <boost/python/class.hpp>
 #include <boost/python/register_ptr_to_python.hpp>
 
 using Mantid::Geometry::IPeak;
+using namespace Mantid::PythonInterface;
 using namespace boost::python;
 
 GET_POINTER_SPECIALIZATION(IPeak)
@@ -40,9 +43,13 @@ void setQSampleFrame2(IPeak &peak, Mantid::Kernel::V3D qSampleFrame,
 void setGoniometerMatrix(IPeak &self, const object &data) {
   self.setGoniometerMatrix(Converters::PyObjectToMatrix(data)());
 }
+
 } // namespace
 
 void export_IPeak() {
+  // return_value_policy for read-only numpy array
+  typedef return_value_policy<Policies::MatrixToNumpy> return_copy_to_numpy;
+
   register_ptr_to_python<IPeak *>();
 
   class_<IPeak, boost::noncopyable>("IPeak", no_init)
@@ -154,9 +161,15 @@ void export_IPeak() {
            "Return the # of counts in the bin at its peak")
       .def("setBinCount", &IPeak::setBinCount, (arg("self"), arg("bin_count")),
            "Set the # of counts in the bin at its peak")
+      .def("getGoniometerMatrix", &IPeak::getGoniometerMatrix, arg("self"),
+           return_copy_to_numpy(),
+           "Get the :class:`~mantid.geometry.Goniometer` rotation matrix of "
+           "this peak."
+           "\n\n.. versionadded:: 3.12.0")
       .def("setGoniometerMatrix", &setGoniometerMatrix,
            (arg("self"), arg("goniometerMatrix")),
-           "Set the :class:`~mantid.geometry.Goniometer` of the peak")
+           "Set the :class:`~mantid.geometry.Goniometer` rotation matrix of "
+           "this peak.")
       .def("getRow", &IPeak::getRow, arg("self"),
            "For :class:`~mantid.geometry.RectangularDetector` s only, returns "
            "the row (y) of the pixel of the "
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp b/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp
index 8ab0a7bd5e5f93319d4304d864b5260a37b13524..cce9b24dbfafac081be236d3b8833d505d007483 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp
@@ -172,7 +172,7 @@ void export_Run() {
            (const Mantid::Geometry::Goniometer &(Run::*)() const) &
                Run::getGoniometer,
            arg("self"), return_value_policy<reference_existing_object>(),
-           "Get the oriented lattice for this sample")
+           "Return the Goniometer object associated with this run.")
 
       .def("addProperty", &addProperty,
            (arg("self"), arg("name"), arg("value"), arg("replace")),
diff --git a/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/AlgorithmAdapter.cpp b/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/AlgorithmAdapter.cpp
index 515d5c2052a7a5bfdb6caad6bf8e508e99b6dffd..6d9358d85c22bde259d8e828fe598f2541187999 100644
--- a/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/AlgorithmAdapter.cpp
+++ b/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/AlgorithmAdapter.cpp
@@ -4,6 +4,9 @@
 #include "MantidPythonInterface/kernel/Environment/CallMethod.h"
 #include "MantidPythonInterface/kernel/Environment/GlobalInterpreterLock.h"
 #include "MantidAPI/DataProcessorAlgorithm.h"
+#include "MantidAPI/SerialAlgorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 
 #include <boost/python/class.hpp>
 #include <boost/python/dict.hpp>
@@ -310,7 +313,13 @@ template <typename BaseAlgorithm> void AlgorithmAdapter<BaseAlgorithm>::exec() {
 //-----------------------------------------------------------------------------------------------------------------------------
 /// API::Algorithm as base
 template class AlgorithmAdapter<API::Algorithm>;
+template class AlgorithmAdapter<API::SerialAlgorithm>;
+template class AlgorithmAdapter<API::ParallelAlgorithm>;
+template class AlgorithmAdapter<API::DistributedAlgorithm>;
 /// API::DataProcesstor as base
 template class AlgorithmAdapter<API::DataProcessorAlgorithm>;
+template class AlgorithmAdapter<API::SerialDataProcessorAlgorithm>;
+template class AlgorithmAdapter<API::ParallelDataProcessorAlgorithm>;
+template class AlgorithmAdapter<API::DistributedDataProcessorAlgorithm>;
 }
 }
diff --git a/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/DataProcessorAdapter.cpp b/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/DataProcessorAdapter.cpp
index bb75454db88695166771bfece58ecc10b171d050..a69ec5a89542dfca5c352bebab3d61266069290d 100644
--- a/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/DataProcessorAdapter.cpp
+++ b/Framework/PythonInterface/mantid/api/src/PythonAlgorithm/DataProcessorAdapter.cpp
@@ -10,6 +10,13 @@ namespace PythonInterface {
  * Construct the "wrapper" and stores the reference to the PyObject
  * @param self A reference to the calling Python object
  */
-DataProcessorAdapter::DataProcessorAdapter(PyObject *self) : SuperClass(self) {}
+template <class Base>
+DataProcessorAdapter<Base>::DataProcessorAdapter(PyObject *self)
+    : AlgorithmAdapter<API::GenericDataProcessorAlgorithm<Base>>(self) {}
+
+template class DataProcessorAdapter<API::Algorithm>;
+template class DataProcessorAdapter<API::SerialAlgorithm>;
+template class DataProcessorAdapter<API::ParallelAlgorithm>;
+template class DataProcessorAdapter<API::DistributedAlgorithm>;
 }
 }
diff --git a/Framework/PythonInterface/mantid/dataobjects/src/Exports/MaskWorkspace.cpp b/Framework/PythonInterface/mantid/dataobjects/src/Exports/MaskWorkspace.cpp
index 052c61bf3ef9ca530758ce9f61300f32f68bb989..f1c720880cbf865281e9297d70b80e05c7490f32 100644
--- a/Framework/PythonInterface/mantid/dataobjects/src/Exports/MaskWorkspace.cpp
+++ b/Framework/PythonInterface/mantid/dataobjects/src/Exports/MaskWorkspace.cpp
@@ -13,7 +13,9 @@ GET_POINTER_SPECIALIZATION(MaskWorkspace)
 
 void export_MaskWorkspace() {
   class_<MaskWorkspace, bases<SpecialWorkspace2D, IMaskWorkspace>,
-         boost::noncopyable>("MaskWorkspace", no_init);
+         boost::noncopyable>("MaskWorkspace", no_init)
+      .def("getMaskedDetectors", &MaskWorkspace::getMaskedDetectors,
+           arg("self"), "Returns all masked detector IDs.");
 
   // register pointers
   RegisterWorkspacePtrToPython<MaskWorkspace>();
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/Goniometer.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/Goniometer.cpp
index c240d2f1b2a27d287fe7b890bfb9235dfdb7705a..99f9c702f97216cda4c07f1ad073c7d4ecd57d85 100644
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/Goniometer.cpp
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/Goniometer.cpp
@@ -42,8 +42,8 @@ void setR(Goniometer &self, const object &data) {
 void export_Goniometer() {
 
   // return_value_policy for read-only numpy array
-  typedef return_value_policy<Policies::MatrixToNumpy<Converters::WrapReadOnly>>
-      return_readonly_numpy;
+  typedef return_value_policy<Policies::MatrixRefToNumpy<
+      Converters::WrapReadOnly>> return_readonly_numpy;
 
   class_<Goniometer>("Goniometer", init<>(arg("self")))
       .def(init<Goniometer const &>((arg("self"), arg("other"))))
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/OrientedLattice.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/OrientedLattice.cpp
index dbc2b67f546cb0a6ca699ce616fe3faca20c5fc6..1256ce10fc66ad9dec6b1e4fd4d9c93ea20fa911 100644
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/OrientedLattice.cpp
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/OrientedLattice.cpp
@@ -45,8 +45,8 @@ Mantid::Kernel::V3D hklFromQ(OrientedLattice &self, const object &vec) {
 
 void export_OrientedLattice() {
   /// return_value_policy for read-only numpy array
-  typedef return_value_policy<Policies::MatrixToNumpy<Converters::WrapReadOnly>>
-      return_readonly_numpy;
+  typedef return_value_policy<Policies::MatrixRefToNumpy<
+      Converters::WrapReadOnly>> return_readonly_numpy;
 
   class_<OrientedLattice, bases<UnitCell>>(
       "OrientedLattice",
diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/UnitCell.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/UnitCell.cpp
index ba908a54dd3a2e766e60826eb075489e821d9cef..38682c016d52d687b2f41af37f75da13dce45a09 100644
--- a/Framework/PythonInterface/mantid/geometry/src/Exports/UnitCell.cpp
+++ b/Framework/PythonInterface/mantid/geometry/src/Exports/UnitCell.cpp
@@ -67,8 +67,8 @@ void export_UnitCell() {
       .export_values();
 
   /// return_value_policy for read-only numpy array
-  typedef return_value_policy<Policies::MatrixToNumpy<Converters::WrapReadOnly>>
-      return_readonly_numpy;
+  typedef return_value_policy<Policies::MatrixRefToNumpy<
+      Converters::WrapReadOnly>> return_readonly_numpy;
 
   class_<UnitCell>(
       "UnitCell",
diff --git a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
index 0534521c8c402218c30f4d96a27515af427f3f14..9b540e18692ffd5dfc8ece5dab2c773d49896332 100644
--- a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
@@ -76,6 +76,7 @@ set ( SRC_FILES
   src/Converters/NumpyFunctions.cpp
   src/Converters/PyArrayType.cpp
   src/Converters/PyObjectToMatrix.cpp
+  src/Converters/PyObjectToString.cpp
   src/Converters/PyObjectToV3D.cpp
   src/Converters/PyObjectToVMD.cpp
   src/Converters/WrapWithNumpy.cpp
@@ -103,6 +104,7 @@ set ( INC_FILES
   ${HEADER_DIR}/kernel/Converters/VectorToNDArray.h
   ${HEADER_DIR}/kernel/Converters/PyArrayType.h
   ${HEADER_DIR}/kernel/Converters/PyObjectToMatrix.h
+  ${HEADER_DIR}/kernel/Converters/PyObjectToString.h
   ${HEADER_DIR}/kernel/Converters/PyObjectToV3D.h
   ${HEADER_DIR}/kernel/Converters/PyObjectToVMD.h
   ${HEADER_DIR}/kernel/Converters/PySequenceToVector.h
diff --git a/Framework/PythonInterface/mantid/kernel/src/Converters/NDArrayToVector.cpp b/Framework/PythonInterface/mantid/kernel/src/Converters/NDArrayToVector.cpp
index 3298cfb2fae2c4bac24c119a0209e692965a3f2b..94668f5a1d3fd66fc43bc29e7028c12a7f9e7fd5 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Converters/NDArrayToVector.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Converters/NDArrayToVector.cpp
@@ -9,6 +9,11 @@
 #include <boost/python/stl_iterator.hpp>
 #include <boost/python/str.hpp>
 
+using boost::python::extract;
+using boost::python::handle;
+using boost::python::object;
+using boost::python::str;
+
 namespace Mantid {
 namespace PythonInterface {
 namespace Converters {
@@ -46,12 +51,12 @@ template <typename DestElementType> struct CopyToImpl {
       void *input;
     } npy_union;
     npy_union data;
-    PyObject *iter = Converters::Impl::func_PyArray_IterNew(arr);
+    object iter(handle<>(Converters::Impl::func_PyArray_IterNew(arr)));
     do {
-      data.input = PyArray_ITER_DATA(iter);
+      data.input = PyArray_ITER_DATA(iter.ptr());
       *first++ = *data.output;
-      PyArray_ITER_NEXT(iter);
-    } while (PyArray_ITER_NOTDONE(iter));
+      PyArray_ITER_NEXT(iter.ptr());
+    } while (PyArray_ITER_NOTDONE(iter.ptr()));
   }
 };
 
@@ -62,7 +67,6 @@ template <typename DestElementType> struct CopyToImpl {
 template <> struct CopyToImpl<std::string> {
   void operator()(typename std::vector<std::string>::iterator first,
                   PyArrayObject *arr) {
-    using namespace boost::python;
     object flattened(handle<>(PyArray_Ravel(arr, NPY_CORDER)));
     const Py_ssize_t nelements = PyArray_Size(flattened.ptr());
     for (Py_ssize_t i = 0; i < nelements; ++i) {
@@ -88,7 +92,7 @@ template <typename DestElementType> struct CoerceType {
  * to convert the underlying representation
  */
 template <> struct CoerceType<std::string> {
-  boost::python::object operator()(const NumPy::NdArray &x) { return x; }
+  object operator()(const NumPy::NdArray &x) { return x; }
 };
 }
 
@@ -143,7 +147,6 @@ void NDArrayToVector<DestElementType>::copyTo(TypedVector &dest) const {
 template <typename DestElementType>
 void NDArrayToVector<DestElementType>::throwIfSizeMismatched(
     const TypedVector &dest) const {
-  namespace bp = boost::python;
   if (PyArray_SIZE((PyArrayObject *)m_arr.ptr()) ==
       static_cast<ssize_t>(dest.size())) {
     return;
diff --git a/Framework/PythonInterface/mantid/kernel/src/Converters/PyObjectToString.cpp b/Framework/PythonInterface/mantid/kernel/src/Converters/PyObjectToString.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7a2f541c2ddd8930c4a0d8310fd24a8aa5928160
--- /dev/null
+++ b/Framework/PythonInterface/mantid/kernel/src/Converters/PyObjectToString.cpp
@@ -0,0 +1,30 @@
+#include "MantidPythonInterface/kernel/Converters/PyObjectToString.h"
+#include <boost/python/extract.hpp>
+#include <boost/python/str.hpp>
+
+using namespace boost;
+
+namespace Mantid {
+namespace PythonInterface {
+namespace Converters {
+
+std::string pyObjToStr(const python::object &value) {
+  python::extract<std::string> extractor(value);
+
+  std::string valuestr;
+  if (extractor.check()) {
+    valuestr = extractor();
+#if PY_VERSION_HEX < 0x03000000
+  } else if (PyUnicode_Check(value.ptr())) {
+    valuestr =
+        python::extract<std::string>(python::str(value).encode("utf-8"))();
+#endif
+  } else {
+    throw std::invalid_argument("Failed to convert python object a string");
+  }
+  return valuestr;
+}
+
+} // namespace Converters
+} // namespace PythonInterface
+} // namespace Mantid
diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/IPropertyManager.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/IPropertyManager.cpp
index 0120cc605fc0b9378022a7bd9b5513f877673b60..4c52435a6c720929ece6a55b312bac62922ec3ee 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Exports/IPropertyManager.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Exports/IPropertyManager.cpp
@@ -1,9 +1,10 @@
 #include "MantidKernel/IPropertyManager.h"
 #include "MantidKernel/IPropertySettings.h"
+#include "MantidPythonInterface/kernel/Converters/PyObjectToString.h"
 #include "MantidPythonInterface/kernel/GetPointer.h"
-#include "MantidPythonInterface/kernel/Registry/TypeRegistry.h"
 #include "MantidPythonInterface/kernel/Registry/PropertyValueHandler.h"
 #include "MantidPythonInterface/kernel/Registry/PropertyWithValueFactory.h"
+#include "MantidPythonInterface/kernel/Registry/TypeRegistry.h"
 
 #include <boost/python/class.hpp>
 #include <boost/python/copy_const_reference.hpp>
@@ -17,32 +18,12 @@
 
 using namespace Mantid::Kernel;
 namespace Registry = Mantid::PythonInterface::Registry;
+namespace Converters = Mantid::PythonInterface::Converters;
 using namespace boost::python;
 
 GET_POINTER_SPECIALIZATION(IPropertyManager)
 
 namespace {
-
-/**
- * Convert a python object to a string or throw an exception. This will convert
- * unicode strings in python2 via utf8.
- */
-std::string pyObjToStr(const boost::python::object &value) {
-  extract<std::string> extractor(value);
-
-  std::string valuestr;
-  if (extractor.check()) {
-    valuestr = extractor();
-#if PY_VERSION_HEX < 0x03000000
-  } else if (PyUnicode_Check(value.ptr())) {
-    valuestr = extract<std::string>(str(value).encode("utf-8"))();
-#endif
-  } else {
-    throw std::invalid_argument("Failed to convert python object a string");
-  }
-  return valuestr;
-}
-
 /**
  * Set the value of a property from the value within the
  * boost::python object
@@ -55,7 +36,7 @@ void setProperty(IPropertyManager &self, const boost::python::object &name,
                  const boost::python::object &value) {
   std::string namestr;
   try {
-    namestr = pyObjToStr(name);
+    namestr = Converters::pyObjToStr(name);
   } catch (std::invalid_argument &) {
     throw std::invalid_argument("Failed to convert property name to a string");
   }
@@ -103,7 +84,7 @@ void setProperties(IPropertyManager &self, const boost::python::dict &kwargs) {
  */
 void declareProperty(IPropertyManager &self, const boost::python::object &name,
                      boost::python::object value) {
-  std::string nameStr = pyObjToStr(name);
+  std::string nameStr = Converters::pyObjToStr(name);
   auto p = std::unique_ptr<Property>(
       Registry::PropertyWithValueFactory::create(nameStr, value, 0));
   self.declareProperty(std::move(p));
@@ -120,7 +101,7 @@ void declareProperty(IPropertyManager &self, const boost::python::object &name,
 void declareOrSetProperty(IPropertyManager &self,
                           const boost::python::object &name,
                           boost::python::object value) {
-  std::string nameStr = pyObjToStr(name);
+  std::string nameStr = Converters::pyObjToStr(name);
   bool propExists = self.existsProperty(nameStr);
   if (propExists) {
     setProperty(self, name, value);
diff --git a/Framework/PythonInterface/mantid/plots/functions.py b/Framework/PythonInterface/mantid/plots/functions.py
index 1a0aa2c2ffd0b59fd658d7edbea889642b16bad4..7f578b8e6a551baeb56e5a8eff3560f1e100c9aa 100644
--- a/Framework/PythonInterface/mantid/plots/functions.py
+++ b/Framework/PythonInterface/mantid/plots/functions.py
@@ -45,7 +45,7 @@ def getAxesLabels(workspace):
     Returns a tuple. The first element is the quantity label, such as "Intensity" or "Counts".
     All other elements in the tuple are labels for axes.
     Some of them are latex formatted already.
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace` 
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`
     '''
     if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
         axes = ['Intensity']
@@ -101,7 +101,11 @@ def _getWkspIndexDistAndLabel(workspace, **kwargs):
 
     # create a label if it isn't already specified
     if 'label' not in kwargs:
-        kwargs['label'] = 'spec {0}'.format(specNum)
+        wsName = workspace.name()
+        if wsName:
+            kwargs['label'] = '{0}: spec {1}'.format(wsName, specNum)
+        else:
+            kwargs['label'] = 'spec {0}'.format(specNum)
 
     (distribution, kwargs) = _getDistribution(workspace, **kwargs)
 
@@ -140,7 +144,9 @@ def _getSpectrum(workspace, wkspIndex, distribution, withDy=False, withDx=False)
             if dy is not None:
                 dy = dy / (x[1:] - x[0:-1])
         x = points_from_boundaries(x)
-
+    y=numpy.ma.masked_invalid(y)
+    if dy is not None:
+        dy=numpy.ma.masked_invalid(dy)
     return (x,y,dy,dx)
 
 
@@ -195,6 +201,7 @@ def _getMatrix2DData(workspace, distribution,histogram2D=False):
             if len(y)==z.shape[0]+1:
                 y=points_from_boundaries(y)
     y = numpy.tile(y,x.shape[1]).reshape(x.shape[1],x.shape[0]).transpose()
+    z=numpy.ma.masked_invalid(z)
     return (x,y,z)
 
 
@@ -242,6 +249,7 @@ def _getUnevenData(workspace, distribution):
                 zvals = zvals / (xvals[1:] - xvals[0:-1])
         else:
             xvals=boundaries_from_points(xvals)
+        zvals=numpy.ma.masked_invalid(zvals)
         z.append(zvals)
         x.append(xvals)
         y.append([yvals[index],yvals[index+1]])
@@ -309,8 +317,10 @@ def _getMDData(workspace,normalization,withError=False):
             err2/=(nev*nev)
         err=numpy.sqrt(err2)
     data=data.squeeze().T
+    data=numpy.ma.masked_invalid(data)
     if err is not None:
         err=err.squeeze().T
+        err=numpy.ma.masked_invalid(err)       
     return (dimarrays,data,err)
 
 
@@ -536,7 +546,7 @@ def _pcolorpieces(axes, workspace, distribution, *args,**kwargs):
                          divide by bin width. ``True`` means do not divide by bin width.
                          Applies only when the the matrix workspace is a histogram.
     :param pcolortype: this keyword allows the plotting to be one of pcolormesh or
-        pcolorfast if there is "mesh" or "fast" in the value of the keyword, or 
+        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
     '''
@@ -554,7 +564,7 @@ def _pcolorpieces(axes, workspace, distribution, *args,**kwargs):
         if kwargs['norm'].vmin==None:
             kwargs['norm'].vmin=mini
         if kwargs['norm'].vmax==None:
-            kwargs['norm'].vmax=maxi    
+            kwargs['norm'].vmax=maxi
     for xi,yi,zi in zip(x,y,z):
         XX,YY=numpy.meshgrid(xi,yi,indexing='ij')
         if 'mesh' in pcolortype.lower():
@@ -564,7 +574,7 @@ def _pcolorpieces(axes, workspace, distribution, *args,**kwargs):
         else:
             cm=axes.pcolor(XX,YY,zi.reshape(-1,1),**kwargs)
     return cm
-    
+
 
 def pcolor(axes, workspace, *args, **kwargs):
     '''
diff --git a/Framework/PythonInterface/plugins/algorithms/ExportSampleLogsToCSVFile.py b/Framework/PythonInterface/plugins/algorithms/ExportSampleLogsToCSVFile.py
index 5eab04d14a55c5e6d8a1f691e0491c0a0d71c65b..be7dd8390cf2485a6eb3d434089d55f1311c5faf 100644
--- a/Framework/PythonInterface/plugins/algorithms/ExportSampleLogsToCSVFile.py
+++ b/Framework/PythonInterface/plugins/algorithms/ExportSampleLogsToCSVFile.py
@@ -2,6 +2,7 @@
 from __future__ import (absolute_import, division, print_function)
 from mantid.api import *
 from mantid.kernel import *
+from distutils.version import LooseVersion
 import numpy as np
 import os
 from six.moves import range # pylint: disable=redefined-builtin
@@ -179,7 +180,8 @@ class ExportSampleLogsToCSVFile(PythonAlgorithm):
             localtimediff = np.timedelta64(0, 's')
 
         epoch = '1990-01-01T00:00'
-        if np.__version__.startswith('1.7.'):
+        # older numpy assumes local timezone
+        if LooseVersion(np.__version__) < LooseVersion('1.9'):
             epoch = epoch+'Z'
         return np.datetime64(epoch) + localtimediff
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLApplySelfShielding.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLApplySelfShielding.py
index 813a968befdde24c2d87a5824ac8e219e2bb4669..7012124906bf21223a2b74fe2b2d3a6768e87488 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLApplySelfShielding.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLApplySelfShielding.py
@@ -90,11 +90,11 @@ class DirectILLApplySelfShielding(DataProcessorAlgorithm):
             defaultValue='',
             validator=inputWorkspaceValidator,
             direction=Direction.Input),
-            doc='Input workspace.')
+            doc='A workspace to which to apply the corrections.')
         self.declareProperty(WorkspaceProperty(name=common.PROP_OUTPUT_WS,
                                                defaultValue='',
                                                direction=Direction.Output),
-                             doc='The output of the algorithm.')
+                             doc='The corrected workspace.')
         self.declareProperty(name=common.PROP_CLEANUP_MODE,
                              defaultValue=common.CLEANUP_ON,
                              validator=StringListValidator([
@@ -116,19 +116,19 @@ class DirectILLApplySelfShielding(DataProcessorAlgorithm):
             validator=inputWorkspaceValidator,
             direction=Direction.Input,
             optional=PropertyMode.Optional),
-            doc='Reduced empty container workspace.')
+            doc='An empty container workspace for subtraction from the input workspace.')
         self.declareProperty(name=common.PROP_EC_SCALING,
                              defaultValue=1.0,
                              validator=scalingFactor,
                              direction=Direction.Input,
-                             doc='Scaling factor (transmission, if no self ' +
-                                 'shielding is applied) for empty container.')
+                             doc='A multiplier (transmission, if no self ' +
+                                 'shielding is applied) for the empty container.')
         self.declareProperty(MatrixWorkspaceProperty(
             name=common.PROP_SELF_SHIELDING_CORRECTION_WS,
             defaultValue='',
             direction=Direction.Input,
             optional=PropertyMode.Optional),
-            doc='A workspace containing self shielding correction factors.')
+            doc='A workspace containing the self shielding correction factors.')
 
     def _applyCorrections(self, mainWS, wsNames, wsCleanup, subalgLogging):
         """Applies self shielding corrections to a workspace, if corrections exist."""
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLCollectData.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLCollectData.py
index 6c36c7e0fd2daff0d858b3c51ff1175a4b932bc8..6b470543e7af76e02e02eb47d6bd20426b7a7720 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLCollectData.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLCollectData.py
@@ -353,7 +353,7 @@ class DirectILLCollectData(DataProcessorAlgorithm):
         self.declareProperty(WorkspaceProperty(name=common.PROP_OUTPUT_WS,
                                                defaultValue='',
                                                direction=Direction.Output),
-                             doc='The output of the algorithm.')
+                             doc='A flux normalized and background subtracted workspace.')
         self.declareProperty(name=common.PROP_CLEANUP_MODE,
                              defaultValue=common.CLEANUP_ON,
                              validator=StringListValidator([
@@ -399,13 +399,13 @@ class DirectILLCollectData(DataProcessorAlgorithm):
                                  common.ELASTIC_CHANNEL_SAMPLE_LOG,
                                  common.ELASTIC_CHANNEL_FIT]),
                              direction=Direction.Input,
-                             doc='How to acquire the elastic channel.')
+                             doc='How to acquire the nominal elastic channel.')
         self.declareProperty(MatrixWorkspaceProperty(
                              name=common.PROP_ELASTIC_CHANNEL_WS,
                              defaultValue='',
                              direction=Direction.Input,
                              optional=PropertyMode.Optional),
-                             doc='A single value workspace containing the elatic channel index. Overrides '
+                             doc='A single value workspace containing the nominal elastic channel index. Overrides '
                                  + common.PROP_ELASTIC_CHANNEL_MODE + '.')
         self.declareProperty(name=common.PROP_MON_INDEX,
                              defaultValue=Property.EMPTY_INT,
@@ -426,7 +426,7 @@ class DirectILLCollectData(DataProcessorAlgorithm):
             defaultValue='',
             direction=Direction.Input,
             optional=PropertyMode.Optional),
-            doc='A single-valued workspace holding the calibrated ' +
+            doc='A single-valued workspace holding a previously determined ' +
                 'incident energy.')
         self.setPropertyGroup(common.PROP_INCIDENT_ENERGY_WS, PROPGROUP_INCIDENT_ENERGY_CALIBRATION)
         self.declareProperty(name=common.PROP_FLAT_BKG,
@@ -442,7 +442,7 @@ class DirectILLCollectData(DataProcessorAlgorithm):
                              defaultValue=1.0,
                              validator=positiveFloat,
                              direction=Direction.Input,
-                             doc='Flat background scaling constant')
+                             doc='Flat background multiplication factor.')
         self.setPropertyGroup(common.PROP_FLAT_BKG_SCALING, PROPGROUP_FLAT_BKG)
         self.declareProperty(name=common.PROP_FLAT_BKG_WINDOW,
                              defaultValue=30,
@@ -456,7 +456,7 @@ class DirectILLCollectData(DataProcessorAlgorithm):
             defaultValue='',
             direction=Direction.Input,
             optional=PropertyMode.Optional),
-            doc='Workspace from which to get flat background data.')
+            doc='Workspace with previously determined flat background data.')
         self.setPropertyGroup(common.PROP_FLAT_BKG_WS, PROPGROUP_FLAT_BKG)
         self.declareProperty(name=common.PROP_NORMALISATION,
                              defaultValue=common.NORM_METHOD_MON,
@@ -480,7 +480,7 @@ class DirectILLCollectData(DataProcessorAlgorithm):
             defaultValue='',
             direction=Direction.Output,
             optional=PropertyMode.Optional),
-            doc='Output the merged runs or ' + common.PROP_INPUT_WS + ' as is.')
+            doc='Non-normalized and non-background subtracted output workspace for DirectILLDiagnostics.')
         self.setPropertyGroup(common.PROP_OUTPUT_RAW_WS,
                               common.PROPGROUP_OPTIONAL_OUTPUT)
         self.declareProperty(WorkspaceProperty(
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLDiagnostics.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLDiagnostics.py
index 14e0c5ec94d6baa1043b130eec5786c591ca0bbb..8427fba7c57ae1a6bf97932dc2e5e8b7939b8a93 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLDiagnostics.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLDiagnostics.py
@@ -419,7 +419,7 @@ class DirectILLDiagnostics(DataProcessorAlgorithm):
             defaultValue='',
             validator=inputWorkspaceValidator,
             direction=Direction.Input),
-            doc='Raw input workspace.')
+            doc="A 'raw' workspace from DirectILLCollectData to calculate the diagnostics from.")
         self.declareProperty(WorkspaceProperty(name=common.PROP_OUTPUT_WS,
                                                defaultValue='',
                                                direction=Direction.Output),
@@ -483,8 +483,8 @@ class DirectILLDiagnostics(DataProcessorAlgorithm):
                              defaultValue=3.3,
                              validator=positiveFloat,
                              direction=Direction.Input,
-                             doc='Error bar multiplier for significance ' +
-                                 'test in the elastic peak diagnostics.')
+                             doc='To fail the elastic peak diagnostics, the intensity must also exceed ' +
+                                 'this number of error bars with respect to the median intensity.')
         self.setPropertyGroup(common.PROP_PEAK_DIAGNOSTICS_SIGNIFICANCE_TEST,
                               PROPGROUP_PEAK_DIAGNOSTICS)
         self.declareProperty(name=common.PROP_BKG_DIAGNOSTICS,
@@ -524,8 +524,8 @@ class DirectILLDiagnostics(DataProcessorAlgorithm):
                              defaultValue=3.3,
                              validator=positiveFloat,
                              direction=Direction.Input,
-                             doc='Error bar multiplier for significance ' +
-                                 'test in the noisy background diagnostics.')
+                             doc='To fail the background diagnostics, the background level must also exceed ' +
+                                 'this number of error bars with respect to the median level.')
         self.setPropertyGroup(common.PROP_BKG_DIAGNOSTICS_SIGNIFICANCE_TEST,
                               PROPGROUP_BKG_DIAGNOSTICS)
         self.declareProperty(name=common.PROP_BEAM_STOP_DIAGNOSTICS,
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLIntegrateVanadium.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLIntegrateVanadium.py
index a4f90a6eda404e40fea30b0f39a952ef72750c38..0d719a89e78c75e37331c10c3f79a25aee067d17 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLIntegrateVanadium.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLIntegrateVanadium.py
@@ -62,11 +62,11 @@ class DirectILLIntegrateVanadium(DataProcessorAlgorithm):
             validator=inputWorkspaceValidator,
             optional=PropertyMode.Mandatory,
             direction=Direction.Input),
-            doc='Input workspace.')
+            doc='A workspace to be integrated.')
         self.declareProperty(WorkspaceProperty(name=common.PROP_OUTPUT_WS,
                                                defaultValue='',
                                                direction=Direction.Output),
-                             doc='The output of the algorithm.')
+                             doc='The integrated workspace.')
         self.declareProperty(name=common.PROP_CLEANUP_MODE,
                              defaultValue=common.CLEANUP_ON,
                              validator=StringListValidator([
@@ -99,8 +99,8 @@ class DirectILLIntegrateVanadium(DataProcessorAlgorithm):
                              defaultValue=Property.EMPTY_DBL,
                              validator=positiveFloat,
                              direction=Direction.Input,
-                             doc='Experimental temperature (Vanadium ' +
-                                 'reduction type only) for the Debye-Waller correction, in Kelvins.')
+                             doc='Vanadium temperature in Kelvin for Debye-Waller correction, ' +
+                                 'overrides the default value from the sample logs.')
         self.setPropertySettings(common.PROP_TEMPERATURE, EnabledWhenProperty(common.PROP_DWF_CORRECTION,
                                                                               PropertyCriterion.IsDefault))
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLReduction.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLReduction.py
index 01db9d40152aea1903b7a9dffd338a7a9fce3f6e..3d4b7bee8fb559a0726f4569fd8310ef0b1a5b30 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLReduction.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLReduction.py
@@ -252,11 +252,11 @@ class DirectILLReduction(DataProcessorAlgorithm):
             defaultValue='',
             validator=inputWorkspaceValidator,
             direction=Direction.Input),
-            doc='Input workspace.')
+            doc='A workspace to reduce.')
         self.declareProperty(WorkspaceProperty(name=common.PROP_OUTPUT_WS,
                                                defaultValue='',
                                                direction=Direction.Output),
-                             doc='The output of the algorithm.')
+                             doc='The reduced S(Q, DeltaE) workspace.')
         self.declareProperty(name=common.PROP_CLEANUP_MODE,
                              defaultValue=common.CLEANUP_ON,
                              validator=StringListValidator([
@@ -278,7 +278,7 @@ class DirectILLReduction(DataProcessorAlgorithm):
             validator=inputWorkspaceValidator,
             direction=Direction.Input,
             optional=PropertyMode.Optional),
-            doc='Reduced vanadium workspace.')
+            doc='An integrated vanadium workspace.')
         self.declareProperty(name=common.PROP_ABSOLUTE_UNITS,
                              defaultValue=common.ABSOLUTE_UNITS_OFF,
                              validator=StringListValidator([
@@ -291,8 +291,7 @@ class DirectILLReduction(DataProcessorAlgorithm):
             defaultValue='',
             direction=Direction.Input,
             optional=PropertyMode.Optional),
-            doc='Detector diagnostics workspace obtained from another ' +
-                'reduction run.')
+            doc='Detector diagnostics workspace for masking.')
         self.declareProperty(FloatArrayProperty(name=common.PROP_REBINNING_PARAMS_W),
                              doc='Manual energy rebinning parameters.')
         self.setPropertyGroup(common.PROP_REBINNING_PARAMS_W, PROPGROUP_REBINNING)
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLSelfShielding.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLSelfShielding.py
index 9d6ca294d7449ee3b30d3a8d555ed61fc97d51b9..5c1391a418ed55800d7d0b54b5dbc8b70ade9a9f 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLSelfShielding.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/DirectILLSelfShielding.py
@@ -65,11 +65,11 @@ class DirectILLSelfShielding(DataProcessorAlgorithm):
             validator=inputWorkspaceValidator,
             optional=PropertyMode.Optional,
             direction=Direction.Input),
-            doc='Input workspace.')
+            doc='A workspace for which to simulate the self shielding.')
         self.declareProperty(MatrixWorkspaceProperty(name=common.PROP_OUTPUT_WS,
                                                      defaultValue='',
                                                      direction=Direction.Output),
-                             doc='The output corrections workspace.')
+                             doc='A workspace containing the self shielding correction factors.')
         self.declareProperty(name=common.PROP_CLEANUP_MODE,
                              defaultValue=common.CLEANUP_ON,
                              validator=StringListValidator([
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/PowderDiffILLDetScanReduction.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/PowderDiffILLDetScanReduction.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fd2620d37e01db78dbdb4589a4a63094589ee76
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/PowderDiffILLDetScanReduction.py
@@ -0,0 +1,134 @@
+from __future__ import (absolute_import, division, print_function)
+
+from mantid.kernel import CompositeValidator, Direction, FloatArrayLengthValidator, FloatArrayOrderedPairsValidator, \
+    FloatArrayProperty, StringListValidator
+from mantid.api import DataProcessorAlgorithm, MultipleFileProperty, Progress, WorkspaceGroupProperty
+from mantid.simpleapi import *
+
+
+class PowderDiffILLDetScanReduction(DataProcessorAlgorithm):
+    _progress = None
+
+    def category(self):
+        return "ILL\\Diffraction;Diffraction\\Reduction"
+
+    def summary(self):
+        return 'Performs powder diffraction data reduction for D2B and D20 (when doing a detector scan).'
+
+    def name(self):
+        return "PowderDiffILLDetScanReduction"
+
+    def validateInputs(self):
+        issues = dict()
+
+        if not (self.getProperty("Output2DTubes").value or
+                self.getProperty("Output2D").value or
+                self.getProperty("Output1D").value):
+            issues['Output2DTubes'] = 'No output chosen'
+            issues['Output2D'] = 'No output chosen'
+            issues['Output1D'] = 'No output chosen'
+
+        return issues
+
+    def PyInit(self):
+
+        self.declareProperty(MultipleFileProperty('Run', extensions=['nxs']),
+                             doc='File path of run(s).')
+
+        self.declareProperty(name='NormaliseTo',
+                             defaultValue='Monitor',
+                             validator=StringListValidator(['None', 'Monitor']),
+                             doc='Normalise to monitor, or skip normalisation.')
+
+        self.declareProperty(name='UseCalibratedData',
+                             defaultValue=True,
+                             doc='Whether or not to use the calibrated data in the NeXus files.')
+
+        self.declareProperty(name='Output2DTubes',
+                             defaultValue=False,
+                             doc='Output a 2D workspace of height along tube against tube scattering angle.')
+
+        self.declareProperty(name='Output2D',
+                             defaultValue=False,
+                             doc='Output a 2D workspace of height along tube against the real scattering angle.')
+
+        self.declareProperty(name='Output1D',
+                             defaultValue=True,
+                             doc='Output a 1D workspace with counts against scattering angle.')
+
+        self.declareProperty(FloatArrayProperty(name='HeightRange', values=[],
+                                                validator=CompositeValidator([FloatArrayOrderedPairsValidator(),
+                                                                              FloatArrayLengthValidator(0, 2)])),
+                             doc='A pair of values, comma separated, to give the minimum and maximum height range (in m). If not specified '
+                                 'the full height range is used.')
+
+        self.declareProperty(WorkspaceGroupProperty('OutputWorkspace', '',
+                                                    direction=Direction.Output),
+                             doc='Output workspace containing the reduced data.')
+
+    def PyExec(self):
+        data_type = 'Raw'
+        if self.getProperty('UseCalibratedData').value:
+            data_type = 'Calibrated'
+
+        self._progress = Progress(self, start=0.0, end=1.0, nreports=6)
+
+        self._progress.report('Loading data')
+        input_workspace = LoadAndMerge(Filename=self.getPropertyValue('Run'),
+                                       LoaderName='LoadILLDiffraction',
+                                       LoaderOptions={'DataType': data_type})
+        # We might already have a group, but group just in case
+        input_group = GroupWorkspaces(InputWorkspaces=input_workspace)
+
+        instrument_name = input_group[0].getInstrument().getName()
+        supported_instruments = ['D2B', 'D20']
+        if instrument_name not in supported_instruments:
+            self.log.warning('Running for unsupported instrument, use with caution. Supported instruments are: '
+                             + str(supported_instruments))
+
+        self._progress.report('Normalising to monitor')
+        if self.getPropertyValue('NormaliseTo') == 'Monitor':
+            input_group = NormaliseToMonitor(InputWorkspace=input_group, MonitorID=0)
+
+        height_range = ''
+        height_range_prop = self.getProperty('HeightRange').value
+        if (len(height_range_prop) == 2):
+            height_range = str(height_range_prop[0]) + ', ' + str(height_range_prop[1])
+
+        output_workspaces = []
+        output_workspace_name = self.getPropertyValue('OutputWorkspace')
+
+        self._progress.report('Doing Output2DTubes Option')
+        if self.getProperty('Output2DTubes').value:
+            output2DTubes = SumOverlappingTubes(InputWorkspaces=input_group,
+                                                OutputType='2DTubes',
+                                                HeightAxis=height_range,
+                                                OutputWorkspace=output_workspace_name + '_2DTubes')
+            output_workspaces.append(output2DTubes)
+
+        self._progress.report('Doing Output2D Option')
+        if self.getProperty('Output2D').value:
+            output2D = SumOverlappingTubes(InputWorkspaces=input_group,
+                                           OutputType='2D',
+                                           CropNegativeScatteringAngles=True,
+                                           HeightAxis=height_range,
+                                           OutputWorkspace = output_workspace_name + '_2D')
+            output_workspaces.append(output2D)
+
+        self._progress.report('Doing Output1D Option')
+        if self.getProperty('Output1D').value:
+            output1D = SumOverlappingTubes(InputWorkspaces=input_group,
+                                           OutputType='1D',
+                                           CropNegativeScatteringAngles=True,
+                                           HeightAxis=height_range,
+                                           OutputWorkspace=output_workspace_name + '_1D')
+            output_workspaces.append(output1D)
+        DeleteWorkspace('input_group')
+
+        self._progress.report('Finishing up...')
+
+        GroupWorkspaces(InputWorkspaces=output_workspaces, OutputWorkspace=output_workspace_name)
+        self.setProperty('OutputWorkspace', output_workspace_name)
+
+# Register the algorithm with Mantid
+AlgorithmFactory.subscribe(PowderDiffILLDetScanReduction)
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCalculateTransmission.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCalculateTransmission.py
index 85bfaf36bbb484af16b580b68fc1dcbf03fa3859..dd179a4f1766d7ac415e7a290eae783a3cf96b88 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCalculateTransmission.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCalculateTransmission.py
@@ -3,7 +3,7 @@
 """ SANSCalculateTransmission algorithm calculates the transmission correction of a SANS workspace."""
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, StringListValidator, PropertyManagerProperty)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode)
+from mantid.api import (ParallelDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode)
 from sans.common.constants import EMPTY_NAME
 from sans.common.general_functions import create_unmanaged_algorithm
 from sans.common.enums import (RangeStepType, RebinType, FitType, DataType)
@@ -15,7 +15,7 @@ from sans.algorithm_detail.calculate_transmission_helper import (get_detector_id
                                                                  get_region_of_interest)
 
 
-class SANSCalculateTransmission(DataProcessorAlgorithm):
+class SANSCalculateTransmission(ParallelDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Adjust'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToQ.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToQ.py
index 2edba033fdd2aaa5c906b28b9dbec550804cd7ac..6d0bb0d4c3ac8fc8e831410391e998938b006a89 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToQ.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToQ.py
@@ -3,7 +3,7 @@
 """ Converts a workspace from wavelengths to momentum transfer."""
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, CompositeValidator)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
                         WorkspaceUnitValidator)
 
 from sans.common.constants import EMPTY_NAME
@@ -13,7 +13,7 @@ from sans.state.state_base import create_deserialized_sans_state_from_property_m
 from sans.algorithm_detail.q_resolution_calculator import QResolutionCalculatorFactory
 
 
-class SANSConvertToQ(DataProcessorAlgorithm):
+class SANSConvertToQ(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\ConvertToQ'
 
@@ -81,7 +81,8 @@ class SANSConvertToQ(DataProcessorAlgorithm):
         # Set the output
         append_to_sans_file_tag(output_workspace, "_convertq")
         self.setProperty("OutputWorkspace", output_workspace)
-        if sum_of_counts_workspace and sum_of_norms_workspace:
+        output_parts = self.getProperty("OutputParts").value
+        if output_parts:
             self._set_partial_workspaces(sum_of_counts_workspace, sum_of_norms_workspace)
 
     def _run_q_1d(self, state):
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelength.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelength.py
index c57fb73620cda10231028b369682c284ced40cca..acbb28a25e876c3e7e8bc7737398382573ad0690 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelength.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelength.py
@@ -3,7 +3,7 @@
 """ SANSConvertToWavelength converts to wavelength units """
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode)
 from sans.common.constants import EMPTY_NAME
 from sans.common.general_functions import (create_unmanaged_algorithm, append_to_sans_file_tag,
                                            get_input_workspace_as_copy_if_not_same_as_output_workspace)
@@ -11,7 +11,7 @@ from sans.common.enums import (RangeStepType, RebinType)
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
 
 
-class SANSConvertToWavelength(DataProcessorAlgorithm):
+class SANSConvertToWavelength(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Wavelength'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelengthAndRebin.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelengthAndRebin.py
index c9faf23419a2406677835eb67ea38963fa168fb7..905a57eabc0d28379e16334942884ab6e979bc94 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelengthAndRebin.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSConvertToWavelengthAndRebin.py
@@ -5,14 +5,14 @@
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, StringListValidator, Property)
 from mantid.dataobjects import EventWorkspace
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
 from sans.common.constants import EMPTY_NAME
 from sans.common.general_functions import (create_unmanaged_algorithm, append_to_sans_file_tag,
                                            get_input_workspace_as_copy_if_not_same_as_output_workspace)
 from sans.common.enums import (RebinType, RangeStepType)
 
 
-class SANSConvertToWavelengthAndRebin(DataProcessorAlgorithm):
+class SANSConvertToWavelengthAndRebin(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Wavelength'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py
index e5be7ef26cedb0358d63fba0bc12f6027fa44ca7..b183e192a66e6d169a3ccdc220a7114e3d1abcbc 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py
@@ -6,7 +6,7 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator, CompositeValidator)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
                         WorkspaceUnitValidator)
 
 from sans.common.constants import EMPTY_NAME
@@ -16,7 +16,7 @@ from sans.state.state_base import create_deserialized_sans_state_from_property_m
 from mantid import AnalysisDataService
 
 
-class SANSCreateAdjustmentWorkspaces(DataProcessorAlgorithm):
+class SANSCreateAdjustmentWorkspaces(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Adjust'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateWavelengthAndPixelAdjustment.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateWavelengthAndPixelAdjustment.py
index e7cbe1105e5c91e6ded7680a91b19557f88e358a..8fad5ef9475e6177528104a4a9de89061f30ccd1 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateWavelengthAndPixelAdjustment.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateWavelengthAndPixelAdjustment.py
@@ -7,7 +7,7 @@
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, StringListValidator, PropertyManagerProperty, CompositeValidator)
 
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
+from mantid.api import (ParallelDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
                         WorkspaceUnitValidator)
 
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
@@ -16,7 +16,7 @@ from sans.common.constants import EMPTY_NAME
 from sans.common.general_functions import create_unmanaged_algorithm
 
 
-class SANSCreateWavelengthAndPixelAdjustment(DataProcessorAlgorithm):
+class SANSCreateWavelengthAndPixelAdjustment(ParallelDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Adjust'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCrop.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCrop.py
index b4313322517434ae871d4b1ade6aade8bef70a4c..431ce38affb2ac5a1ef68e9a1c6ef132d74870c0 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCrop.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCrop.py
@@ -4,7 +4,7 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, StringListValidator)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
 
 from sans.common.constants import EMPTY_NAME
 from sans.common.enums import DetectorType
@@ -12,7 +12,7 @@ from sans.common.general_functions import (create_unmanaged_algorithm, append_to
 from sans.algorithm_detail.crop_helper import get_component_name
 
 
-class SANSCrop(DataProcessorAlgorithm):
+class SANSCrop(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Crop'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py
index f159bf65ee4c15445d4d19fc46d79f8f556790be..1afd23f6c1bf36fec90f3808d75241653421cebf 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py
@@ -5,7 +5,7 @@
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, FloatArrayProperty,
                            EnabledWhenProperty, PropertyCriterion)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress,
+from mantid.api import (ParallelDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress,
                         WorkspaceProperty)
 
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
@@ -14,7 +14,7 @@ from sans.common.general_functions import create_child_algorithm
 from sans.algorithm_detail.load_data import SANSLoadDataFactory
 
 
-class SANSLoad(DataProcessorAlgorithm):
+class SANSLoad(ParallelDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Load'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMaskWorkspace.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMaskWorkspace.py
index 23025237a9e6cf5bbe52b097fe528bc66f958d62..f99fa02a3a0ee973a7b392e0d0b6bba529b3ff7a 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMaskWorkspace.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMaskWorkspace.py
@@ -4,7 +4,7 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
 
 from sans.algorithm_detail.mask_workspace import MaskFactory
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
@@ -12,7 +12,7 @@ from sans.common.enums import DetectorType
 from sans.common.general_functions import append_to_sans_file_tag
 
 
-class SANSMaskWorkspace(DataProcessorAlgorithm):
+class SANSMaskWorkspace(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Mask'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMove.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMove.py
index e1975d3a0c7f09709241acce0398ccabeb35475c..210dc5555f508be80b21522f8a0460bd7d54b87f 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMove.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSMove.py
@@ -5,7 +5,7 @@
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator,
                            FloatArrayProperty)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
 
 from sans.algorithm_detail.move_workspaces import SANSMoveFactory
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
@@ -46,7 +46,7 @@ def get_detector_for_component(move_info, component):
     return selected_detector
 
 
-class SANSMove(DataProcessorAlgorithm):
+class SANSMove(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Move'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSNormalizeToMonitor.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSNormalizeToMonitor.py
index c061576b1c1e1817951296c14efdd03b4021c523..f861d608921ad7e6a4e9df521d0074426ae3d78b 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSNormalizeToMonitor.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSNormalizeToMonitor.py
@@ -4,14 +4,14 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, FloatBoundedValidator, PropertyManagerProperty)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode)
+from mantid.api import (ParallelDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode)
 from sans.common.constants import EMPTY_NAME
 from sans.common.general_functions import create_unmanaged_algorithm
 from sans.common.enums import RebinType, RangeStepType
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
 
 
-class SANSNormalizeToMonitor(DataProcessorAlgorithm):
+class SANSNormalizeToMonitor(ParallelDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Adjust'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCore.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCore.py
index cc17388d70108e4e009abe6a2d2ce18a87a4fe1f..388bd52f3b6eba947ab3eceecb78cdf5143de51d 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCore.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCore.py
@@ -4,7 +4,7 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
                         IEventWorkspace, Progress)
 
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
@@ -13,7 +13,7 @@ from sans.common.general_functions import (create_child_algorithm, append_to_san
 from sans.common.enums import (DetectorType, DataType)
 
 
-class SANSReductionCore(DataProcessorAlgorithm):
+class SANSReductionCore(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Reduction'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSScale.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSScale.py
index b1c77dd643f73fb8d11ac6f9ca4a70679b98cf80..7567b4eb06c888a6873d24fe36bf4f1316616527 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSScale.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSScale.py
@@ -4,14 +4,14 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
 
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
 from sans.algorithm_detail.scale_helpers import (DivideByVolumeFactory, MultiplyByAbsoluteScaleFactory)
 from sans.common.general_functions import (append_to_sans_file_tag)
 
 
-class SANSScale(DataProcessorAlgorithm):
+class SANSScale(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Scale'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py
index 8b1296ca49ee857e2bd9c7aceb15cb31c2513096..3a57a652d7834573212a1cfe769d615cebbd6578 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py
@@ -4,7 +4,7 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, Property)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
 
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
 from sans.common.enums import (ReductionMode, DataType, ISISReductionMode)
@@ -14,7 +14,7 @@ from sans.algorithm_detail.single_execution import (run_core_reduction, get_fina
 from sans.algorithm_detail.bundles import ReductionSettingBundle
 
 
-class SANSSingleReduction(DataProcessorAlgorithm):
+class SANSSingleReduction(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\Reduction'
 
@@ -312,6 +312,9 @@ class SANSSingleReduction(DataProcessorAlgorithm):
         # HAB or LAB anywhere which means that in the future there could be other detectors of relevance. Here we
         # reference HAB and LAB directly since we currently don't want to rely on dynamic properties. See also in PyInit
         for reduction_mode, output_workspace in list(reduction_mode_vs_output_workspaces.items()):
+            # In an MPI reduction output_workspace is produced on the master rank, skip others.
+            if output_workspace is None:
+                continue
             if reduction_mode is ReductionMode.Merged:
                 self.setProperty("OutputWorkspaceMerged", output_workspace)
             elif reduction_mode is ISISReductionMode.LAB:
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSliceEvent.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSliceEvent.py
index bdd93f07afe04d08b9b96764c8819ec05fe046b0..e65b923bf0676662f929b0318a90ea5371d3fcee 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSliceEvent.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSliceEvent.py
@@ -4,7 +4,7 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator)
-from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
+from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
 
 from sans.algorithm_detail.slicer import (SliceEventFactory, get_scaled_workspace)
 from sans.common.general_functions import append_to_sans_file_tag
@@ -12,7 +12,7 @@ from sans.common.enums import DataType
 from sans.state.state_base import create_deserialized_sans_state_from_property_manager
 
 
-class SANSSliceEvent(DataProcessorAlgorithm):
+class SANSSliceEvent(DistributedDataProcessorAlgorithm):
     def category(self):
         return 'SANS\\SliceEvent'
 
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSFitShiftScale.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSFitShiftScale.py
index 288a05f92748fdec45e1fc01e6048d99fd938e7d..d9978e9d5e429643cbd4a858357aab468cb77696 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSFitShiftScale.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSFitShiftScale.py
@@ -3,7 +3,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 from mantid.simpleapi import *
-from mantid.api import DataProcessorAlgorithm, MatrixWorkspaceProperty, PropertyMode, AnalysisDataService
+from mantid.api import ParallelDataProcessorAlgorithm, MatrixWorkspaceProperty, PropertyMode, AnalysisDataService
 from mantid.kernel import Direction, Property, StringListValidator, UnitFactory
 import numpy as np
 
@@ -22,7 +22,7 @@ class Mode(object):
         pass
 
 
-class SANSFitShiftScale(DataProcessorAlgorithm):
+class SANSFitShiftScale(ParallelDataProcessorAlgorithm):
     def _make_mode_map(self):
         return {'ShiftOnly': Mode.ShiftOnly, 'ScaleOnly': Mode.ScaleOnly,
                 'Both': Mode.BothFit, 'None': Mode.NoneFit}
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSStitch.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSStitch.py
index 1ca854a50cb9624f041e4f31fc612e51431d14ca..600c6e8a6ab050588816c8072b6895044c09f007 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSStitch.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSStitch.py
@@ -3,7 +3,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 from mantid.simpleapi import *
-from mantid.api import DataProcessorAlgorithm, MatrixWorkspaceProperty, PropertyMode
+from mantid.api import ParallelDataProcessorAlgorithm, MatrixWorkspaceProperty, PropertyMode
 from mantid.kernel import Direction, Property, StringListValidator, UnitFactory, \
     EnabledWhenProperty, PropertyCriterion
 import numpy as np
@@ -25,7 +25,7 @@ class Mode(object):
         pass
 
 
-class SANSStitch(DataProcessorAlgorithm):
+class SANSStitch(ParallelDataProcessorAlgorithm):
     def _make_mode_map(self):
         return {'ShiftOnly': Mode.ShiftOnly, 'ScaleOnly': Mode.ScaleOnly,
                 'Both': Mode.BothFit, 'None': Mode.NoneFit}
diff --git a/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py b/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
index eb0defff62298be77425795395702f90df915a21..57aa0cf7413a85186712a35b9d4406d5613b728a 100644
--- a/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
+++ b/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
@@ -82,6 +82,7 @@ class IPeakTest(unittest.TestCase):
         self._peak.setQSampleFrame(q_sample)
 
         q_lab = np.dot(self._peak.getQLabFrame(), rotation)
+        npt.assert_allclose(self._peak.getGoniometerMatrix(), rotation)
         npt.assert_allclose(self._peak.getQSampleFrame(), q_sample, atol=self._tolerance)
         npt.assert_allclose(q_lab, q_sample, atol=self._tolerance)
 
diff --git a/Framework/PythonInterface/test/python/mantid/kernel/DateAndTimeTest.py b/Framework/PythonInterface/test/python/mantid/kernel/DateAndTimeTest.py
index 410c5cd8f3c18ee7b41f311daaa5da09633d7046..8e466dbd24dccaf02184aab73df320f9cb2af1de 100644
--- a/Framework/PythonInterface/test/python/mantid/kernel/DateAndTimeTest.py
+++ b/Framework/PythonInterface/test/python/mantid/kernel/DateAndTimeTest.py
@@ -4,6 +4,7 @@ import unittest
 from mantid.kernel import DateAndTime
 import numpy
 from numpy import timedelta64, datetime64
+from distutils.version import LooseVersion
 
 
 class DateAndTimeTest(unittest.TestCase):
@@ -31,7 +32,7 @@ class DateAndTimeTest(unittest.TestCase):
         self.assertEquals(dt, dt_np)
 
     def test_convert_from_np(self):
-        if numpy.__version__.startswith('1.7.'):
+        if LooseVersion(numpy.__version__) < LooseVersion('1.9'):
             dt_np = datetime64('2000-01-01T00:00Z')
         else: # newer numpy only uses UTC and warns on specifying timezones
             dt_np = datetime64('2000-01-01T00:00')
diff --git a/Framework/PythonInterface/test/python/mantid/plots/functionsTest.py b/Framework/PythonInterface/test/python/mantid/plots/functionsTest.py
index 512b20922a569b519d9bce9daaa30bdde09598a2..309e45a7257e58767128faa651d433e8273e3869 100644
--- a/Framework/PythonInterface/test/python/mantid/plots/functionsTest.py
+++ b/Framework/PythonInterface/test/python/mantid/plots/functionsTest.py
@@ -103,12 +103,12 @@ class PlotsFunctionsTest(unittest.TestCase):
         index, dist, kwargs = funcs._getWkspIndexDistAndLabel(self.ws2d_histo,specNum=2)
         self.assertEqual(index,1)
         self.assertTrue(dist)
-        self.assertEqual(kwargs['label'],'spec 2')
+        self.assertEqual(kwargs['label'],'ws2d_histo: spec 2')
         #get info from default spectrum in the 1d case
         index, dist, kwargs = funcs._getWkspIndexDistAndLabel(self.ws1d_point)
         self.assertEqual(index,0)
         self.assertFalse(dist)
-        self.assertEqual(kwargs['label'],'spec 1')
+        self.assertEqual(kwargs['label'],'ws1d_point: spec 1')
 
     def test_getAxesLabels(self):
         axs=funcs.getAxesLabels(self.ws2d_histo)
@@ -181,7 +181,7 @@ class PlotsFunctionsTest(unittest.TestCase):
         #mesh from aligned point data
         x,y,z=funcs._getMatrix2DData(self.ws2d_point,True,histogram2D=True)
         np.testing.assert_allclose(x,np.array([[0.5,1.5,2.5,3.5,4.5],[0.5,1.5,2.5,3.5,4.5],[0.5,1.5,2.5,3.5,4.5],[0.5,1.5,2.5,3.5,4.5]]))
-        np.testing.assert_allclose(y,np.array([[0.5,0.5,0.5,0.5,0.5],[1.5,1.5,1.5,1.5,1.5],[2.5,2.5,2.5,2.5,2.5],[3.5,3.5,3.5,3.5,3.5]]))        
+        np.testing.assert_allclose(y,np.array([[0.5,0.5,0.5,0.5,0.5],[1.5,1.5,1.5,1.5,1.5],[2.5,2.5,2.5,2.5,2.5],[3.5,3.5,3.5,3.5,3.5]]))
         #contour from aligned histo data
         x,y,z=funcs._getMatrix2DData(self.ws2d_histo,True,histogram2D=False)
         np.testing.assert_allclose(x,np.array([[15,25],[15,25]]))
@@ -278,5 +278,6 @@ class PlotsFunctionsTest(unittest.TestCase):
         funcs.pcolormesh(ax,self.ws_MD_2d)
         funcs.pcolorfast(ax,self.ws2d_point_uneven,vmin=-1)
 
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/ExportSampleLogsToCSVFileTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/ExportSampleLogsToCSVFileTest.py
index 6469448ed5a3bb3377e9056ccfc16e0f5ca7233b..c7a54d49750d510698c51933ac25c672a313efa5 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/ExportSampleLogsToCSVFileTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/ExportSampleLogsToCSVFileTest.py
@@ -6,6 +6,7 @@ import mantid.kernel as kernel
 from testhelpers import run_algorithm
 from mantid.api import AnalysisDataService
 import os
+from distutils.version import LooseVersion
 from six.moves import range
 
 
@@ -300,7 +301,8 @@ class ExportVulcanSampleLogTest(unittest.TestCase):
         dtimesec = 0.0010
         timefluc = 0.0001
         runstart = '2014-02-15T13:34:03'
-        if numpy.__version__.startswith('1.7.'): # older numpy assumes local timezone
+        # older numpy assumes local timezone
+        if LooseVersion(numpy.__version__) < LooseVersion('1.9'):
             runstart = runstart + 'Z'
         runstart = datetime64(runstart, 'us') # microsecond needed for deltas
 
diff --git a/MantidPlot/pymantidplot/mpl/backend_mtdqt4agg.py b/MantidPlot/pymantidplot/mpl/backend_mtdqt4agg.py
index 68aae4a2658cda07f5cc4a8fc79d90a5c21f6c0d..4c7307282614fc801cc4946d509d3bad3528518c 100644
--- a/MantidPlot/pymantidplot/mpl/backend_mtdqt4agg.py
+++ b/MantidPlot/pymantidplot/mpl/backend_mtdqt4agg.py
@@ -96,12 +96,15 @@ class ThreadAwareFigureManagerQT(FigureManagerQT):
         FigureManagerQT.__init__(self, canvas, num)
         self._destroy_orig = self.destroy
         self.destroy = QAppThreadCall(self._destroy_orig)
+        self._show_orig = self.show
+        self.show = QAppThreadCall(self._show_orig)
 
 
 # ----------------------------------------------------------------------------------------------------------------------
-# Wrap the required functions
+# Patch known functions
+# ----------------------------------------------------------------------------------------------------------------------
 show = QAppThreadCall(show)
-# Use our figure manager
+
 FigureManager = ThreadAwareFigureManagerQT
 
 if MPL_HAVE_GIVEN_FIG_METHOD:
diff --git a/Testing/Data/DocTest/ILL/D2B/508093.nxs.md5 b/Testing/Data/DocTest/ILL/D2B/508093.nxs.md5
index ef2ac298a245b8af4943b70c238c4b9e465e2823..3dc3a1d5158947f3193b3021f50e6bf8c025415b 100644
--- a/Testing/Data/DocTest/ILL/D2B/508093.nxs.md5
+++ b/Testing/Data/DocTest/ILL/D2B/508093.nxs.md5
@@ -1 +1 @@
-49c19fd17995d112c468b2132eb4391a
+a19ba3c85dd98e4be8aa08629a243ce2
diff --git a/Testing/Data/DocTest/ILL/D2B/508094.nxs.md5 b/Testing/Data/DocTest/ILL/D2B/508094.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..ae148288fd9e443148c5b5250902aea34dbec322
--- /dev/null
+++ b/Testing/Data/DocTest/ILL/D2B/508094.nxs.md5
@@ -0,0 +1 @@
+9bfb539fcfe3c6380cf2e95e16d0cb24
diff --git a/Testing/Data/DocTest/ILL/D2B/508095.nxs.md5 b/Testing/Data/DocTest/ILL/D2B/508095.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..b277ac18b72a40f2443627f5c2a2750f69110f98
--- /dev/null
+++ b/Testing/Data/DocTest/ILL/D2B/508095.nxs.md5
@@ -0,0 +1 @@
+fa40f1242226c1f525a5417a0c3545ea
diff --git a/Testing/Data/SystemTest/ILL/D2B/508093.nxs.md5 b/Testing/Data/SystemTest/ILL/D2B/508093.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..3dc3a1d5158947f3193b3021f50e6bf8c025415b
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D2B/508093.nxs.md5
@@ -0,0 +1 @@
+a19ba3c85dd98e4be8aa08629a243ce2
diff --git a/Testing/Data/SystemTest/ILL/D2B/508094.nxs.md5 b/Testing/Data/SystemTest/ILL/D2B/508094.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..ae148288fd9e443148c5b5250902aea34dbec322
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D2B/508094.nxs.md5
@@ -0,0 +1 @@
+9bfb539fcfe3c6380cf2e95e16d0cb24
diff --git a/Testing/Data/SystemTest/ILL/D2B/508095.nxs.md5 b/Testing/Data/SystemTest/ILL/D2B/508095.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..b277ac18b72a40f2443627f5c2a2750f69110f98
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D2B/508095.nxs.md5
@@ -0,0 +1 @@
+fa40f1242226c1f525a5417a0c3545ea
diff --git a/Testing/Data/SystemTest/ISIS_Powder/input/ISIS_Powder-POLARIS98533_TotalScatteringInput.nxs.md5 b/Testing/Data/SystemTest/ISIS_Powder/input/ISIS_Powder-POLARIS98533_TotalScatteringInput.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..c18228056958b8ece48f9f48b26d9c6479837ddb
--- /dev/null
+++ b/Testing/Data/SystemTest/ISIS_Powder/input/ISIS_Powder-POLARIS98533_TotalScatteringInput.nxs.md5
@@ -0,0 +1 @@
+fb33507ac0702dd7d513d79e352637b4
\ No newline at end of file
diff --git a/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py b/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py
index 609b60ac8b5f8ad6fd7f9a802a61bf86d1f1bb58..63bf9977e52666ab8509df58eeffeb6424ced5f8 100644
--- a/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py
+++ b/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py
@@ -39,6 +39,8 @@ calibration_dir = os.path.join(input_dir, calibration_folder_name)
 spline_path = os.path.join(calibration_dir, spline_rel_path)
 unsplined_van_path = os.path.join(calibration_dir, unsplined_van_rel_path)
 
+total_scattering_input_file = os.path.join(input_dir, "ISIS_Powder-POLARIS98533_TotalScatteringInput.nxs")
+
 
 class CreateVanadiumTest(stresstesting.MantidStressTest):
 
@@ -72,6 +74,7 @@ class FocusTest(stresstesting.MantidStressTest):
 
     focus_results = None
     existing_config = config['datasearch.directories']
+    tolerance = 1e-11
 
     def requiredFiles(self):
         return _gen_required_files()
@@ -101,7 +104,7 @@ class TotalScatteringTest(stresstesting.MantidStressTest):
 
     def runTest(self):
         # Load Focused ws
-        mantid.LoadNexus(Filename="ISIS_Powder-POLARIS98533_FocusSempty.nxs", OutputWorkspace='98533-Results-TOF-Grp')
+        mantid.LoadNexus(Filename=total_scattering_input_file, OutputWorkspace='98533-Results-TOF-Grp')
         self.pdf_output = run_total_scattering('98533', False)
 
     def validate(self):
@@ -130,6 +133,7 @@ def _gen_required_files():
     # Generate file names of form "INSTxxxxx.nxs"
     input_files = [os.path.join(input_dir, (inst_name + "000" + number + ".nxs")) for number in required_run_numbers]
     input_files.append(calibration_map_path)
+    input_files.append(total_scattering_input_file)
     return input_files
 
 
diff --git a/Testing/SystemTests/tests/analysis/PowderDiffILLDetScanReductionTest.py b/Testing/SystemTests/tests/analysis/PowderDiffILLDetScanReductionTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..1740eb86816577a69ad84149c41254340bb3cf99
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/PowderDiffILLDetScanReductionTest.py
@@ -0,0 +1,91 @@
+import stresstesting
+
+from mantid.simpleapi import PowderDiffILLDetScanReduction, \
+    CompareWorkspaces, ExtractSpectra, GroupWorkspaces
+from mantid import config
+
+
+class PowderDiffILLDetScanReductionTest(stresstesting.MantidStressTest):
+
+    def __init__(self):
+        super(PowderDiffILLDetScanReductionTest, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'D2B'
+        config.appendDataSearchSubDir('ILL/D2B/')
+
+    def requiredFiles(self):
+        return ["508093.nxs, 508094.nxs, 508095.nxs, D2B_scan_test.nxs"]
+
+    def d2b_2d_tubes_test(self):
+        ws_2d_tubes = PowderDiffILLDetScanReduction(
+            Run='508093:508095',
+            Output2DTubes = True,
+            Output2D = False,
+            Output1D = False,
+            OutputWorkspace='outWS_2d_tubes')
+        return ws_2d_tubes
+
+    def d2b_2d_tubes_test_using_merge(self):
+        ws_2d_tubes_merge = PowderDiffILLDetScanReduction(
+            Run='508093-508095',
+            Output2DTubes = True,
+            Output2D = False,
+            Output1D = False,
+            OutputWorkspace='outWS_2d_tubes_merge')
+        return ws_2d_tubes_merge
+
+    def d2b_2d_test(self):
+        ws_2d = PowderDiffILLDetScanReduction(
+            Run = '508093:508095',
+            UseCalibratedData = False,
+            NormaliseTo = 'None',
+            Output2DTubes = False,
+            Output2D = True,
+            Output1D = False,
+            OutputWorkspace='outWS_2d')
+        return ws_2d
+
+    def d2b_2d_height_test(self):
+        ws_2d_height = PowderDiffILLDetScanReduction(
+            Run = '508093:508095',
+            UseCalibratedData = False,
+            NormaliseTo = 'None',
+            Output2DTubes = False,
+            Output2D = True,
+            Output1D = False,
+            HeightRange = '-0.05,0.05',
+            OutputWorkspace='outWS_2d_height')
+        return ws_2d_height
+
+    def d2b_1d_test(self):
+        ws_1d = PowderDiffILLDetScanReduction(
+            Run='508093:508095',
+            Output2DTubes = False,
+            Output2D = False,
+            Output1D = True,
+            OutputWorkspace='outWS_1d')
+        return ws_1d
+
+    def runTest(self):
+        ws_2d_tubes = self.d2b_2d_tubes_test()
+        ws_2d = self.d2b_2d_test()
+        ws_1d = self.d2b_1d_test()
+
+        # Check loading and merging, and keeping files separate gives the same results
+        ws_2d_merge = self.d2b_2d_tubes_test_using_merge()
+        result_merge = CompareWorkspaces(Workspace1=ws_2d, Workspace2=ws_2d_merge)
+        self.assertTrue(result_merge)
+
+        # Check height range given is a subset of a full 2D option
+        ws_2d_height = self.d2b_2d_height_test()
+        ws_2d_cropped = ExtractSpectra(InputWorkspace=ws_2d[0], StartWorkspaceIndex=43, EndWorkspaceIndex=84)
+        result_height = CompareWorkspaces(Workspace1=ws_2d_cropped, Workspace2=ws_2d_height)
+        self.assertTrue(result_height)
+
+        GroupWorkspaces([ws_2d_tubes[0], ws_2d[0], ws_1d[0]], OutputWorkspace='grouped_output')
+
+    def validate(self):
+        return 'grouped_output', 'D2B_scan_test.nxs'
diff --git a/Testing/SystemTests/tests/analysis/SANS2DSlicingTest_V2.py b/Testing/SystemTests/tests/analysis/SANS2DSlicingTest_V2.py
index 9c9a9534fe974bbaeb091c8e85c9d4c7e2647278..3be8ef338c9b3dd611c5181695779efc47ed35a0 100644
--- a/Testing/SystemTests/tests/analysis/SANS2DSlicingTest_V2.py
+++ b/Testing/SystemTests/tests/analysis/SANS2DSlicingTest_V2.py
@@ -27,7 +27,10 @@ class SANS2DMinimalBatchReductionSlicedTest_V2(stresstesting.MantidStressTest):
         self.tolerance = 0.02
         self.tolerance_is_reller=True
         self.disableChecking.append('Instrument')
-        return str(AnalysisDataService['trans_test_rear'][0]), 'SANSReductionGUI.nxs'
+        try:
+            return str(AnalysisDataService['trans_test_rear'][0]), 'SANSReductionGUI.nxs'
+        except KeyError:
+            return '', 'SANSReductionGUI.nxs'
 
 
 class SANS2DMinimalSingleReductionSlicedTest_V2(stresstesting.MantidStressTest):
diff --git a/Testing/SystemTests/tests/analysis/reference/D2B_scan_test.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/D2B_scan_test.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..85523cf75a0c8c2d013027436922979b415d23be
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/D2B_scan_test.nxs.md5
@@ -0,0 +1 @@
+9a20698231398a7b4b1036fdffa97e50
diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
index c18228056958b8ece48f9f48b26d9c6479837ddb..34d4ab45e01683bc9d631a0a74667f2879f0e5ab 100644
--- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
@@ -1 +1 @@
-fb33507ac0702dd7d513d79e352637b4
\ No newline at end of file
+5592601106ed9ce1007c810513af63d6
\ No newline at end of file
diff --git a/buildconfig/CMake/Packaging/mantidpython.in b/buildconfig/CMake/Packaging/mantidpython.in
index e1a2150fa10a6bb8d1dfd4a4cebdf7dccb3114c4..2eb2ba7c728cbc47f1a26996abcb7896c92b3b04 100755
--- a/buildconfig/CMake/Packaging/mantidpython.in
+++ b/buildconfig/CMake/Packaging/mantidpython.in
@@ -51,6 +51,11 @@ if [ -n "$1" ] && [ "$1" = "--classic" ]; then
 
     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@
+
 else
     IPYTHON_STARTUP="import IPython;IPython.start_ipython()"
 
diff --git a/docs/source/algorithms/ClearMaskedSpectra-v1.rst b/docs/source/algorithms/ClearMaskedSpectra-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..8574b039493519022bd0ec47722942f4b3c66b02
--- /dev/null
+++ b/docs/source/algorithms/ClearMaskedSpectra-v1.rst
@@ -0,0 +1,44 @@
+
+.. algorithm::
+
+.. summary::
+
+.. alias::
+
+.. properties::
+
+Description
+-----------
+
+Clear counts (or events, if applicable) on all spectra that are fully masked.
+A spectrum is fully masked if all of its associated detectors are masked, e.g., from a call to `MaskInstrument`.
+
+Usage
+-----
+
+**Example - ClearMaskedSpectra**
+
+.. testcode:: ClearMaskedSpectraExample
+
+  ws = CreateSampleWorkspace()
+  ws = MaskInstrument(InputWorkspace=ws, DetectorIDs='100,102-104')
+  ws = ClearMaskedSpectra(InputWorkspace=ws)
+  # Detectors are masked but data is untouched
+  for i in range(6):
+    print("Detector {} masked: {:5} data {}".format(i, str(ws.getDetector(i).isMasked()), ws.readY(i)[0]))
+
+Output:
+
+.. testoutput:: ClearMaskedSpectraExample
+
+  Detector 0 masked: True  data 0.0
+  Detector 1 masked: False data 0.3
+  Detector 2 masked: True  data 0.0
+  Detector 3 masked: True  data 0.0
+  Detector 4 masked: True  data 0.0
+  Detector 5 masked: False data 0.3
+
+.. categories::
+
+.. sourcelink::
+
diff --git a/docs/source/algorithms/DirectILLCollectData-v1.rst b/docs/source/algorithms/DirectILLCollectData-v1.rst
index 34673867bd7ca7f3b4573bbc4b92afe0b2339ccf..1f5df795ea85bbc94dee719e408a493897116e5a 100644
--- a/docs/source/algorithms/DirectILLCollectData-v1.rst
+++ b/docs/source/algorithms/DirectILLCollectData-v1.rst
@@ -9,14 +9,14 @@
 Description
 -----------
 
-This algorithm preprocesses data for the other algorithms in :ref:`ILL's time-of-flight data reduction suite <DirectILL>`. Thus, it is usually the first algorithm to call in the reduction process. The algorithm (optionally) loads data from disk, performs some common basic data reduction steps to the raw data, and provides other workspaces, such as flat background information, which can be used in subsequent reduction steps. The workflow of the algorithm is shown in the diagram below:
+An initial step of the reduction workflow for the direct geometry TOF spectrometers at ILL. It reads and merges NeXus data files, normalizes data to monitor, subtracts a flat background, and adjusts the TOF scale to conform to Mantid standards. More information on how this algorithm interacts with the rest of the ILL's TOF reduction suite can be found :ref:`here <DirectILL>`. The workflow of this algorithm is shown in the diagram below:
 
 .. diagram:: DirectILLCollectData-v1_wkflw.dot
 
 Input data
 ##########
 
-Either *Run* or *InputWorkspace* has to be specified. *Run* can take multiple run numbers. In this case the files will be merged using the :ref:`MergeRuns <algm-MergeRuns>` algorihtm. For example, `'/data/0100:0103,0200:0202'` would merge runs 100, 101, 102, 103, 200, 201 and 202 from directory `/data/`.
+Either *Run* or *InputWorkspace* has to be specified. *Run* can take multiple run numbers. In this case the files will be merged using the :ref:`LoadAndMerge <algm-LoadAndMerge>` algorithm. For example, :literal:`'/data/0100-0103+0200-0202'` would merge runs 100, 101, 102, 103, 200, 201 and 202 from directory :literal:`/data/`.
 
 Basic reduction steps
 #####################
@@ -39,14 +39,19 @@ More detailed description of some of these steps is given below.
 Normalisation to monitor
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-If *Normalisation* is set to 'Normalisation Monitor', the monitor spectrum specified by the 'default-incident-monitor-spectrum' instrument parameter is used for normalisation. If the parameter is not present, the *Monitor* property is used. A flat background is subtracted from the spectrum (no scaling applied), and it is integrated over the range specified by *ElasticPeakWidthInSigmas*. The monitor peak is found using :ref:`FindEPP <algm-FindEPP>`. If :ref:`FindEPP <algm-FindEPP>` fails to find a peak in the monitor spectrum, the entire monitor range is integrated.
+If *Normalisation* is set to :literal:`'Normalisation Monitor'`, the data is divided by the monitor counts. The monitor spectrum is specified by the 'default-incident-monitor-spectrum' instrument parameter or, if not present, by the *Monitor* property. A flat background is subtracted from the spectrum as described below except no background scaling is applied.  The monitor peak is found using :ref:`FindEPP <algm-FindEPP>`. If this fails, the entire monitor range is integrated. Otherwise the spectrum is integrated over a range :math:`\pm` *MonitorPeakWidthInSigmas* :math:`\cdot \sigma` around the position of the monitor peak.
 
-Afterwards, the counts are scaled by a factor defined by the 'scaling_after_monitor_normalisation' entry in instrument parameters, if present.
+Afterwards, the intensities are multiplied by a factor defined by the 'scaling_after_monitor_normalisation' entry in instrument parameters, if present.
+
+Normalisation to time
+^^^^^^^^^^^^^^^^^^^^^
+
+When *Normalisation* is set to :literal:`'Normalisation Time'`, the data is divided by the duration of the experiment, in seconds. By default, the `duration` sample log is used except for ILL's IN4 instrument where the `actual_time` log is used instead.
 
 Flat background subtraction
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-A flat time-independent background for subtraction can be given by *FlatBkgWorkspace*. If this input property is not specified, flat background will be calculated from the detector spectra by (:ref:`CalculateFlatBackground <algm-CalculateFlatBackground>`) using the 'Moving Average' mode. The *FlatBkgAveragingWindow* property is passed directly to (:ref:`CalculateFlatBackground <algm-CalculateFlatBackground>`) as *AveragingWindowWidth*.
+A flat time-independent background for subtraction can be given by *FlatBkgWorkspace*. If this input property is not specified, flat background will be calculated from the detector spectra by :ref:`CalculateFlatBackground <algm-CalculateFlatBackground>` using the :literal:`Moving Average` mode. The *FlatBkgAveragingWindow* property is passed directly to :ref:`CalculateFlatBackground <algm-CalculateFlatBackground>` as *AveragingWindowWidth*.
 
 Before subtraction, the background workspace is multiplied by *FlatBkgScaling*.
 
@@ -55,25 +60,25 @@ The background used for the subtraction can be retrieved using the *OutputFlatBk
 Elastic peak positions (EPP)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Information on the elastic peaks (position, peak width) is needed for incident energy calibration, as well as for the :ref:`DirectILLDiagnostics <algm-DirectILLDiagnostics>` and :ref:`DirectILLIntegrateVanadium <algm-DirectILLIntegrateVanadium>` algorithms. This data comes in the form of a EPP workspace which is a TableWorkspace containing columns specified by the :ref:`FindEPP <algm-FindEPP>` algorithm.
+Information on the elastic peaks (position, peak width) is needed for incident energy calibration, as well as for the :ref:`DirectILLDiagnostics <algm-DirectILLDiagnostics>` and :ref:`DirectILLIntegrateVanadium <algm-DirectILLIntegrateVanadium>` algorithms. This data comes in the form of an EPP workspace which is a TableWorkspace containing columns specified by the :ref:`FindEPP <algm-FindEPP>` algorithm.
 
-If no external EPP table is given by the *EPPWorkspace* property, the algorithm either fits the elastic peaks using :ref:`FindEPP <algm-FindEPP>`, or calculates their nominal positions using :ref:`CreateEPP <algm-CreateEPP>`. This behavior can be controlled by the *EPPCreationMode* property. The default ('EPP Method AUTO') is to calculate the positions for the IN5 instrument, and to fit for any other instrument.
+If no external EPP table is given by the *EPPWorkspace* property, the algorithm either fits the elastic peaks using :ref:`FindEPP <algm-FindEPP>`, or calculates their nominal positions using :ref:`CreateEPP <algm-CreateEPP>`. This behavior can be controlled by the *EPPCreationMode* property. The default (:literal:`'EPP Method AUTO'`) is to calculate the positions for the IN5 instrument, and to fit for any other instrument.
 
 In the calculation case, a nominal peak width can be given using the *Sigma* property.  The peak width is needed for some integration operations. If *Sigma* is not specified, ten times the first bin width in the workspace will be used.
 
-Incident energy calibration
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Incident energy
+^^^^^^^^^^^^^^^
 
-This step applies to IN4 and IN6 only. Incident energy calibration is disabled for IN5 by default.
+The incident energy and the nominal TOF channel are needed to adjust the TOF axis to conform to the standard Mantid notation.
 
-Incident energy is calibrated either by giving a new energy as a single-value workspace in *IncidentEnergyWorkspace* or calculating it from the elastic peak positions. The elastic peak position can be given by *EPPWorkspace*. If this parameter not specified, :ref:`FindEPP <algm-FindEPP>` is used.
+The incident energy written in the data files of IN4 and IN6 and accessible via the `Ei` sample log may be inaccurate. To ensure a correct value is used for the TOF axis adjustment, the value can be calibrated using :ref:`GetEiMonDet <algm-GetEiMonDet>`. The operation is controlled by *IncidentEnergyCalibration*. Elastic peak positions are needed for the calculation which can be supplied by *EPPWorkspace*, otherwise :ref:`FindEPP <algm-FindEPP>` is used.
 
-The calibrated energy can be retrieved as a single-value workspace using the *OutputIncidentEnergyWorkspace* property.
+The calibrated energy can be retrieved as a single-value workspace using the *OutputIncidentEnergyWorkspace* property. This workspace can be passed to further calls to :ref:`DirectILLCollectData <algm-DirectILLCollectData>` to force a common `Ei` and thus a common TOF axis between the datasets. This is needed for, e.g., empty container subtraction.
 
 TOF axis adjustment
 ^^^^^^^^^^^^^^^^^^^
 
-The TOF axis is adjusted according to the elastic channel number found in the 'Detector.elasticpeak' sample log.
+The TOF axis is adjusted such that the nominal elastic channel corresponds to the L1 + L2 distance. For this, incident energy and elastic channel number are needed. The energy is read from the :literal:`Ei` sample log while the elastic channel from `Detector.elasticpeak`. Alternatively, the elastic channel can be determined by fitting, as done with IN5. Whether to use the sample logs or fitting is determined by the *ElasticChannel* property. The channel can be given also directly as a single valued workspace in *ElasticChannelWorkspace*.
 
 Optional inputs and outputs
 ###########################
@@ -82,15 +87,33 @@ The algorithm has some optional input and output workspaces. Their purpose is to
 
 The optional input and output workspaces come in pairs. If the input workspace is specified, it will be used in the reduction and returned as the corresponding output workspace. If the input workspace is not specified, the needed information is calculated from the current spectra, and returned in the output workspace.
 
-* *EPPWorkspace* --- *OutputEPPWorkspace*: elastic peak position table, used for incident energy calibration, but also in :ref:`DirectILLDiagnostics <algm-DirectILLDiagnostics>` and :ref:`DirectILLIntegrateVanadium <algm-DirectILLIntegrateVanadium>`.
+* *EPPWorkspace* --- *OutputEPPWorkspace*: elastic peak position table, used for incident energy calibration, but also in :ref:`DirectILLDiagnostics <algm-DirectILLDiagnostics>` and :ref:`DirectILLIntegrateVanadium <algm-DirectILLIntegrateVanadium>`. The peak positions in the output workspace are for the adjusted TOF axis. This is fine for the incident energy calibration as only relative TOF values are used.
 * *IncidentEnergyWorkspace* --- *OutputIncidentEnergyWorkspace*: single-valued workspace containing calibrated incident energy, used for incident energy calibration.
 * *FlatBkgWorkspace* --- *OutputFlatBkgWorkspace*: a MatrixWorkspace containing the flat backgrounds. Used for flat background subtraction. Note that *FlatBkgScaling* is not applied to *OutputFlatBkgWorkspace*.
+* *ElasticChannelWorkspace* --- *OutputElasticChannelWorkspace*: a single-valued workspace containing the index of the nominal elastic channel. Used for the TOF axis adjustment.
 
 Raw output workspace
 ^^^^^^^^^^^^^^^^^^^^
 
 The *OutputRawWorkspace* property provides an access to a 'raw' data workspace in the sense that no normalisation or background subtraction is applied to this workspace. The raw workspace is useful as an input workspace for the :ref:`DirectILLDiagnostics <algm-DirectILLDiagnostics>` algorithm.
 
+ILL's instrument specific defaults
+----------------------------------
+
+The following settings are used when the :literal:`AUTO` keyword is encountered:
+
++---------------------------+-------------------------+------------------------+-------------------------+-------------------------+
+| Property                  | IN4                     | IN5                    | IN6                     | Others                  |
++===========================+=========================+========================+=========================+=========================+
+| EPPCreationMethod         | Fit EPP                 | Calculate EPP          | Fit EPP                 | Fit EPP                 |
++---------------------------+-------------------------+------------------------+-------------------------+-------------------------+
+| ElasticChannel            | Default Elastic Channel | Fit Elastic Channel    | Default Elastic Channel | Default Elastic Channel |
++---------------------------+-------------------------+------------------------+-------------------------+-------------------------+
+| IncidentEnergyCalibration | Energy Calibration ON   | Energy Calibration OFF | Energy Calibration ON   | Energy Calibration ON   |
++---------------------------+-------------------------+------------------------+-------------------------+-------------------------+
+| FlatBkg                   | Flat Bkg ON             | Flat Bkg OFF           | Flat Bkg ON             | Flat Bkg ON             |
++---------------------------+-------------------------+------------------------+-------------------------+-------------------------+
+
 Usage
 -----
 
diff --git a/docs/source/algorithms/DirectILLDiagnostics-v1.rst b/docs/source/algorithms/DirectILLDiagnostics-v1.rst
index a1c73dfcd34c5bb96a9e7a02827078fcce7898c5..8201ca8e4d28002b176fa35ba33a30fcfc0aa623 100644
--- a/docs/source/algorithms/DirectILLDiagnostics-v1.rst
+++ b/docs/source/algorithms/DirectILLDiagnostics-v1.rst
@@ -52,6 +52,9 @@ Defaul mask
 
 The default mask file is defined by the 'Workflow.MaskFile' instrument parameter.
 
+Currently, there is a default mask available for ILL's IN5 instrument which masks 8 pixels at both ends of every detector tube.
+
+
 Diagnostics reporting
 #####################
 
@@ -68,6 +71,21 @@ The columns can be plotted to get an overview of the diagnostics.
 
 Additionally, a string listing the masked and diagnosed detectors can be accessed via the *OutputReport* property.
 
+ILL's instrument specific defaults
+----------------------------------
+
+The following settings are used when the :literal:`AUTO` keyword is encountered:
+
++------------------------+---------------------------+--------------------------+---------------------------+---------------------------+
+| Property               | IN4                       | IN5                      | IN6                       | Ohters                    |
++========================+===========================+==========================+===========================+===========================+
+| ElasticPeakDiagnostics | Peak Diagnostics ON       | Peak Diagnostics OFF     | Peak Diagnostics ON       | Peak Diagnostics ON       |
++------------------------+---------------------------+--------------------------+---------------------------+---------------------------+
+| BkgDiagnostics         | Bkg Diagnostics ON        | Bkg Diagnostics OFF      | Bkg Diagnostics ON        | Bkg Diagnostics ON        |
++------------------------+---------------------------+--------------------------+---------------------------+---------------------------+
+| BeamStopDiagnostics    | Beam Stop Diagnostics OFF | Beam Stop Diagnostics ON | Beam Stop Diagnostics OFF | Beam Stop Diagnostics OFF |
++------------------------+---------------------------+--------------------------+---------------------------+---------------------------+
+
 Usage
 -----
 
diff --git a/docs/source/algorithms/DirectILLReduction-v1.rst b/docs/source/algorithms/DirectILLReduction-v1.rst
index fbb2ea4d6be244cf5b39a375d6e913a4f67ce5c8..f43b36417cd06d9c727fab25e60c6f5e2b85f105 100644
--- a/docs/source/algorithms/DirectILLReduction-v1.rst
+++ b/docs/source/algorithms/DirectILLReduction-v1.rst
@@ -32,11 +32,11 @@ The optional :math:`S(2\theta,\omega)` output can be enabled by the *OutputSofTh
 Normalisation to absolute units
 ###############################
 
-Normalisation to absolute units can be enabled by setting *AbsoluteUnitsNormalisation* to 'Absolute Units ON'. In this case the data is scaled by a factor
+Normalisation to absolute units can be enabled by setting *AbsoluteUnitsNormalisation* to :literal:`'Absolute Units ON'`. In this case the data is multiplied by a factor
 
     :math:`f = \frac{N_V \sigma_V}{N_S}`
 
-after normalisation to vanadium. In the above, :math:`N_V` stands for the vanadium number density, :math:`\sigma_V` for vanadium total scattering cross section and :math:`N_S` sample number density. 
+after normalisation to vanadium giving units of barn to the data. In the above, :math:`N_V` stands for the vanadium number density, :math:`\sigma_V` for vanadium total scattering cross section and :math:`N_S` sample number density. 
 
 The material properties should be set for *InputWorkspace* and *IntegratedVanadiumWorkspace* by :ref:`SetSample <algm-SetSample>` before running this algorithm .
 
@@ -49,6 +49,11 @@ After conversion from time-of-flight to energy transfer, the binning may differ
 
 *QBinningParams* are passed to :ref:`SofQWNormalisedPolygon <algm-SofQWNormalisedPolygon>` and have the same format as *EnergyRebinningParamas*. If the property is not specified, :math:`q` is binned to ten times the median :math:`2\theta` steps between the spectra.
 
+Transposing output
+##################
+
+After conversion to momentum transfer, the vertical axis of the data is in units of momentum transfer while the horizontal axis is in energy transfer. By default, the data is transposed such that momentum transfer is on the horizontal axis and energy transfer in the vertical. This can be turned off by setting *Transposing* to :literal:`'Transposing OFF'`.
+
 Usage
 -----
 
diff --git a/docs/source/algorithms/MaskInstrument-v1.rst b/docs/source/algorithms/MaskInstrument-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5fbf728786696e7b6c46fb64d96627b8b36ebc92
--- /dev/null
+++ b/docs/source/algorithms/MaskInstrument-v1.rst
@@ -0,0 +1,44 @@
+
+.. algorithm::
+
+.. summary::
+
+.. alias::
+
+.. properties::
+
+Description
+-----------
+
+Mask specified detectors in an instrument.
+This is does *not* clear the data in associated spectra in the workspace.
+To clear the data manually ``ClearMaskedSpectra`` can be called.
+
+Usage
+-----
+
+**Example - MaskInstrument**
+
+.. testcode:: MaskInstrumentExample
+
+  ws = CreateSampleWorkspace()
+  ws = MaskInstrument(InputWorkspace=ws, DetectorIDs='100,102-104')
+  # Detectors are masked but data is untouched
+  for i in range(6):
+    print("Detector {} masked: {:5} data {}".format(i, str(ws.getDetector(i).isMasked()), ws.readY(i)[0]))
+
+Output:
+
+.. testoutput:: MaskInstrumentExample
+
+  Detector 0 masked: True  data 0.3
+  Detector 1 masked: False data 0.3
+  Detector 2 masked: True  data 0.3
+  Detector 3 masked: True  data 0.3
+  Detector 4 masked: True  data 0.3
+  Detector 5 masked: False data 0.3
+
+.. categories::
+
+.. sourcelink::
+
diff --git a/docs/source/algorithms/PowderDiffILLDetScanReduction-v1.rst b/docs/source/algorithms/PowderDiffILLDetScanReduction-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3012609bb5c4617cb351d3690e3950f38d258788
--- /dev/null
+++ b/docs/source/algorithms/PowderDiffILLDetScanReduction-v1.rst
@@ -0,0 +1,98 @@
+.. algorithm::
+
+.. summary::
+
+.. alias::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm performs the data reduction for the D2B instrument at the ILL, and also for D20 when doing a detector
+scan.
+
+Input Runs
+----------
+
+Provide the list of the input runs, that is the runs corresponding to a single detector scan, following the syntax in
+:py:obj:`MultipleFileProperty <mantid.api.MultipleFileProperty>`. For specifying multiple runs it is best to use ``:``
+as the separator - e.g. ``508093:508095``. Using ``-`` is also possible, this will call
+:ref:`MergeRuns <algm-MergeRuns>`, but there is a performance penalty for this.
+
+Calibration
+-----------
+
+The NeXus files for D2B contain raw data and pre-calibrated data. Either of these can be used when loading.
+
+Normalisation Options
+---------------------
+
+The default is for normalisation to monitor, but this can be skipped.
+
+Output
+------
+
+The output from the algorithm is a :py:obj:`WorkspaceGroup <mantid.api.WorkspaceGroup>`, containing the requested
+outputs:
+
+* **Output2DTubes** - Outputs a 2D workspace of tube height against scattering angle. In other words with no correction
+  for Debye-Scherrer cones. It is expected that this is only used for checking alignment.
+* **Output2D** - Outputs a 2D workspace of height along tube against scattering angle for pixel in tube. Here
+  there is effectively a correction for the Debye-Scherrer cones.
+* **Output1D** - Outputs a 1D workspace of counts against scattering angle. The vertical integration range for this
+  is set in the ``HeightRange`` option.
+
+Note for D20 only the ``Output1D`` option will be relevant.
+
+For ``Output2DTubes`` only the negative scattering angles are included, they are excluded for ``Output2D`` and
+``Output1D``.
+
+
+Use :ref:`SaveFocusedXYE <algm-SaveFocusedXYE>` to save in FullProf format #10, or :ref:`SaveGSS <algm-SaveGSS>` for
+GSAS format.
+
+Workflow
+--------
+
+.. diagram:: PowderDiffILLDetScanReduction-v1_wkflw.dot
+
+Related Algorithms
+------------------
+
+:ref:`PowderDiffILLReduction <algm-PowderDiffILLReduction>` can be used for D20 where the detector is static, and
+another variable such as temperature is scanned instead.
+
+Usage
+-----
+
+**Example - PowderDiffDetScanILLReduction**
+
+.. testsetup:: ExPowderDiffDetScanILLReduction
+
+   config['default.facility'] = 'ILL'
+   config['default.instrument'] = 'D2B'
+   config.appendDataSearchSubDir('ILL/D2B/')
+
+.. testcode:: ExPowderDiffDetScanILLReduction
+
+   red_ws = PowderDiffILLDetScanReduction(Run='508093:508095', Output2DTubes=True, Output2D=True, Output1D=True)
+   print("'2DTubes' output workspace has {0} diffractograms having {1} bins each".format(red_ws[0].getNumberHistograms(), red_ws[0].blocksize()))
+   print("'2D' output workspace has {0} diffractograms having {1} bins each".format(red_ws[1].getNumberHistograms(), red_ws[1].blocksize()))
+   print("'1D' output workspace has {0} diffractograms having {1} bins each".format(red_ws[2].getNumberHistograms(), red_ws[2].blocksize()))
+
+Output:
+
+.. testoutput:: ExPowderDiffDetScanILLReduction
+
+    '2DTubes' output workspace has 128 diffractograms having 3250 bins each
+    '2D' output workspace has 128 diffractograms having 3025 bins each
+    '1D' output workspace has 1 diffractograms having 3025 bins each
+
+.. testcleanup:: ExPowderDiffDetScanILLReduction
+
+   mtd.remove('red_ws')
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/SumOverlappingTubes-v1.rst b/docs/source/algorithms/SumOverlappingTubes-v1.rst
index 2d3580deee3a589e8a71398932c507a839fc3bd7..733646374c1ed2fdb19119b2c9dc2d0f39c7e47f 100644
--- a/docs/source/algorithms/SumOverlappingTubes-v1.rst
+++ b/docs/source/algorithms/SumOverlappingTubes-v1.rst
@@ -24,20 +24,20 @@ If the Normalise option is set then the counts for each point in the OutputWorks
 
 where :math:`C_{scaled, i}` is the scaled counts, :math:`C_i` the raw counts, :math:`N_{max}` is the maximum number of tube pixels contributing to any point in the OutputWorkspace, and :math:`N_{i}` is the number of tube pixels contributing to the point being scaled.
 
-2D Option
-+++++++++
+2DTubes Option
+++++++++++++++
 
 In this case the x-axis, the scattering angle, is the scattering angle of the tube centre. The height is the pixel height in the tube.
 
-2DStraight Option
-+++++++++++++++++
+2D Option
++++++++++
 
 In this case the x-axis is the true scattering angle of a pixel, so the Debye-Scherrer cones are effectively straightened. The x-axis is strictly positive in this case, the negative angles from the 2D case are put in the same bins as the equivalent positive angles. The height is the pixel height in the tube as for the 2D Option.
 
-1DStraight Option
-+++++++++++++++++
+1D Option
++++++++++
 
-This is effectively the 2DStraight option, with a single bin in the y-axis. For this case only the HeightAxis option can be given as a minimum and maximum.
+This is the same as the 2D option, with a single bin in the y-axis.
 
 Instrument Parameters
 +++++++++++++++++++++
@@ -46,12 +46,12 @@ If the instrument parameter ``mirror_detector_angles`` is set to true, then outp
 
 Usage
 -----
-**Example - an example of running SumOverlappingTubes in the 2D case.**
+**Example - an example of running SumOverlappingTubes in the 2DTubes case.**
 
 .. testcode:: SumOverlappingTubes2DComponent
 
     ws_508093 = Load('ILL/D2B/508093.nxs')
-    ws = SumOverlappingTubes(InputWorkspaces=ws_508093, OutputType='2D', ComponentForHeightAxis='tube_1')
+    ws = SumOverlappingTubes(InputWorkspaces=ws_508093, OutputType='2DTubes')
     print('X Size: ' + str(ws.blocksize()) + ', Y Size: ' + str(ws.getNumberHistograms()))
     print('Counts: ' + str(ws.dataY(63)[2068:2078]))
     print('Errors: ' + str(ws.dataE(63)[2068:2078]))
@@ -66,12 +66,12 @@ Output:
     Errors: [ 2.03598273  3.00072517  3.46216187  4.1661015   5.29106717  4.88935243
       5.28366714  4.68457643  3.1474581   0.96348311]
 
-**Example - an example of running SumOverlappingTubes in the 1DStraight case.**
+**Example - an example of running SumOverlappingTubes in the 1D case.**
 
 .. testcode:: SumOverlappingTubes1DHeightRange
 
     ws_508093 = Load('ILL/D2B/508093.nxs')
-    ws = SumOverlappingTubes(InputWorkspaces=ws_508093, OutputType='1DStraight', CropNegativeScatteringAngles=True, HeightAxis='-0.05,0.05')
+    ws = SumOverlappingTubes(InputWorkspaces=ws_508093, OutputType='1D', CropNegativeScatteringAngles=True, HeightAxis='-0.05,0.05')
     print('X Size: ' + str(ws.blocksize()) + ', Y Size: ' + str(ws.getNumberHistograms()))
     print('Counts: ' + str(ws.dataY(0)[2068:2078]))
     print('Errors: ' + str(ws.dataE(0)[2068:2078]))
diff --git a/docs/source/development/AlgorithmMPISupport.rst b/docs/source/development/AlgorithmMPISupport.rst
index ab095259a509d52917faf1a430007c6b70147857..d1537164bcb853f74dfadbaf0a6127028d2684b1 100644
--- a/docs/source/development/AlgorithmMPISupport.rst
+++ b/docs/source/development/AlgorithmMPISupport.rst
@@ -139,6 +139,19 @@ Build with MPI support
 To build Mantid with MPI support as described in this document run ``cmake`` with the additional option ``-DMPI_EXPERIMENTAL=ON``.
 This requires ``boost-mpi`` and a working MPI installation.
 
+Configuration
+-------------
+
+To avoid unintentional DDOSing or potential other issues, there is no MPI support for ``DownloadInstrument`` and ``CheckMantidVersion``.
+Error messages can be avoided by disabling these startup checks in the configuration.
+Furthermore, to avoid pollution of usage reports, usage reporting should be disabled:
+
+.. code-block:: sh
+
+  UpdateInstrumentDefinitions.OnStartup = 0
+  CheckMantidVersion.OnStartup = 0
+  usagereports.enabled = 0
+
 Writing and running Python scripts
 ----------------------------------
 
@@ -165,15 +178,11 @@ For example:
       print(ws.readY(i))
 
 
-Run Python with ``mpirun`` and the desired number of MPI ranks:
+Run Python with ``mpirun`` and the desired number of MPI ranks, by using the new ``-n`` flag to ``mantidpython``:
 
 .. code-block:: sh
 
-  mpirun -n 3 python test.py
-
-Note that directly using the Mantid Python wrapper ``mantidpython`` is not possible, i.e., ``mpirun -n 3 mantidpython test.py`` does not work.
-Instead the correct paths to Mantid and library preloads should be set manually.
-Alternatively, a modified version of ``mantidpython`` that internally uses ``mpirun`` to call python could be created.
+  mantidpython -n 3 test.py
 
 Possible output:
 
@@ -276,6 +285,7 @@ The level of thereby enabled MPI support is as follows:
 
 In the latter two cases more than one execution mode is supported.
 Thus this usually works only for algorithms with a single input (and a single output) such that the execution mode can be uniquely derived from the storage mode of the input workpace.
+Multiple inputs are also supported to a certain extent. For example, for ``API::DistributedAlgorithm`` the storage modes of the inputs can be mixed if they are compatible, such as ``StorageMode::Distributed`` with ``StorageMode::Cloned`` (resulting in ``ExecutionMode::Distributed``).
 
 If none of the other virtual methods listed above is implemented, ``Algorithm`` will run the normal ``exec()`` method on all MPI ranks.
 The exception are non-master ranks if the execution mode is ``ExecutionMode::MasterOnly`` -- in that case creating a dummy workspace is attempted.
@@ -456,116 +466,158 @@ Documentation
 When adding MPI support for an algorithm, add it to the table at the end of this document.
 Potential limitations must be described in the comments.
 
+Porting Python reduction scripts in practice
+--------------------------------------------
+
+The mechanism of execution modes and storage modes allows for "guided" porting of algorithms as follows:
+
+1. Run Python script such as a system test with two (or more) MPI ranks.
+2. At some point an algorithm without any MPI support or inadequate MPI support may be encountered, resulting in an error message similar to this:
+
+  .. code-block:: none
+
+    MyAlg-[Error] Error in execution of algorithm MyAlg:
+    MyAlg-[Error] Algorithm does not support execution with input workspaces of the following storage types:
+    MyAlg-[Error] InputWorkspace Parallel::StorageMode::Distributed
+    MyAlg-[Error] InputWorkspaceMonitor Parallel::StorageMode::Cloned
+    MyAlg-[Error] .
+
+3. Add the required MPI support to ``MyAlg`` with one of the mechanisms described above. In rare cases the combination of storage modes of the inputs may be unexpected, indicating an error earlier in the chain which needs to be fixed.
+4. Go to 1., until the script finishes successfully.
+
 Supported Algorithms
 ####################
 
-================================= ======================= ========
-Algorithm                         Supported modes         Comments
-================================= ======================= ========
-BinaryOperation                   all                     not supported if ``AllowDifferentNumberSpectra`` is enabled
-CalculateChiSquared               MasterOnly, Identical   see ``IFittingAlgorithm``
-CalculateCostFunction             MasterOnly, Identical   see ``IFittingAlgorithm``
-CalculateFlatBackground           MasterOnly, Identical
-CalculateTransmission             MasterOnly, Identical
-CloneWorkspace                    all
-CompareWorkspace                  MasterOnly, Identical   if one input has ``StorageMode::Cloned`` and the other has ``StorageMode::MasterOnly`` then ``ExecutionMode::MasterOnly`` is used
-CompressEvents                    all
-ConvertToHistogram                all
-ConvertToPointData                all
-ConvertUnits                      all                     ``AlignBins`` not supported; for indirect energy mode the number of resulting bins is in general inconsistent across MPI ranks
-CreateSingleValuedWorkspace       Identical               ``OutputWorkspace`` has ``StorageMode::Cloned``, support of ``MasterOnly`` would require adding property for selecting the mode
-CreateWorkspace                   all
-CropToComponent                   all
-CropWorkspace                     all                     see ``ExtractSpectra`` regarding X cropping
-Divide                            all                     see ``BinaryOperation``
-EstimateFitParameters             MasterOnly, Identical   see ``IFittingAlgorithm``
-EvaluateFunction                  MasterOnly, Identical   see ``IFittingAlgorithm``
-ExponentialCorrection             all                     see ``UnaryOperation``
-ExtractSingleSpectrum             all                     in practice ``ExecutionMode::Distributed`` not supported due to current nonzero-spectrum-count limitation
-ExtractSpectra2                   all                     currently not available via algorithm factory or Python
-ExtractSpectra                    all                     not supported with ``DetectorList``, cropping in X may exhibit inconsistent behavior in case spectra have common boundaries within some ranks but not within all ranks or across ranks
-FilterBadPulses                   all
-FilterByLogValue                  all
-FilterByTime                      all
-FilterEventsByLogValuePreNexus    Identical               see ``IFileLoader``
-FindDetectorsInShape              all
-Fit                               MasterOnly, Identical   see ``IFittingAlgorithm``
-GroupWorkspaces                   all
-IFileLoader                       Identical               implicitly adds support for many load-algorithms inheriting from this
-IFittingAlgorithm                 MasterOnly, Identical   implicitly adds support for several fit-algorithms inheriting from this
-Load                              all                     actual supported mode is dictated by underlying load algorithm, which depends on file type
-LoadAscii2                        Identical               see ``IFileLoader``
-LoadAscii                         Identical               see ``IFileLoader``
-LoadBBY                           Identical               see ``IFileLoader``
-LoadCanSAS1D                      Identical               see ``IFileLoader``
-LoadDaveGrp                       Identical               see ``IFileLoader``
-LoadEmptyInstrument               Identical               see ``IFileLoader``
-LoadEventNexus                    Distributed             storage mode of output cannot be changed via a parameter currently, min and max bin boundary are not globally the same
-LoadEventPreNexus2                Identical               see ``IFileLoader``
-LoadFITS                          Identical               see ``IFileLoader``
-LoadGSS                           Identical               see ``IFileLoader``
-LoadILLDiffraction                Identical               see ``IFileLoader``
-LoadILLIndirect2                  Identical               see ``IFileLoader``
-LoadILLReflectometry              Identical               see ``IFileLoader``
-LoadILLSANS                       Identical               see ``IFileLoader``
-LoadILLTOF2                       Identical               see ``IFileLoader``
-LoadInstrument                    all
-LoadIsawPeaks                     Identical               see ``IFileLoader``
-LoadISISNexus2                    Identical               see ``IFileLoader``
-LoadLLB                           Identical               see ``IFileLoader``
-LoadMcStas                        Identical               see ``IFileLoader``
-LoadMcStasNexus                   Identical               see ``IFileLoader``
-LoadMD                            Identical               see ``IFileLoader``
-LoadMLZ                           Identical               see ``IFileLoader``
-LoadMuonNexus                     Identical               see ``IFileLoader``
-LoadNexusLogs                     all
-LoadNexusMonitors2                Identical
-LoadNexusProcessed                Identical               see ``IFileLoader``
-LoadNXcanSAS                      Identical               see ``IFileLoader``
-LoadNXSPE                         Identical               see ``IFileLoader``
-LoadParameterFile                 all                     segfaults when used in unit tests with MPI threading backend due to `#9365 <https://github.com/mantidproject/mantid/issues/9365>`_, normal use should be ok
-LoadPDFgetNFile                   Identical               see ``IFileLoader``
-LoadPreNexus                      Identical               see ``IFileLoader``
-LoadQKK                           Identical               see ``IFileLoader``
-LoadRawHelper                     Identical               see ``IFileLoader``
-LoadRKH                           Identical               see ``IFileLoader``
-LoadSassena                       Identical               see ``IFileLoader``
-LoadSESANS                        Identical               see ``IFileLoader``
-LoadSINQFocus                     Identical               see ``IFileLoader``
-LoadSNSspec                       Identical               see ``IFileLoader``
-LoadSPE                           Identical               see ``IFileLoader``
-LoadSpice2D                       Identical               see ``IFileLoader``
-LoadSQW2                          Identical               see ``IFileLoader``
-LoadSQW                           Identical               see ``IFileLoader``
-LoadSwans                         Identical               see ``IFileLoader``
-LoadTBL                           Identical               see ``IFileLoader``
-LoadTOFRawNexus                   Identical               see ``IFileLoader``
-Logarithm                         all                     see ``UnaryOperation``
-MaskBins                          all
-MaskDetectorsInShape              all
-MaskSpectra                       all
-Minus                             all                     see ``BinaryOperation``
-MoveInstrumentComponent           all
-Multiply                          all                     see ``BinaryOperation``
-OneMinusExponentialCor            all                     see ``UnaryOperation``
-Plus                              all                     see ``BinaryOperation``
-PoissonErrors                     all                     see ``BinaryOperation``
-PolynomialCorrection              all                     see ``UnaryOperation``
-Power                             all                     see ``UnaryOperation``
-PowerLawCorrection                all                     see ``UnaryOperation``
-Rebin                             all                     min and max bin boundaries must be given explicitly
-RebinToWorkspace                  all                     ``WorkspaceToMatch`` must have ``StorageMode::Cloned``
-RemovePromptPulse                 all
-ReplaceSpecialValues              all                     see ``UnaryOperation``
-RotateInstrumentComponent         all
-SaveNexus                         MasterOnly
-SaveNexusProcessed                MasterOnly
-SignalOverError                   all                     see ``UnaryOperation``
-SortEvents                        all
-SumSpectra                        MasterOnly, Identical
-UnaryOperation                    all
-WeightedMean                      all                     see ``BinaryOperation``
-================================= ======================= ========
+====================================== ======================= ========
+Algorithm                              Supported modes         Comments
+====================================== ======================= ========
+BinaryOperation                        all                     not supported if ``AllowDifferentNumberSpectra`` is enabled
+CalculateChiSquared                    MasterOnly, Identical   see ``IFittingAlgorithm``
+CalculateCostFunction                  MasterOnly, Identical   see ``IFittingAlgorithm``
+CalculateFlatBackground                MasterOnly, Identical
+CalculateTransmission                  MasterOnly, Identical
+CloneWorkspace                         all
+Comment                                all
+CompareWorkspace                       MasterOnly, Identical   if one input has ``StorageMode::Cloned`` and the other has ``StorageMode::MasterOnly`` then ``ExecutionMode::MasterOnly`` is used, with ``ExecutionMode::MasterOnly`` the workspaces always compare equal on non-master ranks
+CompressEvents                         all
+ConvertToHistogram                     all
+ConvertToPointData                     all
+ConvertUnits                           all                     ``AlignBins`` not supported; for indirect energy mode the number of resulting bins is in general inconsistent across MPI ranks
+CopyInstrumentParameters               all
+CreateSingleValuedWorkspace            Identical               ``OutputWorkspace`` has ``StorageMode::Cloned``, support of ``MasterOnly`` would require adding property for selecting the mode
+CreateWorkspace                        all
+CropToComponent                        all
+CropWorkspace                          all                     see ``ExtractSpectra`` regarding X cropping
+DeleteWorkspace                        all
+Divide                                 all                     see ``BinaryOperation``
+EstimateFitParameters                  MasterOnly, Identical   see ``IFittingAlgorithm``
+EvaluateFunction                       MasterOnly, Identical   see ``IFittingAlgorithm``
+ExponentialCorrection                  all                     see ``UnaryOperation``
+ExtractSingleSpectrum                  all                     in practice ``ExecutionMode::Distributed`` not supported due to current nonzero-spectrum-count limitation
+ExtractSpectra2                        all                     currently not available via algorithm factory or Python
+ExtractSpectra                         all                     not supported with ``DetectorList``, cropping in X may exhibit inconsistent behavior in case spectra have common boundaries within some ranks but not within all ranks or across ranks
+FilterBadPulses                        all
+FilterByLogValue                       all
+FilterByTime                           all
+FilterEventsByLogValuePreNexus         Identical               see ``IFileLoader``
+FindDetectorsInShape                   all
+Fit                                    MasterOnly, Identical   see ``IFittingAlgorithm``
+GroupWorkspaces                        all                     grouping workspaces with mixed ``StorageMode`` is not supported
+IFileLoader                            Identical               implicitly adds support for many load-algorithms inheriting from this
+IFittingAlgorithm                      MasterOnly, Identical   implicitly adds support for several fit-algorithms inheriting from this
+Load                                   all                     actual supported mode is dictated by underlying load algorithm, which depends on file type
+LoadAscii2                             Identical               see ``IFileLoader``
+LoadAscii                              Identical               see ``IFileLoader``
+LoadBBY                                Identical               see ``IFileLoader``
+LoadCanSAS1D                           Identical               see ``IFileLoader``
+LoadDaveGrp                            Identical               see ``IFileLoader``
+LoadEmptyInstrument                    Identical               see ``IFileLoader``
+LoadEventNexus                         Distributed             storage mode of output cannot be changed via a parameter currently, min and max bin boundary are not globally the same
+LoadEventPreNexus2                     Identical               see ``IFileLoader``
+LoadFITS                               Identical               see ``IFileLoader``
+LoadGSS                                Identical               see ``IFileLoader``
+LoadILLDiffraction                     Identical               see ``IFileLoader``
+LoadILLIndirect2                       Identical               see ``IFileLoader``
+LoadILLReflectometry                   Identical               see ``IFileLoader``
+LoadILLSANS                            Identical               see ``IFileLoader``
+LoadILLTOF2                            Identical               see ``IFileLoader``
+LoadInstrument                         all
+LoadIsawPeaks                          Identical               see ``IFileLoader``
+LoadISISNexus2                         Identical               see ``IFileLoader``
+LoadLLB                                Identical               see ``IFileLoader``
+LoadMask                               Identical
+LoadMcStas                             Identical               see ``IFileLoader``
+LoadMcStasNexus                        Identical               see ``IFileLoader``
+LoadMD                                 Identical               see ``IFileLoader``
+LoadMLZ                                Identical               see ``IFileLoader``
+LoadMuonNexus                          Identical               see ``IFileLoader``
+LoadNexusLogs                          all
+LoadNexusMonitors2                     Identical
+LoadNexusProcessed                     Identical               see ``IFileLoader``
+LoadNXcanSAS                           Identical               see ``IFileLoader``
+LoadNXSPE                              Identical               see ``IFileLoader``
+LoadParameterFile                      all                     segfaults when used in unit tests with MPI threading backend due to `#9365 <https://github.com/mantidproject/mantid/issues/9365>`_, normal use should be ok
+LoadPDFgetNFile                        Identical               see ``IFileLoader``
+LoadPreNexus                           Identical               see ``IFileLoader``
+LoadQKK                                Identical               see ``IFileLoader``
+LoadRawHelper                          Identical               see ``IFileLoader``
+LoadRKH                                Identical               see ``IFileLoader``
+LoadSassena                            Identical               see ``IFileLoader``
+LoadSESANS                             Identical               see ``IFileLoader``
+LoadSINQFocus                          Identical               see ``IFileLoader``
+LoadSNSspec                            Identical               see ``IFileLoader``
+LoadSPE                                Identical               see ``IFileLoader``
+LoadSpice2D                            Identical               see ``IFileLoader``
+LoadSQW2                               Identical               see ``IFileLoader``
+LoadSQW                                Identical               see ``IFileLoader``
+LoadSwans                              Identical               see ``IFileLoader``
+LoadTBL                                Identical               see ``IFileLoader``
+LoadTOFRawNexus                        Identical               see ``IFileLoader``
+Logarithm                              all                     see ``UnaryOperation``
+MaskBins                               all
+MaskDetectorsInShape                   all
+MaskSpectra                            all
+Minus                                  all                     see ``BinaryOperation``
+MoveInstrumentComponent                all
+Multiply                               all                     see ``BinaryOperation``
+OneMinusExponentialCor                 all                     see ``UnaryOperation``
+Q1D2                                   all                     not all optional normalization inputs are supported
+Plus                                   all                     see ``BinaryOperation``
+PoissonErrors                          all                     see ``BinaryOperation``
+PolynomialCorrection                   all                     see ``UnaryOperation``
+Power                                  all                     see ``UnaryOperation``
+PowerLawCorrection                     all                     see ``UnaryOperation``
+Rebin                                  all                     min and max bin boundaries must be given explicitly
+RebinToWorkspace                       all                     ``WorkspaceToMatch`` must have ``StorageMode::Cloned``
+RemovePromptPulse                      all
+ReplaceSpecialValues                   all                     see ``UnaryOperation``
+RotateInstrumentComponent              all
+SANSCalculateTransmission              MasterOnly, Identical
+SANSConvertToQ                         all
+SANSConvertToWavelength                all
+SANSConvertToWavelengthAndRebin        all
+SANSCreateAdjustmentWorkspaces         all
+SANSCreateWavelengthAndPixelAdjustment MasterOnly, Identical
+SANSCrop                               all
+SANSFitShiftScale                      MasterOnly, Identical
+SANSLoad                               MasterOnly, Identical   child algorithms may actually be run with ``ExecutionMode::Distributed`` if that is their default
+SANSMaskWorkspace                      all
+SANSMove                               all
+SANSNormalizeToMonitor                 MasterOnly, Identical
+SANSReductionCore                      all
+SANSScale                              all
+SANSSingleReduction                    all
+SANSSliceEvent                         all
+SANSStitch                             MasterOnly, Identical
+SaveNexus                              MasterOnly
+SaveNexusProcessed                     MasterOnly
+Scale                                  all
+SignalOverError                        all                     see ``UnaryOperation``
+SortEvents                             all
+SumSpectra                             MasterOnly, Identical
+UnaryOperation                         all
+WeightedMean                           all                     see ``BinaryOperation``
+====================================== ======================= ========
 
 Currently none of the above algorithms works with ``StorageMode::Distributed`` in case there are zero spectra on any rank.
 
diff --git a/docs/source/diagrams/DirectILLCollectData-v1_wkflw.dot b/docs/source/diagrams/DirectILLCollectData-v1_wkflw.dot
index 064d008b8ae34db018b191156b1c942da9d6b4d3..e31139da8d508b3de37ceb1335fd181ca0c38e86 100644
--- a/docs/source/diagrams/DirectILLCollectData-v1_wkflw.dot
+++ b/docs/source/diagrams/DirectILLCollectData-v1_wkflw.dot
@@ -27,11 +27,10 @@ digraph DirectILLCollectData {
     FindEPP [label="FindEPP \n or \n CreateEPP"]
     FindEPPAgain [label="FindEPP \n or \n CreateEPP"]
     IntegrateMonitor [label="Integrate monitor"]
-    LoadILLTOF [label="LoadILLTOF"]
-    MergeRuns [label="MergeRuns"]
+    LoadAndMerge [label="LoadAndMerge"]
     Normalize [label="Normalise to monitor/time"]
-    ScaleAfterNormalization [label="Scale"]
-    ScaleBkg [label="Scale"]
+    ScaleAfterNormalization [label="Multiply"]
+    ScaleBkg [label="Multiply"]
     SubtractBkgDetector [label="Subtract flat background"]
     SubtractBkgMonitor [label="Subtract flat background"]
   }
@@ -40,6 +39,7 @@ digraph DirectILLCollectData {
     $value_style
     detectors [label="Detector workspace"]
     monitors [label="Monitor workspace"]
+    normalisationScaling [label="'scaling_after_monitor_normalisation'\nsample log"]
   }
 
   subgraph decisions {
@@ -51,9 +51,8 @@ digraph DirectILLCollectData {
   }
 
   inputFileGiven -> inputFile [label="yes"]
-  inputFile -> LoadILLTOF
-  LoadILLTOF -> MergeRuns
-  MergeRuns -> ExtractMonitors
+  inputFile -> LoadAndMerge
+  LoadAndMerge -> ExtractMonitors
   inputFileGiven -> inputWS [label="no"]
   inputWS -> ExtractMonitors
   ExtractMonitors -> monitors
@@ -64,6 +63,7 @@ digraph DirectILLCollectData {
   detectors -> Normalize
   detectors -> CorrectTOFAxisRaw
   Normalize -> ScaleAfterNormalization
+  normalisationScaling -> ScaleAfterNormalization
   ScaleAfterNormalization -> inputBkgGiven
   inputBkgGiven -> CalculateFlatBackground [label="no"]
   CalculateFlatBackground -> outputBkg
diff --git a/docs/source/diagrams/PowderDiffILLDetScanReduction-v1_wkflw.dot b/docs/source/diagrams/PowderDiffILLDetScanReduction-v1_wkflw.dot
new file mode 100644
index 0000000000000000000000000000000000000000..aa4a9303b1f142b4dc108d24041153eea855ec15
--- /dev/null
+++ b/docs/source/diagrams/PowderDiffILLDetScanReduction-v1_wkflw.dot
@@ -0,0 +1,57 @@
+digraph PowderDiffILLDetScanReduction {
+  $global_style
+
+  subgraph values {
+    $value_style
+  }
+
+  subgraph decision {
+    $decision_style
+    MergeType
+    NormaliseTo
+    OutputWorkspaceTypes
+  }
+
+  subgraph params {
+  	$param_style
+    Run
+    UseCalibratedData
+    HeightRange
+    OutputWorkspace
+  }
+
+  subgraph algorithms {
+  	$algorithm_style
+    LoadILLDiffraction
+    MergeRuns
+    NormaliseToMonitor
+    SumOverlappingTubes2DTubes [label="SumOverlappingTubes"]
+    SumOverlappingTubes2D [label="SumOverlappingTubes"]
+    SumOverlappingTubes1D [label="SumOverlappingTubes"]
+    GroupWorkspaces
+  }
+
+  subgraph processes {
+    $process_style
+  }
+
+  Run -> LoadILLDiffraction
+  UseCalibratedData -> LoadILLDiffraction
+  LoadILLDiffraction -> MergeType
+  MergeType -> MergeRuns [label="Merge"]
+  MergeRuns -> NormaliseTo
+  MergeType -> NormaliseTo [label="Skip Merge"]
+  NormaliseTo -> NormaliseToMonitor [label="Monitor"]
+  NormaliseTo -> OutputWorkspaceTypes [label="None"]
+  NormaliseToMonitor -> OutputWorkspaceTypes
+  OutputWorkspaceTypes -> SumOverlappingTubes2DTubes [label="2DTubes"]
+  HeightRange -> SumOverlappingTubes2DTubes
+  HeightRange -> SumOverlappingTubes2D
+  HeightRange -> SumOverlappingTubes1D
+  OutputWorkspaceTypes -> SumOverlappingTubes2D [label="2D"]
+  OutputWorkspaceTypes -> SumOverlappingTubes1D [label="1D"]
+  SumOverlappingTubes2DTubes -> GroupWorkspaces
+  SumOverlappingTubes2D -> GroupWorkspaces
+  SumOverlappingTubes1D -> GroupWorkspaces
+  GroupWorkspaces -> OutputWorkspace
+}
diff --git a/docs/source/release/v3.12.0/diffraction.rst b/docs/source/release/v3.12.0/diffraction.rst
index 2dc53bb4fe4c9306b34bc13d959d97ececf36007..12b41f5859e0f9b7fab294827ac014dc32d4d2a8 100644
--- a/docs/source/release/v3.12.0/diffraction.rst
+++ b/docs/source/release/v3.12.0/diffraction.rst
@@ -25,6 +25,7 @@ Powder Diffraction
 - :ref:`PDCalibration <algm-PDCalibration>` returns three more diagnostic workspaces: one for the fitted peak heights, one for the fitted peak widths, and one for observed resolution.
 - :ref:`LoadILLDiffraction <algm-LoadILLDiffraction>` now supports D2B files with calibrated data.
 - :ref:`PowderDiffILLReduction <algm-PowderDiffILLReduction>` and :ref:`PowderDiffILLDetEffCorr <algm-PowderDiffILLDetEffCorr>` enable the basic data reduction for D20 scanning powder diffractometer at ILL.
+- :ref:`PowderDiffILLDetScanReduction <algm-PowderDiffILLDetScanReduction>` supports D2B and D20 (when doing a detector scan) powder diffraction reduction at the ILL.
 - New algorithm :ref:`algm-SumOverlappingTubes` combines a detector scan for D2B into a single workspace.
 - ISIS Powder scripts for HRPD now support extra TOF windows 10-50 and 180-280
 - After calling create_vanadium and focus in ISIS Powder scripts on POLARIS, the output workspaces always contain the sample material if it is set using set_sample_material. (To view the sample material, right click the workspace and click 'Sample Material...')
@@ -50,7 +51,8 @@ Single Crystal Diffraction
 --------------------------
 - :ref:`FilterPeaks <algm-FilterPeaks>` now supports filtering peaks by TOF, d-spacing, and wavelength.
 - HB3A reduction interface has been enhanced.  A child window is added to it for users to pre-process scans and save the processed and merged data to NeXus files in order to save time when they start to reduce and visualize the data. A record file is generated along with processed scans to record the calibration information. During data reduction, scans that have been processed in pre-processing will be loaded automatically from corresponding MD files.
-- Fix a bug in :ref:`IntegrateEllipsoids <algm-IntegrateEllipsoids>` and :ref:`IntegrateEllipsoidsTwoStep <algm-IntegrateEllipsoidsTwoStep>` that forced output to be weighted by the bin width.
+- Fixed a bug in :ref:`IntegrateEllipsoids <algm-IntegrateEllipsoids>` and :ref:`IntegrateEllipsoidsTwoStep <algm-IntegrateEllipsoidsTwoStep>` that forced output to be weighted by the bin width.
+- Fixed a bug in :ref:`IntegrateEllipsoidsTwoStep <algm-IntegrateEllipsoidsTwoStep>` where peaks with negative intensity values would be set to zero.
 - :ref:`IntegratePeaksMDHKL <algm-IntegratePeaksMDHKL>` now has option to specify background shell instead of using default background determination.
 
 - In HB3A reduction interface, section for downloading experimental data via http server has been removed from main UI.
@@ -66,6 +68,7 @@ Single Crystal Diffraction
 - SCD Event Data Reduction interface now uses the Indexing Tolerance for Index Peaks to index the peaks for the Select Cell options in Choose Cell tab.  Previously it used a constant, 0.12, for the tolerance.
 
 
+
 Total Scattering
 ----------------
 - A basic analysis for total scattering method ``create_total_scattering_pdf`` has been added to POLARIS. More information can be found on the POLARIS reference page.
diff --git a/docs/source/release/v3.12.0/framework.rst b/docs/source/release/v3.12.0/framework.rst
index 55bdd66f8d710be0be46c2600bb8daecc37d47a4..2f68db23046f0deffaaaa7f98455e5399dffb50e 100644
--- a/docs/source/release/v3.12.0/framework.rst
+++ b/docs/source/release/v3.12.0/framework.rst
@@ -81,7 +81,10 @@ In `mantid.simpleapi`, a keyword has been implemented for function-like algorith
 - The ``isDefault`` attribute for workspace properties now works correctly with workspaces not in the ADS.
 - The previously mentioned ``ConfigObserver`` and ``ConfigPropertyObserver`` classes are also exposed to python.
 - ``mantid.kernel.V3D`` vectors now support negation through the usual ``-`` operator.
-- ``mantid.api.IPeak`` has two new functions ``getEnergyTransfer`` which returns the difference between the initial and final energy and ``getIntensityOverSigma`` which gets the peak intensity divided by the error in intensity.
+- ``mantid.api.IPeak`` has three new functions:
+    - ``getEnergyTransfer`` which returns the difference between the initial and final energy.
+    - ``getIntensityOverSigma`` which returns the peak intensity divided by the error in intensity.
+    - ``getGoniometerMatrix`` which returns the goniometer rotation matrix associated with the peak.
 
 Support for unicode property names has been added to python. This means that one can run the following in python2 or python3.
 
diff --git a/docs/source/techniques/DirectILL.rst b/docs/source/techniques/DirectILL.rst
index e518fbde52abc29f5f344501714749de94ed8b4b..41e437435ad0f11825a12ccbc22ed3e00a6c0080 100644
--- a/docs/source/techniques/DirectILL.rst
+++ b/docs/source/techniques/DirectILL.rst
@@ -33,6 +33,11 @@ Together with the other algorithms and services provided by the Mantid framework
 
 This document tries to give an overview on how the algorithms work together via Python examples. Please refer to the algorithm documentation for details of each individual algorithm.
 
+Instrument specific defaults and recommendations
+################################################
+
+Some algorithm properties have the word 'AUTO' in their default value. This means that the default will be chosen according to the instrument by reading the actual default from the instrument parameters. The documentation of the algorithms which have these types of properties includes tables showing the defaults for supported ILL instruments.
+
 Reduction basics
 ================
 
@@ -59,7 +64,7 @@ These steps would translate to something like the following simple Python script
 
     # Vanadium
     DirectILLCollectData(
-        Run='0100:0109',
+        Run='0100-0109',
         OutputWorkspace='vanadium',
         OutputEPPWorkspace='vanadium-epps',  # Elastic peak positions.
         OutputRawWorkspace='vanadium-raw'    # 'Raw' data for diagnostics.
@@ -80,7 +85,7 @@ These steps would translate to something like the following simple Python script
 
     # Sample
     DirectILLCollectData(
-        Run='0201, 0205, 0209-0210',
+        Run='0201+0205+0209-0210',
         OutputWorkspace='sample'
     )
 
@@ -148,7 +153,7 @@ The above workflow would translate to this kind of Python script:
 
     # Vanadium
     DirectILLCollectData(
-        Run='0100:0109',
+        Run='0100-0109',
         OutputWorkspace='vanadium',
         OutputEPPWorkspace='vanadium-epps',  # Elastic peak positions.
         OutputRawWorkspace='vanadium-raw'    # 'Raw' data for diagnostics.
@@ -169,7 +174,7 @@ The above workflow would translate to this kind of Python script:
 
     # Sample
     DirectILLCollectData(
-        Run='0201, 0205, 0209-0210',
+        Run='0201+0205+0209-0210',
         OutputWorkspace='sample',
     )
 
@@ -217,20 +222,20 @@ To alleviate the situation, the output workspaces of :ref:`algm-DirectILLCollect
 .. code-block:: python
 
     DirectILLCollectData(
-        Run='0100:0109',
+        Run='0100-0109',
         OutputWorkspace='sample1',
         OutputIncidentEnergyWorkspace='Ei'  # Get a common incident energy.
     )
 
     # Empty container.
     DirectILLCollectData(
-        Run='0201:0205',
+        Run='0201-0205',
         OutputWorkspace='container',
         IncidentEnergyWorkspace='Ei'  # Empty container should have same TOF binning.
     )
 
     # More samples with same nominal wavelength and container as 'sample1'.
-    runs = ['0110:0119,', '0253:0260']
+    runs = ['0110-0119', '0253-0260']
     index = 1
     for run in runs:
         DirectILLCollectData(
@@ -277,7 +282,7 @@ A corresponding Python script follows.
 
     # Vanadium
     DirectILLCollectData(
-        Run='0100:0109',
+        Run='0100-0109',
         OutputWorkspace='vanadium',
         OutputEPPWorkspace='vanadium-epps',
         OutputRawWorkspace='vanadium-raw'
@@ -298,14 +303,14 @@ A corresponding Python script follows.
 
     # Sample
     DirectILLCollectData(
-        Run='0201, 0205, 0209-0210',
+        Run='0201+0205+0209-0210',
         OutputWorkspace='sample',
         OutputIncidentEnergyWorkspace='Ei'
     )
 
     # Container
     DirectILLCollectData(
-        Run='0333:0335',
+        Run='0333-0335',
         OutputWorkspace='container',
         IncidentEnergyWorkspace='Ei'
     )
@@ -464,7 +469,7 @@ Lets put it all together into a complex Python script. The script below reduces
     mantid.config.appendDataSearchDir('/data/')
 
     # Gather dataset information.
-    containerRuns = '96,97'
+    containerRuns = '96+97'
     vanadiumRuns = '100-103'
     # Samples at 50K, 100K and 150K.
     # Wavelength 1
@@ -473,14 +478,14 @@ Lets put it all together into a complex Python script. The script below reduces
         150: '138-143'
     }
     runs1 = {
-        50: '105, 107-110',
+        50: '105+107-110',
         100: '112-117',
-        150: '119-123, 125'
+        150: '119-123+125'
     }
     # Wavelength 2
     containerRun2 = '166-170'
     runs2 = {
-        50: '146, 148, 150',
+        50: '146+148+150',
         100: '151-156',
         150: '160-165'
     }
diff --git a/docs/source/techniques/ISISPowder-Polaris-v1.rst b/docs/source/techniques/ISISPowder-Polaris-v1.rst
index 7a64aeeee5e0f2950f5c185f3abe92d466821bf9..0fee41eead139b57f9b7638f8447b9252284d7b6 100644
--- a/docs/source/techniques/ISISPowder-Polaris-v1.rst
+++ b/docs/source/techniques/ISISPowder-Polaris-v1.rst
@@ -617,11 +617,11 @@ On POLARIS this is set to the following TOF windows:
 ..  code-block:: python
 
   focused_cropping_values = [
-      (1500, 19900),  # Bank 1
-      (1500, 19900),  # Bank 2
-      (1500, 19900),  # Bank 3
-      (1500, 19900),  # Bank 4
-      (1500, 19900),  # Bank 5
+      (700,  30000),  # Bank 1
+      (1200, 24900),  # Bank 2
+      (1100, 19950),  # Bank 3
+      (1100, 19950),  # Bank 4
+      (1100, 19950),  # Bank 5
       ]
 
 .. _grouping_file_name_polaris_isis-powder-diffraction-ref:
diff --git a/images/MantidSplashScreen_2400x1758.jpg b/images/MantidSplashScreen_2400x1758.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..233fd7adb6b1815522314e05c082016a100b0720
Binary files /dev/null and b/images/MantidSplashScreen_2400x1758.jpg differ
diff --git a/instrument/D20_Parameters.xml b/instrument/D20_Parameters.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9f4ac0587877c80ecde601fa073aeaa48aa44f67
--- /dev/null
+++ b/instrument/D20_Parameters.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<parameter-file instrument="D20" valid-from="1900-01-31 23:59:59">
+	<component-link name="D20">
+
+		<parameter name="detector_for_height_axis" type="string">
+			<value val="panel_1" />
+		</parameter>
+
+	</component-link>
+</parameter-file>
diff --git a/instrument/D2B_Parameters.xml b/instrument/D2B_Parameters.xml
index 5cbb43d7571f0cb66005a5ad1ec2af9592b512f2..49b19be1c5f237c146aa83a022cce4c4c207b2c4 100644
--- a/instrument/D2B_Parameters.xml
+++ b/instrument/D2B_Parameters.xml
@@ -5,6 +5,9 @@
 		<parameter name="mirror_detector_angles" type="bool">
 			<value val="true" />
 		</parameter>
+		<parameter name="detector_for_height_axis" type="string">
+			<value val="tube_1" />
+		</parameter>
 
 	</component-link>
 </parameter-file>
diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt
index cff74d26971a86b59eb6b18ffff2d388950fecbc..28e79e57ca2f3bf5ee139bc00793373bd40f8634 100644
--- a/qt/CMakeLists.txt
+++ b/qt/CMakeLists.txt
@@ -20,7 +20,7 @@ function ( add_python_package pkg_name )
         set ( _startup_exe ${_egg_link_dir}/${pkg_name} )
       endif ()
   endif ()
-  set ( _outputs "${_egg_link} ${_startup_script} ${_startup_exe}" )
+  set ( _outputs ${_egg_link} ${_startup_script} ${_startup_exe} )
   add_custom_command ( OUTPUT ${_outputs}
     COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${_egg_link_dir}
       ${PYTHON_EXECUTABLE} ${_setup_py} develop
diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py
index d9321eaa0aa5ee42aa4cfe42f5a1fc8b3df8bc90..87b32d3f57e6b43895924cec3da8d1956377d466 100644
--- a/qt/applications/workbench/workbench/app/mainwindow.py
+++ b/qt/applications/workbench/workbench/app/mainwindow.py
@@ -27,6 +27,7 @@ import sys
 # -----------------------------------------------------------------------------
 # Constants
 # -----------------------------------------------------------------------------
+MPL_BACKEND = 'module://workbench.plotting.backend_workbench'
 SYSCHECK_INTERVAL = 50
 ORIGINAL_SYS_EXIT = sys.exit
 ORIGINAL_STDOUT = sys.stdout
@@ -42,7 +43,7 @@ requirements.check_qt()
 # -----------------------------------------------------------------------------
 # Qt
 # -----------------------------------------------------------------------------
-from qtpy.QtCore import (QCoreApplication, QEventLoop, Qt, QTimer)  # noqa
+from qtpy.QtCore import (QEventLoop, Qt, QTimer)  # noqa
 from qtpy.QtGui import (QColor, QPixmap)  # noqa
 from qtpy.QtWidgets import (QApplication, QDesktopWidget, QFileDialog,
                             QMainWindow, QSplashScreen)  # noqa
@@ -50,16 +51,13 @@ from mantidqt.utils.qt import plugins, widget_updates_disabled  # noqa
 
 # Pre-application setup
 plugins.setup_library_paths()
-if hasattr(Qt, 'AA_EnableHighDpiScaling'):
-    QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
+
 
 # -----------------------------------------------------------------------------
 # Create the application instance early, set the application name for window
 # titles and hold on to a reference to it. Required to be performed early so
 # that the splash screen can be displayed
 # -----------------------------------------------------------------------------
-
-
 def qapplication():
     """Either return a reference to an existing application instance
     or create a new one
@@ -112,6 +110,8 @@ class MainWindow(QMainWindow):
 
         qapp = QApplication.instance()
         qapp.setAttribute(Qt.AA_UseHighDpiPixmaps)
+        if hasattr(Qt, 'AA_EnableHighDpiScaling'):
+            qapp.setAttribute(Qt.AA_EnableHighDpiScaling, True)
 
         self.setWindowTitle("Mantid Workbench")
 
@@ -175,14 +175,12 @@ class MainWindow(QMainWindow):
         self.ipythonconsole.register_plugin()
         self.widgets.append(self.ipythonconsole)
 
-        self.set_splash("Loading Workspace Widget")
         from workbench.plugins.workspacewidget import WorkspaceWidget
         self.workspacewidget = WorkspaceWidget(self)
         self.workspacewidget.register_plugin()
         self.widgets.append(self.workspacewidget)
 
         self.setup_layout()
-        self.read_user_settings()
         self.create_actions()
         self.populate_menus()
 
@@ -326,9 +324,17 @@ class MainWindow(QMainWindow):
                     row[0].dockwidget.show()
                     row[0].dockwidget.raise_()
 
-    def read_user_settings(self):
-        for widget in self.widgets:
-            widget.read_user_settings(CONF.qsettings)
+    # ----------------------- Events ---------------------------------
+    def closeEvent(self, event):
+        # Close editors
+        self.editor.app_closing()
+
+        # Close all open plots
+        # We don't want this at module scope here
+        import matplotlib.pyplot as plt  #noqa
+        plt.close('all')
+
+        event.accept()
 
     # ----------------------- Slots ---------------------------------
     def open_file(self):
@@ -369,13 +375,28 @@ def start_workbench(app):
     """Given an application instance create the MainWindow,
     show it and start the main event loop
     """
+    # The ordering here is very delicate. Test thoroughly when
+    # changing anything!
     main_window = MainWindow()
-    main_window.setup()
 
-    preloaded_packages = ('mantid',)
-    for name in preloaded_packages:
-        main_window.set_splash('Preloading ' + name)
-        importlib.import_module(name)
+    # Load matplotlib as early as possible.
+    # Setup our custom backend and monkey patch in custom current figure manager
+    main_window.set_splash('Preloading matplotlib')
+    mpl = importlib.import_module('matplotlib')
+    # Replace vanilla Gcf with our custom instance
+    _pylab_helpers = importlib.import_module('matplotlib._pylab_helpers')
+    currentfigure = importlib.import_module('workbench.plotting.currentfigure')
+    setattr(_pylab_helpers, 'Gcf', getattr(currentfigure, 'CurrentFigure'))
+    # Set up out custom matplotlib backend early. It must be done on
+    # the main thread
+    mpl.use(MPL_BACKEND)
+
+    # Setup widget layouts etc. mantid cannot be imported before this
+    # or the log messages don't get through
+    main_window.setup()
+    # start mantid
+    main_window.set_splash('Preloading mantid')
+    importlib.import_module('mantid')
 
     main_window.show()
     if main_window.splash:
diff --git a/qt/applications/workbench/workbench/plotting/__init__.py b/qt/applications/workbench/workbench/plotting/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..38b6452e68f2d819b646bccff5c213e92581ea5a
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/__init__.py
@@ -0,0 +1,16 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
diff --git a/qt/applications/workbench/workbench/plotting/axiseditor.ui b/qt/applications/workbench/workbench/plotting/axiseditor.ui
new file mode 100644
index 0000000000000000000000000000000000000000..63a6b78635fd69681a2d1d63e4bf4f10a36ce367
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/axiseditor.ui
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>245</width>
+    <height>204</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Edit axis</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="3" column="1">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Min</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="2">
+    <widget class="QLineEdit" name="editor_min"/>
+   </item>
+   <item row="4" column="1">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Max</string>
+     </property>
+    </widget>
+   </item>
+   <item row="6" column="2">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="2">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QCheckBox" name="logBox">
+       <property name="layoutDirection">
+        <enum>Qt::RightToLeft</enum>
+       </property>
+       <property name="text">
+        <string>Log:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QCheckBox" name="gridBox">
+       <property name="layoutDirection">
+        <enum>Qt::RightToLeft</enum>
+       </property>
+       <property name="text">
+        <string>Grid:</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="4" column="2">
+    <widget class="QLineEdit" name="editor_max"/>
+   </item>
+   <item row="2" column="1" colspan="2">
+    <widget class="QLabel" name="errors">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="styleSheet">
+      <string notr="true">.QLabel{background-color: #F18888; border: 1px solid gray; border-radius: 5px}</string>
+     </property>
+     <property name="frameShape">
+      <enum>QFrame::Box</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Sunken</enum>
+     </property>
+     <property name="text">
+      <string>Error text</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/qt/applications/workbench/workbench/plotting/backend_workbench.py b/qt/applications/workbench/workbench/plotting/backend_workbench.py
new file mode 100644
index 0000000000000000000000000000000000000000..bcb4613ddfec24ad1128295f76ff0bdf24592300
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/backend_workbench.py
@@ -0,0 +1,57 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Qt-based matplotlib backend that can operate when called from non-gui threads.
+
+It uses qtagg for rendering but the ensures that any rendering calls
+are done on the main thread of the application as the default
+"""
+from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+# std imports
+import importlib
+
+
+# 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
+)
+
+# Import the *real* matplotlib backend for the canvas
+mpl_qtagg_backend = importlib.import_module('matplotlib.backends.backend_qt{}agg'.format(QT_VERSION[0]))
+try:
+    FigureCanvas = getattr(mpl_qtagg_backend, 'FigureCanvasQTAgg')
+except KeyError:
+    raise ImportError("Unknown form of matplotlib Qt backend.")
+
+
+# -----------------------------------------------------------------------------
+# Backend implementation
+# -----------------------------------------------------------------------------
+show = QAppThreadCall(show_impl)
+new_figure_manager = QAppThreadCall(new_figure_manager_impl)
+new_figure_manager_given_figure = QAppThreadCall(new_figure_manager_given_figure_impl)
+draw_if_interactive = QAppThreadCall(draw_if_interactive_impl)
diff --git a/qt/applications/workbench/workbench/plotting/currentfigure.py b/qt/applications/workbench/workbench/plotting/currentfigure.py
new file mode 100644
index 0000000000000000000000000000000000000000..90b0096864166f5a43a410114c8f87dee6ced160
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/currentfigure.py
@@ -0,0 +1,158 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import absolute_import
+
+# std imports
+import atexit
+import gc
+
+# 3rdparty imports
+from six import itervalues
+
+
+class CurrentFigure(object):
+    """A singleton manager of all figures created through it. Analogous to _pylab_helpers.Gcf.
+
+    Each figure has a hold/active button attached to it:
+      - active toggled = next plot operation should replace this figure
+      - hold toggled = next plot operation should produce a new figure
+
+    Attributes:
+
+        *_active*:
+          A reference to the figure that is set as active, can be None
+
+        *figs*:
+          dictionary of the form {*num*: *manager*, ...}
+    """
+    _active = None
+    figs = {}
+
+    @classmethod
+    def get_fig_manager(cls, _):
+        """
+        If an active figure manager exists return it; otherwise
+        return *None*.
+        """
+        return cls.get_active()
+
+    @classmethod
+    def destroy(cls, num):
+        """
+        Try to remove all traces of figure *num*.
+
+        In the interactive backends, this is bound to the
+        window "destroy" and "delete" events.
+        """
+        if not cls.has_fignum(num):
+            return
+        manager = cls.figs[num]
+        manager.canvas.mpl_disconnect(manager._cidgcf)
+
+        if cls._active.num == num:
+            cls._active = None
+        del cls.figs[num]
+        manager.destroy()
+        gc.collect(1)
+
+    @classmethod
+    def destroy_fig(cls, fig):
+        "*fig* is a Figure instance"
+        num = None
+        for manager in itervalues(cls.figs):
+            if manager.canvas.figure == fig:
+                num = manager.num
+                break
+        if num is not None:
+            cls.destroy(num)
+
+    @classmethod
+    def destroy_all(cls):
+        # this is need to ensure that gc is available in corner cases
+        # where modules are being torn down after install with easy_install
+        import gc  # noqa
+        for manager in list(cls.figs.values()):
+            manager.canvas.mpl_disconnect(manager._cidgcf)
+            manager.destroy()
+
+        cls._active = None
+        cls.figs.clear()
+        gc.collect(1)
+
+    @classmethod
+    def has_fignum(cls, num):
+        """
+        Return *True* if figure *num* exists.
+        """
+        return num in cls.figs
+
+    @classmethod
+    def get_all_fig_managers(cls):
+        """
+        Return a list of figure managers.
+        """
+        return list(cls.figs.values())
+
+    @classmethod
+    def get_num_fig_managers(cls):
+        """
+        Return the number of figures being managed.
+        """
+        return len(cls.figs)
+
+    @classmethod
+    def get_active(cls):
+        """
+        Return the manager of the active figure, or *None*.
+        """
+        if cls._active is not None:
+            cls._active.canvas.figure.clf()
+        return cls._active
+
+    @classmethod
+    def set_active(cls, manager):
+        """
+        Make the figure corresponding to *manager* the active one.
+
+        Notifies all other figures to disable their active status.
+        """
+        cls._active = manager
+        active_num = manager.num
+        cls.figs[active_num] = manager
+        for manager in itervalues(cls.figs):
+            if manager.num != active_num:
+                manager.hold()
+
+    @classmethod
+    def draw_all(cls, force=False):
+        """
+        Redraw all figures registered with the pyplot
+        state machine.
+        """
+        for f_mgr in cls.get_all_fig_managers():
+            if force or f_mgr.canvas.figure.stale:
+                f_mgr.canvas.draw_idle()
+
+    # ------------------ Our additional interface -----------------
+
+    @classmethod
+    def set_hold(cls, manager):
+        """If this manager is active then set inactive"""
+        if cls._active == manager:
+            cls._active = None
+
+atexit.register(CurrentFigure.destroy_all)
diff --git a/qt/applications/workbench/workbench/plotting/figuremanager.py b/qt/applications/workbench/workbench/plotting/figuremanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..190771401206aefd22fe0a1b4434d879cfdd81b1
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/figuremanager.py
@@ -0,0 +1,324 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Provides our custom figure manager to wrap the canvas, window and our custom toolbar"""
+
+# std imports
+import functools
+import sys
+
+# 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, QMetaObject, QObject, QThread, Signal, Slot
+from qtpy.QtWidgets import QApplication, QLabel, QMainWindow
+from six import reraise, text_type
+
+# local imports
+from workbench.plotting.propertiesdialog import LabelEditor, XAxisEditor, YAxisEditor
+from workbench.plotting.toolbar import WorkbenchNavigationToolbar
+
+
+qApp = QApplication.instance()
+
+
+class QAppThreadCall(QObject):
+    """
+    Wraps a callable object and forces any calls made to it to be executed
+    on the same thread as the qApp object.
+    """
+
+    def __init__(self, callee):
+        QObject.__init__(self)
+        self.moveToThread(qApp.thread())
+        self.callee = callee
+        # Help should then give the correct doc
+        self.__call__.__func__.__doc__ = callee.__doc__
+        self._args = None
+        self._kwargs = None
+        self._result = None
+        self._exc_info = None
+
+    def __call__(self, *args, **kwargs):
+        """
+        If the current thread is the qApp thread then this
+        performs a straight call to the wrapped callable_obj. Otherwise
+        it invokes the do_call method as a slot via a
+        BlockingQueuedConnection.
+        """
+        if QThread.currentThread() == qApp.thread():
+            return self.callee(*args, **kwargs)
+        else:
+            self._store_function_args(*args, **kwargs)
+            QMetaObject.invokeMethod(self, "on_call",
+                                     Qt.BlockingQueuedConnection)
+            if self._exc_info is not None:
+                reraise(*self._exc_info)
+            return self._result
+
+    @Slot()
+    def on_call(self):
+        """Perform a call to a GUI function across a
+        thread and return the result
+        """
+        try:
+            self._result = \
+                self.callee(*self._args, **self._kwargs)
+        except Exception: # pylint: disable=broad-except
+            self._exc_info = sys.exc_info()
+
+    def _store_function_args(self, *args, **kwargs):
+        self._args = args
+        self._kwargs = kwargs
+        # Reset return value and exception
+        self._result = None
+        self._exc_info = None
+
+
+class MainWindow(QMainWindow):
+    closing = Signal()
+
+    def closeEvent(self, event):
+        self.closing.emit()
+        QMainWindow.closeEvent(self, event)
+
+
+class FigureManagerWorkbench(FigureManagerBase, QObject):
+    """
+    Attributes
+    ----------
+    canvas : `FigureCanvas`
+        The FigureCanvas instance
+    num : int or str
+        The Figure number
+    toolbar : qt.QToolBar
+        The qt.QToolBar
+    window : qt.QMainWindow
+        The qt.QMainWindow
+
+    """
+
+    def __init__(self, canvas, num):
+        QObject.__init__(self)
+        FigureManagerBase.__init__(self, canvas, num)
+        # Patch show/destroy to be thread aware
+        self._destroy_orig = self.destroy
+        self.destroy = QAppThreadCall(self._destroy_orig)
+        self._show_orig = self.show
+        self.show = QAppThreadCall(self._show_orig)
+
+        self.canvas = canvas
+        self.window = MainWindow()
+        self.window.closing.connect(canvas.close_event)
+        self.window.closing.connect(self._widgetclosed)
+
+        self.window.setWindowTitle("Figure %d" % num)
+
+        # Give the keyboard focus to the figure instead of the
+        # manager; StrongFocus accepts both tab and click to focus and
+        # will enable the canvas to process event w/o clicking.
+        # ClickFocus only takes the focus is the window has been
+        # clicked
+        # on. http://qt-project.org/doc/qt-4.8/qt.html#FocusPolicy-enum or
+        # http://doc.qt.digia.com/qt/qt.html#FocusPolicy-enum
+        self.canvas.setFocusPolicy(Qt.StrongFocus)
+        self.canvas.setFocus()
+
+        self.window._destroying = False
+
+        # add text label to status bar
+        self.statusbar_label = QLabel()
+        self.window.statusBar().addWidget(self.statusbar_label)
+
+        self.toolbar = self._get_toolbar(self.canvas, self.window)
+        if self.toolbar is not None:
+            self.window.addToolBar(self.toolbar)
+            self.toolbar.message.connect(self.statusbar_label.setText)
+            self.toolbar.sig_grid_toggle_triggered.connect(self.grid_toggle)
+            if hasattr(Gcf, 'set_hold'):
+                self.toolbar.sig_hold_triggered.connect(functools.partial(Gcf.set_hold, self))
+                self.toolbar.sig_active_triggered.connect(functools.partial(Gcf.set_active, self))
+            tbs_height = self.toolbar.sizeHint().height()
+        else:
+            tbs_height = 0
+
+        # resize the main window so it will display the canvas with the
+        # requested size:
+        cs = canvas.sizeHint()
+        sbs = self.window.statusBar().sizeHint()
+        self._status_and_tool_height = tbs_height + sbs.height()
+        height = cs.height() + self._status_and_tool_height
+        self.window.resize(cs.width(), height)
+
+        self.window.setCentralWidget(self.canvas)
+
+        if matplotlib.is_interactive():
+            self.window.show()
+            self.canvas.draw_idle()
+
+        def notify_axes_change(fig):
+            # This will be called whenever the current axes is changed
+            if self.toolbar is not None:
+                self.toolbar.update()
+        self.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.window.raise_()
+
+    def full_screen_toggle(self):
+        if self.window.isFullScreen():
+            self.window.showNormal()
+        else:
+            self.window.showFullScreen()
+
+    def _widgetclosed(self):
+        if self.window._destroying:
+            return
+        self.window._destroying = True
+        map(self.canvas.mpl_disconnect, self._cids)
+        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)
+
+    def resize(self, width, height):
+        'set the canvas size in pixels'
+        self.window.resize(width, height + self._status_and_tool_height)
+
+    def show(self):  # noqa
+        self.window.show()
+        self.window.activateWindow()
+        self.window.raise_()
+
+        # Hack to ensure the canvas is up to date
+        self.canvas.draw_idle()
+
+    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.window.close()
+
+    def grid_toggle(self):
+        """
+        Toggle grid lines on/off
+        """
+        canvas = self.canvas
+        axes = canvas.figure.get_axes()
+        for ax in axes:
+            ax.grid()
+        canvas.draw_idle()
+
+    def hold(self):
+        """
+        Mark this figure as held
+        """
+        self.toolbar.hold()
+
+    def get_window_title(self):
+        return text_type(self.window.windowTitle())
+
+    def set_window_title(self, title):
+        self.window.setWindowTitle(title)
+
+    # ------------------------ Interaction events --------------------
+    def on_button_press(self, event):
+        if not event.dblclick:
+            # shortcut
+            return
+        # We assume this is used for editing axis information e.g. labels
+        # which are outside of the axes so event.inaxes is no use.
+        canvas = self.canvas
+        figure = canvas.figure
+        axes = figure.get_axes()
+
+        def move_and_show(editor):
+            editor.move(event.x, figure.bbox.height - event.y + self.window.y())
+            editor.exec_()
+
+        for ax in axes:
+            if ax.title.contains(event)[0]:
+                move_and_show(LabelEditor(canvas, ax.title))
+            elif ax.xaxis.label.contains(event)[0]:
+                move_and_show(LabelEditor(canvas, ax.xaxis.label))
+            elif ax.yaxis.label.contains(event)[0]:
+                move_and_show(LabelEditor(canvas, ax.yaxis.label))
+            elif ax.xaxis.contains(event)[0]:
+                move_and_show(XAxisEditor(canvas, ax))
+            elif ax.yaxis.contains(event)[0]:
+                move_and_show(YAxisEditor(canvas, ax))
+
+
+# -----------------------------------------------------------------------------
+# Figure control
+# -----------------------------------------------------------------------------
+
+def new_figure_manager(num, *args, **kwargs):
+    """
+    Create a new figure manager instance
+    """
+    from matplotlib.figure import Figure  # noqa
+    figure_class = kwargs.pop('FigureClass', Figure)
+    this_fig = figure_class(*args, **kwargs)
+    return new_figure_manager_given_figure(num, this_fig)
+
+
+def new_figure_manager_given_figure(num, figure):
+    """
+    Create a new figure manager instance for the given figure.
+    """
+    canvas = FigureCanvasQTAgg(figure)
+    manager = FigureManagerWorkbench(canvas, num)
+    return manager
+
+
+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)
+    cx, sx = np.cos(x), np.sin(x)
+    fig_mgr_1 = new_figure_manager(1)
+    fig1 = fig_mgr_1.canvas.figure
+    ax = fig1.add_subplot(111)
+    ax.set_title("Test title")
+    ax.set_xlabel("$\mu s$")
+    ax.set_ylabel("Counts")
+
+    ax.plot(x, cx)
+    fig1.show()
+    qapp.exec_()
diff --git a/qt/applications/workbench/workbench/plotting/functions.py b/qt/applications/workbench/workbench/plotting/functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1bd66ecee266048f337fc4c3ca070ff8606efcc
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/functions.py
@@ -0,0 +1,184 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Defines a collection of functions to support plotting workspaces with
+our custom window.
+"""
+
+# std imports
+import math
+
+# 3rd party imports
+from mantid.api import MatrixWorkspace
+import matplotlib.pyplot as plt
+
+# local imports
+
+# -----------------------------------------------------------------------------
+# Constants
+# -----------------------------------------------------------------------------
+PROJECTION = 'mantid'
+DEFAULT_COLORMAP = 'viridis'
+# See https://matplotlib.org/api/_as_gen/matplotlib.figure.SubplotParams.html#matplotlib.figure.SubplotParams
+SUBPLOT_WSPACE = 0.5
+SUBPLOT_HSPACE = 0.5
+
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+def raise_if_not_sequence(seq, seq_name):
+    accepted_types = [list, tuple]
+    if type(seq) not in accepted_types:
+        raise ValueError("{} should be a list or tuple".format(seq_name))
+
+
+def _validate_plot_inputs(workspaces, spectrum_nums, wksp_indices):
+    """Raises a ValueError if any arguments have the incorrect types"""
+    if spectrum_nums is not None and wksp_indices is not None:
+        raise ValueError("Both spectrum_nums and wksp_indices supplied. "
+                         "Please supply only 1.")
+
+    if not isinstance(workspaces, MatrixWorkspace):
+        raise_if_not_sequence(workspaces, 'Workspaces')
+
+    if spectrum_nums is not None:
+        raise_if_not_sequence(spectrum_nums, 'spectrum_nums')
+
+    if wksp_indices is not None:
+        raise_if_not_sequence(wksp_indices, 'wksp_indices')
+
+
+def _validate_pcolormesh_inputs(workspaces):
+    """Raises a ValueError if any arguments have the incorrect types"""
+    if not isinstance(workspaces, MatrixWorkspace):
+        raise_if_not_sequence(workspaces, 'Workspaces')
+
+
+def plot(workspaces, spectrum_nums=None, wksp_indices=None, errors=False):
+    """
+    Create a figure with a single subplot and for each workspace/index add a
+    line plot to the new axes. show() is called before returning the figure instance. A legend
+    is added.
+
+    :param workspaces: A list of workspace handles
+    :param spectrum_nums: A list of spectrum number identifiers (general start from 1)
+    :param wksp_indices: A list of workspace indexes (starts from 0)
+    :param errors: If true then error bars are added for each plot
+    :returns: The figure containing the plots
+    """
+    # check inputs
+    _validate_plot_inputs(workspaces, spectrum_nums, wksp_indices)
+    if spectrum_nums is not None:
+        kw, nums = 'specNum', spectrum_nums
+    else:
+        kw, nums = 'wkspIndex', wksp_indices
+    # create figure
+    fig = plt.figure()
+    fig.clf()
+    ax = fig.add_subplot(111, projection=PROJECTION)
+    plot_fn = ax.errorbar if errors else ax.plot
+    for ws in workspaces:
+        for num in nums:
+            plot_fn(ws, **{kw: num})
+
+    ax.legend()
+    ax.set_title(workspaces[0].name())
+    fig.canvas.draw()
+    fig.show()
+    return fig
+
+
+def pcolormesh(workspaces):
+    """
+    Create a figure containing subplots
+
+    :param workspaces: A list of workspace handles
+    :param spectrum_nums: A list of spectrum number identifiers (general start from 1)
+    :param wksp_indices: A list of workspace indexes (starts from 0)
+    :param errors: If true then error bars are added for each plot
+    :returns: The figure containing the plots
+    """
+    # check inputs
+    _validate_pcolormesh_inputs(workspaces)
+
+    # create a subplot of the appropriate number of dimensions
+    # extend in number of columns if the number of plottables is not a square number
+    workspaces_len = len(workspaces)
+    square_side_len = int(math.ceil(math.sqrt(workspaces_len)))
+    nrows, ncols = square_side_len, square_side_len
+    if square_side_len*square_side_len != workspaces_len:
+        # not a square number - square_side_len x square_side_len
+        # will be large enough but we could end up with an empty
+        # row so chop that off
+        if workspaces_len <= (nrows-1)*ncols:
+            nrows -= 1
+
+    fig, axes = plt.subplots(nrows, ncols, squeeze=False,
+                             subplot_kw=dict(projection=PROJECTION))
+    row_idx, col_idx = 0, 0
+    for subplot_idx in range(nrows*ncols):
+        ax = axes[row_idx][col_idx]
+        if subplot_idx < workspaces_len:
+            ws = workspaces[subplot_idx]
+            ax.set_title(ws.name())
+            pcm = ax.pcolormesh(ws, cmap=DEFAULT_COLORMAP)
+            xticks = ax.get_xticklabels()
+            map(lambda lbl: lbl.set_rotation(45), xticks)
+            if col_idx < ncols - 1:
+                col_idx += 1
+            else:
+                row_idx += 1
+                col_idx = 0
+        else:
+            # nothing here
+            ax.axis('off')
+
+    # Adjust locations to ensure the plots don't overlap
+    fig.subplots_adjust(wspace=SUBPLOT_WSPACE, hspace=SUBPLOT_HSPACE)
+    fig.colorbar(pcm, ax=axes.ravel().tolist(), pad=0.06)
+    fig.canvas.draw()
+    fig.show()
+    return fig
+
+
+# Compatibility function for existing MantidPlot functionality
+def plotSpectrum(workspaces, indices, distribution=None, error_bars=False,
+                 type=None, window=None, clearWindow=None,
+                 waterfall=False):
+    """
+    Create a figure with a single subplot and for each workspace/index add a
+    line plot to the new axes. show() is called before returning the figure instance
+
+    :param workspaces: Workspace/workspaces to plot as a string, workspace handle, list of strings or list of
+    workspaces handles.
+    :param indices: A single int or list of ints specifying the workspace indices to plot
+    :param distribution: ``None`` (default) asks the workspace. ``False`` means
+                         divide by bin width. ``True`` means do not divide by bin width.
+                         Applies only when the the workspace is a MatrixWorkspace histogram.
+    :param error_bars: If true then error bars will be added for each curve
+    :param type: curve style for plot (-1: unspecified; 0: line, default; 1: scatter/dots)
+    :param window: Ignored. Here to preserve backwards compatibility
+    :param clearWindow: Ignored. Here to preserve backwards compatibility
+    :param waterfall:
+    """
+    if type == 1:
+        fmt = 'o'
+    else:
+        fmt = '-'
+
+    return plot(workspaces, wksp_indices=indices,
+                errors=error_bars, fmt=fmt)
diff --git a/qt/applications/workbench/workbench/plotting/labeleditor.ui b/qt/applications/workbench/workbench/plotting/labeleditor.ui
new file mode 100644
index 0000000000000000000000000000000000000000..ff6060a546dad139c584a904d7be508dab5ffbde
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/labeleditor.ui
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>218</width>
+    <height>112</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Edit label</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="errors">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="styleSheet">
+      <string notr="true">.QLabel{background-color: #F18888; border: 1px solid gray; border-radius: 5px}</string>
+     </property>
+     <property name="frameShape">
+      <enum>QFrame::Box</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Sunken</enum>
+     </property>
+     <property name="text">
+      <string>Error text</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLineEdit" name="editor"/>
+   </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>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/qt/applications/workbench/workbench/plotting/propertiesdialog.py b/qt/applications/workbench/workbench/plotting/propertiesdialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1d07a1a36944e63fc7e77de41430ffe0dfede7a
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/propertiesdialog.py
@@ -0,0 +1,175 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import (absolute_import, unicode_literals)
+
+# std imports
+
+# 3rdparty imports
+from mantidqt.utils.qt import load_ui
+from qtpy.QtGui import QDoubleValidator
+from qtpy.QtWidgets import QDialog
+
+
+SYMLOG_LIN_THRESHOLD = 0.01
+
+
+class PropertiesEditorBase(QDialog):
+    """Base class for all dialogs responsible for providing
+    access to change figure properties by clicking on the canvas"""
+
+    def __init__(self, ui_file, canvas):
+        """
+        :param canvas: A reference to the canvas to be updated
+        """
+        super(PropertiesEditorBase, self).__init__()
+        self.canvas = canvas
+        self.ui = load_ui(__file__, ui_file, baseinstance=self)
+        self.ui.buttonBox.accepted.connect(self.on_ok)
+        self.ui.buttonBox.rejected.connect(self.reject)
+
+    def on_ok(self):
+        try:
+            self.changes_accepted()
+            self.canvas.draw()
+        except Exception as exc:
+            # restore canvas and display error
+            self.error_occurred(exc)
+            self.canvas.draw()
+        else:
+            self.accept()
+
+    def changes_accepted(self):
+        raise NotImplementedError("Derived classes should override changes_accepted()")
+
+    def error_occurred(self, exc):
+        """Indicates a redraw error occurred. Derived classes should override this
+        and revert the state of the canvas and display the error
+        """
+        raise NotImplementedError("Derived classes should override error_occurred")
+
+
+class LabelEditorModel(object):
+
+    def __init__(self, label_text):
+        self.label_text = label_text
+
+
+class LabelEditor(PropertiesEditorBase):
+    """Provides a dialog box to edit a single label"""
+
+    def __init__(self, canvas, target):
+        """
+        :param target: A reference to the label being edited
+       """
+        super(LabelEditor, self).__init__('labeleditor.ui', canvas)
+        self.ui.errors.hide()
+
+        self.target = target
+        self._memento = LabelEditorModel(target.get_text())
+        self.ui.editor.setText(self._memento.label_text)
+
+    def changes_accepted(self):
+        self.ui.errors.hide()
+        self.target.set_text(self.ui.editor.text())
+
+    def error_occurred(self, exc):
+        """
+        Display errors to user and reset state
+        :param exc: The exception that occurred
+        """
+        self.target.set_text(self._memento.label_text)
+        self.ui.errors.setText(str(exc).strip())
+        self.ui.errors.show()
+
+
+class AxisEditorModel(object):
+
+    min = None
+    max = None
+    log = None
+    grid = None
+
+
+class AxisEditor(PropertiesEditorBase):
+
+    def __init__(self, canvas, axes, axis_id):
+        """
+
+        :param canvas: A reference to the target canvas
+        :param axes: The axes object holding the properties to be edited
+        :param axis_id: A string ID for the axis
+        """
+        super(AxisEditor, self).__init__('axiseditor.ui', canvas)
+        # suppress errors
+        self.ui.errors.hide()
+        # Ensure that only floats can be entered
+        self.ui.editor_min.setValidator(QDoubleValidator())
+        self.ui.editor_max.setValidator(QDoubleValidator())
+
+        self.axes = axes
+        self.axis_id = axis_id
+        self.lim_setter = getattr(axes, 'set_{}lim'.format(axis_id))
+        self.scale_setter = getattr(axes, 'set_{}scale'.format(axis_id))
+        self.linthresholdkw = 'linthres' + axis_id
+        # Grid has no direct accessor from the axes
+        axis = axes.xaxis if axis_id == 'x' else axes.yaxis
+
+        memento = AxisEditorModel()
+        self._momento = memento
+        memento.min, memento.max = getattr(axes, 'get_{}lim'.format(axis_id))()
+        memento.log = getattr(axes, 'get_{}scale'.format(axis_id))() != 'linear'
+        memento.grid = axis.majorTicks[0].gridOn
+
+        self._fill(memento)
+
+    def changes_accepted(self):
+        self.ui.errors.hide()
+        # apply properties
+        axes = self.axes
+
+        limit_min, limit_max = float(self.ui.editor_min.text()), float(self.ui.editor_max.text())
+        self.lim_setter(limit_min, limit_max)
+        if self.ui.logBox.isChecked():
+            self.scale_setter('symlog', **{self.linthresholdkw: SYMLOG_LIN_THRESHOLD})
+        else:
+            self.scale_setter('linear')
+        axes.grid(self.ui.gridBox.isChecked(), axis=self.axis_id)
+
+    def error_occurred(self, exc):
+        # revert
+        self._fill(self._memento)
+        # show error
+        self.ui.errors.setText(str(exc).strip())
+        self.ui.errors.show()
+
+    def _fill(self, model):
+        self.ui.editor_min.setText(str(model.min))
+        self.ui.editor_max.setText(str(model.max))
+        self.ui.logBox.setChecked(model.log)
+        self.ui.gridBox.setChecked(model.grid)
+
+
+class XAxisEditor(AxisEditor):
+
+    def __init__(self, canvas, axes):
+        super(XAxisEditor, self).__init__(canvas, axes, 'x')
+
+
+class YAxisEditor(AxisEditor):
+
+    def __init__(self, canvas, axes):
+        super(YAxisEditor, self).__init__(canvas, axes, 'y')
diff --git a/qt/applications/workbench/workbench/plotting/toolbar.py b/qt/applications/workbench/workbench/plotting/toolbar.py
new file mode 100644
index 0000000000000000000000000000000000000000..be81d4713253d027f12e7053161505d20f7971f6
--- /dev/null
+++ b/qt/applications/workbench/workbench/plotting/toolbar.py
@@ -0,0 +1,113 @@
+#    This file is part of the mantid workbench.
+#
+#    Copyright (C) 2017 mantidproject
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+# system imports
+
+# third-party library imports
+from matplotlib.backends.backend_qt5 import NavigationToolbar2QT
+import qtawesome as qta
+from qtpy import QtCore, QtGui, QtPrintSupport, QtWidgets
+
+# local package imports
+
+
+class WorkbenchNavigationToolbar(NavigationToolbar2QT):
+
+    sig_grid_toggle_triggered = QtCore.Signal()
+    sig_active_triggered = QtCore.Signal()
+    sig_hold_triggered = QtCore.Signal()
+
+    toolitems = (
+        ('Home', 'Reset original view', 'fa.home', 'home', None),
+        ('Pan', 'Pan axes with left mouse, zoom with right', 'fa.arrows-alt', 'pan', False),
+        ('Zoom', 'Zoom to rectangle', 'fa.search-plus', 'zoom', False),
+        (None, None, None, None, None),
+        ('Grid', 'Toggle grid on/off', None, 'toggle_grid', False),
+        (None, None, None, None, None),
+        ('Active', 'When enabled future plots will overwrite this figure', None, 'active', True),
+        ('Hold', 'When enabled this holds this figure open ', None, 'hold', False),
+        (None, None, None, None, None),
+        ('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)
+    )
+
+    def _init_toolbar(self):
+        for text, tooltip_text, fa_icon, callback, checked in self.toolitems:
+            if text is None:
+                self.addSeparator()
+            else:
+                if fa_icon:
+                    a = self.addAction(qta.icon(fa_icon),
+                                       text, getattr(self, callback))
+                else:
+                    a = self.addAction(text, getattr(self, callback))
+                self._actions[callback] = a
+                if checked is not None:
+                    a.setCheckable(True)
+                    a.setChecked(checked)
+                if tooltip_text is not None:
+                    a.setToolTip(tooltip_text)
+
+        self.buttons = {}
+        # Add the x,y location widget at the right side of the toolbar
+        # The stretch factor is 1 which means any resizing of the toolbar
+        # will resize this label instead of the buttons.
+        if self.coordinates:
+            self.locLabel = QtWidgets.QLabel("", self)
+            self.locLabel.setAlignment(
+                    QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
+            self.locLabel.setSizePolicy(
+                QtWidgets.QSizePolicy(QtWidgets.Expanding,
+                                      QtWidgets.QSizePolicy.Ignored))
+            labelAction = self.addWidget(self.locLabel)
+            labelAction.setVisible(True)
+
+        # reference holder for subplots_adjust window
+        self.adj_window = None
+
+        # Adjust icon size or they are too small in PyQt5 by default
+        self.setIconSize(QtCore.QSize(24, 24))
+
+    def toggle_grid(self):
+        self.sig_grid_toggle_triggered.emit()
+
+    def hold(self, *args):
+        self._actions['hold'].setChecked(True)
+        self._actions['active'].setChecked(False)
+        self.sig_hold_triggered.emit()
+
+    def active(self, *args):
+        self._actions['active'].setChecked(True)
+        self._actions['hold'].setChecked(False)
+        self.sig_active_triggered.emit()
+
+    def print_figure(self):
+        printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
+        printer.setOrientation(QtPrintSupport.QPrinter.Landscape)
+        print_dlg = QtPrintSupport.QPrintDialog(printer)
+        if print_dlg.exec_() == QtWidgets.QDialog.Accepted:
+            painter = QtGui.QPainter(printer)
+            page_size = printer.pageRect()
+            pixmap = self.canvas.grab().scaled(page_size.width(), page_size.height(),
+                                               QtCore.Qt.KeepAspectRatio)
+            painter.drawPixmap(0, 0, pixmap)
+            painter.end()
diff --git a/qt/applications/workbench/workbench/plugins/base.py b/qt/applications/workbench/workbench/plugins/base.py
index eec83fde3f53bc641bc53ce5cd15bb21db383d33..659f91debc4fa9a3dfb1a8ff5dd8870c8af935b8 100644
--- a/qt/applications/workbench/workbench/plugins/base.py
+++ b/qt/applications/workbench/workbench/plugins/base.py
@@ -37,6 +37,9 @@ class PluginWidget(QWidget):
 
 # ----------------- Plugin API --------------------
 
+    def app_closing(self):
+        raise NotImplementedError()
+
     def get_plugin_title(self):
         raise NotImplementedError()
 
diff --git a/qt/applications/workbench/workbench/plugins/editor.py b/qt/applications/workbench/workbench/plugins/editor.py
index c8ec86820d508eb7fd0f9d84c8f7ce2934d16c91..2ee7be1c748f5f002e2bd2d292374ad340ebac6e 100644
--- a/qt/applications/workbench/workbench/plugins/editor.py
+++ b/qt/applications/workbench/workbench/plugins/editor.py
@@ -33,8 +33,12 @@ DEFAULT_CONTENT = """# The following line helps with future compatibility with P
 # print must now be used as a function, e.g print('Hello','World')
 from __future__ import (absolute_import, division, print_function, unicode_literals)
 
-# Import all mantid algorithms
+# import mantid algorithms, numpy and matplotlib
 from mantid.simpleapi import *
+
+import matplotlib.pyplot as plt
+
+import numpy as np
 """
 
 
@@ -64,6 +68,9 @@ class MultiFileEditor(PluginWidget):
 
     # ----------- Plugin API --------------------
 
+    def app_closing(self):
+        self.editors.close_all()
+
     def get_plugin_title(self):
         return "Editor"
 
diff --git a/qt/applications/workbench/workbench/plugins/jupyterconsole.py b/qt/applications/workbench/workbench/plugins/jupyterconsole.py
index ed3ad1b1ae5bb2493d0233c606e9cff23a385fcd..7226e26b17b93cafc30d340537a9202701c92eab 100644
--- a/qt/applications/workbench/workbench/plugins/jupyterconsole.py
+++ b/qt/applications/workbench/workbench/plugins/jupyterconsole.py
@@ -17,14 +17,36 @@
 from __future__ import (absolute_import, unicode_literals)
 
 # system imports
+import sys
 
 # third-party library imports
 from mantidqt.widgets.jupyterconsole import InProcessJupyterConsole
+from IPython.core.usage import quick_guide, release as ipy_release
+from matplotlib import __version__ as mpl_version
+from numpy.version import version as np_version
 from qtpy.QtWidgets import QVBoxLayout
 
 # local package imports
 from workbench.plugins.base import PluginWidget
 
+DEFAULT_BANNER_PARTS = [
+    'IPython {version} -- An enhanced Interactive Python.\n'.format(
+        version=ipy_release.version,
+        ),
+    quick_guide,
+    '\nPython {}, numpy {}, matplotlib {}\n'.format(sys.version.split('\n')[0].strip(), np_version, mpl_version),
+    'Type "copyright", "credits" or "license" for more information.\n',
+]
+
+BANNER = ''.join(DEFAULT_BANNER_PARTS)
+
+# should we share this with plugins.editor?
+STARTUP_CODE = """from __future__ import (absolute_import, division, print_function, unicode_literals)
+from mantid.simpleapi import *
+import matplotlib.pyplot as plt
+import numpy as np
+"""
+
 
 class JupyterConsole(PluginWidget):
     """Provides an in-process Jupyter Qt-based console"""
@@ -33,7 +55,8 @@ class JupyterConsole(PluginWidget):
         super(JupyterConsole, self).__init__(parent)
 
         # layout
-        self.console = InProcessJupyterConsole(self)
+        self.console = InProcessJupyterConsole(self, banner=BANNER,
+                                               startup_code=STARTUP_CODE)
         layout = QVBoxLayout()
         layout.addWidget(self.console)
         self.setLayout(layout)
diff --git a/qt/applications/workbench/workbench/plugins/logmessagedisplay.py b/qt/applications/workbench/workbench/plugins/logmessagedisplay.py
index 3a55792c4099529b150a75d7f2f943869292fd04..67c539af9e634df4b374a45bfcea1b31b4ab1188 100644
--- a/qt/applications/workbench/workbench/plugins/logmessagedisplay.py
+++ b/qt/applications/workbench/workbench/plugins/logmessagedisplay.py
@@ -27,6 +27,9 @@ from qtpy.QtWidgets import QHBoxLayout
 # local imports
 from workbench.plugins.base import PluginWidget
 
+# Default logs at notice
+DEFAULT_LOG_PRIORITY = 5
+
 
 class LogMessageDisplay(PluginWidget):
 
@@ -53,7 +56,7 @@ class LogMessageDisplay(PluginWidget):
         self.display.readSettings(qsettings)
 
     def register_plugin(self, menu=None):
-        self.display.attachLoggingChannel()
+        self.display.attachLoggingChannel(DEFAULT_LOG_PRIORITY)
         self._capture_stdout_and_stderr()
         self.main.add_dockwidget(self)
 
diff --git a/qt/applications/workbench/workbench/plugins/workspacewidget.py b/qt/applications/workbench/workbench/plugins/workspacewidget.py
index 8d67163c68209e753080509f9581cc8fd6ef901e..fc77a3bfe493c8021ba03cd55e34e2476f0fdc48 100644
--- a/qt/applications/workbench/workbench/plugins/workspacewidget.py
+++ b/qt/applications/workbench/workbench/plugins/workspacewidget.py
@@ -17,14 +17,52 @@
 from __future__ import (absolute_import, unicode_literals)
 
 # system imports
+import functools
 
 # third-party library imports
-from mantidqt.widgets.workspacewidget.workspacetreewidget import WorkspaceTreeWidget
+from mantid.api import AnalysisDataService, MatrixWorkspace, WorkspaceGroup
+from mantid.kernel import Logger
 from mantidqt.widgets.workspacewidget.mantidtreemodel import MantidTreeModel
+from mantidqt.widgets.workspacewidget.plotselectiondialog import get_plot_selection
+from mantidqt.widgets.workspacewidget.workspacetreewidget import WorkspaceTreeWidget
 from qtpy.QtWidgets import QVBoxLayout
 
 # local package imports
 from workbench.plugins.base import PluginWidget
+from workbench.plotting.functions import pcolormesh, plot
+
+
+LOGGER = Logger(b"workspacewidget")
+
+
+def _workspaces_from_names(names):
+    """
+    Convert a list of workspace names to a list of MatrixWorkspace handles. Any WorkspaceGroup
+    encountered is flattened and its members inserted into the list at this point
+
+    Flattens any workspace groups with the list, preserving the order of the remaining elements
+    :param names: A list of workspace names
+    :return: A list where each element is a single MatrixWorkspace
+    """
+    ads = AnalysisDataService.Instance()
+    flat = []
+    for name in names:
+        try:
+            ws = ads[name.encode('utf-8')]
+        except KeyError:
+            LOGGER.warning("Skipping {} as it does not exist".format(name))
+            continue
+
+        if isinstance(ws, MatrixWorkspace):
+            flat.append(ws)
+        elif isinstance(ws, WorkspaceGroup):
+            group_len = len(ws)
+            for i in range(group_len):
+                flat.append(ws[i])
+        else:
+            LOGGER.warning("{} it is not a MatrixWorkspace or WorkspaceGroup.".format(name))
+
+    return flat
 
 
 class WorkspaceWidget(PluginWidget):
@@ -34,12 +72,20 @@ class WorkspaceWidget(PluginWidget):
         super(WorkspaceWidget, self).__init__(parent)
 
         # layout
-        self.workspacewidget = WorkspaceTreeWidget(MantidTreeModel())
+        workspacewidget = WorkspaceTreeWidget(MantidTreeModel())
+        self.workspacewidget = workspacewidget
         layout = QVBoxLayout()
         layout.addWidget(self.workspacewidget)
         self.setLayout(layout)
 
-# ----------------- Plugin API --------------------
+        # behaviour
+        workspacewidget.plotSpectrumClicked.connect(functools.partial(self._do_plot_spectrum,
+                                                                      errors=False))
+        workspacewidget.plotSpectrumWithErrorsClicked.connect(functools.partial(self._do_plot_spectrum,
+                                                                                errors=True))
+        workspacewidget.plotColorfillClicked.connect(self._do_plot_colorfill)
+
+    # ----------------- Plugin API --------------------
 
     def register_plugin(self):
         self.main.add_dockwidget(self)
@@ -49,3 +95,34 @@ class WorkspaceWidget(PluginWidget):
 
     def read_user_settings(self, _):
         pass
+
+    # ----------------- Behaviour --------------------
+
+    def _do_plot_spectrum(self, names, errors):
+        """
+        Plot spectra from the selected workspaces
+
+        :param names: A list of workspace names
+        :param errors: If true then error bars will be plotted on the points
+        """
+        try:
+            selection = get_plot_selection(_workspaces_from_names(names), self)
+            if selection is not None:
+                plot(selection.workspaces, spectrum_nums=selection.spectra,
+                     wksp_indices=selection.wksp_indices,
+                     errors=errors)
+        except BaseException:
+            import traceback
+            traceback.print_exc()
+
+    def _do_plot_colorfill(self, names):
+        """
+        Plot a colorfill from the selected workspaces
+
+        :param names: A list of workspace names
+        """
+        try:
+            pcolormesh(_workspaces_from_names(names))
+        except BaseException:
+            import traceback
+            traceback.print_exc()
diff --git a/qt/python/CMakeLists.txt b/qt/python/CMakeLists.txt
index 2fe61a4cdcd7bb8057b9b3079a6ca1d74cb7ae67..7a20ede4fbaeeb17c719afdd4174e9cdb42b2b73 100644
--- a/qt/python/CMakeLists.txt
+++ b/qt/python/CMakeLists.txt
@@ -79,6 +79,8 @@ endif ()
     mantidqt/widgets/codeeditor/test/test_interpreter.py
     mantidqt/widgets/codeeditor/test/test_multifileinterpreter.py
 
+    mantidqt/widgets/workspacewidget/test/test_plotselectiondialog.py
+
     mantidqt/widgets/test/test_algorithmselector.py
     mantidqt/widgets/test/test_jupyterconsole.py
     mantidqt/widgets/test/test_messagedisplay.py
diff --git a/qt/python/mantidqt/widgets/codeeditor/execution.py b/qt/python/mantidqt/widgets/codeeditor/execution.py
index 7f5f78bc7abf2325fbd604d3de17e31fc921ba9a..990c348c2f44ebd0b7f1c2792897473c234f597e 100644
--- a/qt/python/mantidqt/widgets/codeeditor/execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/execution.py
@@ -107,7 +107,7 @@ class PythonCodeExecution(QObject):
     sig_exec_error = Signal(object)
     sig_exec_progress = Signal(int)
 
-    def __init__(self):
+    def __init__(self, startup_code=None):
         """Initialize the object"""
         super(PythonCodeExecution, self).__init__()
 
@@ -116,6 +116,9 @@ class PythonCodeExecution(QObject):
 
         self.reset_context()
 
+        if startup_code:
+            self.execute(startup_code)
+
     @property
     def globals_ns(self):
         return self._globals_ns
diff --git a/qt/python/mantidqt/widgets/codeeditor/interpreter.py b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
index 40aafa85b2a618c305740dbf096f9475c9897013..2557197836613ce9f8dd48751891fb96782c54fe 100644
--- a/qt/python/mantidqt/widgets/codeeditor/interpreter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
@@ -115,7 +115,7 @@ class PythonFileInterpreter(QWidget):
         self._setup_editor(content, filename)
 
         self._presenter = PythonFileInterpreterPresenter(self,
-                                                         PythonCodeExecution())
+                                                         PythonCodeExecution(content))
 
         self.editor.modificationChanged.connect(self.sig_editor_modified)
         self.editor.fileNameChanged.connect(self.sig_filename_modified)
@@ -172,9 +172,8 @@ class PythonFileInterpreter(QWidget):
             editor.setText(default_content)
         if filename is not None:
             editor.setFileName(filename)
-            editor.setModified(False)
-        else:
-            editor.setModified(True)
+        # Default content does not count as a modification
+        editor.setModified(False)
 
         editor.enableAutoCompletion(CodeEditor.AcsAll)
 
@@ -192,6 +191,9 @@ class PythonFileInterpreterPresenter(QObject):
         self._is_executing = False
         self._error_formatter = ErrorFormatter()
 
+        # If startup code was executed then populate autocomplete
+        self.view.editor.updateCompletionAPI(self.model.generate_calltips())
+
         # connect signals
         self.model.sig_exec_success.connect(self._on_exec_success)
         self.model.sig_exec_error.connect(self._on_exec_error)
diff --git a/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py b/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py
index 16554230bd6c53503b8b24f14e44d2f15a9ff849..81f530f5e21f6026c709f4ed57a2a4d0ce9dacbb 100644
--- a/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py
@@ -72,7 +72,6 @@ class MultiPythonFileInterpreter(QWidget):
 
         tab_title, tab_toolip = _tab_title_and_toolip(filename)
         tab_idx = self._tabs.addTab(interpreter, tab_title)
-        self.mark_tab_modified(tab_idx, (filename is None))
         self._tabs.setTabToolTip(tab_idx, tab_toolip)
         self._tabs.setCurrentIndex(tab_idx)
         return tab_idx
@@ -81,6 +80,11 @@ class MultiPythonFileInterpreter(QWidget):
         """Request that that the current execution be cancelled"""
         self.current_editor().abort()
 
+    def close_all(self):
+        """Close all tabs"""
+        for idx in reversed(range(self.editor_count)):
+            self.close_tab(idx)
+
     def close_tab(self, idx):
         """Close the tab at the given index."""
         if idx >= self.editor_count:
diff --git a/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py b/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
index f8effdbb8700ffa4e30bcdd4873ebb45941ddb02..4c4c5f5960f278111dc6afa4f4f408b99b3d4db3 100644
--- a/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
@@ -65,6 +65,10 @@ class PythonCodeExecutionTest(unittest.TestCase):
         executor.reset_context()
         self.assertEqual(0, len(executor.globals_ns))
 
+    def test_startup_code_executed_by_default(self):
+        executor = PythonCodeExecution(startup_code="x=100")
+        self.assertEqual(100, executor.globals_ns['x'])
+
     # ---------------------------------------------------------------------------
     # Successful execution tests
     # ---------------------------------------------------------------------------
diff --git a/qt/python/mantidqt/widgets/codeeditor/test/test_interpreter.py b/qt/python/mantidqt/widgets/codeeditor/test/test_interpreter.py
index 81247c1afc4723b253f60e26547e7a2339221db5..18cfbb0d85db0bec0e740485ff0572b9750ba0c6 100644
--- a/qt/python/mantidqt/widgets/codeeditor/test/test_interpreter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/test/test_interpreter.py
@@ -47,8 +47,8 @@ class PythonFileInterpreterTest(unittest.TestCase):
         self.assertTrue("Status: Idle", w.status.currentMessage())
 
     def test_constructor_populates_editor_with_content(self):
-        w = PythonFileInterpreter(content='my funky code')
-        self.assertEqual('my funky code', w.editor.text())
+        w = PythonFileInterpreter(content='# my funky code')
+        self.assertEqual('# my funky code', w.editor.text())
 
     def test_constructor_respects_filename(self):
         w = PythonFileInterpreter(filename='test.py')
diff --git a/qt/python/mantidqt/widgets/jupyterconsole.py b/qt/python/mantidqt/widgets/jupyterconsole.py
index 8e28d6b3587fde0665f711b7140a272068a56044..f3cd0ae13a434dfdde673024f3da6ab57f1c2d69 100644
--- a/qt/python/mantidqt/widgets/jupyterconsole.py
+++ b/qt/python/mantidqt/widgets/jupyterconsole.py
@@ -42,10 +42,32 @@ class InProcessJupyterConsole(RichJupyterWidget):
         """
         A constructor matching that of RichJupyterWidget
         :param args: Positional arguments passed directly to RichJupyterWidget
-        :param kwargs: Keyword arguments passed directly to RichJupyterWidget
+        :param kwargs: Keyword arguments. The following keywords are understood by this widget:
+
+          - banner: Replace the default banner with this text
+          - startup_code: A code snippet to run on startup. It is also added to the banner to inform the user.
+
+        the rest are passed to RichJupyterWidget
         """
+        banner = kwargs.pop("banner", "")
+        startup_code = kwargs.pop("startup_code", "")
         super(InProcessJupyterConsole, self).__init__(*args, **kwargs)
 
+        # adjust startup banner accordingly
+        # newer ipython has banner1 & banner2 and not just banner
+        two_ptr_banner = hasattr(self, 'banner1')
+        if not banner:
+            banner = self.banner1 if two_ptr_banner else self.banner
+        if startup_code:
+            banner += "\n" + \
+                "The following code has been executed at startup:\n\n" + \
+                startup_code
+        if two_ptr_banner:
+            self.banner1 = banner
+            self.banner2 = ''
+        else:
+            self.banner = banner
+
         # create an in-process kernel
         kernel_manager = QtInProcessKernelManager()
         kernel_manager.start_kernel()
@@ -56,9 +78,11 @@ class InProcessJupyterConsole(RichJupyterWidget):
         shell = kernel.shell
         shell.run_code = async_wrapper(shell.run_code, shell)
 
-        # attach channels and start kenel
+        # attach channels, start kenel and run any startup code
         kernel_client = kernel_manager.client()
         kernel_client.start_channels()
+        if startup_code:
+            shell.ex(startup_code)
 
         self.kernel_manager = kernel_manager
         self.kernel_client = kernel_client
diff --git a/qt/python/mantidqt/widgets/src/_widgetscore.sip b/qt/python/mantidqt/widgets/src/_widgetscore.sip
index e42ebb795d8c515fbf2c66188c9d5be6438e8dc2..bc5b92838e6db8ea32ae69443114b0c06f2e3f17 100644
--- a/qt/python/mantidqt/widgets/src/_widgetscore.sip
+++ b/qt/python/mantidqt/widgets/src/_widgetscore.sip
@@ -52,7 +52,7 @@ class MessageDisplay : QWidget, Configurable {
 
 public:
   MessageDisplay(QWidget *parent = 0);
-  void attachLoggingChannel();
+  void attachLoggingChannel(int logLevel = -1);
 
   void appendFatal(const QString &text);
   void appendError(const QString &text);
@@ -216,6 +216,11 @@ class WorkspaceTreeWidgetSimple : WorkspaceTreeWidget {
 public:
   WorkspaceTreeWidgetSimple(MantidDisplayBase *mdb /Transfer/,
                             QWidget *parent /TransferThis/ = nullptr);
+
+signals:
+  void plotSpectrumClicked(const QStringList & workspaceName);
+  void plotSpectrumWithErrorsClicked(const QStringList & workspaceName);
+  void plotColorfillClicked(const QStringList &workspaceName);
 };
 
 // ---------------------------------
@@ -228,4 +233,4 @@ class ManageUserDirectories : QDialog {
 %End
 public:
   ManageUserDirectories(QWidget *parent /TransferThis/ = nullptr);
-};
\ No newline at end of file
+};
diff --git a/qt/python/mantidqt/widgets/test/test_jupyterconsole.py b/qt/python/mantidqt/widgets/test/test_jupyterconsole.py
index 2d1d9f47e43651666ddd462c71af0f770074bd79..411e9f4e2e2f2b5887ac61eff3d55488d627458c 100644
--- a/qt/python/mantidqt/widgets/test/test_jupyterconsole.py
+++ b/qt/python/mantidqt/widgets/test/test_jupyterconsole.py
@@ -35,6 +35,15 @@ class InProcessJupyterConsoleTest(unittest.TestCase):
         self.assertTrue(hasattr(widget, "kernel_client"))
         self.assertTrue(len(widget.banner) > 0)
 
+    def test_construction_with_banner_replaces_default(self):
+        widget = InProcessJupyterConsole(banner="Hello!")
+        self.assertEquals("Hello!", widget.banner)
+
+    def test_construction_with_startup_code_adds_to_banner_and_executes(self):
+        widget = InProcessJupyterConsole(startup_code="x = 1")
+        self.assertTrue("x = 1" in widget.banner)
+        self.assertEquals(1, widget.kernel_manager.kernel.shell.user_ns['x'])
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/qt/python/mantidqt/widgets/workspacewidget/plotselectiondialog.py b/qt/python/mantidqt/widgets/workspacewidget/plotselectiondialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..7fe74f05edc7b7be90db5decb2304be81b3afa61
--- /dev/null
+++ b/qt/python/mantidqt/widgets/workspacewidget/plotselectiondialog.py
@@ -0,0 +1,243 @@
+#  This file is part of the mantidqt package
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import (absolute_import, unicode_literals)
+
+# std imports
+
+# 3rd party imports
+import qtawesome as qta
+from qtpy.QtWidgets import QDialogButtonBox
+
+# local imports
+from mantidqt.utils.qt import load_ui
+
+# Constants
+RANGE_SPECIFIER = '-'
+PLACEHOLDER_FORMAT = 'valid range: {}' + RANGE_SPECIFIER + '{}'
+RED_ASTERISK = None
+
+
+def red_asterisk():
+    global RED_ASTERISK
+    if RED_ASTERISK is None:
+        RED_ASTERISK = qta.icon('fa.asterisk', color='red', scale_factor=0.6)
+    return RED_ASTERISK
+
+
+PlotSelectionDialogUI, PlotSelectionDialogUIBase = load_ui(__file__, 'plotselectiondialog.ui')
+
+
+class PlotSelection(object):
+
+    Individual = 0
+
+    def __init__(self, workspaces):
+        self.workspaces = workspaces
+        self.wksp_indices = None
+        self.spectra = None
+        self.plot_type = PlotSelection.Individual
+
+
+class PlotSelectionDialog(PlotSelectionDialogUIBase):
+
+    def __init__(self, workspaces,
+                 parent=None):
+        super(PlotSelectionDialog, self).__init__(parent)
+        # attributes
+        self._workspaces = workspaces
+        self.spec_min, self.spec_max = None, None
+        self.wi_min, self.wi_max = None, None
+        self.selection = None
+        self._ui = None
+
+        self._init_ui()
+        self._set_placeholder_text()
+        self._setup_connections()
+
+    def on_ok_clicked(self):
+        self.accept()
+
+    def on_plot_all_clicked(self):
+        selection = PlotSelection(self._workspaces)
+        selection.wksp_indices = range(self.wi_min, self.wi_max + 1)
+        self.selection = selection
+        self.accept()
+
+    # ------------------- Private -------------------------
+    def _init_ui(self):
+        ui = PlotSelectionDialogUI()
+        ui.setupUi(self)
+        self._ui = ui
+        # overwrite the "Yes to All" button text
+        ui.buttonBox.button(QDialogButtonBox.YesToAll).setText('Plot All')
+        # ok disabled by default
+        ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
+
+        # validity markers
+        ui.wkspIndicesValid.setIcon(red_asterisk())
+        ui.specNumsValid.setIcon(red_asterisk())
+
+    def _set_placeholder_text(self):
+        """Sets placeholder text to indicate the ranges possible"""
+        workspaces = self._workspaces
+        # workspace index range
+        wi_max = min([ws.getNumberHistograms() - 1 for ws in workspaces])
+        self._ui.wkspIndices.setPlaceholderText(PLACEHOLDER_FORMAT.format(0, wi_max))
+        self.wi_min, self.wi_max = 0, wi_max
+
+        # spectra range
+        ws_spectra = [{ws.getSpectrum(i).getSpectrumNo() for i in range(ws.getNumberHistograms())} for ws in workspaces]
+        plottable = ws_spectra[0]
+        if len(ws_spectra) > 1:
+            for sp_set in ws_spectra[1:]:
+                plottable = plottable.intersection(sp_set)
+        plottable = sorted(plottable)
+        spec_min, spec_max = min(plottable), max(plottable)
+        self._ui.specNums.setPlaceholderText(PLACEHOLDER_FORMAT.format(spec_min, spec_max))
+        self.spec_min, self.spec_max = spec_min, spec_max
+
+    def _setup_connections(self):
+        ui = self._ui
+        # button actions
+        ui.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.on_ok_clicked)
+        ui.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(self.reject)
+        ui.buttonBox.button(QDialogButtonBox.YesToAll).clicked.connect(self.on_plot_all_clicked)
+
+        # line edits are mutually exclusive
+        ui.wkspIndices.textChanged.connect(self._on_wkspindices_changed)
+        ui.specNums.textChanged.connect(self._on_specnums_changed)
+
+    def _on_wkspindices_changed(self):
+        ui = self._ui
+        ui.specNums.clear()
+        ui.specNumsValid.hide()
+
+        self._parse_wksp_indices()
+        ui.wkspIndicesValid.setVisible(not self._is_input_valid())
+        ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(self._is_input_valid())
+
+    def _on_specnums_changed(self):
+        ui = self._ui
+        ui.wkspIndices.clear()
+        ui.wkspIndicesValid.hide()
+
+        self._parse_spec_nums()
+        ui.specNumsValid.setVisible(not self._is_input_valid())
+        ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(self._is_input_valid())
+
+    def _parse_wksp_indices(self):
+        wksp_indices = parse_selection_str(self._ui.wkspIndices.text(), self.wi_min, self.wi_max)
+        if wksp_indices:
+            selection = PlotSelection(self._workspaces)
+            selection.wksp_indices = wksp_indices
+        else:
+            selection = None
+        self.selection = selection
+
+    def _parse_spec_nums(self):
+        spec_nums = parse_selection_str(self._ui.specNums.text(), self.spec_min, self.spec_max)
+        if spec_nums:
+            selection = PlotSelection(self._workspaces)
+            selection.spectra = spec_nums
+        else:
+            selection = None
+        self.selection = selection
+
+    def _is_input_valid(self):
+        return self.selection is not None
+
+
+def get_plot_selection(workspaces, parent_widget):
+    """Decides whether it is necessary to request user input
+    when asked to plot a list of workspaces. The input
+    dialog will only be shown in the case where all workspaces
+    have more than 1 spectrum
+
+    :param workspaces: A list of MatrixWorkspaces that will be plotted
+    :param parent_widget: A parent_widget to use for the input selection dialog
+    :returns: Either a PlotSelection object containing the details of workspaces to plot or None indicating
+    the request was cancelled
+    """
+    single_spectra_ws = [wksp.getNumberHistograms() for wksp in workspaces if wksp.getNumberHistograms() == 1]
+    if len(single_spectra_ws) > 0:
+        # At least 1 workspace contains only a single spectrum so this is all
+        # that is possible to plot for all of them
+        selection = PlotSelection(workspaces)
+        selection.wksp_indices = [0]
+        return selection
+    else:
+        selection_dlg = PlotSelectionDialog(workspaces, parent=parent_widget)
+        res = selection_dlg.exec_()
+        if res == PlotSelectionDialog.Rejected:
+            # cancelled
+            return None
+        else:
+            user_selection = selection_dlg.selection
+            # the dialog should guarantee that only 1 of spectrum/indices is supplied
+            assert user_selection.spectra is None or user_selection.wksp_indices is None
+            return user_selection
+
+
+def parse_selection_str(txt, min_val, max_val):
+    """Parse an input string containing plot index selection.
+
+    :param txt: A single line of text containing a comma-separated list of values or range of values, i.e.
+    3-4,5,6,8,10-11
+    :param min_val: The minimum allowed value
+    :param max_val: The maximum allowed value
+    :returns A list containing each value in the range or None if the string is invalid
+    """
+    def append_if_valid(out, val):
+        try:
+            val = int(val)
+            if is_in_range(val):
+                out.add(val)
+            else:
+                return False
+        except ValueError:
+            return False
+        return True
+
+    def is_in_range(val):
+        return min_val <= val <= max_val
+
+    # split up any commas
+    comma_separated = txt.split(',')
+    # find and expand ranges
+    parsed_numbers = set()
+    valid = True
+    for cs_item in comma_separated:
+        post_split = cs_item.split('-')
+        if len(post_split) == 1:
+            valid = append_if_valid(parsed_numbers, post_split[0])
+        elif len(post_split) == 2:
+            # parse as range
+            try:
+                beg, end = int(post_split[0]), int(post_split[1])
+            except ValueError:
+                valid = False
+            else:
+                if is_in_range(beg) and is_in_range(end):
+                    parsed_numbers = parsed_numbers.union(set(range(beg, end+1)))
+                else:
+                    valid = False
+        else:
+            valid = False
+        if not valid:
+            break
+
+    return list(parsed_numbers) if valid > 0 else None
diff --git a/qt/python/mantidqt/widgets/workspacewidget/plotselectiondialog.ui b/qt/python/mantidqt/widgets/workspacewidget/plotselectiondialog.ui
new file mode 100644
index 0000000000000000000000000000000000000000..5a90e5cc629fa7d5001b246efe79fb864f8c669d
--- /dev/null
+++ b/qt/python/mantidqt/widgets/workspacewidget/plotselectiondialog.ui
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>481</width>
+    <height>186</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Plot 1D</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="4" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Plot type:</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="1">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::YesToAll</set>
+     </property>
+     <property name="centerButtons">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLineEdit" name="specNums">
+     <property name="inputMask">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Workspace Indices:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QLineEdit" name="wkspIndices"/>
+   </item>
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Spectrum Numbers:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="1">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QComboBox" name="plotType">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <item>
+        <property name="text">
+         <string>Individual</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>Or</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <widget class="QPushButton" name="specNumsValid">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>25</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>16</width>
+       <height>16</height>
+      </size>
+     </property>
+     <property name="flat">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <widget class="QPushButton" name="wkspIndicesValid">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>25</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>16</width>
+       <height>16</height>
+      </size>
+     </property>
+     <property name="flat">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/qt/python/mantidqt/widgets/workspacewidget/test/__init__.py b/qt/python/mantidqt/widgets/workspacewidget/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/qt/python/mantidqt/widgets/workspacewidget/test/test_plotselectiondialog.py b/qt/python/mantidqt/widgets/workspacewidget/test/test_plotselectiondialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..36ed6f5bd39dca4ee0763001fc3711609ed8c3af
--- /dev/null
+++ b/qt/python/mantidqt/widgets/workspacewidget/test/test_plotselectiondialog.py
@@ -0,0 +1,115 @@
+#  This file is part of the mantidqt package
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# std imports
+import time
+import unittest
+
+# 3rdparty imports
+from mantid.simpleapi import CreateSampleWorkspace, CropWorkspace
+from qtpy.QtWidgets import QDialogButtonBox
+
+# local imports
+from mantidqt.utils.qt.testing import requires_qapp
+from mantidqt.widgets.workspacewidget.plotselectiondialog import parse_selection_str, PlotSelectionDialog
+
+
+@requires_qapp
+class PlotSelectionDialogTest(unittest.TestCase):
+
+    def test_initial_dialog_setup(self):
+        workspaces = [CreateSampleWorkspace(OutputWorkspace='ws', StoreInADS=False)]
+        dlg = PlotSelectionDialog(workspaces)
+        self.assertFalse(dlg._ui.buttonBox.button(QDialogButtonBox.Ok).isEnabled())
+
+    def test_filling_workspace_details_single_workspace(self):
+        workspaces = [CreateSampleWorkspace(OutputWorkspace='ws', StoreInADS=False)]
+        dlg = PlotSelectionDialog(workspaces)
+        self.assertEqual("valid range: 1-200", dlg._ui.specNums.placeholderText())
+        self.assertEqual("valid range: 0-199", dlg._ui.wkspIndices.placeholderText())
+
+    def test_filling_workspace_details_multiple_workspaces_of_same_size(self):
+        workspaces = [CreateSampleWorkspace(OutputWorkspace='ws', StoreInADS=False),
+                      CreateSampleWorkspace(OutputWorkspace='ws2', StoreInADS=False)]
+        dlg = PlotSelectionDialog(workspaces)
+        self.assertEqual("valid range: 1-200", dlg._ui.specNums.placeholderText())
+        self.assertEqual("valid range: 0-199", dlg._ui.wkspIndices.placeholderText())
+
+    def test_filling_workspace_details_multiple_workspaces_of_different_sizes(self):
+        ws1 = CreateSampleWorkspace(OutputWorkspace='ws', NumBanks=1, StoreInADS=False)
+        ws1 = CropWorkspace(ws1, StartWorkspaceIndex=50)
+        ws2 = CreateSampleWorkspace(OutputWorkspace='ws', StoreInADS=False)
+
+        dlg = PlotSelectionDialog([ws1, ws2])
+        self.assertEqual("valid range: 51-100", dlg._ui.specNums.placeholderText())
+        self.assertEqual("valid range: 0-49", dlg._ui.wkspIndices.placeholderText())
+
+    def test_valid_text_in_boxes_activates_ok(self):
+        workspaces = [CreateSampleWorkspace(OutputWorkspace='ws', StoreInADS=False)]
+        dlg = PlotSelectionDialog(workspaces)
+
+        def do_test(input_box):
+            input_box.setText("1")
+            self.assertTrue(dlg._ui.buttonBox.button(QDialogButtonBox.Ok).isEnabled())
+            input_box.clear()
+            self.assertFalse(dlg._ui.buttonBox.button(QDialogButtonBox.Ok).isEnabled())
+
+        do_test(dlg._ui.wkspIndices)
+        do_test(dlg._ui.specNums)
+
+    def test_plot_all_gives_only_workspaces_indices(self):
+        ws = CreateSampleWorkspace(OutputWorkspace='ws', StoreInADS=False)
+        dlg = PlotSelectionDialog([ws])
+        dlg._ui.buttonBox.button(QDialogButtonBox.YesToAll).click()
+        self.assertTrue(dlg.selection is not None)
+        self.assertTrue(dlg.selection.spectra is None)
+        self.assertEqual(range(200), dlg.selection.wksp_indices)
+
+    def test_parse_selection_str_single_number(self):
+        s = '1'
+        self.assertEqual([1], parse_selection_str(s, 1, 3))
+        s = '2'
+        self.assertEqual([2], parse_selection_str(s, 1, 3))
+        s = '3'
+        self.assertEqual([3], parse_selection_str(s, 1, 3))
+        s = '-1'
+        self.assertTrue(parse_selection_str(s, 1, 1) is None)
+        s = '1'
+        self.assertTrue(parse_selection_str(s, 2, 2) is None)
+        s = '1'
+        self.assertTrue(parse_selection_str(s, 2, 3) is None)
+
+    def test_parse_selection_str_single_range(self):
+        s = '1-3'
+        self.assertEqual([1, 2, 3], parse_selection_str(s, 1, 3))
+        s = '2-4'
+        self.assertEqual([2, 3, 4], parse_selection_str(s, 1, 5))
+        s = '2-4'
+        self.assertTrue(parse_selection_str(s, 2, 3) is None)
+        s = '2-4'
+        self.assertTrue(parse_selection_str(s, 3, 5) is None)
+
+    def test_parse_selection_str_mix_number_range_spaces(self):
+        s = '1-3, 5,8,10, 11 ,12-14 , 15 -16, 16- 19'
+        self.assertEqual([1, 2, 3, 5, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
+                         parse_selection_str(s, 1, 20))
+
+
+if __name__ == '__main__':
+    unittest.main()
+    # investigate why this is needed to avoid a segfault on Linux
+    time.sleep(0.5)
diff --git a/qt/python/mantidqt/widgets/workspacewidget/workspacetreewidget.py b/qt/python/mantidqt/widgets/workspacewidget/workspacetreewidget.py
index 0a3ead39165bf654afd1983b6f876f868855c59a..0563a03872a06e1a0705fdac4e18bc55b03a69e3 100644
--- a/qt/python/mantidqt/widgets/workspacewidget/workspacetreewidget.py
+++ b/qt/python/mantidqt/widgets/workspacewidget/workspacetreewidget.py
@@ -14,9 +14,18 @@
 #
 #  You should have received a copy of the GNU General Public License
 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-from __future__ import (absolute_import)
+from __future__ import (absolute_import, unicode_literals)
 
+# std imports
+
+# 3rd party imports
+
+# local imports
 from mantidqt.utils.qt import import_qtlib
 
+# Constants
+PLACEHOLDER_FORMAT = 'valid range: {}-{}'
+
 
-WorkspaceTreeWidget = import_qtlib('_widgetscore', 'mantidqt.widgets.workspacewidget', 'WorkspaceTreeWidgetSimple')
+WorkspaceTreeWidget = import_qtlib('_widgetscore', 'mantidqt.widgets.workspacewidget',
+                                   'WorkspaceTreeWidgetSimple')
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/MessageDisplay.h b/qt/widgets/common/inc/MantidQtWidgets/Common/MessageDisplay.h
index c2ce9fdadb88491015bdeed13e09171a882a5afc..0f5db76bfe24bf242e4e4c14d5622f69a7e4ffe9 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/MessageDisplay.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/MessageDisplay.h
@@ -54,7 +54,7 @@ public:
   ~MessageDisplay() override;
 
   // Setup logging framework connections
-  void attachLoggingChannel();
+  void attachLoggingChannel(int logLevel = 0);
   /// If set, only Mantid log messages from this source are emitted
   void setSource(const QString &source);
   /// Get the current source are emitted
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidget.h b/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidget.h
index d177655c5f993cf7eba98c6fcb1fe230c2503d0e..4474389aea02859ad20c98022fb3ff76ca2f75a6 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidget.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidget.h
@@ -90,6 +90,8 @@ public:
 
   MantidQt::MantidWidgets::StringList
   getSelectedWorkspaceNames() const override;
+  // Horrible second function to get a return value as QStringList directly
+  QStringList getSelectedWorkspaceNamesAsQList() const;
   Mantid::API::Workspace_sptr getSelectedWorkspace() const override;
 
   bool askUserYesNo(const std::string &caption,
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h b/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h
index 3d2ccc8c54d530f94784d9ff6a4addbda43068dd..f76534fb0847b2925e24dc55a031794abb74abab 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h
@@ -58,12 +58,18 @@ public:
   // Context Menu Handlers
   void popupContextMenu() override;
 
+signals:
+  void plotSpectrumClicked(const QStringList &workspaceName);
+  void plotSpectrumWithErrorsClicked(const QStringList &workspaceName);
+  void plotColorfillClicked(const QStringList &workspaceName);
+
+private slots:
+  void onPlotSpectrumClicked();
+  void onPlotSpectrumWithErrorsClicked();
+  void onPlotColorfillClicked();
+
 private:
-  void addMatrixWorkspaceMenuItems(
-      QMenu *menu,
-      const Mantid::API::MatrixWorkspace_const_sptr &matrixWS) const;
-  void addWorkspaceGroupMenuItems(QMenu *menu) const;
-  void addTableWorkspaceMenuItems(QMenu *menu) const;
+  QAction *m_plotSpectrum, *m_plotSpectrumWithErrs, *m_plotColorfill;
 };
 }
 }
diff --git a/qt/widgets/common/src/MessageDisplay.cpp b/qt/widgets/common/src/MessageDisplay.cpp
index a461aaa2612965530693656ce9b05f1ee85f02f0..2f76a231f68c126d55fb5f98e94fa3749c2ac4d0 100644
--- a/qt/widgets/common/src/MessageDisplay.cpp
+++ b/qt/widgets/common/src/MessageDisplay.cpp
@@ -41,11 +41,11 @@ namespace MantidWidgets {
  * at the group containing the values
  */
 void MessageDisplay::readSettings(const QSettings &storage) {
-  ConfigService::Instance().setFilterChannelLogLevel(
-      m_filterChannelName,
-      storage.value("MessageDisplayPriority", Message::Priority::PRIO_NOTICE)
-          .toInt(),
-      true);
+  const int logLevel = storage.value("MessageDisplayPriority", 0).toInt();
+  if (logLevel > 0) {
+    ConfigService::Instance().setFilterChannelLogLevel(m_filterChannelName,
+                                                       logLevel, true);
+  }
 }
 
 /**
@@ -92,8 +92,10 @@ MessageDisplay::~MessageDisplay() {
 /**
  * Attaches the Mantid logging framework. Starts the ConfigService if
  * required
+ * @param logLevel If > 0 then set the filter channel level to this. A
+ * number =< 0 uses the channel
  */
-void MessageDisplay::attachLoggingChannel() {
+void MessageDisplay::attachLoggingChannel(int logLevel) {
   // Setup logging. ConfigService needs to be started
   auto &configSvc = ConfigService::Instance();
   auto &rootLogger = Poco::Logger::root();
@@ -109,7 +111,9 @@ void MessageDisplay::attachLoggingChannel() {
   configSvc.registerLoggingFilterChannel(m_filterChannelName, m_filterChannel);
   connect(m_logChannel, SIGNAL(messageReceived(const Message &)), this,
           SLOT(append(const Message &)));
-
+  if (logLevel > 0) {
+    configSvc.setFilterChannelLogLevel(m_filterChannelName, logLevel, true);
+  }
   ++ATTACH_COUNT;
 }
 
diff --git a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidget.cpp b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidget.cpp
index 362aebf7c684439e494efa731b4c746213fa4b16..b491ee8266ef555bfac3935c6b429eb2d5ffb41f 100644
--- a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidget.cpp
+++ b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidget.cpp
@@ -193,6 +193,16 @@ StringList WorkspaceTreeWidget::getSelectedWorkspaceNames() const {
   return names;
 }
 
+QStringList WorkspaceTreeWidget::getSelectedWorkspaceNamesAsQList() const {
+  auto items = m_tree->selectedItems();
+  QStringList names;
+
+  for (auto &item : items) {
+    names.append(item->text(0));
+  }
+  return names;
+}
+
 /** Returns a pointer to the selected workspace (the first if multiple
 *   workspaces selected)
 */
@@ -624,7 +634,7 @@ void WorkspaceTreeWidget::createWorkspaceMenuActions() {
   m_showHist = new QAction(tr("Show History"), this);
   connect(m_showHist, SIGNAL(triggered()), this, SLOT(onClickShowAlgHistory()));
 
-  m_saveNexus = new QAction(tr("Save Nexus"), this);
+  m_saveNexus = new QAction(tr("Save NeXus"), this);
   connect(m_saveNexus, SIGNAL(triggered()), this,
           SLOT(onClickSaveNexusWorkspace()));
 
diff --git a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp
index 13f1f8cb0ce5c8bb73bbb7a67fd85a686723371c..f2cf6ba371eff3e1203c185a6784cf6da914fa0d 100644
--- a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp
+++ b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp
@@ -19,7 +19,18 @@ namespace MantidWidgets {
 
 WorkspaceTreeWidgetSimple::WorkspaceTreeWidgetSimple(MantidDisplayBase *mdb,
                                                      QWidget *parent)
-    : WorkspaceTreeWidget(mdb, parent) {}
+    : WorkspaceTreeWidget(mdb, parent),
+      m_plotSpectrum(new QAction("spectrum...", this)),
+      m_plotSpectrumWithErrs(new QAction("spectrum with errors...", this)),
+      m_plotColorfill(new QAction("colorfill", this)) {
+  // connections
+  connect(m_plotSpectrum, SIGNAL(triggered()), this,
+          SLOT(onPlotSpectrumClicked()));
+  connect(m_plotSpectrumWithErrs, SIGNAL(triggered()), this,
+          SLOT(onPlotSpectrumWithErrorsClicked()));
+  connect(m_plotColorfill, SIGNAL(triggered()), this,
+          SLOT(onPlotColorfillClicked()));
+}
 
 WorkspaceTreeWidgetSimple::~WorkspaceTreeWidgetSimple() {}
 
@@ -33,89 +44,24 @@ void WorkspaceTreeWidgetSimple::popupContextMenu() {
 
   QMenu *menu(nullptr);
 
-  // If no workspace is here then have load raw and dae
+  // If no workspace is here then have load items
   if (selectedWsName.isEmpty())
     menu = m_loadMenu;
-  else { // else show instrument, sample logs and delete
-         // Fresh menu
+  else {
     menu = new QMenu(this);
     menu->setObjectName("WorkspaceContextMenu");
-    auto mantidTreeItem = dynamic_cast<MantidTreeWidgetItem *>(treeItem);
-    auto ws = mantidTreeItem->data(0, Qt::UserRole)
-                  .value<Mantid::API::Workspace_sptr>();
-
-    // Add the items that are appropriate for the type
-    if (auto matrixWS =
-            boost::dynamic_pointer_cast<const Mantid::API::MatrixWorkspace>(
-                ws)) {
-      addMatrixWorkspaceMenuItems(menu, matrixWS);
-    } else if (auto groupWS =
-                   boost::dynamic_pointer_cast<const WorkspaceGroup>(ws)) {
-      addWorkspaceGroupMenuItems(menu);
-    } else if (boost::dynamic_pointer_cast<const Mantid::API::ITableWorkspace>(
-                   ws)) {
-      addTableWorkspaceMenuItems(menu);
-    } else {
-      // None of the above? -> not a workspace
-      return;
-    }
-
-    // Get the names of the programs for the send to option
-    std::vector<std::string> programNames =
-        (Mantid::Kernel::ConfigService::Instance().getKeys(
-            "workspace.sendto.name"));
-    bool firstPass(true);
-    // Check to see if any options aren't visible
-    for (auto &programName : programNames) {
-      std::string visible = Mantid::Kernel::ConfigService::Instance().getString(
-          "workspace.sendto." + programName + ".visible");
-      std::string target = Mantid::Kernel::ConfigService::Instance().getString(
-          "workspace.sendto." + programName + ".target");
-      if (Mantid::Kernel::ConfigService::Instance().isExecutable(target) &&
-          visible == "Yes") {
-        bool compatible(true);
-        std::string saveUsing(
-            Mantid::Kernel::ConfigService::Instance().getString(
-                "workspace.sendto." + programName + ".saveusing"));
-        try {
-          Mantid::API::IAlgorithm_sptr alg =
-              Mantid::API::AlgorithmManager::Instance().create(saveUsing);
-          alg->setPropertyValue("InputWorkspace", selectedWsName.toStdString());
-        } catch (std::exception &) {
-          compatible = false;
-        }
-        if (compatible) {
-          if (firstPass) {
-            m_saveToProgram = new QMenu(tr("Send to"), this);
-            menu->addMenu(m_saveToProgram);
-
-            // Sub-menu for program list
-            m_programMapper = new QSignalMapper(this);
-          }
-          QString name = QString::fromStdString(programName);
-          // Setup new menu option for the program
-          m_program = new QAction(name, this);
-          connect(m_program, SIGNAL(triggered()), m_programMapper, SLOT(map()));
-          // Send name of program when clicked
-          m_programMapper->setMapping(m_program, name);
-          m_saveToProgram->addAction(m_program);
-
-          // Set first pass to false so that it doesn't set up another menu
-          // entry for all programs.
-          firstPass = false;
-        }
-      }
-    }
-
-    // Tell the button what to listen for and what to do once clicked (if there
-    // is anything to connect it will be set to false)
-    if (!firstPass)
-      connect(m_programMapper, SIGNAL(mapped(const QString &)), this,
-              SLOT(onClickSaveToProgram(const QString &)));
-
-    // Rename is valid for all workspace types
+
+    // plot submenu first
+    QMenu *plotSubMenu(new QMenu("Plot", menu));
+    plotSubMenu->addAction(m_plotSpectrum);
+    plotSubMenu->addAction(m_plotSpectrumWithErrs);
+    plotSubMenu->addAction(m_plotColorfill);
+    menu->addMenu(plotSubMenu);
+
+    menu->addSeparator();
     menu->addAction(m_rename);
-    // separate delete
+    menu->addAction(m_saveNexus);
+
     menu->addSeparator();
     menu->addAction(m_delete);
   }
@@ -124,32 +70,16 @@ void WorkspaceTreeWidgetSimple::popupContextMenu() {
   menu->popup(QCursor::pos());
 }
 
-/**
-* Add the actions that are appropriate for a MatrixWorkspace
-* @param menu :: The menu to store the items
-* @param matrixWS :: The workspace related to the menu
-*/
-void WorkspaceTreeWidgetSimple::addMatrixWorkspaceMenuItems(
-    QMenu *menu,
-    const Mantid::API::MatrixWorkspace_const_sptr &matrixWS) const {
-  Q_UNUSED(matrixWS);
-  menu->addAction(m_saveNexus);
+void WorkspaceTreeWidgetSimple::onPlotSpectrumClicked() {
+  emit plotSpectrumClicked(getSelectedWorkspaceNamesAsQList());
 }
 
-/**
-* Add the actions that are appropriate for a WorkspaceGroup
-* @param menu :: The menu to store the items
-*/
-void WorkspaceTreeWidgetSimple::addWorkspaceGroupMenuItems(QMenu *menu) const {
-  menu->addAction(m_saveNexus);
+void WorkspaceTreeWidgetSimple::onPlotSpectrumWithErrorsClicked() {
+  emit plotSpectrumWithErrorsClicked(getSelectedWorkspaceNamesAsQList());
 }
 
-/**
-* Add the actions that are appropriate for a MatrixWorkspace
-* @param menu :: The menu to store the items
-*/
-void WorkspaceTreeWidgetSimple::addTableWorkspaceMenuItems(QMenu *menu) const {
-  menu->addAction(m_saveNexus);
+void WorkspaceTreeWidgetSimple::onPlotColorfillClicked() {
+  emit plotColorfillClicked(getSelectedWorkspaceNamesAsQList());
 }
 
 } // namespace MantidWidgets
diff --git a/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt b/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
index 06a4f8fc2300faee03822c3b8d26742ad49b4814..bd3d6b916abcdc6f8859c8a94b891d5f593d243f 100644
--- a/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
+++ b/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
@@ -54,7 +54,6 @@ 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 +69,8 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsPluginsAlgorithmDialogs
     ${OPENGL_LIBRARIES}
   QT4_LINK_LIBS
     Qt4::QtOpenGL
+  QT5_LINK_LIBS
+    Qt5::OpenGL
   MTD_QT_LINK_LIBS
     MantidQtWidgetsCommon
   OSX_INSTALL_RPATH
diff --git a/qt/widgets/plugins/algorithm_dialogs/inc/MantidQtWidgets/Plugins/AlgorithmDialogs/CreateSampleShapeDialog.h b/qt/widgets/plugins/algorithm_dialogs/inc/MantidQtWidgets/Plugins/AlgorithmDialogs/CreateSampleShapeDialog.h
index 2253a1851ae38e70868ea4310191893fbfa193d4..c17c43465a051b023d2a866cfb2693bc25e2e62a 100644
--- a/qt/widgets/plugins/algorithm_dialogs/inc/MantidQtWidgets/Plugins/AlgorithmDialogs/CreateSampleShapeDialog.h
+++ b/qt/widgets/plugins/algorithm_dialogs/inc/MantidQtWidgets/Plugins/AlgorithmDialogs/CreateSampleShapeDialog.h
@@ -160,9 +160,14 @@ public:
   void traverseInPostOrder(BinaryTreeWidgetItem *node,
                            QList<BinaryTreeWidgetItem *> &expression);
 
-  /// Called when the data in the model is changed
+/// Called when the data in the model is changed
+#if QT_VERSION < 0x050000
   void dataChanged(const QModelIndex &topLeft,
                    const QModelIndex &bottomRight) override;
+#else
+  void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+                   const QVector<int> &roles = QVector<int>()) override;
+#endif
 
 signals:
   /// Emitted when data has changed
diff --git a/qt/widgets/plugins/algorithm_dialogs/src/CreateSampleShapeDialog.cpp b/qt/widgets/plugins/algorithm_dialogs/src/CreateSampleShapeDialog.cpp
index 90abd0167da3503eb930d8ad918caf5ff250045a..ef129a526ed3afe07979eb3ba088d503c0caae6e 100644
--- a/qt/widgets/plugins/algorithm_dialogs/src/CreateSampleShapeDialog.cpp
+++ b/qt/widgets/plugins/algorithm_dialogs/src/CreateSampleShapeDialog.cpp
@@ -622,8 +622,13 @@ void BinaryTreeWidget::traverseInPostOrder(
   expression.append(node);
 }
 
+#if QT_VERSION < 0x050000
 void BinaryTreeWidget::dataChanged(const QModelIndex &topLeft,
                                    const QModelIndex &) {
+#else
+void BinaryTreeWidget::dataChanged(const QModelIndex &topLeft,
+                                   const QModelIndex &, const QVector<int> &) {
+#endif
   emit treeDataChange(
       dynamic_cast<BinaryTreeWidgetItem *>(itemFromIndex(topLeft)),
       topLeft.data(Qt::UserRole).toInt());
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py b/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
index 978617fa90503af150466c87f51ba0444a5ba345..8934429ce5f1396395660898f70dd69ff1973b8b 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
@@ -23,11 +23,11 @@ script_params = {
 }
 
 pdf_focused_cropping_values = [
-    (1500, 19900),  # Bank 1
-    (1500, 19900),  # Bank 2
-    (1500, 19900),  # Bank 3
-    (1500, 19900),  # Bank 4
-    (1500, 19900),  # Bank 5
+    (700,  30000),  # Bank 1
+    (1200, 24900),  # Bank 2
+    (1100, 19950),  # Bank 3
+    (1100, 19950),  # Bank 4
+    (1100, 19950),  # Bank 5
     ]
 
 rietveld_focused_cropping_values = [
diff --git a/scripts/SANS/sans/algorithm_detail/mask_workspace.py b/scripts/SANS/sans/algorithm_detail/mask_workspace.py
index dd1234632a1cd6037421d67c643bdf96a2c980c7..10caf14904c97a8cd0e277d8087150da9927e1eb 100644
--- a/scripts/SANS/sans/algorithm_detail/mask_workspace.py
+++ b/scripts/SANS/sans/algorithm_detail/mask_workspace.py
@@ -118,11 +118,11 @@ def mask_with_mask_files(mask_info, workspace):
         load_options = {"Instrument": idf_path,
                         "OutputWorkspace": EMPTY_NAME}
         load_alg = create_unmanaged_algorithm(load_name, **load_options)
+        dummy_params = {"OutputWorkspace": EMPTY_NAME}
+        mask_alg = create_unmanaged_algorithm("MaskInstrument", **dummy_params)
+        clear_alg = create_unmanaged_algorithm("ClearMaskedSpectra", **dummy_params)
 
         # Masker
-        mask_name = "MaskDetectors"
-        mask_options = {"ForceInstrumentMasking": True}
-        mask_alg = create_unmanaged_algorithm(mask_name, **mask_options)
         for mask_file in mask_files:
             mask_file = find_full_file_path(mask_file)
 
@@ -130,11 +130,21 @@ def mask_with_mask_files(mask_info, workspace):
             load_alg.setProperty("InputFile", mask_file)
             load_alg.execute()
             masking_workspace = load_alg.getProperty("OutputWorkspace").value
-            # Mask the detector ids on the original workspace
-            mask_alg.setProperty("Workspace", workspace)
-            mask_alg.setProperty("MaskedWorkspace", masking_workspace)
+            # Could use MaskDetectors directly with masking_workspace but it does not
+            # support MPI. Use a three step approach via a, b, and c instead.
+            # a) Extract detectors to mask from MaskWorkspace
+            det_ids = masking_workspace.getMaskedDetectors()
+            # b) Mask the detector ids on the instrument
+            mask_alg.setProperty("InputWorkspace", workspace)
+            mask_alg.setProperty("OutputWorkspace", workspace)
+            mask_alg.setProperty("DetectorIDs", det_ids)
             mask_alg.execute()
-            workspace = mask_alg.getProperty("Workspace").value
+            workspace = mask_alg.getProperty("OutputWorkspace").value
+        # c) Clear data in all spectra associated with masked detectors
+        clear_alg.setProperty("InputWorkspace", workspace)
+        clear_alg.setProperty("OutputWorkspace", workspace)
+        clear_alg.execute()
+        workspace = clear_alg.getProperty("OutputWorkspace").value
     return workspace
 
 
@@ -243,12 +253,15 @@ def mask_spectra(mask_info, workspace, spectra_block, detector_type):
 
     # Perform the masking
     if total_spectra:
-        mask_name = "MaskDetectors"
-        mask_options = {"Workspace": workspace,
-                        "SpectraList": total_spectra}
+        mask_name = "MaskSpectra"
+        mask_options = {"InputWorkspace": workspace,
+                        "InputWorkspaceIndexType": "SpectrumNumber",
+                        "OutputWorkspace": "__dummy"}
         mask_alg = create_unmanaged_algorithm(mask_name, **mask_options)
+        mask_alg.setProperty("InputWorkspaceIndexSet", list(set(total_spectra)))
+        mask_alg.setProperty("OutputWorkspace", workspace)
         mask_alg.execute()
-        workspace = mask_alg.getProperty("Workspace").value
+        workspace = mask_alg.getProperty("OutputWorkspace").value
     return workspace
 
 
diff --git a/scripts/SANS/sans/algorithm_detail/single_execution.py b/scripts/SANS/sans/algorithm_detail/single_execution.py
index 63f78b7ddef7bac25e93768f0ee2df2903dbafaa..dbaa3eaab6dbef022cefab96bb1e1c2a633e42f8 100644
--- a/scripts/SANS/sans/algorithm_detail/single_execution.py
+++ b/scripts/SANS/sans/algorithm_detail/single_execution.py
@@ -7,6 +7,8 @@ from sans.common.enums import (ISISReductionMode, DetectorType, DataType, Output
 from sans.algorithm_detail.strip_end_nans_and_infs import strip_end_nans
 from sans.algorithm_detail.merge_reductions import (MergeFactory, is_sample, is_can)
 from sans.algorithm_detail.bundles import (OutputBundle, OutputPartsBundle)
+from mantid.kernel import mpisetup
+import sys
 
 
 def run_core_reduction(reduction_alg, reduction_setting_bundle):
@@ -230,7 +232,14 @@ def run_optimized_for_can(reduction_alg, reduction_setting_bundle):
                                      output_parts_bundle.output_workspace_norm is None))
     partial_output_require_reload = output_parts and is_invalid_partial_workspaces
 
-    if output_bundle.output_workspace is None or partial_output_require_reload:
+    must_reload = output_bundle.output_workspace is None or partial_output_require_reload
+    if 'boost.mpi' in sys.modules:
+        # In MPI runs the result is only present on rank 0 (result of Q1D2 integration),
+        # so the reload flag must be broadcasted from rank 0.
+        must_reload = mpisetup.boost.mpi.broadcast(mpisetup.boost.mpi.world, must_reload, 0)
+
+    if must_reload:
+    #if output_bundle.output_workspace is None or partial_output_require_reload:
         output_bundle, output_parts_bundle = run_core_reduction(reduction_alg, reduction_setting_bundle)
 
         # Now we need to tag the workspaces and add it to the ADS
diff --git a/scripts/SANS/sans/algorithm_detail/strip_end_nans_and_infs.py b/scripts/SANS/sans/algorithm_detail/strip_end_nans_and_infs.py
index 08ffc8bf566c8392ad382f49869f86045556d492..fca6921520c7c5d8305ef2001f35d5515715ffbb 100644
--- a/scripts/SANS/sans/algorithm_detail/strip_end_nans_and_infs.py
+++ b/scripts/SANS/sans/algorithm_detail/strip_end_nans_and_infs.py
@@ -13,7 +13,7 @@ def strip_end_nans(workspace, parent_alg=None):
     :return: A trimmed NAN- and INF-trimmed workspace
     """
     # If the workspace is larger than 1D, then there is nothing we can do
-    if workspace.getNumberHistograms() > 1:
+    if workspace is None or workspace.getNumberHistograms() != 1:
         return workspace
     data = workspace.readY(0)
     # Find the index at which the first legal value appears
diff --git a/scripts/SANS/sans/common/log_tagger.py b/scripts/SANS/sans/common/log_tagger.py
index 1c5406b1bb6300a896ff2627ee74c401e9372d00..cec8b7556b0b4726850d7b9372585990209ce352 100644
--- a/scripts/SANS/sans/common/log_tagger.py
+++ b/scripts/SANS/sans/common/log_tagger.py
@@ -67,6 +67,8 @@ def has_tag(tag, workspace):
     :param workspace: the workspace to check.
     :return: true if the tag exists else false.
     """
+    if workspace is None:
+        return False
     check_if_valid_tag_and_workspace(tag, workspace)
     run = workspace.getRun()
     return tag in run
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 62e3d1f730c1bbfc75e1b05c864a3eec9867a3f0..5b764b636dbcafa66dcebc4b8dbf9f0f2b25b618 100644
--- a/scripts/test/SANS/gui_logic/run_tab_presenter_test.py
+++ b/scripts/test/SANS/gui_logic/run_tab_presenter_test.py
@@ -12,7 +12,8 @@ from sans.common.enums import (SANSFacility, ReductionDimensionality, SaveType,
                                RangeStepType, FitType)
 from sans.test_helper.user_file_test_helper import (create_user_file, sample_user_file, sample_user_file_gravity_OFF)
 from sans.test_helper.mock_objects import (create_mock_view)
-from sans.test_helper.common import (remove_file, save_to_csv)
+from sans.test_helper.common import (remove_file)
+from sans.common.enums import BatchReductionEntry
 
 
 if sys.version_info.major == 3:
@@ -20,20 +21,21 @@ if sys.version_info.major == 3:
 else:
     import mock
 
+BATCH_FILE_TEST_CONTENT_1 = [{BatchReductionEntry.SampleScatter: 1, BatchReductionEntry.SampleTransmission: 2,
+                              BatchReductionEntry.SampleDirect: 3, BatchReductionEntry.Output: 'test_file',
+                              BatchReductionEntry.UserFile: 'user_test_file'},
+                             {BatchReductionEntry.SampleScatter: 1, BatchReductionEntry.CanScatter: 2,
+                              BatchReductionEntry.Output: 'test_file2'}]
 
-BATCH_FILE_TEST_CONTENT_1 = "# MANTID_BATCH_FILE add more text here\n" \
-                            "sample_sans,1,sample_trans,2,sample_direct_beam,3," \
-                            "output_as,test_file,user_file,user_test_file\n" \
-                            "sample_sans,1,can_sans,2,output_as,test_file2\n"
+BATCH_FILE_TEST_CONTENT_2 = [{BatchReductionEntry.SampleScatter: 'SANS2D00022024',
+                              BatchReductionEntry.SampleTransmission: 'SANS2D00022048',
+                              BatchReductionEntry.SampleDirect: 'SANS2D00022048',
+                              BatchReductionEntry.Output: 'test_file', BatchReductionEntry.UserFile: 'user_test_file'},
+                             {BatchReductionEntry.SampleScatter: 'SANS2D00022024', BatchReductionEntry.Output: 'test_file2'}]
 
-
-BATCH_FILE_TEST_CONTENT_2 = "# MANTID_BATCH_FILE add more text here\n" \
-                            "sample_sans,SANS2D00022024,sample_trans,SANS2D00022048," \
-                            "sample_direct_beam,SANS2D00022048,output_as,test_file\n" \
-                            "sample_sans,SANS2D00022024,output_as,test_file2\n"
-
-BATCH_FILE_TEST_CONTENT_3 = "# MANTID_BATCH_FILE add more text here\n" \
-                            "sample_sans,1p3,output_as,test_file\n"
+BATCH_FILE_TEST_CONTENT_3 = [{BatchReductionEntry.SampleScatter: '1',
+                              BatchReductionEntry.SampleScatterPeriod: '3',
+                              BatchReductionEntry.Output: 'test_file'}]
 
 
 class MultiPeriodMock(object):
@@ -53,6 +55,13 @@ class RunTabPresenterTest(unittest.TestCase):
     def setUp(self):
         config.setFacility("ISIS")
         config.setString("default.instrument", "SANS2D")
+        patcher = mock.patch('sans.gui_logic.presenter.run_tab_presenter.BatchCsvParser')
+        self.addCleanup(patcher.stop)
+        self.BatchCsvParserMock = patcher.start()
+
+        self.os_patcher = mock.patch('sans.gui_logic.presenter.run_tab_presenter.os')
+        self.addCleanup(self.os_patcher.stop)
+        self.osMock = self.os_patcher.start()
 
     def test_that_will_load_user_file(self):
         # Setup presenter and mock view
@@ -137,6 +146,7 @@ class RunTabPresenterTest(unittest.TestCase):
         remove_file(user_file_path)
 
     def test_fails_silently_when_user_file_does_not_exist(self):
+        self.os_patcher.stop()
         view, _, _ = create_mock_view("non_existent_user_file")
 
         presenter = RunTabPresenter(SANSFacility.ISIS)
@@ -148,6 +158,7 @@ class RunTabPresenterTest(unittest.TestCase):
         except:  # noqa
             has_raised = True
         self.assertFalse(has_raised)
+        self.os_patcher.start()
 
     def do_test_that_loads_batch_file_and_places_it_into_table(self, use_multi_period):
         # Arrange
@@ -198,8 +209,8 @@ class RunTabPresenterTest(unittest.TestCase):
         presenter.on_batch_file_load()
 
         # Assert
-        self.assertTrue(view.add_row.call_count == 1)
-        self.assertTrue(view.set_multi_period_view_mode.call_count == 1)
+        self.assertEqual(view.add_row.call_count, 1)
+        self.assertEqual(view.set_multi_period_view_mode.call_count, 1)
 
         expected_row = "SampleScatter:1,ssp:3,SampleTrans:,stp:,SampleDirect:,sdp:," \
                        "CanScatter:,csp:,CanTrans:,ctp:,CanDirect:,cdp:,OutputName:test_file"
@@ -208,6 +219,7 @@ class RunTabPresenterTest(unittest.TestCase):
         view.add_row.assert_has_calls(calls)
 
     def test_fails_silently_when_batch_file_does_not_exist(self):
+        self.os_patcher.stop()
         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, "non_existent_batch_file")
@@ -222,6 +234,7 @@ class RunTabPresenterTest(unittest.TestCase):
 
         # Clean up
         self._remove_files(user_file_path=user_file_path)
+        self.os_patcher.start()
 
     def test_that_gets_states_from_view(self):
         # Arrange
@@ -310,10 +323,8 @@ class RunTabPresenterTest(unittest.TestCase):
 
     def test_that_returns_none_when_index_does_not_exist(self):
         # Arrange
-        batch_file_path = save_to_csv(BATCH_FILE_TEST_CONTENT_2)
-        user_file_path = create_user_file(sample_user_file)
+        batch_file_path, user_file_path, presenter, _ = self._get_files_and_mock_presenter(BATCH_FILE_TEST_CONTENT_2)
         view, _, _ = create_mock_view(user_file_path, batch_file_path)
-        presenter = RunTabPresenter(SANSFacility.ISIS)
         presenter.set_view(view)
         presenter.on_user_file_load()
         presenter.on_batch_file_load()
@@ -441,9 +452,11 @@ class RunTabPresenterTest(unittest.TestCase):
             if PropertyManagerDataService.doesExist(element):
                 PropertyManagerDataService.remove(element)
 
-    @staticmethod
-    def _get_files_and_mock_presenter(content, is_multi_period=True, row_user_file_path = ""):
-        batch_file_path = save_to_csv(content)
+    def _get_files_and_mock_presenter(self, content, is_multi_period=True, row_user_file_path = ""):
+        batch_parser = mock.MagicMock()
+        batch_parser.parse_batch_file = mock.MagicMock(return_value=content)
+        self.BatchCsvParserMock.return_value = batch_parser
+        batch_file_path = 'batch_file_path'
         user_file_path = create_user_file(sample_user_file)
         view, _, _ = create_mock_view(user_file_path, batch_file_path, row_user_file_path)
         # We just use the sample_user_file since it exists.
diff --git a/scripts/test/SANS/gui_logic/table_model_test.py b/scripts/test/SANS/gui_logic/table_model_test.py
index 739b2aaaa733098e84f66bac0629c0f2b273aea0..8ac1f0d198c9b33e869ab76533d09d5ba17863d6 100644
--- a/scripts/test/SANS/gui_logic/table_model_test.py
+++ b/scripts/test/SANS/gui_logic/table_model_test.py
@@ -66,7 +66,7 @@ class TableModelTest(unittest.TestCase):
         self.assertFalse(has_raised)
 
         # Test raises for non-existent file path
-        self.assertRaises(ValueError, func, "/home/test")
+        self.assertRaises(ValueError, func, "/home/testSDFHSNDFG")
 
         # Test that can be set to valid value
         setattr(table_model, prop, __file__)