From a96c51f36cf0acdd6ca78d5fd49dc9567aeb4620 Mon Sep 17 00:00:00 2001
From: Federico Montesino Pouzols <federico.montesino-pouzols@stfc.ac.uk>
Date: Fri, 27 May 2016 00:11:23 +0100
Subject: [PATCH] use AlignDetectors conversion, GSAS calibration for fitting,
 re #16252

---
 .../EnggDiffractionPresenter.h                |  40 +-
 .../EnggDiffractionPresenter.cpp              | 485 +++++++++++++++---
 2 files changed, 435 insertions(+), 90 deletions(-)

diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionPresenter.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionPresenter.h
index 60771b1d37e..23f1a94e944 100644
--- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionPresenter.h
+++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionPresenter.h
@@ -15,7 +15,7 @@
 #include <QObject>
 
 namespace Poco {
-  class Path;
+class Path;
 }
 
 class QThread;
@@ -23,6 +23,8 @@ class QThread;
 namespace MantidQt {
 namespace CustomInterfaces {
 
+struct GSASCalibrationParms;
+
 /**
 Presenter for the Enggineering Diffraction GUI (presenter as in the
 MVP Model-View-Presenter pattern). In principle, in a strict MVP
@@ -104,9 +106,9 @@ public:
 
   void plotFitPeaksCurves();
 
-  void runEvaluateFunctionAlg(std::string bk2BkExpFunction,
-                              std::string InputName, std::string OutputName,
-                              std::string startX, std::string endX);
+  void runEvaluateFunctionAlg(const std::string &bk2BkExpFunction,
+                              const std::string &InputName, const std::string &OutputName,
+                              const std::string &startX, const std::string &endX);
 
   void runCropWorkspaceAlg(std::string workspaceName);
 
@@ -115,7 +117,13 @@ public:
 
   void runRebinToWorkspaceAlg(std::string workspaceName);
 
-  void runConvetUnitsAlg(std::string workspaceName);
+  void convertUnits(std::string workspaceName);
+  void runConvertUnitsAlg(std::string workspaceName);
+  void runAlignDetectorsAlg(std::string workspaceName);
+
+  void setDifcTzero(Mantid::API::MatrixWorkspace_sptr wks) const;
+  void getDifcTzero(Mantid::API::MatrixWorkspace_const_sptr wks, double &difc,
+                    double &difa, double &tzero) const;
 
   void runCloneWorkspaceAlg(std::string inputWorkspace,
                             const std::string &outputWorkspace);
@@ -164,9 +172,15 @@ private:
                                   const std::string &ceriaNo,
                                   const std::string &bankName = "");
 
+  void updateNewCalib(const std::string &fname);
+
   void parseCalibrateFilename(const std::string &path, std::string &instName,
                               std::string &vanNo, std::string &ceriaNo);
 
+  void grabCalibParms(const std::string &fname);
+
+  void updateCalibParmsTable();
+
   // this may need to be mocked up in tests
   virtual void startAsyncCalibWorker(const std::string &outFilename,
                                      const std::string &vanNo,
@@ -304,7 +318,8 @@ private:
   std::string outFileNameFactory(std::string inputWorkspace, std::string runNo,
                                  std::string bank, std::string format);
 
-  // returns a directory as a path, creating it if not found, and checking errors
+  // returns a directory as a path, creating it if not found, and checking
+  // errors
   Poco::Path outFilesUserDir(const std::string &addToDir);
   Poco::Path outFilesGeneralDir(const std::string &addComponent);
   Poco::Path outFilesRootDir();
@@ -358,6 +373,12 @@ private:
   // name of the workspace with the vanadium (smoothed) curves
   static const std::string g_vanCurvesWSName;
 
+  // name of the workspace with the focused ws being used for fitting
+  static const std::string g_focusedFittingWSName;
+
+  // for the GSAS parameters (difc, difa, tzero) of the banks
+  static const std::string g_calibBanksParms;
+
   /// whether to allow users to give the output calibration filename
   const static bool g_askUserCalibFilename;
 
@@ -380,6 +401,10 @@ private:
   /// path where the calibration has been produced (par/prm file)
   std::string m_calibFullPath;
 
+  /// The current calibration parameters (used for units conversion). It should
+  /// be updated when a new calibration is done or re-loading an existing one
+  std::vector<GSASCalibrationParms> m_currentCalibParms;
+
   /// true if the last focusing completed successfully
   bool m_focusFinishedOK;
   /// true if the last pre-processing/re-binning completed successfully
@@ -387,6 +412,9 @@ private:
   /// true if the last fitting completed successfully
   bool m_fittingFinishedOK;
 
+  // whether to use AlignDetectors to convert units
+  static bool g_useAlignDetectors;
+
   /// Counter for the cropped output files
   static int g_croppedCounter;
 
diff --git a/MantidQt/CustomInterfaces/src/EnggDiffraction/EnggDiffractionPresenter.cpp b/MantidQt/CustomInterfaces/src/EnggDiffraction/EnggDiffractionPresenter.cpp
index cb5ab366b52..6e55caedcb8 100644
--- a/MantidQt/CustomInterfaces/src/EnggDiffraction/EnggDiffractionPresenter.cpp
+++ b/MantidQt/CustomInterfaces/src/EnggDiffraction/EnggDiffractionPresenter.cpp
@@ -1,5 +1,9 @@
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/TableRow.h"
+#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidKernel/Property.h"
+#include <MantidKernel/StringTokenizer.h>
 #include "MantidQtAPI/PythonRunner.h"
 // #include "MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionModel.h"
 #include "MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionPresenter.h"
@@ -23,6 +27,21 @@ using namespace MantidQt::CustomInterfaces;
 namespace MantidQt {
 namespace CustomInterfaces {
 
+/**
+ * Parameters from a GSAS calibration. They define a conversion of
+ * units time-of-flight<->d-spacing that can be calculated with the
+ * algorithm AlignDetectors for example.
+ */
+struct GSASCalibrationParms {
+  GSASCalibrationParms(size_t bid, double dc, double da, double tz)
+      : bankid(bid), difc(dc), difa(da), tzero(tz) {}
+
+  size_t bankid{0};
+  double difc{0};
+  double difa{0};
+  double tzero{0};
+};
+
 namespace {
 Mantid::Kernel::Logger g_log("EngineeringDiffractionGUI");
 }
@@ -51,6 +70,13 @@ const std::string EnggDiffractionPresenter::g_vanIntegrationWSName =
 const std::string EnggDiffractionPresenter::g_vanCurvesWSName =
     "engggui_vanadium_curves_ws";
 
+const std::string EnggDiffractionPresenter::g_focusedFittingWSName =
+    "engggui_fitting_focused_ws";
+
+const std::string EnggDiffractionPresenter::g_calibBanksParms =
+    "engggui_calibration_banks_parameters";
+
+bool EnggDiffractionPresenter::g_useAlignDetectors = true;
 int EnggDiffractionPresenter::g_croppedCounter = 0;
 int EnggDiffractionPresenter::g_plottingCounter = 0;
 bool EnggDiffractionPresenter::g_abortThread = false;
@@ -170,6 +196,8 @@ void EnggDiffractionPresenter::notify(
 void EnggDiffractionPresenter::processStart() {
   EnggDiffCalibSettings cs = m_view->currentCalibSettings();
   m_view->showStatus("Ready");
+
+  updateNewCalib(m_view->currentCalibFile());
 }
 
 void EnggDiffractionPresenter::processLoadExistingCalib() {
@@ -180,6 +208,21 @@ void EnggDiffractionPresenter::processLoadExistingCalib() {
     return;
   }
 
+  updateNewCalib(fname);
+}
+
+/**
+ * Grab a calibration from a (GSAS calibration) file
+ * (.prm/.par/.iparm) and set/use it as current calibration.
+ *
+ * @param fname name/path of the calibration file
+ */
+
+void EnggDiffractionPresenter::updateNewCalib(const std::string &fname) {
+  if (fname.empty()) {
+    return;
+  }
+
   std::string instName, vanNo, ceriaNo;
   try {
     parseCalibrateFilename(fname, instName, vanNo, ceriaNo);
@@ -188,7 +231,105 @@ void EnggDiffractionPresenter::processLoadExistingCalib() {
     return;
   }
 
-  m_view->newCalibLoaded(vanNo, ceriaNo, fname);
+  try {
+    grabCalibParms(fname);
+    updateCalibParmsTable();
+    m_view->newCalibLoaded(vanNo, ceriaNo, fname);
+  } catch (std::runtime_error &rexc) {
+    m_view->userWarning("Problem while updating calibration.", rexc.what());
+  }
+}
+
+/**
+ * Get from a calibration file (GSAS instrument parameters file) the
+ * DIFC, DIFA, TZERO calibration parameters used for units
+ * conversions. Normally this is used on the ...all_banks.prm file
+ * which has the parameters for every bank included in the calibration
+ * process.
+ *
+ * @param fname name of the calibration/GSAS iparm file
+ */
+void EnggDiffractionPresenter::grabCalibParms(const std::string &fname) {
+  std::vector<GSASCalibrationParms> parms;
+
+  // To grab the bank indices, lines like "INS   BANK     2"
+  // To grab the difc,difa,tzero parameters, lines like:
+  // "INS  2 ICONS  18388.00    0.00    -6.76"
+  try {
+    std::ifstream prmFile(fname);
+    std::string line;
+    int opts = Mantid::Kernel::StringTokenizer::TOK_TRIM +
+               Mantid::Kernel::StringTokenizer::TOK_IGNORE_EMPTY;
+    while (std::getline(prmFile, line)) {
+      if (line.find("ICONS") != std::string::npos) {
+        Mantid::Kernel::StringTokenizer tokenizer(line, " ", opts);
+        const size_t numElems = 6;
+        if (tokenizer.count() == numElems) {
+          try {
+            size_t bid = boost::lexical_cast<size_t>(tokenizer[1]);
+            double difc = boost::lexical_cast<double>(tokenizer[3]);
+            double difa = boost::lexical_cast<double>(tokenizer[4]);
+            double tzero = boost::lexical_cast<double>(tokenizer[5]);
+            parms.emplace_back(GSASCalibrationParms(bid, difc, difa, tzero));
+          } catch (std::runtime_error &rexc) {
+            g_log.warning()
+                << "Erro when trying to extract parameters from this line:  "
+                << line << ". This calibration file may not load correctly."
+                << std::endl;
+          }
+        } else {
+          g_log.warning() << "Could not parse correctly a parameters "
+                             "definition line in this calibration file    ("
+                          << fname << "). Did not find  " << numElems
+                          << " elements as expected. The calibration may not "
+                             "load correctly" << std::endl;
+        }
+      }
+    }
+
+  } catch (std::runtime_error &rexc) {
+    g_log.error() << "Error while loading calibration / GSAS IPARM file ("
+                  << fname << "). Could not . Please check the file."
+                  << std::endl;
+  }
+
+  m_currentCalibParms = parms;
+}
+
+/**
+ * Puts in a table workspace, visible in the ADS, the per-bank
+ * calibration parameters for the current calibration.
+ */
+void EnggDiffractionPresenter::updateCalibParmsTable() {
+  if (m_currentCalibParms.empty()) {
+    return;
+  }
+
+  ITableWorkspace_sptr parmsTbl;
+  AnalysisDataServiceImpl &ADS = Mantid::API::AnalysisDataService::Instance();
+  if (ADS.doesExist(g_calibBanksParms)) {
+    parmsTbl = ADS.retrieveWS<ITableWorkspace>(g_calibBanksParms);
+    parmsTbl->setRowCount(0);
+  } else {
+    auto alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+        "CreateEmptyTableWorkspace");
+    alg->initialize();
+    alg->setPropertyValue("OutputWorkspace", g_calibBanksParms);
+    alg->execute();
+
+    parmsTbl = ADS.retrieveWS<ITableWorkspace>(g_calibBanksParms);
+    parmsTbl->addColumn("int", "bankid");
+    parmsTbl->addColumn("double", "difc");
+    parmsTbl->addColumn("double", "difa");
+    parmsTbl->addColumn("double", "tzero");
+  }
+
+  for (const auto &parms : m_currentCalibParms) {
+    // ADS.remove(FocusedFitPeaksTableName);
+    TableRow row = parmsTbl->appendRow();
+    row << static_cast<int>(parms.bankid) << parms.difc << parms.difa
+        << parms.tzero;
+  }
 }
 
 void EnggDiffractionPresenter::processCalcCalib() {
@@ -723,9 +864,9 @@ void EnggDiffractionPresenter::inputChecksBeforeFitting(
   }
 
   if (ExpectedPeaks.empty()) {
-    g_log.warning() << "Expected peaks were not passed, via fitting interface,"
-                       "the default list of"
-                       "expected peaks will be utlised instead." << std::endl;
+    g_log.warning() << "Expected peaks were not passed, via fitting interface, "
+                       "the default list of "
+                       "expected peaks will be utilised instead." << std::endl;
   }
   bool contains_non_digits =
       ExpectedPeaks.find_first_not_of("0123456789,. ") != std::string::npos;
@@ -754,28 +895,73 @@ void EnggDiffractionPresenter::startAsyncFittingWorker(
   m_workerThread->start();
 }
 
+void EnggDiffractionPresenter::setDifcTzero(MatrixWorkspace_sptr wks) const {
+  size_t bankID = 1;
+  // attempt to guess bankID - this should be done in code that is currently
+  // in the view
+  auto fittingFilename = m_view->getFittingRunNo();
+  Poco::File fittingFile(fittingFilename);
+  if (fittingFile.exists()) {
+    Poco::Path path(fittingFile.path());
+    auto name = path.getBaseName();
+    std::vector<std::string> chunks;
+    boost::split(chunks, name, boost::is_any_of("_"));
+    if (!chunks.empty()) {
+      try {
+        bankID = boost::lexical_cast<size_t>(chunks.back());
+      } catch (std::runtime_error &) {
+      }
+    }
+  }
+
+  const std::string units = "none";
+  auto &run = wks->mutableRun();
+
+  if (m_currentCalibParms.empty()) {
+    run.addProperty<int>("bankid", 1, units, true);
+    run.addProperty<double>("difc", 18400.0, units, true);
+    run.addProperty<double>("difa", 0.0, units, true);
+    run.addProperty<double>("tzero", 4.0, units, true);
+  } else {
+    GSASCalibrationParms parms(0, 0.0, 0.0, 0.0);
+    for (const auto &p : m_currentCalibParms) {
+      if (p.bankid == bankID) {
+        parms = p;
+        break;
+      }
+    }
+    if (0 == parms.difc)
+      parms = m_currentCalibParms.front();
+
+    run.addProperty<int>("bankid", static_cast<int>(parms.bankid), units, true);
+    run.addProperty<double>("difc", parms.difc, units, true);
+    run.addProperty<double>("difa", parms.difa, units, true);
+    run.addProperty<double>("tzero", parms.tzero, units, true);
+  }
+}
+
 void EnggDiffractionPresenter::doFitting(const std::string &focusedRunNo,
                                          const std::string &ExpectedPeaks) {
   // disable GUI to avoid any double threads
   m_view->enableCalibrateAndFocusActions(false);
-  g_log.notice() << "EnggDiffraction GUI: starting new fitting. This may "
-                    "take a few seconds... " << std::endl;
+  g_log.notice() << "EnggDiffraction GUI: starting new fitting with file "
+                 << focusedRunNo << ". This may take a few seconds... "
+                 << std::endl;
 
   MatrixWorkspace_sptr focusedWS;
-  const std::string FocusedWSName = "engggui_fitting_focused_ws";
   m_fittingFinishedOK = false;
 
-  // load the focused workspace file to preform single peak fits
+  // load the focused workspace file to perform single peak fits
   try {
     auto load =
         Mantid::API::AlgorithmManager::Instance().createUnmanaged("Load");
     load->initialize();
     load->setPropertyValue("Filename", focusedRunNo);
-    load->setPropertyValue("OutputWorkspace", FocusedWSName);
+    load->setPropertyValue("OutputWorkspace", g_focusedFittingWSName);
     load->execute();
 
     AnalysisDataServiceImpl &ADS = Mantid::API::AnalysisDataService::Instance();
-    focusedWS = ADS.retrieveWS<MatrixWorkspace>(FocusedWSName);
+    focusedWS = ADS.retrieveWS<MatrixWorkspace>(g_focusedFittingWSName);
   } catch (std::runtime_error &re) {
     g_log.error()
         << "Error while loading focused data. "
@@ -783,20 +969,22 @@ void EnggDiffractionPresenter::doFitting(const std::string &focusedRunNo,
            "peaks (file name: " +
                focusedRunNo + "). Error description: " + re.what() +
                " Please check also the previous log messages for details.";
-    throw;
+    return;
   }
 
+  setDifcTzero(focusedWS);
+
   // run the algorithm EnggFitPeaks with workspace loaded above
   // requires unit in Time of Flight
   auto enggFitPeaks =
       Mantid::API::AlgorithmManager::Instance().createUnmanaged("EnggFitPeaks");
-  const std::string FocusedFitPeaksTableName =
+  const std::string focusedFitPeaksTableName =
       "engggui_fitting_fitpeaks_params";
 
   // delete existing table workspace to avoid confusion
   AnalysisDataServiceImpl &ADS = Mantid::API::AnalysisDataService::Instance();
-  if (ADS.doesExist(FocusedFitPeaksTableName)) {
-    ADS.remove(FocusedFitPeaksTableName);
+  if (ADS.doesExist(focusedFitPeaksTableName)) {
+    ADS.remove(focusedFitPeaksTableName);
   }
 
   try {
@@ -805,9 +993,8 @@ void EnggDiffractionPresenter::doFitting(const std::string &focusedRunNo,
     if (!ExpectedPeaks.empty()) {
       enggFitPeaks->setProperty("ExpectedPeaks", ExpectedPeaks);
     }
-    enggFitPeaks->setProperty("FittedPeaks", FocusedFitPeaksTableName);
+    enggFitPeaks->setProperty("FittedPeaks", focusedFitPeaksTableName);
     enggFitPeaks->execute();
-
   } catch (std::exception &re) {
     g_log.error() << "Could not run the algorithm EnggFitPeaks "
                      "successfully for bank, "
@@ -816,12 +1003,10 @@ void EnggDiffractionPresenter::doFitting(const std::string &focusedRunNo,
                          static_cast<std::string>(re.what()) +
                          " Please check also the log message for detail."
                   << std::endl;
-  } catch (...) {
-    g_log.error() << "Caught an unknown exception\n";
   }
 
   try {
-    runFittingAlgs(FocusedFitPeaksTableName, FocusedWSName);
+    runFittingAlgs(focusedFitPeaksTableName, g_focusedFittingWSName);
 
   } catch (std::invalid_argument &ia) {
     g_log.error() << "Error, Fitting could not finish off correctly, " +
@@ -831,92 +1016,89 @@ void EnggDiffractionPresenter::doFitting(const std::string &focusedRunNo,
 }
 
 void EnggDiffractionPresenter::runFittingAlgs(
-    std::string FocusedFitPeaksTableName, std::string FocusedWSName) {
+    std::string focusedFitPeaksTableName, std::string focusedWSName) {
   // retrieve the table with parameters
-  AnalysisDataServiceImpl &ADS = Mantid::API::AnalysisDataService::Instance();
-  if (!ADS.doesExist(FocusedFitPeaksTableName)) {
-
+  auto &ADS = Mantid::API::AnalysisDataService::Instance();
+  if (!ADS.doesExist(focusedFitPeaksTableName)) {
     // convert units so valid dSpacing peaks can still be added to gui
-    if (ADS.doesExist(FocusedWSName))
-      runConvetUnitsAlg(FocusedWSName);
+    if (ADS.doesExist(g_focusedFittingWSName)) {
+      convertUnits(g_focusedFittingWSName);
+    }
 
     throw std::invalid_argument(
-        FocusedFitPeaksTableName +
+        focusedFitPeaksTableName +
         " workspace could not be found. "
         "Please check the log messages for more details.");
   }
 
-  ITableWorkspace_sptr table =
-      ADS.retrieveWS<ITableWorkspace>(FocusedFitPeaksTableName);
-
+  auto table = ADS.retrieveWS<ITableWorkspace>(focusedFitPeaksTableName);
   size_t rowCount = table->rowCount();
   const std::string single_peak_out_WS = "engggui_fitting_single_peaks";
-  std::string current_peak_out_WS;
+  std::string currentPeakOutWS;
 
   std::string Bk2BkExpFunctionStr;
   std::string startX = "";
   std::string endX = "";
-
   for (size_t i = 0; i < rowCount; i++) {
-
     // get the functionStrFactory to generate the string for function
     // property, returns the string with i row from table workspace
     // table is just passed so it works?
     Bk2BkExpFunctionStr =
-        functionStrFactory(table, FocusedFitPeaksTableName, i, startX, endX);
+        functionStrFactory(table, focusedFitPeaksTableName, i, startX, endX);
 
     g_log.debug() << "startX: " + startX + " . endX: " + endX << std::endl;
 
-    current_peak_out_WS = "__engggui_fitting_single_peaks" + std::to_string(i);
-    std::string current_peak_cloned_WS;
+    currentPeakOutWS = "__engggui_fitting_single_peaks" + std::to_string(i);
 
     // run EvaluateFunction algorithm with focused workspace to produce
     // the correct fit function
-    // FocusedWSName is not going to change as its always going to be from
+    // focusedWSName is not going to change as its always going to be from
     // single workspace
-    runEvaluateFunctionAlg(Bk2BkExpFunctionStr, FocusedWSName,
-                           current_peak_out_WS, startX, endX);
+    runEvaluateFunctionAlg(Bk2BkExpFunctionStr, focusedWSName, currentPeakOutWS,
+                           startX, endX);
 
     // crop workspace so only the correct workspace index is plotted
-    runCropWorkspaceAlg(current_peak_out_WS);
+    runCropWorkspaceAlg(currentPeakOutWS);
 
     // apply the same binning as a focused workspace
-    runRebinToWorkspaceAlg(current_peak_out_WS);
+    runRebinToWorkspaceAlg(currentPeakOutWS);
 
     // if the first peak
     if (i == size_t(0)) {
 
       // create a workspace clone of bank focus file
       // this will import all information of the previous file
-      runCloneWorkspaceAlg(FocusedWSName, single_peak_out_WS);
+      runCloneWorkspaceAlg(focusedWSName, single_peak_out_WS);
 
-      setDataToClonedWS(current_peak_out_WS, single_peak_out_WS);
-      ADS.remove(current_peak_out_WS);
+      setDataToClonedWS(currentPeakOutWS, single_peak_out_WS);
+      ADS.remove(currentPeakOutWS);
     } else {
-      current_peak_cloned_WS =
+      const std::string currentPeakClonedWS =
           "__engggui_fitting_cloned_peaks" + std::to_string(i);
 
-      runCloneWorkspaceAlg(FocusedWSName, current_peak_cloned_WS);
+      runCloneWorkspaceAlg(focusedWSName, currentPeakClonedWS);
 
-      setDataToClonedWS(current_peak_out_WS, current_peak_cloned_WS);
+      setDataToClonedWS(currentPeakOutWS, currentPeakClonedWS);
 
       // append all peaks in to single workspace & remove
-      runAppendSpectraAlg(single_peak_out_WS, current_peak_cloned_WS);
-      ADS.remove(current_peak_out_WS);
-      ADS.remove(current_peak_cloned_WS);
+      runAppendSpectraAlg(single_peak_out_WS, currentPeakClonedWS);
+      ADS.remove(currentPeakOutWS);
+      ADS.remove(currentPeakClonedWS);
     }
   }
 
+  convertUnits(g_focusedFittingWSName);
+
   // convert units for both workspaces to dSpacing from ToF
   if (rowCount > size_t(0)) {
-    runConvetUnitsAlg(single_peak_out_WS);
+    auto swks = ADS.retrieveWS<MatrixWorkspace>(single_peak_out_WS);
+    setDifcTzero(swks);
+    convertUnits(single_peak_out_WS);
   } else {
     g_log.error() << "The engggui_fitting_fitpeaks_params table produced is"
                      "empty. Please try again!" << std::endl;
   }
 
-  runConvetUnitsAlg(FocusedWSName);
-
   m_fittingFinishedOK = true;
 }
 
@@ -953,8 +1135,9 @@ std::string EnggDiffractionPresenter::functionStrFactory(
 }
 
 void EnggDiffractionPresenter::runEvaluateFunctionAlg(
-    std::string bk2BkExpFunction, std::string InputName, std::string OutputName,
-    std::string startX, std::string endX) {
+    const std::string &bk2BkExpFunction, const std::string &InputName,
+    const std::string &OutputName, const std::string &startX,
+    const std::string &endX) {
 
   auto evalFunc = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
       "EvaluateFunction");
@@ -1015,7 +1198,7 @@ void EnggDiffractionPresenter::runRebinToWorkspaceAlg(
   try {
     RebinToWs->initialize();
     RebinToWs->setProperty("WorkspaceToRebin", workspaceName);
-    RebinToWs->setProperty("WorkspaceToMatch", "engggui_fitting_focused_ws");
+    RebinToWs->setProperty("WorkspaceToMatch", g_focusedFittingWSName);
     RebinToWs->setProperty("OutputWorkspace", workspaceName);
     RebinToWs->execute();
   } catch (std::runtime_error &re) {
@@ -1025,21 +1208,144 @@ void EnggDiffractionPresenter::runRebinToWorkspaceAlg(
   }
 }
 
-void EnggDiffractionPresenter::runConvetUnitsAlg(std::string workspaceName) {
+/**
+ * Converts from time-of-flight to d-spacing
+ *
+ * @param workspaceName name of the workspace to convert (in place)
+ */
+void EnggDiffractionPresenter::convertUnits(std::string workspaceName) {
+  // Here using the GSAS (DIFC, TZERO) parameters seems preferred
+  if (g_useAlignDetectors) {
+    runAlignDetectorsAlg(workspaceName);
+  } else {
+    runConvertUnitsAlg(workspaceName);
+  }
+}
+
+void EnggDiffractionPresenter::getDifcTzero(MatrixWorkspace_const_sptr wks,
+                                            double &difc, double &difa,
+                                            double &tzero) const {
+
+  try {
+    const auto run = wks->run();
+    // long, step by step way:
+    // auto propC = run.getLogData("difc");
+    // auto doubleC =
+    //     dynamic_cast<Mantid::Kernel::PropertyWithValue<double> *>(propC);
+    // if (!doubleC)
+    //   throw Mantid::Kernel::Exception::NotFoundError(
+    //       "Required difc property not found in workspace.", "difc");
+    difc = run.getPropertyValueAsType<double>("difc");
+    difa = run.getPropertyValueAsType<double>("difa");
+    tzero = run.getPropertyValueAsType<double>("tzero");
+
+  } catch (std::runtime_error &rexc) {
+    // fallback to something reasonable / approximate values so
+    // the fitting tab can work minimally
+    difa = tzero = 0.0;
+    difc = 18400;
+    g_log.warning()
+        << "Could not retrieve the DIFC, DIFA, TZERO values from the workspace "
+        << wks->name() << ". Using default, which is not adjusted for this "
+                          "workspace/run: DIFA: " << difa << ", DIFC: " << difc
+        << ", TZERO: " << tzero << ". Error details: " << rexc.what()
+        << std::endl;
+  }
+}
+
+/**
+ * Converts units from time-of-flight to d-spacing, using
+ * AlignDetectors.  This is the GSAS-style alternative to using the
+ * algorithm ConvertUnits.  Needs to make sure that the workspace is
+ * not of distribution type (and use the algorithm
+ * ConvertFromDistribution if it is). This is a requirement of
+ * AlignDetectors.
+ *
+ * @param workspaceName name of the workspace to convert
+ */
+void EnggDiffractionPresenter::runAlignDetectorsAlg(std::string workspaceName) {
+  const std::string targetUnit = "dSpacing";
+  const std::string algName = "AlignDetectors";
+
+  const auto &ADS = Mantid::API::AnalysisDataService::Instance();
+  auto inputWS = ADS.retrieveWS<MatrixWorkspace>(workspaceName);
+  if (!inputWS)
+    return;
+
+  double difc, difa, tzero;
+  getDifcTzero(inputWS, difc, difa, tzero);
 
+  // create a table with the GSAS calibration parameters
+  ITableWorkspace_sptr difcTable;
+  try {
+    difcTable = Mantid::API::WorkspaceFactory::Instance().createTable();
+    if (!difcTable) {
+      return;
+    }
+    difcTable->addColumn("int", "detid");
+    difcTable->addColumn("double", "difc");
+    difcTable->addColumn("double", "difa");
+    difcTable->addColumn("double", "tzero");
+    TableRow row = difcTable->appendRow();
+    auto spec = inputWS->getSpectrum(0);
+    if (!spec)
+      return;
+    Mantid::detid_t detID = *(spec->getDetectorIDs().cbegin());
+
+    row << detID << difc << difa << tzero;
+  } catch (std::runtime_error &rexc) {
+    g_log.error() << "Failed to prepare calibration table input to convert "
+                     "units with the algorithm " << algName
+                  << ". Error details: " << rexc.what() << std::endl;
+    return;
+  }
+
+  // AlignDetectors doesn't take distribution workspaces (it enforces
+  // RawCountValidator)
+  if (inputWS->isDistribution()) {
+    try {
+      auto alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+          "ConvertFromDistribution");
+      alg->initialize();
+      alg->setProperty("Workspace", workspaceName);
+      alg->execute();
+    } catch (std::runtime_error &rexc) {
+      g_log.error() << "Could not run ConvertFromDistribution"
+                    << std::endl; // TODO TODO TODO TODO *****
+    }
+  }
+
+  try {
+    auto alg =
+        Mantid::API::AlgorithmManager::Instance().createUnmanaged(algName);
+    alg->initialize();
+    alg->setProperty("InputWorkspace", workspaceName);
+    alg->setProperty("OutputWorkspace", workspaceName);
+    alg->setProperty("CalibrationWorkspace", difcTable);
+    alg->execute();
+  } catch (std::runtime_error &rexc) {
+    g_log.error() << "Could not run the algorithm " << algName
+                  << " to convert workspace to " << targetUnit
+                  << ", Error details: " + static_cast<std::string>(rexc.what())
+                  << std::endl;
+  }
+}
+
+void EnggDiffractionPresenter::runConvertUnitsAlg(std::string workspaceName) {
+  const std::string targetUnit = "dSpacing";
   auto ConvertUnits =
       Mantid::API::AlgorithmManager::Instance().createUnmanaged("ConvertUnits");
   try {
     ConvertUnits->initialize();
     ConvertUnits->setProperty("InputWorkspace", workspaceName);
     ConvertUnits->setProperty("OutputWorkspace", workspaceName);
-    std::string targetUnit = "dSpacing";
     ConvertUnits->setProperty("Target", targetUnit);
     ConvertUnits->setPropertyValue("EMode", "Elastic");
     ConvertUnits->execute();
   } catch (std::runtime_error &re) {
-    g_log.error() << "Could not run the algorithm ConvertUnits, "
-                     "Error description: " +
+    g_log.error() << "Could not run the algorithm ConvertUnits to convert "
+                     "workspace to " << targetUnit
+                  << ", Error description: " +
                          static_cast<std::string>(re.what()) << std::endl;
   }
 }
@@ -1074,18 +1380,18 @@ void EnggDiffractionPresenter::setDataToClonedWS(std::string &current_WS,
 void EnggDiffractionPresenter::plotFitPeaksCurves() {
   AnalysisDataServiceImpl &ADS = Mantid::API::AnalysisDataService::Instance();
   std::string singlePeaksWs = "engggui_fitting_single_peaks";
-  std::string focusedPeaksWs = "engggui_fitting_focused_ws";
 
-  if (!ADS.doesExist(singlePeaksWs) && !ADS.doesExist(focusedPeaksWs)) {
+  if (!ADS.doesExist(singlePeaksWs) && !ADS.doesExist(g_focusedFittingWSName)) {
     g_log.error() << "Fitting results could not be plotted as there is no " +
-                         singlePeaksWs + " or " + focusedPeaksWs +
+                         singlePeaksWs + " or " + g_focusedFittingWSName +
                          " workspace found." << std::endl;
     m_view->showStatus("Error while fitting peaks");
     return;
   }
 
   try {
-    auto focusedPeaksWS = ADS.retrieveWS<MatrixWorkspace>(focusedPeaksWs);
+    auto focusedPeaksWS =
+        ADS.retrieveWS<MatrixWorkspace>(g_focusedFittingWSName);
     auto focusedData = ALCHelper::curveDataFromWs(focusedPeaksWS);
     m_view->setDataVector(focusedData, true, m_fittingFinishedOK);
 
@@ -1564,13 +1870,14 @@ void EnggDiffractionPresenter::calibrationFinished() {
 
   m_view->enableCalibrateAndFocusActions(true);
   if (!m_calibFinishedOK) {
-    g_log.warning() << "The cablibration did not finish correctly. Please "
+    g_log.warning() << "The calibration did not finish correctly. Please "
                        "check previous log messages for details." << std::endl;
     m_view->showStatus("Calibration didn't finish succesfully. Ready");
   } else {
     const std::string vanNo = isValidRunNumber(m_view->newVanadiumNo());
 
     const std::string ceriaNo = isValidRunNumber(m_view->newCeriaNo());
+    updateCalibParmsTable();
     m_view->newCalibLoaded(vanNo, ceriaNo, m_calibFullPath);
     g_log.notice()
         << "Cablibration finished and ready as 'current calibration'."
@@ -1658,7 +1965,8 @@ void EnggDiffractionPresenter::doCalib(const EnggDiffCalibSettings &cs,
   } catch (std::runtime_error &re) {
     g_log.error()
         << "Error while loading calibration sample data. "
-           "Could not run the algorithm Load succesfully for the calibration "
+           "Could not run the algorithm Load succesfully for the "
+           "calibration "
            "sample (run number: " +
                ceriaNo + "). Error description: " + re.what() +
                " Please check also the previous log messages for details.";
@@ -1758,6 +2066,7 @@ void EnggDiffractionPresenter::doCalib(const EnggDiffCalibSettings &cs,
   m_calibFullPath = outFullPath.toString();
   writeOutCalibFile(m_calibFullPath, difc, tzero, bankNames, ceriaNo, vanNo);
   copyToGeneral(outFullPath, calibrationComp);
+  m_currentCalibParms.clear();
 
   // Then write one individual file per bank, using different templates and the
   // specific bank name as suffix
@@ -1775,6 +2084,8 @@ void EnggDiffractionPresenter::doCalib(const EnggDiffCalibSettings &cs,
     writeOutCalibFile(outPathName, {difc[bankIdx]}, {tzero[bankIdx]},
                       {bankNames[bankIdx]}, ceriaNo, vanNo, templateFile);
     copyToGeneral(bankOutputFullPath, calibrationComp);
+    m_currentCalibParms.emplace_back(
+        GSASCalibrationParms(bankIdx, difc[bankIdx], 0.0, tzero[bankIdx]));
     if (1 == difc.size()) {
       // it is a  single bank or cropped calibration, so take its specific name
       m_calibFullPath = outPathName;
@@ -2304,12 +2615,13 @@ void EnggDiffractionPresenter::doFocusing(const EnggDiffCalibSettings &cs,
           Mantid::API::AnalysisDataService::Instance();
       inWS = ADS.retrieveWS<MatrixWorkspace>(inWSName);
     } catch (std::runtime_error &re) {
-      g_log.error()
-          << "Error while loading sample data for focusing. "
-             "Could not run the algorithm Load succesfully for the focusing "
-             "sample (run number: " +
-                 runNo + "). Error description: " + re.what() +
-                 " Please check also the previous log messages for details.";
+      g_log.error() << "Error while loading sample data for focusing. "
+                       "Could not run the algorithm Load succesfully for "
+                       "the focusing "
+                       "sample (run number: " +
+                           runNo + "). Error description: " + re.what() +
+                           " Please check also the previous log messages "
+                           "for details.";
       throw;
     }
   }
@@ -2443,9 +2755,8 @@ void EnggDiffractionPresenter::loadOrCalcVanadiumWorkspaces(
 
   // if pre caluclated not found ..
   if (forceRecalc || !foundPrecalc) {
-    g_log.notice()
-        << "Calculating Vanadium corrections. This may take a few seconds..."
-        << std::endl;
+    g_log.notice() << "Calculating Vanadium corrections. This may take a "
+                      "few seconds..." << std::endl;
     try {
       calcVanadiumWorkspaces(vanNo, vanIntegWS, vanCurvesWS);
     } catch (std::invalid_argument &ia) {
@@ -3368,9 +3679,9 @@ EnggDiffractionPresenter::outFilesUserDir(const std::string &addToDir) {
       dirFile.createDirectories();
     }
   } catch (Poco::FileAccessDeniedException &e) {
-    g_log.error()
-        << "Error caused by file access/permission, path to user directory: "
-        << dir.toString() << ". Error details: " << e.what() << std::endl;
+    g_log.error() << "Error caused by file access/permission, path to user "
+                     "directory: " << dir.toString()
+                  << ". Error details: " << e.what() << std::endl;
   } catch (std::runtime_error &re) {
     g_log.error() << "Error while finding/creating a user path: "
                   << dir.toString() << ". Error details: " << re.what()
@@ -3396,9 +3707,9 @@ EnggDiffractionPresenter::outFilesGeneralDir(const std::string &addComponent) {
 
     dir.append(addComponent);
   } catch (Poco::FileAccessDeniedException &e) {
-    g_log.error()
-        << "Error caused by file access/permission, path to general directory: "
-        << dir.toString() << ". Error details: " << e.what() << std::endl;
+    g_log.error() << "Error caused by file access/permission, path to "
+                     "general directory: " << dir.toString()
+                  << ". Error details: " << e.what() << std::endl;
   } catch (std::runtime_error &re) {
     g_log.error() << "Error while finding/creating a general path: "
                   << dir.toString() << ". Error details: " << re.what()
@@ -3461,10 +3772,9 @@ void EnggDiffractionPresenter::copyToGeneral(const Poco::Path &source,
                                              const std::string &pathComp) {
   Poco::File file(source);
   if (!file.exists() || !file.canRead()) {
-    g_log.warning()
-        << "Cannot copy the file " << source.toString()
-        << " to the general/all users directories because it cannot be read."
-        << std::endl;
+    g_log.warning() << "Cannot copy the file " << source.toString()
+                    << " to the general/all users directories because it "
+                       "cannot be read." << std::endl;
     return;
   }
 
@@ -3542,6 +3852,13 @@ void EnggDiffractionPresenter::copyToUser(const Poco::Path &source,
                       << std::endl;
 }
 
+/**
+ * Copies a file from a third location to the standard user/RB number
+ * and the general/all directories. This just uses copyToUser() and
+ * copyToGeneral().
+ *
+ * @param fullFilename full path to the origin file
+ */
 void EnggDiffractionPresenter::copyFocusedToUserAndAll(
     const std::string &fullFilename) {
   // The files are saved by SaveNexus in the Settings/Focusing output folder.
-- 
GitLab