Newer
Older
Gigg, Martyn Anthony
committed
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidDataHandling/LoadNexusLogs.h"
#include <nexus/NeXusException.hpp>
Gigg, Martyn Anthony
committed
#include "MantidKernel/TimeSeriesProperty.h"
Gigg, Martyn Anthony
committed
#include "MantidKernel/LogParser.h"
Gigg, Martyn Anthony
committed
#include "MantidAPI/FileProperty.h"
#include <cctype>
#include <Poco/Path.h>
#include <Poco/DateTimeFormatter.h>
#include <Poco/DateTimeParser.h>
#include <Poco/DateTimeFormat.h>
#include <boost/scoped_array.hpp>
Janik Zikovsky
committed
#include "MantidDataHandling/LoadTOFRawNexus.h"
Gigg, Martyn Anthony
committed
namespace Mantid
{
namespace DataHandling
Gigg, Martyn Anthony
committed
{
// Register the algorithm into the algorithm factory
Gigg, Martyn Anthony
committed
DECLARE_ALGORITHM(LoadNexusLogs)
Gigg, Martyn Anthony
committed
using namespace Kernel;
using API::WorkspaceProperty;
using API::MatrixWorkspace;
using API::MatrixWorkspace_sptr;
using API::FileProperty;
using std::size_t;
/// Empty default constructor
Gigg, Martyn Anthony
committed
LoadNexusLogs::LoadNexusLogs()
Gigg, Martyn Anthony
committed
{}
/// Initialisation method.
Gigg, Martyn Anthony
committed
void LoadNexusLogs::init()
Gigg, Martyn Anthony
committed
{
declareProperty(new WorkspaceProperty<MatrixWorkspace>("Workspace","Anonymous",Direction::InOut),
"The name of the workspace that will be filled with the logs.");
Gigg, Martyn Anthony
committed
std::vector<std::string> exts;
exts.push_back(".nxs");
exts.push_back(".n*");
declareProperty(new FileProperty("Filename", "", FileProperty::Load, exts),
"Path to the .nxs file to load. Can be an EventNeXus or a histogrammed NeXus." );
declareProperty(new PropertyWithValue<bool>("OverwriteLogs", true, Direction::Input),
"If true then existing logs will be overwritten, if false they will not.");
Gigg, Martyn Anthony
committed
}
/** Executes the algorithm. Reading in the file and creating and populating
* the output workspace
*
* @throw Exception::FileError If the Nexus file cannot be found/opened
* @throw std::invalid_argument If the optional properties are set to invalid values
*/
Gigg, Martyn Anthony
committed
void LoadNexusLogs::exec()
Gigg, Martyn Anthony
committed
{
std::string filename = getPropertyValue("Filename");
MatrixWorkspace_sptr workspace = getProperty("Workspace");
Janik Zikovsky
committed
// Find the entry name to use (normally "entry" for SNS, "raw_data_1" for ISIS)
std::string entry_name = LoadTOFRawNexus::getEntryName(filename);
::NeXus::File file(filename);
Gigg, Martyn Anthony
committed
// Find the root entry
try
{
Janik Zikovsky
committed
file.openGroup(entry_name, "NXentry");
Gigg, Martyn Anthony
committed
}
catch(::NeXus::Exception&)
{
Janik Zikovsky
committed
throw std::invalid_argument("Unknown NeXus file format found in file '" + filename + "'");
Gigg, Martyn Anthony
committed
}
Janik Zikovsky
committed
///Use frequency start for Monitor19 and Special1_19 logs with "No Time" for SNAP
try
{
file.openPath("DASlogs");
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
try
{
file.openGroup("frequency", "NXlog");
try
{
file.openData("time");
//----- Start time is an ISO8601 string date and time. ------
try
{
file.getAttr("start", freqStart);
}
catch (::NeXus::Exception &)
{
//Some logs have "offset" instead of start
try
{
file.getAttr("offset", freqStart);
}
catch (::NeXus::Exception &)
{
g_log.warning() << "Log entry has no start time indicated.\n";
file.closeData();
throw;
}
}
file.closeData();
}
catch (::NeXus::Exception&)
{
// No time. This is not an SNS SNAP file
}
file.closeGroup();
}
catch (::NeXus::Exception&)
{
// No time. This is not an SNS frequency group
}
file.closeGroup();
}
catch (::NeXus::Exception&)
{
// No time. This is not an SNS group
Janik Zikovsky
committed
// print out the entry level fields
Gigg, Martyn Anthony
committed
std::map<std::string,std::string> entries = file.getEntries();
std::map<std::string,std::string>::const_iterator iend = entries.end();
for(std::map<std::string,std::string>::const_iterator it = entries.begin();
Gigg, Martyn Anthony
committed
{
Janik Zikovsky
committed
std::string group_name(it->first);
std::string group_class(it->second);
if( group_name == "DASlogs" || group_class == "IXrunlog" ||
Freddie Akeroyd
committed
group_class == "IXselog" || group_name == "framelog" )
Gigg, Martyn Anthony
committed
{
Janik Zikovsky
committed
loadLogs(file, group_name, group_class, workspace);
Gigg, Martyn Anthony
committed
}
}
Gigg, Martyn Anthony
committed
// Freddie Akeroyd 12/10/2011
// current ISIS implementation contains an additional indirection between collected frames via an
// "event_frame_number" array in NXevent_data (which eliminates frames with no events).
// the proton_log is for all frames and so is longer than the event_index array, so we need to
// filter the proton_charge log based on event_frame_number
// This difference will be removed in future for compatibility with SNS, but the code below will allow current SANS2D files to load
if ( workspace->mutableRun().hasProperty("proton_log") )
{
std::vector<int> event_frame_number;
Kernel::TimeSeriesProperty<double> * plog = dynamic_cast<Kernel::TimeSeriesProperty<double> *>( workspace->mutableRun().getProperty("proton_log") );
this->getLogger().notice() << "Using old ISIS proton_log and event_frame_number indirection..." << std::endl;
try
{
// Find the bank/name corresponding to the first event data entry, i.e. one with type NXevent_data.
file.openPath("/" + entry_name);
std::map<std::string, std::string> entries = file.getEntries();
std::map<std::string,std::string>::const_iterator it = entries.begin();
std::string eventEntry;
Gigg, Martyn Anthony
committed
{
if( it->second == "NXevent_data" )
{
eventEntry = it->first;
break;
}
}
this->getLogger().debug() << "Opening" << " /" + entry_name + "/" + eventEntry + "/event_frame_number" << " to find the event_frame_number\n";
file.openPath("/" + entry_name + "/" + eventEntry + "/event_frame_number");
file.getData(event_frame_number);
}
catch(const ::NeXus::Exception&)
Gigg, Martyn Anthony
committed
{
this->getLogger().warning() << "Unable to load event_frame_number - filtering events by time will not work " << std::endl;
}
file.openPath("/"+entry_name);
if (!event_frame_number.empty()) // ISIS indirection - see above comments
Gigg, Martyn Anthony
committed
{
Kernel::TimeSeriesProperty<double>* pcharge = new Kernel::TimeSeriesProperty<double>("proton_charge");
std::vector<double> pval;
std::vector<Mantid::Kernel::DateAndTime> ptime;
pval.reserve(event_frame_number.size());
ptime.reserve(event_frame_number.size());
std::vector<Mantid::Kernel::DateAndTime> plogt = plog->timesAsVector();
std::vector<double> plogv = plog->valuesAsVector();
for (size_t i =0; i < event_frame_number.size(); ++i)
{
ptime.push_back(plogt[ event_frame_number[i] ]);
pval.push_back(plogv[ event_frame_number[i] ]);
}
pcharge->create(ptime, pval);
pcharge->setUnits("uAh");
workspace->mutableRun().addProperty(pcharge, true);
}
}
Gigg, Martyn Anthony
committed
try
{
// Read the start and end time strings
file.openData("start_time");
Kernel::DateAndTime start(file.getStrData());
file.closeData();
file.openData("end_time");
Kernel::DateAndTime end(file.getStrData());
file.closeData();
workspace->mutableRun().setStartAndEndTime(start, end);
}
catch(::NeXus::Exception&)
{}
Gigg, Martyn Anthony
committed
if( !workspace->run().hasProperty("gd_prtn_chrg") )
{
Gigg, Martyn Anthony
committed
// Try pulling it from the main proton_charge entry first
Gigg, Martyn Anthony
committed
try
{
Gigg, Martyn Anthony
committed
file.openData("proton_charge");
std::vector<double> values;
file.getDataCoerce(values);
std::string units;
file.getAttr("units", units);
double charge = values.front();
if( units.find("picoCoulomb") != std::string::npos )
{
charge *= 1.e-06/3600.;
}
workspace->mutableRun().setProtonCharge(charge);
Gigg, Martyn Anthony
committed
}
Gigg, Martyn Anthony
committed
catch(::NeXus::Exception&)
Gigg, Martyn Anthony
committed
{
Gigg, Martyn Anthony
committed
// Try and integrate the proton logs
try
{
//Use the DAS logs to integrate the proton charge (if any).
workspace->mutableRun().integrateProtonCharge();
}
catch (Exception::NotFoundError &)
{
//Ignore not found property error.
}
Gigg, Martyn Anthony
committed
}
}
Gigg, Martyn Anthony
committed
// Close the file
file.close();
Gigg, Martyn Anthony
committed
}
/** Try to load the "Veto_pulse" field in DASLogs
* and convert it to a sample log.
*
* @param file :: open nexus file at the DASLogs group
* @param workspace :: workspace to add to.
*/
void LoadNexusLogs::loadVetoPulses(::NeXus::File & file, boost::shared_ptr<API::MatrixWorkspace> workspace) const
{
try
{
file.openGroup("Veto_pulse", "NXgroup");
}
catch (::NeXus::Exception&)
{
// No group. This is common in older files
return;
}
file.openData("veto_pulse_time");
// Load the start date/time as ISO8601 string.
std::string start_time;
file.getAttr("start_time", start_time);
DateAndTime start(start_time);
// Read the offsets
std::vector<double> time_double;
file.getData(time_double);
// Fake values with zeroes.
std::vector<double> values(time_double.size(), 0.0);
TimeSeriesProperty<double> * tsp = new TimeSeriesProperty<double>("veto_pulse_time");
tsp->create(start, time_double, values);
tsp->setUnits("");
// Add the log
workspace->mutableRun().addProperty(tsp);
file.closeData();
file.closeGroup();
}
Gigg, Martyn Anthony
committed
/**
* Load log entries from the given group
* @param file :: A reference to the NeXus file handle opened such that the
* next call can be to open the named group
* @param entry_name :: The name of the log entry
* @param entry_class :: The class type of the log entry
Gigg, Martyn Anthony
committed
* @param workspace :: A pointer to the workspace to store the logs
*/
Gigg, Martyn Anthony
committed
void LoadNexusLogs::loadLogs(::NeXus::File & file, const std::string & entry_name,
Gigg, Martyn Anthony
committed
const std::string & entry_class,
boost::shared_ptr<API::MatrixWorkspace> workspace) const
Gigg, Martyn Anthony
committed
{
file.openGroup(entry_name, entry_class);
std::map<std::string,std::string> entries = file.getEntries();
std::map<std::string,std::string>::const_iterator iend = entries.end();
for(std::map<std::string,std::string>::const_iterator itr = entries.begin();
Gigg, Martyn Anthony
committed
{
std::string log_class = itr->second;
Gigg, Martyn Anthony
committed
if( log_class == "NXlog" || log_class == "NXpositioner" )
Gigg, Martyn Anthony
committed
{
Gigg, Martyn Anthony
committed
loadNXLog(file, itr->first, log_class, workspace);
Gigg, Martyn Anthony
committed
}
else if( log_class == "IXseblock" )
{
loadSELog(file, itr->first, workspace);
}
}
loadVetoPulses(file, workspace);
Gigg, Martyn Anthony
committed
file.closeGroup();
}
/**
Gigg, Martyn Anthony
committed
* Load an NX log entry a group type that has value and time entries.
Gigg, Martyn Anthony
committed
* @param file :: A reference to the NeXus file handle opened at the parent group
* @param entry_name :: The name of the log entry
Gigg, Martyn Anthony
committed
* @param entry_class :: The type of the entry
Gigg, Martyn Anthony
committed
* @param workspace :: A pointer to the workspace to store the logs
*/
Gigg, Martyn Anthony
committed
void LoadNexusLogs::loadNXLog(::NeXus::File & file, const std::string & entry_name,
Gigg, Martyn Anthony
committed
const std::string & entry_class,
boost::shared_ptr<API::MatrixWorkspace> workspace) const
Gigg, Martyn Anthony
committed
{
g_log.debug() << "processing " << entry_name << ":" << entry_class << "\n";
Gigg, Martyn Anthony
committed
file.openGroup(entry_name, entry_class);
Gigg, Martyn Anthony
committed
// Validate the NX log class.
std::map<std::string, std::string> entries = file.getEntries();
if ((entries.find("value") == entries.end()) ||
(entries.find("time") == entries.end()) )
{
g_log.warning() << "Invalid NXlog entry " << entry_name
<< " found. Did not contain 'value' and 'time'.\n";
file.closeGroup();
return;
}
// whether or not to overwrite logs on workspace
bool overwritelogs = this->getProperty("OverwriteLogs");
try
{
if (overwritelogs || !(workspace->run().hasProperty(entry_name)))
{
Kernel::Property *logValue = createTimeSeries(file, entry_name);
workspace->mutableRun().addProperty(logValue, overwritelogs);
}
Gigg, Martyn Anthony
committed
}
catch(::NeXus::Exception &e)
{
g_log.warning() << "NXlog entry " << entry_name
<< " gave an error when loading:'" << e.what() << "'.\n";
}
file.closeGroup();
}
/**
* Load an SE log entry
* @param file :: A reference to the NeXus file handle opened at the parent group
* @param entry_name :: The name of the log entry
* @param workspace :: A pointer to the workspace to store the logs
*/
Gigg, Martyn Anthony
committed
void LoadNexusLogs::loadSELog(::NeXus::File & file, const std::string & entry_name,
boost::shared_ptr<API::MatrixWorkspace> workspace) const
Gigg, Martyn Anthony
committed
{
// Open the entry
file.openGroup(entry_name, "IXseblock");
std::string propName = entry_name;
if (workspace->run().hasProperty(propName))
{
propName = "selog_" + propName;
}
// There are two possible entries:
Gigg, Martyn Anthony
committed
// value_log - A time series entry. This can contain a corrupt value entry so if it does use the value one
Gigg, Martyn Anthony
committed
// value - A single value float entry
Kernel::Property *logValue(NULL);
std::map<std::string, std::string> entries = file.getEntries();
if( entries.find("value_log") != entries.end() )
{
try
{
try
{
file.openGroup("value_log", "NXlog");
}
catch(::NeXus::Exception&)
{
file.closeGroup();
throw;
}
logValue = createTimeSeries(file, propName);
file.closeGroup();
}
catch(::NeXus::Exception& e)
{
Gigg, Martyn Anthony
committed
g_log.warning() << "IXseblock entry '" << entry_name << "' gave an error when loading "
<< "a time series:'" << e.what() << "'. Skipping entry\n";
file.closeGroup(); //value_log
file.closeGroup();//entry_name
Gigg, Martyn Anthony
committed
return;
}
}
else if( entries.find("value") != entries.end() )
{
try
{
Gigg, Martyn Anthony
committed
// This may have a larger dimension than 1 bit it has no time field so take the first entry
Gigg, Martyn Anthony
committed
file.openData("value");
Gigg, Martyn Anthony
committed
::NeXus::Info info = file.getInfo();
Gigg, Martyn Anthony
committed
if( info.type == ::NeXus::FLOAT32 )
{
boost::scoped_array<float> value(new float[info.dims[0]]);
file.getData(value.get());
file.closeData();
logValue = new Kernel::PropertyWithValue<double>(propName, static_cast<double>(value[0]), true);
}
else
{
file.closeGroup();
return;
}
Gigg, Martyn Anthony
committed
}
catch(::NeXus::Exception& e)
{
g_log.warning() << "IXseblock entry " << entry_name << " gave an error when loading "
<< "a single value:'" << e.what() << "'.\n";
file.closeData();
file.closeGroup();
return;
}
}
else
{
g_log.warning() << "IXseblock entry " << entry_name
Gigg, Martyn Anthony
committed
<< " cannot be read, skipping entry.\n";
Gigg, Martyn Anthony
committed
file.closeGroup();
return;
}
workspace->mutableRun().addProperty(logValue);
file.closeGroup();
}
/**
* Creates a time series property from the currently opened log entry. It is assumed to
* have been checked to have a time field and the value entry's name is given as an argument
* @param file :: A reference to the file handle
* @param prop_name :: The name of the property
* @returns A pointer to a new property containing the time series
*/
Gigg, Martyn Anthony
committed
Kernel::Property * LoadNexusLogs::createTimeSeries(::NeXus::File & file,
Gigg, Martyn Anthony
committed
const std::string & prop_name) const
Gigg, Martyn Anthony
committed
{
file.openData("time");
//----- Start time is an ISO8601 string date and time. ------
std::string start;
try
{
file.getAttr("start", start);
}
catch (::NeXus::Exception &)
{
//Some logs have "offset" instead of start
try
{
file.getAttr("offset", start);
}
catch (::NeXus::Exception &)
{
g_log.warning() << "Log entry has no start time indicated.\n";
file.closeData();
throw;
}
}
if (start.compare("No Time") == 0)
{
start = freqStart;
}
Gigg, Martyn Anthony
committed
//Convert to date and time
Kernel::DateAndTime start_time = Kernel::DateAndTime(start);
std::string time_units;
file.getAttr("units", time_units);
if( time_units.compare("second") < 0 && time_units != "s"
Gigg, Martyn Anthony
committed
&& time_units != "minutes" ) //Can be s/second/seconds/minutes
Gigg, Martyn Anthony
committed
{
file.closeData();
throw ::NeXus::Exception("Unsupported time unit '" + time_units + "'");
}
//--- Load the seconds into a double array ---
std::vector<double> time_double;
try
{
file.getDataCoerce(time_double);
}
catch (::NeXus::Exception &e)
{
g_log.warning() << "Log entry's time field could not be loaded: '" << e.what() << "'.\n";
Gigg, Martyn Anthony
committed
file.closeData();
throw;
}
file.closeData(); // Close time data
g_log.debug() << " done reading \"time\" array\n";
Gigg, Martyn Anthony
committed
// Convert to seconds if needed
if( time_units == "minutes" )
{
std::transform(time_double.begin(),time_double.end(), time_double.begin(),
std::bind2nd(std::multiplies<double>(),60.0));
}
// Now the values: Could be a string, int or double
file.openData("value");
// Get the units of the property
std::string value_units("");
try
{
file.getAttr("units", value_units);
}
catch (::NeXus::Exception &)
{
//Ignore missing units field.
value_units = "";
}
// Now the actual data
::NeXus::Info info = file.getInfo();
Gigg, Martyn Anthony
committed
// Check the size
Janik Zikovsky
committed
if( size_t(info.dims[0]) != time_double.size() )
Gigg, Martyn Anthony
committed
{
file.closeData();
throw ::NeXus::Exception("Invalid value entry for time series");
}
Gigg, Martyn Anthony
committed
if( file.isDataInt() ) // Int type
{
std::vector<int> values;
try
{
file.getDataCoerce(values);
file.closeData();
}
catch(::NeXus::Exception&)
{
file.closeData();
throw;
}
//Make an int TSP
TimeSeriesProperty<int> * tsp = new TimeSeriesProperty<int>(prop_name);
tsp->create(start_time, time_double, values);
tsp->setUnits(value_units);
g_log.debug() << " done reading \"value\" array\n";
Gigg, Martyn Anthony
committed
return tsp;
}
else if( info.type == ::NeXus::CHAR )
{
std::string values;
const int64_t item_length = info.dims[1];
Gigg, Martyn Anthony
committed
try
{
const int64_t nitems = info.dims[0];
const int64_t total_length = nitems*item_length;
Gigg, Martyn Anthony
committed
boost::scoped_array<char> val_array(new char[total_length]);
Gigg, Martyn Anthony
committed
file.getData(val_array.get());
file.closeData();
Gigg, Martyn Anthony
committed
values = std::string(val_array.get(), total_length);
Gigg, Martyn Anthony
committed
}
catch(::NeXus::Exception&)
{
file.closeData();
throw;
}
// The string may contain non-printable (i.e. control) characters, replace these
std::replace_if(values.begin(), values.end(), iscntrl, ' ');
TimeSeriesProperty<std::string> * tsp = new TimeSeriesProperty<std::string>(prop_name);
std::vector<DateAndTime> times;
DateAndTime::createVector(start_time, time_double, times);
const size_t ntimes = times.size();
for(size_t i = 0; i < ntimes; ++i)
{
std::string value_i = std::string(values.data() + i*item_length, item_length);
tsp->addValue(times[i], value_i);
}
tsp->setUnits(value_units);
g_log.debug() << " done reading \"value\" array\n";
Gigg, Martyn Anthony
committed
return tsp;
}
else if( info.type == ::NeXus::FLOAT32 || info.type == ::NeXus::FLOAT64 )
{
std::vector<double> values;
try
{
file.getDataCoerce(values);
file.closeData();
}
catch(::NeXus::Exception&)
{
file.closeData();
throw;
}
TimeSeriesProperty<double> * tsp = new TimeSeriesProperty<double>(prop_name);
tsp->create(start_time, time_double, values);
tsp->setUnits(value_units);
g_log.debug() << " done reading \"value\" array\n";
Gigg, Martyn Anthony
committed
return tsp;
}
else
{
throw ::NeXus::Exception("Invalid value type for time series. Only int, double or strings are "
"supported");
}
}
} // namespace DataHandling
} // namespace Mantid