forked from npshub/mantid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NexusDescriptor.cpp
253 lines (224 loc) · 9.79 KB
/
NexusDescriptor.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source,
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidKernel/NexusDescriptor.h"
// clang-format off
#include <nexus/NeXusFile.hpp>
#include <nexus/NeXusException.hpp>
// clang-format on
#include <Poco/File.h>
#include <Poco/Path.h>
#include <cstring>
#include <string>
namespace Mantid {
namespace Kernel {
//---------------------------------------------------------------------------------------------------------------------------
// static NexusDescriptor constants
//---------------------------------------------------------------------------------------------------------------------------
/// Size of HDF magic number
const size_t NexusDescriptor::HDFMagicSize = 4;
/// HDF cookie that is stored in the first 4 bytes of the file.
const unsigned char NexusDescriptor::HDFMagic[4] = {'\016', '\003', '\023', '\001'}; // From HDF4::hfile.h
/// Size of HDF5 signature
size_t NexusDescriptor::HDF5SignatureSize = 8;
/// signature identifying a HDF5 file.
const unsigned char NexusDescriptor::HDF5Signature[8] = {137, 'H', 'D', 'F', '\r', '\n', '\032', '\n'};
namespace {
//---------------------------------------------------------------------------------------------------------------------------
// Anonymous helper methods to use isReadable methods to use an open file handle
//---------------------------------------------------------------------------------------------------------------------------
/**
* Currently simply checks for the HDF signatures and returns true if one of
* them is found
* @param fileHandle A file handled opened and pointing at the start of the
* file. On return the
* fileHandle is left at the start of the file
* @param version One of the NexusDescriptor::Version enumerations specifying
* the required version
* @return True if the file is considered hierarchical, false otherwise
*/
bool isHDFHandle(FILE *fileHandle, NexusDescriptor::Version version) {
if (!fileHandle)
throw std::invalid_argument("HierarchicalFileDescriptor::isHierarchical - Invalid file handle");
bool result(false);
// HDF4 check requires 4 bytes, HDF5 check requires 8 bytes
// Use same buffer and waste a few bytes if only checking HDF4
unsigned char buffer[8] = {'0', '0', '0', '0', '0', '0', '0', '0'};
if (NexusDescriptor::HDF5SignatureSize !=
std::fread(static_cast<void *>(&buffer), sizeof(unsigned char), NexusDescriptor::HDF5SignatureSize, fileHandle)) {
throw std::runtime_error("Error while reading file");
}
// Number of bytes read doesn't matter as if it is not enough then the memory
// simply won't match
// as the buffer has been "zeroed"
if (version == NexusDescriptor::Version5 || version == NexusDescriptor::AnyVersion) {
result = (std::memcmp(&buffer, &NexusDescriptor::HDF5Signature, NexusDescriptor::HDF5SignatureSize) == 0);
}
if (!result && (version == NexusDescriptor::Version4 || version == NexusDescriptor::AnyVersion)) {
result = (std::memcmp(&buffer, &NexusDescriptor::HDFMagic, NexusDescriptor::HDFMagicSize) == 0);
}
// Return file stream to start of file
std::rewind(fileHandle);
return result;
}
} // namespace
//---------------------------------------------------------------------------------------------------------------------------
// static NexusDescriptor methods
//---------------------------------------------------------------------------------------------------------------------------
/**
* Checks for the HDF signatures and returns true if one of them is found
* @param filename A string filename to check
* @param version One of the NexusDescriptor::Version enumerations specifying
* the required version
* @return True if the file is considered hierarchical, false otherwise
*/
bool NexusDescriptor::isReadable(const std::string &filename, const Version version) {
FILE *fd = fopen(filename.c_str(), "rb");
if (!fd) {
throw std::invalid_argument("HierarchicalFileDescriptor::isHierarchical - Unable to open file '" + filename + "'");
}
const bool result = isHDFHandle(fd, version); // use anonymous helper
fclose(fd);
return result;
}
//---------------------------------------------------------------------------------------------------------------------------
// NexusDescriptor public methods
//---------------------------------------------------------------------------------------------------------------------------
/**
* Constructs the wrapper
* @param filename A string pointing to an existing file
* @throws std::invalid_argument if the file is not identified to be
* hierarchical. This currently
* involves simply checking for the signature if a HDF file at the start of the
* file
*/
NexusDescriptor::NexusDescriptor(const std::string &filename, const bool init)
: m_filename(), m_extension(), m_firstEntryNameType(), m_rootAttrs(), m_pathsToTypes(), m_file(nullptr) {
if (filename.empty()) {
throw std::invalid_argument("NexusDescriptor() - Empty filename '" + filename + "'");
}
if (!Poco::File(filename).exists()) {
throw std::invalid_argument("NexusDescriptor() - File '" + filename + "' does not exist");
}
if (init) {
try {
// this is very expesive as it walk the entire file
initialize(filename);
} catch (::NeXus::Exception &e) {
throw std::invalid_argument("NexusDescriptor::initialize - File '" + filename +
"' does not look like a HDF file.\n Error was: " + e.what());
}
}
}
NexusDescriptor::~NexusDescriptor() {}
/// Returns the name & type of the first entry in the file
const std::pair<std::string, std::string> &NexusDescriptor::firstEntryNameType() const { return m_firstEntryNameType; }
/**
* @param name The name of an attribute
* @return True if the attribute exists, false otherwise
*/
bool NexusDescriptor::hasRootAttr(const std::string &name) const { return (m_rootAttrs.count(name) == 1); }
/**
* @param path A string giving a path using UNIX-style path separators (/), e.g.
* /raw_data_1, /entry/bank1
* @return True if the path exists in the file, false otherwise
*/
bool NexusDescriptor::pathExists(const std::string &path) const {
return (m_pathsToTypes.find(path) != m_pathsToTypes.end());
}
/**
* @param path A string giving a path using UNIX-style path separators (/), e.g.
* /raw_data_1, /entry/bank1
* @param type A string specifying the required type
* @return True if the path exists in the file, false otherwise
*/
bool NexusDescriptor::pathOfTypeExists(const std::string &path, const std::string &type) const {
auto it = m_pathsToTypes.find(path);
if (it != m_pathsToTypes.end()) {
return (it->second == type);
} else
return false;
}
/**
* @param type A string specifying the required type
* @return path A string giving a path using UNIX-style path separators (/),
* e.g. /raw_data_1, /entry/bank1
*/
std::string NexusDescriptor::pathOfType(const std::string &type) const {
auto iend = m_pathsToTypes.end();
for (auto it = m_pathsToTypes.begin(); it != iend; ++it) {
if (type == it->second)
return it->first;
}
return "";
}
/**
* @param classType A string name giving a class type
* @return True if the type exists in the file, false otherwise
*/
bool NexusDescriptor::classTypeExists(const std::string &classType) const {
auto iend = m_pathsToTypes.end();
for (auto it = m_pathsToTypes.begin(); it != iend; ++it) {
if (classType == it->second)
return true;
}
return false;
}
//---------------------------------------------------------------------------------------------------------------------------
// NexusDescriptor private methods
//---------------------------------------------------------------------------------------------------------------------------
/**
* Creates the internal cached structure of the file as a tree of nodes
*/
void NexusDescriptor::initialize(const std::string &filename) {
m_filename = filename;
m_extension = "." + Poco::Path(filename).getExtension();
m_file = std::make_unique<::NeXus::File>(this->filename());
m_file->openPath("/");
m_rootAttrs.clear();
m_pathsToTypes.clear();
walkFile(*m_file, "", "", m_pathsToTypes, 0);
}
/**
* Cache the structure in the given maps
* @param file An open NeXus File object
* @param rootPath The current path that is open in the file
* @param className The class of the current open path
* @param pmap [Out] An output map filled with mappings of path->type
* @param level An integer defining the current level in the file
*/
void NexusDescriptor::walkFile(::NeXus::File &file, const std::string &rootPath, const std::string &className,
std::map<std::string, std::string> &pmap, int level) {
if (!rootPath.empty()) {
pmap.emplace(rootPath, className);
}
if (level == 0) {
auto attrInfos = file.getAttrInfos();
for (auto &attrInfo : attrInfos) {
m_rootAttrs.insert(attrInfo.name);
}
}
auto dirents = file.getEntries();
auto itend = dirents.end();
for (auto it = dirents.begin(); it != itend; ++it) {
const std::string &entryName = it->first;
const std::string &entryClass = it->second;
const std::string entryPath = std::string(rootPath).append("/").append(entryName);
if (entryClass == "SDS" || entryClass == "ILL_data_scan_vars" || entryClass == "NXill_data_scan_vars") {
pmap.emplace(entryPath, entryClass);
} else if (entryClass == "CDF0.0") {
// Do nothing with this
} else {
if (level == 0)
m_firstEntryNameType = (*it); // copy first entry name & type
file.openGroup(entryName, entryClass);
walkFile(file, entryPath, entryClass, pmap, level + 1);
}
}
file.closeGroup();
}
} // namespace Kernel
} // namespace Mantid