Exemple #1
0
/**
 * Calculates binning parameters.
 */
void Iqt::calculateBinning() {
  using namespace Mantid::API;

  disconnect(m_dblManager, SIGNAL(valueChanged(QtProperty *, double)), this,
             SLOT(updatePropertyValues(QtProperty *, double)));

  QString wsName = m_uiForm.dsInput->getCurrentDataName();
  QString resName = m_uiForm.dsResolution->getCurrentDataName();
  if (wsName.isEmpty() || resName.isEmpty())
    return;

  double energyMin = m_dblManager->value(m_properties["ELow"]);
  double energyMax = m_dblManager->value(m_properties["EHigh"]);
  double numBins = m_dblManager->value(m_properties["SampleBinning"]);
  if (numBins == 0)
    return;

  IAlgorithm_sptr furyAlg =
      AlgorithmManager::Instance().create("TransformToIqt");
  furyAlg->initialize();

  furyAlg->setProperty("SampleWorkspace", wsName.toStdString());
  furyAlg->setProperty("ResolutionWorkspace", resName.toStdString());
  furyAlg->setProperty("ParameterWorkspace", "__FuryProperties_temp");

  furyAlg->setProperty("EnergyMin", energyMin);
  furyAlg->setProperty("EnergyMax", energyMax);
  furyAlg->setProperty("BinReductionFactor", numBins);

  furyAlg->setProperty("DryRun", true);

  furyAlg->execute();

  // Get property table from algorithm
  ITableWorkspace_sptr propsTable =
      AnalysisDataService::Instance().retrieveWS<ITableWorkspace>(
          "__FuryProperties_temp");

  // Get data from property table
  double energyWidth = propsTable->getColumn("EnergyWidth")->cell<float>(0);
  int sampleBins = propsTable->getColumn("SampleOutputBins")->cell<int>(0);
  int resolutionBins = propsTable->getColumn("ResolutionBins")->cell<int>(0);

  // Update data in property editor
  m_dblManager->setValue(m_properties["EWidth"], energyWidth);
  m_dblManager->setValue(m_properties["ResolutionBins"], resolutionBins);
  m_dblManager->setValue(m_properties["SampleBins"], sampleBins);

  connect(m_dblManager, SIGNAL(valueChanged(QtProperty *, double)), this,
          SLOT(updatePropertyValues(QtProperty *, double)));

  // Warn for low number of resolution bins
  int numResolutionBins =
      static_cast<int>(m_dblManager->value(m_properties["ResolutionBins"]));
  if (numResolutionBins < 5)
    showMessageBox("Number of resolution bins is less than 5.\nResults may be "
                   "inaccurate.");
}
/**
 * Creates a domain from an ITableWorkspace
 *
 * This method creates a LatticeDomain from a table workspace that contains two
 * columns, HKL and d. HKL can either be a V3D-column or a string column,
 * containing three integers separated by space, comma, semi-colon and
 * optionally surrounded by []. The d-column can be double or a string that can
 * be parsed as a double number.
 *
 * @param workspace :: ITableWorkspace with format specified above.
 * @param domain :: Pointer to outgoing FunctionDomain instance.
 * @param values :: Pointer to outgoing FunctionValues object.
 * @param i0 :: Size offset for values object if it already contains data.
 */
