classdef BeamtimeDaqAccess < handle % BeamtimeDaqAccess provides near online access to data from the MCS4 DAQ. % % A BeamtimeDaqAccess object wraps the BeamtimeDaqAccess library implemented % in Python. It just delegates the method calls to a corresponding Python % object and converts Matlab to Python data types and vice versa. % % Example: % >> daq= BeamtimeDaqAccess('./path-to-root/of-h5-file-directories'); % >> [trace, idInterval]= daq.latestValues('/Experiment/Acqiris/CPCI2/CH01/TD', 10); % % More examples: % * beamtimeDaqAccessDemo.m % * http://hasfweb.desy.de/bin/view/Setup/BeamtimeDaqAccess % % Summary of methods: % * latestsValues to retrieve the lates values for a channel % * valuesOfInterval to retrieve a channel's values for an interval % * firstValuesOfRun to retrieve the first values of a run for a channel % * allValuesOfRun to retrieve all the values of a run and channel % * pulseIdIntervalOfRun to retrieve the interval for a run and channel % * allChannelNames to retrieve all known channel names % % Requirements: % * Matlab version R2015b or later % * Python 2.7, 3.3 or later % * The python BeamtimeDaqAccess library at this file's parent directory % * Python packages h5py, numpy, natsort, pyhamcrest % % Copyright 2015 Erland Muller, License GPL v3 methods function new= BeamtimeDaqAccess(rootDirectoryOfH5Files) % BeamtimeDaqAccess constructs a ready to BeamtimeDaqAccess object. % % The object's delegate caches file data and thus should be reused % for multiple channels, runs and intervals. It's root directory of % HDF files is constant. % % Example: % >> daq= BeamtimeDaqAccess('./path-to-root/of-h5-file-directories'); % % Parameters: % * rootDirectoryOfH5Files: a string defining the location of the % directories, containing the raw .h5 files. % % Returns: a BeamtimeDaqAccess object. % % Throws: % * Assertion error if the directory does not exist or can not be read % * Assertion error if requirements are not met. % assert(~verLessThan('matlab', '8.6'), 'Precond.: Matlab version at least 2015b'); validateattributes(rootDirectoryOfH5Files, {'char'}, {'nonempty'}); pahProjectPath= fullfile(fileparts(which('BeamtimeDaqAccess')), '..'); BeamtimeDaqAccess.extendPythonSearchPath(pahProjectPath); py.importlib.import_module('camp.pah.beamtimedaqaccess'); new.delegate= ... py.camp.pah.beamtimedaqaccess.BeamtimeDaqAccess.create(rootDirectoryOfH5Files); end function [values, idInterval]= latestValues(self, channelName, timeIntervalSeconds, valueSlice) % latestValues returns latest values and the corresponding pulse ID interval. % % Paramters: % * channelName: The fully qualified name of the dataset in the HDF files % * timeIntervalSeconds: The length of time over which the values are returned % * valueSlice (optional): A slice expression as a string; the slicing is done % in the spectral dimensions, not in pulse ID dimension % % Returns: % * values: The latest recored values over the given time interval % * IdInterval: The pulse ID interval. The first element of the % pair is the first pulse ID and the second element corresponds to the % last pulse ID + 1, i.e. an exclusive interval % % Throws: % * AssertionError: If the channel name is invalid % * NoDaqDataException: If there are no data for the given channel % if nargin < 4 valueSlice= ':'; end validateattributes(channelName, {'char'}, {'nonempty'}); validateattributes(timeIntervalSeconds, {'numeric'}, {'scalar', 'positive'}); validateattributes(valueSlice, {'char'}, {'nonempty'}); valuesAndInterval= self.delegate.latestValues(channelName, timeIntervalSeconds); values= self.py2mat(valuesAndInterval{1}); idInterval= double(py.array.array('l', valuesAndInterval{2})); end function values= valuesOfInterval(self, channelName, pulseIdInterval, valueSlice) % valuesOfInterval returns values for given channel and ID interval. % % Parameters: % * channelName: The fully qualified name of the dataset in the HDF files % * pulseIdInterval: The list, defining the interval of pulse % IDs for the values are returned. The first element of the list is the % first pulse ID and the second element corresponds to the last pulse ID + 1. % * valueSlice: A Python slice expression as a string; the slicing is % done in the spectral dimensions, not in pulse ID dimension % % Returns: % values: The values for the given channel and pulse ID interval. % % Throws: % * AssertionError: If the channel name is invalid % * NoDaqDataException: If there are no data for the given channel % * MissingValuesException: If values for only a sub interval of the interval given % could be returned. Note, the exception contains the values available and % the corresponding ID interval. % if nargin < 4 valueSlice= ':'; end validateattributes(channelName, {'char'}, {'nonempty'}); validateattributes(pulseIdInterval, {'numeric'}, {'row', 'positive', 'integer', 'ncols', 2}); validateattributes(valueSlice, {'char'}, {'nonempty'}); pyIdInterval= num2cell(int64(pulseIdInterval)); pyValues= self.delegate.valuesOfInterval(channelName, pyIdInterval, valueSlice); values= self.py2mat(pyValues); end function [values, idInterval]= firstValuesOfRun(self, channelName, runNumber, timeLengthSecs, valueSlice) % firstValuesOfRun returns recorded values for a specific run and a particular time period. % % This method selects the first values available from the specified run and time % duration. If no time duration is specified it defaults to 1 second. % % Parameters: % * channelName: The fully qualified name of the dataset in the HDF files % * runNumber: The FLASH DAQ run number, the given channel was recorded with % * timeLengthSecs (optional): The time length in seconds % * valueSlice (optional): A slice expression as a string; the slicing is % done in the spectral dimensions, not in pulse ID dimension % % Returns: % * values: The first recored values of the run and the given length of time % * idInterval: The corresponding pulse ID interval. The first % element of the list is the first pulse ID and the second element % corresponds to the last pulse ID + 1, i.e. an exclusive interval. % % Throws: % * AssertionError: If the channel name is invalid % * NoDaqDataException: If there are no data for the given channel in the given run % if nargin < 5 valueSlice= ':'; end if nargin < 4 timeLengthSecs= 1; end validateattributes(channelName, {'char'}, {'nonempty'}); validateattributes(runNumber, {'numeric'}, {'scalar', 'positive', 'integer'}); validateattributes(timeLengthSecs, {'numeric'}, {'scalar', 'positive'}); validateattributes(valueSlice, {'char'}, {'nonempty'}); valuesAndInterval= self.delegate.firstValuesOfRun(channelName, int64(runNumber), ... timeLengthSecs, valueSlice); values= self.py2mat(valuesAndInterval{1}); idInterval= double(py.array.array('l', valuesAndInterval{2})); end function [values, idInterval]= allValuesOfRun(self, channelName, runNumber, valueSlice) % allValuesOfRun returns all available values and pulse ID interval for given channel name and run number. % % Parameters: % * channelName: The fully qualified name of the dataset in the HDF files % * runNumber: The FLASH DAQ run number, the given channel was recorded with % * valueSlice (optional): A slice expression as a string; the slicing is % done in the spectral dimensions, not in pulse ID dimension % % Returns: % * values: The recored values of the run for given channel name. % * idInterval: The corresponding pulse ID interval. The first % element of the list is the first pulse ID and the second element % corresponds to the last pulse ID + 1, i.e. an exclusive interval. % % Throws: % * AssertionError: If the channel name is invalid % * NoDaqDataException: If there are no data for the given channel in the given run % if nargin < 4 valueSlice= ':'; end validateattributes(channelName, {'char'}, {'nonempty'}); validateattributes(runNumber, {'numeric'}, {'scalar', 'positive', 'integer'}); validateattributes(valueSlice, {'char'}, {'nonempty'}); valuesAndInterval= self.delegate.allValuesOfRun(channelName, int64(runNumber), valueSlice); values= self.py2mat(valuesAndInterval{1}); idInterval= double(py.array.array('l', valuesAndInterval{2})); end function idInterval= pulseIdIntervalOfRun(self, channelName, runNumber) % pulseIdIntervalOfRun returns the complete pulse ID interval for channel and run number. % % Parameters: % * channelName: The fully qualified name of the dataset in the HDF files % * runNumber: The FLASH DAQ run number, the given channel was recorded with % % Returns: % * idInterval: The pulse ID interval for the given channel. The first % element of the list is the first pulse ID and the second element % corresponds to the last pulse ID + 1, i.e. an exclusive interval. % % Throws: % * AssertionError: If the channel name is invalid % * NoDaqDataException: If there are no data for the given channel in the given run % validateattributes(channelName, {'char'}, {'nonempty'}); validateattributes(runNumber, {'numeric'}, {'scalar', 'positive', 'integer'}); pyIdInterval= self.delegate.pulseIdIntervalOfRun(channelName, int64(runNumber)); idInterval= double(py.array.array('l', pyIdInterval)); end function channels= allChannelNames(self) % allChannelNames returns all known channels for FLASH1. % % The method returns all channel names, which appear in FLASH 1 DAQ HDF files. The % sequence of names is useful to search for a specific channel name. It also permits % to implement design by contract style. % % Returns: % * channelNames: Cell array of known channel names appearing in HDF files % channels= cellfun(@(s) char(s), cell(self.delegate.allChannelNames), 'UniformOutput', false); end function str= version(self) % version returns the libraries version string. str= char(self.delegate.version); end end methods ( Access = private ) function values= py2mat(self, pyValues) values= double(py.array.array('d', py.numpy.nditer(pyValues))); values= reshape(values, flip(cell2mat(cell(pyValues.shape)))); end end methods ( Static, Access = private ) function extendPythonSearchPath(searchPath) currentSearchPaths= cellfun(@(s) char(s), cell(py.sys.path), 'UniformOutput', false); if isempty(find(strcmp(currentSearchPaths, searchPath))) append(py.sys.path, searchPath); end end end properties ( Access = private ) delegate end end