Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LoadDiffCal.cpp 17.97 KiB
#include "MantidDataHandling/LoadDiffCal.h"

#include "MantidAPI/FileProperty.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/Progress.h"
#include "MantidAPI/TableRow.h"
#include "MantidDataHandling/LoadCalFile.h"
#include "MantidDataObjects/GroupingWorkspace.h"
#include "MantidDataObjects/MaskWorkspace.h"
#include "MantidDataObjects/TableWorkspace.h"
#include "MantidDataObjects/Workspace2D.h"

#include <cmath>
#include <H5Cpp.h>

namespace Mantid {
namespace DataHandling {

using Mantid::API::FileProperty;
using Mantid::API::MatrixWorkspace;
using Mantid::API::MatrixWorkspace_sptr;
using Mantid::API::Progress;
using Mantid::API::ITableWorkspace;
using Mantid::API::ITableWorkspace_sptr;
using Mantid::API::WorkspaceProperty;
using Mantid::DataObjects::GroupingWorkspace_sptr;
using Mantid::DataObjects::MaskWorkspace_sptr;
using Mantid::DataObjects::Workspace2D;
using Mantid::Kernel::Direction;
using Mantid::Kernel::PropertyWithValue;

using namespace H5;

// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(LoadDiffCal)

//----------------------------------------------------------------------------------------------
/** Constructor
 */
LoadDiffCal::LoadDiffCal() {}

//----------------------------------------------------------------------------------------------
/** Destructor
 */
LoadDiffCal::~LoadDiffCal() {}

//----------------------------------------------------------------------------------------------

/// Algorithms name for identification. @see Algorithm::name
const std::string LoadDiffCal::name() const { return "LoadDiffCal"; }

/// Algorithm's version for identification. @see Algorithm::version
int LoadDiffCal::version() const { return 1; }

/// Algorithm's category for identification. @see Algorithm::category
const std::string LoadDiffCal::category() const {
  return "DataHandling\\Instrument;Diffraction\\DataHandling";
}

/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
const std::string LoadDiffCal::summary() const {
  return "Loads a calibration file for powder diffraction";
}

//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
 */
void LoadDiffCal::init() {
  // 3 properties for getting the right instrument
  LoadCalFile::getInstrument3WaysInit(this);

  declareProperty(new FileProperty("Filename", "", FileProperty::Load,
                                   {".h5", ".hd5", ".hdf", ".cal"}),
                  "Path to the .h5 file.");

  declareProperty(new PropertyWithValue<bool>("MakeGroupingWorkspace", true,
                                              Direction::Input),
                  "Set to true to create a GroupingWorkspace with called "
                  "WorkspaceName_group.");

  declareProperty(
      new PropertyWithValue<bool>("MakeCalWorkspace", true, Direction::Input),
      "Set to true to create a CalibrationWorkspace with called "
      "WorkspaceName_cal.");

  declareProperty(
      new PropertyWithValue<bool>("MakeMaskWorkspace", true, Direction::Input),
      "Set to true to create a MaskWorkspace with called WorkspaceName_mask.");

  declareProperty(
      new PropertyWithValue<std::string>("WorkspaceName", "", Direction::Input),
      "The base of the output workspace names. Names will have '_group', "
      "'_cal', '_mask' appended to them.");

  std::string grpName("Calibration Validation");
  declareProperty("TofMin", 0., "Minimum for TOF axis. Defaults to 0.");
  declareProperty("TofMax", EMPTY_DBL(),
                  "Maximum for TOF axis. Defaults to Unused.");
  setPropertyGroup("TofMin", grpName);
  setPropertyGroup("TofMax", grpName);
}

namespace { // anonymous

std::string readString(H5File &file, const std::string &path) {
  try {
    DataSet data = file.openDataSet(path);
    std::string value;
    data.read(value, data.getDataType(), data.getSpace());
    return value;
  } catch (H5::FileIException &e) {
    UNUSED_ARG(e);
    return "";
  } catch (H5::GroupIException &e) {
    UNUSED_ARG(e);
    return "";
  }
}

template <typename NumT>
std::vector<NumT> readArrayCoerce(DataSet &dataset,
                                  const DataType &desiredDataType) {
  std::vector<NumT> result;
  DataType dataType = dataset.getDataType();
  DataSpace dataSpace = dataset.getSpace();

  if (desiredDataType == dataType) {
    result.resize(dataSpace.getSelectNpoints());
    dataset.read(&result[0], dataType, dataSpace);
  } else if (PredType::NATIVE_UINT32 == dataType) {
    std::vector<uint32_t> temp(dataSpace.getSelectNpoints());
    dataset.read(&temp[0], dataType, dataSpace);
    result.assign(temp.begin(), temp.end());
  } else if (PredType::NATIVE_FLOAT == dataType) {
    std::vector<float> temp(dataSpace.getSelectNpoints());
    dataset.read(&temp[0], dataType, dataSpace);
    for (float value : temp)
      result.push_back(static_cast<NumT>(value));
  } else {
    throw DataTypeIException();
  }

  return result;
}

bool endswith(const std::string &str, const std::string &ending) {
  if (ending.size() > str.size()) {
    return false;
  }

  return std::equal(str.begin() + str.size() - ending.size(), str.end(),
                    ending.begin());
}

void setGroupWSProperty(API::Algorithm *alg, const std::string &prefix,
                        GroupingWorkspace_sptr wksp) {
  alg->declareProperty(
      new WorkspaceProperty<DataObjects::GroupingWorkspace>(
          "OutputGroupingWorkspace", prefix + "_group", Direction::Output),
      "Set the the output GroupingWorkspace, if any.");
  alg->setProperty("OutputGroupingWorkspace", wksp);
}

void setMaskWSProperty(API::Algorithm *alg, const std::string &prefix,
                       MaskWorkspace_sptr wksp) {
  alg->declareProperty(
      new WorkspaceProperty<DataObjects::MaskWorkspace>(
          "OutputMaskWorkspace", prefix + "_mask", Direction::Output),
      "Set the the output MaskWorkspace, if any.");
  alg->setProperty("OutputMaskWorkspace", wksp);
}

void setCalWSProperty(API::Algorithm *alg, const std::string &prefix,
                      ITableWorkspace_sptr wksp) {
  alg->declareProperty(
      new WorkspaceProperty<ITableWorkspace>(
          "OutputCalWorkspace", prefix + "_cal", Direction::Output),
      "Set the output Diffraction Calibration workspace, if any.");
  alg->setProperty("OutputCalWorkspace", wksp);
}

} // anonymous namespace

std::vector<double> LoadDiffCal::readDoubleArray(Group &group,
                                                 const std::string &name) {
  std::vector<double> result;

  try {
    DataSet dataset = group.openDataSet(name);
    result = readArrayCoerce<double>(dataset, PredType::NATIVE_DOUBLE);
  } catch (H5::GroupIException &e) {
    UNUSED_ARG(e);
    g_log.information() << "Failed to open dataset \"" << name << "\"\n";
  } catch (H5::DataTypeIException &e) {
    UNUSED_ARG(e);
    g_log.information() << "DataSet \"" << name << "\" should be double"
                        << "\n";
  }

  for (double &value : result) {
    if (std::abs(value) < 1.e-10) {
      value = 0.;
    } else if (value != value) { // check for NaN
      value = 0.;
    }
  }

  return result;
}
std::vector<int32_t> LoadDiffCal::readInt32Array(Group &group,
                                                 const std::string &name) {
  std::vector<int32_t> result;

  try {
    DataSet dataset = group.openDataSet(name);
    result = readArrayCoerce<int32_t>(dataset, PredType::NATIVE_INT32);
  } catch (H5::GroupIException &e) {
    UNUSED_ARG(e);
    g_log.information() << "Failed to open dataset \"" << name << "\"\n";
  } catch (H5::DataTypeIException &e) {
    UNUSED_ARG(e);
    g_log.information() << "DataSet \"" << name << "\" should be int32"
                        << "\n";
  }

  return result;
}

void LoadDiffCal::getInstrument(H5File &file) {
  // don't bother if there isn't a mask or grouping requested
  bool makeMask = getProperty("MakeMaskWorkspace");
  bool makeGrouping = getProperty("MakeGroupingWorkspace");
  if ((!makeMask) & (!makeGrouping))
    return;

  // see if the user specified the instrument independently
  if (LoadCalFile::instrumentIsSpecified(this)) {
    m_instrument = LoadCalFile::getInstrument3Ways(this);
    return;
  }

  std::string idf =
      readString(file, "/calibration/instrument/instrument_source");
  std::string instrumentName = readString(file, "/calibration/instrument/name");

  g_log.debug() << "IDF : " << idf << "\n"
                << "NAME: " << instrumentName << "\n";

  API::Algorithm_sptr childAlg =
      this->createChildAlgorithm("LoadInstrument", 0.0, 0.1);
  MatrixWorkspace_sptr tempWS(new Workspace2D());
  childAlg->setProperty<MatrixWorkspace_sptr>("Workspace", tempWS);
  if (idf.empty()) {
    childAlg->setPropertyValue("InstrumentName", instrumentName);
  } else {
    childAlg->setPropertyValue("Filename", idf);
  }
  childAlg->setProperty("RewriteSpectraMap",
                        Mantid::Kernel::OptionalBool(false));
  childAlg->executeAsChildAlg();

  m_instrument = tempWS->getInstrument();

  g_log.information() << "Loaded instrument \"" << m_instrument->getName()
                      << "\" from \"" << m_instrument->getFilename() << "\"\n";
}

void LoadDiffCal::makeGroupingWorkspace(const std::vector<int32_t> &detids,
                                        const std::vector<int32_t> &groups) {
  bool makeWS = getProperty("MakeGroupingWorkspace");
  if (!makeWS) {
    g_log.information("Not making a GroupingWorkspace");
    return;
  }

  size_t numDet = detids.size();
  Progress progress(this, .4, .6, numDet);

  GroupingWorkspace_sptr wksp =
      boost::make_shared<DataObjects::GroupingWorkspace>(m_instrument);
  wksp->setTitle(m_filename);
  wksp->mutableRun().addProperty("Filename", m_filename);

  for (size_t i = 0; i < numDet; ++i) {
    detid_t detid = static_cast<detid_t>(detids[i]);
    wksp->setValue(detid, groups[i]);
    progress.report();
  }

  setGroupWSProperty(this, m_workspaceName, wksp);
}

void LoadDiffCal::makeMaskWorkspace(const std::vector<int32_t> &detids,
                                    const std::vector<int32_t> &use) {
  bool makeWS = getProperty("MakeMaskWorkspace");
  if (!makeWS) {
    g_log.information("Not making a MaskWorkspace");
    return;
  }

  size_t numDet = detids.size();
  Progress progress(this, .6, .8, numDet);

  MaskWorkspace_sptr wksp =
      boost::make_shared<DataObjects::MaskWorkspace>(m_instrument);
  wksp->setTitle(m_filename);
  wksp->mutableRun().addProperty("Filename", m_filename);

  for (size_t i = 0; i < numDet; ++i) {
    bool shouldUse = (use[i] > 0);
    detid_t detid = static_cast<detid_t>(detids[i]);
    // in maskworkspace 0=use, 1=dontuse
    wksp->setMasked(detid, !shouldUse);
    wksp->setValue(detid, (shouldUse ? 0. : 1.));
    progress.report();
  }

  setMaskWSProperty(this, m_workspaceName, wksp);
}

namespace { // anonymous namespace

double calcTofMin(const double difc, const double difa, const double tzero,
                  const double tofmin) {
  if (difa == 0.) {
    if (tzero != 0.) {
      // check for negative d-spacing
      return std::max<double>(-1. * tzero, tofmin);
    }
  } else if (difa > 0) {
    // check for imaginary part in quadratic equation
    return std::max<double>(tzero - .25 * difc * difc / difa, tofmin);
  }

  // everything else is fine so just return supplied tofmin
  return tofmin;
}

double calcTofMax(const double difc, const double difa, const double tzero,
                  const double tofmax) {
  if (difa < 0.) {
    // check for imaginary part in quadratic equation
    return std::min<double>(tzero - .25 * difc * difc / difa, tofmax);
  }

  // everything else is fine so just return supplied tofmax
  return tofmax;
}
} // end of anonymous namespace

void LoadDiffCal::makeCalWorkspace(const std::vector<int32_t> &detids,
                                   const std::vector<double> &difc,
                                   const std::vector<double> &difa,
                                   const std::vector<double> &tzero,
                                   const std::vector<int32_t> &dasids,
                                   const std::vector<double> &offsets) {
  bool makeWS = getProperty("MakeCalWorkspace");
  if (!makeWS) {
    g_log.information("Not making a calibration workspace");
    return;
  }

  size_t numDet = detids.size();
  Progress progress(this, .8, 1., numDet);

  bool haveDasids = !dasids.empty();
  bool haveOffsets = !offsets.empty();

  double tofMin = getProperty("TofMin");
  double tofMax = getProperty("TofMax");
  bool useTofMax = !isEmpty(tofMax);

  ITableWorkspace_sptr wksp = boost::make_shared<DataObjects::TableWorkspace>();
  wksp->setTitle(m_filename);
  wksp->addColumn("int", "detid");
  wksp->addColumn("double", "difc");
  wksp->addColumn("double", "difa");
  wksp->addColumn("double", "tzero");
  // only add these columns if they have values
  if (haveDasids)
    wksp->addColumn("int", "dasid");
  if (haveOffsets)
    wksp->addColumn("double", "offset");

  // columns for valid range of data
  wksp->addColumn("double", "tofmin");
  if (useTofMax)
    wksp->addColumn("double", "tofmax");

  size_t badCount = 0;
  for (size_t i = 0; i < numDet; ++i) {
    API::TableRow newrow = wksp->appendRow();
    newrow << detids[i];
    newrow << difc[i];
    newrow << difa[i];
    newrow << tzero[i];
    if (haveDasids)
      newrow << dasids[i];
    if (haveOffsets)
      newrow << offsets[i];

    // calculate tof range for information
    const double tofMinRow = calcTofMin(difc[i], difa[i], tzero[i], tofMin);
    std::stringstream msg;
    if (tofMinRow != tofMin) {
      msg << "TofMin shifted from " << tofMin << " to " << tofMinRow << " ";
    }
    newrow << tofMinRow;
    if (useTofMax) {
      const double tofMaxRow = calcTofMax(difc[i], difa[i], tzero[i], tofMax);
      newrow << tofMaxRow;

      if (tofMaxRow != tofMax) {
        msg << "TofMax shifted from " << tofMax << " to " << tofMaxRow;
      }
    }
    if (!msg.str().empty()) {
      badCount += 1;
      std::stringstream longMsg;
      longMsg << "[detid=" << detids[i];
      if (haveDasids)
        longMsg << ", dasid=" << dasids[i];
      longMsg << "] " << msg.str();
      this->g_log.warning(longMsg.str());
    }

    progress.report();
  }
  if (badCount > 0) {
    this->g_log.warning() << badCount
                          << " rows have reduced time-of-flight range\n";
  }

  setCalWSProperty(this, m_workspaceName, wksp);
}

void LoadDiffCal::runLoadCalFile() {
  bool makeCalWS = getProperty("MakeCalWorkspace");
  bool makeMaskWS = getProperty("MakeMaskWorkspace");
  bool makeGroupWS = getProperty("MakeGroupingWorkspace");

  auto alg = createChildAlgorithm("LoadCalFile", 0., 1.);
  alg->setPropertyValue("CalFilename", m_filename);
  alg->setPropertyValue("InputWorkspace", getPropertyValue("InputWorkspace"));
  alg->setPropertyValue("InstrumentName", getPropertyValue("InstrumentName"));
  alg->setPropertyValue("InstrumentFilename",
                        getPropertyValue("InstrumentFilename"));
  alg->setProperty<bool>("MakeOffsetsWorkspace", makeCalWS);
  alg->setProperty<bool>("MakeGroupingWorkspace", makeGroupWS);
  alg->setProperty<bool>("MakeMaskWorkspace", makeMaskWS);
  alg->setPropertyValue("WorkspaceName", m_workspaceName);
  alg->executeAsChildAlg();

  if (makeCalWS) {
    ITableWorkspace_sptr wksp = alg->getProperty("OutputCalWorkspace");
    setCalWSProperty(this, m_workspaceName, wksp);
  }

  if (makeMaskWS) {
    MatrixWorkspace_sptr wksp = alg->getProperty("OutputMaskWorkspace");
    setMaskWSProperty(
        this, m_workspaceName,
        boost::dynamic_pointer_cast<DataObjects::MaskWorkspace>(wksp));
  }

  if (makeGroupWS) {
    GroupingWorkspace_sptr wksp = alg->getProperty("OutputGroupingWorkspace");
    setGroupWSProperty(this, m_workspaceName, wksp);
  }
}

//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
 */
void LoadDiffCal::exec() {
  m_filename = getPropertyValue("Filename");
  m_workspaceName = getPropertyValue("WorkspaceName");

  if (endswith(m_filename, ".cal")) {
    runLoadCalFile();
    return;
  }

  // read in everything from the file
  H5File file(m_filename, H5F_ACC_RDONLY);
  H5::Exception::dontPrint();
  getInstrument(file);
  Progress progress(this, 0.1, 0.4, 8);
  Group calibrationGroup;
  try {
    calibrationGroup = file.openGroup("calibration");
  } catch (FileIException &e) {
    e.printError();
    throw std::runtime_error("Did not find group \"/calibration\"");
  }

  progress.report("Reading detid");
  std::vector<int32_t> detids = readInt32Array(calibrationGroup, "detid");
  progress.report("Reading dasid");
  std::vector<int32_t> dasids = readInt32Array(calibrationGroup, "dasid");
  progress.report("Reading group");
  std::vector<int32_t> groups = readInt32Array(calibrationGroup, "group");
  progress.report("Reading use");
  std::vector<int32_t> use = readInt32Array(calibrationGroup, "use");

  progress.report("Reading difc");
  std::vector<double> difc = readDoubleArray(calibrationGroup, "difc");
  progress.report("Reading difa");
  std::vector<double> difa = readDoubleArray(calibrationGroup, "difa");
  progress.report("Reading tzero");
  std::vector<double> tzero = readDoubleArray(calibrationGroup, "tzero");
  progress.report("Reading offset");
  std::vector<double> offset = readDoubleArray(calibrationGroup, "offset");

  file.close();

  // verify the minimum is present
  if (detids.empty()) {
    throw std::runtime_error(
        "File was missing required field \"/calibraion/detid\"");
  }
  if (difc.empty()) {
    throw std::runtime_error(
        "File was missing required field \"/calibraion/difc\"");
  }

  // fix up empty arrays
  if (groups.empty())
    groups.assign(detids.size(), 1); // all go to one spectrum
  if (use.empty())
    use.assign(detids.size(), 1); // use everything
  if (difa.empty())
    difa.assign(detids.size(), 0.); // turn off difa
  if (tzero.empty())
    tzero.assign(detids.size(), 0.); // turn off tzero

  // create the appropriate output workspaces
  makeGroupingWorkspace(detids, groups);
  makeMaskWorkspace(detids, use);
  makeCalWorkspace(detids, difc, difa, tzero, dasids, offset);
}

} // namespace DataHandling
} // namespace Mantid