void LatticeDomainCreator::createDomainFromPeakTable(
    const ITableWorkspace_sptr &workspace,
    boost::shared_ptr<FunctionDomain> &domain,
    boost::shared_ptr<FunctionValues> &values, size_t i0) {

  size_t peakCount = workspace->rowCount();

  if (peakCount < 1) {
    throw std::range_error("Cannot create a domain for 0 peaks.");
  }

  try {
    Column_const_sptr hklColumn = workspace->getColumn("HKL");
    Column_const_sptr dColumn = workspace->getColumn("d");

    std::vector<V3D> hkls;
    hkls.reserve(peakCount);

    std::vector<double> dSpacings;
    dSpacings.reserve(peakCount);

    V3DFromHKLColumnExtractor extractor;

    for (size_t i = 0; i < peakCount; ++i) {
      try {
        V3D hkl = extractor(hklColumn, i);

        if (hkl != V3D(0, 0, 0)) {
          hkls.push_back(hkl);

          double d = (*dColumn)[i];
          dSpacings.push_back(d);
        }
      } catch (const std::bad_alloc &) {
        // do nothing.
      }
    }

    domain = boost::make_shared<LatticeDomain>(hkls);
    if (!values) {
      values = boost::make_shared<FunctionValues>(*domain);
    } else {
      values->expand(i0 + domain->size());
    }

    values->setFitData(dSpacings);

    values->setFitWeights(1.0);
  } catch (const std::runtime_error &) {
    // Column does not exist
    throw std::runtime_error("Can not process table, the following columns are "
                             "required: HKL, d.");
  }
}
/**
 * Removes error columns from the table if all errors are zero,
 * as these columns correspond to fixed parameters.
 * @param table :: [input, output] Pointer to TableWorkspace to edit
 */
void MuonAnalysisResultTableCreator::removeFixedParameterErrors(
    const ITableWorkspace_sptr table) const {
  assert(table);
  const size_t nRows = table->rowCount();
  const auto colNames = table->getColumnNames();
  std::vector<std::string> zeroErrorColumns;

  for (const auto &name : colNames) {
    // if name does not end with "Error", continue
    const size_t nameLength = name.length();
    if (nameLength < ERROR_LENGTH ||
        name.compare(nameLength - ERROR_LENGTH, ERROR_LENGTH, ERROR_STRING)) {
      continue;
    }

    auto col = table->getColumn(name);
    bool allZeros = true;
    // Check if all values in the column are zero
    for (size_t iRow = 0; iRow < nRows; ++iRow) {
      const double val = col->toDouble(iRow);
      if (std::abs(val) > std::numeric_limits<double>::epsilon()) {
        allZeros = false;
        break;
      }
    }
    if (allZeros) {
      zeroErrorColumns.push_back(name);
    }
  }

  for (const auto &name : zeroErrorColumns) {
    table->removeColumn(name);
  }
}
void ConvertTableToMatrixWorkspace::exec()
{
  ITableWorkspace_sptr inputWorkspace = getProperty("InputWorkspace");
  std::string columnX = getProperty("ColumnX");
  std::string columnY = getProperty("ColumnY");
  std::string columnE = getProperty("ColumnE");

  size_t nrows = inputWorkspace->rowCount();
  std::vector<double> X(nrows);
  std::vector<double> Y(nrows);
  std::vector<double> E(nrows);

  inputWorkspace->getColumn(columnX)->numeric_fill(X);
  inputWorkspace->getColumn(columnY)->numeric_fill(Y);

  if (!columnE.empty())
  {
    inputWorkspace->getColumn(columnE)->numeric_fill(E);
  }
  else
  {
    E.assign(X.size(),1.0);
  }

  MatrixWorkspace_sptr outputWorkspace = WorkspaceFactory::Instance().create("Workspace2D",1,X.size(),X.size());
  outputWorkspace->dataX(0).assign(X.begin(),X.end());
  outputWorkspace->dataY(0).assign(Y.begin(),Y.end());
  outputWorkspace->dataE(0).assign(E.begin(),E.end());

  outputWorkspace->generateSpectraMap();
  boost::shared_ptr<Kernel::Units::Label> labelX = boost::dynamic_pointer_cast<Kernel::Units::Label>(
    Kernel::UnitFactory::Instance().create("Label")
    );
  labelX->setLabel(columnX);
  outputWorkspace->getAxis(0)->unit() = labelX;

  outputWorkspace->setYUnitLabel(columnY);

  setProperty("OutputWorkspace", outputWorkspace);

}
/**
 * Handles completion of the preview algorithm.
 *
 * @param error If the algorithm failed
 */
