Mantid
Loading...
Searching...
No Matches
SaveMD.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 +
11#include "MantidAPI/Progress.h"
21#include "MantidKernel/Matrix.h"
23#include <filesystem>
24
25using file_holder_type = std::unique_ptr<Mantid::Nexus::File>;
26
27using namespace Mantid::Kernel;
28using namespace Mantid::API;
29using namespace Mantid::DataObjects;
30
31namespace {
32template <typename MDE, size_t nd>
33void prepareUpdate(MDBoxFlatTree &BoxFlatStruct, BoxController *bc, typename MDEventWorkspace<MDE, nd>::sptr ws,
34 const std::string &filename) {
35 // remove all boxes from the DiskBuffer. DB will calculate boxes positions on HDD.
36 bc->getFileIO()->flushCache();
37 // flatten the box structure; this will remember boxes file positions in the box structure
38 BoxFlatStruct.initFlatStructure(ws, filename);
39}
40} // namespace
41
42namespace Mantid::MDAlgorithms {
43
44// Register the algorithm into the AlgorithmFactory
46
47//----------------------------------------------------------------------------------------------
50void SaveMD::init() {
51 declareProperty(std::make_unique<WorkspaceProperty<IMDWorkspace>>("InputWorkspace", "", Direction::Input),
52 "An input MDEventWorkspace or MDHistoWorkspace.");
53
54 declareProperty(std::make_unique<FileProperty>("Filename", "", FileProperty::OptionalSave, ".nxs"),
55 "The name of the Nexus file to write, as a full or relative path.\n"
56 "Optional if UpdateFileBackEnd is checked.");
57 // Filename is NOT used if UpdateFileBackEnd
58 setPropertySettings("Filename", std::make_unique<EnabledWhenProperty>("UpdateFileBackEnd", IS_EQUAL_TO, "0"));
59
60 declareProperty("UpdateFileBackEnd", false,
61 "Only for MDEventWorkspaces with a file back end: check this to update "
62 "the NXS file on disk\n"
63 "to reflect the current data structure. Filename parameter is ignored.");
64 setPropertySettings("UpdateFileBackEnd", std::make_unique<EnabledWhenProperty>("MakeFileBacked", IS_EQUAL_TO, "0"));
65
66 declareProperty("MakeFileBacked", false,
67 "For an MDEventWorkspace that was created in memory:\n"
68 "This saves it to a file AND makes the workspace into a "
69 "file-backed one.");
70 setPropertySettings("MakeFileBacked", std::make_unique<EnabledWhenProperty>("UpdateFileBackEnd", IS_EQUAL_TO, "0"));
71}
72
73//----------------------------------------------------------------------------------------------
79template <typename MDE, size_t nd> void SaveMD::doSaveEvents(typename MDEventWorkspace<MDE, nd>::sptr ws) {
80 bool updateFileBackend = getProperty("UpdateFileBackEnd");
81 bool makeFileBackend = getProperty("MakeFileBacked");
82 if (updateFileBackend && makeFileBackend)
83 throw std::invalid_argument("Please choose either UpdateFileBackEnd or MakeFileBacked, not both.");
84
85 bool wsIsFileBacked = ws->isFileBacked();
86 std::string filename = getPropertyValue("Filename");
88 auto copyFile = wsIsFileBacked && !filename.empty() && filename != bc->getFilename();
89 if (wsIsFileBacked) {
90 if (makeFileBackend) {
91 throw std::runtime_error("MakeFileBacked selected but workspace is already file backed.");
92 }
93 } else {
94 if (updateFileBackend) {
95 throw std::runtime_error("UpdateFileBackEnd selected but workspace is not file backed.");
96 }
97 }
98
99 if (!wsIsFileBacked) {
100 if (std::filesystem::exists(filename))
101 std::filesystem::remove(filename);
102 }
103
104 auto prog = std::make_unique<Progress>(this, 0.0, 0.05, 1);
105 if (updateFileBackend) // workspace has its own file and ignores any changes to the algorithm parameters
106 {
107 if (!ws->isFileBacked())
108 throw std::runtime_error(" attempt to update non-file backed workspace");
109 filename = bc->getFileIO()->getFileName();
110 }
111
112 //-----------------------------------------------------------------------------------------------------
113 // create or open WS group and put there additional information about WS and its dimensions
114 auto nDims = static_cast<int>(nd);
115 bool data_exist;
116 auto file =
117 file_holder_type(MDBoxFlatTree::createOrOpenMDWSgroup(filename, nDims, MDE::getTypeName(), false, data_exist));
118
119 // Save each NEW ExperimentInfo to a spot in the file
121 if (!updateFileBackend || !data_exist) {
122 MDBoxFlatTree::saveWSGenericInfo(file.get(), ws);
123 }
124 file->closeGroup();
125 file->close();
126
127 MDBoxFlatTree BoxFlatStruct;
128 //-----------------------------------------------------------------------------------------------------
129 if (updateFileBackend) // the workspace is already file backed;
130 {
131 prepareUpdate<MDE, nd>(BoxFlatStruct, bc.get(), ws, filename);
132 } else if (copyFile) {
133 // Update the original file
134 if (ws->fileNeedsUpdating()) {
135 prepareUpdate<MDE, nd>(BoxFlatStruct, bc.get(), ws, filename);
136 BoxFlatStruct.saveBoxStructure(filename);
137 }
138 bc->getFileIO()->copyFileTo(filename);
139 } else // not file backed;
140 {
141 // the boxes file positions are unknown and we need to calculate it.
142 BoxFlatStruct.initFlatStructure(ws, filename);
143 // create saver class
144 auto Saver = std::shared_ptr<API::IBoxControllerIO>(new DataObjects::BoxControllerNeXusIO(bc.get()));
145 Saver->setDataType(sizeof(coord_t), MDE::getTypeName());
146 if (makeFileBackend) {
147 // store saver with box controller
148 bc->setFileBacked(Saver, filename);
149 // get access to boxes array
150 std::vector<API::IMDNode *> &boxes = BoxFlatStruct.getBoxes();
151 // calculate the position of the boxes on file, indicating to make them
152 // saveable and that the boxes were not saved.
153 BoxFlatStruct.setBoxesFilePositions(true);
154 prog->resetNumSteps(boxes.size(), 0.06, 0.90);
155 for (auto &boxe : boxes) {
156 auto saveableTag = boxe->getISaveable();
157 if (saveableTag) // only boxes can be saveable
158 {
159 // do not spend time on empty or masked boxes
160 if (boxe->getDataInMemorySize() == 0 || boxe->getIsMasked())
161 continue;
162 // save boxes directly using the boxes file postion, precalculated in boxFlatStructure.
163 saveableTag->save();
164 // remove boxes data from memory. This will actually correctly set the
165 // tag indicatin that data were not loaded.
166 saveableTag->clearDataFromMemory();
167 // put boxes into write buffer wich will save them when necessary
168 // Saver->toWrite(saveTag);
169 prog->report("Saving Box");
170 }
171 }
172 // remove everything from diskBuffer; (not sure if it really necessary
173 // but just in case , should not make any harm)
174 Saver->flushCache();
175 // drop NeXus on HDD (not sure if it really necessary but just in case )
176 Saver->flushData();
177 } else // just save data, and finish with it
178 {
179 Saver->openFile(filename, "w");
180 BoxFlatStruct.setBoxesFilePositions(false);
181 std::vector<API::IMDNode *> &boxes = BoxFlatStruct.getBoxes();
182 std::vector<uint64_t> &eventIndex = BoxFlatStruct.getEventIndex();
183 prog->resetNumSteps(boxes.size(), 0.06, 0.90);
184 for (size_t i = 0; i < boxes.size(); i++) {
185 if (eventIndex[2 * i + 1] == 0 || boxes[i]->getIsMasked())
186 continue;
187 boxes[i]->saveAt(Saver.get(), eventIndex[2 * i]);
188 prog->report("Saving Box");
189 }
190 Saver->closeFile();
191 }
192 }
193
194 // -------------- Save Box Structure -------------------------------------
195 // OK, we've filled these big arrays of data representing flat box structrre.
196 // Save them.
197 progress(0.91, "Writing Box Data");
198 prog->resetNumSteps(8, 0.92, 1.00);
199
200 // Save box structure;
201 if (!copyFile) {
202 BoxFlatStruct.saveBoxStructure(filename);
203 }
204
205 ws->setFileNeedsUpdating(false);
206}
207
208//----------------------------------------------------------------------------------------------
214 std::string filename = getPropertyValue("Filename");
215
216 // Erase the file if it exists
217 if (std::filesystem::exists(filename))
218 std::filesystem::remove(filename);
219
220 // Create a new file in HDF5 mode.
221 auto file = std::make_unique<Nexus::File>(filename, NXaccess::CREATE5);
222
223 // The base entry. Named so as to distinguish from other workspace types.
224 file->makeGroup("MDHistoWorkspace", "NXentry", true);
225
226 // Write out the coordinate system
227 file->writeData("coordinate_system", static_cast<uint32_t>(ws->getSpecialCoordinateSystem()));
228
229 // Write out the set display normalization
230 file->writeData("visual_normalization", static_cast<uint32_t>(ws->displayNormalization()));
231
232 // Save the algorithm history under "process"
233 ws->getHistory().saveNexus(file.get());
234
235 // Save all the ExperimentInfos
236 for (uint16_t i = 0; i < ws->getNumExperimentInfo(); i++) {
237 ExperimentInfo_sptr ei = ws->getExperimentInfo(i);
238 std::string groupName = "experiment" + Strings::toString(i);
239 if (ei) {
240 // Can't overwrite entries. Just add the new ones
241 file->makeGroup(groupName, "NXgroup", true);
242 file->putAttr("version", 1);
243 ei->saveExperimentInfoNexus(file.get());
244 file->closeGroup();
245 }
246 }
247
248 // Write out some general information like # of dimensions
249 file->writeData("dimensions", int32_t(ws->getNumDims()));
250
251 // Save each dimension, as their XML representation
252 for (size_t d = 0; d < ws->getNumDims(); d++) {
253 std::ostringstream mess;
254 mess << "dimension" << d;
255 file->putAttr(mess.str(), ws->getDimension(d)->toXMLString());
256 }
257
258 // Write out the affine matrices
259 MDBoxFlatTree::saveAffineTransformMatricies(file.get(), std::dynamic_pointer_cast<const IMDWorkspace>(ws));
260
261 // Check that the typedef has not been changed. The NeXus types would need
262 // changing if it does!
263 static_assert(sizeof(signal_t) == sizeof(double), "signal_t using has been changed!");
264
265 // Number of data points
266 auto nPoints = static_cast<int>(ws->getNPoints());
267
268 file->makeData("signal", NXnumtype::FLOAT64, nPoints, true);
269 file->putData(ws->getSignalArray());
270 file->closeData();
271
272 file->makeData("errors_squared", NXnumtype::FLOAT64, nPoints, true);
273 file->putData(ws->getErrorSquaredArray());
274 file->closeData();
275
276 file->makeData("num_events", NXnumtype::FLOAT64, nPoints, true);
277 file->putData(ws->getNumEventsArray());
278 file->closeData();
279
280 file->makeData("mask", NXnumtype::INT8, nPoints, true);
281 file->putData(ws->getMaskArray());
282 file->closeData();
283
284 // TODO: Links to original workspace???
285
286 file->closeGroup();
287 file->close();
288}
289
290//----------------------------------------------------------------------------------------------
294 IMDWorkspace_sptr ws = getProperty("InputWorkspace");
295 IMDEventWorkspace_sptr eventWS = std::dynamic_pointer_cast<IMDEventWorkspace>(ws);
296 MDHistoWorkspace_sptr histoWS = std::dynamic_pointer_cast<MDHistoWorkspace>(ws);
297
298 if (eventWS) {
299 // Wrapper to cast to MDEventWorkspace then call the function
300 CALL_MDEVENT_FUNCTION(this->doSaveEvents, eventWS);
301 } else if (histoWS) {
302 this->doSaveHisto(histoWS);
303 } else
304 throw std::runtime_error("SaveMD can only save MDEventWorkspaces and "
305 "MDHistoWorkspaces.\nPlease use SaveNexus or "
306 "another algorithm appropriate for this workspace "
307 "type.");
308}
309
310} // namespace Mantid::MDAlgorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
std::unique_ptr< Mantid::Nexus::File > file_holder_type
#define CALL_MDEVENT_FUNCTION(funcname, workspace)
Macro that makes it possible to call a templated method for a MDEventWorkspace using a IMDEventWorksp...
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.
This class is used by MDBox and MDGridBox in order to intelligently determine optimal behavior.
IBoxControllerIO * getFileIO()
returns the pointer to the class, responsible for fileIO operations;
@ OptionalSave
to specify a file to write to but an empty string is
void setFileNeedsUpdating(bool value)
Sets the marker set to true when a file-backed workspace needs its back-end file updated (by calling ...
A property class for workspaces.
The class responsible for saving events into nexus file using generic box controller interface Expect...
The class responsible for saving/loading MD boxes structure to/from HDD and for flattening/restoring ...
std::vector< API::IMDNode * > & getBoxes()
void initFlatStructure(const API::IMDEventWorkspace_sptr &pws, const std::string &fileName)
convert MDWS box structure into flat structure used for saving/loading on hdd
std::vector< uint64_t > & getEventIndex()
static void saveAffineTransformMatricies(Mantid::Nexus::File *const file, const API::IMDWorkspace_const_sptr &ws)
Save the affine matrices to both directional conversions to the data.
static void saveWSGenericInfo(Mantid::Nexus::File *const file, const API::IMDWorkspace_const_sptr &ws)
Save workspace generic info like dimension structure, history, title dimensions etc.
void saveBoxStructure(const std::string &fileName)
Save flat box structure into a file, defined by the file name.
void setBoxesFilePositions(bool setFileBacked)
static Mantid::Nexus::File * createOrOpenMDWSgroup(const std::string &fileName, int &nDims, const std::string &WSEventType, bool readOnly, bool &alreadyExists)
The function to create a NeXus MD workspace group with specified events type and number of dimensions...
static void saveExperimentInfos(Mantid::Nexus::File *const file, const API::IMDEventWorkspace_const_sptr &ws)
Save each NEW ExperimentInfo to a spot in the file.
std::shared_ptr< MDEventWorkspace< MDE, nd > > sptr
Typedef for a shared pointer of this kind of event workspace.
Mantid::API::BoxController_sptr getBoxController() override
Returns the BoxController used in this workspace.
static std::string getTypeName()
void flushCache()
Flush out all the data in the memory; and writes out everything in the to-write cache.
Save a MDEventWorkspace to a .nxs file.
Definition SaveMD.h:22
void doSaveEvents(typename DataObjects::MDEventWorkspace< MDE, nd >::sptr ws)
Helper method.
Definition SaveMD.cpp:79
void exec() override
Run the algorithm.
Definition SaveMD.cpp:293
void doSaveHisto(const Mantid::DataObjects::MDHistoWorkspace_sptr &ws)
Save the MDHistoWorkspace.
Definition SaveMD.cpp:213
static unsigned short constexpr INT8
static unsigned short constexpr FLOAT64
std::shared_ptr< IMDEventWorkspace > IMDEventWorkspace_sptr
Shared pointer to Mantid::API::IMDEventWorkspace.
std::shared_ptr< ExperimentInfo > ExperimentInfo_sptr
Shared pointer to ExperimentInfo.
std::shared_ptr< IMDWorkspace > IMDWorkspace_sptr
Shared pointer to the IMDWorkspace base class.
std::shared_ptr< BoxController > BoxController_sptr
Shared ptr to BoxController.
std::shared_ptr< MDHistoWorkspace > MDHistoWorkspace_sptr
A shared pointer to a MDHistoWorkspace.
std::string toString(const T &value)
Convert a number to a string.
Definition Strings.cpp:734
float coord_t
Typedef for the data type to use for coordinate axes in MD objects such as MDBox, MDEventWorkspace,...
Definition MDTypes.h:27
double signal_t
Typedef for the signal recorded in a MDBox, etc.
Definition MDTypes.h:36
@ Input
An input workspace.
Definition Property.h:53