Mantid
Loading...
Searching...
No Matches
SaveNXTomo.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
12#include "MantidAPI/Run.h"
14
16
19
22
23// clang-format off
24#include <nexus/NeXusFile.hpp>
25#include <nexus/NeXusException.hpp>
26// clang-format on
27
28namespace Mantid::DataHandling {
29// Register the algorithm into the algorithm factory
30DECLARE_ALGORITHM(SaveNXTomo)
31
32using namespace Kernel;
33using namespace API;
34using namespace DataObjects;
35
36const std::string SaveNXTomo::NXTOMO_VER = "2.0";
37
39 : API::Algorithm(), m_includeError(false), m_overwriteFile(false), m_spectraCount(0), m_filename("") {}
40
45 auto wsValidator = std::make_shared<CompositeValidator>();
46 // Note: this would be better, but it is too restrictive in
47 // practice when saving image workspaces loaded from different
48 // formats than FITS or not so standard FITS.
49 // wsValidator->add<API::CommonBinsValidator>();
50 wsValidator->add<API::HistogramValidator>();
51
52 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspaces", "", Direction::Input, wsValidator),
53 "The name of the workspace(s) to save - this can be the name of a single "
54 "Workspace2D or the name of a WorkspaceGroup in which case all the "
55 "Workspace2Ds included in the group will be saved.");
56
58 std::make_unique<API::FileProperty>("Filename", "", FileProperty::Save, std::vector<std::string>(1, ".nxs")),
59 "The name of the NXTomo file to write, as a full or relative path");
60
61 declareProperty(std::make_unique<PropertyWithValue<bool>>("OverwriteFile", false, Kernel::Direction::Input),
62 "Replace any existing file of the same name instead of appending data?");
63
64 declareProperty(std::make_unique<PropertyWithValue<bool>>("IncludeError", false, Kernel::Direction::Input),
65 "Write the error values to NXTomo file?");
66}
67
73 try {
74 MatrixWorkspace_sptr m = getProperty("InputWorkspaces");
75 m_workspaces.emplace_back(std::dynamic_pointer_cast<Workspace2D>(m));
76 } catch (...) {
77 }
78
79 if (!m_workspaces.empty())
80 processAll();
81}
82
87 try {
88 std::string name = getPropertyValue("InputWorkspaces");
90
91 for (int i = 0; i < groupWS->getNumberOfEntries(); ++i) {
92 m_workspaces.emplace_back(std::dynamic_pointer_cast<Workspace2D>(groupWS->getItem(i)));
93 }
94 } catch (...) {
95 }
96
97 if (!m_workspaces.empty())
98 processAll();
99
100 return true;
101}
102
110 m_includeError = getProperty("IncludeError");
111 m_overwriteFile = getProperty("OverwriteFile");
112
113 for (auto &workspace : m_workspaces) {
114 const std::string workspaceID = workspace->id();
115
116 if ((workspaceID.find("Workspace2D") == std::string::npos) &&
117 (workspaceID.find("RebinnedOutput") == std::string::npos))
118 throw Exception::NotImplementedError("SaveNXTomo passed invalid workspaces. Must be Workspace2D");
119 }
120
121 // Retrieve the filename from the properties
122 this->m_filename = getPropertyValue("Filename");
123
124 // Populate the dimension array - assume all are the same
125 m_dimensions.emplace_back(m_workspaces.size());
126 m_dimensions.emplace_back(boost::lexical_cast<int64_t>(m_workspaces[0]->mutableRun().getLogData("Axis1")->value()));
127 m_dimensions.emplace_back(boost::lexical_cast<int64_t>(m_workspaces[0]->mutableRun().getLogData("Axis2")->value()));
128
130
131 // Define
133 m_infDimensions[0] = NX_UNLIMITED;
134
135 // What size slabs are we going to write
136 m_slabSize.emplace_back(1);
137 m_slabSize.emplace_back(m_dimensions[1]);
138 m_slabSize.emplace_back(m_dimensions[2]);
139
140 // Init start to first row
141 m_slabStart.emplace_back(0);
142 m_slabStart.emplace_back(0);
143 m_slabStart.emplace_back(0);
144
145 ::NeXus::File nxFile = setupFile();
146
147 // Create a progress reporting object
148 Progress progress(this, 0.0, 1.0, m_workspaces.size());
149
150 for (auto &workspace : m_workspaces) {
152 progress.report();
153 }
154
155 nxFile.close();
156}
157
164::NeXus::File SaveNXTomo::setupFile() {
165 // Try and open the file, if it doesn't exist, create it.
166 NXhandle fileHandle;
167 NXstatus status = NXopen(this->m_filename.c_str(), NXACC_RDWR, &fileHandle);
168
169 if (status != NX_ERROR && !m_overwriteFile) {
170 // Appending to an existing file, return reference to the file
171 ::NeXus::File nxFile(fileHandle);
172 return nxFile;
173 }
174
175 // Either overwriting the file or creating a new one now
176 if (status != NX_ERROR) {
177 ::NeXus::File f(fileHandle);
178 f.close();
179 }
180
181 // If not overwriting, ensure it has a .nxs extension
182 if ((!m_overwriteFile || status == NX_ERROR) && !boost::ends_with(this->m_filename, ".nxs"))
183 m_filename = m_filename + ".nxs";
184
185 status = NXopen(this->m_filename.c_str(), NXACC_CREATE5, &fileHandle);
186
187 if (status == NX_ERROR)
188 throw std::runtime_error("Unable to open or create nexus file.");
189
190 // *********************************
191 // Now genererate file and structure
192
193 ::NeXus::File nxFile(fileHandle);
194
195 // Make the top level entry (and open it)
196 nxFile.makeGroup("entry1", "NXentry", true);
197
198 // Make an entry to store log values from the original files.
199 nxFile.makeGroup("log_info", "NXsubentry", false);
200
201 // Make a sub-group for the entry to work with DAWN software (and open it)
202 nxFile.makeGroup("tomo_entry", "NXsubentry", true);
203
204 // Title
205 nxFile.writeData("title", this->m_filename);
206
207 // Definition name and version
208 nxFile.writeData("definition", "NXtomo");
209 nxFile.openData("definition");
210 nxFile.putAttr("version", NXTOMO_VER);
211 nxFile.closeData();
212
213 // Originating program name and version
214 nxFile.writeData("program_name", "mantid");
215 nxFile.openData("program_name");
216 nxFile.putAttr("version", Mantid::Kernel::MantidVersion::version());
217 nxFile.closeData();
218
219 // ******************************************
220 // NXinstrument
221 nxFile.makeGroup("instrument", "NXinstrument", true);
222 // Write the instrument name | could add short_name attribute to name
223 nxFile.writeData("name", m_workspaces[0]->getInstrument()->getName());
224
225 // detector group - diamond example file contains
226 // {data,distance,image_key,x_pixel_size,y_pixel_size}
227 nxFile.makeGroup("detector", "NXdetector", true);
228
229 std::vector<int64_t> infDim;
230 infDim.emplace_back(NX_UNLIMITED);
231
232 nxFile.makeData("image_key", ::NeXus::FLOAT64, infDim, false);
233 nxFile.closeGroup(); // detector
234
235 // source group // from diamond file contains {current,energy,name,probe,type}
236 // - probe = [neutron | x-ray | electron]
237
238 nxFile.closeGroup(); // NXinstrument
239
240 // ******************************************
241 // NXsample
242 nxFile.makeGroup("sample", "NXsample", true);
243
244 nxFile.makeData("rotation_angle", ::NeXus::FLOAT64, infDim, true);
245 // Create a link object for rotation_angle to use later
246 NXlink rotationLink = nxFile.getDataID();
247 nxFile.closeData();
248 nxFile.closeGroup(); // NXsample
249
250 // ******************************************
251 // Make the NXmonitor group - Holds base beam intensity for each image
252
253 nxFile.makeGroup("control", "NXmonitor", true);
254 nxFile.makeData("data", ::NeXus::FLOAT64, infDim, false);
255 nxFile.closeGroup(); // NXmonitor
256
257 nxFile.makeGroup("data", "NXdata", true);
258 nxFile.putAttr<int>("NumFiles", 0);
259
260 nxFile.makeLink(rotationLink);
261
262 nxFile.makeData("data", ::NeXus::FLOAT64, m_infDimensions, true);
263 // Create a link object for the data
264 NXlink dataLink = nxFile.getDataID();
265 nxFile.closeData();
266
267 if (m_includeError)
268 nxFile.makeData("error", ::NeXus::FLOAT64, m_infDimensions, false);
269
270 nxFile.closeGroup(); // Close Data group
271
272 // Put a link to the data in instrument/detector
273 nxFile.openGroup("instrument", "NXinstrument");
274 nxFile.openGroup("detector", "NXdetector");
275 nxFile.makeLink(dataLink);
276 nxFile.closeGroup();
277 nxFile.closeGroup();
278
279 nxFile.closeGroup(); // tomo_entry sub-group
280 nxFile.closeGroup(); // Top level NXentry
281
282 return nxFile;
283}
284
291 try {
292 nxFile.openPath("/entry1/tomo_entry/data");
293 } catch (...) {
294 throw std::runtime_error("Unable to create a valid NXTomo file");
295 }
296
297 int numFiles = 0;
298 nxFile.getAttr<int>("NumFiles", numFiles);
299
300 // Change slab start to after last data position
301 m_slabStart[0] = numFiles;
302 m_slabSize[0] = 1;
303
304 // Set the rotation value for this WS
305 std::vector<double> rotValue;
306 rotValue.emplace_back(0);
307
308 if (workspace->run().hasProperty("Rotation")) {
309 std::string tmpVal = workspace->run().getLogData("Rotation")->value();
310 try {
311 rotValue[0] = boost::lexical_cast<double>(tmpVal);
312 } catch (...) {
313 }
314 // Invalid Cast is handled below
315 }
316
317 nxFile.openData("rotation_angle");
318 nxFile.putSlab(rotValue, numFiles, 1);
319 nxFile.closeData();
320
321 // Copy data out, remake data with dimension of old size plus new elements.
322 // Insert previous data.
323 nxFile.openData("data");
324
325 auto dataArr = new double[m_spectraCount];
326
327 // images can be as one-spectrum-per-pixel, or one-spectrum-per-row
328 bool spectrumPerPixel = (1 == workspace->y(0).size());
329 for (int64_t i = 0; i < m_dimensions[1]; ++i) {
330 const auto &Y = workspace->y(i);
331 for (int64_t j = 0; j < m_dimensions[2]; ++j) {
332 if (spectrumPerPixel) {
333 dataArr[i * m_dimensions[1] + j] = workspace->y(i * m_dimensions[1] + j)[0];
334 } else {
335 dataArr[i * m_dimensions[1] + j] = Y[j];
336 }
337 }
338 }
339
340 nxFile.putSlab(dataArr, m_slabStart, m_slabSize);
341
342 nxFile.closeData();
343
344 nxFile.putAttr("NumFiles", numFiles + 1);
345
346 nxFile.closeGroup();
347
348 // Write additional log information, intensity and image key
349 writeLogValues(workspace, nxFile, numFiles);
350 writeIntensityValue(workspace, nxFile, numFiles);
351 writeImageKeyValue(workspace, nxFile, numFiles);
352 delete[] dataArr;
353}
354
356 int thisFileInd) {
357 // Add ImageKey to instrument/image_key if present, use 0 if not
358 try {
359 nxFile.openPath("/entry1/tomo_entry/instrument/detector");
360 } catch (...) {
361 throw std::runtime_error("Unable to create a valid NXTomo file");
362 }
363
364 // Set the default key value for this WS
365 std::vector<double> keyValue;
366 keyValue.emplace_back(0);
367
368 if (workspace->run().hasProperty("ImageKey")) {
369 std::string tmpVal = workspace->run().getLogData("ImageKey")->value();
370 try {
371 keyValue[0] = boost::lexical_cast<double>(tmpVal);
372 } catch (...) {
373 }
374 // Invalid Cast is handled below
375 }
376
377 nxFile.openData("image_key");
378 nxFile.putSlab(keyValue, thisFileInd, 1);
379 nxFile.closeData();
380
381 nxFile.closeGroup();
382}
383
385 int thisFileInd) {
386 // Add Log information (minus special values - Rotation, ImageKey, Intensity)
387 // Unable to add multidimensional string data, storing strings as
388 // multidimensional data set of uint8 values
389 try {
390 nxFile.openPath("/entry1/log_info");
391 } catch (...) {
392 throw std::runtime_error("Unable to create a valid NXTomo file");
393 }
394
395 // Loop through all log values, create it if it doesn't exist. Then append
396 // value
397 const auto &logVals = workspace->run().getLogData();
398
399 for (const auto &prop : logVals) {
400 if (prop->name() != "ImageKey" && prop->name() != "Rotation" && prop->name() != "Intensity" &&
401 prop->name() != "Axis1" && prop->name() != "Axis2") {
402 try {
403 nxFile.openData(prop->name());
404 } catch (::NeXus::Exception &) {
405 // Create the data entry if it doesn't exist yet, and open.
406 std::vector<int64_t> infDim;
407 infDim.emplace_back(NX_UNLIMITED);
408 infDim.emplace_back(NX_UNLIMITED);
409 nxFile.makeData(prop->name(), ::NeXus::UINT8, infDim, true);
410 }
411 auto valueAsStr = prop->value();
412 size_t strSize = valueAsStr.length();
413 // If log value is from FITS file as it should be,
414 // it won't be greater than this. Otherwise Shorten it
415 if (strSize > 80)
416 strSize = 80;
417 std::vector<int64_t> start = {thisFileInd, 0};
418 std::vector<int64_t> size = {1, static_cast<int64_t>(strSize)};
419 // single item
420 nxFile.putSlab(valueAsStr.data(), start, size);
421
422 nxFile.closeData();
423 }
424 }
425}
426
428 int thisFileInd) {
429 // Add Intensity to control if present, use 1 if not
430 try {
431 nxFile.openPath("/entry1/tomo_entry/control");
432 } catch (...) {
433 throw std::runtime_error("Unable to create a valid NXTomo file");
434 }
435
436 std::vector<double> intensityValue;
437 intensityValue.emplace_back(1);
438
439 if (workspace->run().hasProperty("Intensity")) {
440 std::string tmpVal = workspace->run().getLogData("Intensity")->value();
441 try {
442 intensityValue[0] = boost::lexical_cast<double>(tmpVal);
443 } catch (...) {
444 }
445 // Invalid Cast is handled below
446 }
447
448 nxFile.openData("data");
449 nxFile.putSlab(intensityValue, thisFileInd, 1);
450 nxFile.closeData();
451}
452
453} // namespace Mantid::DataHandling
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
double value
The value of the point.
Definition: FitMW.cpp:51
IPeaksWorkspace_sptr workspace
Definition: IndexPeaks.cpp:114
std::string getName(const IMDDimension &self)
Base class from which all concrete algorithm classes should be derived.
Definition: Algorithm.h:85
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
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
@ Save
to specify a file to write to, the file may or may not exist
Definition: FileProperty.h:49
A validator which checks that a workspace contains histogram data (the default) or point data as requ...
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
Class to hold a set of workspaces.
A property class for workspaces.
void writeImageKeyValue(const DataObjects::Workspace2D_sptr &workspace, ::NeXus::File &nxFile, int thisFileInd)
Definition: SaveNXTomo.cpp:355
static const std::string NXTOMO_VER
file format version
Definition: SaveNXTomo.h:114
bool processGroups() override
Alternative execution code when operating on a WorkspaceGroup.
Definition: SaveNXTomo.cpp:86
void writeIntensityValue(const DataObjects::Workspace2D_sptr &workspace, ::NeXus::File &nxFile, int thisFileInd)
Definition: SaveNXTomo.cpp:427
::NeXus::File setupFile()
Creates the format for the output file if it doesn't exist.
Definition: SaveNXTomo.cpp:164
void processAll()
Main exec routine, called for group or individual workspace processing.
Definition: SaveNXTomo.cpp:109
std::vector< DataObjects::Workspace2D_sptr > m_workspaces
Definition: SaveNXTomo.h:116
void init() override
Initialisation code.
Definition: SaveNXTomo.cpp:44
std::vector< int64_t > m_slabSize
Definition: SaveNXTomo.h:105
void writeSingleWorkspace(const DataObjects::Workspace2D_sptr &workspace, ::NeXus::File &nxFile)
Writes a single workspace into the file.
Definition: SaveNXTomo.cpp:290
void exec() override
Execution code : Single workspace.
Definition: SaveNXTomo.cpp:72
const std::string name() const override
Algorithm's name for identification overriding a virtual method.
Definition: SaveNXTomo.h:59
std::vector< int64_t > m_dimensions
Definition: SaveNXTomo.h:109
std::vector< int64_t > m_slabStart
Definition: SaveNXTomo.h:104
std::vector< int64_t > m_infDimensions
Definition: SaveNXTomo.h:111
void writeLogValues(const DataObjects::Workspace2D_sptr &workspace, ::NeXus::File &nxFile, int thisFileInd)
Write various pieces of data from the workspace log with checks on the structure of the nexus file.
Definition: SaveNXTomo.cpp:384
std::string m_filename
The filename of the output file.
Definition: SaveNXTomo.h:107
Marks code as not implemented yet.
Definition: Exception.h:138
static const char * version()
The full version number.
The concrete, templated class for properties.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< WorkspaceGroup > WorkspaceGroup_sptr
shared pointer to Mantid::API::WorkspaceGroup
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::shared_ptr< Workspace2D > Workspace2D_sptr
shared pointer to Mantid::DataObjects::Workspace2D
@ Input
An input workspace.
Definition: Property.h:53