26#include <boost/regex.hpp>
27#include <boost/tokenizer.hpp>
35using namespace Kernel;
40 : m_separatorIndex(), m_nBins(0),
m_sep(), m_writeDX(false), m_writeID(false), m_isCommonBins(false),
41 m_writeSpectrumAxisValue(false), m_ws() {}
46 "The name of the workspace containing the data you want to save to a "
50 "The filename of the output Ascii file.");
52 auto mustBePositive = std::make_shared<BoundedValidator<int>>();
53 mustBePositive->setLower(1);
54 auto mustBeZeroGreater = std::make_shared<BoundedValidator<int>>();
55 mustBeZeroGreater->setLower(0);
57 "The starting workspace index. Ignored for Table Workspaces.");
59 "The ending workspace index. Ignored for Table Workspaces.");
61 "List of workspace indices to save. Ignored for Table Workspaces.");
64 "If true, the values will be "
65 "written to the file in "
66 "scientific notation.");
68 "If true, the error on X will be written as the fourth "
69 "column. Ignored for Table Workspaces.");
71 "If false, the spectrum No will not be written for "
72 "single-spectrum workspaces. "
73 "It is always written for workspaces with multiple spectra, "
74 "unless spectrum axis value is written. Ignored for Table Workspaces.");
76 declareProperty(
"CommentIndicator",
"#",
"Character(s) to put in front of comment lines.");
79 std::string spacers[6][2] = {{
"CSV",
","}, {
"Tab",
"\t"}, {
"Space",
" "},
80 {
"Colon",
":"}, {
"SemiColon",
";"}, {
"UserDefined",
"UserDefined"}};
81 std::vector<std::string> sepOptions;
82 for (
auto &spacer : spacers) {
83 std::string option = spacer[0];
84 m_separatorIndex.insert(std::pair<std::string, std::string>(option, spacer[1]));
85 sepOptions.emplace_back(option);
88 declareProperty(
"Separator",
"CSV", std::make_shared<StringListValidator>(sepOptions),
89 "The separator between data columns in the data file. The "
90 "possible values are \"CSV\", \"Tab\", "
91 "\"Space\", \"SemiColon\", \"Colon\" or \"UserDefined\".");
94 "If present, will override any specified choice given to Separator.");
97 std::make_unique<VisibleWhenProperty>(
"Separator",
IS_EQUAL_TO,
"UserDefined"));
100 "If true, put column headers into file. Even if false, a header"
101 "is automatically added if the workspace is Distribution = true.");
104 "A comma separated list that defines data that describes "
105 "each spectrum in a workspace. The valid options for this "
106 "are: SpectrumNumber,Q,Angle. Ignored for Table Workspaces.");
108 declareProperty(
"AppendToFile",
false,
"If true, don't overwrite the file. Append to the end of it. ");
111 "If true, ensure that more than one xspectra is used. "
112 "Ignored for Table Workspaces.");
115 "Write the spectrum axis value if requested. Ignored for "
116 "Table Workspaces.");
119 "List of logs to write to the file header. Ignored for Table "
122 declareProperty(
"OneSpectrumPerFile",
false,
"If true, each spectrum will be saved to an individual file");
131 m_ws = std::dynamic_pointer_cast<const MatrixWorkspace>(ws);
136 const bool writeHeader =
getProperty(
"ColumnHeader");
137 const bool appendToFile =
getProperty(
"AppendToFile");
147 if (!custom.empty()) {
152 else if (choice !=
"UserDefined") {
158 g_log.
notice() <<
"\"UserDefined\" has been selected, but no custom "
159 "separator has been entered."
160 " Using default instead.";
166 return writeTableWorkspace(tws, filename, appendToFile, writeHeader, prec, scientific, comment);
168 return write1DHistoCut(mdws, filename, appendToFile, writeHeader, prec, scientific, comment);
172 throw std::runtime_error(
"SaveAscii does not know how to save this workspace type, " + ws->getName());
176 std::vector<int> spec_list =
getProperty(
"SpectrumList");
177 const int spec_min =
getProperty(
"WorkspaceIndexMin");
178 const int spec_max =
getProperty(
"WorkspaceIndexMax");
184 if (!metaDataString.empty()) {
187 if (containsSpectrumNumber) {
189 m_ws->getSpectrumToWorkspaceIndexMap();
190 }
catch (
const std::runtime_error &) {
191 throw std::runtime_error(
"SpectrumNumber is present in "
192 "SpectrumMetaData but the workspace does not "
193 "have a SpectrumAxis.");
199 if (!containsSpectrumNumber) {
201 m_metaData.insert(firstIter,
"spectrumnumber");
206 auto spectrumAxis =
m_ws->getAxis(1);
208 m_axisProxy = std::make_unique<AxisHelper::BinEdgeAxisProxy>(spectrumAxis);
210 m_axisProxy = std::make_unique<AxisHelper::AxisProxy>(spectrumAxis);
215 if (!boost::regex_match(
m_sep.begin(),
m_sep.end(), boost::regex(
"[^0-9e+-]+", boost::regex::perl))) {
216 throw std::invalid_argument(
"Separators cannot contain numeric characters, "
217 "plus signs, hyphens or 'e'");
220 if (comment.at(0) ==
m_sep.at(0) ||
221 !boost::regex_match(comment.begin(), comment.end(),
222 boost::regex(
"[^0-9e" +
m_sep +
"+-]+", boost::regex::perl))) {
223 throw std::invalid_argument(
"Comment markers cannot contain numeric "
224 "characters, plus signs, hyphens,"
225 " 'e' or the selected separator character");
231 auto nSpectra =
static_cast<int>(
m_ws->getNumberHistograms());
237 if (spec_min >=
nSpectra || spec_max >=
nSpectra || spec_min < 0 || spec_max < 0 || spec_min > spec_max) {
238 throw std::invalid_argument(
"Inconsistent spectra interval");
240 for (
int i = spec_min; i <= spec_max; i++) {
247 if (!spec_list.empty()) {
248 for (
auto &spec : spec_list) {
250 throw std::invalid_argument(
"Inconsistent spectra list");
259 for (
int i = 0; i <
nSpectra; i++) {
265 throw std::runtime_error(
"Trying to save an empty workspace");
268 const bool oneSpectrumPerFile =
getProperty(
"OneSpectrumPerFile");
277 const bool isDistribution =
m_ws->isDistribution();
278 auto idxIt = idx.begin();
279 while (idxIt != idx.end()) {
280 std::string currentFilename;
281 if (oneSpectrumPerFile)
284 currentFilename = filename;
286 std::ofstream file(currentFilename, (appendToFile ? std::ios::app : std::ios::out));
293 file.precision(prec);
296 file << std::scientific;
298 const std::vector<std::string> logList =
getProperty(
"LogList");
299 if (!logList.empty()) {
302 if (writeHeader || isDistribution) {
303 file << comment <<
" X " <<
m_sep <<
" Y " <<
m_sep <<
" E";
305 file <<
" " <<
m_sep <<
" DX";
307 file <<
" Distribution=" << (isDistribution ?
"true" :
"false");
312 if (oneSpectrumPerFile) {
317 while (idxIt != idx.end()) {
324 file.unsetf(std::ios_base::floatfield);
337 size_t extPosition{std::string::npos};
339 extPosition = filename.find(ext);
340 if (extPosition != std::string::npos)
343 if (extPosition == std::string::npos)
344 extPosition = filename.size();
346 std::ostringstream ss;
347 ss << std::string(filename, 0, extPosition) <<
"_" << workspaceIndex;
348 auto axis =
m_ws->getAxis(1);
349 if (axis->isNumeric()) {
350 auto binEdgeAxis =
dynamic_cast<BinEdgeAxis *
>(axis);
352 ss <<
"_" << binEdgeAxis->
label(workspaceIndex) << axis->unit()->label().ascii();
354 ss <<
"_" << axis->getValue(workspaceIndex) << axis->unit()->label().ascii();
355 }
else if (axis->isText())
356 ss <<
"_" << axis->label(workspaceIndex);
357 ss << std::string(filename, extPosition);
376 file <<
" " <<
m_sep <<
" ";
381 auto pointDeltas =
m_ws->pointStandardDeviations(0);
382 auto points0 =
m_ws->points(0);
383 auto pointsSpec =
m_ws->points(wsIndex);
384 bool hasDx =
m_ws->hasDx(0);
385 for (
int bin = 0; bin <
m_nBins; bin++) {
387 file << points0[bin];
390 file << pointsSpec[bin];
393 file <<
m_ws->y(wsIndex)[bin];
396 file <<
m_ws->e(wsIndex)[bin];
400 file << pointDeltas[bin];
402 g_log.
information(
"SaveAscii2: WriteXError is requested but there are no Dx data in the workspace");
416 const std::vector<std::string> validMetaData{
"spectrumnumber",
"q",
"angle"};
417 boost::to_lower(inputString);
419 const auto it = std::find_if(stringVector.cbegin(), stringVector.cend(), [&validMetaData](
const auto &input) {
420 return std::find(validMetaData.begin(), validMetaData.end(), input) == validMetaData.end();
422 if (it != stringVector.cend()) {
423 throw std::runtime_error(*it +
" is not recognised as a possible input "
424 "for SpectrumMetaData.\n Valid inputs "
425 "are: SpectrumNumber, Q, Angle.");
436 std::vector<std::string> qValues;
437 const auto nHist =
m_ws->getNumberHistograms();
438 const auto &spectrumInfo =
m_ws->spectrumInfo();
439 for (
size_t i = 0; i < nHist; i++) {
440 double theta(0.0),
efixed(0.0);
441 if (!spectrumInfo.isMonitor(i)) {
442 theta = 0.5 * spectrumInfo.twoTheta(i);
444 std::shared_ptr<const Geometry::IDetector> detector(&spectrumInfo.detector(i),
NoDeleting());
446 }
catch (std::runtime_error &) {
455 auto qValueStr = boost::lexical_cast<std::string>(qValue);
456 qValues.emplace_back(qValueStr);
465 std::vector<std::string> spectrumNumbers;
466 const size_t nHist =
m_ws->getNumberHistograms();
467 for (
size_t i = 0; i < nHist; i++) {
468 const auto specNum =
m_ws->getSpectrum(i).getSpectrumNo();
470 spectrumNumbers.emplace_back(specNumStr);
479 std::vector<std::string> angles;
480 const size_t nHist =
m_ws->getNumberHistograms();
481 const auto &spectrumInfo =
m_ws->spectrumInfo();
482 for (
size_t i = 0; i < nHist; i++) {
483 const auto two_theta = spectrumInfo.twoTheta(i);
484 constexpr double rad2deg = 180. / M_PI;
485 const auto angleInDeg = two_theta *
rad2deg;
486 const auto angleInDegStr = boost::lexical_cast<std::string>(angleInDeg);
487 angles.emplace_back(angleInDegStr);
497 if (metaDataType ==
"spectrumnumber")
499 if (metaDataType ==
"q")
501 if (metaDataType ==
"angle")
507 return std::find(vector.cbegin(), vector.cend(), toFind) != vector.cend();
511 bool appendToFile,
bool writeHeader,
int prec,
bool scientific,
512 const std::string &comment) {
514 std::ofstream file(filename.c_str(), (appendToFile ? std::ios::app : std::ios::out));
521 file.precision(prec);
524 file << std::scientific;
527 const auto columnCount = tws->columnCount();
530 file << comment <<
" ";
531 for (
size_t colIndex = 0; colIndex < columnCount; colIndex++) {
532 file << tws->getColumn(colIndex)->name() <<
" ";
533 if (colIndex < columnCount - 1) {
534 file <<
m_sep <<
" ";
539 file << comment <<
" ";
540 for (
size_t colIndex = 0; colIndex < columnCount; colIndex++) {
541 file << tws->getColumn(colIndex)->type() <<
" ";
542 if (colIndex < columnCount - 1) {
543 file <<
m_sep <<
" ";
548 g_log.
warning(
"Please note that files written without headers cannot be "
549 "reloaded back into Mantid with LoadAscii.");
553 const auto rowCount = tws->rowCount();
555 for (
size_t rowIndex = 0; rowIndex < rowCount; rowIndex++) {
556 for (
size_t colIndex = 0; colIndex < columnCount; colIndex++) {
557 tws->getColumn(colIndex)->print(rowIndex, file);
558 if (colIndex < columnCount - 1) {
566 file.unsetf(std::ios_base::floatfield);
577 auto run =
m_ws->run();
579 std::string sampleLogValue =
"";
581 sampleLogValue = boost::lexical_cast<std::string>(run.getLogData(logName)->value());
584 sampleLogValue =
"Not defined";
587 std::string sampleLogUnit =
"";
589 sampleLogUnit = boost::lexical_cast<std::string>(run.getLogData(logName)->units());
593 return std::pair(sampleLogValue, sampleLogUnit);
603 for (
const auto &logName : logList) {
605 auto logValue = boost::replace_all_copy(readLog.second,
",",
";");
606 outputFile << logName <<
m_sep << readLog.first <<
m_sep << logValue <<
'\n';
612 bool appendToFile,
bool writeHeader,
int prec,
bool scientific,
613 const std::string &comment) {
614 auto dims = mdws->getNonIntegratedDimensions();
615 if (dims.size() != 1) {
616 throw std::runtime_error(
"SaveAscii does not support saving multidimentional MDHistoWorkspace, and only supports "
617 "1D MDHistoWorkspaces cuts");
620 std::ofstream file(filename.c_str(), (appendToFile ? std::ios::app : std::ios::out));
627 file.precision(prec);
631 file << std::scientific;
634 auto lastAlg = mdws->getHistory().lastAlgorithm();
635 auto &propsVec = lastAlg->getProperties();
641 file << comment <<
" {";
642 for (
size_t i = 0; i < propsVec.size(); i++) {
643 file << propsVec[i]->name() <<
": " << propsVec[i]->valueAsPrettyStr();
644 if (i < propsVec.size() - 1)
645 file <<
m_sep <<
" ";
650 auto dimName = dim->getName();
651 auto dimUnit = dim->getUnits().latex();
653 file << comment <<
" " << dimName <<
" " << dimUnit <<
m_sep <<
" "
654 <<
"Signal" <<
m_sep <<
" "
659 auto nbins = dim->getNBins();
660 auto binWidth = dim->getBinWidth() / 2;
661 auto binMax = dim->getMaximum();
662 auto binMin = dim->getMinimum();
664 auto start = binMin + binWidth;
665 auto end = binMax - binWidth;
666 auto step = (end - start) /
float(nbins - 1);
668 auto nPoints = mdws->getNPoints();
669 auto signal = mdws->getSignalArray();
670 auto error = mdws->getErrorSquaredArray();
675 for (
size_t i = 0; i < nPoints; ++i) {
676 file << start + step * float(i) <<
m_sep << signal[i] <<
m_sep << std::sqrt(
error[i]) <<
"\n";
680 file.unsetf(std::ios_base::floatfield);
#define DECLARE_ALGORITHM(classname)
static char constexpr m_sep
double value
The value of the point.
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Kernel::Property * getPointerToProperty(const std::string &name) const override
Get a property by name.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Stores numeric values that are assumed to be bin edge values.
std::string label(const std::size_t &index) const override
Returns a text label which shows the value at the given bin index.
@ Save
to specify a file to write to, the file may or may not exist
Helper class for reporting progress from algorithms.
A property class for workspaces.
bool findElementInUnorderedStringVector(const std::vector< std::string > &vector, const std::string &toFind)
API::MatrixWorkspace_const_sptr m_ws
void populateAngleMetaData()
Populate the map with the Angle for each spectrum in the workspace.
void populateSpectrumNumberMetaData()
Populate the map with the SpectrumNumber for each Spectrum in the workspace.
void writeSpectrum(const int &wsIndex, std::ofstream &file)
Writes a spectrum to the file using a workspace index.
std::map< std::string, std::vector< std::string > > m_metaDataMap
void writeTableWorkspace(const API::ITableWorkspace_const_sptr &tws, const std::string &filename, bool appendToFile, bool writeHeader, int prec, bool scientific, const std::string &comment)
void writeFileHeader(const std::vector< std::string > &logList, std::ofstream &file)
Write file header.
std::pair< std::string, std::string > sampleLogValueUnit(const std::string &logName)
Retrieve sample log value and its unit.
std::vector< std::string > stringListToVector(std::string &inputString)
Converts a comma separated list to a vector of strings Also ensures all strings are valid input.
bool m_writeSpectrumAxisValue
SaveAscii2()
Default constructor.
void write1DHistoCut(const API::IMDHistoWorkspace_const_sptr &mdws, const std::string &filename, bool appendToFile, bool writeHeader, int prec, bool scientific, const std::string &comment)
void populateAllMetaData()
Populate all required meta data in the meta data map.
const std::vector< std::string > m_asciiExts
void exec() override
Overwrites Algorithm method.
std::vector< std::string > m_metaData
std::string createSpectrumFilename(size_t workspaceIndex)
Create the filename used for the export of a specific spectrum.
void init() override
Overwrites Algorithm method.
std::map< std::string, std::string > m_separatorIndex
Map the separator options to their string equivalents.
void populateQMetaData()
Populate the map with the Q values associated with each spectrum in the workspace.
std::unique_ptr< AxisHelper::AxisProxy > m_axisProxy
Void deleter for shared pointers.
Support for a property that holds an array of values.
Records the filename and the description of failure.
Exception for when an item is not found in a collection.
void setPropertySettings(const std::string &name, std::unique_ptr< IPropertySettings > settings)
void notice(const std::string &msg)
Logs at notice level.
void warning(const std::string &msg)
Logs at warning level.
void information(const std::string &msg)
Logs at information level.
The concrete, templated class for properties.
void setAutoTrim(const bool &setting)
Sets if the property is set to automatically trim string unput values of whitespace.
static double convertToElasticQ(const double theta, const double efixed)
Convert to ElasticQ from Energy.
std::shared_ptr< const ITableWorkspace > ITableWorkspace_const_sptr
shared pointer to Mantid::API::ITableWorkspace (const version)
std::shared_ptr< const Workspace > Workspace_const_sptr
shared pointer to Mantid::API::Workspace (const version)
std::shared_ptr< const IMDHistoWorkspace > IMDHistoWorkspace_const_sptr
shared pointer to Mantid::API::IMDHistoWorkspace (const version)
std::shared_ptr< const IMDDimension > IMDDimension_const_sptr
Shared Pointer to const IMDDimension.
template DLLExport std::vector< std::string > splitStringIntoVector< std::string >(std::string listString, const std::string &separator)
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.