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/predicate.hpp>
18#include <boost/algorithm/string/split.hpp>
19#include <boost/algorithm/string/trim.hpp>
20#include <boost/regex.hpp>
21
22#include <algorithm>
23#include <cctype>
24#include <cmath>
25#include <fstream>
26
27namespace { // Anonymous namespace for helper functions
28
33bool space(const char &c) { return isspace(c) != 0; }
34
39bool notSpace(const char &c) { return !space(c); }
40
45bool allSpaces(const std::string &str) { return std::all_of(str.begin(), str.end(), space); }
46
54std::string repeatAndJoin(const std::string &str, const std::string &delim, const int &n) {
55 std::string result = "";
56 for (int i = 0; i < n - 1; i++) {
57 result += str + delim;
58 }
59 return result + str;
60}
61} // Anonymous namespace
62
63namespace Mantid::DataHandling {
64
65// Register with the AlgorithmFactory
67
68
69const std::string LoadSESANS::name() const { return "LoadSESANS"; }
70
72const std::string LoadSESANS::summary() const { return "Load a file using the SESANS format"; }
73
75int LoadSESANS::version() const { return 1; }
76
78const std::string LoadSESANS::category() const { return "DataHandling\\Text"; }
79
86 // Check we're looking at a text-based file
87 if (!descriptor.isAscii())
88 return 0;
89
90 // If the file has a SESANS extension
91 if (std::find(m_fileExtensions.begin(), m_fileExtensions.end(), descriptor.extension()) != m_fileExtensions.end())
92 return 70;
93
94 // Nothing was obviously right or wrong, so we'll have to dig around a bit in
95 // the file
96
97 auto &file = descriptor.data();
98 std::string line;
99
100 // First line should be FileFormatVersion
101 std::getline(file, line);
102 bool ffvFound = boost::starts_with(line, "FileFormatVersion");
103
104 // Next few lines should be key-value pairs
105 boost::regex kvPair(R"([\w_]+\s+[\w\d\.\-]+(\s+[\w\d\.\-\$]+)*)");
106 int kvPairsFound = 0;
107
108 for (int i = 0; i < 3 && !line.empty(); i++) {
109 if (boost::regex_match(line, kvPair)) {
110 kvPairsFound++;
111 }
112 std::getline(file, line);
113 }
114
115 // There are 13 mandatory key-value pairs. If there are 11 found, a couple may
116 // just have been missed off, but if there are fewer than we're probably
117 // looking at something else
118 if (kvPairsFound < 10)
119 return 0;
120
121 // Next non-blank line
122 while (std::getline(file, line) && line.empty())
123 ;
124
125 bool beginFound = line == m_beginData;
126
127 // Return something which takes us above other ASCII formats, as long as
128 // FileFormatVersion and BEGIN_DATA were found
129 return 15 + 3 * ffvFound + 3 * beginFound;
130}
131
136 declareProperty(std::make_unique<API::FileProperty>("Filename", "", API::FileProperty::Load, m_fileExtensions),
137 "Name of the SESANS file to load");
138 declareProperty(std::make_unique<API::WorkspaceProperty<>>("OutputWorkspace", "", Kernel::Direction::Output),
139 "The name to use for the output workspace");
140}
141
146 std::string filename = getPropertyValue("Filename");
147 std::ifstream infile(filename);
148
149 // Check file is readable
150 if (!infile) {
151 g_log.error("Unable to open file " + filename);
152 throw Kernel::Exception::FileError("Unable to open file ", filename);
153 }
154
155 g_log.information() << "Opened file \"" << filename << "\" for reading\n";
156
157 int lineNum = 0;
158 std::string line;
159 std::getline(infile, line);
160
161 // First line must be FileFormatVersion:
162 if (!boost::starts_with(line, "FileFormatVersion"))
163 throwFormatError(line, "File must begin by providing FileFormatVersion", lineNum);
164
165 // Read in all the header values, and make sure all the mandatory ones are
166 // supplied
167 AttributeMap attributes = consumeHeaders(infile, line, lineNum);
168 lineNum++;
169 checkMandatoryHeaders(attributes);
170
171 // Make sure we haven't reached the end of the file without reading any data
172 if (!boost::starts_with(line, m_beginData))
173 throwFormatError("<EOF>", "Expected \"" + m_beginData + "\" before EOF", lineNum + 1);
174
175 // Read file columns into a map - now we can get rid of the file
176 ColumnMap columns = consumeData(infile, line, lineNum);
177 infile.close();
178
179 // Make a workspace from the columns and set it as the output
180 API::MatrixWorkspace_sptr newWorkspace = makeWorkspace(columns);
181
182 newWorkspace->setTitle(attributes["DataFileTitle"]);
183 newWorkspace->mutableSample().setName(attributes["Sample"]);
184 newWorkspace->mutableSample().setThickness(std::stod(attributes["Thickness"]));
185
186 setProperty("OutputWorkspace", newWorkspace);
187}
188
198AttributeMap LoadSESANS::consumeHeaders(std::ifstream &infile, std::string &line, int &lineNum) {
199 AttributeMap attributes;
200 std::pair<std::string, std::string> attr;
201
202 do {
203 lineNum++;
204 if (!allSpaces(line)) {
205 // Split up the line into a key-value pair and add it to our set of
206 // attributes
207 attr = splitHeader(line, lineNum);
208 attributes.insert(attr);
209 }
210 } while (std::getline(infile, line) && !boost::starts_with(line, m_beginData));
211
212 return attributes;
213}
214
225ColumnMap LoadSESANS::consumeData(std::ifstream &infile, std::string &line, int &lineNum) {
226 std::getline(infile, line);
227 std::vector<std::string> columnHeaders;
228 boost::trim(line);
229 boost::split(columnHeaders, line, isspace, boost::token_compress_on);
230
231 // Make sure all 4 mandatory columns have been supplied
232 for (const std::string &header : m_mandatoryColumnHeaders)
233 if (std::find(columnHeaders.begin(), columnHeaders.end(), header) == columnHeaders.end())
234 throwFormatError(line, "Failed to supply mandatory column header: \"" + header + "\"", lineNum);
235
236 std::string numberRegex = R"((-?\d+(\.\d+)?([Ee][-\+]?\d+)?))";
237 // static_cast is safe as realistically our file is never going to have enough
238 // columns to overflow
239 std::string rawRegex = "^\\s*" + repeatAndJoin(numberRegex, "\\s+", static_cast<int>(columnHeaders.size())) + "\\s*$";
240 boost::regex lineRegex(rawRegex);
241
242 // Map of column name -> column values
243 ColumnMap columns;
244
245 while (std::getline(infile, line)) {
246 lineNum++;
247
248 // Tokens in a line
249 std::vector<std::string> tokens;
250
251 if (boost::regex_match(line, lineRegex)) {
252 boost::trim(line);
253 boost::split(tokens, line, isspace, boost::token_compress_on);
254
255 for (size_t i = 0; i < tokens.size(); i++)
256 columns[columnHeaders[i]].emplace_back(std::stod(tokens[i]));
257 } else {
258 g_log.warning("Line " + std::to_string(lineNum) + " discarded, as it was badly formed. Expected " +
259 std::to_string(columnHeaders.size()) + " numbers, but got \"" + line + "\"");
260 }
261 }
262 g_log.information("Loaded " + std::to_string(columns[columnHeaders[0]].size()) + " rows of data");
263 return columns;
264}
265
275std::pair<std::string, std::string> LoadSESANS::splitHeader(const std::string &untrimmedLine, const int &lineNum) {
276 std::pair<std::string, std::string> attribute;
277 const auto &line = boost::trim_copy(untrimmedLine);
278
279 auto i = line.begin();
280 // Find the end of the first word
281 auto j = find_if(i, line.end(), space);
282
283 if (j == line.end())
284 throwFormatError(line, "Expected key-value pair", lineNum);
285
286 attribute.first = std::string(i, j);
287
288 // Find start of the second word
289 i = find_if(j, line.end(), notSpace);
290 if (i == line.end())
291 throwFormatError(line, "Expected key-value pair", lineNum);
292
293 // Grab from start of second word to the end of the line
294 attribute.second = std::string(i, line.end());
295
296 return attribute;
297}
298
305void LoadSESANS::throwFormatError(const std::string &line, const std::string &message, const int &lineNum) {
306 std::string output = "Badly formed line at line " + std::to_string(lineNum) + ": \"" + line + "\"\n(" + message + ")";
307 g_log.error(output);
308 throw std::runtime_error(output);
309}
310
315 for (const std::string &attr : m_mandatoryAttributes) {
316 if (!attributes.count(attr)) {
317 std::string err = "Failed to supply parameter: \"" + attr + "\"";
318 g_log.error(err);
319 throw std::runtime_error(err);
320 }
321 }
322}
323
329 size_t histogramLength = columns[m_spinEchoLength].size();
330 API::MatrixWorkspace_sptr newWorkspace =
331 API::WorkspaceFactory::Instance().create("Workspace2D", 1, histogramLength, histogramLength);
332
333 const auto &xValues = columns[m_spinEchoLength];
334 const auto &yValues = columns[m_depolarisation];
335 const auto &eValues = columns[m_depolarisationError];
336
337 auto &dataX = newWorkspace->mutableX(0);
338 auto &dataY = newWorkspace->mutableY(0);
339 auto &dataE = newWorkspace->mutableE(0);
340
341 for (size_t i = 0; i < histogramLength; i++) {
342 dataX[i] = xValues[i];
343 dataY[i] = yValues[i];
344 dataE[i] = eValues[i];
345 }
346
347 return newWorkspace;
348}
349
350} // namespace Mantid::DataHandling
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.
Definition: Algorithm.cpp:1913
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Definition: Algorithm.cpp:2026
@ Load
allowed here which will be passed to the algorithm
Definition: FileProperty.h:52
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.
Definition: LoadSESANS.cpp:305
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].
Definition: LoadSESANS.cpp:225
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.
Definition: LoadSESANS.cpp:135
API::MatrixWorkspace_sptr makeWorkspace(ColumnMap columns)
Create a new workspace with the columns read from the file.
Definition: LoadSESANS.cpp:328
const std::string category() const override
Get algorithm's category.
Definition: LoadSESANS.cpp:78
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...
Definition: LoadSESANS.cpp:275
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.
Definition: LoadSESANS.cpp:198
const std::vector< std::string > m_fileExtensions
Definition: LoadSESANS.h:56
void exec() override
Execute the algorithm.
Definition: LoadSESANS.cpp:145
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.
Definition: LoadSESANS.cpp:85
int version() const override
Get algorithm's version number.
Definition: LoadSESANS.cpp:75
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.
Definition: LoadSESANS.cpp:314
const std::string summary() const override
Get summary of algorithm.
Definition: LoadSESANS.cpp:72
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:77
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Kernel::Logger g_log("ExperimentInfo")
static logger object
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