Skip to content
Snippets Groups Projects
EnggDiffractionPresenter.cpp 101 KiB
Newer Older
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
//     NScD Oak Ridge National Laboratory, European Spallation Source
//     & Institut Laue - Langevin
// SPDX - License - Identifier: GPL - 3.0 +
#include "EnggDiffractionPresenter.h"
#include "EnggDiffractionPresWorker.h"
#include "EnggVanadiumCorrectionsModel.h"
#include "IEnggDiffractionView.h"
#include "MantidAPI/AlgorithmManager.h"
#include "MantidAPI/AnalysisDataService.h"
#include "MantidAPI/ITableWorkspace.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/TableRow.h"
#include "MantidKernel/Property.h"
#include "MantidKernel/StringTokenizer.h"
#include "MantidQtWidgets/Common/PythonRunner.h"
#include <cctype>
#include <Poco/File.h>
#include <QThread>
using namespace Mantid::API;
using namespace MantidQt::CustomInterfaces;

namespace MantidQt {
namespace CustomInterfaces {

namespace {
Mantid::Kernel::Logger g_log("EngineeringDiffractionGUI");
}

const std::string EnggDiffractionPresenter::g_runNumberErrorStr =
    " cannot be empty, must be an integer number, valid ENGINX run number/s "
    "or "
    "valid directory/directories.";
// discouraged at the moment
const bool EnggDiffractionPresenter::g_askUserCalibFilename = false;
const std::string EnggDiffractionPresenter::g_calibBanksParms =
    "engggui_calibration_banks_parameters";

int EnggDiffractionPresenter::g_croppedCounter = 0;
int EnggDiffractionPresenter::g_plottingCounter = 0;
bool EnggDiffractionPresenter::g_abortThread = false;
std::string EnggDiffractionPresenter::g_lastValidRun = "";
std::string EnggDiffractionPresenter::g_calibCropIdentifier = "SpectrumNumbers";
std::string EnggDiffractionPresenter::g_sumOfFilesFocus = "";
EnggDiffractionPresenter::EnggDiffractionPresenter(IEnggDiffractionView *view)
    : m_workerThread(nullptr), m_calibFinishedOK(false),
      m_focusFinishedOK(false), m_rebinningFinishedOK(false), m_view(view),
      m_viewHasClosed(false),
      m_vanadiumCorrectionsModel(
          boost::make_shared<EnggVanadiumCorrectionsModel>(
              m_view->currentCalibSettings(), m_view->currentInstrument())) {
  if (!m_view) {
    throw std::runtime_error(
        "Severe inconsistency found. Presenter created "
        "with an empty/null view (Engineering diffraction interface). "

  m_currentInst = m_view->currentInstrument();
}

EnggDiffractionPresenter::~EnggDiffractionPresenter() { cleanup(); }

/**
 * Close open sessions, kill threads etc., save settings, etc. for a
 * graceful window close/destruction
 */
void EnggDiffractionPresenter::cleanup() {
  // m_model->cleanup();

  // this may still be running
  if (m_workerThread) {
    if (m_workerThread->isRunning()) {
      g_log.notice() << "A calibration process is currently running, shutting "
                        "it down immediately...\n";
      m_workerThread->wait(10);
    }
    delete m_workerThread;

  // Remove the workspace which is loaded when the interface starts
  auto &ADS = Mantid::API::AnalysisDataService::Instance();
  if (ADS.doesExist(g_calibBanksParms)) {
    ADS.remove(g_calibBanksParms);
}

void EnggDiffractionPresenter::notify(
    IEnggDiffractionPresenter::Notification notif) {

  // Check the view is valid - QT can send multiple notification
  // signals in any order at any time. This means that it is possible
  // to receive a shutdown signal and subsequently an input example
  // for example. As we can't guarantee the state of the viewer
  // after calling shutdown instead we shouldn't do anything after
  if (m_viewHasClosed) {
    return;
  }

  switch (notif) {

  case IEnggDiffractionPresenter::Start:
    processStart();
    break;

  case IEnggDiffractionPresenter::LoadExistingCalib:
    processLoadExistingCalib();
    break;

  case IEnggDiffractionPresenter::CalcCalib:
    processCalcCalib();
    break;

  case IEnggDiffractionPresenter::CropCalib:
  case IEnggDiffractionPresenter::FocusRun:
    processFocusBasic();
    break;

  case IEnggDiffractionPresenter::FocusCropped:
    processFocusCropped();
    break;

  case IEnggDiffractionPresenter::FocusTexture:
    processFocusTexture();
    break;

  case IEnggDiffractionPresenter::ResetFocus:
    processResetFocus();
  case IEnggDiffractionPresenter::RebinTime:
    processRebinTime();
    break;

  case IEnggDiffractionPresenter::RebinMultiperiod:
    processRebinMultiperiod();
    break;

  case IEnggDiffractionPresenter::LogMsg:
    processLogMsg();
    break;

  case IEnggDiffractionPresenter::InstrumentChange:
    processInstChange();
  case IEnggDiffractionPresenter::RBNumberChange:
    processRBNumberChange();
    break;

  case IEnggDiffractionPresenter::ShutDown:
    processShutDown();
    break;

  case IEnggDiffractionPresenter::StopFocus:
    processStopFocus();
    break;
Martyn Gigg's avatar
Martyn Gigg committed
void EnggDiffractionPresenter::processStart() { m_view->showStatus("Ready"); }

void EnggDiffractionPresenter::processLoadExistingCalib() {
  const std::string fname = m_view->askExistingCalibFilename();
  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) {
David Fairbrother's avatar
David Fairbrother committed
  Poco::Path pocoPath;
  const bool pathValid = pocoPath.tryParse(fname);
  if (!pathValid || fname.empty() || pocoPath.isDirectory()) {
    // Log that we couldn't open the file - its probably and invalid
    // path which will be regenerated
    g_log.warning("Could not open GSAS calibration file: " + fname);
  try {
    parseCalibrateFilename(fname, instName, vanNo, ceriaNo);
  } catch (std::invalid_argument &ia) {
    m_view->userWarning("Invalid calibration filename : " + fname, ia.what());
    return;
  }
    grabCalibParms(fname, vanNo, ceriaNo);
    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
 * @param vanNo output param to hold the vanadium run
 * @param ceriaNo output param to hold the ceria run
Sam Jenkins's avatar
Sam Jenkins committed
void EnggDiffractionPresenter::grabCalibParms(const std::string &fname,
                                              std::string &vanNo,
                                              std::string &ceriaNo) {
  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()
                << "Error when trying to extract parameters from this line:  "
                << line
                << ". This calibration file may not load correctly. "
                   "Error details: "
                << rexc.what() << '\n';
          }
        } 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\n";
Sam Jenkins's avatar
Sam Jenkins committed
      } else if (line.find("CALIB") != std::string::npos) {
        Mantid::Kernel::StringTokenizer tokenizer(line, " ", opts);
        ceriaNo = tokenizer[2];
        vanNo = tokenizer[3];
    g_log.error()
        << "Error while loading calibration / GSAS IPARM file (" << fname
        << "). Could not parse the file. Please check its contents. Details: "
        << rexc.what() << '\n';
  }

  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() {
  const std::string vanNo = isValidRunNumber(m_view->newVanadiumNo());
  const std::string ceriaNo = isValidRunNumber(m_view->newCeriaNo());
  try {
    inputChecksBeforeCalibrate(vanNo, ceriaNo);
  } catch (std::invalid_argument &ia) {
    m_view->userWarning("Error in the inputs required for calibrate",
                        ia.what());
  g_log.notice() << "EnggDiffraction GUI: starting new calibration. This may "
                    "take a few seconds... \n";

  const std::string outFilename = outputCalibFilename(vanNo, ceriaNo);

  m_view->showStatus("Calculating calibration...");
  m_view->enableCalibrateFocusFitUserActions(false);
  // alternatively, this would be GUI-blocking:
  // doNewCalibration(outFilename, vanNo, ceriaNo, "");
  // calibrationFinished();
  startAsyncCalibWorker(outFilename, vanNo, ceriaNo, "");
void EnggDiffractionPresenter::ProcessCropCalib() {
  const std::string vanNo = isValidRunNumber(m_view->newVanadiumNo());
  const std::string ceriaNo = isValidRunNumber(m_view->newCeriaNo());
  int specNoNum = m_view->currentCropCalibBankName();
  enum BankMode { SPECNOS = 0, NORTH = 1, SOUTH = 2 };
    inputChecksBeforeCalibrate(vanNo, ceriaNo);
    if (m_view->currentCalibSpecNos().empty() &&
        specNoNum == BankMode::SPECNOS) {
      throw std::invalid_argument(
          "The Spectrum Numbers field cannot be empty, must be a "
          "valid range or a Bank Name can be selected instead.");
  } catch (std::invalid_argument &ia) {
    m_view->userWarning("Error in the inputs required for cropped calibration",
  g_log.notice()
      << "EnggDiffraction GUI: starting cropped calibration. This may "
         "take a few seconds... \n";

  const std::string outFilename = outputCalibFilename(vanNo, ceriaNo);

  std::string specNo = "";
  if (specNoNum == BankMode::NORTH) {
    specNo = "North";
    g_calibCropIdentifier = "Bank";
  } else if (specNoNum == BankMode::SOUTH) {
    specNo = "South";
    g_calibCropIdentifier = "Bank";
  } else if (specNoNum == BankMode::SPECNOS) {
    g_calibCropIdentifier = "SpectrumNumbers";
    specNo = m_view->currentCalibSpecNos();
  m_view->showStatus("Calculating cropped calibration...");
  m_view->enableCalibrateFocusFitUserActions(false);
  startAsyncCalibWorker(outFilename, vanNo, ceriaNo, specNo);
void EnggDiffractionPresenter::processFocusBasic() {
  std::vector<std::string> multi_RunNo =
      isValidMultiRunNumber(m_view->focusingRunNo());
  const std::vector<bool> banks = m_view->focusingBanks();

  g_abortThread = false;
  // check if valid run number provided before focusin
  try {
    inputChecksBeforeFocusBasic(multi_RunNo, banks);
  } catch (std::invalid_argument &ia) {
    m_view->userWarning("Error in the inputs required to focus a run",
                        ia.what());
    return;
  int focusMode = m_view->currentMultiRunMode();
  if (focusMode == 0) {
Hahn, Steven's avatar
Hahn, Steven committed
    g_log.debug() << " focus mode selected Individual Run Files Separately \n";
    startFocusing(multi_RunNo, banks, "", "");
  } else if (focusMode == 1) {
    g_log.debug() << " focus mode selected Focus Sum Of Files \n";
    std::vector<std::string> firstRun;
    firstRun.push_back(multi_RunNo[0]);

    // to avoid multiple loops, use firstRun instead as the
    // multi-run number is not required for sumOfFiles
    startFocusing(firstRun, banks, "", "");
}

void EnggDiffractionPresenter::processFocusCropped() {
  const std::vector<std::string> multi_RunNo =
      isValidMultiRunNumber(m_view->focusingCroppedRunNo());
  const std::vector<bool> banks = m_view->focusingBanks();
  const std::string specNos = m_view->focusingCroppedSpectrumNos();
  g_abortThread = false;
  // check if valid run number provided before focusin
  try {
    inputChecksBeforeFocusCropped(multi_RunNo, banks, specNos);
  } catch (std::invalid_argument &ia) {
    m_view->userWarning(
        "Error in the inputs required to focus a run (in cropped mode)",
        ia.what());
    return;

  int focusMode = m_view->currentMultiRunMode();
  if (focusMode == 0) {
Hahn, Steven's avatar
Hahn, Steven committed
    g_log.debug() << " focus mode selected Individual Run Files Separately \n";
    startFocusing(multi_RunNo, banks, specNos, "");
    g_log.debug() << " focus mode selected Focus Sum Of Files \n";
    g_sumOfFilesFocus = "cropped";
    std::vector<std::string> firstRun{multi_RunNo[0]};

    // to avoid multiple loops, use firstRun instead as the
    // multi-run number is not required for sumOfFiles
    startFocusing(firstRun, banks, specNos, "");
  }
}

void EnggDiffractionPresenter::processFocusTexture() {
  const std::vector<std::string> multi_RunNo =
      isValidMultiRunNumber(m_view->focusingTextureRunNo());
  const std::string dgFile = m_view->focusingTextureGroupingFile();
  g_abortThread = false;
  // check if valid run number provided before focusing
  try {
    inputChecksBeforeFocusTexture(multi_RunNo, dgFile);
  } catch (std::invalid_argument &ia) {
    m_view->userWarning(
        "Error in the inputs required to focus a run (in texture mode)",
        ia.what());
    return;

  int focusMode = m_view->currentMultiRunMode();
  if (focusMode == 0) {
Hahn, Steven's avatar
Hahn, Steven committed
    g_log.debug() << " focus mode selected Individual Run Files Separately \n";
    startFocusing(multi_RunNo, std::vector<bool>(), "", dgFile);
    g_log.debug() << " focus mode selected Focus Sum Of Files \n";
    g_sumOfFilesFocus = "texture";
    std::vector<std::string> firstRun{multi_RunNo[0]};

    // to avoid multiple loops, use firstRun instead as the
    // multi-run number is not required for sumOfFiles
    startFocusing(firstRun, std::vector<bool>(), "", dgFile);
 * Starts a focusing worker, for different modes depending on the
 * inputs provided. Assumes that the inputs have been checked by the
 * respective specific processFocus methods (for normal, cropped,
 * texture, etc. focusing).
 *
 * @param multi_RunNo vector of run/file number to focus
 * @param banks banks to include in the focusing, processed one at a time
 *
 * @param specNos list of spectra to use when focusing. If not empty
 * this implies focusing in cropped mode.
 *
 * @param dgFile detector grouping file to define banks (texture). If
 * not empty, this implies focusing in texture mode.
 */
void EnggDiffractionPresenter::startFocusing(
Shahroz Ahmed's avatar
Shahroz Ahmed committed
    const std::vector<std::string> &multi_RunNo, const std::vector<bool> &banks,
    const std::string &specNos, const std::string &dgFile) {
    optMsg = " (cropped)";
  } else if (!dgFile.empty()) {
    optMsg = " (texture)";
  }
  g_log.notice() << "EnggDiffraction GUI: starting new focusing" << optMsg
                 << ". This may take some seconds... \n";
  m_view->enableCalibrateFocusFitUserActions(false);
  startAsyncFocusWorker(multi_RunNo, banks, specNos, dgFile);
void EnggDiffractionPresenter::processResetFocus() { m_view->resetFocus(); }

void EnggDiffractionPresenter::processRebinTime() {
  const std::string runNo = isValidRunNumber(m_view->currentPreprocRunNo());
  double bin = m_view->rebinningTimeBin();

  try {
    inputChecksBeforeRebinTime(runNo, bin);
  } catch (std::invalid_argument &ia) {
    m_view->userWarning(
        "Error in the inputs required to pre-process (rebin) a run", ia.what());
    return;
  }

  const std::string outWSName = "engggui_preproc_time_ws";
  g_log.notice() << "EnggDiffraction GUI: starting new pre-processing "
                    "(re-binning) with a TOF bin into workspace '" +
                        outWSName +
                        "'. This "
                        "may take some seconds... \n";
  m_view->showStatus("Rebinning by time...");
  m_view->enableCalibrateFocusFitUserActions(false);
  startAsyncRebinningTimeWorker(runNo, bin, outWSName);
}

void EnggDiffractionPresenter::processRebinMultiperiod() {
  const std::string runNo = isValidRunNumber(m_view->currentPreprocRunNo());
  size_t nperiods = m_view->rebinningPulsesNumberPeriods();
  double timeStep = m_view->rebinningPulsesTime();

  try {
    inputChecksBeforeRebinPulses(runNo, nperiods, timeStep);
  } catch (std::invalid_argument &ia) {
    m_view->userWarning("Error in the inputs required to pre-process (rebin) a "
                        "run by pulse times",
                        ia.what());
    return;
  }
  const std::string outWSName = "engggui_preproc_by_pulse_time_ws";
  g_log.notice() << "EnggDiffraction GUI: starting new pre-processing "
                    "(re-binning) by pulse times into workspace '" +
                        outWSName +
                        "'. This "
                        "may take some seconds... \n";
  m_view->showStatus("Rebinning by pulses...");
  m_view->enableCalibrateFocusFitUserActions(false);
  startAsyncRebinningPulsesWorker(runNo, nperiods, timeStep, outWSName);
void EnggDiffractionPresenter::processLogMsg() {
  std::vector<std::string> msgs = m_view->logMsgs();
Tom Titcombe's avatar
Tom Titcombe committed
  for (const auto &msg : msgs) {
    g_log.information() << msg << '\n';
  }
}

void EnggDiffractionPresenter::processInstChange() {
  m_currentInst = m_view->currentInstrument();
  m_view->updateTabsInstrument(m_currentInst);
void EnggDiffractionPresenter::processRBNumberChange() {
  const std::string rbn = m_view->getRBNumber();
  auto valid = validateRBNumber(rbn);
  m_view->enableTabs(valid);
  m_view->showInvalidRBNumber(valid);
  if (!valid) {
    m_view->showStatus("Valid RB number required");
  } else {
    m_view->showStatus("Ready");
  }
void EnggDiffractionPresenter::processShutDown() {
  // Set that the view has closed in case QT attempts to fire another
  // signal whilst we are shutting down. This stops notify before
  // it hits the switch statement as the view could be in any state.
  m_viewHasClosed = true;
void EnggDiffractionPresenter::processStopFocus() {
  if (m_workerThread) {
    if (m_workerThread->isRunning()) {
      g_log.notice() << "A focus process is currently running, shutting "
                        "it down as soon as possible...\n";
      g_abortThread = true;
      g_log.warning() << "Focus Stop has been clicked, please wait until "
Hahn, Steven's avatar
Hahn, Steven committed
                         "current focus run process has been completed. \n";
 * Check if an RB number is valid to work with it (retrieve data,
 * calibrate, focus, etc.). For now this will accept any non-empty
 * string. Later on we might be more strict about valid RB numbers /
 * experiment IDs.
 *
 * @param rbn RB number as given by the user
 */
bool EnggDiffractionPresenter::validateRBNumber(const std::string &rbn) const {
  return !rbn.empty();
}

 * Checks if the provided run number is valid and if a directory is
 * provided it will convert it to a run number. It also records the
 * paths the user has browsed to.
 *
 * @param userPaths the input/directory given by the user via the "browse"
 * button
 *
 * @return run_number 6 character string of a run number
 */
std::string EnggDiffractionPresenter::isValidRunNumber(
    const std::vector<std::string> &userPaths) {
  if (userPaths.empty() || userPaths.front().empty()) {
  return userPaths[0];
 * Checks if the provided run number is valid and if a directory is provided
 *
 * @param paths takes the input/paths of the user
 *
 * @return vector of string multi_run_number, 6 character string of a run number
 */
std::vector<std::string> EnggDiffractionPresenter::isValidMultiRunNumber(
    const std::vector<std::string> &paths) {
  std::vector<std::string> multi_run_number;
  if (paths.empty() || paths.front().empty())
Sam Jenkins's avatar
Sam Jenkins committed
  return paths;
 * Does several checks on the current inputs and settings. This should
 * be done before starting any calibration work. The message return
 * should in principle be shown to the user as a visible message
 * (pop-up, error log, etc.)
 *
 * @param newVanNo number of the Vanadium run for the new calibration
 * @param newCeriaNo number of the Ceria run for the new calibration
 *
 * @throws std::invalid_argument with an informative message.
 */
void EnggDiffractionPresenter::inputChecksBeforeCalibrate(
    const std::string &newVanNo, const std::string &newCeriaNo) {
  if (newVanNo.empty()) {
    throw std::invalid_argument("The Vanadium number" + g_runNumberErrorStr);
    throw std::invalid_argument("The Ceria number" + g_runNumberErrorStr);
  const auto &cs = m_view->currentCalibSettings();
  const auto &pixelCalib = cs.m_pixelCalibFilename;
        "You need to set a pixel (full) calibration in settings.");
  }
  const auto &templGSAS = cs.m_templateGSAS_PRM;
        "You need to set a template calibration file for GSAS in settings.");
  }
 * What should be the name of the output GSAS calibration file, given
 * the Vanadium and Ceria runs
 *
 * @param vanNo number of the Vanadium run, which is normally part of the name
 * @param ceriaNo number of the Ceria run, which is normally part of the name
 * @param bankName bank name when generating a file for an individual
 * bank. Leave empty to use a generic name for all banks
 *
 * @return filename (without the full path)
 */
std::string
EnggDiffractionPresenter::outputCalibFilename(const std::string &vanNo,
                                              const std::string &ceriaNo,
                                              const std::string &bankName) {
  const std::string sugg =
      buildCalibrateSuggestedFilename(vanNo, ceriaNo, bankName);
  if (!g_askUserCalibFilename) {
    outFilename = sugg;
  } else {
    outFilename = m_view->askNewCalibrationFilename(sugg);
    if (!outFilename.empty()) {
      // make sure it follows the rules
      try {
        std::string inst, van, ceria;
        parseCalibrateFilename(outFilename, inst, van, ceria);
      } catch (std::invalid_argument &ia) {
        m_view->userWarning(
            "Invalid output calibration filename: " + outFilename, ia.what());
        outFilename = "";
      }
 * Parses the name of a calibration file and guesses the instrument,
 * vanadium and ceria run numbers, assuming that the name has been
 * build with buildCalibrateSuggestedFilename().
 *
 * @todo this is currently based on the filename. This method should
 * do a basic sanity check that the file is actually a calibration
 * file (IPARM file for GSAS), and get the numbers from the file not
 * from the name of the file. Leaving this as a TODO for now, as the
 * file template and contents are still subject to change.
 *
 * @param path full path to a calibration file
 * @param instName instrument used in the file
 * @param vanNo number of the Vanadium run used for this calibration file
 * @param ceriaNo number of the Ceria run
 *
 * @throws invalid_argument with an informative message if tha name does
 * not look correct (does not follow the conventions).
 */
void EnggDiffractionPresenter::parseCalibrateFilename(const std::string &path,
                                                      std::string &instName,
                                                      std::string &vanNo,
                                                      std::string &ceriaNo) {
  instName = "";
  vanNo = "";
  ceriaNo = "";

  Poco::Path fullPath(path);
  const std::string &filename = fullPath.getFileName();
  const std::string explMsg =
      "Expected a file name like 'INSTR_vanNo_ceriaNo_....par', "
      "where INSTR is the instrument name and vanNo and ceriaNo are the "
      "numbers of the Vanadium and calibration sample (Ceria, CeO2) runs.";
  std::vector<std::string> parts;
  boost::split(parts, filename, boost::is_any_of("_"));
  if (parts.size() < 4) {
    throw std::invalid_argument(
        "Failed to find at least the 4 required parts of the file name.\n\n" +
        explMsg);
  }

  // check the rules on the file name
  if (m_currentInst != parts[0]) {
    throw std::invalid_argument("The first component of the file name is not "
                                "the expected instrument name: " +
                                m_currentInst + ".\n\n" + explMsg);
 * Start the calibration work without blocking the GUI. This uses
 * connect for Qt signals/slots so that it runs well with the Qt event
 * loop. Because of that this class needs to be a Q_OBJECT.
 *
 * @param outFilename name for the output GSAS calibration file
 * @param vanNo vanadium run number
 * @param ceriaNo ceria run number
 * @param specNos SpecNos or bank name to be passed
 */
void EnggDiffractionPresenter::startAsyncCalibWorker(
    const std::string &outFilename, const std::string &vanNo,
    const std::string &ceriaNo, const std::string &specNos) {
  delete m_workerThread;
  m_workerThread = new QThread(this);
  EnggDiffWorker *worker =
      new EnggDiffWorker(this, outFilename, vanNo, ceriaNo, specNos);
  worker->moveToThread(m_workerThread);

  connect(m_workerThread, SIGNAL(started()), worker, SLOT(calibrate()));
  connect(worker, SIGNAL(finished()), this, SLOT(calibrationFinished()));
  // early delete of thread and worker
  connect(m_workerThread, SIGNAL(finished()), m_workerThread,
          SLOT(deleteLater()), Qt::DirectConnection);
  connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
  m_workerThread->start();
}

 * Calculate a new calibration. This is what threads/workers should
 * use to run the calculations in response to the user clicking
 * 'calibrate' or similar.
 *
 * @param outFilename name for the output GSAS calibration file
 * @param vanNo vanadium run number
 * @param ceriaNo ceria run number
 * @param specNos SpecNos or bank name to be passed
 */
void EnggDiffractionPresenter::doNewCalibration(const std::string &outFilename,
                                                const std::string &vanNo,
                                                const std::string &ceriaNo,
                                                const std::string &specNos) {
  g_log.notice() << "Generating new calibration file: " << outFilename << '\n';
  EnggDiffCalibSettings cs = m_view->currentCalibSettings();
  Mantid::Kernel::ConfigServiceImpl &conf =
      Mantid::Kernel::ConfigService::Instance();
  const std::vector<std::string> tmpDirs = conf.getDataSearchDirs();
  // in principle, the run files will be found from 'DirRaw', and the
  // pre-calculated Vanadium corrections from 'DirCalib'
  if (!cs.m_inputDirCalib.empty() && Poco::File(cs.m_inputDirCalib).exists()) {
    conf.appendDataSearchDir(cs.m_inputDirCalib);
  }
  if (!cs.m_inputDirRaw.empty() && Poco::File(cs.m_inputDirRaw).exists())
    conf.appendDataSearchDir(cs.m_inputDirRaw);
  for (const auto &browsed : m_browsedToPaths) {
    conf.appendDataSearchDir(browsed);
  }
    doCalib(cs, vanNo, ceriaNo, outFilename, specNos);
    m_calibFinishedOK = false;
    m_calibError = "The calibration calculations failed. One of the "
                   "algorithms did not execute correctly. See log messages for "
                   "further details.";
    g_log.error()
        << "The calibration calculations failed. One of the "
           "algorithms did not execute correctly. See log messages for "
           "further details. Error: " +
               std::string(rexc.what())
        << '\n';
  } catch (std::invalid_argument &iaexc) {
    m_calibFinishedOK = false;
    m_calibError = "The calibration calculations failed. Some input properties "
Sam Jenkins's avatar
Sam Jenkins committed
                   "were not valid. See log messages for details. \n Error: " +
                   std::string(iaexc.what());
        << "The calibration calculations failed. Some input properties "
           "were not valid. See log messages for details. \n";
  } catch (Mantid::API::Algorithm::CancelException &) {
    m_calibFinishedOK = false;
    m_cancelled = true;
    g_log.error() << "Execution terminated by user. \n";
  // restore normal data search paths
  conf.setDataSearchDirs(tmpDirs);
 * Method to call when the calibration work has finished, either from
 * a separate thread or not (as in this presenter's' test).
 */
void EnggDiffractionPresenter::calibrationFinished() {
  if (!m_view)
    return;

  m_view->enableCalibrateFocusFitUserActions(true);
    if (!m_cancelled) {
      m_view->userWarning("Calibration Error", m_calibError);
    }
    m_cancelled = false;
    m_view->showStatus("Calibration didn't finish succesfully. Ready");
    const std::string vanNo = isValidRunNumber(m_view->newVanadiumNo());

    const std::string ceriaNo = isValidRunNumber(m_view->newCeriaNo());
    m_view->newCalibLoaded(vanNo, ceriaNo, m_calibFullPath);
Hahn, Steven's avatar
Hahn, Steven committed
        << "Calibration finished and ready as 'current calibration'.\n";
    m_view->showStatus("Calibration finished succesfully. Ready");
  }
  if (m_workerThread) {
    delete m_workerThread;
 * Build a suggested name for a new calibration, by appending instrument name,
 * relevant run numbers, etc., like: ENGINX_241391_236516_all_banks.par
 *
 * @param vanNo number of the Vanadium run
 * @param ceriaNo number of the Ceria run
 * @param bankName bank name when generating a file for an individual
 * bank. Leave empty to use a generic name for all banks
 *
 * @return Suggested name for a new calibration file, following
 * ENGIN-X practices
 */
std::string EnggDiffractionPresenter::buildCalibrateSuggestedFilename(
    const std::string &vanNo, const std::string &ceriaNo,
    const std::string &bankName) const {
  // default and only one supported instrument for now
  std::string instStr = m_currentInst;
  std::string nameAppendix = "_all_banks";
  if (!bankName.empty()) {
    nameAppendix = "_" + bankName;
  }
  // default extension for calibration files
  const std::string calibExt = ".prm";
Sam Jenkins's avatar
Sam Jenkins committed
  std::string vanFilename = Poco::Path(vanNo).getBaseName();
  std::string ceriaFilename = Poco::Path(ceriaNo).getBaseName();
  std::string vanRun =
      vanFilename.substr(vanFilename.find(instStr) + instStr.size());
  std::string ceriaRun =
      ceriaFilename.substr(ceriaFilename.find(instStr) + instStr.size());
  vanRun.erase(0, std::min(vanRun.find_first_not_of('0'), vanRun.size() - 1));
  ceriaRun.erase(
      0, std::min(ceriaRun.find_first_not_of('0'), ceriaRun.size() - 1));
Sam Jenkins's avatar
Sam Jenkins committed
      instStr + "_" + vanRun + "_" + ceriaRun + nameAppendix + calibExt;
std::vector<GSASCalibrationParms>
EnggDiffractionPresenter::currentCalibration() const {
  return m_currentCalibParms;
}

 * Calculate a calibration, responding the the "new calibration"
 * action/button.
 *
 * @param cs user settings
 * @param vanNo Vanadium run number
 * @param ceriaNo Ceria run number
 * @param outFilename output filename chosen by the user
 * @param specNos SpecNos or bank name to be passed
 */
void EnggDiffractionPresenter::doCalib(const EnggDiffCalibSettings &cs,
                                       const std::string &vanNo,
                                       const std::string &ceriaNo,
                                       const std::string &outFilename,
                                       const std::string &specNos) {
  if (cs.m_inputDirCalib.empty()) {
    m_calibError =
        "No calibration directory selected. Please select a calibration "
        "directory in Settings. This will be used to "
        "cache Vanadium calibration data";