SNSAppendGeometryToNexus.cpp 15.9 KB
Newer Older
1
2
3
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4
5
//   NScD Oak Ridge National Laboratory, European Spallation Source,
//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
// SPDX - License - Identifier: GPL - 3.0 +
7
#include "MantidDataHandling/SNSAppendGeometryToNexus.h"
8
#include "MantidAPI/FileProperty.h"
9
#include "MantidAPI/InstrumentFileFinder.h"
10
#include "MantidAPI/WorkspaceFactory.h"
11
#include "MantidDataObjects/Workspace2D.h"
12
#include "MantidGeometry/Instrument.h"
Hahn, Steven's avatar
Hahn, Steven committed
13
14
#include "MantidKernel/OptionalBool.h"
#include "MantidKernel/System.h"
15

16
// clang-format off
17
18
#include <nexus/NeXusFile.hpp>
#include <nexus/NeXusException.hpp>
19
// clang-format on
20

21
22
#include <Poco/File.h>
#include <Poco/Path.h>
23
#include <Poco/Exception.h>
24

25
26
27
28
using namespace Mantid::Kernel;
using namespace Mantid::API;
using namespace ::NeXus;

29
30
31
32
namespace Mantid {
namespace DataHandling {

// Register the algorithm into the AlgorithmFactory
33
DECLARE_ALGORITHM(SNSAppendGeometryToNexus)
34
35
36
37

//----------------------------------------------------------------------------------------------
/** Constructor
 */
38
39
40
41
42
SNSAppendGeometryToNexus::SNSAppendGeometryToNexus()
    : m_makeNexusCopy(false), m_instrumentLoadedCorrectly(false), m_logsLoadedCorrectly(false) {
  // inform deprecation alias status
  setDeprecationDate("2021-09-14");
}
43
44
45
46

//----------------------------------------------------------------------------------------------
/** Destructor
 */
47
SNSAppendGeometryToNexus::~SNSAppendGeometryToNexus() {
48
49
50
51
52
  // delete workspace
}

//----------------------------------------------------------------------------------------------
/// Algorithm's name for identification. @see Algorithm::name
53
const std::string SNSAppendGeometryToNexus::name() const { return "SNSAppendGeometryToNexus"; }
54
55

/// Algorithm's version for identification. @see Algorithm::version
56
int SNSAppendGeometryToNexus::version() const { return 1; }
57
58

/// Algorithm's category for identification. @see Algorithm::category
59
const std::string SNSAppendGeometryToNexus::category() const { return "DataHandling\\DataAcquisition"; }
60
61
62
63
64
65

//----------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
 */
66
void SNSAppendGeometryToNexus::init() {
67
  // Declare potential extensions for input NeXus file
68
  std::vector<std::string> extensions{".nxs", ".h5"};
69

70
  declareProperty(std::make_unique<API::FileProperty>("Filename", "", API::FileProperty::Load, extensions),
71
72
73
74
                  "The name of the NeXus file to append geometry to.");

  // TODO: change MakeCopy default to False when comfortable. Otherwise need to
  // remove the extra copy once in production.
75
76
  declareProperty(std::make_unique<PropertyWithValue<bool>>("MakeCopy", true, Direction::Input),
                  "Copy the NeXus file first before appending (optional, default True).");
77
78
79
80
81
}

//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
 */
82
void SNSAppendGeometryToNexus::exec() {
83
84
  // TODO: rename the created arrays before moving to production
  g_log.warning() << "This is intended as a proof of principle and not a long "
85
                     "term implementation.\n";
86
  g_log.warning() << "(the created arrays in the NeXus file will have the '_new' suffix)\n";
87
88
89
90
91
92
93
94
95
96
97
98

  // Retrieve filename from the properties
  m_filename = getPropertyValue("Filename");

  // Are we going to make a copy of the file ?
  m_makeNexusCopy = getProperty("MakeCopy");

  if (m_makeNexusCopy) {
    Poco::File originalFile(m_filename);
    Poco::Path originalPath(m_filename);

    if (originalFile.exists()) {
99
      Poco::File destinationFile(Poco::Path(Poco::Path::temp(), originalPath.getFileName()));
100
101
102

      try {
        originalFile.copyTo(destinationFile.path());
103
        g_log.notice() << "Copied " << m_filename << " to " << destinationFile.path() << ".\n";
104
105
106
107
        m_filename = destinationFile.path();
      } catch (Poco::FileAccessDeniedException &) {
        throw std::runtime_error("A Problem occurred in making a copy of the "
                                 "NeXus file. Failed to copy " +
108
                                 originalFile.path() + " to " + destinationFile.path() +
109
110
111
                                 ". Please check file permissions.");
      }
    } else {
112
      g_log.error() << "Cannot copy a file that doesn't exist! (" << originalFile.path() << ").\n";
113
    }
114
  }
Campbell, Stuart's avatar
Campbell, Stuart committed
115

116
117
  // Let's check to see if we can write to the NeXus file.
  if (!(Poco::File(m_filename).canWrite())) {
118
    throw std::runtime_error("The specified NeXus file (" + m_filename + ") is not writable.");
119
120
  }

121
122
  // Let's look for the instrument name
  m_instrument = getInstrumentName(m_filename);
123

124
  if (m_instrument.length() == 0) {
125

126
    throw std::runtime_error("Failed to get instrument name from " + m_filename +
127
128
                             ". Can't identify instrument definition file.");
  }
129

130
131
  // Temp workspace name to load the instrument into
  // std::string workspaceName = "__" + m_instrument + "_geometry_ws";
132

133
134
135
  // Now what is the instrument definition filename ?
  // TODO: Modify to use /entry/instrument/instrument_xml/data after
  // establishing a way to maintain ADARA Geometry Packet
136
  m_idf_filename = InstrumentFileFinder::getInstrumentFilename(m_instrument);
137
  g_log.debug() << "Loading instrument definition from " << m_idf_filename << ".\n";
138

139
140
  // Modified to call LoadInstrument directly as a Child Algorithm
  ws = WorkspaceFactory::Instance().create("Workspace2D", 1, 2, 1);
141

142
  // Load NeXus logs for HYSPEC, HYSPECA(testing), and SNAP
143
  if (m_instrument == "HYSPEC" || m_instrument == "HYSPECA" || m_instrument == "SNAP") {
144
    g_log.debug() << "Run LoadNexusLogs Child Algorithm.\n";
145
    m_logsLoadedCorrectly = runLoadNexusLogs(m_filename, ws, this);
146

147
    if (!m_logsLoadedCorrectly)
148
149
      throw std::runtime_error("Failed to run LoadNexusLogs Child Algorithm.");
  }
150

151
  g_log.debug() << "Run LoadInstrument Child Algorithm.\n";
152
  m_instrumentLoadedCorrectly = runLoadInstrument(m_idf_filename, ws, this);
153

154
  if (!m_instrumentLoadedCorrectly)
155
156
157
158
159
160
    throw std::runtime_error("Failed to run LoadInstrument Child Algorithm.");

  // Get the number of detectors (just for progress reporting)
  // Get the number of histograms/detectors
  const size_t numDetectors = ws->getInstrument()->getDetectorIDs().size();

161
  API::Progress progress(this, 0.0, 1.0, numDetectors);
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

  // Get the instrument
  Geometry::Instrument_const_sptr instrument = ws->getInstrument();

  // Get the sample (needed to calculate distances)
  Geometry::IComponent_const_sptr sample = instrument->getSample();
  // Get the source (moderator)
  Geometry::IComponent_const_sptr source = instrument->getSource();

  // Open the NeXus file
  ::NeXus::File nxfile(m_filename, NXACC_RDWR);

  // typedef std::map<std::string,std::string> string_map_t;
  std::map<std::string, std::string>::const_iterator root_iter;
  std::map<std::string, std::string> entries = nxfile.getEntries();

  for (root_iter = entries.begin(); root_iter != entries.end(); ++root_iter) {
    // Open all NXentry
    if (root_iter->second == "NXentry") {
      nxfile.openGroup(root_iter->first, "NXentry");

      // Get a list of items within the entry.
      std::map<std::string, std::string> entry_items = nxfile.getEntries();
      // Create an iterator for this
      std::map<std::string, std::string>::const_iterator entry_iter;

188
      for (entry_iter = entry_items.begin(); entry_iter != entry_items.end(); ++entry_iter) {
189
190
191
192
193
194
        // Look for an instrument
        if (entry_iter->second == "NXinstrument") {
          // Open the instrument
          nxfile.openGroup(entry_iter->first, "NXinstrument");
          std::map<std::string, std::string> instr_items = nxfile.getEntries();
          std::map<std::string, std::string>::const_iterator instr_iter;
195
          for (instr_iter = instr_items.begin(); instr_iter != instr_items.end(); ++instr_iter) {
196
197
            // Look for NXdetectors
            if (instr_iter->second == "NXdetector") {
198
              g_log.debug() << "Detector called '" << instr_iter->first << "' found.\n";
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
              std::string bankName = instr_iter->first;
              std::vector<Geometry::IDetector_const_sptr> dets;
              ws->getInstrument()->getDetectorsInBank(dets, bankName);

              if (!dets.empty()) {
                nxfile.openGroup(bankName, "NXdetector");

                // Let's create some vectors for the parameters to write
                // Pixel IDs
                std::vector<int> pixel_id;
                std::vector<double> distance;
                std::vector<double> polar_angle;
                std::vector<double> azimuthal_angle;

                pixel_id.reserve(dets.size());
                distance.reserve(dets.size());
                polar_angle.reserve(dets.size());
                azimuthal_angle.reserve(dets.size());

218
                for (auto &det : dets) {
219
220
221
222
                  pixel_id.emplace_back(det->getID());
                  distance.emplace_back(det->getDistance(*sample));
                  azimuthal_angle.emplace_back(det->getPhi());
                  polar_angle.emplace_back(ws->detectorTwoTheta(*det));
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
                }

                // Write Pixel ID to file
                nxfile.writeData("pixel_id_new", pixel_id);

                // Write Secondary Flight Path to file
                nxfile.writeData("distance_new", distance);
                nxfile.openData("distance_new");
                nxfile.putAttr("units", "metre");
                nxfile.closeData();

                // Write Polar Angle (2theta) to file
                nxfile.writeData("polar_angle_new", polar_angle);
                nxfile.openData("polar_angle_new");
                nxfile.putAttr("units", "radian");
                nxfile.closeData();

                // Write Azimuthal Angle (Phi) to file
                nxfile.writeData("azimuthal_angle_new", azimuthal_angle);
                nxfile.openData("azimuthal_angle_new");
                nxfile.putAttr("units", "radian");
                nxfile.closeData();

                nxfile.closeGroup(); // close NXdetector

248
                progress.report(dets.size());
249
              } else {
250
251
252
                throw std::runtime_error("Could not find any detectors for the bank named " + bankName +
                                         " that is listed in the NeXus file."
                                         "Check that it exists in the Instrument Definition File.");
253
254
255
              }
            }
          }
256

257
          nxfile.closeGroup(); // NXinstrument
258

259
260
261
        }
        // Look for monitors
        else if (entry_iter->second == "NXmonitor") {
262
          g_log.debug() << "Monitor called '" << entry_iter->first << "' found.\n";
263
          nxfile.openGroup(entry_iter->first, "NXmonitor");
264

265
          Geometry::IComponent_const_sptr monitor = instrument->getComponentByName(entry_iter->first);
266

267
268
          // Write Pixel ID to file
          // nxfile.writeData("pixel_id_new", monitor->get);
Campbell, Stuart's avatar
Campbell, Stuart committed
269

270
271
          double source_monitor = source->getDistance(*monitor);
          double source_sample = source->getDistance(*sample);
Campbell, Stuart's avatar
Campbell, Stuart committed
272

273
274
          g_log.debug() << "source->monitor=" << source_monitor << '\n';
          g_log.debug() << "source->sample=" << source_sample << '\n';
275
          g_log.debug() << "sample->monitor=" << (source_monitor - source_sample) << '\n';
Campbell, Stuart's avatar
Campbell, Stuart committed
276

277
278
279
280
281
          // Distance
          nxfile.writeData("distance_new", (source_monitor - source_sample));
          nxfile.openData("distance_new");
          nxfile.putAttr("units", "metre");
          nxfile.closeData();
282

283
284
          nxfile.closeGroup(); // NXmonitor
        }
Campbell, Stuart's avatar
Campbell, Stuart committed
285
      }
286

287
    } else {
288
      g_log.error() << "There are no NXentry nodes in the specified NeXus file.\n";
289
    }
290
291
292
293
294
295
296
297
298
  }
}

//----------------------------------------------------------------------------------------------
/** Get the instrument name from the input NeXus file.
 *
 * @param nxfilename :: Input NeXus file.
 * @return the instrument name, empty string if failed.
 */
299
std::string SNSAppendGeometryToNexus::getInstrumentName(const std::string &nxfilename) {
300
301
302
303
304
305
306
307
308
  std::string instrument;

  // Open the NeXus file
  ::NeXus::File nxfile(nxfilename);
  // What is the first entry ?
  std::map<std::string, std::string> entries = nxfile.getEntries();

  // For now, let's just open the first entry
  nxfile.openGroup(entries.begin()->first, "NXentry");
309
  g_log.debug() << "Using entry '" << entries.begin()->first << "' to determine instrument name.\n";
310
311
312
313
314
315
316
317
318
319
320

  nxfile.openGroup("instrument", "NXinstrument");
  try {
    nxfile.openData("name");
    instrument = nxfile.getStrData();
  } catch (::NeXus::Exception &) {
    // TODO: try and get the instrument name from the filename instead.
    // Note in filename we have instrument short name yet
    // ExperimentiInfo.getInstrumentFilename() expects instrument long name
    instrument = "";
  }
321

322
  g_log.debug() << " Instrument name read from NeXus file is " << instrument << '\n';
323
324
325
326
327
328
329
330
331
332
333
334
335
336

  return instrument;
}

//----------------------------------------------------------------------------------------------
/** Load the instrument using the input instrument definition file.
 *
 * @param idf_filename :: Input instrument definition file.
 * @param localWorkspace :: MatrixWorkspace in which to put the instrument
 *geometry
 * @param alg :: Handle of an algorithm for logging access
 * @return true if successful
 */

337
bool SNSAppendGeometryToNexus::runLoadInstrument(const std::string &idf_filename,
338
                                                 const API::MatrixWorkspace_sptr &localWorkspace, Algorithm *alg) {
339
340
341
342
343
344
345
  IAlgorithm_sptr loadInst = createChildAlgorithm("LoadInstrument", 0, 1, true);

  // Execute the Child Algorithm.
  bool executionSuccessful(true);
  try {
    loadInst->setPropertyValue("Filename", idf_filename);
    loadInst->setProperty<MatrixWorkspace_sptr>("Workspace", localWorkspace);
346
    loadInst->setProperty("RewriteSpectraMap", OptionalBool(false));
347
348
    loadInst->execute();
  } catch (std::invalid_argument &e) {
349
    alg->getLogger().information("Invalid argument to LoadInstrument Child Algorithm");
350
351
352
    alg->getLogger().information(e.what());
    executionSuccessful = false;
  } catch (std::runtime_error &e) {
353
    alg->getLogger().information("Failed to run LoadInstrument Child Algorithm");
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
    alg->getLogger().information(e.what());
    executionSuccessful = false;
  }

  // Throwing an error if failed
  if (!executionSuccessful) {
    alg->getLogger().error("Error loading instrument\n");
  }
  return executionSuccessful;
}

//-----------------------------------------------------------------------------
/** Load the logs from the input NeXus file.
 *
 * @param nexusFileName :: Name of the NeXus file to load logs from.
 * @param localWorkspace :: MatrixWorkspace in which to put the logs.
 * @param alg :: Handle of an algorithm for logging access.
 * @return true if successful.
 */
373
bool SNSAppendGeometryToNexus::runLoadNexusLogs(const std::string &nexusFileName,
374
375
                                                const API::MatrixWorkspace_sptr &localWorkspace, Algorithm *alg) {
  IAlgorithm_sptr loadLogs = alg->createChildAlgorithm("LoadNexusLogs", 0, 1, true);
376
377
378
379

  // Execute the Child Algorithm, catching errors without stopping.
  bool executionSuccessful(true);
  try {
Hahn, Steven's avatar
Hahn, Steven committed
380
    alg->getLogger().information() << "Loading logs from the NeXus file...\n";
381
382
383
384
    loadLogs->setPropertyValue("Filename", nexusFileName);
    loadLogs->setProperty<MatrixWorkspace_sptr>("Workspace", localWorkspace);
    loadLogs->executeAsChildAlg();
  } catch (std::invalid_argument &e) {
385
    alg->getLogger().information("Invalid argument to LoadNexusLogs Child Algorithm");
386
387
388
    alg->getLogger().information(e.what());
    executionSuccessful = false;
  } catch (std::runtime_error &) {
389
    alg->getLogger().information("Unable to successfully run runLoadNexusLogs Child Algorithm./n");
390
    executionSuccessful = false;
391
392
  }

393
394
  return executionSuccessful;
}
395
} // namespace DataHandling
LamarMoore's avatar
LamarMoore committed
396
} // namespace Mantid