Unverified Commit a26b5d1e authored by stephen scott tucker's avatar stephen scott tucker Committed by GitHub
Browse files

1.29.0 -- Stim update (#32)

* Change uses of stim "values" to "amps" or "amplitudes" in method names

* Added 'states' propery to StimClass, added getter, setter, and initializer methods and added states to old ones

* Added hmrR_GLM_new

* Fix to SnirfClass GetStims

* Fix in new version of SnirfClass GetStims

* Removed ability to toggle stim state from StimEditGUI

* Changes to SnirfClass MoveStims to support new data dimensions

* Snirf indefinite column stim data support

* Removed attribute testing block

* dataLabels support re: latest SNIRF spec. Edits to AddStims and SetData to ensure stim array is always sorted properly for easy edit with table

* Suppressed debug stuff

* fallback to default column titles for no dataLabels

* Stim toggle interface in MainGUI

* PatchCallback no longer toggles stims

* Fix crashes and visual errors when the cond list is empty

* User function for filtering stims based on any of their values, incl additional scalar columns recently added to SNIRF

* Error checking on dataLabels setter

* hmrR_StimCriteria

* Button for editing extra column names. Still no way to add or remove them

* Fix to UpdateStim method for no original stim case

* Version number

* Ensure exclude regions are visible to user

* Fixed line endings in StimClass for better diff

* Fixed SnirfClas GetStims

* Changed errmargin

* Edit columns button

* Fixed a bug which caused aux plot to fail in stimEditGUI if aux offset wasn't defined

* Removed cond name 1 char limit; MenuBox no longer crashes on window close

* MainGUI.fig merge; add checkboxExcludeStims to EnableDisable fn

* Constructor converts nirs-style values into state values
parent bfa8af50
Loading
Loading
Loading
Loading
+8 −8
Original line number Diff line number Diff line
@@ -34,15 +34,15 @@ classdef NirsClass < AcqDataClass & FileLoadSaveClass
            % 
            %           NirsClass with properties:
            % 
            %                      SD: [11 struct]
            %                       t: [126981 double]
            %                       s: [126982 double]
            %                       d: [1269818 double]
            %                     aux: [126981 double]
            %                      SD: [11 struct]
            %                       t: [126981 double]
            %                       s: [126982 double]
            %                       d: [1269818 double]
            %                     aux: [126981 double]
            %               CondNames: {'1'  '2'}
            %                filename: './s1/neuro_run01.nirs'
            %              fileformat: 'mat'
            %         supportedFomats: [11 struct]
            %         supportedFomats: [11 struct]
            %                     err: 0
            %
            
@@ -686,13 +686,13 @@ classdef NirsClass < AcqDataClass & FileLoadSaveClass
                
                
        % ----------------------------------------------------------------------------------
        function SetStimValues(obj, icond, vals)
        function SetStimAmplitudes(obj, icond, vals)
            return;
        end
        
        
        % ----------------------------------------------------------------------------------
        function vals = GetStimValues(obj, icond)
        function vals = GetStimAmplitudes(obj, icond)
            vals = [];
        end
        
+46 −39
Original line number Diff line number Diff line
@@ -969,7 +969,7 @@ classdef SnirfClass < AcqDataClass & FileLoadSaveClass
                    if ~obj.stim(ii).Exists(t(tidxs(jj)))
                        obj.stim(ii).AddStims(t(tidxs(jj)));
                    else
                        obj.stim(ii).EditValue(t(tidxs(jj)), s(tidxs(jj),ii));
                        obj.stim(ii).EditState(t(tidxs(jj)), s(tidxs(jj),ii));
                    end
                end
            end
@@ -978,14 +978,35 @@ classdef SnirfClass < AcqDataClass & FileLoadSaveClass
        
        % ---------------------------------------------------------
        function s = GetStims(obj, t)
            % Returns a .nirs style stim signal. Stim state marks are
            % interpolated onto the time series t from their respective
            % onset times.
            s = zeros(length(t), length(obj.stim));
            for i=1:length(obj.stim)
                states = obj.stim(i).GetStates();
                if ~isempty(states)
                    [~, k] = nearest_point(t, states(:, 1));
                    if ~isempty(k)
                        s(k,i) = states(:, 2);
                    end
                end
            end
        end
        
        
        % ---------------------------------------------------------
        function s = GetStimAmps(obj, t)
            % Returns a .nirs style stim signal. Stim amplitudes are
            % interpolated onto the time series t from their respective
            % onset times.
            s = zeros(length(t), length(obj.stim));
            for ii=1:length(obj.stim)
                [ts, v] = obj.stim(ii).GetStim();
                [~, k] = nearest_point(t, ts);
                data = obj.stim.GetData();
                [~, k] = nearest_point(t, data(:, 1));
                if isempty(k)
                    continue;
                end
                s(k,ii) = v;
                s(k,ii) = data(:, 3);
            end
        end
        
@@ -1240,57 +1261,43 @@ classdef SnirfClass < AcqDataClass & FileLoadSaveClass
            
            % Find the destination condition to move stims (among the time pts in tPts)
            % to
            j = [];
            idx_dst = [];
            for ii=1:length(obj.stim)
                if strcmp(condition, obj.stim(ii).GetName())
                    j=ii;
                    idx_dst=ii;
                    break;
                end
            end
            
            % If no destination condition found among existing conditions,
            % then create a new condition to move stims to
            if isempty(j)
                j = length(obj.stim)+1;
            if isempty(idx_dst)
                idx_dst = length(obj.stim)+1;
                
                % Otherwise we have a new condition to which to add the stims.
                obj.stim(j) = StimClass([], condition);
                obj.stim(idx_dst) = StimClass([], condition);
                obj.SortStims();
                
                % Recalculate j after sort
                for ii=1:length(obj.stim)
                    if strcmp(condition, obj.stim(ii).GetName())
                        j=ii;
                        idx_dst=ii;
                        break;
                    end
                end
            end
            
            % Find all stims for any conditions which match the time points.
            for ii=1:length(tPts)
                for kk=1:length(obj.stim)
                    d = obj.stim(kk).GetData();
                    if isempty(d)
                        continue;
                    end
                    k = find(d(:,1)==tPts(ii));
                    if ~isempty(k)
                        if kk==j
                            continue;
                        end
                        
                        % If stim at time point tPts(ii) exists in stim
                        % condition kk, then move stim from obj.stim(kk) to
                        % obj.stim(j)
                        obj.stim(j).AddStims(tPts(ii), d(k(1),2), d(k(1),3));
                        
                        % After moving stim from obj.stim(kk) to
                        % obj.stim(j), delete it from obj.stim(kk)
                        d(k(1),:)=[];
                        obj.stim(kk).SetData(d);
                        
                        % Move on to next time point
                        break;
            for i=1:length(obj.stim)
                data = obj.stim(i).GetData();
                for j=1:size(data, 1)
                    onset = data(j, 1);
                    if onset > min(tPts) & onset < max(tPts)
                        % Delete the stim from its condition and add it to selected dst
                        duration = data(j, 2);
                        amplitude = data(j, 3);
                        more = data(j, 4:end);
                        obj.stim(i).DeleteStims(onset);
                        obj.stim(idx_dst).AddStims(onset, duration, amplitude, more);
                    end
                end
            end
@@ -1330,19 +1337,19 @@ classdef SnirfClass < AcqDataClass & FileLoadSaveClass
        
        
        % ----------------------------------------------------------------------------------
        function SetStimValues(obj, icond, vals)
            obj.stim(icond).SetValues(vals);
        function SetStimAmplitudes(obj, icond, amps)
            obj.stim(icond).SetAmplitudes(amps);
        end
        
        
        
        % ----------------------------------------------------------------------------------
        function vals = GetStimValues(obj, icond)
        function vals = GetStimAmplitudes(obj, icond)
            if icond>length(obj.stim)
                vals = [];
                return;
            end
            vals = obj.stim(icond).GetValues();
            vals = obj.stim(icond).GetAmplitudes();
        end
        
        
+552 −449
Original line number Diff line number Diff line
@@ -3,11 +3,13 @@ classdef StimClass < FileLoadSaveClass
    properties
        name
        data
        dataLabels
        states  % Stim marks enabled/disabled. Not part of SNIRF
    end
   
    % Properties not part of the SNIRF spec. These parameters aren't loaded or saved to files
    properties (Access = private)
        errmargin
        errmargin  % Margin for interpolating onset times. Not part of SNIRF
    end    
    
    methods
@@ -16,8 +18,8 @@ classdef StimClass < FileLoadSaveClass
        function obj = StimClass(varargin)
            % Set class properties not part of the SNIRF format
            obj.SetFileFormat('hdf5');
            obj.errmargin = 1e-3;

            obj.errmargin = 1e-2;
            obj.states = [];
            if nargin==1 
                if isa(varargin{1}, 'StimClass')
                    obj.Copy(varargin{1});
@@ -28,19 +30,22 @@ classdef StimClass < FileLoadSaveClass
                    else
                        obj.name = varargin{1};
                        obj.data = [];
                        obj.dataLabels = {'Onset', 'Duration', 'Amplitude'};
                    end
                end
            elseif nargin==2
                if ischar(varargin{1})
                    obj.SetFilename(varargin{1});
                    obj.Load(varargin{1}, obj.fileformat, varargin{2});
                    % Note that states are not loaded from file
                else
                    t        = varargin{1};
                    CondName = varargin{2};
                    obj.name = CondName;
                    for ii=1:length(t)
                        obj.data(end+1,:) = [t(ii), 5, 1];
                        obj.data(end+1,:) = [t(ii), 10, 1];
                    end
                    obj.dataLabels = {'Onset', 'Duration', 'Amplitude'};
                end
            elseif nargin==3
                s        = varargin{1};
@@ -48,12 +53,16 @@ classdef StimClass < FileLoadSaveClass
                CondName = varargin{3};
                obj.name = CondName;
                k = s>0 | s==-1 | s==-2;  % Include stim marks with these values
                obj.data = [t(k), 5*ones(length(t(k)),1), s(k)];
                obj.data = [t(k), 10*ones(length(t(k)),1), ones(length(t(k)),1)];
                obj.states = [t(k) s(k)];
                obj.dataLabels = {'Onset', 'Duration', 'Amplitude'};
            elseif nargin==0
                obj.name = '';
                obj.data = [];
                obj.dataLabels = {'Onset', 'Duration', 'Amplitude'};
            end
            
            obj.SortTpts();
            obj.updateStates();  % Generates enabled states to match the data array
        end
        
        
@@ -92,6 +101,7 @@ classdef StimClass < FileLoadSaveClass
                % Load datasets
                obj.name   = HDF5_DatasetLoad(gid, 'name');
                obj.data   = HDF5_DatasetLoad(gid, 'data', [], '2D');
                obj.dataLabels   = HDF5_DatasetLoad(gid, 'dataLabels', {});
                if all(obj.data(:)==0)
                    obj.data = [];
                end
@@ -105,7 +115,7 @@ classdef StimClass < FileLoadSaveClass
                return
                
            end
            
            obj.updateStates();
        end
        
        
@@ -132,11 +142,13 @@ classdef StimClass < FileLoadSaveClass
                fid = H5F.create(fileobj, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT');
                H5F.close(fid);
            end
            
            hdf5write_safe(fileobj, [location, '/name'], obj.name);
            
            % Since this is a writable writeable parameter AFTER it's creation, we 
            % call hdf5write_safe with the 'rw' option
            hdf5write_safe(fileobj, [location, '/data'], obj.data, 'rw:2D');
            hdf5write_safe(fileobj, [location, '/dataLabels'], obj.dataLabels, 'rw');
        end
        
        
@@ -149,6 +161,7 @@ classdef StimClass < FileLoadSaveClass
            end
            hdf5write_safe(fileobj, [location, '/name'], obj.name);
            hdf5write_safe(fileobj, [location, '/data'], obj.data, 'w');
            hdf5write_safe(fileobj, [location, '/dataLabels'], obj.dataLabels, 'w');
        end
        
        
@@ -156,6 +169,8 @@ classdef StimClass < FileLoadSaveClass
        function Copy(obj, obj2)
            obj.name = obj2.name;
            obj.data = obj2.data;
            obj.dataLabels = obj2.dataLabels;
            obj.states = obj2.states;
        end
        
        
@@ -178,6 +193,30 @@ classdef StimClass < FileLoadSaveClass
            B = true;
        end
    
        function updateStates(obj)
            % Generate or regenerate a state list compatible with the data
            % array. Match up existing states with new list of time points
            if isempty(obj.data)
                return;
            elseif size(obj.states, 1) == size(obj.data, 1)
                obj.states(:, 1) = obj.data(:, 1);
                return;
            end
            old = obj.states;
            obj.states = ones(size(obj.data, 1), 2);
            for i=1:size(obj.data, 1)  % For each data row
                if ~isempty(old)
                    k = find(abs(obj.data(i,1) - old(:, 1)) < obj.errmargin);
                else
                    k = []; 
                end
                if isempty(k) % If old state not there, generate new one
                    obj.states(i, :) = [obj.data(i, 1), 1];
                else % Get old state if it exists 
                   obj.states(i, :) = old(k, :);
                end
            end
        end
    end
    
    
@@ -199,6 +238,7 @@ classdef StimClass < FileLoadSaveClass
        % -------------------------------------------------------
        function SetData(obj, val)
            obj.data = val;
            obj.updateStates();
        end
        
        % -------------------------------------------------------
@@ -206,6 +246,31 @@ classdef StimClass < FileLoadSaveClass
            val = obj.data;
        end

        % -------------------------------------------------------
        function SetStates(obj, states)
            obj.states = states;
            obj.updateStates();
        end
        
        % -------------------------------------------------------
        function val = GetStates(obj)
            val = obj.states;
        end
        
        function SetDataLabels(obj, dataLabels)
            if length(dataLabels) < size(obj.data, 2)
               for i = 1:size(obj.data, 2) - length(dataLabels)
                  dataLabels{end + 1} = ''; 
               end
            end
            obj.dataLabels = dataLabels(1:size(obj.data, 2));
        end
        
        % -------------------------------------------------------
        function val = GetDataLabels(obj)
            val = obj.dataLabels;
        end
        
    end
    
    
@@ -231,48 +296,70 @@ classdef StimClass < FileLoadSaveClass

        
        % ----------------------------------------------------------------------------------
        function AddStims(obj, tPts, duration, val)
        function AddStims(obj, tPts, duration, amp, more)
            % Add one or more stims to with a given duration, amplitude, and additional
            % column data given by more
            if ~exist('duration','var')
                duration = 5;
                duration = 10;
            end
            if ~exist('val','var')
                val = 1;
            if ~exist('amp','var')
                amp = 1;
            end
            if ~exist('more', 'var') | isempty(more)
               more = zeros(size(obj.data, 2) - 3);
            end
            if ~isempty(obj.data)
                if ~isempty(more) & length(more) > (size(obj.data, 2) - 3)
                    obj.data(:, end+length(more)) = 0;  % Pad to accomodate additional data columns 
                end
                for i=1:length(tPts)
                    if ~obj.Exists(tPts(i))
                        obj.data(end+1,:) = [tPts(i), duration, amp, more];
                        obj.states(end+1,:) = [tPts(i), 1];
                    end
            for ii=1:length(tPts)
                if ~obj.Exists(tPts(ii))
                    obj.data(end+1,:) = [tPts(ii), duration, val];
                end 
            else  % If this stim is being added to an empty condition
                for i=1:length(tPts)
                    obj.data = [tPts(i), duration, amp, more];
                    obj.states = [tPts(i), 1];
                end
            end
        end

        
        % ----------------------------------------------------------------------------------
        function EditValue(obj, tPts, val)
        function EditAmplitude(obj, tPts, amp)
            if isempty(obj.data)
                return;
            end
            if ~exist('val','var')
                val = 1;
            if ~exist('amp','var')
                amp = 1;
            end
            for ii=1:length(tPts)
                k = find( abs(obj.data(:,1)-tPts(ii)) < obj.errmargin );
                if isempty(k)
                    continue;
                end
                obj.data(k,3) = val;
                obj.data(k,3) = amp;
            end
        end

        
        % -------------------------------------------------------
        function [ts, v] = GetStim(obj)
            ts = [];
            v = [];
        % ----------------------------------------------------------------------------------
        function EditState(obj, tPts, state)
            if isempty(obj.data)
                return;
            end
            ts = obj.data(:,1);
            v = obj.data(:,3);
            if ~exist('state','var')
                state = 1;
            end
            for ii=1:length(tPts)
                k = find( abs(obj.states(:,1)-tPts(ii)) < obj.errmargin );
                if isempty(k)
                    continue;
                end
                obj.states(k,2) = state;
            end
        end
        
        
@@ -288,6 +375,7 @@ classdef StimClass < FileLoadSaveClass
                return;
            end
            obj.data(k,1) = tPts;
            obj.SortTpts();
        end

        
@@ -344,12 +432,12 @@ classdef StimClass < FileLoadSaveClass
        
        
        % ----------------------------------------------------------------------------------
        function SetValues(obj, vals, tPts)
        function SetAmplitudes(obj, amps, tPts)
            if isempty(obj.data)
                return;
            end
            if ~exist('vals','var')
                vals = 1;
            if ~exist('amps','var')
                amps = 1;
            end
            if ~exist('tPts','var')
                tPts = obj.data(:,1);
@@ -361,13 +449,13 @@ classdef StimClass < FileLoadSaveClass
                    continue;
                end
            end
            obj.data(k,3) = vals;
            obj.data(k,3) = amps;
        end

        
        % -------------------------------------------------------
        function vals = GetValues(obj, tPts)
            vals = [];
        function amps = GetAmplitudes(obj, tPts)
            amps = [];
            if isempty(obj.data)
                return;
            end
@@ -378,7 +466,7 @@ classdef StimClass < FileLoadSaveClass
            for ii=1:length(tPts)
                k(ii) = find( abs(obj.data(:,1)-tPts(ii)) < obj.errmargin );
            end
            vals = obj.data(k,3);
            amps = obj.data(k,3);
        end
        
        
@@ -391,10 +479,12 @@ classdef StimClass < FileLoadSaveClass
            % Find all stims for any conditions which match the time points and 
            % delete them from data. 
            k = [];
            j = [];
            for ii=1:length(tPts)
                k = [k, find( abs(obj.data(:,1)-tPts(ii)) < obj.errmargin )];
            end
            obj.data(k,:) = [];
            obj.states(k,:) = [];
        end
        
        
@@ -406,12 +496,12 @@ classdef StimClass < FileLoadSaveClass
            end
            
            % Find all stims for any conditions which match the time points and 
            % flip it's value.
            % flip their states
            k = [];
            for ii=1:length(tPts)
                k = [k, find( abs(obj.data(:,1)-tPts(ii)) < obj.errmargin )];
                k = [k, find( abs(obj.states(:,1)-tPts(ii)) < obj.errmargin )];
            end
            obj.data(k,3) = -1*obj.data(k,3);
            obj.states(k,2) = -1*obj.states(k,2);
        end
        
        
@@ -435,13 +525,26 @@ classdef StimClass < FileLoadSaveClass
        end
        
        
        
        % ----------------------------------------------------------------------------------
        function SortTpts(obj)
            try
                [~, idx] = sort(obj.data(:, 1));
                obj.data = obj.data(idx, :);
                obj.states = obj.states(idx, :);
            catch  % Index error
               return; 
            end
        end
           
        
        % ----------------------------------------------------------------------------------
        function nbytes = MemoryRequired(obj)
            nbytes = 0;
            if isempty(obj)
                return
            end
            nbytes = sizeof(obj.name) + sizeof(obj.data) + sizeof(obj.GetFilename()) + sizeof(obj.GetFileFormat()) + sizeof(obj.GetSupportedFormats()) + 8;
            nbytes = sizeof(obj.states) + sizeof(obj.name) + sizeof(obj.data) + sizeof(obj.GetFilename()) + sizeof(obj.GetFileFormat()) + sizeof(obj.GetSupportedFormats()) + 8;
        end
        
    end
+5 −5
Original line number Diff line number Diff line
@@ -321,7 +321,7 @@ classdef ProcInputClass < handle
        function [tpts, duration, vals] = GetStimData(obj, icond)
            tpts     = obj.GetStimTpts(icond);
            duration = obj.GetStimDuration(icond);
            vals     = obj.GetStimValues(icond);
            vals     = obj.GetStimAmplitudes(icond);
        end
        
    
@@ -356,17 +356,17 @@ classdef ProcInputClass < handle
        
        
        % ----------------------------------------------------------------------------------
        function SetStimValues(obj, icond, vals)
            obj.acquired.SetStimValues(icond, vals);
        function SetStimAmplitudes(obj, icond, vals)
            obj.acquired.SetStimAmplitudes(icond, vals);
        end
        
    
        % ----------------------------------------------------------------------------------
        function vals = GetStimValues(obj, icond)
        function vals = GetStimAmplitudes(obj, icond)
            if ~exist('icond','var')
                icond=1;
            end
            vals = obj.acquired.GetStimValues(icond);
            vals = obj.acquired.GetStimAmplitudes(icond);
        end
        
        
+5 −5
Original line number Diff line number Diff line
@@ -1385,7 +1385,7 @@ classdef ProcStreamClass < handle
        function [tpts, duration, vals] = GetStimData(obj, icond)
            tpts     = obj.GetStimTpts(icond);
            duration = obj.GetStimDuration(icond);
            vals     = obj.GetStimValues(icond);
            vals     = obj.GetStimAmplitudes(icond);
        end
        
    
@@ -1420,17 +1420,17 @@ classdef ProcStreamClass < handle
        
        
        % ----------------------------------------------------------------------------------
        function SetStimValues(obj, icond, vals)
            obj.input.SetStimValues(icond, vals);
        function SetStimAmplitudes(obj, icond, vals)
            obj.input.SetStimAmplitudes(icond, vals);
        end
        
    
        % ----------------------------------------------------------------------------------
        function vals = GetStimValues(obj, icond)
        function vals = GetStimAmplitudes(obj, icond)
            if ~exist('icond','var')
                icond=1;
            end
            vals = obj.input.GetStimValues(icond);
            vals = obj.input.GetStimAmplitudes(icond);
        end
                       
        
Loading