Page tree
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
  • No labels