Mantid
Loading...
Searching...
No Matches
LoadSESANS.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8
13#include "MantidAPI/Sample.h"
14#include "MantidAPI/Workspace.h"
16
17#include <boost/algorithm/string/split.hpp>
18#include <boost/algorithm/string/trim.hpp>
19#include <boost/regex.hpp>
20
21#include <algorithm>
22#include <cctype>
23#include <cmath>
24#include <fstream>
25
26namespace { // Anonymous namespace for helper functions
27
32bool space(const char &c) { return isspace(c) != 0; }
33
38bool notSpace(const char &c) { return !space(c); }
39
44bool allSpaces(const std::string &str) { return std::all_of(str.begin(), str.end(), space); }
45
53std::string repeatAndJoin(const std::string &str, const std::string &delim, const int &n) {
54 std::string result = "";
55 for (int i = 0; i < n - 1; i++) {
56 result += str + delim;
57 }
58 return result + str;
59}
60} // Anonymous namespace
61
62namespace Mantid::DataHandling {
63
64// Register with the AlgorithmFactory
66
67
68const std::string LoadSESANS::name() const { return "LoadSESANS"; }
69
71const std::string LoadSESANS::summary() const { return "Load a file using the SESANS format"; }
72
74int LoadSESANS::version() const { return 1; }
75
77const std::string LoadSESANS::category() const { return "DataHandling\\Text"; }
78
85 // Check we're looking at a text-based file
86 if (!descriptor.isAscii())
87 return 0;
88
89 // If the file has a SESANS extension
90 if (std::find(m_fileExtensions.begin(), m_fileExtensions.end(), descriptor.extension()) != m_fileExtensions.end())
91 return 70;
92
93 // Nothing was obviously right or wrong, so we'll have to dig around a bit in
94 // the file
95
96 auto &file = descriptor.data();
97 std::string line;
98
99 // First line should be FileFormatVersion
100 std::getline(file, line);
101 bool ffvFound = line.starts_with("FileFormatVersion");
102
103 // Next few lines should be key-value pairs
104 boost::regex kvPair(R"([\w_]+\s+[\w\d\.\-]+(\s+[\w\d\.\-\$]+)*)");
105 int kvPairsFound = 0;
106
107 for (int i = 0; i < 3 && !line.empty(); i++) {
108 if (boost::regex_match(line, kvPair)) {
109 kvPairsFound++;
110 }
111 std::getline(file, line);
112 }
113
114 // There are 13 mandatory key-value pairs. If there are 11 found, a couple may
115 // just have been missed off, but if there are fewer than we're probably
116 // looking at something else
117 if (kvPairsFound < 10)
118 return 0;
119
120 // Next non-blank line
121 while (std::getline(file, line) && line.empty())
122 ;
123
124 bool beginFound = line == m_beginData;
125
126 // Return something which takes us above other ASCII formats, as long as
127 // FileFormatVersion and BEGIN_DATA were found
128 return 15 + 3 * ffvFound + 3 * beginFound;
129}
130
135 declareProperty(std::make_unique<API::FileProperty>("Filename", "", API::FileProperty::Load, m_fileExtensions),
136 "Name of the SESANS file to load");
137 declareProperty(std::make_unique<API::WorkspaceProperty<>>("OutputWorkspace", "", Kernel::Direction::Output),
138 "The name to use for the output workspace");
139}
140
145 std::string filename = getPropertyValue("Filename");
146 std::ifstream infile(filename);
147
148 // Check file is readable
149 if (!infile) {
150 g_log.error("Unable to open file " + filename);
151 throw Kernel::Exception::FileError("Unable to open file ", filename);
152 }
153
154 g_log.information() << "Opened file \"" << filename << "\" for reading\n";
155
156 int lineNum = 0;
157 std::string line;
158 std::getline(infile, line);
159
160 // First line must be FileFormatVersion:
161 if (!line.starts_with("FileFormatVersion"))
162 throwFormatError(line, "File must begin by providing FileFormatVersion", lineNum);
163
164 // Read in all the header values, and make sure all the mandatory ones are
165 // supplied
166 AttributeMap attributes = consumeHeaders(infile, line, lineNum);
167 lineNum++;
168 checkMandatoryHeaders(attributes);
169
170 // Make sure we haven't reached the end of the file without reading any data
171 if (!line.starts_with(m_beginData))
172 throwFormatError("<EOF>", "Expected \"" + m_beginData + "\" before EOF", lineNum + 1);
173
174 // Read file columns into a map - now we can get rid of the file
175 ColumnMap columns = consumeData(infile, line, lineNum);
176 infile.close();
177
178 // Make a workspace from the columns and set it as the output
179 API::MatrixWorkspace_sptr newWorkspace = makeWorkspace(columns);
180
181 newWorkspace->setTitle(attributes["DataFileTitle"]);
182 newWorkspace->mutableSample().setName(attributes["Sample"]);
183 newWorkspace->mutableSample().setThickness(std::stod(attributes["Thickness"]));
184
185 setProperty("OutputWorkspace", newWorkspace);
186}
187
197AttributeMap LoadSESANS::consumeHeaders(std::ifstream &infile, std::string &line, int &lineNum) {
198 AttributeMap attributes;
199 std::pair<std::string, std::string> attr;
200
201 do {
202 lineNum++;
203 if (!allSpaces(line)) {
204 // Split up the line into a key-value pair and add it to our set of
205 // attributes
206 attr = splitHeader(line, lineNum);
207 attributes.insert(attr);
208 }
209 } while (std::getline(infile, line) && !line.starts_with(m_beginData));
210
211 return attributes;
212}
213
224ColumnMap LoadSESANS::consumeData(std::ifstream &infile, std::string &line, int &lineNum) {
225 std::getline(infile, line);
226 std::vector<std::string> columnHeaders;
227 boost::trim(line);
228 boost::split(columnHeaders, line, isspace, boost::token_compress_on);
229
230 // Make sure all 4 mandatory columns have been supplied
231 for (const std::string &header : m_mandatoryColumnHeaders)
232 if (std::find(columnHeaders.begin(), columnHeaders.end(), header) == columnHeaders.end())
233 throwFormatError(line, "Failed to supply mandatory column header: \"" + header + "\"", lineNum);
234
235 std::string numberRegex = R"((-?\d+(\.\d+)?([Ee][-\+]?\d+)?))";
236 // static_cast is safe as realistically our file is never going to have enough
237 // columns to overflow
238 std::string rawRegex = "^\\s*" + repeatAndJoin(numberRegex, "\\s+", static_cast<int>(columnHeaders.size())) + "\\s*$";
239 boost::regex lineRegex(rawRegex);
240
241 // Map of column name -> column values
242 ColumnMap columns;
243
244 while (std::getline(infile, line)) {
245 lineNum++;
246
247 if (boost::regex_match(line, lineRegex)) {
248 // Tokens in a line
249 std::vector<std::string> tokens;
250
251 boost::trim(line);
252 boost::split(tokens, line, isspace, boost::token_compress_on);
253
254 for (size_t i = 0; i < tokens.size(); i++)
255 columns[columnHeaders[i]].emplace_back(std::stod(tokens[i]));
256 } else {
257 g_log.warning("Line " + std::to_string(lineNum) + " discarded, as it was badly formed. Expected " +
258 std::to_string(columnHeaders.size()) + " numbers, but got \"" + line + "\"");
259 }
260 }
261 g_log.information("Loaded " + std::to_string(columns[columnHeaders[0]].size()) + " rows of data");
262 return columns;
263}
264
274std::pair<std::string, std::string> LoadSESANS::splitHeader(const std::string &untrimmedLine, const int &lineNum) {
275 std::pair<std::string, std::string> attribute;
276 const auto &line = boost::trim_copy(untrimmedLine);
277
278 auto i = line.begin();
279 // Find the end of the first word
280 auto j = find_if(i, line.end(), space);
281
282 if (j == line.end())
283 throwFormatError(line, "Expected key-value pair", lineNum);
284
285 attribute.first = std::string(i, j);
286
287 // Find start of the second word
288 i = find_if(j, line.end(), notSpace);
289 if (i == line.end())
290 throwFormatError(line, "Expected key-value pair", lineNum);
291
292 // Grab from start of second word to the end of the line
293 attribute.second = std::string(i, line.end());
294
295 return attribute;
296}
297
304void LoadSESANS::throwFormatError(const std::string &line, const std::string &message, const int &lineNum) {
305 std::string output = "Badly formed line at line " + std::to_string(lineNum) + ": \"" + line + "\"\n(" + message + ")";
306 g_log.error(output);
307 throw std::runtime_error(output);
308}
309
314 const auto it = std::find_if(m_mandatoryAttributes.cbegin(), m_mandatoryAttributes.cend(),
315 [&attributes](const auto &attr) { return !attributes.count(attr); });
316 if (it != m_mandatoryAttributes.cend()) {
317 std::string err = "Failed to supply parameter: \"" + *it + "\"";
318 g_log.error(err);
319 throw std::runtime_error(err);
320 }
321}
322
328 size_t histogramLength = columns[m_spinEchoLength].size();
329 API::MatrixWorkspace_sptr newWorkspace =
330 API::WorkspaceFactory::Instance().create("Workspace2D", 1, histogramLength, histogramLength);
331
332 const auto &xValues = columns[m_spinEchoLength];
333 const auto &yValues = columns[m_depolarisation];
334 const auto &eValues = columns[m_depolarisationError];
335
336 auto &dataX = newWorkspace->mutableX(0);
337 auto &dataY = newWorkspace->mutableY(0);
338 auto &dataE = newWorkspace->mutableE(0);
339
340 for (size_t i = 0; i < histogramLength; i++) {
341 dataX[i] = xValues[i];
342 dataY[i] = yValues[i];
343 dataE[i] = eValues[i];
344 }
345
346 return newWorkspace;
347}
348
349} // namespace Mantid::DataHandling
std::string name
Definition Run.cpp:60
std::unordered_map< std::string, Column > ColumnMap
Definition LoadSESANS.h:16
std::unordered_map< std::string, std::string > AttributeMap
Definition LoadSESANS.h:17
#define DECLARE_FILELOADER_ALGORITHM(classname)
DECLARE_FILELOADER_ALGORITHM should be used in place of the standard DECLARE_ALGORITHM macro when wri...
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Kernel::Logger & g_log
Definition Algorithm.h:422
@ Load
allowed here which will be passed to the algorithm
A property class for workspaces.
LoadSESANS : Load a workspace in the SESANS file format.
Definition LoadSESANS.h:33
void throwFormatError(const std::string &line, const std::string &message, const int &lineNum)
Helper function to throw an error relating to the format of the file.
ColumnMap consumeData(std::ifstream &infile, std::string &line, int &lineNum)
Read numerical data from the file into a map of the form [column name] -> [column data].
const std::vector< std::string > m_mandatoryAttributes
Definition LoadSESANS.h:50
const std::string m_beginData
Definition LoadSESANS.h:48
const std::vector< std::string > m_mandatoryColumnHeaders
Definition LoadSESANS.h:54
const std::string m_depolarisationError
Definition LoadSESANS.h:47
void init() override
Initialise the algorithm.
API::MatrixWorkspace_sptr makeWorkspace(ColumnMap columns)
Create a new workspace with the columns read from the file.
const std::string category() const override
Get algorithm's category.
std::pair< std::string, std::string > splitHeader(const std::string &line, const int &lineNum)
Split a header into a key-value pair delimited by whitespace, where the first token is the key and th...
AttributeMap consumeHeaders(std::ifstream &infile, std::string &line, int &lineNum)
Read headers from the input file into the attribute map, until BEGIN_DATA is found.
const std::vector< std::string > m_fileExtensions
Definition LoadSESANS.h:56
void exec() override
Execute the algorithm.
const std::string m_spinEchoLength
Definition LoadSESANS.h:44
int confidence(Kernel::FileDescriptor &descriptor) const override
Get the confidence that this algorithm can load a file.
int version() const override
Get algorithm's version number.
const std::string m_depolarisation
Definition LoadSESANS.h:46
void checkMandatoryHeaders(const AttributeMap &attributes)
Make sure that all mandatory headers are supplied in the file.
const std::string summary() const override
Get summary of algorithm.
Records the filename and the description of failure.
Definition Exception.h:98
Defines a wrapper around an open file.
static bool isAscii(const std::string &filename, const size_t nbytes=256)
Returns true if the file is considered ascii.
std::istream & data()
Access the open file stream.
const std::string & extension() const
Access the file extension.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
STL namespace.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Output
An output workspace.
Definition Property.h:54