/
SaveMD.cpp
351 lines (306 loc) · 12.7 KB
/
SaveMD.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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
#include "MantidAPI/CoordTransform.h"
#include "MantidAPI/FileProperty.h"
#include "MantidAPI/IMDEventWorkspace.h"
#include "MantidKernel/Matrix.h"
#include "MantidKernel/System.h"
#include "MantidDataObjects/MDBoxIterator.h"
#include "MantidDataObjects/MDEventFactory.h"
#include "MantidDataObjects/MDEventWorkspace.h"
#include "MantidMDAlgorithms/SaveMD.h"
#include "MantidDataObjects/MDBox.h"
#include "MantidAPI/Progress.h"
#include "MantidKernel/EnabledWhenProperty.h"
#include <Poco/File.h>
#include "MantidDataObjects/MDHistoWorkspace.h"
#include "MantidDataObjects/MDBoxFlatTree.h"
#include "MantidDataObjects/BoxControllerNeXusIO.h"
typedef std::unique_ptr<::NeXus::File> file_holder_type;
using namespace Mantid::Kernel;
using namespace Mantid::API;
using namespace Mantid::DataObjects;
namespace {
template <typename MDE, size_t nd>
void prepareUpdate(MDBoxFlatTree &BoxFlatStruct, BoxController *bc,
typename MDEventWorkspace<MDE, nd>::sptr ws,
std::string filename) {
// remove all boxes from the DiskBuffer. DB will calculate boxes positions
// on HDD.
bc->getFileIO()->flushCache();
// flatten the box structure; this will remember boxes file positions in the
// box structure
BoxFlatStruct.initFlatStructure(ws, filename);
}
}
namespace Mantid {
namespace MDAlgorithms {
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(SaveMD)
//----------------------------------------------------------------------------------------------
/** Constructor
*/
SaveMD::SaveMD() {}
//----------------------------------------------------------------------------------------------
/** Destructor
*/
SaveMD::~SaveMD() {}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
*/
void SaveMD::init() {
declareProperty(make_unique<WorkspaceProperty<IMDWorkspace>>(
"InputWorkspace", "", Direction::Input),
"An input MDEventWorkspace or MDHistoWorkspace.");
declareProperty(
make_unique<FileProperty>("Filename", "", FileProperty::OptionalSave,
".nxs"),
"The name of the Nexus file to write, as a full or relative path.\n"
"Optional if UpdateFileBackEnd is checked.");
// Filename is NOT used if UpdateFileBackEnd
setPropertySettings("Filename", make_unique<EnabledWhenProperty>(
"UpdateFileBackEnd", IS_EQUAL_TO, "0"));
declareProperty(
"UpdateFileBackEnd", false,
"Only for MDEventWorkspaces with a file back end: check this to update "
"the NXS file on disk\n"
"to reflect the current data structure. Filename parameter is ignored.");
setPropertySettings(
"UpdateFileBackEnd",
make_unique<EnabledWhenProperty>("MakeFileBacked", IS_EQUAL_TO, "0"));
declareProperty("MakeFileBacked", false,
"For an MDEventWorkspace that was created in memory:\n"
"This saves it to a file AND makes the workspace into a "
"file-backed one.");
setPropertySettings(
"MakeFileBacked",
make_unique<EnabledWhenProperty>("UpdateFileBackEnd", IS_EQUAL_TO, "0"));
}
//----------------------------------------------------------------------------------------------
/** Save the MDEventWorskpace to a file.
* Based on the Intermediate Data Format Detailed Design Document, v.1.R3 found
*in SVN.
*
* @param ws :: MDEventWorkspace of the given type
*/
template <typename MDE, size_t nd>
void SaveMD::doSaveEvents(typename MDEventWorkspace<MDE, nd>::sptr ws) {
bool updateFileBackend = getProperty("UpdateFileBackEnd");
bool makeFileBackend = getProperty("MakeFileBacked");
if (updateFileBackend && makeFileBackend)
throw std::invalid_argument(
"Please choose either UpdateFileBackEnd or MakeFileBacked, not both.");
bool wsIsFileBacked = ws->isFileBacked();
std::string filename = getPropertyValue("Filename");
BoxController_sptr bc = ws->getBoxController();
auto copyFile =
wsIsFileBacked && !filename.empty() && filename != bc->getFilename();
if (wsIsFileBacked) {
if (makeFileBackend) {
throw std::runtime_error(
"MakeFileBacked selected but workspace is already file backed.");
}
} else {
if (updateFileBackend) {
throw std::runtime_error(
"UpdateFileBackEnd selected but workspace is not file backed.");
}
}
if (!wsIsFileBacked) {
Poco::File oldFile(filename);
if (oldFile.exists())
oldFile.remove();
}
auto prog = new Progress(this, 0.0, 0.05, 1);
if (updateFileBackend) // workspace has its own file and ignores any changes
// to the
// algorithm parameters
{
if (!ws->isFileBacked())
throw std::runtime_error(" attempt to update non-file backed workspace");
filename = bc->getFileIO()->getFileName();
}
//-----------------------------------------------------------------------------------------------------
// create or open WS group and put there additional information about WS and
// its dimensions
int nDims = static_cast<int>(nd);
bool data_exist;
auto file = file_holder_type(MDBoxFlatTree::createOrOpenMDWSgroup(
filename, nDims, MDE::getTypeName(), false, data_exist));
// Save each NEW ExperimentInfo to a spot in the file
MDBoxFlatTree::saveExperimentInfos(file.get(), ws);
if (!updateFileBackend || !data_exist) {
MDBoxFlatTree::saveWSGenericInfo(file.get(), ws);
}
file->closeGroup();
file->close();
MDBoxFlatTree BoxFlatStruct;
//-----------------------------------------------------------------------------------------------------
if (updateFileBackend) // the workspace is already file backed;
{
prepareUpdate<MDE, nd>(BoxFlatStruct, bc.get(), ws, filename);
} else if (copyFile) {
// Update the original file
if (ws->fileNeedsUpdating()) {
prepareUpdate<MDE, nd>(BoxFlatStruct, bc.get(), ws, filename);
BoxFlatStruct.saveBoxStructure(filename);
}
Poco::File(bc->getFilename()).copyTo(filename);
} else // not file backed;
{
// the boxes file positions are unknown and we need to calculate it.
BoxFlatStruct.initFlatStructure(ws, filename);
// create saver class
auto Saver = boost::shared_ptr<API::IBoxControllerIO>(
new DataObjects::BoxControllerNeXusIO(bc.get()));
Saver->setDataType(sizeof(coord_t), MDE::getTypeName());
if (makeFileBackend) {
// store saver with box controller
bc->setFileBacked(Saver, filename);
// get access to boxes array
std::vector<API::IMDNode *> &boxes = BoxFlatStruct.getBoxes();
// calculate the position of the boxes on file, indicating to make them
// saveable and that the boxes were not saved.
BoxFlatStruct.setBoxesFilePositions(true);
prog->resetNumSteps(boxes.size(), 0.06, 0.90);
for (auto &boxe : boxes) {
auto saveableTag = boxe->getISaveable();
if (saveableTag) // only boxes can be saveable
{
// do not spend time on empty or masked boxes
if (boxe->getDataInMemorySize() == 0 || boxe->getIsMasked())
continue;
// save boxes directly using the boxes file postion, precalculated in
// boxFlatStructure.
saveableTag->save();
// remove boxes data from memory. This will actually correctly set the
// tag indicatin that data were not loaded.
saveableTag->clearDataFromMemory();
// put boxes into write buffer wich will save them when necessary
// Saver->toWrite(saveTag);
prog->report("Saving Box");
}
}
// remove everything from diskBuffer; (not sure if it really necessary
// but just in case , should not make any harm)
Saver->flushCache();
// drop NeXus on HDD (not sure if it really necessary but just in case )
Saver->flushData();
} else // just save data, and finish with it
{
Saver->openFile(filename, "w");
BoxFlatStruct.setBoxesFilePositions(false);
std::vector<API::IMDNode *> &boxes = BoxFlatStruct.getBoxes();
std::vector<uint64_t> &eventIndex = BoxFlatStruct.getEventIndex();
prog->resetNumSteps(boxes.size(), 0.06, 0.90);
for (size_t i = 0; i < boxes.size(); i++) {
if (eventIndex[2 * i + 1] == 0 || boxes[i]->getIsMasked())
continue;
boxes[i]->saveAt(Saver.get(), eventIndex[2 * i]);
prog->report("Saving Box");
}
Saver->closeFile();
}
}
// -------------- Save Box Structure -------------------------------------
// OK, we've filled these big arrays of data representing flat box structrre.
// Save them.
progress(0.91, "Writing Box Data");
prog->resetNumSteps(8, 0.92, 1.00);
// Save box structure;
if (!copyFile) {
BoxFlatStruct.saveBoxStructure(filename);
}
delete prog;
ws->setFileNeedsUpdating(false);
}
//----------------------------------------------------------------------------------------------
/** Save a MDHistoWorkspace to a .nxs file
*
* @param ws :: MDHistoWorkspace to save
*/
void SaveMD::doSaveHisto(Mantid::DataObjects::MDHistoWorkspace_sptr ws) {
std::string filename = getPropertyValue("Filename");
// Erase the file if it exists
Poco::File oldFile(filename);
if (oldFile.exists())
oldFile.remove();
// Create a new file in HDF5 mode.
::NeXus::File *file;
file = new ::NeXus::File(filename, NXACC_CREATE5);
// The base entry. Named so as to distinguish from other workspace types.
file->makeGroup("MDHistoWorkspace", "NXentry", true);
// Write out the coordinate system
file->writeData("coordinate_system",
static_cast<uint32_t>(ws->getSpecialCoordinateSystem()));
// Write out the set display normalization
file->writeData("visual_normalization",
static_cast<uint32_t>(ws->displayNormalization()));
// Save the algorithm history under "process"
ws->getHistory().saveNexus(file);
// Save all the ExperimentInfos
for (uint16_t i = 0; i < ws->getNumExperimentInfo(); i++) {
ExperimentInfo_sptr ei = ws->getExperimentInfo(i);
std::string groupName = "experiment" + Strings::toString(i);
if (ei) {
// Can't overwrite entries. Just add the new ones
file->makeGroup(groupName, "NXgroup", true);
file->putAttr("version", 1);
ei->saveExperimentInfoNexus(file);
file->closeGroup();
}
}
// Write out some general information like # of dimensions
file->writeData("dimensions", int32_t(ws->getNumDims()));
// Save each dimension, as their XML representation
for (size_t d = 0; d < ws->getNumDims(); d++) {
std::ostringstream mess;
mess << "dimension" << d;
file->putAttr(mess.str(), ws->getDimension(d)->toXMLString());
}
// Write out the affine matrices
MDBoxFlatTree::saveAffineTransformMatricies(
file, boost::dynamic_pointer_cast<const IMDWorkspace>(ws));
// Check that the typedef has not been changed. The NeXus types would need
// changing if it does!
static_assert(sizeof(signal_t) == sizeof(double),
"signal_t typedef has been changed!");
// Number of data points
int nPoints = static_cast<int>(ws->getNPoints());
file->makeData("signal", ::NeXus::FLOAT64, nPoints, true);
file->putData(ws->getSignalArray());
file->closeData();
file->makeData("errors_squared", ::NeXus::FLOAT64, nPoints, true);
file->putData(ws->getErrorSquaredArray());
file->closeData();
file->makeData("num_events", ::NeXus::FLOAT64, nPoints, true);
file->putData(ws->getNumEventsArray());
file->closeData();
file->makeData("mask", ::NeXus::INT8, nPoints, true);
file->putData(ws->getMaskArray());
file->closeData();
// TODO: Links to original workspace???
file->closeGroup();
file->close();
}
//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
*/
void SaveMD::exec() {
IMDWorkspace_sptr ws = getProperty("InputWorkspace");
IMDEventWorkspace_sptr eventWS =
boost::dynamic_pointer_cast<IMDEventWorkspace>(ws);
MDHistoWorkspace_sptr histoWS =
boost::dynamic_pointer_cast<MDHistoWorkspace>(ws);
if (eventWS) {
// Wrapper to cast to MDEventWorkspace then call the function
CALL_MDEVENT_FUNCTION(this->doSaveEvents, eventWS);
} else if (histoWS) {
this->doSaveHisto(histoWS);
} else
throw std::runtime_error("SaveMD can only save MDEventWorkspaces and "
"MDHistoWorkspaces.\nPlease use SaveNexus or "
"another algorithm appropriate for this workspace "
"type.");
}
} // namespace Mantid
} // namespace DataObjects