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 <Poco/File.h>
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 Poco::File oldFile(filename);
101 if (oldFile.exists())
102 oldFile.remove();
103 }
104
105 auto prog = std::make_unique<Progress>(this, 0.0, 0.05, 1);
106 if (updateFileBackend) // workspace has its own file and ignores any changes to the algorithm parameters
107 {
108 if (!ws->isFileBacked())
109 throw std::runtime_error(" attempt to update non-file backed workspace");
110 filename = bc->getFileIO()->getFileName();
111 }
112
113 //-----------------------------------------------------------------------------------------------------
114 // create or open WS group and put there additional information about WS and its dimensions
115 auto nDims = static_cast<int>(nd);
116 bool data_exist;
117 auto file =
118 file_holder_type(MDBoxFlatTree::createOrOpenMDWSgroup(filename, nDims, MDE::getTypeName(), false, data_exist));
119
120 // Save each NEW ExperimentInfo to a spot in the file
122 if (!updateFileBackend || !data_exist) {
123 MDBoxFlatTree::saveWSGenericInfo(file.get(), ws);
124 }
125 file->closeGroup();
126 file->close();
127
128 MDBoxFlatTree BoxFlatStruct;
129 //-----------------------------------------------------------------------------------------------------
130 if (updateFileBackend) // the workspace is already file backed;
131 {
132 prepareUpdate<MDE, nd>(BoxFlatStruct, bc.get(), ws, filename);
133 } else if (copyFile) {
134 // Update the original file
135 if (ws->fileNeedsUpdating()) {
136 prepareUpdate<MDE, nd>(BoxFlatStruct, bc.get(), ws, filename);
137 BoxFlatStruct.saveBoxStructure(filename);
138 }
139 bc->getFileIO()->copyFileTo(filename);
140 } else // not file backed;
141 {
142 // the boxes file positions are unknown and we need to calculate it.
143 BoxFlatStruct.initFlatStructure(ws, filename);
144 // create saver class
145 auto Saver = std::shared_ptr<API::IBoxControllerIO>(new DataObjects::BoxControllerNeXusIO(bc.get()));
146 Saver->setDataType(sizeof(coord_t), MDE::getTypeName());
147 if (makeFileBackend) {
148 // store saver with box controller
149 bc->setFileBacked(Saver, filename);
150 // get access to boxes array
151 std::vector<API::IMDNode *> &boxes = BoxFlatStruct.getBoxes();
152 // calculate the position of the boxes on file, indicating to make them
153 // saveable and that the boxes were not saved.
154 BoxFlatStruct.setBoxesFilePositions(true);
155 prog->resetNumSteps(boxes.size(), 0.06, 0.90);
156 for (auto &boxe : boxes) {
157 auto saveableTag = boxe->getISaveable();
158 if (saveableTag) // only boxes can be saveable
159 {
160 // do not spend time on empty or masked boxes
161 if (boxe->getDataInMemorySize() == 0 || boxe->getIsMasked())
162 continue;
163 // save boxes directly using the boxes file postion, precalculated in boxFlatStructure.
164 saveableTag->save();
165 // remove boxes data from memory. This will actually correctly set the
166 // tag indicatin that data were not loaded.
167 saveableTag->clearDataFromMemory();
168 // put boxes into write buffer wich will save them when necessary
169 // Saver->toWrite(saveTag);
170 prog->report("Saving Box");
171 }
172 }
173 // remove everything from diskBuffer; (not sure if it really necessary
174 // but just in case , should not make any harm)
175 Saver->flushCache();
176 // drop NeXus on HDD (not sure if it really necessary but just in case )
177 Saver->flushData();
178 } else // just save data, and finish with it
179 {
180 Saver->openFile(filename, "w");
181 BoxFlatStruct.setBoxesFilePositions(false);
182 std::vector<API::IMDNode *> &boxes = BoxFlatStruct.getBoxes();
183 std::vector<uint64_t> &eventIndex = BoxFlatStruct.getEventIndex();
184 prog->resetNumSteps(boxes.size(), 0.06, 0.90);
185 for (size_t i = 0; i < boxes.size(); i++) {
186 if (eventIndex[2 * i + 1] == 0 || boxes[i]->getIsMasked())
187 continue;
188 boxes[i]->saveAt(Saver.get(), eventIndex[2 * i]);
189 prog->report("Saving Box");
190 }
191 Saver->closeFile();
192 }
193 }
194
195 // -------------- Save Box Structure -------------------------------------
196 // OK, we've filled these big arrays of data representing flat box structrre.
197 // Save them.
198 progress(0.91, "Writing Box Data");
199 prog->resetNumSteps(8, 0.92, 1.00);
200
201 // Save box structure;
202 if (!copyFile) {
203 BoxFlatStruct.saveBoxStructure(filename);
204 }
205
206 ws->setFileNeedsUpdating(false);
207}
208
209//----------------------------------------------------------------------------------------------
215 std::string filename = getPropertyValue("Filename");
216
217 // Erase the file if it exists
218 Poco::File oldFile(filename);
219 if (oldFile.exists())
220 oldFile.remove();
221
222 // Create a new file in HDF5 mode.
223 auto file = std::make_unique<Nexus::File>(filename, NXaccess::CREATE5);
224
225 // The base entry. Named so as to distinguish from other workspace types.
226 file->makeGroup("MDHistoWorkspace", "NXentry", true);
227
228 // Write out the coordinate system
229 file->writeData("coordinate_system", static_cast<uint32_t>(ws->getSpecialCoordinateSystem()));
230
231 // Write out the set display normalization
232 file->writeData("visual_normalization", static_cast<uint32_t>(ws->displayNormalization()));
233
234 // Save the algorithm history under "process"
235 ws->getHistory().saveNexus(file.get());
236
237 // Save all the ExperimentInfos
238 for (uint16_t i = 0; i < ws->getNumExperimentInfo(); i++) {
239 ExperimentInfo_sptr ei = ws->getExperimentInfo(i);
240 std::string groupName = "experiment" + Strings::toString(i);
241 if (ei) {
242 // Can't overwrite entries. Just add the new ones
243 file->makeGroup(groupName, "NXgroup", true);
244 file->putAttr("version", 1);
245 ei->saveExperimentInfoNexus(file.get());
246 file->closeGroup();
247 }
248 }
249
250 // Write out some general information like # of dimensions
251 file->writeData("dimensions", int32_t(ws->getNumDims()));
252
253 // Save each dimension, as their XML representation
254 for (size_t d = 0; d < ws->getNumDims(); d++) {
255 std::ostringstream mess;
256 mess << "dimension" << d;
257 file->putAttr(mess.str(), ws->getDimension(d)->toXMLString());
258 }
259
260 // Write out the affine matrices
261 MDBoxFlatTree::saveAffineTransformMatricies(file.get(), std::dynamic_pointer_cast<const IMDWorkspace>(ws));
262
263 // Check that the typedef has not been changed. The NeXus types would need
264 // changing if it does!
265 static_assert(sizeof(signal_t) == sizeof(double), "signal_t using has been changed!");
266
267 // Number of data points
268 auto nPoints = static_cast<int>(ws->getNPoints());
269
270 file->makeData("signal", NXnumtype::FLOAT64, nPoints, true);
271 file->putData(ws->getSignalArray());
272 file->closeData();
273
274 file->makeData("errors_squared", NXnumtype::FLOAT64, nPoints, true);
275 file->putData(ws->getErrorSquaredArray());
276 file->closeData();
277
278 file->makeData("num_events", NXnumtype::FLOAT64, nPoints, true);
279 file->putData(ws->getNumEventsArray());
280 file->closeData();
281
282 file->makeData("mask", NXnumtype::INT8, nPoints, true);
283 file->putData(ws->getMaskArray());
284 file->closeData();
285
286 // TODO: Links to original workspace???
287
288 file->closeGroup();
289 file->close();
290}
291
292//----------------------------------------------------------------------------------------------
296 IMDWorkspace_sptr ws = getProperty("InputWorkspace");
297 IMDEventWorkspace_sptr eventWS = std::dynamic_pointer_cast<IMDEventWorkspace>(ws);
298 MDHistoWorkspace_sptr histoWS = std::dynamic_pointer_cast<MDHistoWorkspace>(ws);
299
300 if (eventWS) {
301 // Wrapper to cast to MDEventWorkspace then call the function
302 CALL_MDEVENT_FUNCTION(this->doSaveEvents, eventWS);
303 } else if (histoWS) {
304 this->doSaveHisto(histoWS);
305 } else
306 throw std::runtime_error("SaveMD can only save MDEventWorkspaces and "
307 "MDHistoWorkspaces.\nPlease use SaveNexus or "
308 "another algorithm appropriate for this workspace "
309 "type.");
310}
311
312} // 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:295
void doSaveHisto(const Mantid::DataObjects::MDHistoWorkspace_sptr &ws)
Save the MDHistoWorkspace.
Definition SaveMD.cpp:214
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