void IndirectSymmetrise::previewAlgDone(bool error) {
  if (error)
    return;

  QString workspaceName = m_uiForm.dsInput->getCurrentDataName();
  int spectrumNumber =
      static_cast<int>(m_dblManager->value(m_properties["PreviewSpec"]));

  MatrixWorkspace_sptr sampleWS =
      AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
          workspaceName.toStdString());
  ITableWorkspace_sptr propsTable =
      AnalysisDataService::Instance().retrieveWS<ITableWorkspace>(
          "__SymmetriseProps_temp");
  MatrixWorkspace_sptr symmWS =
      AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
          "__Symmetrise_temp");

  // Get the index of XCut on each side of zero
  int negativeIndex = propsTable->getColumn("NegativeXMinIndex")->cell<int>(0);
  int positiveIndex = propsTable->getColumn("PositiveXMinIndex")->cell<int>(0);

  // Get the Y values for each XCut and the difference between them
  double negativeY = sampleWS->dataY(0)[negativeIndex];
  double positiveY = sampleWS->dataY(0)[positiveIndex];
  double deltaY = fabs(negativeY - positiveY);

  // Show values in property tree
  m_dblManager->setValue(m_properties["NegativeYValue"], negativeY);
  m_dblManager->setValue(m_properties["PositiveYValue"], positiveY);
  m_dblManager->setValue(m_properties["DeltaY"], deltaY);

  // Set indicator positions
  m_uiForm.ppRawPlot->getRangeSelector("NegativeEMinYPos")
      ->setMinimum(negativeY);
  m_uiForm.ppRawPlot->getRangeSelector("PositiveEMinYPos")
      ->setMinimum(positiveY);

  // Plot preview plot
  size_t spectrumIndex = symmWS->getIndexFromSpectrumNumber(spectrumNumber);
  m_uiForm.ppPreviewPlot->clear();
  m_uiForm.ppPreviewPlot->addSpectrum("Symmetrised", "__Symmetrise_temp",
                                      spectrumIndex);

  // Don't want this to trigger when the algorithm is run for all spectra
  disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
             SLOT(previewAlgDone(bool)));
}
/**
 * Write log and parameter values to the table for the case of a single fit.
 * @param table :: [input, output] Table to write to
 * @param paramsByLabel :: [input] Map of <label name, <workspace name,
 * <parameter, value>>>
 * @param paramsToDisplay :: [input] List of parameters to display in table
 */
void MuonAnalysisResultTableCreator::writeDataForSingleFit(
    ITableWorkspace_sptr &table,
    const QMap<QString, WSParameterList> &paramsByLabel,
    const QStringList &paramsToDisplay) const {
  assert(!m_multiple);
  assert(m_logValues);

  for (const auto &wsName : m_items) {
    Mantid::API::TableRow row = table->appendRow();
    row << wsName.toStdString();

    // Get log values for this row
    const auto &logValues = m_logValues->value(wsName);

    // Write log values in each column
    for (int i = 0; i < m_logs.size(); ++i) {
      Mantid::API::Column_sptr col = table->getColumn(i);
      const auto &log = m_logs[i];
      const QVariant &val = logValues[log];
      QString valueToWrite;
      // Special case: if log is time in sec, subtract the first start time
      if (log.endsWith(" (s)")) {
        auto seconds =
            val.toDouble() - static_cast<double>(m_firstStart_ns) * 1.e-9;
        valueToWrite = QString::number(seconds);
      } else if (MuonAnalysisHelper::isNumber(val.toString()) &&
                 !log.endsWith(" (text)")) {
        valueToWrite = QString::number(val.toDouble());
      } else {
        valueToWrite = val.toString();
      }

      if (MuonAnalysisHelper::isNumber(val.toString()) &&
          !log.endsWith(" (text)")) {
        row << valueToWrite.toDouble();
      } else {
        row << valueToWrite.toStdString();
      }
    }

    // Add param values (params same for all workspaces)
    QMap<QString, double> paramsList = paramsByLabel.begin()->value(wsName);
    for (const auto &paramName : paramsToDisplay) {
      row << paramsList[paramName];
    }
  }
}
Exemple #7
0
/**
 * Sets a new preview spectrum for the mini plot.
 *
 * @param value workspace index
 */
