Newer
Older
#include "MantidDataHandling/LoadNexusMonitors.h"
#include "MantidDataHandling/LoadEventNexus.h"
#include "MantidDataHandling/ISISRunLogs.h"
Michael Reuter
committed
#include "MantidAPI/FileProperty.h"
#include "MantidAPI/WorkspaceGroup.h"
Michael Reuter
committed
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/DateAndTime.h"
Michael Reuter
committed
#include "MantidKernel/UnitFactory.h"
#include <Poco/File.h>
#include <Poco/Path.h>
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <cmath>
#include <map>
#include <vector>
using Mantid::DataObjects::EventWorkspace;
using Mantid::DataObjects::EventWorkspace_sptr;
using Mantid::API::WorkspaceGroup;
using Mantid::API::WorkspaceGroup_sptr;
namespace Mantid {
namespace DataHandling {
Michael Reuter
committed
DECLARE_ALGORITHM(LoadNexusMonitors)
namespace {
// Comparison class for sorting monitor group names according to their
// monitor_number
class MonitorNameSorter {
::NeXus::File &file, Mantid::API::MatrixWorkspace_sptr const WS) {
try {
file.openGroup("isis_vms_compat", "IXvms");
} catch (::NeXus::Exception &) {
// No problem, it just means that this entry does not exist
return;
}
// read the data
try {
std::vector<int32_t> spb;
std::vector<float> rspb;
file.readData("SPB", spb);
file.readData("RSPB", rspb);
WS->mutableSample().setGeometryFlag(
spb[2]); // the flag is in the third value
WS->mutableSample().setThickness(rspb[3]);
WS->mutableSample().setHeight(rspb[4]);
WS->mutableSample().setWidth(rspb[5]);
} catch (::NeXus::Exception &ex) {
// it means that the data was not as expected, report the problem
std::stringstream s;
s << "Wrong definition found in isis_vms_compat :> " << ex.what();
file.closeGroup();
throw std::runtime_error(s.str());
}
file.closeGroup();
}
Janik Zikovsky
committed
LoadNexusMonitors::LoadNexusMonitors() : Algorithm(), nMonitors(0) {}
Michael Reuter
committed
LoadNexusMonitors::~LoadNexusMonitors() {}
Michael Reuter
committed
/// Initialization method.
Michael Reuter
committed
declareProperty(
new API::FileProperty("Filename", "", API::FileProperty::Load, ".nxs"),
"The name (including its full or relative path) of the NeXus file to "
"attempt to load. The file extension must either be .nxs or .NXS");
Federico Montesino Pouzols
committed
declareProperty(
new API::WorkspaceProperty<API::Workspace>("OutputWorkspace", "",
Kernel::Direction::Output),
"The name of the output workspace in which to load the NeXus monitors.");
declareProperty(new Kernel::PropertyWithValue<bool>("MonitorsAsEvents", true,
Kernel::Direction::Input),
"If enabled (by default), load the monitors as events (into "
"an EventWorkspace), as long as there is event data. If "
"disabled, load monitors as spectra (into a Workspace2D, "
"regardless of whether event data is found.");
Michael Reuter
committed
}
/**
* Executes the algorithm. Reading in the file and creating and populating
* the output workspace
*/
Michael Reuter
committed
// Retrieve the filename from the properties
this->filename = this->getPropertyValue("Filename");
API::Progress prog1(this, 0.0, 0.2, 2);
if (!canOpenAsNeXus(this->filename)) {
throw std::runtime_error(
"Failed to recognize this file as a NeXus file, cannot continue.");
}
Michael Reuter
committed
// top level file information
::NeXus::File file(this->filename);
// Start with the base entry
typedef std::map<std::string, std::string> string_map_t;
string_map_t::const_iterator it;
string_map_t entries = file.getEntries();
for (it = entries.begin(); it != entries.end(); ++it) {
if (((it->first == "entry") || (it->first == "raw_data_1")) &&
(it->second == "NXentry")) {
file.openGroup(it->first, it->second);
m_top_entry_name = it->first;
break;
}
Michael Reuter
committed
prog1.report();
// Now we want to go through and find the monitors
entries = file.getEntries();
Michael Reuter
committed
std::vector<std::string> monitorNames;
size_t numHistMon = 0;
size_t numEventMon = 0;
size_t numPeriods = 0;
// we want to sort monitors by monitor_number if they are present
std::map<int, std::string> monitorNumber2Name;
Michael Reuter
committed
prog1.report();
API::Progress prog2(this, 0.2, 0.6, entries.size());
it = entries.begin();
for (; it != entries.end(); ++it) {
Michael Reuter
committed
std::string entry_name(it->first);
std::string entry_class(it->second);
if ((entry_class == "NXmonitor")) {
monitorNames.push_back(entry_name);
// check for event/histogram monitor
// -> This will prefer event monitors over histogram
// if they are found in the same group.
file.openGroup(entry_name, "NXmonitor");
int numEventThings =
0; // number of things that are eventish - should be 3
string_map_t inner_entries = file.getEntries(); // get list of entries
for (auto inner = inner_entries.begin(); inner != inner_entries.end();
++inner) {
if (inner->first == "event_index") {
numEventThings += 1;
continue;
} else if (inner->first == "event_time_offset") {
numEventThings += 1;
continue;
} else if (inner->first == "event_time_zero") {
numEventThings += 1;
continue;
}
}
if (inner_entries.find("monitor_number") != inner_entries.end()) {
specid_t monitorNo;
file.openData("monitor_number");
file.getData(&monitorNo);
file.closeData();
monitorNumber2Name[monitorNo] = entry_name;
}
if ((numPeriods == 0) &&
(inner_entries.find("period_index") != inner_entries.end())) {
MantidVec period_data;
file.openData("period_index");
file.getDataCoerce(period_data);
file.closeData();
numPeriods = period_data.size();
}
}
file.closeGroup(); // close NXmonitor
Michael Reuter
committed
}
prog2.report();
}
this->nMonitors = monitorNames.size();
// Nothing to do
// previous version just used to return, but that
// threw an error when the OutputWorkspace property was not set.
// and the error message was confusing.
// This has changed to throw a specific error.
throw std::invalid_argument(this->filename +
" does not contain any monitors");
// With this property you can make the exception that even if there's event
// data, monitors will be loaded
// as histograms (if set to false)
Federico Montesino Pouzols
committed
bool monitorsAsEvents = getProperty("MonitorsAsEvents");
// Beware, even if monitorsAsEvents==False (user requests to load monitors as
// histograms)
// check if there's histogram data. If not, ignore setting, step back and load
// monitors as
// events which is the only possibility left.
if (!monitorsAsEvents)
if (!allMonitorsHaveHistoData(file, monitorNames)) {
g_log.information() << "Cannot load monitors as histogram data. Loading "
"as events even if the opposite was requested by "
"disabling the property MonitorsAsEvents"
<< std::endl;
monitorsAsEvents = true;
}
// only used if using event monitors
EventWorkspace_sptr eventWS;
Michael Reuter
committed
// Create the output workspace
if (numHistMon == this->nMonitors || !monitorsAsEvents) {
if (this->nMonitors == 0)
throw std::runtime_error(
"Not loading event data. Trying to load histogram data but failed to "
"find monitors with histogram data or could not interpret the data. "
"This file may be corrupted or it may not be supported");
this->WS = API::WorkspaceFactory::Instance().create("Workspace2D",
this->nMonitors, 1, 1);
// if there is a distinct monitor number for each monitor sort them by that
// number
if (monitorNumber2Name.size() == monitorNames.size()) {
for (auto it = monitorNumber2Name.begin(); it != monitorNumber2Name.end();
++it) {
monitorNames.push_back(it->second);
} else if (numEventMon == this->nMonitors) {
eventWS = EventWorkspace_sptr(new EventWorkspace());
useEventMon = true;
eventWS->initialize(this->nMonitors, 1, 1);
eventWS->getAxis(0)->unit() =
Mantid::Kernel::UnitFactory::Instance().create("TOF");
eventWS->setYUnit("Counts");
this->WS = eventWS;
} else {
g_log.error() << "Found " << numEventMon << " event monitors and "
<< numHistMon << " histogram monitors (" << this->nMonitors
<< " total)\n";
throw std::runtime_error(
"All monitors must be either event or histogram based");
Michael Reuter
committed
// a temporary place to put the spectra/detector numbers
Gigg, Martyn Anthony
committed
boost::scoped_array<specid_t> spectra_numbers(new specid_t[this->nMonitors]);
boost::scoped_array<detid_t> detector_numbers(new detid_t[this->nMonitors]);
Michael Reuter
committed
API::Progress prog3(this, 0.6, 1.0, this->nMonitors);
for (std::size_t i = 0; i < this->nMonitors; ++i) {
g_log.information() << "Loading " << monitorNames[i] << std::endl;
Michael Reuter
committed
// Do not rely on the order in path list
Poco::Path monPath(monitorNames[i]);
std::string monitorName = monPath.getBaseName();
Freddie Akeroyd
committed
// check for monitor name - in our case will be of the form either monitor1
// or monitor_1
Gigg, Martyn Anthony
committed
std::string::size_type loc = monitorName.rfind('_');
Gigg, Martyn Anthony
committed
loc = monitorName.rfind('r');
}
detid_t monIndex = -1 * boost::lexical_cast<int>(
monitorName.substr(loc + 1)); // SNS default
Gigg, Martyn Anthony
committed
file.openGroup(monitorNames[i], "NXmonitor");
Campbell, Stuart
committed
Gigg, Martyn Anthony
committed
// Check if the spectra index is there
specid_t spectrumNo(static_cast<specid_t>(i + 1));
try {
Gigg, Martyn Anthony
committed
file.openData("spectrum_index");
file.getData(&spectrumNo);
file.closeData();
Gigg, Martyn Anthony
committed
// Use the default as matching the workspace index
}
Campbell, Stuart
committed
g_log.debug() << "monIndex = " << monIndex << std::endl;
Gigg, Martyn Anthony
committed
g_log.debug() << "spectrumNo = " << spectrumNo << std::endl;
Michael Reuter
committed
Gigg, Martyn Anthony
committed
spectra_numbers[i] = spectrumNo;
detector_numbers[i] = monIndex;
// setup local variables
std::vector<uint64_t> event_index;
MantidVec time_of_flight;
std::string tof_units;
MantidVec seconds;
// read in the data
file.openData("event_index");
file.getData(event_index);
file.closeData();
file.openData("event_time_offset");
file.getDataCoerce(time_of_flight);
file.getAttr("units", tof_units);
file.closeData();
file.openData("event_time_zero");
file.getDataCoerce(seconds);
Mantid::Kernel::DateAndTime pulsetime_offset;
{
std::string startTime;
file.getAttr("offset", startTime);
pulsetime_offset = Mantid::Kernel::DateAndTime(startTime);
}
file.closeData();
// load up the event list
DataObjects::EventList &event_list = eventWS->getEventList(i);
Mantid::Kernel::DateAndTime pulsetime(0);
Mantid::Kernel::DateAndTime lastpulsetime(0);
std::size_t numEvents = time_of_flight.size();
bool pulsetimesincreasing = true;
size_t pulse_index(0);
size_t numPulses = seconds.size();
for (std::size_t j = 0; j < numEvents; ++j) {
while (!((j >= event_index[pulse_index]) &&
(j < event_index[pulse_index + 1]))) {
if (pulse_index > (numPulses + 1))
if (pulse_index >= (numPulses))
pulse_index = numPulses - 1; // fix it
pulsetime = pulsetime_offset + seconds[pulse_index];
if (pulsetime < lastpulsetime)
pulsetimesincreasing = false;
lastpulsetime = pulsetime;
event_list.addEventQuickly(
DataObjects::TofEvent(time_of_flight[j], pulsetime));
}
if (pulsetimesincreasing)
event_list.setSortOrder(DataObjects::PULSETIME_SORT);
{
// Now, actually retrieve the necessary data
file.openData("data");
MantidVec data;
file.getDataCoerce(data);
file.closeData();
MantidVec error(data.size()); // create vector of correct size
// Transform errors via square root
std::transform(data.begin(), data.end(), error.begin(),
// Get the TOF axis
file.openData("time_of_flight");
MantidVec tof;
file.getDataCoerce(tof);
file.closeData();
this->WS->dataX(i) = tof;
this->WS->dataY(i) = data;
this->WS->dataE(i) = error;
}
file.closeGroup(); // NXmonitor
Janik Zikovsky
committed
// Default values, might change later.
this->WS->getSpectrum(i)->setSpectrumNo(spectrumNo);
this->WS->getSpectrum(i)->setDetectorID(monIndex);
Michael Reuter
committed
prog3.report();
}
if (useEventMon) // set the x-range to be the range for min/max events
{
double xmin, xmax;
eventWS->getEventXMinMax(xmin, xmax);
Kernel::cow_ptr<MantidVec> axis;
MantidVec &xRef = axis.access();
xRef.resize(2, 0.0);
if (eventWS->getNumberEvents() > 0) {
xRef[0] = xmin - 1; // Just to make sure the bins hold it all
eventWS->setAllX(axis); // Set the binning axis using this.
Gigg, Martyn Anthony
committed
// Fix the detector numbers if the defaults above are not correct
fixUDets(detector_numbers, file, spectra_numbers, nMonitors);
// Check for and ISIS compat block to get the detector IDs for the loaded
// spectrum numbers
Gigg, Martyn Anthony
committed
// @todo: Find out if there is a better (i.e. more generic) way to do this
try {
g_log.debug() << "Load Sample data isis" << std::endl;
loadSampleDataISIScompatibilityInfo(file, this->WS);
Gigg, Martyn Anthony
committed
}
Michael Reuter
committed
// Need to get the instrument name from the file
std::string instrumentName;
file.openGroup("instrument", "NXinstrument");
file.openData("name");
instrumentName = file.getStrData();
// Now let's close the file as we don't need it anymore to load the
// instrument.
file.closeData();
file.closeGroup(); // Close the NXentry
file.close();
} catch (std::runtime_error &) // no correct instrument definition (old ISIS
// file, fall back to isis_vms_compat)
{
file.closeGroup(); // Close the instrument NXentry
instrumentName = LoadEventNexus::readInstrumentFromISIS_VMSCompat(file);
file.close();
}
g_log.debug() << "Instrument name read from NeXus file is " << instrumentName
<< std::endl;
Michael Reuter
committed
this->WS->getAxis(0)->unit() = Kernel::UnitFactory::Instance().create("TOF");
this->WS->setYUnit("Counts");
// Load the logs
this->runLoadLogs(this->filename, this->WS);
// Old SNS files don't have this
// The run_start will be loaded from the pulse times.
Kernel::DateAndTime run_start(0, 0);
run_start = this->WS->getFirstPulseTime();
this->WS->mutableRun().addProperty("run_start", run_start.toISO8601String(),
true);
} catch (...) {
// Old files have the start_time defined, so all SHOULD be good.
// The start_time, however, has been known to be wrong in old files.
}
Michael Reuter
committed
// Load the instrument
LoadEventNexus::loadInstrument(filename, WS, m_top_entry_name, this);
Michael Reuter
committed
// Load the meta data, but don't stop on errors
g_log.debug() << "Loading metadata" << std::endl;
LoadEventNexus::loadEntryMetadata<API::MatrixWorkspace_sptr>(
this->filename, WS, m_top_entry_name);
} catch (std::exception &e) {
g_log.warning() << "Error while loading meta data: " << e.what()
<< std::endl;
Janik Zikovsky
committed
// Fix the detector IDs/spectrum numbers
for (size_t i = 0; i < WS->getNumberHistograms(); i++) {
Janik Zikovsky
committed
WS->getSpectrum(i)->setSpectrumNo(spectra_numbers[i]);
WS->getSpectrum(i)->setDetectorID(detector_numbers[i]);
}
// add filename
WS->mutableRun().addProperty("Filename", this->filename);
// if multiperiod histogram data
if ((numPeriods > 1) && (!useEventMon)) {
splitMutiPeriodHistrogramData(numPeriods);
this->setProperty("OutputWorkspace", this->WS);
}
Michael Reuter
committed
}
Gigg, Martyn Anthony
committed
/**
* Can we get a histogram (non event data) for every monitor?
*
* @param file :: NeXus file object (open)
* @param monitorNames :: names of monitors of interest
*
* @return If there seems to be histograms for all monitors (they have "data"
**/
bool LoadNexusMonitors::allMonitorsHaveHistoData(
::NeXus::File &file, const std::vector<std::string> &monitorNames) {
bool res = true;
for (std::size_t i = 0; i < this->nMonitors; ++i) {
file.openGroup(monitorNames[i], "NXmonitor");
file.openData("data");
file.closeData();
file.closeGroup();
}
file.closeGroup();
res = false;
}
return res;
}
Gigg, Martyn Anthony
committed
/**
* Fix the detector numbers if the defaults are not correct. Currently checks
* the isis_vms_compat
Gigg, Martyn Anthony
committed
* block and reads them from there if possible
* @param det_ids :: An array of prefilled detector IDs
* @param file :: A reference to the NeXus file opened at the root entry
* @param spec_ids :: An array of spectrum numbers that the monitors have
* @param nmonitors :: The size of the det_ids and spec_ids arrays
*/
void LoadNexusMonitors::fixUDets(boost::scoped_array<detid_t> &det_ids,
::NeXus::File &file,
const boost::scoped_array<specid_t> &spec_ids,
const size_t nmonitors) const {
try {
Gigg, Martyn Anthony
committed
file.openGroup("isis_vms_compat", "IXvms");
Gigg, Martyn Anthony
committed
return;
}
// UDET
file.openData("UDET");
std::vector<int32_t> udet;
file.getData(udet);
file.closeData();
Gigg, Martyn Anthony
committed
file.openData("SPEC");
std::vector<int32_t> spec;
file.getData(spec);
file.closeData();
// This is a little complicated: Each value in the spec_id array is a value
// found in the
// SPEC block of isis_vms_compat. The index that this value is found at then
// corresponds
Gigg, Martyn Anthony
committed
// to the index within the UDET block that holds the detector ID
std::vector<int32_t>::const_iterator beg = spec.begin();
for (size_t mon_index = 0; mon_index < nmonitors; ++mon_index) {
std::vector<int32_t>::const_iterator itr =
std::find(spec.begin(), spec.end(), spec_ids[mon_index]);
if (itr == spec.end()) {
Gigg, Martyn Anthony
committed
det_ids[mon_index] = -1;
continue;
}
std::vector<int32_t>::difference_type udet_index = std::distance(beg, itr);
det_ids[mon_index] = udet[udet_index];
}
file.closeGroup();
}
void LoadNexusMonitors::runLoadLogs(const std::string filename,
API::MatrixWorkspace_sptr localWorkspace) {
// do the actual work
API::IAlgorithm_sptr loadLogs = createChildAlgorithm("LoadNexusLogs");
// Now execute the Child Algorithm. Catch and log any error, but don't stop.
try {
g_log.information() << "Loading logs from NeXus file..." << std::endl;
loadLogs->setPropertyValue("Filename", filename);
loadLogs->setProperty<API::MatrixWorkspace_sptr>("Workspace",
localWorkspace);
loadLogs->execute();
} catch (...) {
g_log.error() << "Error while loading Logs from Nexus. Some sample logs "
"may be missing." << std::endl;
}
Gigg, Martyn Anthony
committed
/**
* Helper method to make sure that a file is / can be openend as a NeXus file
*
* @param fname :: name of the file
* @return True if opening the file as NeXus and retrieving entries succeeds
bool LoadNexusMonitors::canOpenAsNeXus(const std::string &fname) {
bool res = true;
::NeXus::File *f = NULL;
try {
f = new ::NeXus::File(fname);
if (f)
f->getEntries();
} catch (::NeXus::Exception &e) {
g_log.error() << "Failed to open as a NeXus file: '" << fname
<< "', error description: " << e.what() << std::endl;
res = false;
}
if (f)
delete f;
return res;
}
* Splits multiperiod histogram data into seperate workspaces and puts them in a
*group
*
* @param numPeriods :: number of periods
**/
void LoadNexusMonitors::splitMutiPeriodHistrogramData(const size_t numPeriods) {
// protection - we should not have entered the routine if these are not true
// More than 1 period
if (numPeriods < 2) {
g_log.warning()
<< "Attempted to split multiperiod histogram workspace with "
<< numPeriods << "periods, aborted." << std::endl;
return;
}
// Y array should be divisible by the number of periods
if (this->WS->blocksize() % numPeriods != 0) {
g_log.warning()
<< "Attempted to split multiperiod histogram workspace with "
<< this->WS->blocksize() << "data entries, into " << numPeriods
<< "periods."
" Aborted." << std::endl;
return;
}
WorkspaceGroup_sptr wsGroup(new WorkspaceGroup);
size_t yLength = this->WS->blocksize() / numPeriods;
size_t xLength = yLength + 1;
size_t numSpectra = this->WS->getNumberHistograms();
ISISRunLogs monLogCreator(this->WS->run(), static_cast<int>(numPeriods));
for (size_t i = 0; i < numPeriods; i++) {
// create the period workspace
API::MatrixWorkspace_sptr wsPeriod =
API::WorkspaceFactory::Instance().create(this->WS, numSpectra, xLength,
yLength);
// assign x values - restart at start for all periods
for (size_t specIndex = 0; specIndex < numSpectra; specIndex++) {
MantidVec &outputVec = wsPeriod->dataX(specIndex);
const MantidVec &inputVec = this->WS->readX(specIndex);
for (size_t index = 0; index < xLength; index++) {
outputVec[index] = inputVec[index];
}
}
// assign y values - use the values offset by the period number
for (size_t specIndex = 0; specIndex < numSpectra; specIndex++) {
MantidVec &outputVec = wsPeriod->dataY(specIndex);
const MantidVec &inputVec = this->WS->readY(specIndex);
for (size_t index = 0; index < yLength; index++) {
outputVec[index] = inputVec[(yLength * i) + index];
}
}
for (size_t specIndex = 0; specIndex < numSpectra; specIndex++) {
MantidVec &outputVec = wsPeriod->dataE(specIndex);
const MantidVec &inputVec = this->WS->readE(specIndex);
for (size_t index = 0; index < yLength; index++) {
outputVec[index] = inputVec[(yLength * i) + index];
}
}
// add period logs
monLogCreator.addPeriodLogs(static_cast<int>(i + 1),
wsPeriod->mutableRun());
// add to workspace group
wsGroup->addWorkspace(wsPeriod);
}
// set the output workspace
this->setProperty("OutputWorkspace", wsGroup);
}
} // end DataHandling
Michael Reuter
committed
} // end Mantid