LoadInstrument.cpp 12.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 +
Neil Vaytet's avatar
Neil Vaytet committed
7
#include "MantidDataHandling/LoadInstrument.h"
8
9
#include "MantidAPI/FileProperty.h"
#include "MantidAPI/InstrumentDataService.h"
10
#include "MantidAPI/MatrixWorkspace.h"
11
#include "MantidAPI/Progress.h"
12
#include "MantidDataHandling/LoadGeometry.h"
13
#include "MantidGeometry/Instrument.h"
14
#include "MantidGeometry/Instrument/InstrumentDefinitionParser.h"
15
#include "MantidKernel/ArrayProperty.h"
16
#include "MantidKernel/ConfigService.h"
17
#include "MantidKernel/MandatoryValidator.h"
LamarMoore's avatar
LamarMoore committed
18
#include "MantidKernel/OptionalBool.h"
19
#include "MantidKernel/Strings.h"
20
#include "MantidNexusGeometry/NexusGeometryParser.h"
21

22
23
#include <boost/algorithm/string.hpp>

24
25
26
27
28
29
30
31
32
namespace Mantid {
namespace DataHandling {

DECLARE_ALGORITHM(LoadInstrument)

using namespace Kernel;
using namespace API;
using namespace Geometry;

33
std::recursive_mutex LoadInstrument::m_mutex;
34

35
36
37
38
39
40
41
42
// This enum class is used to remember easily which instrument loader should be
// used. There are 3 different loaders: from XML string, from an IDF file and
// from a Nexus file. Some parts of the exec() function are common to Xml and
// Idf, some parts are common to Idf and Nxs, and some parts are common to all
// three. Assigning numbers to the 3 types allows us to write statements like
// if (loader_type < LoaderType::Nxs) then do all things common to Xml and Idf.
enum class LoaderType { Xml = 1, Idf = 2, Nxs = 3 };

43
44
45
46
/// Initialisation method.
void LoadInstrument::init() {
  // When used as a Child Algorithm the workspace name is not used - hence the
  // "Anonymous" to satisfy the validator
47
  declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>(
48
49
50
51
                      "Workspace", "Anonymous", Direction::InOut),
                  "The name of the workspace to load the instrument definition "
                  "into. Any existing instrument will be replaced.");
  declareProperty(
52
      std::make_unique<FileProperty>("Filename", "", FileProperty::OptionalLoad,
Sam Jenkins's avatar
Sam Jenkins committed
53
                                     LoadGeometry::validExtensions()),
54
55
      "The filename (including its full or relative path) of an instrument "
      "definition file. The file extension must either be .xml or .XML when "
56
57
      "specifying an instrument definition file. Files can also be .hdf5 or "
      ".nxs for usage with NeXus Geometry files. Note Filename or "
58
      "InstrumentName must be specified but not both.");
Sam Jenkins's avatar
Sam Jenkins committed
59
60
61
62
  declareProperty(std::make_unique<ArrayProperty<detid_t>>("MonitorList",
                                                           Direction::Output),
                  "Will be filled with a list of the detector ids of any "
                  "monitors loaded in to the workspace.");
63
64
  declareProperty(
      "InstrumentName", "",
Neil Vaytet's avatar
Neil Vaytet committed
65
66
      "Name of instrument. Can be used instead of Filename to specify an"
      "instrument definition.");
67
68
  declareProperty("InstrumentXML", "",
                  "The full XML instrument definition as a string.");
Owen Arnold's avatar
Owen Arnold committed
69
  declareProperty(
70
      std::make_unique<PropertyWithValue<OptionalBool>>(
Owen Arnold's avatar
Owen Arnold committed
71
          "RewriteSpectraMap", OptionalBool::Unset,
72
          std::make_shared<MandatoryValidator<OptionalBool>>()),
73
74
      "If set to True then a 1:1 map between the spectrum numbers and "
      "detector/monitor IDs is set up such that the detector/monitor IDs in "
75
76
      "the IDF are ordered from smallest to largest number and then assigned "
      "in that order to the spectra in the workspace. For example if the IDF "
77
78
79
80
81
82
83
84
85
86
87
      "has defined detectors/monitors with IDs 1, 5, 10 and the workspace "
      "contains 3 spectra with numbers 1, 2, 3 (and workspace indices 0, 1, 2) "
      "then spectrum number 1 is associated with detector ID 1, spectrum "
      "number 2 with detector ID 5 and spectrum number 3 with detector ID 10."
      "If the number of spectra and detectors do not match then the operation "
      "is performed until the maximum number of either is reached. For example "
      "if there are 12 spectra and 50 detectors then the first 12 detectors "
      "are assigned to the 12 spectra in the workspace."
      "If set to False then the spectrum numbers and detector IDs of the "
      "workspace are not modified."
      "This property must be set to either True or False.");
88
89
90
91
}

//------------------------------------------------------------------------------------------------------------------------------
/** Executes the algorithm. Reading in the file and creating and populating
LamarMoore's avatar
LamarMoore committed
92
93
94
95
96
97
 *  the output workspace
 *
 *  @throw FileError Thrown if unable to parse XML file
 *  @throw InstrumentDefinitionError Thrown if issues with the content of XML
 *instrument file
 */
98
99
void LoadInstrument::exec() {
  // Get the input workspace
100
  std::shared_ptr<API::MatrixWorkspace> ws = getProperty("Workspace");
101
102
  std::string filename = getPropertyValue("Filename");
  std::string instname = getPropertyValue("InstrumentName");
103
104
  // std::pair<std::string, std::string> loader_type;
  LoaderType loader_type;
105

106
107
  // If instrumentXML is not default (i.e. it has been defined), then use that
  // Note: this is part of the IDF loader
108
109
110
  const Property *const InstrumentXML = getProperty("InstrumentXML");
  if (!InstrumentXML->isDefault()) {
    // We need the instrument name to be set as well because, for whatever
111
    // reason, this isn't pulled out of the XML.
112
    if (instname.empty())
113
114
115
116
      throw std::runtime_error("The InstrumentName property must be set when "
                               "using the InstrumentXML property.");
    // If the Filename property is not set, set it to the same as the instrument
    // name
117
118
    if (filename.empty())
      filename = instname;
119

120
121
    // Assign the loader type to Xml
    loader_type = LoaderType::Xml;
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

  } else {
    // This part of the loader searches through the instrument directories for
    // a valid IDF or Nexus geometry file

    // The first step is to define a valid filename

    // If the filename is empty, try to find a file from the instrument name
    if (filename.empty()) {
      // look to see if an Instrument name provided in which case create
      // filename on the fly
      if (instname.empty()) {
        g_log.error("Either the InstrumentName or Filename property of "
                    "LoadInstrument most be specified");
        throw Kernel::Exception::FileError(
            "Either the InstrumentName or Filename property of LoadInstrument "
            "must be specified to load an instrument",
            filename);
      } else {
        filename = ExperimentInfo::getInstrumentFilename(
            instname, ws->getWorkspaceStartDate());
        setPropertyValue("Filename", filename);
      }
    }
    if (filename.empty()) {
      throw Exception::NotFoundError(
          "Unable to find an Instrument File for instrument: ", instname);
    }
150

151
152
153
154
    // Remove the path from the filename for use with the InstrumentDataService
    const std::string::size_type stripPath = filename.find_last_of("\\/");
    std::string instrumentFile =
        filename.substr(stripPath + 1, filename.size());
155

156
    // Strip off "_Definition.xml"
157
158
    auto definitionRange = boost::ifind_first(instrumentFile, "_Def");
    if (definitionRange) {
159
160
      instname = instrumentFile.substr(
          0, std::distance(instrumentFile.begin(), definitionRange.begin()));
161
162
    } else {
      g_log.warning("The instrument definition filename does not contain "
163
164
                    "_Definition. Your instrument name will be set to: " +
                    instrumentFile);
165
166
      instname = instrumentFile;
    }
167

168
169
    // Now that we have a file name, decide whether to use Nexus or IDF loading
    if (LoadGeometry::isIDF(filename)) {
170
171
      // Assign the loader type to Idf
      loader_type = LoaderType::Idf;
172
    } else if (LoadGeometry::isNexus(filename)) {
173
174
      // Assign the loader type to Nxs
      loader_type = LoaderType::Nxs;
175
176
    } else {
      throw Kernel::Exception::FileError(
177
          "No valid loader found for instrument file ", filename);
178
    }
179
180
  }

181
182
183
  InstrumentDefinitionParser parser;
  std::string instrumentNameMangled;
  Instrument_sptr instrument;
184

185
  // Define a parser if using IDFs
186
  if (loader_type == LoaderType::Xml)
Neil Vaytet's avatar
Neil Vaytet committed
187
188
    parser =
        InstrumentDefinitionParser(filename, instname, InstrumentXML->value());
189
  else if (loader_type == LoaderType::Idf)
Neil Vaytet's avatar
Neil Vaytet committed
190
191
    parser = InstrumentDefinitionParser(filename, instname,
                                        Strings::loadFile(filename));
192

193
  // Find the mangled instrument name that includes the modified date
194
  if (loader_type < LoaderType::Nxs)
195
    instrumentNameMangled = parser.getMangledName();
196
  else if (loader_type == LoaderType::Nxs)
197
    instrumentNameMangled =
Neil Vaytet's avatar
Neil Vaytet committed
198
        NexusGeometry::NexusGeometryParser::getMangledName(filename, instname);
199
  else
200
    throw std::runtime_error("Unknown instrument LoaderType");
201

202
203
  {
    // Make InstrumentService access thread-safe
204
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
205

206
    // Check whether the instrument is already in the InstrumentDataService
207
208
209
    if (InstrumentDataService::Instance().doesExist(instrumentNameMangled)) {
      // If it does, just use the one from the one stored there
      instrument =
210
          InstrumentDataService::Instance().retrieve(instrumentNameMangled);
211
    } else {
212

213
      if (loader_type < LoaderType::Nxs) {
214
215
216
217
        // Really create the instrument
        Progress prog(this, 0.0, 1.0, 100);
        instrument = parser.parseXML(&prog);
        // Parse the instrument tree (internally create ComponentInfo and
Neil Vaytet's avatar
Neil Vaytet committed
218
219
        // DetectorInfo). This is an optimization that avoids duplicate parsing
        // of the instrument tree when loading multiple workspaces with the same
220
221
222
223
224
        // instrument. As a consequence less time is spent and less memory is
        // used. Note that this is only possible since the tree in `instrument`
        // will not be modified once we add it to the IDS.
        instrument->parseTreeAndCacheBeamline();
      } else {
Neil Vaytet's avatar
Neil Vaytet committed
225
        Instrument_const_sptr ins =
Owen Arnold's avatar
Owen Arnold committed
226
227
            NexusGeometry::NexusGeometryParser::createInstrument(
                filename, NexusGeometry::makeLogger(&m_log));
228
        instrument = std::const_pointer_cast<Instrument>(ins);
229
      }
230
231
232
      // Add to data service for later retrieval
      InstrumentDataService::Instance().add(instrumentNameMangled, instrument);
    }
233
    ws->setInstrument(instrument);
234

235
    // populate parameter map of workspace
236
    ws->populateInstrumentParameters();
237

238
239
240
    // LoadParameterFile modifies the base instrument stored in the IDS so this
    // must also be protected by the lock until LoadParameterFile is fixed.
    // check if default parameter file is also present, unless loading from
241
    if (!filename.empty() && (loader_type < LoaderType::Nxs))
242
      runLoadParameterFile(ws, filename);
243
  } // end of mutex scope
244

245
246
247
248
249
250
251
252
  // Set the monitors output property
  setProperty("MonitorList", (ws->getInstrument())->getMonitors());

  // Rebuild the spectra map for this workspace so that it matches the
  // instrument, if required
  const OptionalBool RewriteSpectraMap = getProperty("RewriteSpectraMap");
  if (RewriteSpectraMap == OptionalBool::True)
    ws->rebuildSpectraMapping();
253
}
254
255
256

//-----------------------------------------------------------------------------------------------------------------------
/// Run the Child Algorithm LoadInstrument (or LoadInstrumentFromRaw)
Neil Vaytet's avatar
Neil Vaytet committed
257
void LoadInstrument::runLoadParameterFile(
258
    const std::shared_ptr<API::MatrixWorkspace> &ws,
David Fairbrother's avatar
David Fairbrother committed
259
    const std::string &filename) {
260
261
262
  g_log.debug("Loading the parameter definition...");

  // First search for XML parameter file in same folder as IDF file
263
  const std::string::size_type dir_end = filename.find_last_of("\\/");
264
  std::string directoryName =
265
      filename.substr(0, dir_end + 1); // include final '/'.
266
267
  std::string fullPathParamIDF =
      ExperimentInfo::getFullPathParamIDF(filename, directoryName);
268
269
270

  if (!fullPathParamIDF.empty()) {

271
    g_log.debug() << "Parameter file: " << fullPathParamIDF << '\n';
272
273
274
275
276
277
    // Now execute the Child Algorithm. Catch and log any error, but don't stop.
    try {
      // To allow the use of ExperimentInfo instead of workspace, we call it
      // manually
      Algorithm_sptr loadParamAlg = createChildAlgorithm("LoadParameterFile");
      loadParamAlg->setProperty("Filename", fullPathParamIDF);
278
      loadParamAlg->setProperty("Workspace", ws);
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
      loadParamAlg->execute();
      g_log.debug("Parameters loaded successfully.");
    } catch (std::invalid_argument &e) {
      g_log.information(
          "LoadParameterFile: No parameter file found for this instrument");
      g_log.information(e.what());
    } catch (std::runtime_error &e) {
      g_log.information(
          "Unable to successfully run LoadParameterFile Child Algorithm");
      g_log.information(e.what());
    }
  } else {
    g_log.information("No parameter file found for this instrument");
  }
}

} // namespace DataHandling
Nick Draper's avatar
re #41  
Nick Draper committed
296
} // namespace Mantid