void ResNorm::previewSpecChanged(int value) {
  m_previewSpec = value;

  // Update vanadium plot
  if (m_uiForm.dsVanadium->isValid())
    m_uiForm.ppPlot->addSpectrum(
        "Vanadium", m_uiForm.dsVanadium->getCurrentDataName(), m_previewSpec);

  // Update fit plot
  std::string fitWsGroupName(m_pythonExportWsName + "_Fit_Workspaces");
  std::string fitParamsName(m_pythonExportWsName + "_Fit");
  if (AnalysisDataService::Instance().doesExist(fitWsGroupName)) {
    WorkspaceGroup_sptr fitWorkspaces =
        AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(
            fitWsGroupName);
    ITableWorkspace_sptr fitParams =
        AnalysisDataService::Instance().retrieveWS<ITableWorkspace>(
            fitParamsName);
    if (fitWorkspaces && fitParams) {
      Column_const_sptr scaleFactors = fitParams->getColumn("Scaling");
      std::string fitWsName(fitWorkspaces->getItem(m_previewSpec)->name());
      MatrixWorkspace_const_sptr fitWs =
          AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
              fitWsName);

      MatrixWorkspace_sptr fit = WorkspaceFactory::Instance().create(fitWs, 1);
      fit->setX(0, fitWs->readX(1));
      fit->getSpectrum(0)->setData(fitWs->readY(1), fitWs->readE(1));

      for (size_t i = 0; i < fit->blocksize(); i++)
        fit->dataY(0)[i] /= scaleFactors->cell<double>(m_previewSpec);

      m_uiForm.ppPlot->addSpectrum("Fit", fit, 0, Qt::red);
    }
  }
}
Exemple #8
0
/**
*   Executes the algorithm.
*/
void LoadTBL::exec() {
  std::string filename = getProperty("Filename");
  std::ifstream file(filename.c_str());
  if (!file) {
    throw Exception::FileError("Unable to open file: ", filename);
  }
  std::string line;

  ITableWorkspace_sptr ws = WorkspaceFactory::Instance().createTable();

  std::vector<std::string> columnHeadings;

  Kernel::Strings::extractToEOL(file, line);
  // We want to check if the first line contains an empty string or series of
  // ",,,,,"
  // to see if we are loading a TBL file that actually contains data or not.
  boost::split(columnHeadings, line, boost::is_any_of(","),
               boost::token_compress_off);
  for (auto entry = columnHeadings.begin(); entry != columnHeadings.end();) {
    if (entry->empty()) {
      // erase the empty values
      entry = columnHeadings.erase(entry);
    } else {
      // keep any non-empty values
      ++entry;
    }
  }
  if (columnHeadings.empty()) {
    // we have an empty string or series of ",,,,,"
    throw std::runtime_error("The file you are trying to load is Empty. \n "
                             "Please load a non-empty TBL file");
  } else {
    // set columns back to empty ready to populated with columnHeadings.
    columnHeadings.clear();
  }
  // this will tell us if we need to just fill in the cell values
  // or whether we will have to create the column headings as well.
  bool isOld = getColumnHeadings(line, columnHeadings);

  std::vector<std::string> rowVec;
  if (isOld) {
    /**THIS IS ESSENTIALLY THE OLD LoadReflTBL CODE**/
    // create the column headings
    auto colStitch = ws->addColumn("str", "StitchGroup");
    auto colRuns = ws->addColumn("str", "Run(s)");
    auto colTheta = ws->addColumn("str", "ThetaIn");
    auto colTrans = ws->addColumn("str", "TransRun(s)");
    auto colQmin = ws->addColumn("str", "Qmin");
    auto colQmax = ws->addColumn("str", "Qmax");
    auto colDqq = ws->addColumn("str", "dq/q");
    auto colScale = ws->addColumn("str", "Scale");
    auto colOptions = ws->addColumn("str", "Options");
    auto colHiddenOptions = ws->addColumn("str", "HiddenOptions");

    for (size_t i = 0; i < ws->columnCount(); i++) {
      auto col = ws->getColumn(i);
      col->setPlotType(0);
    }

    // we are using the old ReflTBL format
    // where all of the entries are on one line
    // so we must reset the stream to reread the first line.
    std::ifstream file(filename.c_str());
    if (!file) {
      throw Exception::FileError("Unable to open file: ", filename);
    }
    std::string line;
    int stitchID = 1;
    while (Kernel::Strings::extractToEOL(file, line)) {
      if (line.empty() || line == ",,,,,,,,,,,,,,,,") {
        continue;
      }
      getCells(line, rowVec, 16, isOld);
      const std::string scaleStr = rowVec.at(16);
      const std::string stitchStr = boost::lexical_cast<std::string>(stitchID);

      // check if the first run in the row has any data associated with it
      // 0 = runs, 1 = theta, 2 = trans, 3 = qmin, 4 = qmax
      if (!rowVec[0].empty() || !rowVec[1].empty() || !rowVec[2].empty() ||
          !rowVec[3].empty() || !rowVec[4].empty()) {
        TableRow row = ws->appendRow();
        row << stitchStr;
        for (int i = 0; i < 5; ++i) {
          row << rowVec.at(i);
        }
        row << rowVec.at(15);
        row << scaleStr;
      }

      // check if the second run in the row has any data associated with it
      // 5 = runs, 6 = theta, 7 = trans, 8 = qmin, 9 = qmax
      if (!rowVec[5].empty() || !rowVec[6].empty() || !rowVec[7].empty() ||
          !rowVec[8].empty() || !rowVec[9].empty()) {
        TableRow row = ws->appendRow();
        row << stitchStr;
        for (int i = 5; i < 10; ++i) {
          row << rowVec.at(i);
        }
        row << rowVec.at(15);
        row << scaleStr;
      }

      // check if the third run in the row has any data associated with it
      // 10 = runs, 11 = theta, 12 = trans, 13 = qmin, 14 = qmax
      if (!rowVec[10].empty() || !rowVec[11].empty() || !rowVec[12].empty() ||
          !rowVec[13].empty() || !rowVec[14].empty()) {
        TableRow row = ws->appendRow();
        row << stitchStr;
        for (int i = 10; i < 17; ++i) {
          if (i == 16)
            row << scaleStr;
          else
            row << rowVec.at(i);
        }
      }
      ++stitchID;
      setProperty("OutputWorkspace", ws);
    }

  } else {
    // we have a TBL format that contains column headings
    // on the first row. These are now entries in the columns vector
    if (!columnHeadings.empty()) {
      // now we need to add the custom column headings from
      // the columns vector to the TableWorkspace
      for (auto heading = columnHeadings.begin();
           heading != columnHeadings.end();) {
        if (heading->empty()) {
          // there is no need to have empty column headings.
          heading = columnHeadings.erase(heading);
        } else {
          Mantid::API::Column_sptr col;
          col = ws->addColumn("str", *heading);
          col->setPlotType(0);
          heading++;
        }
      }
    }
    size_t expectedCommas = columnHeadings.size() - 1;
    while (Kernel::Strings::extractToEOL(file, line)) {
      if (line.empty() || line == ",,,,,,,,,,,,,,,,") {
        // skip over any empty lines
        continue;
      }
      getCells(line, rowVec, columnHeadings.size() - 1, isOld);
      // populate the columns with their values for this row.
      TableRow row = ws->appendRow();
      for (size_t i = 0; i < expectedCommas + 1; ++i) {
        row << rowVec.at(i);
      }
    }
    setProperty("OutputWorkspace", ws);
  }
}
Exemple #9
0
//----------------------------------------------------------------------------------------------
/// Run the algorithm
void QueryMDWorkspace::exec() {
    // Extract the required normalisation.
    std::string strNormalisation = getPropertyValue("Normalisation");
    MDNormalization requestedNormalisation = whichNormalisation(strNormalisation);

    IMDWorkspace_sptr input = getProperty("InputWorkspace");

    const bool transformCoordsToOriginal = getProperty("TransformCoordsToOriginal");

    // Define a table workspace with a specific column schema.
    ITableWorkspace_sptr output = WorkspaceFactory::Instance().createTable();
    const std::string signalColumnName = "Signal/" + strNormalisation;
    const std::string errorColumnName = "Error/" + strNormalisation;
    output->addColumn("double", signalColumnName);
    output->addColumn("double", errorColumnName);
    output->addColumn("int", "Number of Events");

    const size_t ndims = input->getNumDims();
    for (size_t index = 0; index < ndims; ++index) {
        Mantid::Geometry::IMDDimension_const_sptr dim = input->getDimension(index);
        std::string dimInUnit = dim->getName() + "/" + dim->getUnits().ascii();
        output->addColumn("double", dimInUnit);
        // Magic numbers required to configure the X axis.
        output->getColumn(dimInUnit)->setPlotType(1);
    }

    // Magic numbers required to configure the Y axis.
    output->getColumn(signalColumnName)->setPlotType(2);
    output->getColumn(errorColumnName)->setPlotType(5);

    IMDIterator *it = input->createIterator();
    it->setNormalization(requestedNormalisation);

    bool bLimitRows = getProperty("LimitRows");
    int maxRows = 0;
    if (bLimitRows) {
        maxRows = getProperty("MaximumRows");
    }

    // Use the iterator to loop through each MDBoxBase and create a row for each
    // entry.
    int rowCounter = 0;

    Progress progress(this, 0, 1, int64_t(input->getNPoints()));
    while (true) {
        size_t cellIndex = 0;
        output->appendRow();
        output->cell<double>(rowCounter, cellIndex++) = it->getNormalizedSignal();
        output->cell<double>(rowCounter, cellIndex++) = it->getNormalizedError();
        output->cell<int>(rowCounter, cellIndex++) = int(it->getNumEvents());
        VMD center = it->getCenter();
        const size_t numberOriginal = input->getNumberTransformsToOriginal();
        if (transformCoordsToOriginal && numberOriginal > 0) {
            const size_t index = numberOriginal - 1;
            CoordTransform const *transform = input->getTransformToOriginal(index);
            VMD temp = transform->applyVMD(center);
            center = temp;
        }

        for (size_t index = 0; index < ndims; ++index) {
            output->cell<double>(rowCounter, cellIndex++) = center[index];
        }

        progress.report();
        if (!it->next() || (bLimitRows && ((rowCounter + 1) >= maxRows))) {
            break;
        }
        rowCounter++;
    }
    setProperty("OutputWorkspace", output);
    delete it;

    //
    IMDEventWorkspace_sptr mdew;
    CALL_MDEVENT_FUNCTION(this->getBoxData, input);
}
/**
 * Write log and parameter values to the table for the case of multiple fits.
 * @param table :: [input, output] Table to write to
 * @param paramsByLabel :: [input] Map of <label name, <workspace name,
 * <parameter, value>>>
 * @param paramsToDisplay :: [input] List of parameters to display in table
 */
