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 efixed =
m_ws->getEFixed(spectrumInfo.firstDetectorID(i));
445 }
catch (std::runtime_error &) {
454 auto qValueStr = boost::lexical_cast<std::string>(qValue);
455 qValues.emplace_back(qValueStr);
464 std::vector<std::string> spectrumNumbers;
465 const size_t nHist =
m_ws->getNumberHistograms();
466 for (
size_t i = 0; i < nHist; i++) {
467 const auto specNum =
m_ws->getSpectrum(i).getSpectrumNo();
469 spectrumNumbers.emplace_back(specNumStr);
478 std::vector<std::string> angles;
479 const size_t nHist =
m_ws->getNumberHistograms();
480 const auto &spectrumInfo =
m_ws->spectrumInfo();
481 for (
size_t i = 0; i < nHist; i++) {
482 const auto two_theta = spectrumInfo.twoTheta(i);
483 constexpr double rad2deg = 180. / M_PI;
484 const auto angleInDeg = two_theta *
rad2deg;
485 const auto angleInDegStr = boost::lexical_cast<std::string>(angleInDeg);
486 angles.emplace_back(angleInDegStr);
496 if (metaDataType ==
"spectrumnumber")
498 if (metaDataType ==
"q")
500 if (metaDataType ==
"angle")
506 return std::find(vector.cbegin(), vector.cend(), toFind) != vector.cend();
510 bool appendToFile,
bool writeHeader,
int prec,
bool scientific,
511 const std::string &comment) {
513 std::ofstream file(filename.c_str(), (appendToFile ? std::ios::app : std::ios::out));
520 file.precision(prec);
523 file << std::scientific;
526 const auto columnCount = tws->columnCount();
529 file << comment <<
" ";
530 for (
size_t colIndex = 0; colIndex < columnCount; colIndex++) {
531 file << tws->getColumn(colIndex)->name() <<
" ";
532 if (colIndex < columnCount - 1) {
533 file <<
m_sep <<
" ";
538 file << comment <<
" ";
539 for (
size_t colIndex = 0; colIndex < columnCount; colIndex++) {
540 file << tws->getColumn(colIndex)->type() <<
" ";
541 if (colIndex < columnCount - 1) {
542 file <<
m_sep <<
" ";
547 g_log.
warning(
"Please note that files written without headers cannot be "
548 "reloaded back into Mantid with LoadAscii.");
552 const auto rowCount = tws->rowCount();
554 for (
size_t rowIndex = 0; rowIndex < rowCount; rowIndex++) {
555 for (
size_t colIndex = 0; colIndex < columnCount; colIndex++) {
556 tws->getColumn(colIndex)->print(rowIndex, file);
557 if (colIndex < columnCount - 1) {
565 file.unsetf(std::ios_base::floatfield);
576 auto run =
m_ws->run();
578 std::string sampleLogValue =
"";
580 sampleLogValue = boost::lexical_cast<std::string>(run.getLogData(logName)->value());
583 sampleLogValue =
"Not defined";
586 std::string sampleLogUnit =
"";
588 sampleLogUnit = boost::lexical_cast<std::string>(run.getLogData(logName)->units());
592 return std::pair(sampleLogValue, sampleLogUnit);
602 for (
const auto &logName : logList) {
604 auto logValue = boost::replace_all_copy(readLog.second,
",",
";");
605 outputFile << logName <<
m_sep << readLog.first <<
m_sep << logValue <<
'\n';
611 bool appendToFile,
bool writeHeader,
int prec,
bool scientific,
612 const std::string &comment) {
613 auto dims = mdws->getNonIntegratedDimensions();
614 if (dims.size() != 1) {
615 throw std::runtime_error(
"SaveAscii does not support saving multidimentional MDHistoWorkspace, and only supports "
616 "1D MDHistoWorkspaces cuts");
619 std::ofstream file(filename.c_str(), (appendToFile ? std::ios::app : std::ios::out));
626 file.precision(prec);
630 file << std::scientific;
633 auto lastAlg = mdws->getHistory().lastAlgorithm();
634 auto &propsVec = lastAlg->getProperties();
640 file << comment <<
" {";
641 for (
size_t i = 0; i < propsVec.size(); i++) {
642 file << propsVec[i]->name() <<
": " << propsVec[i]->valueAsPrettyStr();
643 if (i < propsVec.size() - 1)
644 file <<
m_sep <<
" ";
649 auto dimName = dim->getName();
650 auto dimUnit = dim->getUnits().latex();
652 file << comment <<
" " << dimName <<
" " << dimUnit <<
m_sep <<
" "
653 <<
"Signal" <<
m_sep <<
" "
658 auto nbins = dim->getNBins();
659 auto binWidth = dim->getBinWidth() / 2;
660 auto binMax = dim->getMaximum();
661 auto binMin = dim->getMinimum();
663 auto start = binMin + binWidth;
664 auto end = binMax - binWidth;
665 auto step = (end - start) /
float(nbins - 1);
667 auto nPoints = mdws->getNPoints();
668 auto signal = mdws->getSignalArray();
669 auto error = mdws->getErrorSquaredArray();
674 for (
size_t i = 0; i < nPoints; ++i) {
675 file << start + step * float(i) <<
m_sep << signal[i] <<
m_sep << std::sqrt(
error[i]) <<
"\n";
679 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
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 const > settings)
Add a PropertySettings instance to the chain of settings for a given property.
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 MANTID_KERNEL_DLL 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.