void MuonAnalysisResultTableCreator::writeDataForMultipleFits(
    ITableWorkspace_sptr &table,
    const QMap<QString, WSParameterList> &paramsByLabel,
    const QStringList &paramsToDisplay) const {
  assert(m_multiple);
  assert(m_logValues);

  // Add data to table
  for (const auto &labelName : m_items) {
    Mantid::API::TableRow row = table->appendRow();
    size_t columnIndex(0); // Which column we are writing to

    row << labelName.toStdString();
    columnIndex++;

    // Get log values for this row and write in table
    for (const auto &log : m_logs) {
      QStringList valuesPerWorkspace;
      for (const auto &wsName : paramsByLabel[labelName].keys()) {
        const auto &logValues = m_logValues->value(wsName);
        const auto &val = logValues[log];

        auto dashIndex = val.toString().indexOf("-");
        // Special case: if log is time in sec, subtract the first start time
        if (log.endsWith(" (s)")) {
          auto seconds =
              val.toDouble() - static_cast<double>(m_firstStart_ns) * 1.e-9;
          valuesPerWorkspace.append(QString::number(seconds));
        } else if (dashIndex != 0 && dashIndex != -1) {
          valuesPerWorkspace.append(logValues[log].toString());
        } else if (MuonAnalysisHelper::isNumber(val.toString()) &&
                   !log.endsWith(" (text)")) {

          valuesPerWorkspace.append(QString::number(val.toDouble()));

        } else {
          valuesPerWorkspace.append(logValues[log].toString());
        }
      }

      // Range of values - use string comparison as works for numbers too
      // Why not use std::minmax_element? To avoid MSVC warning: QT bug 41092
      // (https://bugreports.qt.io/browse/QTBUG-41092)
      valuesPerWorkspace.sort();

      auto dashIndex =
          valuesPerWorkspace.front().toStdString().find_first_of("-");
      if (dashIndex != std::string::npos && dashIndex != 0) {
        std::ostringstream oss;
        auto dad = valuesPerWorkspace.front().toStdString();
        oss << valuesPerWorkspace.front().toStdString();
        row << oss.str();

      } else {
        if (MuonAnalysisHelper::isNumber(valuesPerWorkspace.front())) {
          const auto &min = valuesPerWorkspace.front().toDouble();
          const auto &max = valuesPerWorkspace.back().toDouble();
          if (min == max) {
            row << min;
          } else {
            std::ostringstream oss;
            oss << valuesPerWorkspace.front().toStdString() << "-"
                << valuesPerWorkspace.back().toStdString();
            row << oss.str();
          }
        } else {
          const auto &front = valuesPerWorkspace.front().toStdString();
          const auto &back = valuesPerWorkspace.back().toStdString();
          if (front == back) {
            row << front;
          } else {
            std::ostringstream oss;
            oss << valuesPerWorkspace[0].toStdString();

            for (int k = 1; k < valuesPerWorkspace.size(); k++) {
              oss << ", " << valuesPerWorkspace[k].toStdString();
              row << oss.str();
            }
          }
        }
      }
      columnIndex++;
    }

    // Parse column name - could be param name or f[n].param
    const auto parseColumnName =
        [&paramsToDisplay](
            const std::string &columnName) -> std::pair<int, std::string> {
      if (paramsToDisplay.contains(QString::fromStdString(columnName))) {
        return {0, columnName};
      } else {
        // column name is f[n].param
        size_t pos = columnName.find_first_of('.');
        if (pos != std::string::npos) {
          try {
            const auto &paramName = columnName.substr(pos + 1);
            const auto wsIndex = std::stoi(columnName.substr(1, pos));
            return {wsIndex, paramName};
          } catch (const std::exception &ex) {
            throw std::runtime_error("Failed to parse column name " +
                                     columnName + ": " + ex.what());
          }
        } else {
          throw std::runtime_error("Failed to parse column name " + columnName);
        }
      }
    };

    // Add param values
    const auto &params = paramsByLabel[labelName];
    while (columnIndex < table->columnCount()) {
      const auto &parsedColName =
          parseColumnName(table->getColumn(columnIndex)->name());
      const QString wsName = params.keys().at(parsedColName.first);
      const QString &paramName = QString::fromStdString(parsedColName.second);
      row << params[wsName].value(paramName);
      columnIndex++;
    }
  }
}