diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..5d2497da --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +### Description + +Please include a summary of the changes made. Include any relevant context, screenshots, or details. + +### Type of Change + +- [ ] Bug fix +- [ ] New feature +- [ ] Documentation update +- [ ] Breaking change +- [ ] Other (please specify): + +### Checklist + +- [ ] I have tested the changes locally. +- [ ] I have updated the documentation accordingly. +- [ ] I have added necessary tests to cover the changes. +- [ ] I have followed the code style guidelines. +- [ ] I have verified that the code works in all target environments. +- [ ] I have made sure the technicians are comfortable using the new feature (if applicable). + +### Related Issues + +Closes # (issue number) + diff --git a/.gitignore b/.gitignore index a71a258a..3b4f6f90 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ .svn/ SoloData/ Bonsai/ +training_videos/ ExperPort/Settings/Settings_Custom_v0_789.conf ExperPort/Settings/Settings_Custom_v2_0.conf ExperPort/Settings/Settings_Custom.conf ExperPort/sendsummary_error_log.txt +/ExperPort/settings_@*.mat PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat +.gitattributes +.DS_Store +Screenshot 2025-01-22 at 11.22.30.png diff --git a/ExperPort/Modules/@NeuropixelNeuroblueprint/NeuropixelNeuroblueprint.asv b/ExperPort/Modules/@NeuropixelNeuroblueprint/NeuropixelNeuroblueprint.asv new file mode 100644 index 00000000..1e357a40 --- /dev/null +++ b/ExperPort/Modules/@NeuropixelNeuroblueprint/NeuropixelNeuroblueprint.asv @@ -0,0 +1,1514 @@ +function [obj, varargout] = NeuropixelNeuroblueprint(varargin) +% NEUROPIXEL_NEUROBLUEPRINT_GUI - Integrated GUI for electrophysiology and behavior experiments +% +% This GUI manages experimental workflows for coordinated electrophysiology recording +% (using either Open Ephys or SpikeGLX) with behavioral protocols using Bpod/ExperPort. +% +% FEATURES: +% - Support for both Open Ephys and SpikeGLX recording systems +% - Neuropixels probe management (NP 1.0 and 2.0) +% - NeuroBlueprint data organization format +% - Automated session management and data saving +% - SVN integration for version control +% - Pre/post-session sampling across probe banks +% +% PRE-REQUISITES: +% 1. 'open-ephys-matlab-tools' must be in MATLAB path (for Open Ephys) +% 2. 'SpikeGLX-MATLAB-SDK' must be in MATLAB path (for SpikeGLX) +% 3. Bpod/ratter/ExperPort environment fully configured +% +% USAGE: +% gui_obj = NeuropixelNeuroblueprintGUI(); +% +%% Boilerplate for class definition and action handling +obj = class(struct, mfilename); +varargout = {}; +% Display usage help when function is called directly +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')) + display_usage_help(); + return; +end +if isa(varargin{1}, mfilename) + if length(varargin) < 2 || ~ischar(varargin{2}) + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else + action = varargin{2}; + varargin = varargin(3:end); + end +else + action = varargin{1}; + varargin = varargin(2:end); +end +if ~ischar(action) + error('The action parameter must be a string'); +end +GetSoloFunctionArgs(obj); +%% Main Action Router +switch action + % ========================================================================= + % CASE INIT + % ========================================================================= + case 'init' + % So that only the CPU-based software renderer instead of your graphics card + % opengl software; + + % Start Bpod if not already running + if evalin('base', 'exist(''BpodSystem'', ''var'')') + if evalin('base', '~isempty(BpodSystem)'), newstartup; else, flush; end + else, Bpod('COM5');newstartup; + end + + % --- State Variables as SoloParamHandles --- + SoloParamHandle(obj, 'currentState', 'value', 'Load'); + SoloParamHandle(obj, 'behavState', 'value', 'Run'); + SoloParamHandle(obj, 'ephysState', 'value', 'Run'); + SoloParamHandle(obj, 'is_running', 'value', 0); + SoloParamHandle(obj, 'recording_software', 'value', 'OpenEphys'); % 'OpenEphys' or 'SpikeGLX' + SoloParamHandle(obj, 'recording_controller', 'value', []); % Will hold OE or SpikeGLX controller + SoloParamHandle(obj, 'behav_obj', 'value', []); + SoloParamHandle(obj, 'blinking_timer', 'value', []); + SoloParamHandle(obj, 'current_params', 'value', []); + SoloParamHandle(obj, 'session_base_path', 'value', ''); + SoloParamHandle(obj, 'probe_settings', 'value', struct('version', '2.0', 'reference', 'Tip', 'bank', 0, 'imro_path', '')); + SoloParamHandle(obj, 'probe_gui_handles', 'value', []); + SoloParamHandle(obj, 'session_info_list', 'value', []); % Holds parsed info from folders + + % Create stopping timer for behavior + scr = timer; + set(scr,'Period', 0.2,'ExecutionMode','FixedRate','TasksToExecute',Inf,... + 'BusyMode','drop','TimerFcn',[mfilename,'(''End_Continued'')']); + SoloParamHandle(obj, 'stopping_complete_timer', 'value', scr); + + % --- Create the GUI Figure --- + SoloParamHandle(obj, 'myfig', 'saveable', 0); + myfig.value = figure('Name', 'Neuropixels Recording & Behavior Controller',... + 'NumberTitle', 'off', 'MenuBar', 'none', ... + 'ToolBar', 'none', 'Units', 'normalized', ... + 'Position', [0.1, 0.1, 0.7, 0.85],... + 'Color', [0.94, 0.94, 0.94], ... + 'CloseRequestFcn', {@(h,e) feval(mfilename, obj, 'close')}); + % --- UI Creation --- + handles = struct(); + + % Activity Log Panel + uipanel('Title', 'Activity Log', 'FontSize', 12, 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.64, 0.03, 0.34, 0.94]); + handles.log_box = uicontrol('Style', 'edit', 'Units', 'normalized', 'Position', [0.65, 0.05, 0.32, 0.89], 'String', {'Log started...'}, 'Max', 10, 'Min', 1, 'HorizontalAlignment', 'left', 'Enable', 'inactive', 'BackgroundColor', [1, 1, 1]); + + % Panel 0: Recording Software Selection + p0 = uipanel('Title', '0. Recording Software', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.88, 0.6, 0.09]); + handles.software_group = uibuttongroup(p0, 'Title', '', 'BorderType', 'none', 'Units', 'normalized', 'Position', [0.05, 0.1, 0.9, 0.8], 'SelectionChangedFcn', {@(h,e) feval(mfilename, obj, 'recording_software_callback')}); + uicontrol(handles.software_group, 'Style', 'radiobutton', 'String', 'Open Ephys', 'Units', 'normalized', 'Position', [0.1, 0.3, 0.4, 0.4], 'Tag', 'OpenEphys', 'FontSize', 10); + uicontrol(handles.software_group, 'Style', 'radiobutton', 'String', 'SpikeGLX', 'Units', 'normalized', 'Position', [0.5, 0.3, 0.4, 0.4], 'Tag', 'SpikeGLX', 'FontSize', 10); + + % Panel 1: Behavior + p1 = uipanel('Title', '1. Behavior', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.74, 0.6, 0.13]); + uicontrol(p1, 'Style', 'text', 'String', 'Protocol Name:', 'Units', 'normalized', 'Position', [0.05, 0.7, 0.22, 0.25], 'HorizontalAlignment', 'right'); + handles.protocol_edit = uicontrol(p1, 'Style', 'edit', 'String', 'ArpitSoundCatContinuous', 'Units', 'normalized', 'Position', [0.3, 0.7, 0.45, 0.25]); + handles.manual_test = uicontrol(p1, 'Style', 'checkbox', 'String', 'Manual Test', 'Value', 1, 'Units', 'normalized', 'Position', [0.78, 0.7, 0.2, 0.25]); + uicontrol(p1, 'Style', 'text', 'String', 'Experimenter:', 'Units', 'normalized', 'Position', [0.02, 0.4, 0.2, 0.25], 'HorizontalAlignment', 'right'); + handles.exp_popup = uicontrol(p1, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.23, 0.4, 0.25, 0.25], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'filter')}); + uicontrol(p1, 'Style', 'text', 'String', 'Rat Name:', 'Units', 'normalized', 'Position', [0.50, 0.4, 0.18, 0.25], 'HorizontalAlignment', 'right'); + handles.rat_name_popup = uicontrol(p1, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.69, 0.4, 0.30, 0.25], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'filter')}); + uicontrol(p1, 'Style', 'text', 'String', 'Distribution:', 'Units', 'normalized', 'Position', [0.02, 0.1, 0.18, 0.25], 'HorizontalAlignment', 'right'); + handles.distribution_popup = uicontrol(p1, 'Style', 'popupmenu', 'String', {'random', 'Uniform', 'Hard A', 'Hard B'}, 'Units', 'normalized', 'Position', [0.21, 0.1, 0.35, 0.25]); + uicontrol(p1, 'Style', 'text', 'String', 'Path:', 'Units', 'normalized', 'Position', [0.58, 0.1, 0.1, 0.25], 'HorizontalAlignment', 'right'); + handles.behav_edit = uicontrol(p1, 'Style', 'edit', 'String', 'C:\ratter', 'Units', 'normalized', 'Position', [0.69, 0.1, 0.30, 0.25]); + + % Panel 2: NeuroBlueprint Format + p2 = uipanel('Title', '2. NeuroBlueprint Format', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.42, 0.6, 0.31]); + uicontrol(p2, 'Style', 'text', 'String', 'Project Name:', 'Units', 'normalized', 'Position', [0.01, 0.85, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.proj_edit = uicontrol(p2, 'Style', 'edit', 'String', 'sound_cat_rat', 'Units', 'normalized', 'Position', [0.3, 0.85, 0.65, 0.12], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'rescan')}); + uicontrol(p2, 'Style', 'text', 'String', 'Subject ID:', 'Units', 'normalized', 'Position', [0.01, 0.7, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.sub_popup = uicontrol(p2, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.3, 0.7, 0.3, 0.12], 'Enable', 'inactive'); + uicontrol(p2, 'Style', 'text', 'String', 'Session ID:', 'Units', 'normalized', 'Position', [0.61, 0.7, 0.2, 0.1], 'HorizontalAlignment', 'right'); + handles.session_popup = uicontrol(p2, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.82, 0.7, 0.15, 0.12]); + uicontrol(p2, 'Style', 'text', 'String', 'Local Path:', 'Units', 'normalized', 'Position', [0.01, 0.55, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.local_edit = uicontrol(p2, 'Style', 'edit', 'String', 'C:\Ephys_Experiment_Data', 'Units', 'normalized', 'Position', [0.3, 0.55, 0.5, 0.12], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'rescan')}); + handles.local_browse = uicontrol(p2, 'Style', 'pushbutton', 'String', 'Browse...', 'Units', 'normalized', 'Position', [0.81, 0.55, 0.16, 0.13], 'Callback', {@(h,e) feval(mfilename, obj, 'browse_path', 'local')}); + uicontrol(p2, 'Style', 'text', 'String', 'Central Path:', 'Units', 'normalized', 'Position', [0.01, 0.4, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.central_edit = uicontrol(p2, 'Style', 'edit', 'String', 'Z:\_projects', 'Units', 'normalized', 'Position', [0.3, 0.4, 0.5, 0.12], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'rescan')}); + handles.central_browse = uicontrol(p2, 'Style', 'pushbutton', 'String', 'Browse...', 'Units', 'normalized', 'Position', [0.81, 0.4, 0.16, 0.13], 'Callback', {@(h,e) feval(mfilename, obj, 'browse_path', 'central')}); + uicontrol(p2, 'Style', 'text', 'String', 'Subfolders to Create:', 'Units', 'normalized', 'Position', [0.05, 0.2, 0.9, 0.1], 'HorizontalAlignment', 'left'); + handles.cb_ephys = uicontrol(p2, 'Style', 'checkbox', 'String', 'ephys', 'Value', 1, 'Units', 'normalized', 'Position', [0.05, 0.05, 0.2, 0.15]); + handles.cb_behav = uicontrol(p2, 'Style', 'checkbox', 'String', 'behav', 'Value', 1, 'Units', 'normalized', 'Position', [0.28, 0.05, 0.2, 0.15]); + handles.cb_anat = uicontrol(p2, 'Style', 'checkbox', 'String', 'anat', 'Value', 0, 'Units', 'normalized', 'Position', [0.51, 0.05, 0.2, 0.15]); + handles.cb_funcimg = uicontrol(p2, 'Style', 'checkbox', 'String', 'funcimg', 'Value', 0, 'Units', 'normalized', 'Position', [0.74, 0.05, 0.25, 0.15]); + + % Panel 3: Pre/Post-Experiment Sampling + p3 = uipanel('Title', '3. Pre/Post-Experiment Sampling', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.28, 0.6, 0.12]); + uicontrol(p3, 'Style', 'text', 'String', 'Duration/Bank (s):', 'Units', 'normalized', 'Position', [0.01, 0.6, 0.25, 0.25], 'HorizontalAlignment', 'right'); + handles.sample_duration = uicontrol(p3, 'Style', 'edit', 'String', '60', 'Units', 'normalized', 'Position', [0.27, 0.6, 0.1, 0.3]); + handles.target_display = uicontrol(p3, 'Style', 'text', 'String', 'Target: Bank 0', 'Units', 'normalized', 'Position', [0.38, 0.6, 0.3, 0.25], 'HorizontalAlignment', 'right', 'FontWeight', 'bold'); + handles.probe_button = uicontrol(p3, 'Style', 'pushbutton', 'String', 'Probe Setting', 'Units', 'normalized', 'Position', [0.7, 0.55, 0.28, 0.4], 'FontSize', 10, 'Callback', {@(h,e) feval(mfilename, obj, 'open_probe_gui')}); + handles.sample_button = uicontrol(p3, 'Style', 'pushbutton', 'String', 'Start Sample Recording', 'Units', 'normalized', 'Position', [0.05, 0.1, 0.9, 0.4], 'FontSize', 12, 'FontWeight', 'bold', 'BackgroundColor', [0.8, 0.7, 1], 'Callback', {@(h,e) feval(mfilename, obj, 'sample_recording_wrapper')}); + + % Panel 4: Recording Settings + p4 = uipanel('Title', '4. Recording Settings', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.15, 0.6, 0.11]); + handles.settings_panel = p4; + + % Open Ephys Settings (initially visible) + handles.oe_ip_label = uicontrol(p4, 'Style', 'text', 'String', 'GUI IP:', 'Units', 'normalized', 'Position', [0.01, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right'); + handles.oe_ip_edit = uicontrol(p4, 'Style', 'edit', 'String', '127.0.0.1', 'Units', 'normalized', 'Position', [0.17, 0.25, 0.15, 0.5]); + handles.oe_proc_label = uicontrol(p4, 'Style', 'text', 'String', 'Proc ID:', 'Units', 'normalized', 'Position', [0.33, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right'); + handles.oe_proc_edit = uicontrol(p4, 'Style', 'edit', 'String', '100', 'Units', 'normalized', 'Position', [0.49, 0.25, 0.15, 0.5]); + handles.oe_rec_label = uicontrol(p4, 'Style', 'text', 'String', 'Rec ID:', 'Units', 'normalized', 'Position', [0.65, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right'); + handles.oe_rec_edit = uicontrol(p4, 'Style', 'edit', 'String', '101', 'Units', 'normalized', 'Position', [0.81, 0.25, 0.15, 0.5]); + + % SpikeGLX Settings (initially hidden) + handles.sglx_host_label = uicontrol(p4, 'Style', 'text', 'String', 'Host IP:', 'Units', 'normalized', 'Position', [0.01, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right', 'Visible', 'off'); + handles.sglx_host_edit = uicontrol(p4, 'Style', 'edit', 'String', 'localhost', 'Units', 'normalized', 'Position', [0.17, 0.25, 0.15, 0.5], 'Visible', 'off'); + handles.sglx_port_label = uicontrol(p4, 'Style', 'text', 'String', 'Port:', 'Units', 'normalized', 'Position', [0.33, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right', 'Visible', 'off'); + handles.sglx_port_edit = uicontrol(p4, 'Style', 'edit', 'String', '4142', 'Units', 'normalized', 'Position', [0.49, 0.25, 0.15, 0.5], 'Visible', 'off'); + handles.sglx_probe_label = uicontrol(p4, 'Style', 'text', 'String', 'Probe Idx:', 'Units', 'normalized', 'Position', [0.65, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right', 'Visible', 'off'); + handles.sglx_probe_edit = uicontrol(p4, 'Style', 'edit', 'String', '0', 'Units', 'normalized', 'Position', [0.81, 0.25, 0.15, 0.5], 'Visible', 'off'); + % --- Control Buttons Panel --- + p5 = uipanel('Title', 'Controls', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.02, 0.6, 0.11]); + handles.control_button = uicontrol(p5, 'Style', 'pushbutton', 'String', 'Load', 'Units', 'normalized', 'FontSize', 14, 'FontWeight', 'bold', 'Position', [0.02, 0.1, 0.3, 0.8], 'BackgroundColor', [0.2, 0.6, 0.8], 'Callback', {@(h,e) feval(mfilename, obj, 'main_control_callback')}); + handles.behav_button = uicontrol(p5, 'Style', 'pushbutton', 'String', 'Run Behav', 'Units', 'normalized', 'FontSize', 12, 'Position', [0.35, 0.1, 0.3, 0.8], 'BackgroundColor', [1, 0.8, 0.6], 'Callback', {@(h,e) feval(mfilename, obj, 'behav_control_callback')}); + handles.ephys_button = uicontrol(p5, 'Style', 'pushbutton', 'String', 'Run Ephys', 'Units', 'normalized', 'FontSize', 12, 'Position', [0.68, 0.1, 0.3, 0.8], 'BackgroundColor', [0.8, 0.6, 1], 'Callback', {@(h,e) feval(mfilename, obj, 'ephys_control_callback')}); + + SoloParamHandle(obj, 'ui_handles', 'value', handles); + + feval(mfilename, obj, 'populate_and_filter_lists', 'rescan'); + log_message(handles, 'GUI initialization complete.'); + % ========================================================================= + % CASE MAIN_CONTROL_CALLBACK + % ========================================================================= + case 'main_control_callback' + switch value(currentState) + case 'Load', feval(mfilename, obj, 'load_sequence'); + case 'Run', feval(mfilename, obj, 'run_sequence'); + case 'Stop', feval(mfilename, obj, 'stop_sequence'); + case 'PostExperiment', feval(mfilename, obj, 'reset_to_load_state'); + end + % ========================================================================= + % CASE RECORDING_SOFTWARE_CALLBACK + % ========================================================================= + case 'recording_software_callback' + handles = value(ui_handles); + selected_software = get(get(handles.software_group, 'SelectedObject'), 'Tag'); + recording_software.value = selected_software; + + if strcmp(selected_software, 'OpenEphys') + % Show OpenEphys settings, hide SpikeGLX + set([handles.oe_ip_label, handles.oe_ip_edit, handles.oe_proc_label, handles.oe_proc_edit, handles.oe_rec_label, handles.oe_rec_edit], 'Visible', 'on'); + set([handles.sglx_host_label, handles.sglx_host_edit, handles.sglx_port_label, handles.sglx_port_edit, handles.sglx_probe_label, handles.sglx_probe_edit], 'Visible', 'off'); + log_message(handles, 'Switched to Open Ephys recording mode.'); + else % SpikeGLX + % Hide OpenEphys settings, show SpikeGLX + set([handles.oe_ip_label, handles.oe_ip_edit, handles.oe_proc_label, handles.oe_proc_edit, handles.oe_rec_label, handles.oe_rec_edit], 'Visible', 'off'); + set([handles.sglx_host_label, handles.sglx_host_edit, handles.sglx_port_label, handles.sglx_port_edit, handles.sglx_probe_label, handles.sglx_probe_edit], 'Visible', 'on'); + log_message(handles, 'Switched to SpikeGLX recording mode.'); + end + + % ========================================================================= + % WORKFLOW ACTIONS + % ========================================================================= + case 'load_sequence' + handles = value(ui_handles); + log_message(handles, '--- LOAD sequence initiated ---'); + set(handles.control_button, 'Enable', 'off', 'String', 'Loading...'); + set(handles.sample_button, 'Enable', 'off'); + + try + software = value(recording_software); + params = get_all_parameters(handles,software); + + % --- ADDED: Confirmation for overwriting existing session data --- + if ~strcmp(params.session_id, 'New') + % Construct path to check for existing data + subject_name_part = sprintf('sub-%s_id-%s_expmtr-%s', params.subject_id, params.rat_name, params.experimenter); + session_name_part = sprintf('ses-%s', params.session_id); % Assumes session_id is formatted '01', '02' etc. + base_path_part = fullfile(params.project_name, 'rawdata', subject_name_part, session_name_part); + + local_behav_path = fullfile(params.local_path, base_path_part, 'behav'); + local_ephys_path = fullfile(params.local_path, base_path_part, 'ephys'); + central_behav_path = fullfile(params.central_path, base_path_part, 'behav'); + central_ephys_path = fullfile(params.central_path, base_path_part, 'ephys'); + + data_exists = does_dir_have_data(local_behav_path) || does_dir_have_data(local_ephys_path) || ... + does_dir_have_data(central_behav_path) || does_dir_have_data(central_ephys_path); + + if data_exists + question = sprintf('Session %s for this subject already contains data. Continuing may result in data being overwritten. Are you sure you want to proceed?', params.session_id); + answer = questdlg(question, 'Confirm Overwrite', 'Yes', 'No', 'No'); + if strcmp(answer, 'No') + log_message(handles, 'Load sequence aborted by user to prevent overwriting data.'); + feval(mfilename, obj, 'reset_to_load_state'); + return; % Abort the load sequence + end + end + end + % --- END ADDED SECTION --- + + if ~validate_all_inputs(params,handles,software) + feval(mfilename, obj, 'reset_to_load_state'); + return; + end + current_params.value = params; + + [session_path, ~] = construct_session_paths(handles, params); + if isempty(session_path) || ~create_session_directories(handles, params, session_path) + feval(mfilename, obj, 'reset_to_load_state'); + return; + end + session_base_path.value = session_path; + + % Initialize the Behavior system + feval(mfilename, obj, 'initialize_behavior_system'); + + catch ME + log_message(handles, sprintf('ERROR during load sequence: %s', ME.message)); + feval(mfilename, obj, 'reset_to_load_state'); + rethrow(ME); + end + case 'run_sequence' + handles = value(ui_handles); + params = value(current_params); + + % --- ADDED: Pre-run checklist dialog --- + software = value(recording_software); + if strcmp(software, 'OpenEphys') + title = 'Open Ephys Pre-Run Checklist'; + message = { + 'Before continuing, please ensure the following in Open Ephys:', ... + '', ... + '1. Refresh the Neuropixel PXI module to ensure probes are detected.', ... + '2. Set the probe geometry (Bank, Reference, or select the IMRO file).', ... + '3. Configure the main sync slot and SMA settings.', ... + '', ... + 'NOTE: The file save directory and name will be set automatically by this program.' ... + }; + else % SpikeGLX + title = 'SpikeGLX Pre-Run Checklist'; + message = { + 'Before continuing, please ensure the following in SpikeGLX:', ... + '', ... + '1. Press ''Detect'' to find the connected probes.', ... + '2. In Sync settings, set ''Square Wave Source'' to ''Disable sync waveform''.', ... + '3. Set the inputs for the ''imec PXI SMA slot''.', ... + '4. Select the correct IMRO file for the animal within SpikeGLX.',... + '', ... + 'NOTE: The run name and directory will be set automatically by this program.'... + }; + end + + answer = questdlg(message, title, 'Continue', 'Cancel', 'Continue'); + + if ~strcmp(answer, 'Continue') + log_message(handles, 'Run sequence cancelled by user at pre-run checklist.'); + return; % Abort the run sequence + end + % --- END ADDED SECTION --- + + log_message(handles, '--- RUN sequence initiated ---'); + set(handles.sample_button, 'Enable', 'off'); + + try + if get(handles.cb_ephys, 'Value') + % Initialize the selected recording system + recording_save_path = fullfile(params.local_path, value(session_base_path), 'ephys'); + feval(mfilename, obj, 'initialize_recording_system', params, recording_save_path); + if isempty(value(recording_controller)) + feval(mfilename, obj, 'reset_to_load_state'); + return; + end + % Start Ephys Recording + feval(mfilename, obj, 'start_electrophysiology_recording', params); + else + log_message(handles, 'Ephys checkbox not selected. No recording started.'); + end + + currentState.value = 'Stop'; + set(handles.control_button, 'String', 'Stop'); + feval(mfilename, obj, 'start_blinking'); + + feval(mfilename, obj, 'start_behavioral_protocol', params); + log_message(handles, '--- RUN sequence complete. Experiment is live. ---'); + + catch ME + log_message(handles, sprintf('ERROR during run sequence: %s', ME.message)); + feval(mfilename, obj, 'stop_blinking'); + rethrow(ME); + end + case 'stop_sequence' + handles = value(ui_handles); + params = value(current_params); + log_message(handles, '--- STOP sequence initiated ---'); + feval(mfilename, obj, 'stop_blinking'); + + try + behav_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + feval(mfilename, obj, 'stop_behavioral_protocol', params, behav_save_dir); + + if get(handles.cb_ephys, 'Value') && ~isempty(value(recording_controller)) + feval(mfilename, obj, 'stop_electrophysiology_recording'); + end + + log_message(handles, '--- Experiment finished. Post-session sampling available. ---'); + currentState.value = 'PostExperiment'; + set(handles.control_button, 'String', 'Start New Experiment', 'BackgroundColor', [0.2, 0.8, 0.6]); + set(handles.sample_button, 'Enable', 'on'); + + catch ME + log_message(handles, sprintf('ERROR during stop sequence: %s', ME.message)); + rethrow(ME); + end + % ========================================================================= + % SYSTEM INITIALIZATION & CONTROL + % ========================================================================= + case 'initialize_recording_system' + params = varargin{1}; + save_path = varargin{2}; + software = value(recording_software); + handles = value(ui_handles); + + try + if strcmp(software, 'OpenEphys') + log_message(handles, 'Initializing Open Ephys controller...'); + controller = OpenEphysHTTPServer(params.oe_gui_ip, 37497); + if isempty(controller), error('Failed to create Open Ephys controller'); end + else % SpikeGLX + log_message(handles, 'Initializing SpikeGLX controller...'); + controller = SpikeGL(params.sglx_host_ip, params.sglx_port); + % if ~controller.IsConnected(), error('Failed to connect to SpikeGLX'); end + end + recording_controller.value = controller; + log_message(handles, sprintf('%s controller initialized successfully.', software)); + + % Set initial recording path + feval(mfilename, obj, 'set_recording_path', save_path); + + catch ME + log_message(handles, sprintf('Failed to initialize %s: %s', software, ME.message)); + recording_controller.value = []; + rethrow(ME); + end + + case 'set_recording_path' + save_path = varargin{1}; + software = value(recording_software); + controller = value(recording_controller); + params = value(current_params); + handles = value(ui_handles); + if isempty(controller), return; end + + try + if strcmp(software, 'OpenEphys') + controller.setRecordPath(params.oe_rec_node_id, save_path); + else % SpikeGLX + controller = SetDataDir( controller, 0, save_path); + recording_controller.value = controller; + end + log_message(handles, sprintf('Recording path set to: %s', save_path)); + catch ME + log_message(handles, sprintf('Failed to set recording path: %s', ME.message)); + rethrow(ME); + end + case 'start_electrophysiology_recording' + params = varargin{1}; + software = value(recording_software); + controller = value(recording_controller); + handles = value(ui_handles); + if isempty(controller), error('Recording controller not initialized'); end + + try + probe_settings_struct = value(probe_settings); + feval(mfilename, obj, 'apply_probe_configuration', probe_settings_struct); + + main_ephys_path = fullfile(params.local_path, value(session_base_path), 'ephys'); + feval(mfilename, obj, 'set_recording_path', main_ephys_path); + + if strcmp(software, 'OpenEphys') + log_message(handles, 'Starting Open Ephys acquisition and recording...'); + controller.acquire(); pause(1); + controller.record(); + else % SpikeGLX + log_message(handles, 'Starting SpikeGLX recording...'); + run_name = sprintf('experiment_%s', datestr(now, 'yyyymmdd_HHMMSS')); + boolval = IsInitialized( controller); + if boolval + spikeglx_params = GetParams( controller ); + controller = SetRunName(controller,run_name); % setting run name + controller = StartRun(controller); % starting acquisition + pause(2); + + runningval = false; + while ~runningval % waiting for acquisition to start + runningval = IsRunning( controller ); + if runningval + controller = SetRecordingEnable( controller, 1 ); % Start Recording + end + end + recording_controller.value = controller; + end + end + + log_message(handles, 'Electrophysiology recording is LIVE.'); + ephysState.value = 'Stop'; + set(handles.ephys_button, 'String', 'Stop Ephys', 'BackgroundColor', [1 0.6 0.6]); + + catch ME + log_message(handles, sprintf('Failed to start recording: %s', ME.message)); + rethrow(ME); + end + case 'stop_electrophysiology_recording' + software = value(recording_software); + controller = value(recording_controller); + handles = value(ui_handles); + if isempty(controller), return; end + + try + if strcmp(software, 'OpenEphys') + log_message(handles, 'Stopping Open Ephys recording...'); + controller.idle(); + else % SpikeGLX + log_message(handles, 'Stopping SpikeGLX recording...'); + boolval = IsSaving( controller ); + if boolval + controller = SetRecordingEnable( controller, 0 ); + pause(1); % Brief pause to ensure recording stops + controller = StopRun(controller); + else + controller = StopRun(controller); + end + recording_controller.value = controller; + end + + log_message(handles, 'Electrophysiology recording stopped.'); + ephysState.value = 'Run'; + set(handles.ephys_button, 'String', 'Run Ephys', 'BackgroundColor', [0.8, 0.6, 1]); + + catch ME + log_message(handles, sprintf('Failed to stop recording: %s', ME.message)); + rethrow(ME); + end + case 'initialize_behavior_system' + params = value(current_params); + handles = value(ui_handles); + try + log_message(handles, 'Initializing behavior control system...'); + behav_obj.value = dispatcher('init'); + h=get_sphandle('owner','dispatcher','name','myfig'); + set(value(h{1}), 'Visible','Off'); + if params.do_manual_test + feval(mfilename, obj, 'behav_control', 'manual_test'); + else + feval(mfilename, obj, 'continue_load_after_manual_test'); + end + catch ME + log_message(handles, ['FATAL ERROR initializing behavior system: ' ME.message]); + errordlg(['Failed to initialize behavior system. Check path and logs. Error: ' ME.message], 'Behavior System Error'); + rethrow(ME); + end + + case 'start_behavioral_protocol' + params = varargin{1}; + handles = value(ui_handles); + try + log_message(handles, 'Starting behavioral protocol...'); + feval(mfilename, obj, 'behav_control', 'run', params.protocol_name); + log_message(handles, 'Behavioral protocol is LIVE.'); + catch ME + log_message(handles, ['FATAL ERROR starting behavior protocol: ' ME.message]); + errordlg(['Failed to start behavior protocol. Check logs. Error: ' ME.message], 'Behavior System Error'); + rethrow(ME); + end + case 'stop_behavioral_protocol' + params = varargin{1}; + behav_save_dir = varargin{2}; + handles = value(ui_handles); + try + log_message(handles, 'Ending behavioral session (saving data)...'); + feval(mfilename, obj, 'behav_control', 'end', params.protocol_name, params.behav_path, behav_save_dir); + log_message(handles, 'Behavioral data saved successfully.'); + catch ME + log_message(handles, ['FATAL ERROR ending behavioral session: ' ME.message]); + errordlg(['Failed to save behavioral data. Check logs. Error: ' ME.message], 'Behavior System Error'); + rethrow(ME); + end + + case 'manual_test_stopping' + handles = value(ui_handles); + log_message(handles, 'Manual rig test complete. Cleaning up...'); + dispatcher(value(behav_obj), 'Stop'); + %Let's pause until we know dispatcher is done running + set(value(stopping_complete_timer), 'TimerFcn', {@(h,e) feval(mfilename, obj, 'manual_test_stopped')}); + start(value(stopping_complete_timer)); + case 'manual_test_stopped' + if value(stopping_process_completed) %This is provided by RunningSection + stop(value(stopping_complete_timer)); %Stop looping. + dispatcher('set_protocol', ''); + is_running.value = 0; + feval(mfilename, obj, 'continue_load_after_manual_test'); + end + + case 'continue_load_after_manual_test' + params = value(current_params); + handles = value(ui_handles); + video_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + try + log_message(handles, 'Loading main behavioral protocol...'); + feval(mfilename, obj, 'behav_control', 'load_main_protocol', params.experimenter, params.rat_name, params.protocol_name, video_save_dir, params.behav_path,params.stim_distribution); + log_message(handles, 'Behavior system loaded and ready.'); + log_message(handles, '--- LOAD sequence complete. Ready to run. ---'); + currentState.value = 'Run'; + set(handles.control_button, 'Enable', 'on', 'String', 'Run', 'BackgroundColor', [0.4, 0.8, 0.4]); + set(handles.sample_button, 'Enable', 'on'); + + % --- ADDED: Confirmation dialog after successful load --- + msgbox('Behavior settings loaded successfully. The system is now ready to run.', 'Load Complete', 'help'); + % --- END ADDED SECTION --- + + catch ME + log_message(handles, ['FATAL ERROR loading main protocol: ' ME.message]); + errordlg(['Failed to load main protocol. Error: ' ME.message], 'Behavior System Error'); + feval(mfilename, obj, 'reset_to_load_state'); + end + + % ========================================================================= + % INDIVIDUAL CONTROL CALLBACKS + % ========================================================================= + case 'behav_control_callback' + switch value(behavState) + case 'Run', feval(mfilename, obj, 'behav_control', 'load_run'); + case 'Stop', feval(mfilename, obj, 'behav_control', 'end'); + end + + case 'ephys_control_callback' + switch value(ephysState) + case 'Run', feval(mfilename, obj, 'run_ephys_individually'); + case 'Stop', feval(mfilename, obj, 'stop_ephys_individually'); + end + + case 'run_ephys_individually' + params = value(current_params); + handles = value(ui_handles); + log_message(handles, '--- Starting Ephys Recording Individually ---'); + set(handles.ephys_button, 'Enable', 'off'); + try + if isempty(value(recording_controller)) + feval(mfilename, obj, 'initialize_recording_system', params, ''); + end + feval(mfilename, obj, 'start_electrophysiology_recording', params); + catch ME + log_message(handles, sprintf('ERROR starting ephys: %s', ME.message)); + end + set(handles.ephys_button, 'Enable', 'on'); + + case 'stop_ephys_individually' + handles = value(ui_handles); + log_message(handles, '--- Stopping Ephys Recording Individually ---'); + set(handles.ephys_button, 'Enable', 'off'); + try + feval(mfilename, obj, 'stop_electrophysiology_recording'); + catch ME + log_message(handles, sprintf('ERROR stopping ephys: %s', ME.message)); + end + set(handles.ephys_button, 'Enable', 'on'); + % ========================================================================= + % BEHAVIOR CONTROL ACTIONS + % ========================================================================= + case 'behav_control' + sub_action = varargin{1}; + args = varargin(2:end); + handles = value(ui_handles); + + switch sub_action + case 'load_main_protocol' + experimenter = args{1}; ratname = args{2}; protocol_name = args{3}; + video_save_dir = args{4}; behav_path = args{5}; stim_distribution = args{6}; + log_message(handles, ['Loading protocol: ' protocol_name]); + dispatcher('set_protocol', protocol_name); + rath = get_sphandle('name', 'ratname', 'owner', protocol_name); + exph = get_sphandle('name', 'experimenter', 'owner', protocol_name); + rath{1}.value = ratname; exph{1}.value = experimenter; + protobj = eval(protocol_name); + log_message(handles, ['Loading settings for ' ratname]); + [~, sfile] = load_solouiparamvalues(ratname, 'experimenter', experimenter, 'owner', class(protobj), 'interactive', 0); + feval(protocol_name, protobj, 'set_setting_params', ratname, experimenter, sfile, char(datetime('now')), video_save_dir); + feval(protocol_name, protobj, 'set_stim_distribution',stim_distribution); + if ~dispatcher('is_running'), pop_history(class(protobj), 'include_non_gui', 1); feval(protocol_name, protobj, 'prepare_next_trial'); end + + case 'crashed' + log_message(handles, '--- BEHAVIOR CRASH RECOVERY INITIATED ---'); + params = value(current_params); + video_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + feval(mfilename, obj, 'behav_control', 'load_protocol_after_crash', params.experimenter, params.rat_name, params.protocol_name, video_save_dir, params.behav_path); + feval(mfilename, obj, 'behav_control', 'run', params.protocol_name); + log_message(handles, '--- RECOVERY COMPLETE: Behavior protocol restarted ---'); + + case 'load_protocol_after_crash' + experimenter = args{1}; ratname = args{2}; protocol_name = args{3}; + video_save_dir = args{4}; behav_path = args{5}; + log_message(handles, ['Loading protocol after crash: ' protocol_name]); + dispatcher('set_protocol', protocol_name); + rath = get_sphandle('name', 'ratname', 'owner', protocol_name); + exph = get_sphandle('name', 'experimenter', 'owner', protocol_name); + rath{1}.value = ratname; exph{1}.value = experimenter; + protobj = eval(protocol_name); + try + log_message(handles, ['Loading previous data for ' ratname]); + today_date = char(datetime('now','format','yyMMdd')); + temp_data_dir = fullfile(behav_path,'SoloData','Data',experimenter,ratname); + temp_data_file = sprintf('data_@%s_%s_%s_%s_ASV.mat',protocol_name,experimenter,ratname,today_date); + if isfile(fullfile(temp_data_dir,temp_data_file)) + dispatcher('runstart_disable'); + load_soloparamvalues(ratname, 'experimenter', experimenter, 'owner', protocol_name, 'interactive', 0,'data_file',fullfile(temp_data_dir,temp_data_file)); + dispatcher('runstart_enable'); + end + feval(protocol_name, protobj, 'psychometricUpdate_aftercrash'); % update parameters for psychometric plots which were not saved so cant be loaded + if ~dispatcher('is_running'), pop_history(class(protobj), 'include_non_gui', 1); feval(protocol_name, protobj, 'prepare_next_trial'); end + catch + log_message(handles, ['Loading settings for ' ratname]); + [~, sfile] = load_solouiparamvalues(ratname, 'experimenter', experimenter, 'owner', class(protobj), 'interactive', 0); + feval(protocol_name, protobj, 'set_setting_params', ratname, experimenter, sfile, char(datetime('now')), video_save_dir); + if ~dispatcher('is_running'), pop_history(class(protobj), 'include_non_gui', 1); feval(protocol_name, protobj, 'prepare_next_trial'); end + end + case 'load_run' + set(handles.behav_button, 'Enable', 'off'); + log_message(handles, '--- STARTING BEHAV PROTOCOL ---'); + params = value(current_params); + video_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + feval(mfilename, obj, 'behav_control', 'load_protocol_after_crash', params.experimenter, params.rat_name, params.protocol_name, video_save_dir, params.behav_path); + feval(mfilename, obj, 'behav_control', 'run', params.protocol_name); + log_message(handles, '--- START COMPLETE: Behavior protocol started ---'); + set(handles.behav_button, 'Enable', 'on'); + case 'run' + protocol_name = args{1}; protobj = eval(protocol_name); + log_message(handles, 'Starting video recording via protocol...'); + feval(protocol_name, protobj, 'start_recording'); + log_message(handles, 'Starting dispatcher to run trials...'); + is_running.value = 1; + behavState.value = 'Stop'; + set(handles.behav_button, 'String', 'Stop Behav', 'BackgroundColor', [1 0.6 0.6]); + dispatcher(value(behav_obj), 'Run'); + + case 'end' + set(handles.behav_button, 'Enable', 'off'); + if length(args) >= 3 + protocol_name = args{1}; root_dir = args{2}; behav_copy_dir = args{3}; + else + params = value(current_params); + protocol_name = params.protocol_name; + root_dir = params.behav_path; + behav_copy_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + end + log_message(handles, 'Stopping dispatcher...'); + dispatcher(value(behav_obj), 'Stop'); + set(value(stopping_complete_timer), 'Period', 0.8,'TimerFcn', {@(h,e) feval(mfilename, obj, 'behav_control','end_continued',protocol_name, root_dir, behav_copy_dir)}); + start(value(stopping_complete_timer)); + case 'end_continued' + if value(stopping_process_completed) % This is provided by RunningSection + protocol_name = args{1}; root_dir = args{2}; destination_path = args{3}; + stop(value(stopping_complete_timer)); %Stop looping. + is_running.value = 0; + feval(mfilename, obj, 'behav_control', 'send_empty_state_machine'); + protobj = eval(protocol_name); + log_message(handles, 'Ending session via protocol...'); + feval(protocol_name, protobj, 'end_session'); + log_message(handles, 'Saving data and settings...'); + data_file = SavingSection(protobj, 'savedata', 'interactive', 0); + try + feval(protocol_name, protobj, 'pre_saving_settings'); + catch + log_message(handles, 'Protocol does not have a pre_saving_settings section.'); + end + [settings_file, ~] = SavingSection(protobj, 'get_set_filename'); + SavingSection(protobj, 'savesets', 'interactive', 0); + log_message(handles, 'Committing data and settings to SVN...'); + commit_to_svn(handles, data_file, settings_file, root_dir); + dispatcher('set_protocol', ''); + data_file = [data_file '.mat']; + [status, msg] = copyfile(data_file, destination_path); + if status, log_message(handles,'Data File copied successfully.'); + else, log_message(handles,['Error copying Data file: ' msg]); + end + behavState.value = 'Run'; + set(handles.behav_button, 'String', 'Run Behav', 'BackgroundColor', [1, 0.8, 0.6]); + feval(mfilename, obj, 'save_log_file'); + set(handles.behav_button, 'Enable', 'on'); + end + case 'manual_test' + log_message(handles, 'Loading manual rig test protocol...'); + dispatcher('set_protocol', 'Rigtest_singletrial'); + h=get_sphandle('owner','Rigtest_singletrial','name', 'myfig'); + for i=1:numel(h); set(value(h{i}),'Visible','Off'); end + is_running.value = 1; + log_message(handles, 'Starting manual rig test. Please complete the one-trial test.'); + dispatcher(value(behav_obj), 'Run'); + case 'create_svn_data_dir' + experimenter = args{1}; ratname = args{2}; behav_dir = args{3}; dir_name = args{4}; + dirCurrent = cd; + settings_path = fullfile(behav_dir, 'SoloData', dir_name); + exp_path = fullfile(settings_path, experimenter); + rat_path = fullfile(exp_path, ratname); + if ~isfolder(settings_path), mkdir(settings_path); system(['svn add ' dir_name]); end + if ~isfolder(exp_path), cd(settings_path); mkdir(experimenter); system(['svn add ' experimenter]); end + if ~isfolder(rat_path), cd(exp_path); mkdir(ratname); system(['svn add ' ratname]); end + cd(dirCurrent); + log_message(handles, ['Created SVN directory structure for ' ratname]); + case 'send_empty_state_machine' + state_machine_server = bSettings('get', 'RIGS', 'state_machine_server'); + server_slot = bSettings('get', 'RIGS', 'server_slot'); if isnan(server_slot), server_slot = 0; end + card_slot = bSettings('get', 'RIGS', 'card_slot'); if isnan(card_slot), card_slot = 0; end + sm = BpodSM(state_machine_server, 3333, server_slot); sm = Initialize(sm); + [inL, outL] = MachinesSection(dispatcher, 'determine_io_maps'); + sma = StateMachineAssembler('full_trial_structure'); + sma = add_state(sma, 'name', 'vapid_state_in_vapid_matrix'); + send(sma, sm, 'run_trial_asap', 0, 'input_lines', inL, 'dout_lines', outL, 'sound_card_slot', int2str(card_slot)); + + end + + case 'crash_detected' + % handles = feval(mfilename, obj, 'get_ui_handles'); + handles = value(ui_handles); + if ~strcmp(value(currentState), 'Stop') || isempty(value(behav_obj)), return; end + log_message(handles, '!!! CRASH DETECTED: Behavior system is not running. Attempting recovery...'); + try + feval(mfilename, obj, 'behav_control', 'crashed'); + catch ME + log_message(handles, sprintf('FATAL: Recovery attempt failed: %s', ME.message)); + getReport(ME, 'extended', 'hyperlinks', 'on'); + errordlg('Automatic recovery failed. Please stop the experiment manually.', 'Recovery Failed'); + end + % ========================================================================= + % SAMPLE EPHYS RECORDINGS + % ========================================================================= + case 'sample_recording_wrapper' + if strcmp(value(currentState), 'PostExperiment') + feval(mfilename, obj, 'sample_recording', 'post_session'); + else + feval(mfilename, obj, 'sample_recording', 'pre_session'); + end + + case 'sample_recording' + prefix = varargin{1}; + handles = value(ui_handles); + log_message(handles, ['--- ' upper(prefix) ' SAMPLE RECORDING INITIATED ---']); + set([handles.sample_button, handles.control_button], 'Enable', 'off', 'String', 'Sampling...'); + drawnow; + + try + software = value(recording_software); + params = get_all_parameters(handles,software); + if isempty(value(recording_controller)) + feval(mfilename, obj, 'initialize_recording_system', params, ''); + end + + if isempty(value(session_base_path)) + [session_path, ~] = construct_session_paths(handles, params); + if isempty(session_path) || ~create_session_directories(handles, params, session_path) + error('Failed to create session directories'); + end + session_base_path.value = session_path; + end + + sample_dir_name = sprintf('%s_sample_recording', prefix); + sample_save_path = fullfile(params.local_path, value(session_base_path), 'ephys', sample_dir_name); + if ~exist(sample_save_path, 'dir'), mkdir(sample_save_path); end + + software = value(recording_software); + if strcmp(software, 'OpenEphys') + feval(mfilename, obj, 'execute_openephys_sampling', sample_save_path); + else + feval(mfilename, obj, 'execute_spikeglx_sampling', sample_save_path); + end + log_message(handles, '--- SAMPLE RECORDING COMPLETE ---'); + + catch ME + log_message(handles, sprintf('ERROR during sample recording: %s', ME.message)); + rethrow(ME); + end + + % Reset button states + if strcmp(value(currentState), 'PostExperiment') + set(handles.control_button, 'Enable', 'on', 'String', 'Start New Experiment'); + feval(mfilename, obj, 'save_log_file'); + else + set(handles.control_button, 'Enable', 'on', 'String', 'Load'); + end + set(handles.sample_button, 'Enable', 'on', 'String', 'Start Sample Recording'); + + case 'execute_openephys_sampling' + save_path = varargin{1}; + controller = value(recording_controller); + params = value(current_params); + handles = value(ui_handles); + probe_settings_struct = value(probe_settings); + duration = str2double(get(handles.sample_duration, 'String')); + + controller.setParameters(params.oe_proc_node_id, 0, 'Reference', probe_settings_struct.reference); + if strcmp(probe_settings_struct.version, '1.0'), num_banks = 3; else, num_banks = 4; end + + for bank = 0:(num_banks - 1) + log_message(handles, sprintf('Recording OE Bank %d for %d seconds...', bank, duration)); + controller.setParameters(params.oe_proc_node_id, 0, 'bank', bank); pause(1); + controller.setRecordPath(params.oe_rec_node_id, save_path); pause(1); + controller.acquire(duration); pause(1); + controller.record(duration); + controller.idle(); + log_message(handles, sprintf('Finished recording Bank %d.', bank)); pause(1); + end + + if ~isempty(probe_settings_struct.imro_path) + controller.config(params.oe_proc_node_id, ['LOADIMRO ' probe_settings_struct.imro_path]); + else + controller.setParameters(params.oe_proc_node_id, 0, 'bank', probe_settings_struct.bank); + end + + case 'execute_spikeglx_sampling' + save_path = varargin{1}; + controller = value(recording_controller); + handles = value(ui_handles); + probe_settings_struct = value(probe_settings); + duration = str2double(get(handles.sample_duration, 'String')); + + controller.SetDataDir(save_path); + if strcmp(probe_settings_struct.version, '1.0'), num_banks = 3; else, num_banks = 4; end + + for bank = 0:(num_banks - 1) + log_message(handles, sprintf('Recording SGLX Bank %d for %d seconds...', bank, duration)); + % Bank selection for SpikeGLX depends on specific API calls for channel selection, not a simple 'bank' parameter + % This is a placeholder for more complex channel/bank setting logic. + % For now, we record with the currently active map. + + run_name = sprintf('sample_bank_%d_%s', bank, datestr(now, 'yyyymmdd_HHMMSS')); + controller.SetRunName(run_name); + controller.StartRun(); + pause(duration); + controller.StopRun(); + log_message(handles, sprintf('Finished recording Bank %d.', bank)); pause(1); + end + + % ========================================================================= + % PROBE GUI AND SETTINGS + % ========================================================================= + case 'open_probe_gui' + handles = value(ui_handles); + log_message(handles, 'Opening probe settings GUI...'); + probe_fig = figure('Name', 'Neuropixel Probe Settings', 'Position', [300 300 450 350], ... + 'MenuBar', 'none', 'ToolBar', 'none', 'NumberTitle', 'off', 'Resize', 'off'); + p_handles = struct(); + + p_handles.version_group = uibuttongroup(probe_fig, 'Title', 'Probe Version', 'Position', [0.05 0.78 0.9 0.2]); + uicontrol(p_handles.version_group, 'Style', 'radiobutton', 'String', 'NP 1.0 (3 Banks)', 'Position', [10 5 150 25], 'Tag', '1.0'); + uicontrol(p_handles.version_group, 'Style', 'radiobutton', 'String', 'NP 2.0 (4 Banks)', 'Position', [200 5 150 25], 'Tag', '2.0'); + + p_handles.ref_group = uibuttongroup(probe_fig, 'Title', 'Reference', 'Position', [0.05 0.55 0.4 0.2]); + uicontrol(p_handles.ref_group, 'Style', 'radiobutton', 'String', 'Tip', 'Position', [10 5 80 25], 'Tag', 'Tip'); + uicontrol(p_handles.ref_group, 'Style', 'radiobutton', 'String', 'External', 'Position', [100 5 80 25], 'Tag', 'External'); + + p_handles.bank_panel = uipanel(probe_fig, 'Title', 'Target Bank', 'Position', [0.5 0.55 0.45 0.2]); + uicontrol(p_handles.bank_panel, 'Style', 'text', 'String', 'Bank:', 'Position', [10 5 40 20]); + p_handles.bank_edit = uicontrol(p_handles.bank_panel, 'Style', 'edit', 'String', '0', 'Position', [60 5 50 25]); + + uicontrol(probe_fig, 'Style', 'text', 'String', 'Sync IMEC Slot:', 'Position', [20 160 100 20], 'HorizontalAlignment', 'right'); + p_handles.sync_slot_edit = uicontrol(probe_fig, 'Style', 'edit', 'String', '2', 'Position', [130 160 50 25]); + + uicontrol(probe_fig, 'Style', 'text', 'String', 'IMRO File:', 'Position', [20 120 100 20], 'HorizontalAlignment', 'right'); + p_handles.imro_text = uicontrol(probe_fig, 'Style', 'text', 'String', 'None selected', 'Position', [130 120 280 20], 'HorizontalAlignment', 'left'); + + uicontrol(probe_fig, 'Style', 'pushbutton', 'String', 'Browse...', 'Position', [130 85 100 30], 'Callback', {@(h,e) feval(mfilename, obj, 'browse_imro', p_handles)}); + uicontrol(probe_fig, 'Style', 'pushbutton', 'String', 'Clear IMRO', 'Position', [240 85 100 30], 'Callback', {@(h,e) feval(mfilename, obj, 'clear_imro', p_handles)}); + + uicontrol(probe_fig, 'Style', 'pushbutton', 'String', 'Apply & Close', 'Position', [250 25 180 30], 'FontWeight', 'bold', 'Callback', {@(h,e) feval(mfilename, obj, 'apply_probe_settings', p_handles)}); + probe_gui_handles.value = p_handles; + + case 'browse_imro' + p_handles = varargin{1}; + [file, path] = uigetfile('*.imro', 'Select IMRO File'); + if isequal(file, 0) || isequal(path, 0), return; + else + full_path = fullfile(path, file); + set(p_handles.imro_text, 'String', full_path); + set(findobj(p_handles.bank_panel, '-property', 'Enable'), 'Enable', 'off'); + end + + case 'clear_imro' + p_handles = varargin{1}; + set(p_handles.imro_text, 'String', 'None selected'); + set(findobj(p_handles.bank_panel, '-property', 'Enable'), 'Enable', 'on'); + + case 'apply_probe_settings' + p_handles = varargin{1}; + handles = value(ui_handles); + settings.version = get(get(p_handles.version_group, 'SelectedObject'), 'Tag'); + settings.reference = get(get(p_handles.ref_group, 'SelectedObject'), 'Tag'); + settings.bank = str2double(get(p_handles.bank_edit, 'String')); + settings.imro_path = get(p_handles.imro_text, 'String'); + settings.sync_slot = str2double(get(p_handles.sync_slot_edit, 'String')); + if strcmp(settings.imro_path, 'None selected'), settings.imro_path = ''; end + probe_settings.value = settings; + if ~isempty(settings.imro_path) + set(handles.target_display, 'String', 'Target: IMRO File'); + else + set(handles.target_display, 'String', ['Target: Bank ' num2str(settings.bank)]); + end + log_message(handles, 'Probe settings saved.'); + close(p_handles.ref_group.Parent); + probe_gui_handles.value = []; + + case 'apply_probe_configuration' + probe_settings = varargin{1}; + software = value(recording_software); + controller = value(recording_controller); + params = value(current_params); + handles = value(ui_handles); + if isempty(controller), return; end + + try + if strcmp(software, 'OpenEphys') + log_message(handles, sprintf('Setting OE reference to: %s', probe_settings.reference)); + controller.setParameters(params.oe_proc_node_id, 0, 'Reference', probe_settings.reference); + % Applying Sync Slot setting + log_message(handles, sprintf('Applying Sync IMEC Slot: %d', probe_settings.sync_slot)); + % NOTE: This assumes a parameter like 'syncSlot' exists in the Neuropix-PXI plugin. + % The actual parameter name might need to be verified in the Open Ephys plugin. + % controller.setParameters(params.oe_proc_node_id, 0, 'syncSlot', probe_settings.sync_slot); + if ~isempty(probe_settings.imro_path) + log_message(handles, sprintf('Loading IMRO file: %s', probe_settings.imro_path)); + controller.config(params.oe_proc_node_id, ['LOADIMRO ' probe_settings.imro_path]); + else + log_message(handles, sprintf('Setting bank to: %d', probe_settings.bank)); + controller.setParameters(params.oe_proc_node_id, 0, 'bank', probe_settings.bank); + end + else % SpikeGLX + % SpikeGLX probe configuration (e.g., reference, channel map) is more complex + % and typically handled by setting parameters or loading a meta file. + % This is a placeholder for those more complex API calls. + log_message(handles, 'Applying SpikeGLX probe settings (via meta file or API)...'); + if ~isempty(probe_settings.imro_path) + log_message(handles, 'Note: For SpikeGLX, ensure IMRO settings are loaded within the SpikeGLX GUI and save as part of the meta file.'); + end + end + log_message(handles, 'Probe configuration applied successfully.'); + catch ME + log_message(handles, sprintf('Failed to apply probe settings: %s', ME.message)); + rethrow(ME); + end + + % ========================================================================= + % UTILITY & OTHER ACTIONS + % ========================================================================= + case 'browse_path' + type = varargin{1}; + handles = value(ui_handles); + log_message(handles, ['Opening browse dialog for ' type ' path...']); + folder_path = uigetdir; + if folder_path ~= 0 + if strcmp(type, 'local') + set(handles.local_edit, 'String', folder_path); + feval(mfilename, obj, 'populate_and_filter_lists', 'rescan'); + elseif strcmp(type, 'central') + set(handles.central_edit, 'String', folder_path); + feval(mfilename, obj, 'populate_and_filter_lists', 'rescan'); + elseif strcmp(type, 'behav') + set(handles.behav_edit, 'String', folder_path); + end + log_message(handles, [type ' path set.']); + else, log_message(handles, 'Path selection cancelled.'); end + + case 'populate_and_filter_lists' + trigger_type = varargin{1}; % 'rescan' or 'filter' + handles = value(ui_handles); + + % --- Step 1: Scan directories if needed --- + if strcmp(trigger_type, 'rescan') + log_message(handles, 'Scanning directories for session info...'); + local_path = get(handles.local_edit, 'String'); + central_path = get(handles.central_edit, 'String'); + project_name = get(handles.proj_edit, 'String'); + + all_info = struct('folder_name', {}, 'subject_id', {}, 'rat_name', {}, 'experimenter', {}); + if ~isempty(local_path) && exist(local_path, 'dir') + all_info = [all_info; scan_for_info(fullfile(local_path, project_name, 'rawdata'))]; + end + if ~isempty(central_path) && exist(central_path, 'dir') + all_info = [all_info; scan_for_info(fullfile(central_path, project_name, 'rawdata'))]; + end + + if ~isempty(all_info) + keys = cell(numel(all_info), 1); + for i = 1:numel(all_info) + keys{i} = sprintf('%s|%s|%s', all_info(i).subject_id, all_info(i).rat_name, all_info(i).experimenter); + end + [~, ia] = unique(keys, 'stable'); + session_info_list.value = all_info(ia); + log_message(handles, sprintf('Found %d unique subjects.', numel(value(session_info_list)))); + else + session_info_list.value = []; + log_message(handles, 'No valid subject folders found.'); + end + + % Initial population of just the experimenter list + exp_options = unique([{value(session_info_list).experimenter}]); + update_popup(handles.exp_popup, exp_options, '-'); + update_popup(handles.rat_name_popup, {}, '-'); + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + return; + end + + % --- Step 2: Filter Logic triggered by user interaction --- + full_list = value(session_info_list); + source_handle = gcbo; + + % Get current selections, handling 'Add New...' dialogs + [exp_selection, exp_updated] = get_and_handle_new(handles.exp_popup, 'New Experimenter'); + if exp_updated % New exp added, reset everything below it + update_popup(handles.rat_name_popup, {}, '-'); + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + return; + end + + if source_handle == handles.exp_popup + if ~strcmp(exp_selection, '-') + filtered_by_exp = full_list(strcmpi({full_list.experimenter}, exp_selection)); + rat_options = unique([{filtered_by_exp.rat_name}]); + update_popup(handles.rat_name_popup, rat_options, '-'); + else + update_popup(handles.rat_name_popup, {}, '-'); + end + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + return; + end + + [rat_selection, rat_updated] = get_and_handle_new(handles.rat_name_popup, 'New Rat Name'); + + if source_handle == handles.rat_name_popup + if ~strcmp(rat_selection, '-') + subject_entry = full_list(strcmpi({full_list.experimenter}, exp_selection) & strcmpi({full_list.rat_name}, rat_selection)); + + if ~isempty(subject_entry) % Existing pair + sub_id = subject_entry(1).subject_id; + set(handles.sub_popup, 'String', {sub_id}, 'Value', 1); + elseif rat_updated % New Rat for this Experimenter + max_id = 0; + if ~isempty(full_list) + all_ids = str2double({full_list.subject_id}); + if ~all(isnan(all_ids)), max_id = max(all_ids(~isnan(all_ids))); end + end + sub_id = sprintf('%03d', max_id + 1); + set(handles.sub_popup, 'String', {sub_id}, 'Value', 1); + else + sub_id = '-'; + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + end + + % Populate Session list + if ~strcmp(sub_id, '-') + local_path = get(handles.local_edit, 'String'); + central_path = get(handles.central_edit, 'String'); + project_name = get(handles.proj_edit, 'String'); + subject_folder = sprintf('sub-%s_id-%s_expmtr-%s', sub_id, rat_selection, exp_selection); + session_numbers = []; + path1 = fullfile(local_path, project_name, 'rawdata', subject_folder); + if exist(path1, 'dir'), session_numbers = [session_numbers, find_session_numbers(path1)]; end + path2 = fullfile(central_path, project_name, 'rawdata', subject_folder); + if exist(path2, 'dir'), session_numbers = [session_numbers, find_session_numbers(path2)]; end + + unique_sessions = unique(session_numbers); + if ~isempty(unique_sessions) + sorted_sessions = sort(unique_sessions, 'descend'); + last_three = sorted_sessions(1:min(3, end)); + session_strings = arrayfun(@(x) sprintf('%02d', x), sort(last_three), 'UniformOutput', false); + set(handles.session_popup, 'String', [{'New'}, session_strings], 'Value', 1, 'Enable', 'on'); + else + set(handles.session_popup, 'String', {'New'}, 'Value', 1, 'Enable', 'on'); + end + else + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + end + else % rat set to '-' + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + end + end + + case 'save_log_file' + handles = value(ui_handles); + params = value(current_params); + session_path = value(session_base_path); + if isempty(session_path) + log_message(handles, 'WARNING: Cannot save log file. Session path not set.'); return; + end + log_path = fullfile(params.local_path, session_path, 'behav'); + if ~exist(log_path, 'dir') + log_message(handles, ['WARNING: Behavior folder not found. Cannot save log. Path: ' log_path]); return; + end + log_file_path = fullfile(log_path, 'session_log.txt'); + try + log_content = get(handles.log_box, 'String'); + fid = fopen(log_file_path, 'w'); + if fid == -1, error('Could not open file for writing.'); end + for i = 1:length(log_content), fprintf(fid, '%s\n', log_content{i}); end + fclose(fid); + log_message(handles, ['Log file saved successfully to: ' log_file_path]); + catch ME + log_message(handles, ['ERROR: Could not save log file. Details: ' ME.message]); + end + case 'reset_to_load_state' + handles = value(ui_handles); + currentState.value = 'Load'; + behavState.value = 'Run'; + ephysState.value = 'Run'; + set(handles.control_button, 'Enable', 'on', 'String', 'Load', 'BackgroundColor', [0.2, 0.6, 0.8]); + set(handles.sample_button, 'Enable', 'on'); + recording_controller.value = []; + behav_obj.value = []; + current_params.value = []; + session_base_path.value = ''; + log_message(handles, 'GUI reset to load state.'); + case 'close' + try + feval(mfilename, obj, 'stop_blinking'); + if ~isempty(value(recording_controller)), delete(value(recording_controller)); end + if ishandle(value(myfig)), delete(value(myfig)); end + delete_sphandle('owner', ['^@' mfilename '$']); + if ~isempty(value(behav_obj)), dispatcher(value(behav_obj),'close'); end + obj = []; + catch + if exist('myfig','var') == 1 + if ishandle(value(myfig)), delete(value(myfig)); end + else + delete(gcbf); + end + delete_sphandle('owner', ['^@' mfilename '$']); + obj = []; + end + case 'is_running' + if exist('is_running','var') == 1 + obj = logical(value(is_running)); + else + obj = 0; + end + + case 'start_blinking' + handles = value(ui_handles); + blinking_timer.value = timer('ExecutionMode', 'fixedRate', 'Period', 0.5, 'TimerFcn', {@toggle_button_color, handles.control_button}); + start(value(blinking_timer)); + case 'stop_blinking' + handles = value(ui_handles); + if ~isempty(value(blinking_timer)) && isvalid(value(blinking_timer)) + stop(value(blinking_timer)); + delete(value(blinking_timer)); + blinking_timer.value = []; + end + set(handles.control_button, 'BackgroundColor', [1, 0.4, 0.4]); + otherwise + error('Unknown action: %s', action); +end +return; +%% ======================================================================= +% PARAMETER AND VALIDATION FUNCTIONS +% ======================================================================= +function params = get_all_parameters(handles,software) + params.protocol_name = get(handles.protocol_edit, 'String'); + params.do_manual_test = get(handles.manual_test, 'Value'); + + % Get values from popup menus + exp_items = get(handles.exp_popup, 'String'); + params.experimenter = exp_items{get(handles.exp_popup, 'Value')}; + + rat_items = get(handles.rat_name_popup, 'String'); + params.rat_name = rat_items{get(handles.rat_name_popup, 'Value')}; + + sub_items = get(handles.sub_popup, 'String'); + params.subject_id = sub_items{get(handles.sub_popup, 'Value')}; + + ses_items = get(handles.session_popup, 'String'); + params.session_id = ses_items{get(handles.session_popup, 'Value')}; + + popup_string = get(handles.distribution_popup,'String'); + params.stim_distribution = popup_string{get(handles.distribution_popup,'Value')}; + params.behav_path = get(handles.behav_edit, 'String'); + params.project_name = get(handles.proj_edit, 'String'); + params.local_path = get(handles.local_edit, 'String'); + params.central_path = get(handles.central_edit, 'String'); + + if strcmp(software, 'OpenEphys') + params.oe_gui_ip = get(handles.oe_ip_edit, 'String'); + params.oe_proc_node_id = get(handles.oe_proc_edit, 'String'); + params.oe_rec_node_id = get(handles.oe_rec_edit, 'String'); + else + params.sglx_host_ip = get(handles.sglx_host_edit, 'String'); + params.sglx_port = str2double(get(handles.sglx_port_edit, 'String')); + params.sglx_probe_index = str2double(get(handles.sglx_probe_edit, 'String')); + end +function is_valid = validate_all_inputs(params,handles,software) + is_valid = false; + required_fields = {'protocol_name', 'rat_name', 'behav_path', 'project_name', 'subject_id', 'local_path', 'session_id'}; + for i = 1:length(required_fields) + field_val = params.(required_fields{i}); + if ~isfield(params, required_fields{i}) || isempty(field_val) || strcmp(field_val, '-') + msg = sprintf('Field "%s" must have a valid selection before loading.', strrep(required_fields{i}, '_', ' ')); + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + end + if ~get(handles.cb_ephys, 'Value') && ~get(handles.cb_behav, 'Value') && ~get(handles.cb_anat, 'Value') && ~get(handles.cb_funcimg, 'Value') + msg = 'At least one subfolder must be selected.'; + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + if strcmp(software, 'OpenEphys') + if isempty(params.oe_gui_ip) || isempty(params.oe_proc_node_id) || isempty(params.oe_rec_node_id) + msg = 'Open Ephys connection parameters cannot be empty.'; + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + else + if isempty(params.sglx_host_ip) || isnan(params.sglx_port) || isnan(params.sglx_probe_index) + msg = 'SpikeGLX connection parameters cannot be empty or non-numeric.'; + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + end + is_valid = true; +%% ======================================================================= +% PATH AND DIRECTORY FUNCTIONS +% ======================================================================= +function [session_base, recording_path] = construct_session_paths(handles, params) + if isempty(params.experimenter) || strcmp(params.experimenter, '-') + subject_name = sprintf('sub-%s_id-%s', params.subject_id, params.rat_name); + else + subject_name = sprintf('sub-%s_id-%s_expmtr-%s', params.subject_id, params.rat_name, params.experimenter); + end + subject_base_path = fullfile(params.project_name, 'rawdata', subject_name); + local_subject_dir = fullfile(params.local_path, subject_base_path); + central_subject_dir = fullfile(params.central_path, subject_base_path); + + if strcmp(params.session_id, 'New') + new_ses_num = max(find_max_session_number(local_subject_dir), find_max_session_number(central_subject_dir)) + 1; + log_message(handles, sprintf('Last session found: %d. Creating new session: %d.', new_ses_num - 1, new_ses_num)); + else + new_ses_num = str2double(params.session_id); + log_message(handles, sprintf('Using existing session number: %d.', new_ses_num)); + end + + session_datetime_str = char(datetime('now', 'Format', 'yyyyMMdd''T''HHmmss')); + session_folder_name = sprintf('ses-%02d_date-%s_dtype-ephys', new_ses_num, session_datetime_str); + session_base = fullfile(subject_base_path, session_folder_name); + recording_path = fullfile(params.local_path, session_base, 'ephys'); + log_message(handles, ['New session path determined: ' session_base]); +function max_ses = find_max_session_number(base_path) + max_ses = 0; if ~exist(base_path, 'dir'), return; end + dir_contents = dir(fullfile(base_path, 'ses-*')); + if isempty(dir_contents), return; end + session_numbers = []; + for i = 1:length(dir_contents) + if dir_contents(i).isdir + token = regexp(dir_contents(i).name, '^ses-(\d+)', 'tokens'); + if ~isempty(token), session_numbers(end+1) = str2double(token{1}{1}); end + end + end + if ~isempty(session_numbers), max_ses = max(session_numbers); end +function success = create_session_directories(handles, params,session_base_path) + success = false; + subfolders = {}; + if get(handles.cb_ephys, 'Value'), subfolders{end+1} = 'ephys'; end + if get(handles.cb_behav, 'Value'), subfolders{end+1} = 'behav'; end + if get(handles.cb_anat, 'Value'), subfolders{end+1} = 'anat'; end + if get(handles.cb_funcimg, 'Value'), subfolders{end+1} = 'funcimg'; end + try + for i = 1:length(subfolders) + local_target_path = fullfile(params.local_path, session_base_path, subfolders{i}); + log_message(handles, ['Creating local directory: ' local_target_path]); + if ~exist(local_target_path, 'dir'), mkdir(local_target_path); end + end + log_message(handles, 'All selected local directories created successfully.'); + success = true; + catch ME + msg = sprintf('Failed to create directories: %s', ME.message); + log_message(handles, ['ERROR: ' msg]); errordlg(msg, 'Directory Error'); + end +%% ======================================================================= +% HELPER & UTILITY FUNCTIONS +% ======================================================================= +function info = scan_for_info(search_path) + info = struct('folder_name', {}, 'subject_id', {}, 'rat_name', {}, 'experimenter', {}); + if ~exist(search_path, 'dir'), return; end + + dir_contents = dir(search_path); + if isempty(dir_contents), return; end + + pattern = '^sub-([^_]+)_id-([^_]+)_expmtr-([^_]+)$'; % More robust pattern + + for i = 1:length(dir_contents) + if dir_contents(i).isdir + folder_name = dir_contents(i).name; + tokens = regexp(folder_name, pattern, 'tokens'); + + if ~isempty(tokens) + new_entry.folder_name = folder_name; + new_entry.subject_id = tokens{1}{1}; + new_entry.rat_name = tokens{1}{2}; + new_entry.experimenter = tokens{1}{3}; + info(end+1, 1) = new_entry; + end + end + end +function session_nums = find_session_numbers(subject_path) + session_nums = []; + if ~exist(subject_path, 'dir'), return; end + dir_contents = dir(fullfile(subject_path, 'ses-*')); + for i = 1:length(dir_contents) + if dir_contents(i).isdir + token = regexp(dir_contents(i).name, '^ses-(\d+)', 'tokens'); + if ~isempty(token), session_nums(end+1) = str2double(token{1}{1}); end + end + end +function update_popup(h, options, selection) + if isempty(options), options = {}; end + % --- MODIFIED: Removed 'All' from options --- + new_string = [{'-'}, unique(options), {'Add New...'}]; + + % Find the index for the current selection in the new list + val = find(strcmp(new_string, selection), 1); + if isempty(val), val = 1; end % Default to first item '-' if not found + % --- END MODIFICATION --- + + set(h, 'String', new_string, 'Value', val); +function [selection, updated] = get_and_handle_new(h, prompt_title) + updated = false; + items = get(h, 'String'); + val = get(h, 'Value'); + selection = items{val}; + + if strcmp(selection, 'Add New...') + new_val_cell = inputdlg(['Enter new value for ' prompt_title], prompt_title, [1 50]); + if ~isempty(new_val_cell) && ~isempty(new_val_cell{1}) + new_val = new_val_cell{1}; + % Check if it already exists (case-insensitive) + existing_idx = find(strcmpi(items, new_val), 1); + if ~isempty(existing_idx) + set(h, 'Value', existing_idx); + else + items{end} = new_val; % Replace 'Add New...' with the new value + items{end+1} = 'Add New...'; % Add it back at the end + set(h, 'String', items, 'Value', numel(items)-1); + end + updated = true; + selection = new_val; + else + set(h, 'Value', 1); % Revert to first item '-' if cancelled + selection = '-'; + end + end +function log_message(handles,logStr) + try + if ~isfield(handles, 'log_box') || ~isvalid(handles.log_box), return; end + current_text = get(handles.log_box, 'String'); + timestamp = char(datetime('now', 'Format', '[HH:mm:ss] ')); + new_line = [timestamp, logStr]; + new_text = [current_text; {new_line}]; + set(handles.log_box, 'String', new_text, 'Value', numel(new_text)); + drawnow; + catch + fprintf('%s: %s\n', char(datetime('now', 'Format', '[HH:mm:ss] ')), logStr); + end +% --- ADDED: Helper function to check for data in a directory --- +function has_data = does_dir_have_data(path_to_check) + has_data = false; + if exist(path_to_check, 'dir') + dir_contents = dir(path_to_check); + % Check if there are more than 2 entries (i.e., more than '.' and '..') + if numel(dir_contents) > 2 + has_data = true; + end + end +function toggle_button_color(~, ~, button_handle) + if ~isvalid(button_handle), return; end + currentColor = get(button_handle, 'BackgroundColor'); + if isequal(currentColor, [1, 0.4, 0.4]), set(button_handle, 'BackgroundColor', [1, 0.7, 0.4]); + else, set(button_handle, 'BackgroundColor', [1, 0.4, 0.4]); end +function commit_to_svn(handles, file_path_data,file_path_settings, root_dir) + +if isempty(file_path_data), return; end + if isempty(file_path_settings), return; end + [pname_data, fname_data, ~] = fileparts(file_path_data); + [pname_settings, fname_settings, ~] = fileparts(file_path_settings); + + configFilePath = fullfile(root_dir,'PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat'); + if ~exist(configFilePath, 'file') + log_message(handles, ['SVN commit failed: Password config file not found at ' configFilePath]); + return; + end + load(configFilePath, 'svn_user', 'svn_password'); + logmsg_data = char(strcat('automated commit from GUI for data and settings for ', {' '} ,fname_data,{'@'})); + % current_dir = cd; + cd(pname_data); + add_cmd_data = char(strcat('svn add', {' '}, fname_data, '.mat',{'@'})); + system(add_cmd_data); + commit_cmd_data = sprintf('svn ci --username="%s" --password="%s" -m "%s"', svn_user, svn_password, logmsg_data); + [status, ~] = system(commit_cmd_data); + cd(pname_settings); + add_cmd_settings = char(strcat('svn add', {' '}, fname_settings, '.mat',{'@'})); + system(add_cmd_settings); + logmsg_setting = char(strcat('automated commit from GUI for data and settings for ', {' '} ,fname_settings,{'@'})); + commit_cmd_setting = sprintf('svn ci --username="%s" --password="%s" -m "%s"', svn_user, svn_password, logmsg_setting); + [status, ~] = system(commit_cmd_setting); + + if status == 0 + log_message(handles, ['SVN commit successful for ' fname_data]); + else + log_message(handles, ['SVN commit FAILED for ' fname_data '.']); + end + + cd(fullfile(root_dir,'ExperPort')); + %% ======================================================================= +% DOCUMENTATION AND USAGE EXAMPLES +% ======================================================================= +function display_usage_help() +% DISPLAY_USAGE_HELP - Display usage instructions and examples +% +% This function provides comprehensive usage documentation for the GUI + fprintf('\n=== Neuropixels Recording & Behavior Controller Usage Guide ===\n\n'); + + fprintf('1. INITIALIZATION:\n'); + fprintf(' OpenEphys_Neuroblueprint_GUI(''init'');\n\n'); + + fprintf('2. WORKFLOW:\n'); + fprintf(' a) Select recording software (Open Ephys or SpikeGLX)\n'); + fprintf(' b) Configure behavior settings (protocol, experimenter, rat)\n'); + fprintf(' c) Set up NeuroBlueprint data paths\n'); + fprintf(' d) Configure probe settings (version, reference, bank/IMRO)\n'); + fprintf(' e) Set recording software connection parameters\n'); + fprintf(' f) Click "Load" to initialize systems\n'); + fprintf(' g) Click "Run" to start experiment\n'); + fprintf(' h) Click "Stop" to end experiment and save data\n\n'); + + fprintf('3. PROBE CONFIGURATION:\n'); + fprintf(' - Supports Neuropixels 1.0 (3 banks) and 2.0 (4 banks)\n'); + fprintf(' - Reference options: Tip or External\n'); + fprintf(' - Bank selection: Manual bank number or IMRO file\n'); + fprintf(' - Pre/post-session sampling across all banks\n\n'); + + fprintf('4. DATA ORGANIZATION:\n'); + fprintf(' - Follows NeuroBlueprint format\n'); + fprintf(' - Structure: project/rawdata/subject/session/datatype/\n'); + fprintf(' - Automatic session numbering\n'); + fprintf(' - SVN integration for version control\n\n'); + + fprintf('5. RECORDING SOFTWARE SUPPORT:\n'); + fprintf(' Open Ephys:\n'); + fprintf(' - HTTP API control\n'); + fprintf(' - Real-time parameter adjustment\n'); + fprintf(' - Acquisition and recording control\n\n'); + fprintf(' SpikeGLX:\n'); + fprintf(' - MATLAB SDK integration\n'); + fprintf(' - Run name management\n'); + fprintf(' - Recording enable/disable control\n\n'); + + fprintf('6. ERROR HANDLING:\n'); + fprintf(' - Comprehensive validation of inputs\n'); + fprintf(' - Automatic crash recovery for behavior protocols\n'); + fprintf(' - Detailed logging with timestamps\n'); + fprintf(' - Graceful fallbacks for system failures\n\n'); + + fprintf('For more information, see function documentation within the code.\n'); + fprintf('================================================================\n\n'); \ No newline at end of file diff --git a/ExperPort/Modules/@NeuropixelNeuroblueprint/NeuropixelNeuroblueprint.m b/ExperPort/Modules/@NeuropixelNeuroblueprint/NeuropixelNeuroblueprint.m new file mode 100644 index 00000000..2ab54c68 --- /dev/null +++ b/ExperPort/Modules/@NeuropixelNeuroblueprint/NeuropixelNeuroblueprint.m @@ -0,0 +1,1539 @@ +function [obj, varargout] = NeuropixelNeuroblueprint(varargin) +% NEUROPIXEL_NEUROBLUEPRINT_GUI - Integrated GUI for electrophysiology and behavior experiments +% +% This GUI manages experimental workflows for coordinated electrophysiology recording +% (using either Open Ephys or SpikeGLX) with behavioral protocols using Bpod/ExperPort. +% +% FEATURES: +% - Support for both Open Ephys and SpikeGLX recording systems +% - Neuropixels probe management (NP 1.0 and 2.0) +% - NeuroBlueprint data organization format +% - Automated session management and data saving +% - SVN integration for version control +% - Pre/post-session sampling across probe banks +% +% PRE-REQUISITES: +% 1. 'open-ephys-matlab-tools' must be in MATLAB path (for Open Ephys) +% 2. 'SpikeGLX-MATLAB-SDK' must be in MATLAB path (for SpikeGLX) +% 3. Bpod/ratter/ExperPort environment fully configured +% +% USAGE: +% gui_obj = NeuropixelNeuroblueprintGUI(); +% +%% Boilerplate for class definition and action handling +obj = class(struct, mfilename); +varargout = {}; +% Display usage help when function is called directly +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')) + display_usage_help(); + return; +end +if isa(varargin{1}, mfilename) + if length(varargin) < 2 || ~ischar(varargin{2}) + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else + action = varargin{2}; + varargin = varargin(3:end); + end +else + action = varargin{1}; + varargin = varargin(2:end); +end +if ~ischar(action) + error('The action parameter must be a string'); +end +GetSoloFunctionArgs(obj); +%% Main Action Router +switch action + % ========================================================================= + % CASE INIT + % ========================================================================= + case 'init' + % So that only the CPU-based software renderer instead of your graphics card + % opengl software; + + % Start Bpod if not already running + if evalin('base', 'exist(''BpodSystem'', ''var'')') + if evalin('base', '~isempty(BpodSystem)'), newstartup; else, flush; end + else, Bpod('COM5');newstartup; + end + + % --- State Variables as SoloParamHandles --- + SoloParamHandle(obj, 'currentState', 'value', 'Load'); + SoloParamHandle(obj, 'behavState', 'value', 'Run'); + SoloParamHandle(obj, 'ephysState', 'value', 'Run'); + SoloParamHandle(obj, 'is_running', 'value', 0); + SoloParamHandle(obj, 'recording_software', 'value', 'OpenEphys'); % 'OpenEphys' or 'SpikeGLX' + SoloParamHandle(obj, 'recording_controller', 'value', []); % Will hold OE or SpikeGLX controller + SoloParamHandle(obj, 'behav_obj', 'value', []); + SoloParamHandle(obj, 'blinking_timer', 'value', []); + SoloParamHandle(obj, 'current_params', 'value', []); + SoloParamHandle(obj, 'session_base_path', 'value', ''); + SoloParamHandle(obj, 'probe_settings', 'value', struct('version', '2.0', 'reference', 'Tip', 'bank', 0, 'imro_path', '', 'sync_slot', 2)); + SoloParamHandle(obj, 'probe_gui_handles', 'value', []); + SoloParamHandle(obj, 'session_info_list', 'value', []); % Holds parsed info from folders + + % Create stopping timer for behavior + scr = timer; + set(scr,'Period', 0.2,'ExecutionMode','FixedRate','TasksToExecute',Inf,... + 'BusyMode','drop','TimerFcn',[mfilename,'(''End_Continued'')']); + SoloParamHandle(obj, 'stopping_complete_timer', 'value', scr); + + % --- Create the GUI Figure --- + SoloParamHandle(obj, 'myfig', 'saveable', 0); + myfig.value = figure('Name', 'Neuropixels Recording & Behavior Controller',... + 'NumberTitle', 'off', 'MenuBar', 'none', ... + 'ToolBar', 'none', 'Units', 'normalized', ... + 'Position', [0.1, 0.1, 0.7, 0.85],... + 'Color', [0.94, 0.94, 0.94], ... + 'CloseRequestFcn', {@(h,e) feval(mfilename, obj, 'close')}); + % --- UI Creation --- + handles = struct(); + + % Activity Log Panel + uipanel('Title', 'Activity Log', 'FontSize', 12, 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.64, 0.03, 0.34, 0.94]); + handles.log_box = uicontrol('Style', 'edit', 'Units', 'normalized', 'Position', [0.65, 0.05, 0.32, 0.89], 'String', {'Log started...'}, 'Max', 10, 'Min', 1, 'HorizontalAlignment', 'left', 'Enable', 'inactive', 'BackgroundColor', [1, 1, 1]); + + % Panel 0: Recording Software Selection + p0 = uipanel('Title', '0. Recording Software', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.88, 0.6, 0.09]); + handles.software_group = uibuttongroup(p0, 'Title', '', 'BorderType', 'none', 'Units', 'normalized', 'Position', [0.05, 0.1, 0.9, 0.8], 'SelectionChangedFcn', {@(h,e) feval(mfilename, obj, 'recording_software_callback')}); + uicontrol(handles.software_group, 'Style', 'radiobutton', 'String', 'Open Ephys', 'Units', 'normalized', 'Position', [0.1, 0.3, 0.4, 0.4], 'Tag', 'OpenEphys', 'FontSize', 10); + uicontrol(handles.software_group, 'Style', 'radiobutton', 'String', 'SpikeGLX', 'Units', 'normalized', 'Position', [0.5, 0.3, 0.4, 0.4], 'Tag', 'SpikeGLX', 'FontSize', 10); + + % Panel 1: Behavior + p1 = uipanel('Title', '1. Behavior', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.74, 0.6, 0.13]); + uicontrol(p1, 'Style', 'text', 'String', 'Protocol Name:', 'Units', 'normalized', 'Position', [0.05, 0.7, 0.22, 0.25], 'HorizontalAlignment', 'right'); + handles.protocol_edit = uicontrol(p1, 'Style', 'edit', 'String', 'ArpitSoundCatContinuous', 'Units', 'normalized', 'Position', [0.3, 0.7, 0.45, 0.25]); + handles.manual_test = uicontrol(p1, 'Style', 'checkbox', 'String', 'Manual Test', 'Value', 1, 'Units', 'normalized', 'Position', [0.78, 0.7, 0.2, 0.25]); + uicontrol(p1, 'Style', 'text', 'String', 'Experimenter:', 'Units', 'normalized', 'Position', [0.02, 0.4, 0.2, 0.25], 'HorizontalAlignment', 'right'); + handles.exp_popup = uicontrol(p1, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.23, 0.4, 0.25, 0.25], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'filter')}); + uicontrol(p1, 'Style', 'text', 'String', 'Rat Name:', 'Units', 'normalized', 'Position', [0.50, 0.4, 0.18, 0.25], 'HorizontalAlignment', 'right'); + handles.rat_name_popup = uicontrol(p1, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.69, 0.4, 0.30, 0.25], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'filter')}); + uicontrol(p1, 'Style', 'text', 'String', 'Distribution:', 'Units', 'normalized', 'Position', [0.02, 0.1, 0.18, 0.25], 'HorizontalAlignment', 'right'); + handles.distribution_popup = uicontrol(p1, 'Style', 'popupmenu', 'String', {'random', 'Uniform', 'Hard A', 'Hard B'}, 'Units', 'normalized', 'Position', [0.21, 0.1, 0.35, 0.25]); + uicontrol(p1, 'Style', 'text', 'String', 'Path:', 'Units', 'normalized', 'Position', [0.58, 0.1, 0.1, 0.25], 'HorizontalAlignment', 'right'); + handles.behav_edit = uicontrol(p1, 'Style', 'edit', 'String', 'C:\ratter', 'Units', 'normalized', 'Position', [0.69, 0.1, 0.30, 0.25]); + + % Panel 2: NeuroBlueprint Format + p2 = uipanel('Title', '2. NeuroBlueprint Format', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.42, 0.6, 0.31]); + uicontrol(p2, 'Style', 'text', 'String', 'Project Name:', 'Units', 'normalized', 'Position', [0.01, 0.85, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.proj_edit = uicontrol(p2, 'Style', 'edit', 'String', 'sound_cat_rat', 'Units', 'normalized', 'Position', [0.3, 0.85, 0.65, 0.12], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'rescan')}); + uicontrol(p2, 'Style', 'text', 'String', 'Subject ID:', 'Units', 'normalized', 'Position', [0.01, 0.7, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.sub_popup = uicontrol(p2, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.3, 0.7, 0.3, 0.12], 'Enable', 'inactive'); + uicontrol(p2, 'Style', 'text', 'String', 'Session ID:', 'Units', 'normalized', 'Position', [0.61, 0.7, 0.2, 0.1], 'HorizontalAlignment', 'right'); + handles.session_popup = uicontrol(p2, 'Style', 'popupmenu', 'String', {'-'}, 'Units', 'normalized', 'Position', [0.82, 0.7, 0.15, 0.12]); + uicontrol(p2, 'Style', 'text', 'String', 'Local Path:', 'Units', 'normalized', 'Position', [0.01, 0.55, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.local_edit = uicontrol(p2, 'Style', 'edit', 'String', 'C:\Ephys_Experiment_Data', 'Units', 'normalized', 'Position', [0.3, 0.55, 0.5, 0.12], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'rescan')}); + handles.local_browse = uicontrol(p2, 'Style', 'pushbutton', 'String', 'Browse...', 'Units', 'normalized', 'Position', [0.81, 0.55, 0.16, 0.13], 'Callback', {@(h,e) feval(mfilename, obj, 'browse_path', 'local')}); + uicontrol(p2, 'Style', 'text', 'String', 'Central Path:', 'Units', 'normalized', 'Position', [0.01, 0.4, 0.28, 0.1], 'HorizontalAlignment', 'right'); + handles.central_edit = uicontrol(p2, 'Style', 'edit', 'String', 'Z:\_projects', 'Units', 'normalized', 'Position', [0.3, 0.4, 0.5, 0.12], 'Callback', {@(h,e) feval(mfilename, obj, 'populate_and_filter_lists', 'rescan')}); + handles.central_browse = uicontrol(p2, 'Style', 'pushbutton', 'String', 'Browse...', 'Units', 'normalized', 'Position', [0.81, 0.4, 0.16, 0.13], 'Callback', {@(h,e) feval(mfilename, obj, 'browse_path', 'central')}); + uicontrol(p2, 'Style', 'text', 'String', 'Subfolders to Create:', 'Units', 'normalized', 'Position', [0.05, 0.2, 0.9, 0.1], 'HorizontalAlignment', 'left'); + handles.cb_ephys = uicontrol(p2, 'Style', 'checkbox', 'String', 'ephys', 'Value', 1, 'Units', 'normalized', 'Position', [0.05, 0.05, 0.2, 0.15]); + handles.cb_behav = uicontrol(p2, 'Style', 'checkbox', 'String', 'behav', 'Value', 1, 'Units', 'normalized', 'Position', [0.28, 0.05, 0.2, 0.15]); + handles.cb_anat = uicontrol(p2, 'Style', 'checkbox', 'String', 'anat', 'Value', 0, 'Units', 'normalized', 'Position', [0.51, 0.05, 0.2, 0.15]); + handles.cb_funcimg = uicontrol(p2, 'Style', 'checkbox', 'String', 'funcimg', 'Value', 0, 'Units', 'normalized', 'Position', [0.74, 0.05, 0.25, 0.15]); + + % Panel 3: Pre/Post-Experiment Sampling + p3 = uipanel('Title', '3. Pre/Post-Experiment Sampling', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.28, 0.6, 0.12]); + uicontrol(p3, 'Style', 'text', 'String', 'Duration/Bank (s):', 'Units', 'normalized', 'Position', [0.01, 0.6, 0.25, 0.25], 'HorizontalAlignment', 'right'); + handles.sample_duration = uicontrol(p3, 'Style', 'edit', 'String', '60', 'Units', 'normalized', 'Position', [0.27, 0.6, 0.1, 0.3]); + handles.target_display = uicontrol(p3, 'Style', 'text', 'String', 'Target: Bank 0', 'Units', 'normalized', 'Position', [0.38, 0.6, 0.3, 0.25], 'HorizontalAlignment', 'right', 'FontWeight', 'bold'); + handles.probe_button = uicontrol(p3, 'Style', 'pushbutton', 'String', 'Probe Setting', 'Units', 'normalized', 'Position', [0.7, 0.55, 0.28, 0.4], 'FontSize', 10, 'Callback', {@(h,e) feval(mfilename, obj, 'open_probe_gui')}); + handles.sample_button = uicontrol(p3, 'Style', 'pushbutton', 'String', 'Start Sample Recording', 'Units', 'normalized', 'Position', [0.05, 0.1, 0.9, 0.4], 'FontSize', 12, 'FontWeight', 'bold', 'BackgroundColor', [0.8, 0.7, 1], 'Callback', {@(h,e) feval(mfilename, obj, 'sample_recording_wrapper')}); + + % Panel 4: Recording Settings + p4 = uipanel('Title', '4. Recording Settings', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.15, 0.6, 0.11]); + handles.settings_panel = p4; + + % Open Ephys Settings (initially visible) + handles.oe_ip_label = uicontrol(p4, 'Style', 'text', 'String', 'GUI IP:', 'Units', 'normalized', 'Position', [0.01, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right'); + handles.oe_ip_edit = uicontrol(p4, 'Style', 'edit', 'String', '127.0.0.1', 'Units', 'normalized', 'Position', [0.17, 0.25, 0.15, 0.5]); + handles.oe_proc_label = uicontrol(p4, 'Style', 'text', 'String', 'Proc ID:', 'Units', 'normalized', 'Position', [0.33, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right'); + handles.oe_proc_edit = uicontrol(p4, 'Style', 'edit', 'String', '100', 'Units', 'normalized', 'Position', [0.49, 0.25, 0.15, 0.5]); + handles.oe_rec_label = uicontrol(p4, 'Style', 'text', 'String', 'Rec ID:', 'Units', 'normalized', 'Position', [0.65, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right'); + handles.oe_rec_edit = uicontrol(p4, 'Style', 'edit', 'String', '101', 'Units', 'normalized', 'Position', [0.81, 0.25, 0.15, 0.5]); + + % SpikeGLX Settings (initially hidden) + handles.sglx_host_label = uicontrol(p4, 'Style', 'text', 'String', 'Host IP:', 'Units', 'normalized', 'Position', [0.01, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right', 'Visible', 'off'); + handles.sglx_host_edit = uicontrol(p4, 'Style', 'edit', 'String', 'localhost', 'Units', 'normalized', 'Position', [0.17, 0.25, 0.15, 0.5], 'Visible', 'off'); + handles.sglx_port_label = uicontrol(p4, 'Style', 'text', 'String', 'Port:', 'Units', 'normalized', 'Position', [0.33, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right', 'Visible', 'off'); + handles.sglx_port_edit = uicontrol(p4, 'Style', 'edit', 'String', '4142', 'Units', 'normalized', 'Position', [0.49, 0.25, 0.15, 0.5], 'Visible', 'off'); + handles.sglx_probe_label = uicontrol(p4, 'Style', 'text', 'String', 'Probe Idx:', 'Units', 'normalized', 'Position', [0.65, 0.3, 0.15, 0.4], 'HorizontalAlignment', 'right', 'Visible', 'off'); + handles.sglx_probe_edit = uicontrol(p4, 'Style', 'edit', 'String', '0', 'Units', 'normalized', 'Position', [0.81, 0.25, 0.15, 0.5], 'Visible', 'off'); + % --- Control Buttons Panel --- + p5 = uipanel('Title', 'Controls', 'FontSize', 12, 'FontWeight', 'bold', 'BorderType', 'etchedin', 'BorderWidth', 1, 'Units', 'normalized', 'Position', [0.02, 0.02, 0.6, 0.11]); + handles.control_button = uicontrol(p5, 'Style', 'pushbutton', 'String', 'Load', 'Units', 'normalized', 'FontSize', 14, 'FontWeight', 'bold', 'Position', [0.02, 0.1, 0.3, 0.8], 'BackgroundColor', [0.2, 0.6, 0.8], 'Callback', {@(h,e) feval(mfilename, obj, 'main_control_callback')}); + handles.behav_button = uicontrol(p5, 'Style', 'pushbutton', 'String', 'Run Behav', 'Units', 'normalized', 'FontSize', 12, 'Position', [0.35, 0.1, 0.3, 0.8], 'BackgroundColor', [1, 0.8, 0.6], 'Callback', {@(h,e) feval(mfilename, obj, 'behav_control_callback')}); + handles.ephys_button = uicontrol(p5, 'Style', 'pushbutton', 'String', 'Run Ephys', 'Units', 'normalized', 'FontSize', 12, 'Position', [0.68, 0.1, 0.3, 0.8], 'BackgroundColor', [0.8, 0.6, 1], 'Callback', {@(h,e) feval(mfilename, obj, 'ephys_control_callback')}); + + SoloParamHandle(obj, 'ui_handles', 'value', handles); + + feval(mfilename, obj, 'populate_and_filter_lists', 'rescan'); + log_message(handles, 'GUI initialization complete.'); + % ========================================================================= + % CASE MAIN_CONTROL_CALLBACK + % ========================================================================= + case 'main_control_callback' + switch value(currentState) + case 'Load', feval(mfilename, obj, 'load_sequence'); + case 'Run', feval(mfilename, obj, 'run_sequence'); + case 'Stop', feval(mfilename, obj, 'stop_sequence'); + case 'PostExperiment', feval(mfilename, obj, 'reset_to_load_state'); + end + % ========================================================================= + % CASE RECORDING_SOFTWARE_CALLBACK + % ========================================================================= + case 'recording_software_callback' + handles = value(ui_handles); + selected_software = get(get(handles.software_group, 'SelectedObject'), 'Tag'); + recording_software.value = selected_software; + + if strcmp(selected_software, 'OpenEphys') + % Show OpenEphys settings, hide SpikeGLX + set([handles.oe_ip_label, handles.oe_ip_edit, handles.oe_proc_label, handles.oe_proc_edit, handles.oe_rec_label, handles.oe_rec_edit], 'Visible', 'on'); + set([handles.sglx_host_label, handles.sglx_host_edit, handles.sglx_port_label, handles.sglx_port_edit, handles.sglx_probe_label, handles.sglx_probe_edit], 'Visible', 'off'); + log_message(handles, 'Switched to Open Ephys recording mode.'); + else % SpikeGLX + % Hide OpenEphys settings, show SpikeGLX + set([handles.oe_ip_label, handles.oe_ip_edit, handles.oe_proc_label, handles.oe_proc_edit, handles.oe_rec_label, handles.oe_rec_edit], 'Visible', 'off'); + set([handles.sglx_host_label, handles.sglx_host_edit, handles.sglx_port_label, handles.sglx_port_edit, handles.sglx_probe_label, handles.sglx_probe_edit], 'Visible', 'on'); + log_message(handles, 'Switched to SpikeGLX recording mode.'); + end + + % ========================================================================= + % WORKFLOW ACTIONS + % ========================================================================= + case 'load_sequence' + handles = value(ui_handles); + log_message(handles, '--- LOAD sequence initiated ---'); + set(handles.control_button, 'Enable', 'off', 'String', 'Loading...'); + set(handles.sample_button, 'Enable', 'off'); + + try + software = value(recording_software); + params = get_all_parameters(handles,software); + + % --- ADDED: Confirmation for overwriting existing session data --- + if ~strcmp(params.session_id, 'New') + % Construct path to check for existing data + subject_name_part = sprintf('sub-%s_id-%s_expmtr-%s', params.subject_id, params.rat_name, params.experimenter); + session_name_part = sprintf('ses-%s', params.session_id); % Assumes session_id is formatted '01', '02' etc. + base_path_part = fullfile(params.project_name, 'rawdata', subject_name_part, session_name_part); + + local_behav_path = fullfile(params.local_path, base_path_part, 'behav'); + local_ephys_path = fullfile(params.local_path, base_path_part, 'ephys'); + central_behav_path = fullfile(params.central_path, base_path_part, 'behav'); + central_ephys_path = fullfile(params.central_path, base_path_part, 'ephys'); + + data_exists = does_dir_have_data(local_behav_path) || does_dir_have_data(local_ephys_path) || ... + does_dir_have_data(central_behav_path) || does_dir_have_data(central_ephys_path); + + if data_exists + question = sprintf('Session %s for this subject already contains data. Continuing may result in data being overwritten. Are you sure you want to proceed?', params.session_id); + answer = questdlg(question, 'Confirm Overwrite', 'Yes', 'No', 'No'); + if strcmp(answer, 'No') + log_message(handles, 'Load sequence aborted by user to prevent overwriting data.'); + feval(mfilename, obj, 'reset_to_load_state'); + return; % Abort the load sequence + end + end + end + % --- END ADDED SECTION --- + + if ~validate_all_inputs(params,handles,software) + feval(mfilename, obj, 'reset_to_load_state'); + return; + end + current_params.value = params; + + [session_path, ~] = construct_session_paths(handles, params); + if isempty(session_path) || ~create_session_directories(handles, params, session_path) + feval(mfilename, obj, 'reset_to_load_state'); + return; + end + session_base_path.value = session_path; + + % Initialize the Behavior system + feval(mfilename, obj, 'initialize_behavior_system'); + + catch ME + log_message(handles, sprintf('ERROR during load sequence: %s', ME.message)); + feval(mfilename, obj, 'reset_to_load_state'); + rethrow(ME); + end + case 'run_sequence' + handles = value(ui_handles); + params = value(current_params); + + % --- ADDED: Pre-run checklist dialog --- + software = value(recording_software); + if strcmp(software, 'OpenEphys') + title = 'Open Ephys Pre-Run Checklist'; + message = { + 'Before continuing, please ensure the following in Open Ephys:', ... + '', ... + '1. Refresh the Neuropixel PXI module to ensure probes are detected.', ... + '2. Set the probe geometry (Bank, Reference, or select the IMRO file).', ... + '3. Configure the main sync slot and SMA settings.', ... + '', ... + 'NOTE: The file save directory and name will be set automatically by this program.' ... + }; + else % SpikeGLX + title = 'SpikeGLX Pre-Run Checklist'; + message = { + 'Before continuing, please ensure the following in SpikeGLX:', ... + '', ... + '1. Press ''Detect'' to find the connected probes.', ... + '2. In Sync settings, set ''Square Wave Source'' to ''Disable sync waveform''.', ... + '3. Set the inputs for the ''imec PXI SMA slot''.', ... + '4. Select the correct IMRO file for the animal within SpikeGLX.',... + '', ... + 'NOTE: The run name and directory will be set automatically by this program.'... + }; + end + + answer = questdlg(message, title, 'Continue', 'Cancel', 'Continue'); + + if ~strcmp(answer, 'Continue') + log_message(handles, 'Run sequence cancelled by user at pre-run checklist.'); + return; % Abort the run sequence + end + % --- END ADDED SECTION --- + + log_message(handles, '--- RUN sequence initiated ---'); + set(handles.sample_button, 'Enable', 'off'); + + try + if get(handles.cb_ephys, 'Value') + % Initialize the selected recording system + recording_save_path = fullfile(params.local_path, value(session_base_path), 'ephys'); + feval(mfilename, obj, 'initialize_recording_system', params, recording_save_path); + if isempty(value(recording_controller)) + feval(mfilename, obj, 'reset_to_load_state'); + return; + end + % Start Ephys Recording + feval(mfilename, obj, 'start_electrophysiology_recording', params); + else + log_message(handles, 'Ephys checkbox not selected. No recording started.'); + end + + currentState.value = 'Stop'; + set(handles.control_button, 'String', 'Stop'); + feval(mfilename, obj, 'start_blinking'); + + feval(mfilename, obj, 'start_behavioral_protocol', params); + log_message(handles, '--- RUN sequence complete. Experiment is live. ---'); + + catch ME + log_message(handles, sprintf('ERROR during run sequence: %s', ME.message)); + feval(mfilename, obj, 'stop_blinking'); + rethrow(ME); + end + case 'stop_sequence' + handles = value(ui_handles); + params = value(current_params); + log_message(handles, '--- STOP sequence initiated ---'); + feval(mfilename, obj, 'stop_blinking'); + + try + behav_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + feval(mfilename, obj, 'stop_behavioral_protocol', params, behav_save_dir); + + if get(handles.cb_ephys, 'Value') && ~isempty(value(recording_controller)) + feval(mfilename, obj, 'stop_electrophysiology_recording'); + end + + log_message(handles, '--- Experiment finished. Post-session sampling available. ---'); + currentState.value = 'PostExperiment'; + set(handles.control_button, 'String', 'Start New Experiment', 'BackgroundColor', [0.2, 0.8, 0.6]); + set(handles.sample_button, 'Enable', 'on'); + + catch ME + log_message(handles, sprintf('ERROR during stop sequence: %s', ME.message)); + rethrow(ME); + end + % ========================================================================= + % SYSTEM INITIALIZATION & CONTROL + % ========================================================================= + case 'initialize_recording_system' + params = varargin{1}; + save_path = varargin{2}; + software = value(recording_software); + handles = value(ui_handles); + + try + if strcmp(software, 'OpenEphys') + log_message(handles, 'Initializing Open Ephys controller...'); + controller = OpenEphysHTTPServer(params.oe_gui_ip, 37497); + if isempty(controller), error('Failed to create Open Ephys controller'); end + else % SpikeGLX + log_message(handles, 'Initializing SpikeGLX controller...'); + controller = SpikeGL(params.sglx_host_ip, params.sglx_port); + % if ~controller.IsConnected(), error('Failed to connect to SpikeGLX'); end + end + recording_controller.value = controller; + log_message(handles, sprintf('%s controller initialized successfully.', software)); + + % Set initial recording path + feval(mfilename, obj, 'set_recording_path', save_path); + + catch ME + log_message(handles, sprintf('Failed to initialize %s: %s', software, ME.message)); + recording_controller.value = []; + rethrow(ME); + end + + case 'set_recording_path' + save_path = varargin{1}; + software = value(recording_software); + controller = value(recording_controller); + params = value(current_params); + handles = value(ui_handles); + if isempty(controller), return; end + + try + if strcmp(software, 'OpenEphys') + controller.setRecordPath(params.oe_rec_node_id, save_path); + else % SpikeGLX + controller = SetDataDir( controller, 0, save_path); + recording_controller.value = controller; + end + log_message(handles, sprintf('Recording path set to: %s', save_path)); + catch ME + log_message(handles, sprintf('Failed to set recording path: %s', ME.message)); + rethrow(ME); + end + case 'start_electrophysiology_recording' + params = varargin{1}; + software = value(recording_software); + controller = value(recording_controller); + handles = value(ui_handles); + if isempty(controller), error('Recording controller not initialized'); end + + try + probe_settings_struct = value(probe_settings); + feval(mfilename, obj, 'apply_probe_configuration', probe_settings_struct); + + main_ephys_path = fullfile(params.local_path, value(session_base_path), 'ephys'); + feval(mfilename, obj, 'set_recording_path', main_ephys_path); + + if strcmp(software, 'OpenEphys') + log_message(handles, 'Starting Open Ephys acquisition and recording...'); + controller.acquire(); pause(1); + controller.record(); + else % SpikeGLX + log_message(handles, 'Starting SpikeGLX recording...'); + run_name = sprintf('experiment_%s', datestr(now, 'yyyymmdd_HHMMSS')); + boolval = IsInitialized( controller); + if boolval + spikeglx_params = GetParams( controller ); + controller = SetRunName(controller,run_name); % setting run name + controller = StartRun(controller); % starting acquisition + pause(2); + + runningval = false; + while ~runningval % waiting for acquisition to start + runningval = IsRunning( controller ); + if runningval + controller = SetRecordingEnable( controller, 1 ); % Start Recording + end + end + recording_controller.value = controller; + end + end + + log_message(handles, 'Electrophysiology recording is LIVE.'); + ephysState.value = 'Stop'; + set(handles.ephys_button, 'String', 'Stop Ephys', 'BackgroundColor', [1 0.6 0.6]); + + catch ME + log_message(handles, sprintf('Failed to start recording: %s', ME.message)); + rethrow(ME); + end + case 'stop_electrophysiology_recording' + software = value(recording_software); + controller = value(recording_controller); + handles = value(ui_handles); + if isempty(controller), return; end + + try + if strcmp(software, 'OpenEphys') + log_message(handles, 'Stopping Open Ephys recording...'); + controller.idle(); + else % SpikeGLX + log_message(handles, 'Stopping SpikeGLX recording...'); + boolval = IsSaving( controller ); + if boolval + controller = SetRecordingEnable( controller, 0 ); + pause(1); % Brief pause to ensure recording stops + controller = StopRun(controller); + else + controller = StopRun(controller); + end + recording_controller.value = controller; + end + + log_message(handles, 'Electrophysiology recording stopped.'); + ephysState.value = 'Run'; + set(handles.ephys_button, 'String', 'Run Ephys', 'BackgroundColor', [0.8, 0.6, 1]); + + catch ME + log_message(handles, sprintf('Failed to stop recording: %s', ME.message)); + rethrow(ME); + end + case 'initialize_behavior_system' + params = value(current_params); + handles = value(ui_handles); + try + log_message(handles, 'Initializing behavior control system...'); + behav_obj.value = dispatcher('init'); + h=get_sphandle('owner','dispatcher','name','myfig'); + set(value(h{1}), 'Visible','Off'); + if params.do_manual_test + feval(mfilename, obj, 'behav_control', 'manual_test'); + else + feval(mfilename, obj, 'continue_load_after_manual_test'); + end + catch ME + log_message(handles, ['FATAL ERROR initializing behavior system: ' ME.message]); + errordlg(['Failed to initialize behavior system. Check path and logs. Error: ' ME.message], 'Behavior System Error'); + rethrow(ME); + end + + case 'start_behavioral_protocol' + params = varargin{1}; + handles = value(ui_handles); + try + log_message(handles, 'Starting behavioral protocol...'); + feval(mfilename, obj, 'behav_control', 'run', params.protocol_name); + log_message(handles, 'Behavioral protocol is LIVE.'); + catch ME + log_message(handles, ['FATAL ERROR starting behavior protocol: ' ME.message]); + errordlg(['Failed to start behavior protocol. Check logs. Error: ' ME.message], 'Behavior System Error'); + rethrow(ME); + end + case 'stop_behavioral_protocol' + params = varargin{1}; + behav_save_dir = varargin{2}; + handles = value(ui_handles); + try + log_message(handles, 'Ending behavioral session (saving data)...'); + feval(mfilename, obj, 'behav_control', 'end', params.protocol_name, params.behav_path, behav_save_dir); + log_message(handles, 'Behavioral data saved successfully.'); + catch ME + log_message(handles, ['FATAL ERROR ending behavioral session: ' ME.message]); + errordlg(['Failed to save behavioral data. Check logs. Error: ' ME.message], 'Behavior System Error'); + rethrow(ME); + end + + case 'manual_test_stopping' + handles = value(ui_handles); + log_message(handles, 'Manual rig test complete. Cleaning up...'); + dispatcher(value(behav_obj), 'Stop'); + %Let's pause until we know dispatcher is done running + set(value(stopping_complete_timer), 'TimerFcn', {@(h,e) feval(mfilename, obj, 'manual_test_stopped')}); + start(value(stopping_complete_timer)); + case 'manual_test_stopped' + if value(stopping_process_completed) %This is provided by RunningSection + stop(value(stopping_complete_timer)); %Stop looping. + dispatcher('set_protocol', ''); + is_running.value = 0; + feval(mfilename, obj, 'continue_load_after_manual_test'); + end + + case 'continue_load_after_manual_test' + params = value(current_params); + handles = value(ui_handles); + video_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + try + log_message(handles, 'Loading main behavioral protocol...'); + feval(mfilename, obj, 'behav_control', 'load_main_protocol', params.experimenter, params.rat_name, params.protocol_name, video_save_dir, params.behav_path,params.stim_distribution); + log_message(handles, 'Behavior system loaded and ready.'); + log_message(handles, '--- LOAD sequence complete. Ready to run. ---'); + currentState.value = 'Run'; + set(handles.control_button, 'Enable', 'on', 'String', 'Run', 'BackgroundColor', [0.4, 0.8, 0.4]); + set(handles.sample_button, 'Enable', 'on'); + + % --- ADDED: Confirmation dialog after successful load --- + msgbox('Behavior settings loaded successfully. The system is now ready to run.', 'Load Complete', 'help'); + % --- END ADDED SECTION --- + + catch ME + log_message(handles, ['FATAL ERROR loading main protocol: ' ME.message]); + errordlg(['Failed to load main protocol. Error: ' ME.message], 'Behavior System Error'); + feval(mfilename, obj, 'reset_to_load_state'); + end + + % ========================================================================= + % INDIVIDUAL CONTROL CALLBACKS + % ========================================================================= + case 'behav_control_callback' + switch value(behavState) + case 'Run', feval(mfilename, obj, 'behav_control', 'load_run'); + case 'Stop', feval(mfilename, obj, 'behav_control', 'end'); + end + + case 'ephys_control_callback' + switch value(ephysState) + case 'Run', feval(mfilename, obj, 'run_ephys_individually'); + case 'Stop', feval(mfilename, obj, 'stop_ephys_individually'); + end + + case 'run_ephys_individually' + params = value(current_params); + handles = value(ui_handles); + log_message(handles, '--- Starting Ephys Recording Individually ---'); + set(handles.ephys_button, 'Enable', 'off'); + try + if isempty(value(recording_controller)) + feval(mfilename, obj, 'initialize_recording_system', params, ''); + end + feval(mfilename, obj, 'start_electrophysiology_recording', params); + catch ME + log_message(handles, sprintf('ERROR starting ephys: %s', ME.message)); + end + set(handles.ephys_button, 'Enable', 'on'); + + case 'stop_ephys_individually' + handles = value(ui_handles); + log_message(handles, '--- Stopping Ephys Recording Individually ---'); + set(handles.ephys_button, 'Enable', 'off'); + try + feval(mfilename, obj, 'stop_electrophysiology_recording'); + catch ME + log_message(handles, sprintf('ERROR stopping ephys: %s', ME.message)); + end + set(handles.ephys_button, 'Enable', 'on'); + % ========================================================================= + % BEHAVIOR CONTROL ACTIONS + % ========================================================================= + case 'behav_control' + sub_action = varargin{1}; + args = varargin(2:end); + handles = value(ui_handles); + + switch sub_action + case 'load_main_protocol' + experimenter = args{1}; ratname = args{2}; protocol_name = args{3}; + video_save_dir = args{4}; behav_path = args{5}; stim_distribution = args{6}; + log_message(handles, ['Loading protocol: ' protocol_name]); + dispatcher('set_protocol', protocol_name); + rath = get_sphandle('name', 'ratname', 'owner', protocol_name); + exph = get_sphandle('name', 'experimenter', 'owner', protocol_name); + rath{1}.value = ratname; exph{1}.value = experimenter; + protobj = eval(protocol_name); + log_message(handles, ['Loading settings for ' ratname]); + [~, sfile] = load_solouiparamvalues(ratname, 'experimenter', experimenter, 'owner', class(protobj), 'interactive', 0); + feval(protocol_name, protobj, 'set_setting_params', ratname, experimenter, sfile, char(datetime('now')), video_save_dir); + feval(protocol_name, protobj, 'set_stim_distribution',stim_distribution); + if ~dispatcher('is_running'), pop_history(class(protobj), 'include_non_gui', 1); feval(protocol_name, protobj, 'prepare_next_trial'); end + + case 'crashed' + log_message(handles, '--- BEHAVIOR CRASH RECOVERY INITIATED ---'); + params = value(current_params); + video_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + feval(mfilename, obj, 'behav_control', 'load_protocol_after_crash', params.experimenter, params.rat_name, params.protocol_name, video_save_dir, params.behav_path); + feval(mfilename, obj, 'behav_control', 'run', params.protocol_name); + log_message(handles, '--- RECOVERY COMPLETE: Behavior protocol restarted ---'); + + case 'load_protocol_after_crash' + experimenter = args{1}; ratname = args{2}; protocol_name = args{3}; + video_save_dir = args{4}; behav_path = args{5}; + log_message(handles, ['Loading protocol after crash: ' protocol_name]); + dispatcher('set_protocol', protocol_name); + rath = get_sphandle('name', 'ratname', 'owner', protocol_name); + exph = get_sphandle('name', 'experimenter', 'owner', protocol_name); + rath{1}.value = ratname; exph{1}.value = experimenter; + protobj = eval(protocol_name); + try + log_message(handles, ['Loading previous data for ' ratname]); + today_date = char(datetime('now','format','yyMMdd')); + temp_data_dir = fullfile(behav_path,'SoloData','Data',experimenter,ratname); + temp_data_file = sprintf('data_@%s_%s_%s_%s_ASV.mat',protocol_name,experimenter,ratname,today_date); + if isfile(fullfile(temp_data_dir,temp_data_file)) + dispatcher('runstart_disable'); + load_soloparamvalues(ratname, 'experimenter', experimenter, 'owner', protocol_name, 'interactive', 0,'data_file',fullfile(temp_data_dir,temp_data_file)); + dispatcher('runstart_enable'); + end + feval(protocol_name, protobj, 'psychometricUpdate_aftercrash'); % update parameters for psychometric plots which were not saved so cant be loaded + if ~dispatcher('is_running'), pop_history(class(protobj), 'include_non_gui', 1); feval(protocol_name, protobj, 'prepare_next_trial'); end + catch + log_message(handles, ['Loading settings for ' ratname]); + [~, sfile] = load_solouiparamvalues(ratname, 'experimenter', experimenter, 'owner', class(protobj), 'interactive', 0); + feval(protocol_name, protobj, 'set_setting_params', ratname, experimenter, sfile, char(datetime('now')), video_save_dir); + if ~dispatcher('is_running'), pop_history(class(protobj), 'include_non_gui', 1); feval(protocol_name, protobj, 'prepare_next_trial'); end + end + case 'load_run' + set(handles.behav_button, 'Enable', 'off'); + log_message(handles, '--- STARTING BEHAV PROTOCOL ---'); + params = value(current_params); + video_save_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + feval(mfilename, obj, 'behav_control', 'load_protocol_after_crash', params.experimenter, params.rat_name, params.protocol_name, video_save_dir, params.behav_path); + feval(mfilename, obj, 'behav_control', 'run', params.protocol_name); + log_message(handles, '--- START COMPLETE: Behavior protocol started ---'); + set(handles.behav_button, 'Enable', 'on'); + case 'run' + protocol_name = args{1}; protobj = eval(protocol_name); + log_message(handles, 'Starting video recording via protocol...'); + feval(protocol_name, protobj, 'start_recording'); + log_message(handles, 'Starting dispatcher to run trials...'); + is_running.value = 1; + behavState.value = 'Stop'; + set(handles.behav_button, 'String', 'Stop Behav', 'BackgroundColor', [1 0.6 0.6]); + dispatcher(value(behav_obj), 'Run'); + + case 'end' + set(handles.behav_button, 'Enable', 'off'); + if length(args) >= 3 + protocol_name = args{1}; root_dir = args{2}; behav_copy_dir = args{3}; + else + params = value(current_params); + protocol_name = params.protocol_name; + root_dir = params.behav_path; + behav_copy_dir = fullfile(params.local_path, value(session_base_path), 'behav'); + end + log_message(handles, 'Stopping dispatcher...'); + dispatcher(value(behav_obj), 'Stop'); + set(value(stopping_complete_timer), 'Period', 0.8,'TimerFcn', {@(h,e) feval(mfilename, obj, 'behav_control','end_continued',protocol_name, root_dir, behav_copy_dir)}); + start(value(stopping_complete_timer)); + case 'end_continued' + if value(stopping_process_completed) % This is provided by RunningSection + protocol_name = args{1}; root_dir = args{2}; destination_path = args{3}; + stop(value(stopping_complete_timer)); %Stop looping. + is_running.value = 0; + feval(mfilename, obj, 'behav_control', 'send_empty_state_machine'); + protobj = eval(protocol_name); + log_message(handles, 'Ending session via protocol...'); + feval(protocol_name, protobj, 'end_session'); + log_message(handles, 'Saving data and settings...'); + data_file = SavingSection(protobj, 'savedata', 'interactive', 0); + try + feval(protocol_name, protobj, 'pre_saving_settings'); + catch + log_message(handles, 'Protocol does not have a pre_saving_settings section.'); + end + [settings_file, ~] = SavingSection(protobj, 'get_set_filename'); + SavingSection(protobj, 'savesets', 'interactive', 0); + log_message(handles, 'Committing data and settings to SVN...'); + commit_to_svn(handles, data_file, settings_file, root_dir); + dispatcher('set_protocol', ''); + data_file = [data_file '.mat']; + [status, msg] = copyfile(data_file, destination_path); + if status, log_message(handles,'Data File copied successfully.'); + else, log_message(handles,['Error copying Data file: ' msg]); + end + behavState.value = 'Run'; + set(handles.behav_button, 'String', 'Run Behav', 'BackgroundColor', [1, 0.8, 0.6]); + feval(mfilename, obj, 'save_log_file'); + set(handles.behav_button, 'Enable', 'on'); + end + case 'manual_test' + log_message(handles, 'Loading manual rig test protocol...'); + dispatcher('set_protocol', 'Rigtest_singletrial'); + h=get_sphandle('owner','Rigtest_singletrial','name', 'myfig'); + for i=1:numel(h); set(value(h{i}),'Visible','Off'); end + is_running.value = 1; + log_message(handles, 'Starting manual rig test. Please complete the one-trial test.'); + dispatcher(value(behav_obj), 'Run'); + case 'create_svn_data_dir' + experimenter = args{1}; ratname = args{2}; behav_dir = args{3}; dir_name = args{4}; + dirCurrent = cd; + settings_path = fullfile(behav_dir, 'SoloData', dir_name); + exp_path = fullfile(settings_path, experimenter); + rat_path = fullfile(exp_path, ratname); + if ~isfolder(settings_path), mkdir(settings_path); system(['svn add ' dir_name]); end + if ~isfolder(exp_path), cd(settings_path); mkdir(experimenter); system(['svn add ' experimenter]); end + if ~isfolder(rat_path), cd(exp_path); mkdir(ratname); system(['svn add ' ratname]); end + cd(dirCurrent); + log_message(handles, ['Created SVN directory structure for ' ratname]); + case 'send_empty_state_machine' + state_machine_server = bSettings('get', 'RIGS', 'state_machine_server'); + server_slot = bSettings('get', 'RIGS', 'server_slot'); if isnan(server_slot), server_slot = 0; end + card_slot = bSettings('get', 'RIGS', 'card_slot'); if isnan(card_slot), card_slot = 0; end + sm = BpodSM(state_machine_server, 3333, server_slot); sm = Initialize(sm); + [inL, outL] = MachinesSection(dispatcher, 'determine_io_maps'); + sma = StateMachineAssembler('full_trial_structure'); + sma = add_state(sma, 'name', 'vapid_state_in_vapid_matrix'); + send(sma, sm, 'run_trial_asap', 0, 'input_lines', inL, 'dout_lines', outL, 'sound_card_slot', int2str(card_slot)); + + end + + case 'crash_detected' + % handles = feval(mfilename, obj, 'get_ui_handles'); + handles = value(ui_handles); + if ~strcmp(value(currentState), 'Stop') || isempty(value(behav_obj)), return; end + log_message(handles, '!!! CRASH DETECTED: Behavior system is not running. Attempting recovery...'); + try + feval(mfilename, obj, 'behav_control', 'crashed'); + catch ME + log_message(handles, sprintf('FATAL: Recovery attempt failed: %s', ME.message)); + getReport(ME, 'extended', 'hyperlinks', 'on'); + errordlg('Automatic recovery failed. Please stop the experiment manually.', 'Recovery Failed'); + end + % ========================================================================= + % SAMPLE EPHYS RECORDINGS + % ========================================================================= + case 'sample_recording_wrapper' + if strcmp(value(currentState), 'PostExperiment') + feval(mfilename, obj, 'sample_recording', 'post_session'); + else + feval(mfilename, obj, 'sample_recording', 'pre_session'); + end + + case 'sample_recording' + prefix = varargin{1}; + handles = value(ui_handles); + log_message(handles, ['--- ' upper(prefix) ' SAMPLE RECORDING INITIATED ---']); + set([handles.sample_button, handles.control_button], 'Enable', 'off', 'String', 'Sampling...'); + drawnow; + + try + software = value(recording_software); + params = get_all_parameters(handles,software); + if isempty(value(recording_controller)) + feval(mfilename, obj, 'initialize_recording_system', params, ''); + end + + if isempty(value(session_base_path)) + [session_path, ~] = construct_session_paths(handles, params); + if isempty(session_path) || ~create_session_directories(handles, params, session_path) + error('Failed to create session directories'); + end + session_base_path.value = session_path; + end + + sample_dir_name = sprintf('%s_sample_recording', prefix); + sample_save_path = fullfile(params.local_path, value(session_base_path), 'ephys', sample_dir_name); + if ~exist(sample_save_path, 'dir'), mkdir(sample_save_path); end + + software = value(recording_software); + if strcmp(software, 'OpenEphys') + feval(mfilename, obj, 'execute_openephys_sampling', sample_save_path); + else + feval(mfilename, obj, 'execute_spikeglx_sampling', sample_save_path); + end + log_message(handles, '--- SAMPLE RECORDING COMPLETE ---'); + + catch ME + log_message(handles, sprintf('ERROR during sample recording: %s', ME.message)); + rethrow(ME); + end + + % Reset button states + if strcmp(value(currentState), 'PostExperiment') + set(handles.control_button, 'Enable', 'on', 'String', 'Start New Experiment'); + feval(mfilename, obj, 'save_log_file'); + else + set(handles.control_button, 'Enable', 'on', 'String', 'Load'); + end + set(handles.sample_button, 'Enable', 'on', 'String', 'Start Sample Recording'); + + case 'execute_openephys_sampling' + save_path = varargin{1}; + controller = value(recording_controller); + params = value(current_params); + handles = value(ui_handles); + probe_settings_struct = value(probe_settings); + duration = str2double(get(handles.sample_duration, 'String')); + + controller.setParameters(params.oe_proc_node_id, 0, 'Reference', probe_settings_struct.reference); + if strcmp(probe_settings_struct.version, '1.0'), num_banks = 3; else, num_banks = 4; end + + for bank = 0:(num_banks - 1) + log_message(handles, sprintf('Recording OE Bank %d for %d seconds...', bank, duration)); + controller.setParameters(params.oe_proc_node_id, 0, 'bank', bank); pause(1); + controller.setRecordPath(params.oe_rec_node_id, save_path); pause(1); + controller.acquire(duration); pause(1); + controller.record(duration); + controller.idle(); + log_message(handles, sprintf('Finished recording Bank %d.', bank)); pause(1); + end + + if ~isempty(probe_settings_struct.imro_path) + controller.config(params.oe_proc_node_id, ['LOADIMRO ' probe_settings_struct.imro_path]); + else + controller.setParameters(params.oe_proc_node_id, 0, 'bank', probe_settings_struct.bank); + end + + case 'execute_spikeglx_sampling' + save_path = varargin{1}; + controller = value(recording_controller); + handles = value(ui_handles); + probe_settings_struct = value(probe_settings); + duration = str2double(get(handles.sample_duration, 'String')); + + controller.SetDataDir(save_path); + if strcmp(probe_settings_struct.version, '1.0'), num_banks = 3; else, num_banks = 4; end + + for bank = 0:(num_banks - 1) + log_message(handles, sprintf('Recording SGLX Bank %d for %d seconds...', bank, duration)); + % Bank selection for SpikeGLX depends on specific API calls for channel selection, not a simple 'bank' parameter + % This is a placeholder for more complex channel/bank setting logic. + % For now, we record with the currently active map. + + run_name = sprintf('sample_bank_%d_%s', bank, datestr(now, 'yyyymmdd_HHMMSS')); + controller.SetRunName(run_name); + controller.StartRun(); + pause(duration); + controller.StopRun(); + log_message(handles, sprintf('Finished recording Bank %d.', bank)); pause(1); + end + + % ========================================================================= + % PROBE GUI AND SETTINGS + % ========================================================================= + case 'open_probe_gui' + handles = value(ui_handles); + log_message(handles, 'Opening probe settings GUI...'); + probe_fig = figure('Name', 'Neuropixel Probe Settings', 'Position', [300 300 450 350], ... + 'MenuBar', 'none', 'ToolBar', 'none', 'NumberTitle', 'off', 'Resize', 'off'); + p_handles = struct(); + + p_handles.version_group = uibuttongroup(probe_fig, 'Title', 'Probe Version', 'Position', [0.05 0.78 0.9 0.2]); + uicontrol(p_handles.version_group, 'Style', 'radiobutton', 'String', 'NP 1.0 (3 Banks)', 'Position', [10 5 150 25], 'Tag', '1.0'); + uicontrol(p_handles.version_group, 'Style', 'radiobutton', 'String', 'NP 2.0 (4 Banks)', 'Position', [200 5 150 25], 'Tag', '2.0'); + + p_handles.ref_group = uibuttongroup(probe_fig, 'Title', 'Reference', 'Position', [0.05 0.55 0.4 0.2]); + uicontrol(p_handles.ref_group, 'Style', 'radiobutton', 'String', 'Tip', 'Position', [10 5 80 25], 'Tag', 'Tip'); + uicontrol(p_handles.ref_group, 'Style', 'radiobutton', 'String', 'External', 'Position', [100 5 80 25], 'Tag', 'External'); + + p_handles.bank_panel = uipanel(probe_fig, 'Title', 'Target Bank', 'Position', [0.5 0.55 0.45 0.2]); + uicontrol(p_handles.bank_panel, 'Style', 'text', 'String', 'Bank:', 'Position', [10 5 40 20]); + p_handles.bank_edit = uicontrol(p_handles.bank_panel, 'Style', 'edit', 'String', '0', 'Position', [60 5 50 25]); + + uicontrol(probe_fig, 'Style', 'text', 'String', 'Sync IMEC Slot:', 'Position', [20 160 100 20], 'HorizontalAlignment', 'right'); + p_handles.sync_slot_edit = uicontrol(probe_fig, 'Style', 'edit', 'String', '2', 'Position', [130 160 50 25]); + + uicontrol(probe_fig, 'Style', 'text', 'String', 'IMRO File:', 'Position', [20 120 100 20], 'HorizontalAlignment', 'right'); + p_handles.imro_text = uicontrol(probe_fig, 'Style', 'text', 'String', 'None selected', 'Position', [130 120 280 20], 'HorizontalAlignment', 'left'); + + uicontrol(probe_fig, 'Style', 'pushbutton', 'String', 'Browse...', 'Position', [130 85 100 30], 'Callback', {@(h,e) feval(mfilename, obj, 'browse_imro', p_handles)}); + uicontrol(probe_fig, 'Style', 'pushbutton', 'String', 'Clear IMRO', 'Position', [240 85 100 30], 'Callback', {@(h,e) feval(mfilename, obj, 'clear_imro', p_handles)}); + + uicontrol(probe_fig, 'Style', 'pushbutton', 'String', 'Apply & Close', 'Position', [250 25 180 30], 'FontWeight', 'bold', 'Callback', {@(h,e) feval(mfilename, obj, 'apply_probe_settings', p_handles)}); + probe_gui_handles.value = p_handles; + + case 'browse_imro' + p_handles = varargin{1}; + [file, path] = uigetfile('*.imro', 'Select IMRO File'); + if isequal(file, 0) || isequal(path, 0), return; + else + full_path = fullfile(path, file); + set(p_handles.imro_text, 'String', full_path); + set(findobj(p_handles.bank_panel, '-property', 'Enable'), 'Enable', 'off'); + end + + case 'clear_imro' + p_handles = varargin{1}; + set(p_handles.imro_text, 'String', 'None selected'); + set(findobj(p_handles.bank_panel, '-property', 'Enable'), 'Enable', 'on'); + + case 'apply_probe_settings' + p_handles = varargin{1}; + handles = value(ui_handles); + settings.version = get(get(p_handles.version_group, 'SelectedObject'), 'Tag'); + settings.reference = get(get(p_handles.ref_group, 'SelectedObject'), 'Tag'); + settings.bank = str2double(get(p_handles.bank_edit, 'String')); + settings.imro_path = get(p_handles.imro_text, 'String'); + settings.sync_slot = str2double(get(p_handles.sync_slot_edit, 'String')); + if strcmp(settings.imro_path, 'None selected'), settings.imro_path = ''; end + + probe_settings.value = settings; + + if ~isempty(settings.imro_path) + set(handles.target_display, 'String', 'Target: IMRO File'); + else + set(handles.target_display, 'String', ['Target: Bank ' num2str(settings.bank)]); + end + log_message(handles, 'Probe settings saved.'); + close(p_handles.ref_group.Parent); + probe_gui_handles.value = []; + + case 'apply_probe_configuration' + probe_settings = varargin{1}; + software = value(recording_software); + controller = value(recording_controller); + params = value(current_params); + handles = value(ui_handles); + if isempty(controller), return; end + + try + if strcmp(software, 'OpenEphys') + log_message(handles, sprintf('Setting OE reference to: %s', probe_settings.reference)); + controller.setParameters(params.oe_proc_node_id, 0, 'Reference', probe_settings.reference); + + % Applying Sync Slot setting + log_message(handles, sprintf('Applying Sync IMEC Slot: %d', probe_settings.sync_slot)); + % NOTE: This assumes a parameter like 'syncSlot' exists in the Neuropix-PXI plugin. + % The actual parameter name might need to be verified in the Open Ephys plugin. + % controller.setParameters(params.oe_proc_node_id, 0, 'syncSlot', probe_settings.sync_slot); + + if ~isempty(probe_settings.imro_path) + log_message(handles, sprintf('Loading IMRO file: %s', probe_settings.imro_path)); + controller.config(params.oe_proc_node_id, ['LOADIMRO ' probe_settings.imro_path]); + else + log_message(handles, sprintf('Setting bank to: %d', probe_settings.bank)); + controller.setParameters(params.oe_proc_node_id, 0, 'bank', probe_settings.bank); + end + else % SpikeGLX + log_message(handles, 'Applying SpikeGLX sync settings...'); + + %% Done Manually by the User but can be uncommented + % params_to_set = struct(); + % params_to_set.syncSourceIdx = 1; % Set sync source to PXI SMA + % params_to_set.syncImInputSlot = probe_settings.sync_slot; + % + % SetParams(controller, params_to_set); + % + % log_message(handles, 'Waiting for SpikeGLX to stabilise (3 seconds)...'); + % pause(3); + % + % log_message(handles, 'Reconnecting to SpikeGLX to reflect changes...'); + % controller = SpikeGL(params.sglx_host_ip, params.sglx_port); + % recording_controller.value = controller; % Update the handle + % + + % Verify settings + prms = GetParams(controller); + if prms.syncSourceIdx == 1 && prms.syncImInputSlot == probe_settings.sync_slot + log_message(handles, 'Success! SpikeGLX sync settings applied correctly.'); + else + log_message(handles, 'Warning: SpikeGLX settings may not have been applied. Please check SpikeGLX.'); + end + + if ~isempty(probe_settings.imro_path) + log_message(handles, 'Note: For SpikeGLX, ensure IMRO file is loaded within the SpikeGLX GUI and save as part of the meta file.'); + end + end + log_message(handles, 'Probe configuration applied successfully.'); + catch ME + log_message(handles, sprintf('Failed to apply probe settings: %s', ME.message)); + rethrow(ME); + end + % ========================================================================= + % UTILITY & OTHER ACTIONS + % ========================================================================= + case 'browse_path' + type = varargin{1}; + handles = value(ui_handles); + log_message(handles, ['Opening browse dialog for ' type ' path...']); + folder_path = uigetdir; + if folder_path ~= 0 + if strcmp(type, 'local') + set(handles.local_edit, 'String', folder_path); + feval(mfilename, obj, 'populate_and_filter_lists', 'rescan'); + elseif strcmp(type, 'central') + set(handles.central_edit, 'String', folder_path); + feval(mfilename, obj, 'populate_and_filter_lists', 'rescan'); + elseif strcmp(type, 'behav') + set(handles.behav_edit, 'String', folder_path); + end + log_message(handles, [type ' path set.']); + else, log_message(handles, 'Path selection cancelled.'); end + + case 'populate_and_filter_lists' + trigger_type = varargin{1}; % 'rescan' or 'filter' + handles = value(ui_handles); + + % --- Step 1: Scan directories if needed --- + if strcmp(trigger_type, 'rescan') + log_message(handles, 'Scanning directories for session info...'); + local_path = get(handles.local_edit, 'String'); + central_path = get(handles.central_edit, 'String'); + project_name = get(handles.proj_edit, 'String'); + + all_info = struct('folder_name', {}, 'subject_id', {}, 'rat_name', {}, 'experimenter', {}); + if ~isempty(local_path) && exist(local_path, 'dir') + all_info = [all_info; scan_for_info(fullfile(local_path, project_name, 'rawdata'))]; + end + if ~isempty(central_path) && exist(central_path, 'dir') + all_info = [all_info; scan_for_info(fullfile(central_path, project_name, 'rawdata'))]; + end + + if ~isempty(all_info) + keys = cell(numel(all_info), 1); + for i = 1:numel(all_info) + keys{i} = sprintf('%s|%s|%s', all_info(i).subject_id, all_info(i).rat_name, all_info(i).experimenter); + end + [~, ia] = unique(keys, 'stable'); + session_info_list.value = all_info(ia); + log_message(handles, sprintf('Found %d unique subjects.', numel(value(session_info_list)))); + else + session_info_list.value = []; + log_message(handles, 'No valid subject folders found.'); + end + + % Initial population of just the experimenter list + exp_options = unique([{value(session_info_list).experimenter}]); + update_popup(handles.exp_popup, exp_options, '-'); + update_popup(handles.rat_name_popup, {}, '-'); + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + return; + end + + % --- Step 2: Filter Logic triggered by user interaction --- + full_list = value(session_info_list); + source_handle = gcbo; + + % Get current selections, handling 'Add New...' dialogs + [exp_selection, exp_updated] = get_and_handle_new(handles.exp_popup, 'New Experimenter'); + if exp_updated % New exp added, reset everything below it + update_popup(handles.rat_name_popup, {}, '-'); + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + return; + end + + if source_handle == handles.exp_popup + if ~strcmp(exp_selection, '-') + filtered_by_exp = full_list(strcmpi({full_list.experimenter}, exp_selection)); + rat_options = unique([{filtered_by_exp.rat_name}]); + update_popup(handles.rat_name_popup, rat_options, '-'); + else + update_popup(handles.rat_name_popup, {}, '-'); + end + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + return; + end + + [rat_selection, rat_updated] = get_and_handle_new(handles.rat_name_popup, 'New Rat Name'); + + if source_handle == handles.rat_name_popup + if ~strcmp(rat_selection, '-') + subject_entry = full_list(strcmpi({full_list.experimenter}, exp_selection) & strcmpi({full_list.rat_name}, rat_selection)); + + if ~isempty(subject_entry) % Existing pair + sub_id = subject_entry(1).subject_id; + set(handles.sub_popup, 'String', {sub_id}, 'Value', 1); + elseif rat_updated % New Rat for this Experimenter + max_id = 0; + if ~isempty(full_list) + all_ids = str2double({full_list.subject_id}); + if ~all(isnan(all_ids)), max_id = max(all_ids(~isnan(all_ids))); end + end + sub_id = sprintf('%03d', max_id + 1); + set(handles.sub_popup, 'String', {sub_id}, 'Value', 1); + else + sub_id = '-'; + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + end + + % Populate Session list + if ~strcmp(sub_id, '-') + local_path = get(handles.local_edit, 'String'); + central_path = get(handles.central_edit, 'String'); + project_name = get(handles.proj_edit, 'String'); + subject_folder = sprintf('sub-%s_id-%s_expmtr-%s', sub_id, rat_selection, exp_selection); + session_numbers = []; + path1 = fullfile(local_path, project_name, 'rawdata', subject_folder); + if exist(path1, 'dir'), session_numbers = [session_numbers, find_session_numbers(path1)]; end + path2 = fullfile(central_path, project_name, 'rawdata', subject_folder); + if exist(path2, 'dir'), session_numbers = [session_numbers, find_session_numbers(path2)]; end + + unique_sessions = unique(session_numbers); + if ~isempty(unique_sessions) + sorted_sessions = sort(unique_sessions, 'descend'); + last_three = sorted_sessions(1:min(3, end)); + session_strings = arrayfun(@(x) sprintf('%02d', x), sort(last_three), 'UniformOutput', false); + set(handles.session_popup, 'String', [{'New'}, session_strings], 'Value', 1, 'Enable', 'on'); + else + set(handles.session_popup, 'String', {'New'}, 'Value', 1, 'Enable', 'on'); + end + else + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + end + else % rat set to '-' + set(handles.sub_popup, 'String', {'-'}, 'Value', 1); + set(handles.session_popup, 'String', {'-'}, 'Value', 1, 'Enable', 'off'); + end + end + + case 'save_log_file' + handles = value(ui_handles); + params = value(current_params); + session_path = value(session_base_path); + if isempty(session_path) + log_message(handles, 'WARNING: Cannot save log file. Session path not set.'); return; + end + log_path = fullfile(params.local_path, session_path, 'behav'); + if ~exist(log_path, 'dir') + log_message(handles, ['WARNING: Behavior folder not found. Cannot save log. Path: ' log_path]); return; + end + log_file_path = fullfile(log_path, 'session_log.txt'); + try + log_content = get(handles.log_box, 'String'); + fid = fopen(log_file_path, 'w'); + if fid == -1, error('Could not open file for writing.'); end + for i = 1:length(log_content), fprintf(fid, '%s\n', log_content{i}); end + fclose(fid); + log_message(handles, ['Log file saved successfully to: ' log_file_path]); + catch ME + log_message(handles, ['ERROR: Could not save log file. Details: ' ME.message]); + end + case 'reset_to_load_state' + handles = value(ui_handles); + currentState.value = 'Load'; + behavState.value = 'Run'; + ephysState.value = 'Run'; + set(handles.control_button, 'Enable', 'on', 'String', 'Load', 'BackgroundColor', [0.2, 0.6, 0.8]); + set(handles.sample_button, 'Enable', 'on'); + recording_controller.value = []; + behav_obj.value = []; + current_params.value = []; + session_base_path.value = ''; + log_message(handles, 'GUI reset to load state.'); + case 'close' + try + feval(mfilename, obj, 'stop_blinking'); + if ~isempty(value(recording_controller)), delete(value(recording_controller)); end + if ishandle(value(myfig)), delete(value(myfig)); end + delete_sphandle('owner', ['^@' mfilename '$']); + if ~isempty(value(behav_obj)), dispatcher(value(behav_obj),'close'); end + obj = []; + catch + if exist('myfig','var') == 1 + if ishandle(value(myfig)), delete(value(myfig)); end + else + delete(gcbf); + end + delete_sphandle('owner', ['^@' mfilename '$']); + obj = []; + end + case 'is_running' + if exist('is_running','var') == 1 + obj = logical(value(is_running)); + else + obj = 0; + end + + case 'start_blinking' + handles = value(ui_handles); + blinking_timer.value = timer('ExecutionMode', 'fixedRate', 'Period', 0.5, 'TimerFcn', {@toggle_button_color, handles.control_button}); + start(value(blinking_timer)); + case 'stop_blinking' + handles = value(ui_handles); + if ~isempty(value(blinking_timer)) && isvalid(value(blinking_timer)) + stop(value(blinking_timer)); + delete(value(blinking_timer)); + blinking_timer.value = []; + end + set(handles.control_button, 'BackgroundColor', [1, 0.4, 0.4]); + otherwise + error('Unknown action: %s', action); +end +return; +%% ======================================================================= +% PARAMETER AND VALIDATION FUNCTIONS +% ======================================================================= +function params = get_all_parameters(handles,software) + params.protocol_name = get(handles.protocol_edit, 'String'); + params.do_manual_test = get(handles.manual_test, 'Value'); + + % Get values from popup menus + exp_items = get(handles.exp_popup, 'String'); + params.experimenter = exp_items{get(handles.exp_popup, 'Value')}; + + rat_items = get(handles.rat_name_popup, 'String'); + params.rat_name = rat_items{get(handles.rat_name_popup, 'Value')}; + + sub_items = get(handles.sub_popup, 'String'); + params.subject_id = sub_items{get(handles.sub_popup, 'Value')}; + + ses_items = get(handles.session_popup, 'String'); + params.session_id = ses_items{get(handles.session_popup, 'Value')}; + + popup_string = get(handles.distribution_popup,'String'); + params.stim_distribution = popup_string{get(handles.distribution_popup,'Value')}; + params.behav_path = get(handles.behav_edit, 'String'); + params.project_name = get(handles.proj_edit, 'String'); + params.local_path = get(handles.local_edit, 'String'); + params.central_path = get(handles.central_edit, 'String'); + + if strcmp(software, 'OpenEphys') + params.oe_gui_ip = get(handles.oe_ip_edit, 'String'); + params.oe_proc_node_id = get(handles.oe_proc_edit, 'String'); + params.oe_rec_node_id = get(handles.oe_rec_edit, 'String'); + else + params.sglx_host_ip = get(handles.sglx_host_edit, 'String'); + params.sglx_port = str2double(get(handles.sglx_port_edit, 'String')); + params.sglx_probe_index = str2double(get(handles.sglx_probe_edit, 'String')); + end +function is_valid = validate_all_inputs(params,handles,software) + is_valid = false; + required_fields = {'protocol_name', 'rat_name', 'behav_path', 'project_name', 'subject_id', 'local_path', 'session_id'}; + for i = 1:length(required_fields) + field_val = params.(required_fields{i}); + if ~isfield(params, required_fields{i}) || isempty(field_val) || strcmp(field_val, '-') + msg = sprintf('Field "%s" must have a valid selection before loading.', strrep(required_fields{i}, '_', ' ')); + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + end + if ~get(handles.cb_ephys, 'Value') && ~get(handles.cb_behav, 'Value') && ~get(handles.cb_anat, 'Value') && ~get(handles.cb_funcimg, 'Value') + msg = 'At least one subfolder must be selected.'; + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + if strcmp(software, 'OpenEphys') + if isempty(params.oe_gui_ip) || isempty(params.oe_proc_node_id) || isempty(params.oe_rec_node_id) + msg = 'Open Ephys connection parameters cannot be empty.'; + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + else + if isempty(params.sglx_host_ip) || isnan(params.sglx_port) || isnan(params.sglx_probe_index) + msg = 'SpikeGLX connection parameters cannot be empty or non-numeric.'; + log_message(handles, sprintf('ERROR: %s', msg)); errordlg(msg, 'Input Error'); + return; + end + end + is_valid = true; +%% ======================================================================= +% PATH AND DIRECTORY FUNCTIONS +% ======================================================================= +function [session_base, recording_path] = construct_session_paths(handles, params) + if isempty(params.experimenter) || strcmp(params.experimenter, '-') + subject_name = sprintf('sub-%s_id-%s', params.subject_id, params.rat_name); + else + subject_name = sprintf('sub-%s_id-%s_expmtr-%s', params.subject_id, params.rat_name, params.experimenter); + end + subject_base_path = fullfile(params.project_name, 'rawdata', subject_name); + local_subject_dir = fullfile(params.local_path, subject_base_path); + central_subject_dir = fullfile(params.central_path, subject_base_path); + + if strcmp(params.session_id, 'New') + new_ses_num = max(find_max_session_number(local_subject_dir), find_max_session_number(central_subject_dir)) + 1; + log_message(handles, sprintf('Last session found: %d. Creating new session: %d.', new_ses_num - 1, new_ses_num)); + else + new_ses_num = str2double(params.session_id); + log_message(handles, sprintf('Using existing session number: %d.', new_ses_num)); + end + + session_datetime_str = char(datetime('now', 'Format', 'yyyyMMdd''T''HHmmss')); + session_folder_name = sprintf('ses-%02d_date-%s_dtype-ephys', new_ses_num, session_datetime_str); + session_base = fullfile(subject_base_path, session_folder_name); + recording_path = fullfile(params.local_path, session_base, 'ephys'); + log_message(handles, ['New session path determined: ' session_base]); +function max_ses = find_max_session_number(base_path) + max_ses = 0; if ~exist(base_path, 'dir'), return; end + dir_contents = dir(fullfile(base_path, 'ses-*')); + if isempty(dir_contents), return; end + session_numbers = []; + for i = 1:length(dir_contents) + if dir_contents(i).isdir + token = regexp(dir_contents(i).name, '^ses-(\d+)', 'tokens'); + if ~isempty(token), session_numbers(end+1) = str2double(token{1}{1}); end + end + end + if ~isempty(session_numbers), max_ses = max(session_numbers); end +function success = create_session_directories(handles, params,session_base_path) + success = false; + subfolders = {}; + if get(handles.cb_ephys, 'Value'), subfolders{end+1} = 'ephys'; end + if get(handles.cb_behav, 'Value'), subfolders{end+1} = 'behav'; end + if get(handles.cb_anat, 'Value'), subfolders{end+1} = 'anat'; end + if get(handles.cb_funcimg, 'Value'), subfolders{end+1} = 'funcimg'; end + try + for i = 1:length(subfolders) + local_target_path = fullfile(params.local_path, session_base_path, subfolders{i}); + log_message(handles, ['Creating local directory: ' local_target_path]); + if ~exist(local_target_path, 'dir'), mkdir(local_target_path); end + end + log_message(handles, 'All selected local directories created successfully.'); + success = true; + catch ME + msg = sprintf('Failed to create directories: %s', ME.message); + log_message(handles, ['ERROR: ' msg]); errordlg(msg, 'Directory Error'); + end +%% ======================================================================= +% HELPER & UTILITY FUNCTIONS +% ======================================================================= +function info = scan_for_info(search_path) + info = struct('folder_name', {}, 'subject_id', {}, 'rat_name', {}, 'experimenter', {}); + if ~exist(search_path, 'dir'), return; end + + dir_contents = dir(search_path); + if isempty(dir_contents), return; end + + pattern = '^sub-([^_]+)_id-([^_]+)_expmtr-([^_]+)$'; % More robust pattern + + for i = 1:length(dir_contents) + if dir_contents(i).isdir + folder_name = dir_contents(i).name; + tokens = regexp(folder_name, pattern, 'tokens'); + + if ~isempty(tokens) + new_entry.folder_name = folder_name; + new_entry.subject_id = tokens{1}{1}; + new_entry.rat_name = tokens{1}{2}; + new_entry.experimenter = tokens{1}{3}; + info(end+1, 1) = new_entry; + end + end + end +function session_nums = find_session_numbers(subject_path) + session_nums = []; + if ~exist(subject_path, 'dir'), return; end + dir_contents = dir(fullfile(subject_path, 'ses-*')); + for i = 1:length(dir_contents) + if dir_contents(i).isdir + token = regexp(dir_contents(i).name, '^ses-(\d+)', 'tokens'); + if ~isempty(token), session_nums(end+1) = str2double(token{1}{1}); end + end + end +function update_popup(h, options, selection) + if isempty(options), options = {}; end + % --- MODIFIED: Removed 'All' from options --- + new_string = [{'-'}, unique(options), {'Add New...'}]; + + % Find the index for the current selection in the new list + val = find(strcmp(new_string, selection), 1); + if isempty(val), val = 1; end % Default to first item '-' if not found + % --- END MODIFICATION --- + + set(h, 'String', new_string, 'Value', val); +function [selection, updated] = get_and_handle_new(h, prompt_title) + updated = false; + items = get(h, 'String'); + val = get(h, 'Value'); + selection = items{val}; + + if strcmp(selection, 'Add New...') + new_val_cell = inputdlg(['Enter new value for ' prompt_title], prompt_title, [1 50]); + if ~isempty(new_val_cell) && ~isempty(new_val_cell{1}) + new_val = new_val_cell{1}; + % Check if it already exists (case-insensitive) + existing_idx = find(strcmpi(items, new_val), 1); + if ~isempty(existing_idx) + set(h, 'Value', existing_idx); + else + items{end} = new_val; % Replace 'Add New...' with the new value + items{end+1} = 'Add New...'; % Add it back at the end + set(h, 'String', items, 'Value', numel(items)-1); + end + updated = true; + selection = new_val; + else + set(h, 'Value', 1); % Revert to first item '-' if cancelled + selection = '-'; + end + end +function log_message(handles,logStr) + try + if ~isfield(handles, 'log_box') || ~isvalid(handles.log_box), return; end + current_text = get(handles.log_box, 'String'); + timestamp = char(datetime('now', 'Format', '[HH:mm:ss] ')); + new_line = [timestamp, logStr]; + new_text = [current_text; {new_line}]; + set(handles.log_box, 'String', new_text, 'Value', numel(new_text)); + drawnow; + catch + fprintf('%s: %s\n', char(datetime('now', 'Format', '[HH:mm:ss] ')), logStr); + end +% --- ADDED: Helper function to check for data in a directory --- +function has_data = does_dir_have_data(path_to_check) + has_data = false; + if exist(path_to_check, 'dir') + dir_contents = dir(path_to_check); + % Check if there are more than 2 entries (i.e., more than '.' and '..') + if numel(dir_contents) > 2 + has_data = true; + end + end +function toggle_button_color(~, ~, button_handle) + if ~isvalid(button_handle), return; end + currentColor = get(button_handle, 'BackgroundColor'); + if isequal(currentColor, [1, 0.4, 0.4]), set(button_handle, 'BackgroundColor', [1, 0.7, 0.4]); + else, set(button_handle, 'BackgroundColor', [1, 0.4, 0.4]); end +function commit_to_svn(handles, file_path_data,file_path_settings, root_dir) + +if isempty(file_path_data), return; end + if isempty(file_path_settings), return; end + [pname_data, fname_data, ~] = fileparts(file_path_data); + [pname_settings, fname_settings, ~] = fileparts(file_path_settings); + + configFilePath = fullfile(root_dir,'PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat'); + if ~exist(configFilePath, 'file') + log_message(handles, ['SVN commit failed: Password config file not found at ' configFilePath]); + return; + end + load(configFilePath, 'svn_user', 'svn_password'); + logmsg_data = char(strcat('automated commit from GUI for data and settings for ', {' '} ,fname_data,{'@'})); + % current_dir = cd; + cd(pname_data); + add_cmd_data = char(strcat('svn add', {' '}, fname_data, '.mat',{'@'})); + system(add_cmd_data); + commit_cmd_data = sprintf('svn ci --username="%s" --password="%s" -m "%s"', svn_user, svn_password, logmsg_data); + [status, ~] = system(commit_cmd_data); + cd(pname_settings); + add_cmd_settings = char(strcat('svn add', {' '}, fname_settings, '.mat',{'@'})); + system(add_cmd_settings); + logmsg_setting = char(strcat('automated commit from GUI for data and settings for ', {' '} ,fname_settings,{'@'})); + commit_cmd_setting = sprintf('svn ci --username="%s" --password="%s" -m "%s"', svn_user, svn_password, logmsg_setting); + [status, ~] = system(commit_cmd_setting); + + if status == 0 + log_message(handles, ['SVN commit successful for ' fname_data]); + else + log_message(handles, ['SVN commit FAILED for ' fname_data '.']); + end + + cd(fullfile(root_dir,'ExperPort')); + %% ======================================================================= +% DOCUMENTATION AND USAGE EXAMPLES +% ======================================================================= +function display_usage_help() +% DISPLAY_USAGE_HELP - Display usage instructions and examples +% +% This function provides comprehensive usage documentation for the GUI + fprintf('\n=== Neuropixels Recording & Behavior Controller Usage Guide ===\n\n'); + + fprintf('1. INITIALIZATION:\n'); + fprintf(' OpenEphys_Neuroblueprint_GUI(''init'');\n\n'); + + fprintf('2. WORKFLOW:\n'); + fprintf(' a) Select recording software (Open Ephys or SpikeGLX)\n'); + fprintf(' b) Configure behavior settings (protocol, experimenter, rat)\n'); + fprintf(' c) Set up NeuroBlueprint data paths\n'); + fprintf(' d) Configure probe settings (version, reference, bank/IMRO)\n'); + fprintf(' e) Set recording software connection parameters\n'); + fprintf(' f) Click "Load" to initialize systems\n'); + fprintf(' g) Click "Run" to start experiment\n'); + fprintf(' h) Click "Stop" to end experiment and save data\n\n'); + + fprintf('3. PROBE CONFIGURATION:\n'); + fprintf(' - Supports Neuropixels 1.0 (3 banks) and 2.0 (4 banks)\n'); + fprintf(' - Reference options: Tip or External\n'); + fprintf(' - Bank selection: Manual bank number or IMRO file\n'); + fprintf(' - Pre/post-session sampling across all banks\n\n'); + + fprintf('4. DATA ORGANIZATION:\n'); + fprintf(' - Follows NeuroBlueprint format\n'); + fprintf(' - Structure: project/rawdata/subject/session/datatype/\n'); + fprintf(' - Automatic session numbering\n'); + fprintf(' - SVN integration for version control\n\n'); + + fprintf('5. RECORDING SOFTWARE SUPPORT:\n'); + fprintf(' Open Ephys:\n'); + fprintf(' - HTTP API control\n'); + fprintf(' - Real-time parameter adjustment\n'); + fprintf(' - Acquisition and recording control\n\n'); + fprintf(' SpikeGLX:\n'); + fprintf(' - MATLAB SDK integration\n'); + fprintf(' - Run name management\n'); + fprintf(' - Recording enable/disable control\n\n'); + + fprintf('6. ERROR HANDLING:\n'); + fprintf(' - Comprehensive validation of inputs\n'); + fprintf(' - Automatic crash recovery for behavior protocols\n'); + fprintf(' - Detailed logging with timestamps\n'); + fprintf(' - Graceful fallbacks for system failures\n\n'); + + fprintf('For more information, see function documentation within the code.\n'); + fprintf('================================================================\n\n'); + diff --git a/ExperPort/Modules/@StateMachineAssembler/StateMachineAssembler.m b/ExperPort/Modules/@StateMachineAssembler/StateMachineAssembler.m index 899e7d9d..5c891d83 100644 --- a/ExperPort/Modules/@StateMachineAssembler/StateMachineAssembler.m +++ b/ExperPort/Modules/@StateMachineAssembler/StateMachineAssembler.m @@ -17,7 +17,7 @@ % % Written by Carlos Brody October 2006; edited by Sebastien Awwad 2007,2008 - +% Additional Input lines added December 2012 CDB function [sma] = StateMachineAssembler(varargin) @@ -36,30 +36,13 @@ sma_struct = []; end; - - fnames = {'name', 'detectorFunctionName', 'inputNumber', 'happId'}; - hs = { - 'Cin', 'line_in', 1, 1 ; ... - 'Cout', 'line_out', 1, 2 ; ... - 'Lin', 'line_in', 2, 3 ; ... - 'Lout', 'line_out', 2, 4 ; ... - 'Rin', 'line_in', 3, 5 ; ... - 'Rout', 'line_out', 3, 6 ; ... - 'Chi', 'line_high', 1, 7 ; ... - 'Clo', 'line_low', 1, 8 ; ... - 'Lhi', 'line_high', 2, 9 ; ... - 'Llo', 'line_low', 2, 10 ; ... - 'Rhi', 'line_high', 3, 11 ; ... - 'Rlo', 'line_low', 3, 12 ; ... - }; - - default_happSpec = cell2struct(hs, fnames, 2); - - + pairs = { ... 'default_DOut' 0 ; ... - 'default_happSpec' default_happSpec ; ... + 'default_happSpec' [] ; ... 'use_happenings' 0 ; ... + 'n_input_lines' 3 ; ... + 'line_names' 'CLRABDEFGHIJKL' ; ... }; singles = { ... 'no_dead_time_technology', 'inputarg', 'no_dead_time_technology', '' ; ... @@ -68,6 +51,58 @@ }; parseargs(varargin, pairs, singles); + if n_input_lines ~= 3 && ~use_happenings, + error('Can use n_input_lines different to 3 only if using happenings'); + end; + + if isempty(default_happSpec) %#ok<*NODEF> + fnames = {'name', 'detectorFunctionName', 'inputNumber', 'happId'}; + happnames = {'line_in', 'line_out', 'line_high', 'line_low'}; + happabbrev = {'in', 'out', 'hi', 'lo'}; + hid = 0; + hs = {}; + + for li = 1:3, + for hni = 1:2, + hid = hid + 1; + hs = [hs ; ... + {[line_names(li) happabbrev{hni}], happnames{hni}, li, hid}]; + end; + end; + for li = 1:3, + for hni = 1:2, + hid = hid + 1; + hs = [hs ; ... + {[line_names(li) happabbrev{hni+2}], happnames{hni+2}, li, hid}]; + end; + end; + + for li=4:n_input_lines, + for hni = 1:4, + hid = hid + 1; + hs = [hs ; ... + {[line_names(li) happabbrev{hni}], happnames{hni}, li, hid}]; + end; + end; + + %Chuck's code to try to remap inputs + inputs = bSettings('get','INPUTLINES','all'); + for i = 1:size(hs,1) + inputnum = find(strcmp(inputs(:,1),hs{i,1}(1)) == 1,1,'first'); + if isempty(inputnum) + error(['can''t find input channel ',hs{i,1}(1),' in INPUTLINES ']); + elseif numel(inputnum) > 1 + error(['found more than one instance of input channel ',hs{i,1}(1),' in INPUTLINES ']); + end + + hs{i,3} = inputs{inputnum,2}; + end + %end Chuck's code + + default_happSpec = cell2struct(hs, fnames, 2); + end; + + if isempty(default_DOut), error('you passed in an empty matrix as a default_DOut -- can''t do that'); end; @@ -75,41 +110,56 @@ % error('sorry, default_DOut different to zero not yet supported'); end; - default_input_map = { ... - 'Cin' 1 ; ... - 'Cout' 2 ; ... - 'Lin' 3 ; ...in - 'Lout' 4 ; ... - 'Rin' 5 ; ... - 'Rout' 6 ; ... - 'Tup' 7 ; ... - }; + ncols = 7; % Keeping track of total number of state matrix columns + input_line_names = line_names; + default_input_map = cell(0, 2); + for k=1:n_input_lines, + if k > numel(input_line_names), + error('Sorry, trying to have more input lines than I can handle!'); + end; + default_input_map = [default_input_map ; ... + {[input_line_names(k) 'in'] k*2-1 ; ... + [input_line_names(k) 'out'] k*2 ; ... + }]; + end; + ncols = n_input_lines*2+1; + default_input_map = [default_input_map ; {'Tup' ncols}]; + + % default_input_map = { ... +% 'Cin' 1 ; ... +% 'Cout' 2 ; ... +% 'Lin' 3 ; ... +% 'Lout' 4 ; ... +% 'Rin' 5 ; ... +% 'Rout' 6 ; ... +% 'Tup' ncols ; ... +% }; + default_self_timer_map = { ... - 'Timer' 8 ; ... + 'Timer' ncols+1 ; ... }; default_output_map = { ... - 'DOut' 9 ; ... - 'SoundOut' 10 ; ... + 'DOut' ncols+2 ; ... + 'SoundOut' ncols+3 ; ... }; - + ncols = ncols+3; - sma = struct( ... + sma = struct( ... + 'n_input_lines', n_input_lines, ... 'input_map', {default_input_map}, ... 'self_timer_map', {default_self_timer_map}, ... 'output_map', {default_output_map}, ... - 'use_happenings', 1, ... % added by Viktor - 'n_input_lines', 3, ... % added by Viktor - ...%'use_happenings', use_happenings, ... + 'use_happenings', use_happenings, ... 'happSpec', default_happSpec, ... 'happList', {cell(0, 1)}, ... 'state_name_list', {cell(0,3)}, ... 'current_state', 0, ... - 'states', zeros(0, 10), ... + 'states', zeros(0, ncols), ... 'default_actions', {cell(0, 1)}, ... 'current_iti_state', 0, ... - 'iti_states', zeros(0, 10), ... + 'iti_states', zeros(0, ncols), ... 'default_iti_actions', {cell(0, 1)}, ... 'dio_sched_wave_cols', 8, ... 'sched_waves', struct('name', {}, 'id', {}, 'in_column', {}, ... @@ -150,7 +200,6 @@ sma = class(sma, 'StateMachineAssembler'); return; end; - %clear classes %mod by Viktor sma = class(sma, 'StateMachineAssembler'); diff --git a/ExperPort/Modules/@dispatcher/RunningSection.m b/ExperPort/Modules/@dispatcher/RunningSection.m index ffdffd2a..65db96a3 100644 --- a/ExperPort/Modules/@dispatcher/RunningSection.m +++ b/ExperPort/Modules/@dispatcher/RunningSection.m @@ -31,6 +31,7 @@ SoloParamHandle(obj, 'stop_after_next_update', 'value', 0); SoloParamHandle(obj, 'stopping_process_completed', 'value', 1); SoloFunctionAddVars('runrats','func_owner','@runrats','ro_args','stopping_process_completed'); % This is a bit unpleasant. We give ro access to the flag denoting the stop process as complete to runrats so that runrats can wait on it using a timer. :P + SoloFunctionAddVars('NeuropixelNeuroblueprint','func_owner','@NeuropixelNeuroblueprint','ro_args','stopping_process_completed'); % This is a bit unpleasant. We give ro access to the flag denoting the stop process as complete so that NeuropixelNeuropblueprint can wait on it using a timer. :P SoloFunctionAddVars('TowerWaterDelivery','func_owner','@TowerWaterDelivery','ro_args','stopping_process_completed'); % This is a bit unpleasant. We give ro access to the flag denoting the stop process as complete to runrats so that runrats can wait on it using a timer. :P set(get_ghandle(RunButton), 'FontSize', 20); % (defined by GetSoloFunctionArgs) @@ -283,6 +284,9 @@ if runrats('is_running'); runrats('crashed',me); Running.value = 0; + elseif NeuropixelNeuroblueprint('is_running') + NeuropixelNeuroblueprint('crash_detected'); + Running.value = 0; else rethrow(me); end diff --git a/ExperPort/Modules/@runrats/runrats.m b/ExperPort/Modules/@runrats/runrats.m index 619e4bc2..cd3fd2db 100644 --- a/ExperPort/Modules/@runrats/runrats.m +++ b/ExperPort/Modules/@runrats/runrats.m @@ -45,14 +45,15 @@ display('test'); %If we are starting from a forced reboot from runrats itself we %need to make sure the do_on_reboot.bat file is set back to nothing - try %#ok - p = pwd; - cd('\ratter\Rigscripts') - - !del do_on_reboot.bat - !copy nothing.bat do_on_reboot.bat /Y - cd(p); - end + + % try %#ok + % p = pwd; + % cd('\ratter\Rigscripts') + % + % !del do_on_reboot.bat + % !copy nothing.bat do_on_reboot.bat /Y + % cd(p); + % end %If a dispatcher is already open, let's close it so we don't ever have %more than 1 @@ -113,38 +114,17 @@ 'NumberTitle','off','Name','RunRats V2.2 ','Resize','off',... 'closerequestfcn', [mfilename '(''close'')']); SoloParamHandle(obj,'myfig', 'value',fig); + try - % Assuming myfig is your original figure handle - originalFig = value(myfig); - - % Create a new figure that will stay on top - newFig = figure; - set(newFig, 'Position', get(originalFig, 'Position')); % Set the new figure's position to cover the original one - - % Optionally, you can set the Name property of the new figure to distinguish it - set(newFig, 'Name', ['New Figure: ', get(originalFig, 'Name')]); - - % Perform operations on newFig as needed - % For demonstration, let's just pause here - pause(10); % Keep the new figure open for 10 seconds - - % Close the new figure after the operation is done - close(newFig); - catch + set(value(myfig), 'WindowStyle', 'modal'); + pause(0.1); + + catch %#ok + disp('WARNING: Failed to keep runrats on top'); end - - % try - % jf=get(value(myfig), 'JavaFrame'); - % pause(0.1); - % javaMethod('setAlwaysOnTop', jf.fFigureClient.getWindow, 1); - % %delete or change - sharbat - % catch %#ok - % disp('WARNING: Failed to keep runrats on top'); - % end - - + %Create the non-gui variables we will need SoloParamHandle(obj,'RigID', 'value',bSettings('get','RIGS','Rig_ID')); %The rig ID SoloParamHandle(obj,'RatSch', 'value',cell(10,1)); %The rats that run in this rig in session order @@ -159,6 +139,7 @@ SoloParamHandle(obj,'do_full_restart', 'value',1); %1 if we want to restart matlab between each session SoloParamHandle(obj,'SafetyMode', 'value',''); %empty for no safety, B for before, A for after SoloParamHandle(obj,'OptoPlugColor', 'value',[]); %figure handle for opto plug color panel + SoloParamHandle(obj,'Rerun_AfterCrash', 'value',1); % should load the previous protocol if runrats/dispatcher crashed if ~exist('phys','var'); SoloParamHandle(obj,'phys','value',0); end @@ -166,6 +147,10 @@ SoloParamHandle(obj, 'curProtocol', 'value', ''); %Current Protocol SoloParamHandle(obj,'schedDay','value',[]); + % For Live Webcam Feed + % SoloParamHandle(obj,'Camera_Fig_window','value',[]); + % SoloParamHandle(obj,'Camera_Obj','value',[]); + % SoloParamHandle(obj,'Camera_Image','value',[]); %Let's make the menus try @@ -312,7 +297,7 @@ %prepare to load, let's do it. runrats(obj,'update_exprat'); runrats(obj,'check_rig_flushed'); - runrats(obj,'live_loop'); + % runrats(obj,'live_loop'); case 'send_empty_state_machine' @@ -693,7 +678,7 @@ end; cd(dirRat); - update_folder(pwd,'svn'); + % update_folder(pwd,'svn'); cd(dirCurrent); runrats(obj,'enable_all'); @@ -764,7 +749,29 @@ %runrats(obj,'updatelog','update_exprat'); +%% SPECIAL CASE ADDED BY ARPIT FOR CLICK AND SELECT +% doesn't effect the running of the other cases/functions + case 'update exp_rat_userclick' + + ExpMenu.value = varargin{1}; + runrats(obj,'update_ratmenu',varargin{2}); + %If the rat has changed, let's update it. + if ~strcmp(value(RatMenu),varargin{2}) + runrats(obj,'update_rat',value(InLiveLoop)); %#ok + else + %Stay in the loop if we haven't changed anything + InLiveLoop.value = 1; + end + + runrats(obj,'begin_load_protocol'); + + % Added to send the details about the experimenter and rat + case 'exp_rat_names' + varargout{1} = value(ExpMenu); + varargout{2} = value(RatMenu); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% case 'update_tech_instructions' %% update_tech_instructions %Posts the tech instructions for the active rat on the screen @@ -1052,11 +1059,18 @@ case 'update_schedule' %% update_schedule + %Here we grab the current schedule for this rig - if ~isnan(value(RigID)); - [rats slots] = bdata(['select ratname, timeslot from scheduler where date="',... - datestr(now,'yyyy-mm-dd'),'" and rig=',num2str(value(RigID))]); + % if ~isnan(value(RigID)); + % [rats slots] = bdata(['select ratname, timeslot from scheduler where date="',... + % datestr(now,'yyyy-mm-dd'),'" and rig=',num2str(value(RigID))]); + % end + + % Updated by Arpit - instead of taking the date, we will look for + % if the rats which are in training in scheduler table. + if ~isnan(value(RigID)) + [rats,slots] = bdata(['select ratname, timeslot from scheduler where in_training="1" and rig=',num2str(value(RigID))]); end %Let's populate the 5 training sessions (changed by sharbat, with @@ -1092,10 +1106,10 @@ CS = value(CurrSession); - [ratSCH slots] = bdata(['select ratname, timeslot from scheduler where date="',... + [ratSCH, slots] = bdata(['select ratname, timeslot from scheduler where date="',... datestr(now,'yyyy-mm-dd'),'"']); ratSES = bdata(['select ratname from sessions where sessiondate="',datestr(now,'yyyy-mm-dd'),'"']); - [ratSS ST] = bdata(['select ratname, starttime from sess_started where sessiondate="',... + [ratSS, ST] = bdata(['select ratname, starttime from sess_started where sessiondate="',... datestr(now,'yyyy-mm-dd'),'"']); %Let's cycle through each of the 6 training sessions and see if the @@ -1303,19 +1317,26 @@ StatusBar.value='Loading protocol and settings. Please be patient!'; pause(0.1); + %%%%%%%%%%%% ARPIT %%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % NOT REQUIRED AS INSTEAD OF SVN WE ARE USING GITHUB + %Let's also make sure we have the most up-to-date code - CurrDir = pwd; - pname = bSettings('get','GENERAL','Main_Code_Directory'); - if ~isempty(pname) && ischar(pname) - update_folder(pname,'svn'); - end + + % CurrDir = pwd; + % pname = bSettings('get','GENERAL','Main_Code_Directory'); + % if ~isempty(pname) && ischar(pname) + % update_folder(pname,'svn'); + % end + % + % %And finally we make sure the protocols are up-to-date + % pname = bSettings('get','GENERAL','Protocols_Directory'); + % if ~isempty(pname) && ischar(pname) + % update_folder(pname,'svn'); + % end + % cd(CurrDir); - %And finally we make sure the protocols are up-to-date - pname = bSettings('get','GENERAL','Protocols_Directory'); - if ~isempty(pname) && ischar(pname) - update_folder(pname,'svn'); - end - cd(CurrDir); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Let's get the protocol for the rat and load it CurrProtocol.value = getProtocol(value(ExpMenu),value(RatMenu)); %#ok @@ -1422,19 +1443,44 @@ sendstarttime(eval(value(CurrProtocol))); %#ok catch %#ok disp('ERROR: Failed to add the start time to the MySQL table.'); - end; + end %Let's make everything unresponsive for 5 more seconds to prevent %double clicks from stopping the session pause(5); %Start raspberry pi_camera - try - disp('trying camera') - start_camera(value(RigID),value(RatMenu),value(CurrProtocol),'start') - catch - disp('failed to start pi camera') - end + % try + % disp('trying camera') + % start_camera(value(RigID),value(RatMenu),value(CurrProtocol),'start') + % catch + % disp('failed to start pi camera') + % end + + % If using USB Webcam, then try using it instead using Bonsai + % try + % disp('Connecting to USB HD Camera') + % webcam_connected = webcamlist; + % webcam_idx = find(contains(webcam_connected,'USB')); + % if ~isempty(webcam_idx) % USB Camera connected + % cam = webcam(webcam_connected{webcam_idx}); + % fig = figure('NumberTitle','off','MenuBar','none'); + % fig.Name = 'My Camera'; + % ax = axes(fig); + % frame = snapshot(cam); + % im = image(ax,zeros(size(frame),'uint8')); + % axis(ax,'image'); + % preview(cam,im) + % Camera_Fig_window.value = fig; + % Camera_Obj.value = cam; + % Camera_Image.value = im; + % else + % disp('No USB camera connected') + % end + % catch + % disp('failed to connect to USB camera') + % end + %Enable the Multi button so the user can stop the session enable(Multi); @@ -1446,7 +1492,13 @@ set(get_ghandle(Multi),'enable','on'); set(get_ghandle(Safety),'visible','off','string',''); end + + % Let start recording the videos by sending the command to protocol + % itself instead of the plugin bonsaicamera + protobj=eval(value(CurrProtocol)); + feval(value(CurrProtocol), protobj, 'start_recording'); + % Now ready to run with dispatcher dispatcher(value(dispobj),'Run'); %#ok @@ -1464,13 +1516,25 @@ runrats(obj,'updatelog','runend'); runrats(obj,'disable_all'); set(get_ghandle(Multi),'String','Saving...','Fontsize',32); + %Stop raspberry pi_camera - try - disp('stopping camera') - start_camera(value(RigID),value(RatMenu),value(CurrProtocol),'stop') - catch - disp('failed to stop pi camera') - end + % try + % disp('stopping camera') + % start_camera(value(RigID),value(RatMenu),value(CurrProtocol),'stop') + % catch + % disp('failed to stop pi camera') + % end + + % Stop USB Camera + % try + % closePreview(value(Camera_Obj)) + % clear(value(Camera_Image)) + % clear(value(Camera_Obj)); + % close(value(Camera_Fig_window)); + % disp('USB camera stopped') + % catch + % disp('failed to stop USB camera') + % end %Stop dispatcher and wait for it to respond dispatcher(value(dispobj),'Stop'); %#ok @@ -1543,7 +1607,7 @@ %Let's reset the Multi button and hop back in the live loop set(get_ghandle(Multi),'ForegroundColor',[0,0,0],'BackgroundColor',... [1,1,0.4],'string','Load Protocol','FontSize',24); - InLiveLoop.value = 1; + InLiveLoop.value = 0; % Changed by Arpit as the timer function is preventing 'flush' to run runrats(obj,'enable_all'); %We need to turn RunRats back to live mode @@ -1553,6 +1617,15 @@ end set(get_ghandle(UpdateMode),'String','Live Update On','BackgroundColor',[0.6 1 0.6],'ForegroundColor',[0 0 0]); + %%%%%%%%%%%%%%Removing the part of full restart %%%%%%%%%%%%%% + %%%%%%%%%%%%% ARPIT %%%%%%%%%%%%%%%%%%%%% + + do_full_restart.value = 0; + p = bSettings('get','GENERAL','Main_Code_Directory'); + cd(p); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %Another option is to now kill MatLab completely and restart %runrats. This ensures windows don't pile up, and code can get %updated before each session @@ -1588,7 +1661,7 @@ end %And now we hop back in the loop - runrats(obj,'live_loop'); + % runrats(obj,'live_loop'); end @@ -1644,16 +1717,24 @@ RunningSection(value(dispobj),'RunStop'); %#ok dispatcher('set_protocol',''); - %Now we can email the rat's owner a crash report + %Now we can email the rat's owner a detailed crash report try %#ok + error_message = lsterr.message; + error_message = strrep(error_message, '\', '\\'); + error_message = strrep(error_message, '"', '\"'); message = cell(0); message{end+1} = ['Rig ',num2str(value(RigID)),' crashed while running ',value(RatMenu),' at ',datestr(now,13)]; %#ok message{end+1} = ''; - message{end+1} = lsterr.message; + message{end+1} = lsterr.identifier; + message{end+1} = error_message; + file_path = lsterr.stack(1).file; + message{end+1} = strrep(file_path, '\', '\\'); + message{end+1} = lsterr.stack(1).name; + message{end+1} = num2str(lsterr.stack(1).line); message{end+1} = ''; - + for i = 1:length(lsterr.stack) - message{end+1} = [lsterr.stack(i).name,' at ',num2str(lsterr.stack(i).line)]; %#ok + message{end+1} = ['Line ' num2str(lsterr.stack(i).line) ', File ' lsterr.stack(i).file ', Function ' lsterr.stack(i).name]; %#ok end IP = get_network_info; @@ -1665,7 +1746,7 @@ %setpref('Internet','SMTP_Server','brodyfs2.princeton.edu'); %setpref('Internet','E_mail',['RunRats',datestr(now,'yymm'),'@Princeton.EDU']); - set_email_sender + % set_email_sender owner = bdata(['select contact from rats where ratname="',value(RatMenu),'"']); if ~isempty(owner) @@ -1676,22 +1757,73 @@ for i = 1:length(cms)-1 exp = owner(cms(i)+1:cms(i+1)-1); - sendmail([exp,'@princeton.edu'],[value(RatMenu),' Crashed'],message); + % sendmail([exp,'@ucl.ac.uk'],[value(RatMenu),' Crashed'],message); + gmail_SMTP([exp,'@ucl.ac.uk'],[value(RatMenu),' Crashed'],message); end end end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %% Modified/Added by Arpit + %Let's update the MySQL table to indicate a crash has happened - id = bdata(['select sessid from sess_started where ratname="',value(RatMenu),... - '" and was_ended=0 and sessiondate="',datestr(now,'yyyy-mm-dd'),'"']); - if ~isempty(id) - id = id(end); - bdata('call mark_crashed("{S}")',id); + %Since this sql table is missing, the same information can be + % obtained from sess_started table where was_ended will stay 0. + + % id = bdata(['select sessid from sess_started where ratname="',value(RatMenu),... + % '" and was_ended=0 and sessiondate="',datestr(now,'yyyy-mm-dd'),'"']); + % if ~isempty(id) + % id = id(end); + % bdata('call mark_crashed("{S}")',id); + % end + + %% Lets try and rerun the protocol and only do it if the animal is training + try + is_rat_training = bdata(['select in_training from rats where experimenter="',value(ExpMenu),'" and ratname="', value(RatMenu),'"']); + catch + is_rat_training = 1; % if couldn't find then try rerun of the protocol + end + + if value(Rerun_AfterCrash) == 1 && is_rat_training == 1 + runrats(obj,'rerun'); end + case 'rerun' % called in 'crash' and made by combining 'begin_load_protocol' , 'load_protocol' and 'run' + InLiveLoop.value = 0; + runrats(obj,'disable_all'); + + set(get_ghandle(Multi),'string','Unloading...','fontsize',28); + x = ''; + try x = dispatcher('get_protocol_object'); end %#ok + if ~isempty(x) + %There was a protocol previously open. Let's not trust that + %their close section is working properly. + try %#ok + %rigscripts does not exist currently, try Protocols (ask + %Athena) -sharbat + p = bSettings('get','GENERAL','Main_Code_Directory'); + p(strfind(p,'ExperPort'):end) = ''; + p = [p,'Rigscripts']; + cd(p); + if ispc == 1 + system('restart_runrats.bat'); + end + end + end + + dispatcher('set_protocol',''); + + % Loading the protocol and setting file + runrats(obj,'load_protocol') + + % Running the protocol + runrats(obj,'run') + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% case 'crash_cleanup' %% crash_cleanup %The tech has acknowledged the crash. Let's jump back in the loop @@ -1701,7 +1833,7 @@ [1,1,0.4],'string','Load Protocol','FontSize',24); InLiveLoop.value = 1; - runrats(obj,'live_loop'); + % runrats(obj,'live_loop'); case 'updatelog' @@ -1732,7 +1864,7 @@ try %#ok %setpref('Internet','SMTP_Server','brodyfs2.princeton.edu'); %setpref('Internet','E_mail',['RunRats',datestr(now,'yymm'),'@Princeton.EDU']); - set_email_sender + % set_email_sender message{1} = ['Load failed for ',value(RatMenu)]; %#ok if strcmp(varargin{1},'no settings') @@ -1760,7 +1892,8 @@ cms = find(contact == ','); for i = 1:length(cms)-1 email = contact(cms(i)+1:cms(i+1)-1); - sendmail([email,'@princeton.edu'],subject,message); + % sendmail([email,'@princeton.edu'],subject,message); + gmail_SMTP([email,'@ucl.ac.uk'],subject,message); end end end @@ -1768,7 +1901,7 @@ - case 'is_running', + case 'is_running' %% is_running %If the Multi button exists, runrats has been loaded if exist('Multi','var'), obj = 1; else obj = 0; end @@ -1794,30 +1927,7 @@ else varargout{1} = ''; end - - - case 'send_empty_state_machine' - %% send_empty_state_machine - %Sends an empty state matrix. This allows us to toggle DIO lines - - state_machine_server = bSettings('get','RIGS','state_machine_server'); - - server_slot = bSettings('get','RIGS','server_slot'); - if isnan(server_slot); server_slot = 0; end - - card_slot = bSettings('get', 'RIGS', 'card_slot'); - if isnan(card_slot); card_slot = 0; end - - sm = BPodSM(state_machine_server, 3333,server_slot); - sm = Initialize(sm); - - [inL outL] = MachinesSection(dispatcher,'determine_io_maps'); - - sma = StateMachineAssembler('full_trial_structure'); - sma = add_state(sma,'name','vapid_state_in_vapid_matrix'); - - send(sma,sm,'run_trial_asap',0,'input_lines',inL,'dout_lines',outL,'sound_card_slot', int2str(card_slot)); - + case 'close' %% close @@ -1849,7 +1959,7 @@ otherwise warning('Unknown action " %s" !', action);%#ok -end; +end return; @@ -1879,7 +1989,7 @@ setdate{xi}=r(1:7); %#ok else % not a file we want, give it a really early date, 2000: setdate{xi}='000101a'; %#ok - end; + end end [srtdsets, sdi]=sort(setdate); @@ -1918,7 +2028,7 @@ function update_folder(pname,vn) if failed1 == 1 || failed2 == 1 %setpref('Internet','SMTP_Server','brodyfs2.princeton.edu'); %setpref('Internet','E_mail',['RunRats',datestr(now,'yymm'),'@Princeton.EDU']); - set_email_sender + % set_email_sender if pname(1) ~= filesep; pname = [filesep,pname]; end if pname(end) ~= filesep; pname = [pname,filesep]; end @@ -1953,7 +2063,8 @@ function update_folder(pname,vn) cms = find(ctemp == ','); for i = 1:length(cms)-1 email = ctemp(cms(i)+1:cms(i+1)-1); - sendmail([email,'@princeton.edu'],['SVN Cleanup FAILED on Rig ',rig],message); + % sendmail([email,'@princeton.edu'],['SVN Cleanup FAILED on Rig ',rig],message); + gmail_SMTP([email,'@ucl.ac.uk'],['SVN Cleanup FAILED on Rig ',rig],message); end end @@ -1979,7 +2090,8 @@ function update_folder(pname,vn) cms = find(ctemp == ','); for i = 1:length(cms)-1 email = ctemp(cms(i)+1:cms(i+1)-1); - sendmail([email,'@ucl.ac.uk'],subject,message); + % sendmail([email,'@ucl.ac.uk'],subject,message); % + gmail_SMTP([email,'@ucl.ac.uk'],subject,message); % Using gmail instead of brody smtp end end end @@ -1989,4 +2101,34 @@ function update_folder(pname,vn) +function gmail_SMTP(recipient_email,subject_line,email_body) + +smtp_server = 'smtp.gmail.com'; +smtp_port = '587'; % Use TLS +email_address = 'behav.akramilab@gmail.com'; +email_password = 'fakc mdbw woef lqmq'; % IMPORTANT: this is set in setting of gmail + +% recipient_email = 'arpit.agarwal@ucl.ac.uk'; +% subject_line = 'Test Email from MATLAB via Gmail'; +% email_body = 'This email was sent using Gmail SMTP from MATLAB.'; + +% --- Set MATLAB Email Preferences --- +setpref('Internet','SMTP_Server',smtp_server); +setpref('Internet','E_mail',email_address); +setpref('Internet','SMTP_Username',email_address); +setpref('Internet','SMTP_Password',email_password); + +% Set server properties +props = java.lang.System.getProperties; +props.setProperty('mail.smtp.auth','true'); +props.setProperty('mail.smtp.starttls.enable','true'); +props.setProperty('mail.smtp.port',smtp_port); + +% --- Send the Email --- +try + sendmail(recipient_email, subject_line, email_body); + disp('Email sent successfully via Gmail SMTP.'); +catch ME + disp(['Error sending email: ' ME.message]); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/NetworkControl.m b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/NetworkControl.m new file mode 100644 index 00000000..5f339aa9 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/NetworkControl.m @@ -0,0 +1,179 @@ +% MIT License + +% Copyright (c) 2021 Open Ephys + +% Permission is hereby granted, free of charge, to any person obtaining a copy +% of this software and associated documentation files (the "Software"), to deal +% in the Software without restriction, including without limitation the rights +% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +% copies of the Software, and to permit persons to whom the Software is +% furnished to do so, subject to the following conditions: + +% The above copyright notice and this permission notice shall be included in all +% copies or substantial portions of the Software. + +% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +% SOFTWARE. + +classdef NetworkControl + + %NETWORKCONTROL - A class that communicates with the Open Ephys NetworkEvents plugin + % NETWORKCONTROL - See: https://github.com/open-ephys-plugins/NetworkEvents for more info. + % + %It can be used to start/stop acquisition, start/stop recording, and + %send TTL events to an instance of the Open Ephys GUI, either locally + %or over a network connection. + % + % SYNTAX: + % gui = NetworkControl( '127.0.0.1', 5556 ) + % + % PROPERTIES: + % ipAddress - IP Adress of machine running OpenEphys + % port - port as indicated by active NetworkEvents plugin in OpenEphys + % url - full tcp address + % context - ZMQ context running in REQ-REP patter + % socket - the socket for this context + % + % EXAMPLES: + % gui.startAcquisition() + % Response: StartedAcquisition + % + % gui.isAcquiring() + % True + % + % gui.record() + % Response: StartedRecording + % + % gui.isRecording() + % True + % + % gui.sendTTL(5, 1) + % Response: TTLHandled: Channel=5 on=1 + % + % gui.stopRecording() + % Response: StoppedRecording + % + % gui.isRecording() + % False + % + % gui.stopAcquisition() + % Response: StoppedAcquisition + + properties + + ipAddress + port + + url + context + socket + + end + + methods + + function self = NetworkControl(varargin) + + if nargin == 0 + self.ipAddress = '127.0.0.1'; + self.port = 5556; + elseif nargin == 2 + self.ipAddress = varargin{1}; + self.port = varargin{2}; + else + fprintf("Error: NetworkControl takes either 0 or 2 input parameters\n"); + return; + end + + self.url = ['tcp://' self.ipAddress ':' num2str(self.port)]; + + self.context = zmq.core.ctx_new(); + self.socket = zmq.core.socket(self.context, 'ZMQ_REQ'); + + zmq.core.connect(self.socket, self.url); + + end + + function delete(self) + + zmq.core.disconnect(self.socket, self.url); + zmq.core.close(self.socket); + + end + + function startAcquisition(self) + + zmq.core.send(self.socket, uint8('StartAcquisition')); + reply = char(zmq.core.recv(self.socket)); + + end + + function stopAcquisition(self) + + zmq.core.send(self.socket, uint8('StopAcquisition')); + reply = char(zmq.core.recv(self.socket)); + + end + + function record(self) + + zmq.core.send(self.socket, uint8('StartRecord')); + reply = char(zmq.core.recv(self.socket)); + + end + + function startRecording(self) + + self.record(); + + end + + function stopRecording(self) + + zmq.core.send(self.socket, uint8('StopRecord')); + reply = char(zmq.core.recv(self.socket)); + + end + + function reply = isRecording(self) + + zmq.core.send(self.socket, uint8('IsRecording')); + reply = char(zmq.core.recv(self.socket)) == '1'; + + end + + function reply = isAcquiring(self) + + zmq.core.send(self.socket, uint8('IsAcquiring')); + reply = char(zmq.core.recv(self.socket)) == '1'; + + end + + function sendTTL(self, channel, state) + + zmq.core.send(['TTL Channel=' num2str(channel) ' on=' num2str(state)]); + reply = char(zmq.core.recv(self.socket)); + + end + + function wait(self, timeInSeconds) + + pause(timeInSeconds); + + end + + function getResponse(self) + + zmq.core.send(self.socket, uint8('StopAcquisition')); + reply = char(zmq.core.recv(self.socket)); + + end + + end + +end \ No newline at end of file diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/NetworkEventsConsole.m b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/NetworkEventsConsole.m new file mode 100644 index 00000000..992e3982 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/NetworkEventsConsole.m @@ -0,0 +1,47 @@ +%Command line version of NetworkControl + +menu = [ ... +'\nAvailable commands: \n' ... +'--------------------\n' ... +'startAcquisition\n' ... +'stopAcquisition\n' ... +'startRecording\n' ... +'stopRecording \n\n' ... +'Available queries: \n' ... +'-------------------\n' ... +'IsAcquiring\n' ... +'IsRecording\n\n' ... +'Send a TTL event: \n' ... +'---------------------\n' ... +'TTL Channel=1 on=1\n' ... +'TTL Channel=1 on=0\n\n' ... +'To exit, enter "q", "quit", or "exit"\n\n\n' ... +]; + +networkControl = NetworkControl(); +prompt = menu; + +while true + + cmd = input(prompt, 's'); + + if any(strcmp({'q','quit','exit','quit()'}, cmd)) + break; + end + + try + nargin = length(strsplit(cmd)); + if nargin == 3 %TLL event + ttlMessage = strsplit(cmd); + channel = strsplit(ttlMessage{2}, '='); + state = strsplit(ttlMessage{3}, '='); + cmd = ['sendTTL(', channel{2}, ',', state{2}, ')'] + end + eval(['networkControl.' cmd]); + prompt = '\n'; + catch ME %unrecognized command + fprintf("Invalid command!\n\n\n"); + prompt = menu; + end + +end \ No newline at end of file diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/OpenEphysHTTPServer.m b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/OpenEphysHTTPServer.m new file mode 100644 index 00000000..891207e9 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/OpenEphysHTTPServer.m @@ -0,0 +1,554 @@ +% MIT License +% +% Copyright (c) 2021 Open Ephys +% +% Permission is hereby granted, free of charge, to any person obtaining a copy +% of this software and associated documentation files (the "Software"), to deal +% in the Software without restriction, including without limitation the rights +% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +% copies of the Software, and to permit persons to whom the Software is +% furnished to do so, subject to the following conditions: +% +% The above copyright notice and this permission notice shall be included in all +% copies or substantial portions of the Software. +% +% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +% SOFTWARE. + +classdef OpenEphysHTTPServer < handle + + properties (Constant) + end + + properties + + address + options + + end + + methods + + function self = OpenEphysHTTPServer(host, port) + + self.address = strcat('http://', host, ':', num2str(port)); + self.options = weboptions(... + 'MediaType', 'application/json',... + 'RequestMethod', 'put'); + + end + + end + + methods + + function resp = send(self, varargin) + +% Send a request to the server. +% +% Parameters +% ---------- +% endpoint : String +% The API endpoint for the request. +% Must begin with "/api/" +% payload (optional): Dictionary +% The payload to send with the request. +% +% If a payload is specified, a PUT request +% will be used; otherwise it will be a GET request. + + + endpoint = varargin{1}; + if nargin > 2 + payload = varargin{2}; + end + + try + + if nargin == 2 + resp = webread(strcat(self.address, endpoint)); + else + resp = webwrite(strcat(self.address, endpoint), payload, self.options); + end + + catch ME +% TODO: Catch matlab equivalents of these +% except requests.exceptions.Timeout: +% # Maybe set up for a retry, or continue in a retry loop +% print("Timeout") +% except requests.exceptions.TooManyRedirects: +% # Tell the user their URL was bad and try a different one +% print("Bad URL") +% except requests.exceptions.RequestException as e: +% # Open Ephys server needs to be enabled +% print("Open Ephys HTTP Server likely not enabled") + + resp = "GUI was closed!"; + end + + end + + function resp = load(self, path) + +% Load a configuration file. +% +% Parameters +% ---------- +% path : String +% The path to the configuration file. + + payload = struct('path', path); + + resp = self.send('/api/load', payload); + pause(1); + + end + + function processors = getProcessorList(self) + +% Returns all available processors in the GUI's Processor List + + data = self.send('/api/processors/list'); + processors = string(char({data.processors.name})); + + end + + function processors = getProcessors(self, varargin) + +% Get the list of processors. + +% Parameters +% ---------- +% filterByName : String (Optional) +% Filter the list by processor name. + + data = self.send('/api/processors'); + processors = data.processors; + if nargin > 1 + indices = cellfun(@(v)strcmp(v,varargin{1}),{processors.name}); + processors = processors(indices); + end + + end + + function resp = clearSignalChain(self) + +% Clear the signal chain. + + resp = self.send('/api/processors/clear'); + + end + + function resp = addProcessor(self, name, varargin) + +% Add a processor to the signal chain. +% +% Parameters +% ---------- +% name : String +% The name of the processor to add (e.g. "Record Node") +% source : Integer +% The 3-digit processor ID of the source (e.g. 101) +% dest : Integer +% The 3-digit processor ID of the destination (e.g. 102) + + endpoint = '/api/processors/add'; + payload = struct('name', name); + +% If only processor name is specified, set source to most recently added processor + if nargin == 2 + existingProcessors = self.getProcessors(); + if ~isempty(existingProcessors) + index = find([existingProcessors.id] == max([existingProcessors.id])); + mostRecentProcessor = existingProcessors(index); + payload.source_id = mostRecentProcessor.id; + end + elseif nargin > 2 + payload.source_id = varargin{1}; + if nargin == 4 + payload.dest_id = varargin{2}; + end + end + + resp = self.send(endpoint, payload); + + end + + + function resp = deleteProcessor(self, id) + +% Delete a processor. +% +% Parameters +% ---------- +% processor_id : Integer +% The 3-digit processor ID (e.g. 101) +% + endpoint = '/api/processors/delete'; + payload = struct('id', id); + + resp = self.send(endpoint, payload); + + end + + + function resp = getParameters(self, processorId, streamIndex) + +% Get parameters for a stream. +% +% Parameters +% ---------- +% processorId : Integer +% The 3-digit processor ID (e.g. 101) +% streamIndex : Integer +% The index of the stream (e.g. 0). +% + endpoint = strcat('/api/processors/', num2str(processorId), '/streams/', num2str(streamIndex), '/parameters'); + resp = self.send(endpoint).parameters; + + end + + function resp = setParameters(self, processorID, streamIndex, paramName, value) + +% Update a parameter value +% +% Parameters +% ---------- +% processorID : Integer +% The 3-digit processor ID (e.g. 101) +% streamIndex : Integer +% The index of the stream (e.g. 0) +% paramName : String +% The parameter name (e.g. low_cut) +% value : Any +% The parameter value (must match the parameter type). +% Hint: Float parameters must be sent with a decimal +% included (e.g. 1000.0 instead of 1000) +% + endpoint = strcat('/api/processors/', num2str(processorID), '/streams/', num2str(streamIndex), '/parameters/', paramName); + +% TO FIX:matlab automatically casts doubles to int if no +% integers after the decimal point of a float. + if isa(value, 'double') + payload = struct('value', [], 'class', {'double'}); + payload.value = value + 0.00000000001; + else + payload = struct('value', value); + end + resp = self.send(endpoint, payload); + + end + + + function resp = getRecordingInfo(self, varargin) + +% Get recording information. +% +% Parameters +% ---------- +% key : String +% The key to get. +% + + data = self.send('/api/recording'); + if nargin == 1 + resp = data; + elseif isfield(data, varargin{1}) + resp = data.(varargin{1}); + else + resp = "Invalid key"; + end + end + + function resp = setParentDirectory(self, path) + +% Set the parent directory. +% +% Parameters +% ---------- +% path : String +% The path to the parent directory. + + payload = struct('parent_directory', path); + data = self.send('/api/recording', payload); + resp = data; + + end + + function resp = setPrependText(self, text) + +% Set the prepend text. +% +% Parameters +% ---------- +% text : String +% The text to prepend. +% + payload = struct('prepend_text', text); + data = self.send('/api/recording', payload); + resp = data; + + end + + function resp = setBaseText(self, text) + +% Set the base text. +% +% Parameters +% ---------- +% text : String +% The text to base name of the recording directory (see GUI docs). +% + payload = struct('base_text', text); + data = self.send('/api/recording', payload); + resp = data; + + end + + function resp = setAppendText(self, text) + +% Set the append text. +% +% Parameters +% ---------- +% text : String +% The text to append. +% + payload = struct('append_text', text); + data = self.send('/api/recording', payload); + resp = data; + + end + + + function resp = setStartNewDirectory(self) + +% Set if GUI should start a new directory for the next recording. + + payload = struct('start_new_directory', 'true'); + data = self.send('/api/recording', payload); + resp = data; + + end + + function resp = setFileReaderPath(self, nodeId, filePath) + +% Set the file path. + +% Parameters +% ---------- +% nodeId : Integer +% The node ID. +% filePath : String +% The file path. + + endpoint = strcat('/api/processors/', num2str(nodeId), '/config'); + payload = struct('text', strcat("file=", num2str(filePath))); + data = self.send(endpoint, payload); + + resp = data; + + end + + function resp = setFileReaderIndex(self, nodeId, fileIndex) + +% Set the file index. +% +% Parameters +% ---------- +% nodeId : Integer +% The node ID. +% fileIndex : Integer +% The file index. + + endpoint = strcat('/api/processors/', num2str(nodeId), '/config'); + payload = struct('text', strcat("index=", num2str(fileIndex))); + data = self.send(endpoint, payload); + + resp = data; + + end + + function resp = setRecordEngine(self, nodeId, engine) + +% Set the record engine for a record node. +% +% Parameters +% ---------- +% nodeId : Integer +% The node ID. +% engine: Integer +% The record engine index. + + endpoint = strcat('/api/processors/', num2str(nodeId), '/config'); + payload = struct('text', strcat("engine=", engine)); + data = self.send(endpoint, payload); + + resp = data; + + end + + function resp = setRecordPath(self, nodeId, directory) + +% Set the record path for a Record Node +% +% Parameters +% ---------- +% nodeId: Integer +% The node ID. +% directory : String +% The record path. + + endpoint = strcat('/api/recording/', num2str(nodeId)); + payload = struct('parent_directory', directory); + data = self.send(endpoint, payload); + + resp = data; + + end + function resp = getStatus(self) + +% Returns the current status of the GUI (IDLE, ACQUIRE, or RECORD) + + data = self.send('/api/status'); + + resp = data; + + end + + function resp = acquire(self, varargin) + +% Start acquisition. +% +% Parameters +% ---------- +% duration : Integer (optional) +% The acquisition duration in seconds. If given, the +% GUI will acquire data for the specified interval +% and then stop. +% +% By default, acquisition will continue until it +% is stopped by another command. + + payload = struct('mode', 'ACQUIRE'); + data = self.send('/api/status', payload); + + if nargin == 2 + payload = struct('mode', 'IDLE'); + pause(varargin{1}); + data = self.send('/api/status', payload); + end + + resp = data; + + end + + function resp = record(self, varargin) + +% Start recording. +% +% Parameters +% ---------- +% duration : Integer (optional) +% The record duration in seconds. If given, the +% GUI will record data for the specified interval +% and then stop. +% +% By default, recoridng will continue until it +% is stopped by another command. + + payload = struct('mode', 'RECORD'); + data = self.send('/api/status', payload); + + if nargin == 2 + payload = struct('mode', 'IDLE'); + pause(varargin{1}); + data = self.send('/api/status', payload); + end + + resp = data; + + end + + function resp = idle(self, varargin) + +% Stop acquisition/recording. +% +% Parameters +% ---------- +% duration : Integer (optional) +% The idle duration in seconds. If given, the +% GUI will idle for the specified interval +% and then return to its previous state. +% +% By default, this command will stop +% acquisition/recording and return immediately. + + mode = self.getStatus().mode; + + payload = struct('mode', 'IDLE'); + data = self.send('/api/status', payload); + + if nargin == 2 + payload = struct('mode', mode); + pause(varargin{1}); + data = self.send('/api/status', payload); + end + + resp = data; + + end + + function resp = message(self, message) + +% Broadcast a message to all processors during acquisition +% +% Parameters +% ---------- +% message : String +% The message to send. + + payload = struct('text', message); + data = self.send('/api/message', payload); + + resp = data; + + end + + function resp = config(self, nodeId, message) + +% Send a configuration message to a specific processor. +% +% Parameters +% ---------- +% nodeId : Integer +% The 3-digit processor ID (e.g. 101) +% message : String +% The message to send. + + endpoint = strcat('/api/processors/', num2str(nodeId), '/config'); + payload = struct('text', message); + data = self.send(endpoint, payload); + + resp = data; + + end + + function resp = quit(self) + +% Quit the GUI + + payload = struct('command', 'quit'); + data = self.send('/api/window', payload); + + resp = data; + end + + end + +end \ No newline at end of file diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/README.md b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/README.md new file mode 100644 index 00000000..4d26a663 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/OpenEphys_MATLAB-SDK/README.md @@ -0,0 +1,240 @@ +# `open_ephys.control` + +This module makes it possible to control the [Open Ephys GUI](https://open-ephys.org/gui) via Matlab, either running locally or over a network. + +## OpenEphysHTTPServer + +Starting in GUI v0.6.0 we recommend using the OpenEphysHTTPServer to control the GUI remotely. + +### Usage + +Create an instance of the OpenEphysHTTPServer class: + +```matlab +gui = OpenEphysHTTPServer('127.0.0.1', 37497) +``` + +Get the processor list (all available processors to use in a signal chain) + +```matlab +gui.getProcessorList() +``` + +Get the processors in the current signal chain + +```matlab +gui.getProcessors() +``` + +Clear the current signal chain + +```matlab +gui.clearSignalChain() +``` + +Add a processor to the signal chain (source and destination are optional, if not included will add to end of signal chain) + +```matlab +gui.addProcessor(processorName, source, destination) +``` + +Delete a processor from the signal chain + +```matlab +gui.deleteProcessor(processorId) +``` + +Get the parameters for a processor + +```matlab +gui.getParameters(processorId, streamIdx) +``` + +Set the parameters for a processor + +```matlab +gui.setParameter(processorId, streamIdx, paramName, value) +``` + +Get recording information + +```matlab +gui.getRecordingInfo(key) +``` + +Set parent recording directory + +```matlab +gui.setParentDirectory(path) +``` + +Set prepend text + +```matlab +gui.setPrependText(text) +``` + +Set base text + +```matlab +gui.setBaseText(text) +``` + +Set append text + +```matlab +gui.setAppendText(text) +``` + +Set start new directory flag (starts a new directory for the next recording) + +```matlab +gui.setStartNewDirectory() +``` + +Set file path to load for a FileReader + +```matlab +gui.setFileReaderPath(nodeId, path) +``` + +Set file index to load for a FileReader + +```matlab +gui.setFileReaderIndex(nodeId, index) +``` + +Set record engine + +```matlab +gui.setRecordEngine(nodeId, engine) +``` + +Set record path + +```matlab +gui.setRecordPath(nodeId, directory) +``` + +Get GUI status (acquiring, recording or idle) + +```matlab +gui.getStatus() +``` + +Start acquisition (duration is optional) + +```matlab +gui.acquire(duration) +``` + +Stop recording (duration is optional) + +```matlab +gui.record(duration) +``` + +Stop acquisition/recording (duration is optional) + +```matlab +gui.idle(duration) +``` + +Send a text message to all processors in the signal chain + +```matlab +gui.message(text) +``` + +Quit the GUI + +```matlab +gui.quit() +``` + +## NetworkControl + +### Usage + +Your GUI's signal chain must include a [NetworkEvents](https://open-ephys.github.io/gui-docs/User-Manual/Plugins/Network-Events.html) plugin in order for this module to work. + +To use the control module in Matlab: + +- [Download ZeroMQ](https://zeromq.org/download/) for your specific platform +- Edit the paths to your zmq library file locations in control/matlab-zmq/config.m +- Run control/matlab-zmq/make.m to generate the required .mex files + +Note: There are known issues when generating mex files for Matlab 2017+. It is possible to generate the mex files with an older version of Matlab and copy them to a newer version of Matlab. + +See this issue for more information: +https://github.com/fagg/matlab-zmq/issues/40#issuecomment-1030198530 + +## Usage + +### Initialization + +To control a GUI instance running on the same machine, simply enter: + +```matlab +gui = NetworkControl() +``` + +To specify a custom IP address or port number, use: + +```matlab +gui = NetworkControl('10.127.50.1', 2000) +``` + +### Starting and stopping acquisition + +To start acquisition, enter: + +```matlab +gui.startAcquisition() +``` + +To stop acquisition, enter: + +```matlab +gui.stopAcquisition() +``` + +To query acquisition status, use: + +```matlab +gui.isAcquiring() +``` + +### Starting and stopping recording + +To start recording, enter: + +```matlab +gui.startRecording() +``` + +To stop recording while keeping acquisition active, enter: + +```matlab +gui.stopRecording() +``` + +To query recording status, use: + +```matlab +gui.isRecording() +``` + +### Sending TTL events + +To send a TTL "ON" event, enter: + +```matlab +gui.sendTTL(5, 1) %channel = 5, state = 1 +``` + +To send a TTL "OFF" event, enter: + +```matlab +gui.sendTTL(5, 0) %channel = 5, state = 0 +``` diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/.editorconfig b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/.editorconfig new file mode 100644 index 00000000..f85932e7 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/.editorconfig @@ -0,0 +1,8 @@ + +root = true + +[*] +indent_style = space +indent_size = 4 +tab_width = 4 + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/.gitignore b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/.gitignore new file mode 100644 index 00000000..23a42b9e --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/.gitignore @@ -0,0 +1,61 @@ +# Compiled object files +*.slo +*.lo +*.o +*.obj + +# Compiled dynamic libraries +*.dylib + +# Compiled static libraries +*.lai +*.la +*.a + +# Precompiled headers +*.gch +*.pch + +# Qt generated +build-* +Makefile +Makefile.* +moc_*.cpp +qrc_*.cpp +ui_*.h +*.moc + +# Project files +*.pro.user +*.pro.user.* +*.qmlproject.user +*.qmlproject.user.* + +# Windows-specific +Win32/ +Debug/ +Release/ +Thumbs.db +desktop.ini +*.idb +*.ncb +*.aps +*.sln +*.suo +*.vcproj +*.vcproj.* + +# Mac-specific +.DS_Store + +# Log files +*.log + +# Editor temporary files +*~ +*.autosave + +# Other +*.bin +*.exe + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ChkConn.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ChkConn.m new file mode 100644 index 00000000..9d2776ff --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ChkConn.m @@ -0,0 +1,34 @@ +% THIS FUNCTION IS PRIVATE AND SHOULD NOT BE CALLED BY OUTSIDE CODE! +% +function [sm] = ChkConn( sm ) + + if( sm.in_chkconn ) + return; + end + + sm.in_chkconn = 1; + + if( sm.handle == -1 ) + + sm.handle = CalinsNetMex( 'create', sm.host, sm.port ); + + if( isempty( CalinsNetMex( 'connect', sm.handle ) ) ) + error( 'ChkConn: Unable to connect to server.' ); + end + + sm.ver = DoQuery( sm, 'GETVERSION' ); + + else + + ok = CalinsNetMex( 'sendstring', sm.handle, sprintf( 'NOOP\n' ) ); + + if( isempty( ok ) || isempty( CalinsNetMex( 'readline', sm.handle ) ) ) + + if( isempty( CalinsNetMex( 'connect', sm.handle ) ) ) + error( 'ChkConn: Still unable to connect to server.' ); + end + end + end + + sm.in_chkconn = 0; +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Close.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Close.m new file mode 100644 index 00000000..b82b660a --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Close.m @@ -0,0 +1,11 @@ +% myobj = Close( myobj ) +% +% Close the network connection to SpikeGLX and release +% associated MATLAB resources. +% +function [s] = Close( s ) + + CalinsNetMex( 'disconnect', s.handle ); + CalinsNetMex( 'destroy', s.handle ); + s.handle = -1; +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ConsoleHide.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ConsoleHide.m new file mode 100644 index 00000000..595f352f --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ConsoleHide.m @@ -0,0 +1,8 @@ +% myobj = ConsoleHide( myobj ) +% +% Hide SpikeGLX console window to reduce screen clutter. +% +function [s] = ConsoleHide( s ) + + s = DoSimpleCmd( s, 'CONSOLEHIDE' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ConsoleShow.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ConsoleShow.m new file mode 100644 index 00000000..3cc8633e --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ConsoleShow.m @@ -0,0 +1,8 @@ +% myobj = ConsoleShow( myobj ) +% +% Show SpikeGLX console window. +% +function [s] = ConsoleShow( s ) + + s = DoSimpleCmd( s, 'CONSOLESHOW' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Contents.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Contents.m new file mode 100644 index 00000000..3a411987 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Contents.m @@ -0,0 +1,623 @@ +% SYNOPSIS +% -------- +% +% The @SpikeGL class is a MATLAB object with methods to access the +% SpikeGLX program via TCP/IP. SpikeGLX and MATLAB can run on the +% same machine (via loopback socket address 127.0.0.1 and port 4142) +% or across a network. +% +% This class provides extensive control over a running SpikeGLX process: +% starting and stopping a run, setting parameters, calling the Par2 and +% SHA1 tools, and so on. +% +% Users of this class merely need to construct an instance of a @SpikeGL +% object and all network communication with the SpikeGLX process is handled +% automatically. +% +% The network socket handle is used with the 'CalinsNetMex' mexFunction, +% which is a helper mexFunction that does all the actual socket +% communications for this class (since MATLAB lacks native network +% support). +% +% Instances of @SpikeGL are weakly stateful: merely keeping a handle to a +% network socket. It is ok to create and destroy several of these objects. +% Each network connection cleans up after itself after 10 seconds of +% inactivity. By the way, if your script has pauses longer than 10 seconds, +% and you reuse a handle that has timed out, the handle will automatically +% reestablish a connection and the script will likely continue without +% problems, but a warning will appear in the Command Window reflecting +% the timeout. Such warnings have a warningid, so you can suppress them +% by typing >> warning( 'off', 'CalinsNetMex:connectionClosed' ). +% +% EXAMPLES +% -------- +% +% my_s = SpikeGL; % connect to SpikeGLX running on local machine +% +% prms = GetParams( my_s ); % retrieve run params +% +% SetParams( my_s, struct('niMNChans1','0:5','niDualDevMode','false',...) ); +% +% StartRun( my_s ); % starts data acquisition run using last-set params +% +% StopRun( my_s ); % stop run and clean up +% +% (js, ip) +% -------- +% +% The two integer values (js, ip) select a data stream. +% js: stream type: {0=nidq, 1=obx, 2=imec-probe}. +% ip: substream: {0=nidq (if js=0), 0+=which OneBox or imec probe}. +% Examples (js, ip): +% (0, 0) = nidq. // for nidq, ip is arbitrary but zero by convention +% (1, 4) = obx4. +% (2, 7) = imec7. +% Note: ip has range [0..np-1], where, np is queried using GetStreamNP(). +% +% FUNCTION REFERENCE +% ------------------ +% +% myobj = SpikeGL() +% myobj = SpikeGL( host ) +% myobj = SpikeGL( host, port ) +% +% Construct a new @SpikeGL instance and immediately attempt +% a network connection. If omitted, the defaults for host and +% port are {'localhost, 4142}. +% +% myobj = Close( myobj ) +% +% Close the network connection to SpikeGLX and release +% associated MATLAB resources. +% +% myobj = ConsoleHide( myobj ) +% +% Hide SpikeGLX console window to reduce screen clutter. +% +% myobj = ConsoleShow( myobj ) +% +% Show the SpikeGLX console window. +% +% params = EnumDataDir( myobj, i ) +% +% Retrieve a listing of files in the ith data directory. +% Get main data directory by setting i=0 or omitting it. +% +% [daqData,headCt] = Fetch( myObj, js, ip, start_samp, max_samps, channel_subset, downsample_ratio ) +% +% Get MxN matrix of stream data. +% M = samp_count, MIN(max_samps,available). +% N = channel count... +% Data are int16 type. +% If filtered IM stream buffers are enabled, you may fetch from them with js=-2. +% Fetching starts at index start_samp. +% channel_subset is an optional vector of specific channels to fetch [a,b,c...], or, +% [-1] = all acquired channels, or, +% [-2] = all saved channels. +% downsample_ratio is an integer; return every Nth sample (default = 1). +% Also returns headCt = index of first sample in matrix. +% +% [daqData,headCt] = FetchLatest( myObj, js, ip, max_samps, channel_subset, downsample_ratio ) +% +% Get MxN matrix of the most recent stream data. +% M = samp_count, MIN(max_samps,available). +% N = channel count... +% Data are int16 type. +% If filtered IM stream buffers are enabled, you may fetch from them with js=-2. +% channel_subset is an optional vector of specific channels to fetch [a,b,c...], or, +% [-1] = all acquired channels, or, +% [-2] = all saved channels. +% downsample_ratio is an integer; return every Nth sample (default = 1). +% Also returns headCt = index of first sample in matrix. +% +% dir = GetDataDir( myobj, i ) +% +% Get ith global data directory. +% Get main data directory by setting i=0 or omitting it. +% +% params = GetGeomMap( myobj, ip ) +% +% Get geomMap for given logical imec probe. +% Returned as a struct of name/value pairs. +% Header fields: +% head_partNumber ; string +% head_numShanks +% head_shankPitch ; microns +% head_shankWidth ; microns +% Channel 5, e.g.: +% ch5_s ; shank index +% ch5_x ; microns from left edge of shank +% ch5_z ; microns from center of tip-most electrode row +% ch5_u ; used-flag (in CAR operations) +% +% [APgain,LFgain] = GetImecChanGains( myobj, ip, chan ) +% +% Returns the AP and LF gains for given probe and channel. +% +% params = GetParams( myobj ) +% +% Get the most recently used run parameters. +% These are a struct of name/value pairs. +% +% params = GetParamsImecCommon( myobj ) +% +% Get imec parameters common to all enabled probes. +% Returned as a struct of name/value pairs. +% +% params = GetParamsImecProbe( myobj, ip ) +% +% Get imec parameters for given logical probe. +% Returned as a struct of name/value pairs. +% +% params = GetParamsOneBox( myobj, ip, slot ) +% +% Get parameters for selected OneBox; +% returned as a struct of name/value pairs. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +% list = GetProbeList( myobj ) +% +% Get string with format: +% (probeID,nShanks,partNumber)()... +% - A parenthesized entry for each selected probe. +% - probeID: zero-based integer. +% - nShanks: integer {1,4}. +% - partNumber: string, e.g., NP1000. +% - If no probes, return '()'. +% +% name = GetRunName( myobj ) +% +% Get run base name. +% +% chanCounts = GetStreamAcqChans( myobj, js, ip ) +% +% For the selected substream, returns a vector of the +% number of channels of each type that stream is acquiring. +% +% js = 0: NI channels: {MN,MA,XA,DW}. +% js = 1: OB channels: {XA,DW,SY}. +% js = 2: IM channels: {AP,LF,SY}. +% +% startingSample = GetStreamFileStart( myobj, js, ip ) +% +% Returns index of first sample in selected file, +% or zero if unavailable. +% +% mult = GetStreamI16ToVolts( myobj, js, ip, chan ) +% +% Returns multiplier converting 16-bit binary channel to volts. +% +% maxInt = GetStreamMaxInt( myobj, js, ip ) +% +% Returns largest positive integer value for selected stream. +% +% n_substreams = GetStreamNP( myobj, js ) +% +% Returns number (np) of js-type substreams. +% For the given js, ip has range [0..np-1]. +% +% sampleCount = GetStreamSampleCount( myobj, js, ip ) +% +% Returns number of samples since current run started, +% or zero if not running. +% +% sampleRate = GetStreamSampleRate( myobj, js, ip ) +% +% Returns sample rate of selected stream in Hz. +% +% channelSubset = GetStreamSaveChans( myobj, js, ip ) +% +% Returns a vector containing the indices of +% the acquired channels that are being saved. +% +% [nS,nC,nR,mat] = GetStreamShankMap( myObj, js, ip ) +% +% Get shank map for NI stream (js = 0): +% {nS,nC,nR} = max {shanks, cols, rows} on this probe; +% mat = Mx4 matrix of shank map entries, where, +% M = channel count. +% 4 = given channel's zero-based {shank, col, row} indices, +% plus a 'used' flag which is 1 if the channel should be +% included in displays and spatial averaging operations. +% Data are int16 type. +% +% [SN,type] = GetStreamSN( myobj, js, ip ) +% +% js = 1: Return OneBox SN and slot. +% js = 2: Return probe SN and type. +% SN = serial number string. +% +% [Vmin,Vmax] = GetStreamVoltageRange( myobj, js, ip ) +% +% Returns voltage range of selected data stream. +% +% time = GetTime( myobj ) +% +% Returns (double) number of seconds since SpikeGLX application +% was launched. +% +% version = GetVersion( myobj ) +% +% Get SpikeGLX version string. +% +% boolval = IsConsoleHidden( myobj ) +% +% Returns 1 if console window is hidden, false otherwise. +% The console window may be hidden/shown using ConsoleHide() +% and ConsoleShow(). +% +% boolval = IsInitialized( myobj ) +% +% Return 1 if SpikeGLX has completed its startup +% initialization and is ready to run. +% +% boolval = IsRunning( myobj ) +% +% Returns 1 if SpikeGLX is currently acquiring data. +% +% boolval = IsSaving( myobj ) +% +% Returns 1 if the software is currently running +% AND saving data. +% +% boolval = IsUserOrder( myobj, js, ip ) +% +% Returns 1 if graphs currently sorted in user order. +% This query is sent only to the main Graphs window. +% +% dstSample = MapSample( myobj, dstjs, dstip, srcSample, srcjs, srcip ) +% +% Returns sample in dst stream corresponding to +% given sample in src stream. +% +% myobj = NI_DO_Set( myobj, 'lines', bits ) +% +% Set one or more NI lines high/low. +% - lines is a string list of lines to set, e.g.: +% 'Dev6/port0/line2,Dev6/port0/line5' +% 'Dev6/port1/line0:3' +% 'Dev6/port1:2' +% - bits is a uint32 value, each bit maps to a line: +% The lowest 8 bits map to port 0. +% The next higher 8 bits map to port 1, etc. +% +% myobj = NI_Wave_Arm( myobj, 'outChan', 'trigTerm' ) +% +% General sequence: +% 1. NI_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. NI_Wave_Arm : Set triggering parameters. +% 3. NI_Wave_StartStop : Start if software trigger, stop when done. +% +% Set trigger method. +% - trigTerm is a string naming any trigger-capable terminal on your +% device, e.g., '/dev1/pfi2'. NI-DAQ requires names of terminals to +% start with a '/' character. This is indeed different than channel +% names which do not start with a slash. +% (1) Give a correct terminal string to trigger playback upon +% receiving a rising edge at that terminal. +% (2) Give any string that does NOT start with a '/' to trigger +% playback via the NI_Wave_StartStop command. +% - Multiple trigger events can NOT be given. For each trigger +% event after the first, you must first call NI_Wave_StartStop +% AND NI_Wave_Arm to stop and then rearm the task. +% +% outChan is a string naming any wave-capable analog output +% channel on your device, e.g., 'dev1/ao1'. +% +% myobj = NI_Wave_Load( myobj, 'outChan', 'wave_name', loop_mode ) +% +% General sequence: +% 1. NI_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. NI_Wave_Arm : Set triggering parameters. +% 3. NI_Wave_StartStop : Start if software trigger, stop when done. +% +% Load a wave descriptor already placed in SpikeGLX/_Waves. +% - Pass 'mywavename' to this function; no path; no extension. +% - The playback loop_modes are: {1=loop until stopped, 0=once only}. +% +% outChan is a string naming any wave-capable analog output +% channel on your device, e.g., 'dev1/ao1'. +% +% myobj = NI_Wave_StartStop( myobj, 'outChan', start_bool ) +% +% General sequence: +% 1. NI_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. NI_Wave_Arm : Set triggering parameters. +% 3. NI_Wave_StartStop : Start if software trigger, stop when done. +% +% Start (optionally) or stop wave playback. +% - If you selected software triggering with NI_Wave_Arm, +% then set start_bool=1 to start playback. +% - In all cases, set start_bool=0 to stop playback. +% - It is best to stop playback before changing wave parameters. +% - After playback or if looping mode is interrupted, the voltage +% remains at the last output level. +% +% outChan is a string naming any wave-capable analog output +% channel on your device, e.g., 'dev1/ao1'. +% +% myobj = OBX_AO_Set( myobj, ip, slot, 'chn_vlt' ) +% +% Set one or more OneBox AO (DAC) channel voltages. +% - chn_vlt is a string with format: (chan,volts)(chan,volts)...() +% - The chan values are integer AO indices in range [0,11]. +% - You can only use AO channels already listed on the OBX setup tab. +% - Voltages are double values in range [-5.0,5.0] V. +% - DAC is 16-bit; theoretical resolution is (10 V)/(2^16) ~ .0001526 V. +% - Practical resolution, given noise, appears to be ~ 0.002 V. +% - AO channels are disabled at run start/end; voltage ~ 1.56 V. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +% myobj = OBX_Wave_Arm( myobj, ip, slot, trigger, loop_mode ) +% +% General sequence: +% 1. OBX_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. OBX_Wave_Arm : Set triggering parameters. +% 3. OBX_Wave_StartStop : Start if software trigger, stop when done. +% +% Set trigger method, and playback loop_mode. +% - Trigger values...Playback starts: +% -2 : By calling OBX_Wave_StartStop. +% -1 : When TTL rising edge sent to SMA1. +% 0-11 : When TTL rising edge sent to that XA (ADC) channel. +% - To use an ADC channel, you must name it as an XA channel on +% the OBX setup tab of the Acquisition Configuration dialog. +% - Multiple trigger events (either hardware or software) can be +% given without needing to rearm. +% - The playback loop_modes are: {1=loop until stopped, 0=once only}. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +% myobj = OBX_Wave_Load( myobj, ip, slot, 'wave_name' ) +% +% General sequence: +% 1. OBX_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. OBX_Wave_Arm : Set triggering parameters. +% 3. OBX_Wave_StartStop : Start if software trigger, stop when done. +% +% Load a wave descriptor already placed in SpikeGLX/_Waves. +% - Pass 'mywavename' to this function; no path; no extension. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +% myobj = OBX_Wave_StartStop( myobj, ip, slot, start_bool ) +% +% General sequence: +% 1. OBX_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. OBX_Wave_Arm : Set triggering parameters. +% 3. OBX_Wave_StartStop : Start if software trigger, stop when done. +% +% Start (optionally) or stop wave playback. +% - If you selected software triggering with OBX_Wave_Arm, +% then set start_bool=1 to start playback. +% - In all cases, set start_bool=0 to stop playback. +% - It is best to stop playback before changing wave parameters. +% - Waves only play at AO (DAC) channel-0. +% - To use the waveplayer, you must name channel AO-0 on +% the OBX setup tab of the Acquisition Configuration dialog. +% - After playback or if looping mode is interrupted, the voltage +% remains at the last output level. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +% myobj = Opto_emit( myobj, ip, color, site ) +% +% Direct emission to specified site (-1=dark). +% ip: imec probe index. +% color: {0=blue, 1=red}. +% site: [0..13], or, -1=dark. +% +% [site_atten_factors] = Opto_getAttenuations( myobj, ip, color ) +% +% Returns vector of 14 (double) site power attenuation factors. +% ip: imec probe index. +% color: {0=blue, 1=red}. +% +% res = Par2( myobj, op, 'filename' ) +% +% Create, Verify, or Repair Par2 redundancy files for +% 'filename'. Arguments: +% +% op: a string that is either 'c', 'v', or 'r' for create, +% verify or repair respectively. +% +% filename: the .par2 or .bin file to which 'op' is applied. +% +% Progress is reported to the command window. +% +% myobj = PauseGraphs( myobj, bool_flag ) +% +% Pause Graphs window displays. +% Note: The displays are updated at ~10 Hz. +% +% myobj = SetAnatomy_Pinpoint( myobj, 'shankdat' ) +% +% Set anatomy data string with Pinpoint format: +% [probe-id,shank-id](startpos,endpos,R,G,B,rgnname)(startpos,endpos,R,G,B,rgnname)...() +% - probe-id: SpikeGLX logical probe id. +% - shank-id: [0..n-shanks]. +% - startpos: region start in microns from tip. +% - endpos: region end in microns from tip. +% - R,G,B: region color as RGB, each [0..255]. +% - rgnname: region name text. +% +% myobj = SetAudioEnable( myobj, bool_flag ) +% +% Set audio output on/off. Note that this command has +% no effect if not currently running. +% +% myobj = SetAudioParams( myobj, group_string, params_struct ) +% +% Set subgroup of parameters for audio-out operation. Parameters +% are a struct of name/value pairs. This call stops current output. +% Call SetAudioEnable( myobj, 1 ) to restart it. +% +% myobj = SetDataDir( myobj, idir, 'dir' ) +% +% Set ith global data directory. +% Set required parameter idir to zero for main data directory. +% +% myobj = SetMetadata( myobj, metadata_struct ) +% +% If a run is in progress, set metadata to be added to the +% next output file-set. Metadata must be in the form of a +% struct of name/value pairs. +% +% myobj = SetMultiDriveEnable( myobj, bool_flag ) +% +% Set multi-drive run-splitting on/off. +% +% myobj = SetNextFileName( myobj, 'name' ) +% +% For only the next trigger (file writing event) this overrides +% all auto-naming, giving you complete control of where to save +% the files, the file name, and what g- and t-indices you want +% (if any). For example, regardless of the run's current data dir, +% run name and indices, if you set: 'otherdir/yyy_g5/yyy_g5_t7', +% SpikeGLX will save the next files in flat directory yyy_g5/: +% - otherdir/yyy_g5/yyy.g5_t7.nidq.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec0.ap.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec0.lf.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec1.ap.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec1.lf.bin,meta +% - etc. +% +% - The destination directory must already exist...No parent directories +% or probe subfolders are created in this naming mode. +% - The run must already be in progress. +% - Neither the custom name nor its indices are displayed in the Graphs +% window toolbars. Rather, the toolbars reflect standard auto-names. +% - After writing this file set, the override is cleared and auto-naming +% will resume as if you never called setNextFileName. You have to call +% setNextFileName before each trigger event to create custom trial series. +% For example, you can build a software-triggered t-series using sequence: +% + setNextFileName( 'otherdir/yyy_g0/yyy_g0_t0' ) +% + setRecordingEnable( 1 ) +% + setRecordingEnable( 0 ) +% + setNextFileName( 'otherdir/yyy_g0/yyy_g0_t1' ) +% + setRecordingEnable( 1 ) +% + setRecordingEnable( 0 ) +% + etc. +% +% myobj = SetParams( myobj, params_struct ) +% +% The inverse of GetParams.m, this sets run parameters. +% Alternatively, you can pass the parameters to StartRun() +% which calls this in turn. Run parameters are a struct of +% name/value pairs. The call will error if a run is currently +% in progress. +% +% Note: You can set any subset of [DAQSettings]. +% +% myobj = SetParamsImecCommon( myobj, params_struct ) +% +% The inverse of GetParamsImecCommon.m, this sets parameters +% common to all enabled probes. Parameters are a struct of +% name/value pairs. The call will error if a run is currently +% in progress. +% +% Note: You can set any subset of [DAQ_Imec_All]. +% +% myobj = SetParamsImecProbe( myobj, params_struct, ip ) +% +% The inverse of GetParamsImecProbe.m, this sets parameters +% for a given logical probe. Parameters are a struct of +% name/value pairs. The call will error if file writing +% is currently in progress. +% +% Note: You can set any subset of fields under [SerialNumberToProbe]/SNjjj. +% +% myobj = SetParamsOneBox( myobj, params_struct, ip, slot ) +% +% The inverse of GetParamsOneBox.m, this sets params +% for a selected OneBox. Parameters are a struct of +% name/value pairs. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +% The call will error if a run is currently in progress. +% +% Note: You can set any subset of fields under [SerialNumberToOneBox]/SNjjj. +% +% myobj = SetRecordingEnable( myobj, bool_flag ) +% +% Set gate (file writing) on/off during run. +% +% When auto-naming is in effect, opening the gate advances +% the g-index and resets the t-index to zero. Auto-naming is +% on unless SetNextFileName has been used to override it. +% +% myobj = SetRunName( myobj, 'name' ) +% +% Set the run name for the next time files are created +% (either by trigger, SetRecordingEnable() or by StartRun()). +% +% myobj = SetTriggerOffBeep( myobj, hertz, millisec ) +% +% During a run, set frequency and duration of Windows +% beep signaling file closure. hertz=0 disables the beep. +% +% myobj = SetTriggerOnBeep( myobj, hertz, millisec ) +% +% During a run set frequency and duration of Windows +% beep signaling file creation. hertz=0 disables the beep. +% +% myobj = StartRun( myobj ) +% myobj = StartRun( myobj, params_struct ) +% myobj = StartRun( myobj, 'runName' ) +% +% Start data acquisition run. Optional second argument (params) +% is a struct of name/value pairs as returned from GetParams.m. +% Alternatively, the second argument can be a string (runName). +% Last-used parameters remain in effect if not specified here. +% An error is flagged if already running or a parameter is bad. +% +% myobj = StopRun( myobj ) +% +% Unconditionally stop current run, close data files +% and return to idle state. +% +% myobj = TriggerGT( myobj, g, t ) +% +% Using standard auto-naming, set both the gate (g) and +% trigger (t) levels that control file writing. +% -1 = no change. +% 0 = set low. +% 1 = increment and set high. +% E.g., triggerGT( -1, 1 ) = same g, increment t, start writing. +% +% - TriggerGT only affects the 'Remote controlled' gate type and/or +% the 'Remote controlled' trigger type. +% - The 'Enable Recording' button, when shown, is a master override +% switch. TriggerGT is blocked until you click the button or call +% SetRecordingEnable. +% +% res = VerifySha1( myobj, 'filename' ) +% +% Verifies the SHA1 sum of the file specified by filename. +% If filename is relative, it is appended to the run dir. +% Absolute path/filenames are also supported. Since this is +% a potentially long operation, it uses the 'disp' command +% to print progress information to the MATLAB console. The +% returned value is 1 if verified, 0 otherwise. + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoGetCells.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoGetCells.m new file mode 100644 index 00000000..86c5eaf1 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoGetCells.m @@ -0,0 +1,17 @@ +% THIS FUNCTION IS PRIVATE AND SHOULD NOT BE CALLED BY OUTSIDE CODE! +% +% Fetch one or more lines, excluding {OK, ERROR} lines. +% Return as cell-array of strings. +% +function [res] = DoGetCells( sm, cmd ) + + ChkConn( sm ); + + ok = CalinsNetMex( 'sendstring', sm.handle, sprintf( '%s\n', cmd ) ); + + if( isempty( ok ) ) + error( '[%s] error: Cannot send string.', cmd ); + end + + res = CalinsNetMex( 'getcells', sm.handle ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoQuery.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoQuery.m new file mode 100644 index 00000000..05c86bcc --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoQuery.m @@ -0,0 +1,20 @@ +% THIS FUNCTION IS PRIVATE AND SHOULD NOT BE CALLED BY OUTSIDE CODE! +% +% Fetch one line, excluding {OK, ERROR} lines. +% Return as single string. +% +% To include OK, m-files should directly call: +% line = CalinsNetMex( 'readline', s.handle ); +% +function [res] = DoQuery( sm, cmd ) + + ChkConn( sm ); + + ok = CalinsNetMex( 'sendstring', sm.handle, sprintf( '%s\n', cmd ) ); + + if( isempty( ok ) ) + error( '[%s] error: Cannot send string.', cmd ); + end + + res = CalinsNetMex( 'querystring', sm.handle ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoSimpleCmd.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoSimpleCmd.m new file mode 100644 index 00000000..58cc3ea8 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/DoSimpleCmd.m @@ -0,0 +1,14 @@ +% THIS FUNCTION IS PRIVATE AND SHOULD NOT BE CALLED BY OUTSIDE CODE! +% +function [res] = DoSimpleCmd( sm, cmd ) + + ChkConn( sm ); + + res = CalinsNetMex( 'sendstring', sm.handle, sprintf( '%s\n', cmd ) ); + + if( isempty( res ) ) + error( 'Empty result for command [%s]; probably disconnected.', cmd ); + end + + ReceiveOK( sm, cmd ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/EnumDataDir.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/EnumDataDir.m new file mode 100644 index 00000000..72cbfd8d --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/EnumDataDir.m @@ -0,0 +1,15 @@ +% params = EnumDataDir( myobj, i ) +% +% Retrieve a listing of files in the ith data directory. +% Get main data directory by setting i=0 or omitting it. +% +function [ret] = EnumDataDir( s, varargin ) + + i = 0; + + if( nargin > 1 ) + i = varargin{1}; + end + + ret = DoGetCells( s, sprintf( 'ENUMDATADIR %d', i ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Fetch.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Fetch.m new file mode 100644 index 00000000..c177db52 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Fetch.m @@ -0,0 +1,74 @@ +% [daqData,headCt] = Fetch( myObj, js, ip, start_samp, max_samps, channel_subset, downsample_ratio ) +% +% Get MxN matrix of stream data. +% M = samp_count, MIN(max_samps,available). +% N = channel count... +% Data are int16 type. +% If filtered IM stream buffers are enabled, you may fetch from them with js=-2. +% Fetching starts at index start_samp. +% channel_subset is an optional vector of specific channels to fetch [a,b,c...], or, +% [-1] = all acquired channels, or, +% [-2] = all saved channels. +% downsample_ratio is an integer; return every Nth sample (default = 1). +% Also returns headCt = index of first sample in matrix. +% +function [mat,headCt] = Fetch( s, js, ip, start_samp, max_samps, varargin ) + + if( nargin < 5 ) + error( 'Fetch: Requires at least 5 arguments.' ); + end + + if( ~isnumeric( start_samp ) || ~size( start_samp, 1 ) ) + error( 'Fetch: Invalid samp_start parameter.' ); + end + + if( ~isnumeric( max_samps ) || ~size( max_samps, 1 ) ) + error( 'Fetch: Invalid max_samps parameter.' ); + end + + ChkConn( s ); + + % subset has pattern id1#id2#... + if( nargin >= 6 ) + subset = sprintf( '%d#', varargin{1} ); + else + subset = '-1#'; + end + + dwnsmp = 1; + + if( nargin >= 7 ) + + dwnsmp = varargin{2}; + + if( ~isnumeric( dwnsmp ) || length( dwnsmp ) > 1 ) + error( 'Fetch: Downsample factor must be a single numeric value.' ); + end + end + + ok = CalinsNetMex( 'sendstring', s.handle, ... + sprintf( 'FETCH %d %d %u %d %s %d\n', ... + js, ip, uint64(start_samp), max_samps, subset, dwnsmp ) ); + + line = CalinsNetMex( 'readline', s.handle ); + + if( isempty( line ) ) + error( 'Fetch: Failed - see warning.' ); + end + + % cells = strsplit( line ); + cells = strread( line, '%s' ); + mat_dims = [str2num(cells{2}) str2num(cells{3})]; + headCt = str2num(cells{4}); + + if( ~isnumeric( mat_dims ) || ~size( mat_dims, 2 ) ) + error( 'Fetch: Invalid matrix dimensions.' ); + end + + mat = CalinsNetMex( 'readmatrix', s.handle, 'int16', mat_dims ); + + % transpose + mat = mat'; + + ReceiveOK( s, 'FETCH' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/FetchLatest.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/FetchLatest.m new file mode 100644 index 00000000..d029202e --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/FetchLatest.m @@ -0,0 +1,42 @@ +% [daqData,headCt] = FetchLatest( myObj, js, ip, max_samps, channel_subset, downsample_ratio ) +% +% Get MxN matrix of the most recent stream data. +% M = samp_count, MIN(max_samps,available). +% N = channel count... +% Data are int16 type. +% If filtered IM stream buffers are enabled, you may fetch from them with js=-2. +% channel_subset is an optional vector of specific channels to fetch [a,b,c...], or, +% [-1] = all acquired channels, or, +% [-2] = all saved channels. +% downsample_ratio is an integer; return every Nth sample (default = 1). +% Also returns headCt = index of first sample in matrix. +% +function [mat,headCt] = FetchLatest( s, js, ip, max_samps, varargin ) + + if( nargin < 4 ) + error( 'FetchLatest: Requires at least four arguments.' ); + else if( nargin >= 5 ) + subset = varargin{1}; + else + subset = [-1]; + end + + dwnsmp = 1; + + if( nargin >= 6 ) + + dwnsmp = varargin{2}; + + if( ~isnumeric( dwnsmp ) || length( dwnsmp ) > 1 ) + error( 'FetchLatest: Downsample factor must be a single numeric value.' ); + end + end + + max_ct = GetStreamSampleCount( s, js, ip ); + + if( max_samps > max_ct ) + max_samps = max_ct; + end + + [mat,headCt] = Fetch( s, js, ip, max_ct - max_samps, max_samps, subset, dwnsmp ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetDataDir.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetDataDir.m new file mode 100644 index 00000000..edb2764c --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetDataDir.m @@ -0,0 +1,15 @@ +% dir = GetDataDir( myobj, i ) +% +% Get ith global data directory. +% Get main data directory by setting i=0 or omitting it. +% +function [ret] = GetDataDir( s, varargin ) + + i = 0; + + if( nargin > 1 ) + i = varargin{1}; + end + + ret = DoQuery( s, sprintf( 'GETDATADIR %d', i ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetGeomMap.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetGeomMap.m new file mode 100644 index 00000000..d19cfb9f --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetGeomMap.m @@ -0,0 +1,47 @@ +% params = GetGeomMap( myobj, ip ) +% +% Get geomMap for given logical imec probe. +% Returned as a struct of name/value pairs. +% Header fields: +% head_partNumber ; string +% head_numShanks +% head_shankPitch ; microns +% head_shankWidth ; microns +% Channel 5, e.g.: +% ch5_s ; shank index +% ch5_x ; microns from left edge of shank +% ch5_z ; microns from center of tip-most electrode row +% ch5_u ; used-flag (in CAR operations) +% +function ret = GetGeomMap( s, ip ) + + ret = struct(); + res = DoGetCells( s, sprintf( 'GETGEOMMAP %d', ip ) ); + + % res is a cell array, each cell containing a string of form + % ' = ' + % Parameter names are sequences of word characters [a-z_A-Z0-9]. + % Parameter values become doubles. + + for i = 1:length( res ) + + % (?expr) captures token matching expr and names it 'xxx' + + pair = ... + regexp( res{i}, ... + '^\s*(?\w+)\s*=\s*(?.*)\s*$', 'names' ); + + % pair is a struct array with at most one element. If there + % is an element, then pair.name is the (string) name, and + % pair.value is a double value, except for head_partNumber. + + if( ~isempty( pair ) ) + if( i == 1 ) + % partNumber string + ret.(pair.name) = pair.value; + else + ret.(pair.name) = str2num( pair.value ); + end + end + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetImecChanGains.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetImecChanGains.m new file mode 100644 index 00000000..52b73344 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetImecChanGains.m @@ -0,0 +1,12 @@ +% [APgain,LFgain] = GetImecChanGains( myobj, ip, chan ) +% +% Returns the AP and LF gains for given probe and channel. +% +function [APgain,LFgain] = GetImecChanGains( s, ip, chan ) + + ret = DoQuery( s, sprintf( 'GETIMECCHANGAINS %d %d', ip, chan ) ); + C = textscan( ret, '%f %f' ); + + APgain = C{1}; + LFgain = C{2}; +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParams.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParams.m new file mode 100644 index 00000000..35cba5c1 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParams.m @@ -0,0 +1,54 @@ +% params = GetParams( myobj ) +% +% Get the most recently used run parameters. +% These are a struct of name/value pairs. +% +function ret = GetParams( s ) + + ret = struct(); + res = DoGetCells( s, 'GETPARAMS' ); + + % res is a cell array, each cell containing a string of form + % ' = ' + % Parameter names are sequences of word characters [a-z_A-Z0-9] + % some examples of parameter values: + % 3 + % 0.5 + % -1 + % false + % 0:1 + % 0=1,1=2 + % Dev1 + % C:/Documents and Settings/labadmin/My Documents/data.bin + + for i = 1:length( res ) + + % (?expr) captures token matching expr and names it 'xxx' + + pair = ... + regexp( res{i}, ... + '^\s*(?\w+)\s*=\s*(?.*)\s*$', 'names' ); + + % pair is a struct array with at most one element. If there + % is an element, then pair.name is the (string) name, and + % pair.value is the (string) value. + + if( ~isempty( pair ) ) + + % If looks like a double, convert to double. + + dblpat = ... + regexp( pair.value, ... + '^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$', ... + 'once' ); + + if( isempty( dblpat ) ) + % store the value as a string + ret.(pair.name) = pair.value; + else + % convert to double + ret.(pair.name) = str2double( pair.value ); + end + end + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsImecCommon.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsImecCommon.m new file mode 100644 index 00000000..b7afa569 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsImecCommon.m @@ -0,0 +1,54 @@ +% params = GetParamsImecCommon( myobj ) +% +% Get imec parameters common to all enabled probes. +% Returned as a struct of name/value pairs. +% +function ret = GetParamsImecCommon( s ) + + ret = struct(); + res = DoGetCells( s, 'GETPARAMSIMALL' ); + + % res is a cell array, each cell containing a string of form + % ' = ' + % Parameter names are sequences of word characters [a-z_A-Z0-9] + % some examples of parameter values: + % 3 + % 0.5 + % -1 + % false + % 0:1 + % 0=1,1=2 + % Dev1 + % C:/Documents and Settings/labadmin/My Documents/data.bin + + for i = 1:length( res ) + + % (?expr) captures token matching expr and names it 'xxx' + + pair = ... + regexp( res{i}, ... + '^\s*(?\w+)\s*=\s*(?.*)\s*$', 'names' ); + + % pair is a struct array with at most one element. If there + % is an element, then pair.name is the (string) name, and + % pair.value is the (string) value. + + if( ~isempty( pair ) ) + + % If looks like a double, convert to double. + + dblpat = ... + regexp( pair.value, ... + '^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$', ... + 'once' ); + + if( isempty( dblpat ) ) + % store the value as a string + ret.(pair.name) = pair.value; + else + % convert to double + ret.(pair.name) = str2double( pair.value ); + end + end + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsImecProbe.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsImecProbe.m new file mode 100644 index 00000000..5074e97f --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsImecProbe.m @@ -0,0 +1,54 @@ +% params = GetParamsImecProbe( myobj, ip ) +% +% Get imec parameters for given logical probe. +% Returned as a struct of name/value pairs. +% +function ret = GetParamsImecProbe( s, ip ) + + ret = struct(); + res = DoGetCells( s, sprintf( 'GETPARAMSIMPRB %d', ip ) ); + + % res is a cell array, each cell containing a string of form + % ' = ' + % Parameter names are sequences of word characters [a-z_A-Z0-9] + % some examples of parameter values: + % 3 + % 0.5 + % -1 + % false + % 0:1 + % 0=1,1=2 + % Dev1 + % C:/Documents and Settings/labadmin/My Documents/data.bin + + for i = 1:length( res ) + + % (?expr) captures token matching expr and names it 'xxx' + + pair = ... + regexp( res{i}, ... + '^\s*(?\w+)\s*=\s*(?.*)\s*$', 'names' ); + + % pair is a struct array with at most one element. If there + % is an element, then pair.name is the (string) name, and + % pair.value is the (string) value. + + if( ~isempty( pair ) ) + + % If looks like a double, convert to double. + + dblpat = ... + regexp( pair.value, ... + '^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$', ... + 'once' ); + + if( isempty( dblpat ) ) + % store the value as a string + ret.(pair.name) = pair.value; + else + % convert to double + ret.(pair.name) = str2double( pair.value ); + end + end + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsOnebox.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsOnebox.m new file mode 100644 index 00000000..87d7993b --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetParamsOnebox.m @@ -0,0 +1,59 @@ +% params = GetParamsOneBox( myobj, ip, slot ) +% +% Get parameters for selected OneBox; +% returned as a struct of name/value pairs. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +function ret = GetParamsOneBox( s, ip, slot ) + + ret = struct(); + res = DoGetCells( s, sprintf( 'GETPARAMSOBX %d %d', ip, slot ) ); + + % res is a cell array, each cell containing a string of form + % ' = ' + % Parameter names are sequences of word characters [a-z_A-Z0-9] + % some examples of parameter values: + % 3 + % 0.5 + % -1 + % false + % 0:1 + % 0=1,1=2 + % Dev1 + % C:/Documents and Settings/labadmin/My Documents/data.bin + + for i = 1:length( res ) + + % (?expr) captures token matching expr and names it 'xxx' + + pair = ... + regexp( res{i}, ... + '^\s*(?\w+)\s*=\s*(?.*)\s*$', 'names' ); + + % pair is a struct array with at most one element. If there + % is an element, then pair.name is the (string) name, and + % pair.value is the (string) value. + + if( ~isempty( pair ) ) + + % If looks like a double, convert to double. + + dblpat = ... + regexp( pair.value, ... + '^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$', ... + 'once' ); + + if( isempty( dblpat ) ) + % store the value as a string + ret.(pair.name) = pair.value; + else + % convert to double + ret.(pair.name) = str2double( pair.value ); + end + end + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetProbeList.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetProbeList.m new file mode 100644 index 00000000..65d528ad --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetProbeList.m @@ -0,0 +1,14 @@ +% list = GetProbeList( myobj ) +% +% Get string with format: +% (probeID,nShanks,partNumber)()... +% - A parenthesized entry for each selected probe. +% - probeID: zero-based integer. +% - nShanks: integer {1,4}. +% - partNumber: string, e.g., NP1000. +% - If no probes, return '()'. +% +function [list] = GetProbeList( s ) + + list = DoQuery( s, 'GETPROBELIST' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetRunName.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetRunName.m new file mode 100644 index 00000000..2ce170ec --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetRunName.m @@ -0,0 +1,8 @@ +% name = GetRunName( myobj ) +% +% Get run base name. +% +function [name] = GetRunName( s ) + + name = DoQuery( s, 'GETRUNNAME' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamAcqChans.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamAcqChans.m new file mode 100644 index 00000000..b2588abc --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamAcqChans.m @@ -0,0 +1,13 @@ +% chanCounts = GetStreamAcqChans( myobj, js, ip ) +% +% For the selected substream, returns a vector of the +% number of channels of each type that stream is acquiring. +% +% js = 0: NI channels: {MN,MA,XA,DW}. +% js = 1: OB channels: {XA,DW,SY}. +% js = 2: IM channels: {AP,LF,SY}. +% +function [ret] = GetStreamAcqChans( s, js, ip ) + + ret = str2num( DoQuery( s, sprintf( 'GETSTREAMACQCHANS %d %d', js, ip ) ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamFileStart.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamFileStart.m new file mode 100644 index 00000000..85b23f1b --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamFileStart.m @@ -0,0 +1,9 @@ +% startingSample = GetStreamFileStart( myobj, js, ip ) +% +% Returns index of first sample in selected file, +% or zero if unavailable. +% +function [ret] = GetStreamFileStart( s, js, ip ) + + ret = str2double( DoQuery( s, sprintf( 'GETSTREAMFILESTART %d %d', js, ip ) ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamI16ToVolts.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamI16ToVolts.m new file mode 100644 index 00000000..4bc2009e --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamI16ToVolts.m @@ -0,0 +1,9 @@ +% mult = GetStreamI16ToVolts( myobj, js, ip, chan ) +% +% Returns multiplier converting 16-bit binary channel to volts. +% +function [ret] = GetStreamI16ToVolts( s, js, ip, chan ) + + ret = str2double( DoQuery( s, ... + sprintf( 'GETSTREAMI16TOVOLTS %d %d %d', js, ip, chan ) ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamMaxInt.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamMaxInt.m new file mode 100644 index 00000000..66a70f64 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamMaxInt.m @@ -0,0 +1,8 @@ +% maxInt = GetStreamMaxInt( myobj, js, ip ) +% +% Returns largest positive integer value for selected stream. +% +function [ret] = GetStreamMaxInt( s, js, ip ) + + ret = sscanf( DoQuery( s, sprintf( 'GETSTREAMMAXINT %d %d', js, ip ) ), '%d' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamNP.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamNP.m new file mode 100644 index 00000000..d21c352a --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamNP.m @@ -0,0 +1,9 @@ +% n_substreams = GetStreamNP( myobj, js ) +% +% Returns number (np) of js-type substreams. +% For the given js, ip has range [0..np-1]. +% +function [ret] = GetStreamNP( s, js ) + + ret = sscanf( DoQuery( s, sprintf( 'GETSTREAMNP %d', js ) ), '%d' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSN.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSN.m new file mode 100644 index 00000000..af9f76b3 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSN.m @@ -0,0 +1,14 @@ +% [SN,type] = GetStreamSN( myobj, js, ip ) +% +% js = 1: Return OneBox SN and slot. +% js = 2: Return probe SN and type. +% SN = serial number string. +% +function [SN,type] = GetStreamSN( s, js, ip ) + + ret = DoQuery( s, sprintf( 'GETSTREAMSN %d %d', js, ip ) ); + C = textscan( ret, '%[^ ] %d' ); + + SN = C{1}; + type = C{2}; +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSampleCount.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSampleCount.m new file mode 100644 index 00000000..f1cc2f0d --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSampleCount.m @@ -0,0 +1,9 @@ +% sampleCount = GetStreamSampleCount( myobj, js, ip ) +% +% Returns number of samples since current run started, +% or zero if not running. +% +function [ret] = GetStreamSampleCount( s, js, ip ) + + ret = str2double( DoQuery( s, sprintf( 'GETSTREAMSAMPLECOUNT %d %d', js, ip ) ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSampleRate.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSampleRate.m new file mode 100644 index 00000000..c475c274 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSampleRate.m @@ -0,0 +1,8 @@ +% sampleRate = GetStreamSampleRate( myobj, js, ip ) +% +% Returns sample rate of selected stream in Hz. +% +function [ret] = GetStreamSampleRate( s, js, ip ) + + ret = str2double( DoQuery( s, sprintf( 'GETSTREAMSAMPLERATE %d %d', js, ip ) ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSaveChans.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSaveChans.m new file mode 100644 index 00000000..d7fe7ed8 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamSaveChans.m @@ -0,0 +1,9 @@ +% channelSubset = GetStreamSaveChans( myobj, js, ip ) +% +% Returns a vector containing the indices of +% the acquired channels that are being saved. +% +function [ret] = GetStreamSaveChans( s, js, ip ) + + ret = str2num( DoQuery( s, sprintf( 'GETSTREAMSAVECHANS %d %d', js, ip ) ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamShankMap.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamShankMap.m new file mode 100644 index 00000000..ca2b9c76 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamShankMap.m @@ -0,0 +1,37 @@ +% [nS,nC,nR,mat] = GetStreamShankMap( myObj, js, ip ) +% +% Get shank map for NI stream (js = 0): +% {nS,nC,nR} = max {shanks, cols, rows} on this probe; +% mat = Mx4 matrix of shank map entries, where, +% M = channel count. +% 4 = given channel's zero-based {shank, col, row} indices, +% plus a 'used' flag which is 1 if the channel should be +% included in displays and spatial averaging operations. +% Data are int16 type. +% +function [nS,nC,nR,mat] = GetStreamShankMap( s, js, ip ) + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, ... + sprintf( 'GETSTREAMSHANKMAP %d %d\n', js, ip ) ); + + line = CalinsNetMex( 'readline', s.handle ); + + if( isempty( line ) ) + error( 'GetStreamShankMap: Failed - see warning.' ); + end + + cells = strread( line, '%s' ); + nS = str2num(cells{2}); + nC = str2num(cells{3}); + nR = str2num(cells{4}); + dims = [4 str2num(cells{5})]; + + mat = CalinsNetMex( 'readmatrix', s.handle, 'int16', dims ); + + % transpose + mat = mat'; + + ReceiveOK( s, 'GETSTREAMSHANKMAP' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamVoltageRange.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamVoltageRange.m new file mode 100644 index 00000000..9da8eee6 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetStreamVoltageRange.m @@ -0,0 +1,12 @@ +% [Vmin,Vmax] = GetStreamVoltageRange( myobj, js, ip ) +% +% Returns voltage range of selected data stream. +% +function [Vmin,Vmax] = GetStreamVoltageRange( s, js, ip ) + + ret = DoQuery( s, sprintf( 'GETSTREAMVOLTAGERANGE %d %d', js, ip ) ); + C = textscan( ret, '%f %f' ); + + Vmin = C{1}; + Vmax = C{2}; +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetTime.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetTime.m new file mode 100644 index 00000000..393d8a2a --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetTime.m @@ -0,0 +1,9 @@ +% time = GetTime( myobj ) +% +% Returns (double) number of seconds since SpikeGLX application +% was launched. +% +function [ret] = GetTime( s ) + + ret = sscanf( DoQuery( s, 'GETTIME' ), '%f' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetVersion.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetVersion.m new file mode 100644 index 00000000..ee65de7c --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/GetVersion.m @@ -0,0 +1,8 @@ +% version = GetVersion( myobj ) +% +% Get SpikeGLX version string. +% +function [ret] = GetVersion( s ) + + ret = DoQuery( s, 'GETVERSION' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsConsoleHidden.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsConsoleHidden.m new file mode 100644 index 00000000..056fb4a9 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsConsoleHidden.m @@ -0,0 +1,10 @@ +% boolval = IsConsoleHidden( myobj ) +% +% Returns 1 if console window is hidden, false otherwise. +% The console window may be hidden/shown using ConsoleHide() +% and ConsoleShow(). +% +function [ret] = IsConsoleHidden( s ) + + ret = sscanf( DoQuery( s, 'ISCONSOLEHIDDEN' ), '%d' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsInitialized.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsInitialized.m new file mode 100644 index 00000000..b58c75a4 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsInitialized.m @@ -0,0 +1,9 @@ +% boolval = IsInitialized( myobj ) +% +% Return 1 if SpikeGLX has completed its startup +% initialization and is ready to run. +% +function ret = IsInitialized( s ) + + ret = sscanf( DoQuery( s, 'ISINITIALIZED' ), '%d' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsRunning.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsRunning.m new file mode 100644 index 00000000..c56c9fe6 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsRunning.m @@ -0,0 +1,8 @@ +% boolval = IsRunning( myobj ) +% +% Returns 1 if SpikeGLX is currently acquiring data. +% +function [ret] = IsRunning( s ) + + ret = sscanf( DoQuery( s, 'ISRUNNING' ), '%d' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsSaving.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsSaving.m new file mode 100644 index 00000000..87f4facc --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsSaving.m @@ -0,0 +1,9 @@ +% boolval = IsSaving( myobj ) +% +% Returns 1 if the software is currently running +% AND saving data. +% +function [ret] = IsSaving( s ) + + ret = sscanf( DoQuery( s, 'ISSAVING' ), '%d' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsUserOrder.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsUserOrder.m new file mode 100644 index 00000000..f5014fcb --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/IsUserOrder.m @@ -0,0 +1,9 @@ +% boolval = IsUserOrder( myobj, js, ip ) +% +% Returns 1 if graphs currently sorted in user order. +% This query is sent only to the main Graphs window. +% +function [ret] = IsUserOrder( s, js, ip ) + + ret = sscanf( DoQuery( s, sprintf( 'ISUSRORDER %d %d', js, ip ) ), '%d' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/MapSample.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/MapSample.m new file mode 100644 index 00000000..5b5ce191 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/MapSample.m @@ -0,0 +1,12 @@ +% dstSample = MapSample( myobj, dstjs, dstip, srcSample, srcjs, srcip ) +% +% Returns sample in dst stream corresponding to +% given sample in src stream. +% +function [ret] = MapSample( s, dstjs, dstip, srcSample, srcjs, srcip ) + + ret = str2double( ... + DoQuery( s, ... + sprintf( 'MAPSAMPLE %d %d %u %d %d', ... + dstjs, dstip, uint64(srcSample), srcjs, srcip ) ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_DO_Set.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_DO_Set.m new file mode 100644 index 00000000..3c1cb073 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_DO_Set.m @@ -0,0 +1,15 @@ +% myobj = NI_DO_Set( myobj, 'lines', bits ) +% +% Set one or more NI lines high/low. +% - lines is a string list of lines to set, e.g.: +% 'Dev6/port0/line2,Dev6/port0/line5' +% 'Dev6/port1/line0:3' +% 'Dev6/port1:2' +% - bits is a uint32 value, each bit maps to a line: +% The lowest 8 bits map to port 0. +% The next higher 8 bits map to port 1, etc. +% +function [s] = NI_DO_Set( s, lines, bits ) + + DoSimpleCmd( s, sprintf( 'NIDOSET %s %u', lines, bits ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_Arm.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_Arm.m new file mode 100644 index 00000000..16c9a7b8 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_Arm.m @@ -0,0 +1,27 @@ +% myobj = NI_Wave_Arm( myobj, 'outChan', 'trigTerm' ) +% +% General sequence: +% 1. NI_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. NI_Wave_Arm : Set triggering parameters. +% 3. NI_Wave_StartStop : Start if software trigger, stop when done. +% +% Set trigger method. +% - trigTerm is a string naming any trigger-capable terminal on your +% device, e.g., '/dev1/pfi2'. NI-DAQ requires names of terminals to +% start with a '/' character. This is indeed different than channel +% names which do not start with a slash. +% (1) Give a correct terminal string to trigger playback upon +% receiving a rising edge at that terminal. +% (2) Give any string that does NOT start with a '/' to trigger +% playback via the NI_Wave_StartStop command. +% - Multiple trigger events can NOT be given. For each trigger +% event after the first, you must first call NI_Wave_StartStop +% AND NI_Wave_Arm to stop and then rearm the task. +% +% outChan is a string naming any wave-capable analog output +% channel on your device, e.g., 'dev1/ao1'. +% +function [s] = NI_Wave_Arm( s, outChan, trigTerm ) + + DoSimpleCmd( s, sprintf( 'NIWVARM %s %s', outChan, trigTerm ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_Load.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_Load.m new file mode 100644 index 00000000..6d1e3b7f --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_Load.m @@ -0,0 +1,18 @@ +% myobj = NI_Wave_Load( myobj, 'outChan', 'wave_name', loop_mode ) +% +% General sequence: +% 1. NI_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. NI_Wave_Arm : Set triggering parameters. +% 3. NI_Wave_StartStop : Start if software trigger, stop when done. +% +% Load a wave descriptor already placed in SpikeGLX/_Waves. +% - Pass 'mywavename' to this function; no path; no extension. +% - The playback loop_modes are: {1=loop until stopped, 0=once only}. +% +% outChan is a string naming any wave-capable analog output +% channel on your device, e.g., 'dev1/ao1'. +% +function [s] = NI_Wave_Load( s, outChan, wave_name, loop_mode ) + + DoSimpleCmd( s, sprintf( 'NIWVLOAD %s %s %d', outChan, wave_name, loop_mode ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_StartStop.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_StartStop.m new file mode 100644 index 00000000..58d999aa --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/NI_Wave_StartStop.m @@ -0,0 +1,22 @@ +% myobj = NI_Wave_StartStop( myobj, 'outChan', start_bool ) +% +% General sequence: +% 1. NI_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. NI_Wave_Arm : Set triggering parameters. +% 3. NI_Wave_StartStop : Start if software trigger, stop when done. +% +% Start (optionally) or stop wave playback. +% - If you selected software triggering with NI_Wave_Arm, +% then set start_bool=1 to start playback. +% - In all cases, set start_bool=0 to stop playback. +% - It is best to stop playback before changing wave parameters. +% - After playback or if looping mode is interrupted, the voltage +% remains at the last output level. +% +% outChan is a string naming any wave-capable analog output +% channel on your device, e.g., 'dev1/ao1'. +% +function [s] = NI_Wave_StartStop( s, outChan, start_bool ) + + DoSimpleCmd( s, sprintf( 'NIWVSTSP %s %d', outChan, start_bool ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_AO_Set.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_AO_Set.m new file mode 100644 index 00000000..ec0bd8eb --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_AO_Set.m @@ -0,0 +1,20 @@ +% myobj = OBX_AO_Set( myobj, ip, slot, 'chn_vlt' ) +% +% Set one or more OneBox AO (DAC) channel voltages. +% - chn_vlt is a string with format: (chan,volts)(chan,volts)...() +% - The chan values are integer AO indices in range [0,11]. +% - You can only use AO channels already listed on the OBX setup tab. +% - Voltages are double values in range [-5.0,5.0] V. +% - DAC is 16-bit; theoretical resolution is (10 V)/(2^16) ~ .0001526 V. +% - Practical resolution, given noise, appears to be ~ 0.002 V. +% - AO channels are disabled at run start/end; voltage ~ 1.56 V. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +function [s] = OBX_AO_Set( s, ip, slot, chn_vlt ) + + DoSimpleCmd( s, sprintf( 'OBXAOSET %d %d %s', ip, slot, chn_vlt ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_Arm.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_Arm.m new file mode 100644 index 00000000..e0e21e9b --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_Arm.m @@ -0,0 +1,27 @@ +% myobj = OBX_Wave_Arm( myobj, ip, slot, trigger, loop_mode ) +% +% General sequence: +% 1. OBX_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. OBX_Wave_Arm : Set triggering parameters. +% 3. OBX_Wave_StartStop : Start if software trigger, stop when done. +% +% Set trigger method, and playback loop_mode. +% - Trigger values...Playback starts: +% -2 : By calling OBX_Wave_StartStop. +% -1 : When TTL rising edge sent to SMA1. +% 0-11 : When TTL rising edge sent to that XA (ADC) channel. +% - To use an ADC channel, you must name it as an XA channel on +% the OBX setup tab of the Acquisition Configuration dialog. +% - Multiple trigger events (either hardware or software) can be +% given without needing to rearm. +% - The playback loop_modes are: {1=loop until stopped, 0=once only}. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +function [s] = OBX_Wave_Arm( s, ip, slot, trigger, loop_mode ) + + DoSimpleCmd( s, sprintf( 'OBXWVARM %d %d %d %d', ip, slot, trigger, loop_mode ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_Load.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_Load.m new file mode 100644 index 00000000..7c0cf5e0 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_Load.m @@ -0,0 +1,19 @@ +% myobj = OBX_Wave_Load( myobj, ip, slot, 'wave_name' ) +% +% General sequence: +% 1. OBX_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. OBX_Wave_Arm : Set triggering parameters. +% 3. OBX_Wave_StartStop : Start if software trigger, stop when done. +% +% Load a wave descriptor already placed in SpikeGLX/_Waves. +% - Pass 'mywavename' to this function; no path; no extension. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +function [s] = OBX_Wave_Load( s, ip, slot, wave_name ) + + DoSimpleCmd( s, sprintf( 'OBXWVLOAD %d %d %s', ip, slot, wave_name ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_StartStop.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_StartStop.m new file mode 100644 index 00000000..1f144463 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/OBX_Wave_StartStop.m @@ -0,0 +1,27 @@ +% myobj = OBX_Wave_StartStop( myobj, ip, slot, start_bool ) +% +% General sequence: +% 1. OBX_Wave_Load : Load wave from SpikeGLX/_Waves folder. +% 2. OBX_Wave_Arm : Set triggering parameters. +% 3. OBX_Wave_StartStop : Start if software trigger, stop when done. +% +% Start (optionally) or stop wave playback. +% - If you selected software triggering with OBX_Wave_Arm, +% then set start_bool=1 to start playback. +% - In all cases, set start_bool=0 to stop playback. +% - It is best to stop playback before changing wave parameters. +% - Waves only play at AO (DAC) channel-0. +% - To use the waveplayer, you must name channel AO-0 on +% the OBX setup tab of the Acquisition Configuration dialog. +% - After playback or if looping mode is interrupted, the voltage +% remains at the last output level. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +function [s] = OBX_Wave_StartStop( s, ip, slot, start_bool ) + + DoSimpleCmd( s, sprintf( 'OBXWVSTSP %d %d %d', ip, slot, start_bool ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Opto_emit.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Opto_emit.m new file mode 100644 index 00000000..69244d88 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Opto_emit.m @@ -0,0 +1,11 @@ +% myobj = Opto_emit( myobj, ip, color, site ) +% +% Direct emission to specified site (-1=dark). +% ip: imec probe index. +% color: {0=blue, 1=red}. +% site: [0..13], or, -1=dark. +% +function [s] = Opto_emit( s, ip, color, site ) + + DoSimpleCmd( s, sprintf( 'OPTOEMIT %d %d %d', ip, color, site ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Opto_getAttenuations.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Opto_getAttenuations.m new file mode 100644 index 00000000..1f734e6b --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Opto_getAttenuations.m @@ -0,0 +1,13 @@ +% [site_atten_factors] = Opto_getAttenuations( myobj, ip, color ) +% +% Returns vector of 14 (double) site power attenuation factors. +% ip: imec probe index. +% color: {0=blue, 1=red}. +% +function [ret] = Opto_getAttenuations( s, ip, color ) + + ret = sscanf( ... + DoQuery( s, ... + sprintf( 'OPTOGETATTENS %d %d', ip, color ) ), ... + '%f' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Par2.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Par2.m new file mode 100644 index 00000000..0d8e4ef7 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/Par2.m @@ -0,0 +1,43 @@ +% res = Par2( myobj, op, 'filename' ) +% +% Create, Verify, or Repair Par2 redundancy files for +% 'filename'. Arguments: +% +% op: a string that is either 'c', 'v', or 'r' for create, +% verify or repair respectively. +% +% filename: the .par2 or .bin file to which 'op' is applied. +% +% Progress is reported to the command window. +% +function [res] = Par2( s, op, file ) + + res = 0; + + if( ~strcmp( op, 'v' ) && ~strcmp( op, 'r' ) && ~strcmp( op, 'c' ) ) + error( 'Par2: Op must be one of ''v'', ''r'' or ''c''.' ); + end + + ChkConn( s ); + + if( IsRunning( s ) ) + error( 'Par2: Cannot use while running.' ); + end + + ok = CalinsNetMex( 'sendstring', s.handle, ... + sprintf( 'PAR2 %s %s\n', op, file ) ); + + while( 1 ) + + line = CalinsNetMex( 'readline', s.handle ); + + if( strcmp( line, 'OK' ) ) + res = 1; + break; + end + + if( ~isempty( line ) ) + fprintf( '%s\n', line ); + end + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/PauseGraphs.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/PauseGraphs.m new file mode 100644 index 00000000..6bf25154 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/PauseGraphs.m @@ -0,0 +1,13 @@ +% myobj = PauseGraphs( myobj, bool_flag ) +% +% Pause Graphs window displays. +% Note: The displays are updated at ~10 Hz. +% +function [s] = PauseGraphs( s, b ) + + if( ~isnumeric( b ) ) + error( 'PauseGraphs: Arg 2 must be a Boolean value {0,1}.' ); + end + + DoSimpleCmd( s, sprintf( 'PAUSEGRF %d', b ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ReceiveOK.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ReceiveOK.m new file mode 100644 index 00000000..43706f02 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ReceiveOK.m @@ -0,0 +1,10 @@ +% THIS FUNCTION IS PRIVATE AND SHOULD NOT BE CALLED BY OUTSIDE CODE! +% +function [] = ReceiveOK( sm, cmd ) + + line = CalinsNetMex( 'readline', sm.handle ); + + if( isempty( strfind( line, 'OK' ) ) ) + error( 'After cmd [%s] got [%s] but expected ''OK''.\n', cmd, line ); + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ReceiveREADY.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ReceiveREADY.m new file mode 100644 index 00000000..35c3cf16 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/ReceiveREADY.m @@ -0,0 +1,10 @@ +% THIS FUNCTION IS PRIVATE AND SHOULD NOT BE CALLED BY OUTSIDE CODE! +% +function [] = ReceiveREADY( sm, cmd ) + + line = CalinsNetMex( 'readline', sm.handle ); + + if( isempty( strfind( line, 'READY' ) ) ) + error( 'After cmd [%s] got [%s] but expected ''READY''.\n', cmd, line ); + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAnatomy_Pinpoint.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAnatomy_Pinpoint.m new file mode 100644 index 00000000..c9bbcb13 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAnatomy_Pinpoint.m @@ -0,0 +1,19 @@ +% myobj = SetAnatomy_Pinpoint( myobj, 'shankdat' ) +% +% Set anatomy data string with Pinpoint format: +% [probe-id,shank-id](startpos,endpos,R,G,B,rgnname)(startpos,endpos,R,G,B,rgnname)...() +% - probe-id: SpikeGLX logical probe id. +% - shank-id: [0..n-shanks]. +% - startpos: region start in microns from tip. +% - endpos: region end in microns from tip. +% - R,G,B: region color as RGB, each [0..255]. +% - rgnname: region name text. +% +function [s] = SetAnatomy_Pinpoint( s, shankdat ) + + if( ~ischar( shankdat ) ) + error( 'SetAnatomy_Pinpoint: Argument must be a string.' ); + end + + DoSimpleCmd( s, sprintf( 'SETANATOMYPP %s', shankdat ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAudioEnable.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAudioEnable.m new file mode 100644 index 00000000..f701590d --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAudioEnable.m @@ -0,0 +1,18 @@ +% myobj = SetAudioEnable( myobj, bool_flag ) +% +% Set audio output on/off. Note that this command has +% no effect if not currently running. +% +function [s] = SetAudioEnable( s, b ) + + if( ~isnumeric( b ) ) + error( 'SetAudioEnable: Arg 2 must be a Boolean value {0,1}.' ); + end + + if( ~IsRunning( s ) ) + warning( 'SetAudioEnable: Not running, command ignored.' ); + return; + end + + DoSimpleCmd( s, sprintf( 'SETAUDIOENABLE %d', b ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAudioParams.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAudioParams.m new file mode 100644 index 00000000..c09c0779 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetAudioParams.m @@ -0,0 +1,49 @@ +% myobj = SetAudioParams( myobj, group_string, params_struct ) +% +% Set subgroup of parameters for audio-out operation. Parameters +% are a struct of name/value pairs. This call stops current output. +% Call SetAudioEnable( myobj, 1 ) to restart it. +% +function [s] = SetAudioParams( s, group, params ) + + if( ~ischar( group ) ) + error( 'SetAudioParams: ''group'' argument must be a string.' ); + end + + if( ~length( group ) ) + error( 'SetAudioParams: ''group'' argument must not be empty.' ); + end + + if( ~isstruct( params ) ) + error( 'SetAudioParams: ''params'' argument must be a struct.' ); + end + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, ... + sprintf( 'SETAUDIOPARAMS %s\n', group ) ); + + ReceiveREADY( s, 'SETAUDIOPARAMS' ); + + names = fieldnames( params ); + + for i = 1:length( names ) + + f = params.(names{i}); + + if( isnumeric( f ) && isscalar( f ) ) + line = sprintf( '%s=%g\n', names{i}, f ); + elseif( ischar( f ) ) + line = sprintf( '%s=%s\n', names{i}, f ); + else + error( 'SetAudioParams: Field %s must be numeric scalar or a string.', names{i} ); + end + + ok = CalinsNetMex( 'sendstring', s.handle, line ); + end + + % end with blank line + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( '\n' ) ); + + ReceiveOK( s, 'SETAUDIOPARAMS' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetDataDir.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetDataDir.m new file mode 100644 index 00000000..22903ba9 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetDataDir.m @@ -0,0 +1,13 @@ +% myobj = SetDataDir( myobj, idir, 'dir' ) +% +% Set ith global data directory. +% Set required parameter idir to zero for main data directory. +% +function [s] = SetDataDir( s, idir, dir ) + + if( ~ischar( dir ) ) + error( 'SetDataDir: ''dir'' argument must be a string.' ); + end + + DoSimpleCmd( s, sprintf( 'SETDATADIR %d %s', idir, dir ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetMetaData.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetMetaData.m new file mode 100644 index 00000000..bfab7f3d --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetMetaData.m @@ -0,0 +1,39 @@ +% myobj = SetMetadata( myobj, metadata_struct ) +% +% If a run is in progress, set metadata to be added to the +% next output file-set. Metadata must be in the form of a +% struct of name/value pairs. +% +function [s] = SetMetadata( s, meta ) + + if( ~isstruct( meta ) ) + error( 'SetMetaData: Argument must be a struct.' ); + end + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( 'SETMETADATA\n' ) ); + ReceiveREADY( s, 'SETMETADATA' ); + + names = fieldnames( meta ); + + for i = 1:length( names ) + + f = meta.(names{i}); + + if( isnumeric( f ) && isscalar( f ) ) + line = sprintf( '%s=%g\n', names{i}, f ); + elseif( ischar( f ) ) + line = sprintf( '%s=%s\n', names{i}, f ); + else + error( 'SetMetaData: Field %s must be numeric scalar or a string.', names{i} ); + end + + ok = CalinsNetMex( 'sendstring', s.handle, line ); + end + + % end with blank line + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( '\n' ) ); + + ReceiveOK( s, 'SETMETADATA' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetMultiDriveEnable.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetMultiDriveEnable.m new file mode 100644 index 00000000..f0991d4a --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetMultiDriveEnable.m @@ -0,0 +1,12 @@ +% myobj = SetMultiDriveEnable( myobj, bool_flag ) +% +% Set multi-drive run-splitting on/off. +% +function [s] = SetMultiDriveEnable( s, b ) + + if( ~isnumeric( b ) ) + error( 'SetMultiDriveEnable: Arg 2 must be a Boolean value {0,1}.' ); + end + + DoSimpleCmd( s, sprintf( 'SETMULTIDRIVEENABLE %d', b ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetNextFileName.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetNextFileName.m new file mode 100644 index 00000000..f5522dcc --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetNextFileName.m @@ -0,0 +1,40 @@ +% myobj = SetNextFileName( myobj, 'name' ) +% +% For only the next trigger (file writing event) this overrides +% all auto-naming, giving you complete control of where to save +% the files, the file name, and what g- and t-indices you want +% (if any). For example, regardless of the run's current data dir, +% run name and indices, if you set: 'otherdir/yyy_g5/yyy_g5_t7', +% SpikeGLX will save the next files in flat directory yyy_g5/: +% - otherdir/yyy_g5/yyy.g5_t7.nidq.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec0.ap.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec0.lf.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec1.ap.bin,meta +% - otherdir/yyy_g5/yyy.g5_t7.imec1.lf.bin,meta +% - etc. +% +% - The destination directory must already exist...No parent directories +% or probe subfolders are created in this naming mode. +% - The run must already be in progress. +% - Neither the custom name nor its indices are displayed in the Graphs +% window toolbars. Rather, the toolbars reflect standard auto-names. +% - After writing this file set, the override is cleared and auto-naming +% will resume as if you never called setNextFileName. You have to call +% setNextFileName before each trigger event to create custom trial series. +% For example, you can build a software-triggered t-series using sequence: +% + setNextFileName( 'otherdir/yyy_g0/yyy_g0_t0' ) +% + setRecordingEnable( 1 ) +% + setRecordingEnable( 0 ) +% + setNextFileName( 'otherdir/yyy_g0/yyy_g0_t1' ) +% + setRecordingEnable( 1 ) +% + setRecordingEnable( 0 ) +% + etc. +% +function [s] = SetNextFileName( s, name ) + + if( ~ischar( name ) ) + error( 'SetNextFileName: Argument must be a string.' ); + end + + DoSimpleCmd( s, sprintf( 'SETNEXTFILENAME %s', name ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParams.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParams.m new file mode 100644 index 00000000..a7f514ee --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParams.m @@ -0,0 +1,43 @@ +% myobj = SetParams( myobj, params_struct ) +% +% The inverse of GetParams.m, this sets run parameters. +% Alternatively, you can pass the parameters to StartRun() +% which calls this in turn. Run parameters are a struct of +% name/value pairs. The call will error if a run is currently +% in progress. +% +% Note: You can set any subset of [DAQSettings]. +% +function [s] = SetParams( s, params ) + + if( ~isstruct( params ) ) + error( 'SetParams: Argument must be a struct.' ); + end + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( 'SETPARAMS\n' ) ); + ReceiveREADY( s, 'SETPARAMS' ); + + names = fieldnames( params ); + + for i = 1:length( names ) + + f = params.(names{i}); + + if( isnumeric( f ) && isscalar( f ) ) + line = sprintf( '%s=%g\n', names{i}, f ); + elseif( ischar( f ) ) + line = sprintf( '%s=%s\n', names{i}, f ); + else + error( 'SetParams: Field %s must be numeric scalar or a string.', names{i} ); + end + + ok = CalinsNetMex( 'sendstring', s.handle, line ); + end + + % end with blank line + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( '\n' ) ); + + ReceiveOK( s, 'SETPARAMS' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsImecCommon.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsImecCommon.m new file mode 100644 index 00000000..1c9aafbf --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsImecCommon.m @@ -0,0 +1,42 @@ +% myobj = SetParamsImecCommon( myobj, params_struct ) +% +% The inverse of GetParamsImecCommon.m, this sets parameters +% common to all enabled probes. Parameters are a struct of +% name/value pairs. The call will error if a run is currently +% in progress. +% +% Note: You can set any subset of [DAQ_Imec_All]. +% +function [s] = SetParamsImecCommon( s, params ) + + if( ~isstruct( params ) ) + error( 'SetParamsImecCommon: Argument must be a struct.' ); + end + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( 'SETPARAMSIMALL\n' ) ); + ReceiveREADY( s, 'SETPARAMSIMALL' ); + + names = fieldnames( params ); + + for i = 1:length( names ) + + f = params.(names{i}); + + if( isnumeric( f ) && isscalar( f ) ) + line = sprintf( '%s=%g\n', names{i}, f ); + elseif( ischar( f ) ) + line = sprintf( '%s=%s\n', names{i}, f ); + else + error( 'SetParamsImecCommon: Field %s must be numeric scalar or a string.', names{i} ); + end + + ok = CalinsNetMex( 'sendstring', s.handle, line ); + end + + % end with blank line + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( '\n' ) ); + + ReceiveOK( s, 'SETPARAMSIMALL' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsImecProbe.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsImecProbe.m new file mode 100644 index 00000000..5276a63a --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsImecProbe.m @@ -0,0 +1,44 @@ +% myobj = SetParamsImecProbe( myobj, params_struct, ip ) +% +% The inverse of GetParamsImecProbe.m, this sets parameters +% for a given logical probe. Parameters are a struct of +% name/value pairs. The call will error if file writing +% is currently in progress. +% +% Note: You can set any subset of fields under [SerialNumberToProbe]/SNjjj. +% +function [s] = SetParamsImecProbe( s, params, ip ) + + if( ~isstruct( params ) ) + error( 'SetParamsImecProbe: Argument must be a struct.' ); + end + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, ... + sprintf( 'SETPARAMSIMPRB %d\n',ip ) ); + + ReceiveREADY( s, 'SETPARAMSIMPRB' ); + + names = fieldnames( params ); + + for i = 1:length( names ) + + f = params.(names{i}); + + if( isnumeric( f ) && isscalar( f ) ) + line = sprintf( '%s=%g\n', names{i}, f ); + elseif( ischar( f ) ) + line = sprintf( '%s=%s\n', names{i}, f ); + else + error( 'SetParamsImecProbe: Field %s must be numeric scalar or a string.', names{i} ); + end + + ok = CalinsNetMex( 'sendstring', s.handle, line ); + end + + % end with blank line + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( '\n' ) ); + + ReceiveOK( s, 'SETPARAMSIMPRB' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsOnebox.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsOnebox.m new file mode 100644 index 00000000..1f1ad517 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetParamsOnebox.m @@ -0,0 +1,50 @@ +% myobj = SetParamsOneBox( myobj, params_struct, ip, slot ) +% +% The inverse of GetParamsOneBox.m, this sets params +% for a selected OneBox. Parameters are a struct of +% name/value pairs. +% +% To reference a OneBox configured as a recording stream +% set ip to its stream-id; if ip >= 0, slot is ignored. +% Any selected OneBox can also be referenced by setting +% ip = -1, and giving its slot index. +% +% The call will error if a run is currently in progress. +% +% Note: You can set any subset of fields under [SerialNumberToOneBox]/SNjjj. +% +function [s] = SetParamsOneBox( s, params, ip, slot ) + + if( ~isstruct( params ) ) + error( 'SetParamsOneBox: Argument must be a struct.' ); + end + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, ... + sprintf( 'SETPARAMSOBX %d %d\n', ip, slot ) ); + + ReceiveREADY( s, 'SETPARAMSOBX' ); + + names = fieldnames( params ); + + for i = 1:length( names ) + + f = params.(names{i}); + + if( isnumeric( f ) && isscalar( f ) ) + line = sprintf( '%s=%g\n', names{i}, f ); + elseif( ischar( f ) ) + line = sprintf( '%s=%s\n', names{i}, f ); + else + error( 'SetParamsOneBox: Field %s must be numeric scalar or a string.', names{i} ); + end + + ok = CalinsNetMex( 'sendstring', s.handle, line ); + end + + % end with blank line + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( '\n' ) ); + + ReceiveOK( s, 'SETPARAMSOBX' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetRecordingEnable.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetRecordingEnable.m new file mode 100644 index 00000000..21a81d9b --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetRecordingEnable.m @@ -0,0 +1,16 @@ +% myobj = SetRecordingEnable( myobj, bool_flag ) +% +% Set gate (file writing) on/off during run. +% +% When auto-naming is in effect, opening the gate advances +% the g-index and resets the t-index to zero. Auto-naming is +% on unless SetNextFileName has been used to override it. +% +function [s] = SetRecordingEnable( s, b ) + + if( ~isnumeric( b ) ) + error( 'SetRecordingEnable: Arg 2 must be a Boolean value {0,1}.' ); + end + + DoSimpleCmd( s, sprintf( 'SETRECORDENAB %d', b ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetRunName.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetRunName.m new file mode 100644 index 00000000..959b6702 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetRunName.m @@ -0,0 +1,13 @@ +% myobj = SetRunName( myobj, 'name' ) +% +% Set the run name for the next time files are created +% (either by trigger, SetRecordingEnable() or by StartRun()). +% +function [s] = SetRunName( s, name ) + + if( ~ischar( name ) ) + error( 'SetRunName: Argument must be a string.' ); + end + + DoSimpleCmd( s, sprintf( 'SETRUNNAME %s', name ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetTriggerOffBeep.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetTriggerOffBeep.m new file mode 100644 index 00000000..325c6e17 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetTriggerOffBeep.m @@ -0,0 +1,9 @@ +% myobj = SetTriggerOffBeep( myobj, hertz, millisec ) +% +% During a run, set frequency and duration of Windows +% beep signaling file closure. hertz=0 disables the beep. +% +function [s] = SetTriggerOffBeep( s, hertz, millisec ) + + DoSimpleCmd( s, sprintf( 'SETTRIGGEROFFBEEP %d %d', hertz, millisec ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetTriggerOnBeep.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetTriggerOnBeep.m new file mode 100644 index 00000000..9291f88f --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SetTriggerOnBeep.m @@ -0,0 +1,9 @@ +% myobj = SetTriggerOnBeep( myobj, hertz, millisec ) +% +% During a run set frequency and duration of Windows +% beep signaling file creation. hertz=0 disables the beep. +% +function [s] = SetTriggerOnBeep( s, hertz, millisec ) + + DoSimpleCmd( s, sprintf( 'SETTRIGGERONBEEP %d %d', hertz, millisec ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SpikeGL.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SpikeGL.m new file mode 100644 index 00000000..dd06f9ae --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/SpikeGL.m @@ -0,0 +1,35 @@ +% myobj = SpikeGL() +% myobj = SpikeGL( host ) +% myobj = SpikeGL( host, port ) +% +% Construct a new @SpikeGL instance and immediately attempt +% a network connection. If omitted, the defaults for host and +% port are {'localhost, 4142}. +% +function [s] = SpikeGL( varargin ) + + host = 'localhost'; + port = 4142; + + if( nargin >= 1 ) + host = varargin{1}; + end + + if( nargin >= 2 ) + port = varargin{2}; + end + + if( ~ischar( host ) || ~isnumeric( port ) ) + error( 'SpikeGL: Host must be a string; port must be a number.' ); + end + + s = struct; + s.host = host; + s.port = port; + s.in_chkconn = 0; + s.handle = -1; + s.ver = ''; + + s = class( s, 'SpikeGL' ); + s = ChkConn( s ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/StartRun.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/StartRun.m new file mode 100644 index 00000000..4792c0fa --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/StartRun.m @@ -0,0 +1,45 @@ +% myobj = StartRun( myobj ) +% myobj = StartRun( myobj, params_struct ) +% myobj = StartRun( myobj, 'runName' ) +% +% Start data acquisition run. Optional second argument (params) +% is a struct of name/value pairs as returned from GetParams.m. +% Alternatively, the second argument can be a string (runName). +% Last-used parameters remain in effect if not specified here. +% An error is flagged if already running or a parameter is bad. +% +function [s] = StartRun( varargin ) + + s = varargin{1}; + rname = []; + params = []; + + if( nargin > 1 ) + + arg2 = varargin{2}; + + if( ischar( arg2 ) ) + rname = arg2; + elseif( isstruct( arg2 ) ) + params = arg2; + else + error( 'StartRun: Invalid second argument; must be a string or struct.' ); + end + end + + if( IsRunning( s ) ) + error( 'StartRun: Already running.' ); + end + + if( isempty( params ) ) + params = GetParams( s ); + end + + if( ~isempty( rname ) ) + params.snsRunName = rname; + end + + SetParams( s, params ); + + DoSimpleCmd( s, 'STARTRUN' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/StopRun.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/StopRun.m new file mode 100644 index 00000000..101ed21b --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/StopRun.m @@ -0,0 +1,9 @@ +% myobj = StopRun( myobj ) +% +% Unconditionally stop current run, close data files +% and return to idle state. +% +function [s] = StopRun( s ) + + DoSimpleCmd( s, 'STOPRUN' ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/TriggerGT.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/TriggerGT.m new file mode 100644 index 00000000..cd81aa26 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/TriggerGT.m @@ -0,0 +1,19 @@ +% myobj = TriggerGT( myobj, g, t ) +% +% Using standard auto-naming, set both the gate (g) and +% trigger (t) levels that control file writing. +% -1 = no change. +% 0 = set low. +% 1 = increment and set high. +% E.g., triggerGT( -1, 1 ) = same g, increment t, start writing. +% +% - TriggerGT only affects the 'Remote controlled' gate type and/or +% the 'Remote controlled' trigger type. +% - The 'Enable Recording' button, when shown, is a master override +% switch. TriggerGT is blocked until you click the button or call +% SetRecordingEnable. +% +function [s] = TriggerGT( s, g, t ) + + DoSimpleCmd( s, sprintf( 'TRIGGERGT %d %d', g, t ) ); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/VerifySha1.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/VerifySha1.m new file mode 100644 index 00000000..071aa11f --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/@SpikeGL/VerifySha1.m @@ -0,0 +1,31 @@ +% res = VerifySha1( myobj, 'filename' ) +% +% Verifies the SHA1 sum of the file specified by filename. +% If filename is relative, it is appended to the run dir. +% Absolute path/filenames are also supported. Since this is +% a potentially long operation, it uses the 'disp' command +% to print progress information to the MATLAB console. The +% returned value is 1 if verified, 0 otherwise. +% +function [res] = VerifySha1( s, file ) + + res = 0; + + ChkConn( s ); + + ok = CalinsNetMex( 'sendstring', s.handle, sprintf( 'VERIFYSHA1 %s\n', file ) ); + + while( 1 ) + + line = CalinsNetMex( 'readline', s.handle ); + + if( strcmp( line, 'OK' ) ) + res = 1; + break; + end + + if( ~isempty( line ) ) + fprintf( 'Verify progress: %s%%\n', line ); + end + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/CalinsNetMex.mexw64 b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/CalinsNetMex.mexw64 new file mode 100644 index 00000000..b32c1a30 Binary files /dev/null and b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/API/CalinsNetMex.mexw64 differ diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/CalinsNetMex.cpp b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/CalinsNetMex.cpp new file mode 100644 index 00000000..2420c888 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/CalinsNetMex.cpp @@ -0,0 +1,792 @@ + +#include "NetClient.h" + +#include +#include + +#include + +#include +#include + +/* ---------------------------------------------------------------- */ +/* Shared-Mem-Filename -------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#ifdef WIN32 + +#include + +class SMF { +private: + const char *mapName; + HANDLE hMapFile; + void *pBuf; + const uint BUF_SIZE; +public: + SMF() + : mapName("Global\\SpikeGLFileNameShm"), + hMapFile(NULL), pBuf(NULL), BUF_SIZE(1024) {} + virtual ~SMF() {detach();} + + string getName(); + +private: + void attach(); + void detach(); +}; + + +string SMF::getName() +{ + attach(); + + if( hMapFile && pBuf ) + return (char*)pBuf; + + return "Not attached to shared memory."; +} + + +void SMF::attach() +{ + if( hMapFile ) + return; + + hMapFile = OpenFileMappingA( + FILE_MAP_ALL_ACCESS, // read/write access + FALSE, // do not inherit the name + mapName ); // name of mapping object + + if( !hMapFile ) { + mexWarnMsgTxt( + "Could not attach to SpikeGLFileNameShm" + " -- OpenFileMappingA() failed!." ); + return; + } + + pBuf = MapViewOfFile( + hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write perms + 0, // view offset-hi + 0, // view offset-lo + BUF_SIZE ); // view length + + if( !pBuf ) { + detach(); + mexWarnMsgTxt( + "Could not attach to SpikeGLFileNameShm" + " -- MapViewOfFile() failed." ); + } +} + + +void SMF::detach() +{ + if( !hMapFile ) + return; + + if( pBuf ) { + UnmapViewOfFile( pBuf ); + pBuf = NULL; + } + + if( hMapFile ) { + CloseHandle( hMapFile ); + hMapFile = NULL; + } +} + + +#else + +class SMF { +public: + string getName() {return "Requires Windows OS.";} +}; + +#endif + +/* ---------------------------------------------------------------- */ +/* Types ---------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +typedef map NetClientMap; + +/* ---------------------------------------------------------------- */ +/* Macros --------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#ifndef _MSC_VER +#define _strcmpi strcasecmp +#endif + +#define RETURN( x ) \ + do { \ + plhs[0] = mxCreateDoubleScalar( static_cast(x) ); \ + return; \ + } while(0) + +#define RETURN_NULL() \ + do { \ + plhs[0] = mxCreateDoubleMatrix( 0, 0, mxREAL ); \ + return; \ + } while(0) + +/* ---------------------------------------------------------------- */ +/* Statics -------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +static SMF smf; +static NetClientMap clientMap; +static int handleId = 0; // keeps getting incremented.. + +/* ---------------------------------------------------------------- */ +/* Helpers -------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +static NetClient *MapFind( int handle ) +{ + NetClientMap::iterator it = clientMap.find( handle ); + + if( it == clientMap.end() ) + return NULL; + + return it->second; +} + + +static void MapPut( int handle, NetClient *client ) +{ + NetClient *old = MapFind( handle ); + + if( old ) + delete old; + + clientMap[handle] = client; +} + + +static void MapDestroy( int handle ) +{ + NetClientMap::iterator it = clientMap.find( handle ); + + if( it != clientMap.end() ) { + + delete it->second; + clientMap.erase( it ); + } + else { + mexWarnMsgTxt( + "Invalid or unknown handle passed" + " to CalinsNetMex MapDestroy." ); + } +} + + +static int GetHandle( int nrhs, const mxArray *prhs[] ) +{ + if( nrhs < 1 ) + mexErrMsgTxt( "Need numeric handle argument." ); + + const mxArray *handle = prhs[0]; + + if( !mxIsDouble( handle ) + || mxGetM( handle ) != 1 + || mxGetN( handle ) != 1 ) { + + mexErrMsgTxt( "Handle must be a scalar double value." ); + } + + return static_cast(*mxGetPr( handle )); +} + + +static NetClient *GetNetClient( int nrhs, const mxArray *prhs[] ) +{ + NetClient *nc = MapFind( GetHandle( nrhs, prhs ) ); + + if( !nc ) { + mexErrMsgTxt( + "INTERNAL ERROR -- Cannot find the NetClient" + " for the specified handle in CalinsNetMex." ); + } + + return nc; +} + +/* ---------------------------------------------------------------- */ +/* Handlers ------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +// handle = createNewClient( host, port ) +// +void createNewClient( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs != 1 ) { + mexErrMsgTxt( "createNewClient: Returns handle to LHS." ); + } + + if( nrhs != 2 ) { + mexErrMsgTxt( "createNewClient: Requires RHS: host, port." ); + } + + const mxArray *host = prhs[0], + *port = prhs[1]; + + if( !mxIsChar( host ) || mxGetM( host ) != 1 ) + mexErrMsgTxt( "createNewClient: Hostname must be a string row vector." ); + + if( !mxIsDouble( port ) + || mxGetM( port ) != 1 + || mxGetN( port ) != 1 ) { + + mexErrMsgTxt( "createNewClient: Port must be a scalar numeric value." ); + } + + char *hostStr = mxArrayToString( host ); + uint16 portNum = static_cast(*mxGetPr( port )); + NetClient *nc = new NetClient( hostStr, portNum ); + int h = handleId++; + + mxFree( hostStr ); + + MapPut( h, nc ); + + RETURN( h ); +} + + +// destroyClient( handle ) +// +void destroyClient( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + MapDestroy( GetHandle( nrhs, prhs ) ); +} + + +// ok = tryConnection( handle ) +// +void tryConnection( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs < 1 ) + mexErrMsgTxt( "tryConnection: Returns ok to LHS." ); + + NetClient *nc = GetNetClient( nrhs, prhs ); + + try { + + if( !nc->tcpConnect() ) { + + mexWarnMsgTxt( nc->errorReason().c_str() ); + RETURN_NULL(); + } + } + catch( const exception &e ) { + + if( e.what()[0] ) + mexWarnMsgTxt( e.what() ); + + RETURN_NULL(); + } + + RETURN( 1 ); +} + + +// closeSocket( handle ) +// +void closeSocket( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + NetClient *nc = GetNetClient( nrhs, prhs ); + + nc->tcpDisconnect(); +} + + +// ok = sendString( handle, string ) +// +void sendString( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs < 1 ) + mexErrMsgTxt( "sendString: Returns ok to LHS." ); + + if( nrhs != 2 ) + mexErrMsgTxt( "sendString: Requires RHS: handle, string." ); + + if( mxGetClassID( prhs[1] ) != mxCHAR_CLASS ) + mexErrMsgTxt( "sendString: Arg 2 must be a string." ); + + char *tmp = mxArrayToString( prhs[1] ); + string theString = tmp; + mxFree( tmp ); + + NetClient *nc = GetNetClient( nrhs, prhs ); + + try { + nc->sendString( theString ); + } + catch( const exception &e ) { + + if( e.what()[0] ) + mexWarnMsgTxt( e.what() ); + + RETURN_NULL(); + } + + RETURN( 1 ); +} + + +// ok = sendMatrix( handle, matrix ) +// +void sendMatrix( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs < 1 ) + mexErrMsgTxt( "sendMatrix: Returns ok to LHS." ); + + if( nrhs != 2 ) + mexErrMsgTxt( "sendMatrix: Requires RHS: handle, matrix." ); + + ulong datalen = ulong(mxGetM( prhs[1] ) * mxGetN( prhs[1] )); + + switch( mxGetClassID( prhs[1] ) ) { + + case mxINT8_CLASS: + case mxUINT8_CLASS: + case mxCHAR_CLASS: + break; + case mxINT16_CLASS: + case mxUINT16_CLASS: + datalen *= sizeof(short); + break; + case mxUINT32_CLASS: + case mxINT32_CLASS: + datalen *= sizeof(int); + break; + case mxSINGLE_CLASS: + datalen *= sizeof(float); + break; + case mxDOUBLE_CLASS: + datalen *= sizeof(double); + break; + default: + mexErrMsgTxt( "sendMatrix: Sent matrix must have numeric type." ); + } + + NetClient *nc = GetNetClient( nrhs, prhs ); + void *theMatrix = mxGetPr( prhs[1] ); + + try { + nc->sendData( theMatrix, datalen ); + } + catch( const exception &e ) { + + if( e.what()[0] ) + mexErrMsgTxt( e.what() ); + + RETURN_NULL(); + } + + RETURN( 1 ); +} + + +// Fetch next line, excluding ERROR lines. +// Return as single string. +// +void readLine( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs < 1 ) + mexErrMsgTxt( "readLine: Returns string to LHS." ); + + NetClient *nc = GetNetClient( nrhs, prhs ); + + try { + vector line; + nc->rcvLine( line ); + + if( line.size() ) { + + const char *s = &line[0]; + + if( !strncmp( s, "ERROR", 5 ) ) + throw std::runtime_error( s ); + + plhs[0] = mxCreateString( s ); + } + } + catch( const exception &e ) { + + if( e.what()[0] ) + mexWarnMsgTxt( e.what() ); + + RETURN_NULL(); + } +} + + +// Fetch one line, excluding {OK, ERROR} lines. +// Return as single string. +// +void queryString( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs < 1 ) + mexErrMsgTxt( "queryString: Returns string to LHS." ); + + NetClient *nc = GetNetClient( nrhs, prhs ); + + try { + vector line; + + for(;;) { + + nc->rcvLine( line ); + + if( line.size() ) { + + const char *s = &line[0]; + + if( !strcmp( s, "OK" ) ) + return; + + if( !strncmp( s, "ERROR", 5 ) ) + throw std::runtime_error( s ); + + plhs[0] = mxCreateString( s ); + } + } + } + catch( const exception &e ) { + + if( e.what()[0] ) + mexErrMsgTxt( e.what() ); + + RETURN_NULL(); + } +} + + +// Fetch one or more lines, excluding {OK, ERROR} lines. +// Return as cell-array of strings. +// +void getCells( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs < 1 ) + mexErrMsgTxt( "getCells: Returns cells{} to LHS." ); + + NetClient *nc = GetNetClient( nrhs, prhs ); + + try { + vector > vlines; + nc->rcvLines( vlines ); + + mwSize m = mwSize(vlines.size()); + + if( m ) { + + plhs[0] = mxCreateCellArray( 1, &m ); + + for( int i = 0; i < m; ++i ) + mxSetCell( plhs[0], i, mxCreateString( &vlines[i][0] ) ); + } + } + catch( const exception &e ) { + + if( e.what()[0] ) + mexErrMsgTxt( e.what() ); + + RETURN_NULL(); + } +} + + +// matrix = readMatrix( handle, datatype, dims-vector ) +// +void readMatrix( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ +// ------------- +// Validate args +// ------------- + + int ndims, datalen; + + if( !nlhs ) + mexErrMsgTxt( "readMatrix: Returns matrix to LHS." ); + + if( nrhs < 3 + || !mxIsChar( prhs[1] ) + || !mxIsDouble( prhs[2] ) + || (ndims = int(mxGetN( prhs[2] ))) < 2 + || mxGetM( prhs[2] ) != 1 ) { + + mexErrMsgTxt( + "readMatrix: Needs arguments:\n" + " (1) Handle\n" + " (2) String datatype {'double', 'single', 'uint8'}\n" + " (3) Vector of dims {1x2, 1x3, 1x4} holding m,n[,o,p]" ); + } + +// ---------------- +// Get datatype str +// ---------------- + + const char *str; + int buflen = int(mxGetM( prhs[1] ) * mxGetN( prhs[1] )) + 1; + string stdstr( buflen, '0' ); + + mxGetString( prhs[1], &stdstr[0], buflen ); + str = stdstr.c_str(); + +// ------------------------------------- +// Calculate data size (product of dims) +// ------------------------------------- + + double *pdims = mxGetPr( prhs[2] ); + + if( ndims > 4 ) + ndims = 4; + + int dims[] = { + static_cast(pdims[0]), + static_cast(pdims[1]), + (ndims >= 3 ? static_cast(pdims[2]) : 1), + (ndims >= 4 ? static_cast(pdims[3]) : 1) + }; + + datalen = dims[0] * dims[1] * dims[2] * dims[3]; + +// -------------------------- +// Adjust sizing for datatype +// -------------------------- + + mxClassID cls; + + if( !_strcmpi( str, "double" ) ) { + cls = mxDOUBLE_CLASS; + datalen *= sizeof(double); + } + else if( !_strcmpi( str, "single" ) ) { + cls = mxSINGLE_CLASS; + datalen *= sizeof(float); + } + else if( !_strcmpi( str, "int8" ) ) { + cls = mxINT8_CLASS; + datalen *= sizeof(char); + } + else if( !_strcmpi( str, "uint8" ) ) { + cls = mxUINT8_CLASS; + datalen *= sizeof(char); + } + else if( !_strcmpi( str, "int16" ) ) { + cls = mxINT16_CLASS; + datalen *= sizeof(short); + } + else if( !_strcmpi( str, "uint16" ) ) { + cls = mxUINT16_CLASS; + datalen *= sizeof(short); + } + else if( !_strcmpi( str, "int32" ) ) { + cls = mxINT32_CLASS; + datalen *= sizeof(int); + } + else if( !_strcmpi( str, "uint32" ) ) { + cls = mxUINT32_CLASS; + datalen *= sizeof(int); + } + else { + mexErrMsgTxt( + "readMatrix: Output matrix type must be one of {" + "'single', 'double', 'int8', 'uint8'," + " 'int16', 'uint16', 'int32', 'uint32'." ); + } + +// -------------------- +// Allocate destination +// -------------------- + + plhs[0] = mxCreateNumericArray( ndims, dims, cls, mxREAL ); + +// -------- +// Get data +// -------- + + NetClient *nc = GetNetClient( nrhs, prhs ); + + try { + nc->receiveData( mxGetData( plhs[0] ), datalen ); + } + catch( const exception &e ) { + + if( e.what()[0] ) + mexErrMsgTxt( e.what() ); + + mxDestroyArray( plhs[0] ); + plhs[0] = 0; + + RETURN_NULL(); + } +} + + +// filename = fastGetFilename() +// +void fastGetFilename( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ + if( nlhs < 1 ) + mexErrMsgTxt( "fastGetFilename: Returns filename to LHS." ); + + plhs[0] = mxCreateString( smf.getName().c_str() ); +} + +/* ---------------------------------------------------------------- */ +/* Dispatch - Entry point ----------------------------------------- */ +/* ---------------------------------------------------------------- */ + +typedef void (*T_Mexfunc)( int, mxArray**, int, const mxArray** ); + +static map cmd2fun; + + +// var = mexFunction( cmdstring, var ) +// +void mexFunction( + int nlhs, + mxArray *plhs[], + int nrhs, + const mxArray *prhs[] ) +{ +// --------------------------------------- +// Build function table (lowercase keys!!) +// --------------------------------------- + + if( !cmd2fun.size() ) { + cmd2fun["create"] = createNewClient; + cmd2fun["destroy"] = destroyClient; + cmd2fun["connect"] = tryConnection; + cmd2fun["disconnect"] = closeSocket; + cmd2fun["sendstring"] = sendString; + cmd2fun["sendmatrix"] = sendMatrix; + cmd2fun["readline"] = readLine; + cmd2fun["querystring"] = queryString; + cmd2fun["getcells"] = getCells; + cmd2fun["readmatrix"] = readMatrix; + cmd2fun["getspikeglfilenamefromshm"] = fastGetFilename; + } + +// ------------------ +// At least two args? +// ------------------ + + const mxArray *cmd; + string scmd, + serr; + + if( nrhs < 2 ) { + serr += "CalinsNetMex requires at least two RHS args.\n"; + goto usage; + } + else + cmd = prhs[0]; + +// -------------------- +// Command is a string? +// -------------------- + + if( !mxIsChar( cmd ) ) { + serr += "CalinsNetMex arg 1 must be a string.\n"; + goto usage; + } + + if( mxGetM( cmd ) != 1 ) { + serr += "CalinsNetMex arg 1 must be a row vector.\n"; + goto usage; + } + +// ------------------------- +// Convert command to string +// ------------------------- + + { + char *tmp = mxArrayToString( cmd ); + scmd = tmp; + mxFree( tmp ); + } + +// -------------------------------- +// Dispatch using lowercase command +// -------------------------------- + + { + transform( scmd.begin(), scmd.end(), scmd.begin(), tolower ); + + map::iterator it = cmd2fun.find( scmd ); + + if( it != cmd2fun.end() ) { + it->second( nlhs, plhs, nrhs - 1, prhs + 1 ); + return; + } + } + + serr += "CalinsNetMex unknown command <" + scmd + ">.\n"; + +// ----- +// Error +// ----- + +usage: + serr += "Legal commands:\n"; + + map::iterator it = cmd2fun.begin(); + + while( it != cmd2fun.end() ) + serr += " - " + it++->first + "\n"; + + mexErrMsgTxt( serr.c_str() ); +} + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/NetClient.cpp b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/NetClient.cpp new file mode 100644 index 00000000..f9b820b9 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/NetClient.cpp @@ -0,0 +1,223 @@ + +#include "NetClient.h" + +#include + + + + +NetClient::NetClient( + const string &host, + uint16 port, + uint read_timeout_secs ) + : Socket(TCP), read_timeout_secs(read_timeout_secs) +{ + setHost( host ); + setPort( port ); +} + + +uint NetClient::sendData( const void *src, uint srcBytes ) noexcept(false) +{ + static const uint maxsend = 2*1024*1024; + + vbuf.clear(); // clear response + + if( !srcBytes ) + return 0; + + uint sent = 0; + const char *buf = static_cast(src); + + while( sent < srcBytes ) { + + uint tosend = srcBytes - sent; + + if( tosend > maxsend ) + tosend = maxsend; + + sent += Socket::sendData( buf + sent, tosend ); + } + + return sent; +} + + +uint NetClient::receiveData( void *dst, uint dstBytes ) noexcept(false) +{ + static const uint maxrecv = 2*1024*1024; + + if( !dstBytes ) + return 0; + + ulong recvd = 0, + nB = ulong(vbuf.size()); + char *buf = static_cast(dst); + + if( nB ) { + + recvd = nB; + + if( recvd > dstBytes ) + recvd = dstBytes; + + memcpy( buf, &vbuf[0], recvd ); + + if( (nB -= recvd) > 0 ) + memcpy( &vbuf[0], &vbuf[recvd], nB ); + + vbuf.resize( nB ); + } + + while( recvd < dstBytes ) { + + uint retval, + nR = nReadyForRead(); + + if( nR ) { + + if( nR > dstBytes - recvd ) + nR = dstBytes - recvd; + + if( nR > maxrecv ) + nR = maxrecv; + + retval = Socket::receiveData( buf + recvd, nR ); + recvd += retval; + + if( retval == 0 ) + break; + } + else if( !waitData( 1000 * read_timeout_secs ) ) + throw std::runtime_error("receiveData: Receive timed out."); + else if( !nReadyForRead() ) { + + // In this failure mode... + // flush out garbage byte for next time + char dump[4]; + Socket::receiveData( dump, 1 ); + throw std::runtime_error("receiveData: Data transfer interrupted."); + } + } + + return recvd; +} + + +uint NetClient::sendString( const string &s ) noexcept(false) +{ + vbuf.clear(); // clear response + + return Socket::sendData( s.data(), uint(s.length()) ); +} + + +// Return one line; newline stripped, null-terminated. +// +void NetClient::rcvLine( vector &line ) noexcept(false) +{ + line.clear(); + + for(;;) { + + char *v0, *vN; + uint nB = uint(vbuf.size()); + + // ------------------ + // Is '\n' in buffer? + // ------------------ + + if( nB && (vN = (char*)memchr( v0 = &vbuf[0], '\n', nB )) ) { + + // ------------- + // Copy data out + // ------------- + + *vN++ = 0; // convert '\n' to null + + uint nL = uint(vN - v0); + + line.resize( nL ); + memcpy( &line[0], v0, nL ); + + // ----------------------- + // Remove line from buffer + // ----------------------- + + if( (nB -= nL) > 0 ) + memcpy( v0, vN, nB ); + + vbuf.resize( nB ); + break; + } + + // ----------------------- + // Else, buffer more chars + // ----------------------- + + uint nR = nReadyForRead(); + + if( nR ) { + + vbuf.resize( nB + nR ); + Socket::receiveData( &vbuf[nB], nR ); + } + else if( !waitData( 1000 * read_timeout_secs ) ) { + + // nothing to read - quit. + line.push_back( 0 ); + throw std::runtime_error("rcvLine: Receive timed out."); + } + else { + + // should be something to read after the wait + + uint nR = nReadyForRead(); + + if( nR ) { + vbuf.resize( nB + nR ); + Socket::receiveData( &vbuf[nB], nR ); + } + else { + // In this failure mode... + // flush out garbage character for next time + vbuf.resize( nB + 1 ); + Socket::receiveData( &vbuf[nB], 1 ); + throw std::runtime_error("rcvLine: Data transfer interrupted."); + } + } + } +} + + +// Return true if OK received, data excludes OK. +// Return false if ERROR received, ERROR line is thrown. +// +bool NetClient::rcvLines( vector > &vlines ) noexcept(false) +{ + vlines.clear(); + + vector line; + + for(;;) { + + rcvLine( line ); + + if( line.size() ) { + + const char *s = &line[0]; + + if( !strcmp( s, "OK" ) ) + return true; + + if( !strncmp( s, "ERROR", 5 ) ) { + throw std::runtime_error( (string("rcvLines: ") + s).c_str() ); + return false; + } + + vlines.push_back( line ); + } + } +} + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/NetClient.h b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/NetClient.h new file mode 100644 index 00000000..fa6be996 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/NetClient.h @@ -0,0 +1,43 @@ +#ifndef NETCLIENT_H +#define NETCLIENT_H + +/* ---------------------------------------------------------------- */ +/* Includes ------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#include "Socket.h" + +#include +using namespace std; + +/* ---------------------------------------------------------------- */ +/* NetClient ------------------------------------------------------ */ +/* ---------------------------------------------------------------- */ + +class NetClient : public Socket +{ +private: + vector vbuf; // response buffer + uint read_timeout_secs; + +public: + NetClient( + const string &host = "localhost", + uint16 port = 0, + uint read_timeout_secs = 10 ); + + virtual uint sendData( const void *src, uint srcBytes ) noexcept(false); + + virtual uint receiveData( + void *dst, + uint dstBytes ) noexcept(false); + + uint sendString( const string &s ) noexcept(false); + + void rcvLine( vector &line ) noexcept(false); + bool rcvLines( vector > &vlines ) noexcept(false); +}; + +#endif // NETCLIENT_H + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/Socket.cpp b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/Socket.cpp new file mode 100644 index 00000000..67ca2d3d --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/Socket.cpp @@ -0,0 +1,625 @@ + +//#include "stdafx.h" // enable if using Visual Studio precompiled headers + +#include "Socket.h" + +#include +#include + +#define CONNCLOSED std::runtime_error("Connection closed by peer.") + + +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* WINDOWS -------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#ifdef WIN32 // WINDOWS + +#include +#include + +static std::string WSAGetLastErrorMessage( + const char *prefix = "", + int errorid = 0 ); + +typedef int socklen_t; + +#define SHUT_RDWR 2 +#define CLOSE( x ) closesocket( x ) +#define IOCTL( x, y, z ) ioctlsocket( x, y, z ) +#define LASTERROR_STR( prefix ) WSAGetLastErrorMessage( prefix ) +#define LASTERROR_IS_CONNCLOSED() \ + (WSAGetLastError() == WSAENETRESET || \ + WSAGetLastError() == WSAECONNABORTED || \ + WSAGetLastError() == WSAECONNRESET) + +static bool StartupCalled = false; +static WSADATA wsaData; + +/* ---------------------------------------------------------------- */ +/* DoCleanup ------------------------------------------------------ */ +/* ---------------------------------------------------------------- */ + +// Called from atexit() as C-fun + +extern "C" void DoCleanup() +{ + if( StartupCalled ) + WSACleanup(); + + StartupCalled = false; +} + +/* ---------------------------------------------------------------- */ +/* DO_STARTUP ----------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +static inline void DO_STARTUP() +{ + if( !StartupCalled ) { + + if( WSAStartup( 1<<8 | 1, &wsaData ) ) { + + throw std::runtime_error( + "Could not start up winsock dll," + " WSAStartup() failed." ); + } + + atexit( DoCleanup ); + StartupCalled = true; + } +} + +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* UNIX ----------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#else // UNIX + +#include +#include +#include +#include +#include + +static std::string pfxerr( const char *prefix, const char *serr ); + +#define DO_STARTUP() do { } while(0) +#define CLOSE( x ) close( x ) +#define IOCTL( x, y, z ) ioctl( x, y, z ) +#define LASTERROR_STR( prefix ) pfxerr( prefix, std::strerror( errno ) ) +#define LASTERROR_IS_CONNCLOSED() \ + (errno == ECONNRESET || errno == ENOTCONN || errno == EPIPE) + +#endif // WIN32 or UNIX + +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* Common --------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +Socket::Socket( int type ) + : m_addr(0), m_host("localhost"), m_error("Success"), + m_sock(-1), m_type(type), m_port(-1), + m_tcpNDelay(true), m_reuseAddr(true) +{ + DO_STARTUP(); + m_addr = new struct sockaddr_in; + m_addr->sin_family = AF_INET; +} + + +Socket::~Socket() +{ + tcpDisconnect(); + delete m_addr; + m_addr = 0; +} + + +bool Socket::tcpConnect( const std::string &host, uint16 port ) noexcept(false) +{ + tcpDisconnect(); + + if( host.length() ) + setHost( host ); + + if( port ) + setPort( port ); + + switch( m_type ) { + + case UDP: + m_sock = socket( PF_INET, SOCK_DGRAM, IPPROTO_IP ); + break; + + default: + m_sock = socket( PF_INET, SOCK_STREAM, IPPROTO_IP ); + break; + } + + if( !isValid() ) { + m_error = LASTERROR_STR( "tcpConnect: Invalid socket" ); + throw std::runtime_error( m_error ); + return false; + } + + if( m_type == TCP ) { + + setTCPNDelay(); + resolveHostAddr(); + + if( connect( + m_sock, + reinterpret_cast(m_addr), + sizeof(*m_addr) ) ) { + + m_error = LASTERROR_STR( "tcpConnect: Can't connect" ); + CLOSE( m_sock ); + m_sock = -1; + throw std::runtime_error( m_error ); + + return false; + } + } + + return true; +} + + +void Socket::tcpDisconnect() +{ + if( isValid() ) { + + shutdown( m_sock, SHUT_RDWR ); + CLOSE( m_sock ); + } + + m_sock = -1; +} + + +void Socket::setSocketOption( int option, bool enable ) +{ + if( !isValid() ) + return; + + switch( option ) { + + case TCPNoDelay: + m_tcpNDelay = enable; + setTCPNDelay(); + break; + + case ReuseAddr: + m_reuseAddr = enable; + setReuseAddr(); + break; + } +} + + +bool Socket::bind( const std::string &iface, uint16 port ) noexcept(false) +{ + setReuseAddr(); + + struct sockaddr_in addr; + + addr.sin_addr.s_addr = inet_addr( iface.c_str() ); + addr.sin_port = htons( port ); + addr.sin_family = AF_INET; + + if( ::bind( + m_sock, + reinterpret_cast(&addr), + sizeof(addr) ) != 0 ) { + + m_error = LASTERROR_STR( __func__ ); + throw std::runtime_error( m_error ); + + return false; + } + + return true; +} + + +uint Socket::sendData( const void *src, uint srcBytes ) noexcept(false) +{ + if( !srcBytes ) + return 0; + + int count = 0; + +// TODO: non-blocking IO here!? + + if( m_type == UDP ) { + + // Datagram/UDP + resolveHostAddr(); + + count = sendto( + m_sock, + static_cast(src), + srcBytes, + 0, + reinterpret_cast(m_addr), + sizeof(*m_addr) ); + } + else { + + // Stream/TCP + count = send( + m_sock, + static_cast(src), + srcBytes, + 0 ); + } + + if( count < 0 ) { + + if( LASTERROR_IS_CONNCLOSED() ) + throw CONNCLOSED; + + m_error = LASTERROR_STR( __func__ ); + throw std::runtime_error( m_error ); + } + else if( count == 0 ) + throw std::runtime_error(std::string("sendData: EOF on socket to ") + m_host); + + return count; +} + + +uint Socket::receiveData( void *dst, uint dstBytes ) noexcept(false) +{ + if( !dstBytes ) + return 0; + + int count = 0; + +// TODO: non-blocking IO here!? + + if( m_type == UDP ) { + + // Datagram/UDP + socklen_t fromlen = sizeof(*m_addr); + + resolveHostAddr(); + + count = recvfrom( + m_sock, + static_cast(dst), + dstBytes, + 0, + reinterpret_cast(m_addr), + &fromlen ); + } + else { + + // Stream/TCP + count = recv( + m_sock, + static_cast(dst), + dstBytes, + 0 ); + } + + if( count < 0 ) { + + if( LASTERROR_IS_CONNCLOSED() ) + throw CONNCLOSED; + + m_error = LASTERROR_STR( __func__ ); + throw std::runtime_error( m_error ); + } + else if( count == 0 ) + throw std::runtime_error(std::string("receiveData: EOF on socket to ") + m_host); + + return count; +} + + +bool Socket::waitData( uint waitMS ) noexcept(false) +{ + if( !isValid() ) + return false; + + int sec = waitMS / 1000, + ms = waitMS - 1000 * sec; + + struct timeval tv; + tv.tv_sec = sec; + tv.tv_usec = 1000 * ms; + + fd_set readfds; + FD_ZERO( &readfds ); + FD_SET( m_sock, &readfds ); + + int ret = select( m_sock + 1, &readfds, 0, 0, &tv ); + +// Ready + if( ret > 0 ) + return true; + +// !Ready + if( ret == 0 ) + return false; + +// Error + if( LASTERROR_IS_CONNCLOSED() ) + throw CONNCLOSED; + +// Else + m_error = LASTERROR_STR( __func__ ); + throw std::runtime_error( m_error ); + + return false; +} + + +uint Socket::nReadyForRead() noexcept(false) +{ + if( !isValid() ) + return 0; + +#ifdef WIN32 + ulong n = 0; +#else + int n = 0; +#endif + + if( IOCTL( m_sock, FIONREAD, &n ) == 0 ) + return (uint)n; + + if( LASTERROR_IS_CONNCLOSED() ) + throw CONNCLOSED; + + m_error = LASTERROR_STR( __func__ ); + throw std::runtime_error( m_error ); + + return 0; +} + +/* ---------------------------------------------------------------- */ +/* Private -------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +void Socket::resolveHostAddr() +{ + resolveHostAddr( *m_addr, m_host, m_port ); +} + + +void Socket::resolveHostAddr( + struct sockaddr_in &addr, + const std::string &host, + uint16 port ) +{ + struct hostent *he = gethostbyname( host.c_str() ); + + if( !he ) { + + throw std::runtime_error( + host + + " is not found by the resolver (" + + LASTERROR_STR( "" ) + + ")." ); + } + + memcpy( &addr.sin_addr.s_addr, he->h_addr, he->h_length ); + addr.sin_port = htons( port ); + addr.sin_family = AF_INET; +} + + +void Socket::setTCPNDelay() const +{ +#ifdef WIN32 + BOOL flag = m_tcpNDelay; + int ret = setsockopt( + m_sock, + IPPROTO_TCP, + TCP_NODELAY, + reinterpret_cast(&flag), + sizeof(flag) ); +#else + long flag = m_tcpNDelay; + int ret = setsockopt( + m_sock, + IPPROTO_TCP, + TCP_NODELAY, + &flag, + sizeof(flag) ); +#endif + + if( ret != 0 ) + throw std::runtime_error( "Could not set TCP No Delay." ); +} + + +void Socket::setReuseAddr() const +{ +#ifdef WIN32 + BOOL flag = m_reuseAddr; + int ret = setsockopt( + m_sock, + SOL_SOCKET , + SO_REUSEADDR, + reinterpret_cast(&flag), + sizeof(flag) ); +#else + long flag = m_reuseAddr; + int ret = setsockopt( + m_sock, + SOL_SOCKET, + SO_REUSEADDR, + &flag, + sizeof(flag) ); +#endif + + if( ret != 0 ) + throw std::runtime_error( "Could not set SO_REUSEADDR." ); +} + +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* WINDOWS -------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#ifdef WIN32 + +#include +#include + +/* ---------------------------------------------------------------- */ +/* ErrorEntry ----------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +// List of Winsock error constants mapped to an interpretation string. +// Note that this list must remain sorted by the error constant +// values, because we do a binary search on the list when looking up +// items. + +static struct ErrorEntry { + const char* msg; + int nID; + + ErrorEntry( int id, const char* msg = 0 ) : msg(msg), nID(id) {} + + bool operator< ( const ErrorEntry &rhs ) {return nID < rhs.nID;} + +} gaErrorList[] = { + ErrorEntry(0, "No error"), + ErrorEntry(WSAEINTR, "Interrupted system call"), + ErrorEntry(WSAEBADF, "Bad file number"), + ErrorEntry(WSAEACCES, "Permission denied"), + ErrorEntry(WSAEFAULT, "Bad address"), + ErrorEntry(WSAEINVAL, "Invalid argument"), + ErrorEntry(WSAEMFILE, "Too many open sockets"), + ErrorEntry(WSAEWOULDBLOCK, "Operation would block"), + ErrorEntry(WSAEINPROGRESS, "Operation now in progress"), + ErrorEntry(WSAEALREADY, "Operation already in progress"), + ErrorEntry(WSAENOTSOCK, "Socket operation on non-socket"), + ErrorEntry(WSAEDESTADDRREQ, "Destination address required"), + ErrorEntry(WSAEMSGSIZE, "Message too long"), + ErrorEntry(WSAEPROTOTYPE, "Protocol wrong type for socket"), + ErrorEntry(WSAENOPROTOOPT, "Bad protocol option"), + ErrorEntry(WSAEPROTONOSUPPORT, "Protocol not supported"), + ErrorEntry(WSAESOCKTNOSUPPORT, "Socket type not supported"), + ErrorEntry(WSAEOPNOTSUPP, "Operation not supported on socket"), + ErrorEntry(WSAEPFNOSUPPORT, "Protocol family not supported"), + ErrorEntry(WSAEAFNOSUPPORT, "Address family not supported"), + ErrorEntry(WSAEADDRINUSE, "Address already in use"), + ErrorEntry(WSAEADDRNOTAVAIL, "Can't assign requested address"), + ErrorEntry(WSAENETDOWN, "Network is down"), + ErrorEntry(WSAENETUNREACH, "Network is unreachable"), + ErrorEntry(WSAENETRESET, "Net connection reset"), + ErrorEntry(WSAECONNABORTED, "Software caused connection abort"), + ErrorEntry(WSAECONNRESET, "Connection reset by peer"), + ErrorEntry(WSAENOBUFS, "No buffer space available"), + ErrorEntry(WSAEISCONN, "Socket is already connected"), + ErrorEntry(WSAENOTCONN, "Socket is not connected"), + ErrorEntry(WSAESHUTDOWN, "Can't send after socket shutdown"), + ErrorEntry(WSAETOOMANYREFS, "Too many references, can't splice"), + ErrorEntry(WSAETIMEDOUT, "Connection timed out"), + ErrorEntry(WSAECONNREFUSED, "Connection refused"), + ErrorEntry(WSAELOOP, "Too many levels of symbolic links"), + ErrorEntry(WSAENAMETOOLONG, "File name too long"), + ErrorEntry(WSAEHOSTDOWN, "Host is down"), + ErrorEntry(WSAEHOSTUNREACH, "No route to host"), + ErrorEntry(WSAENOTEMPTY, "Directory not empty"), + ErrorEntry(WSAEPROCLIM, "Too many processes"), + ErrorEntry(WSAEUSERS, "Too many users"), + ErrorEntry(WSAEDQUOT, "Disc quota exceeded"), + ErrorEntry(WSAESTALE, "Stale NFS file handle"), + ErrorEntry(WSAEREMOTE, "Too many levels of remote in path"), + ErrorEntry(WSASYSNOTREADY, "Network system is unavailable"), + ErrorEntry(WSAVERNOTSUPPORTED, "Winsock version out of range"), + ErrorEntry(WSANOTINITIALISED, "WSAStartup not yet called"), + ErrorEntry(WSAEDISCON, "Graceful shutdown in progress"), + ErrorEntry(WSAHOST_NOT_FOUND, "Host not found"), + ErrorEntry(WSANO_DATA, "No host data of that type was found") +}; + +static const int kNumMessages = sizeof(gaErrorList) / sizeof(ErrorEntry); + +/* ---------------------------------------------------------------- */ +/* WSAGetLastErrorMessage ----------------------------------------- */ +/* ---------------------------------------------------------------- */ + +// A function similar in spirit to Unix's perror() that tacks a canned +// interpretation of the value of WSAGetLastError() onto the end of a +// passed string, separated by a ": ". +// +std::string WSAGetLastErrorMessage( + const char *prefix, + int errorid ) +{ +// Build basic error string + + std::ostringstream outs; + + if( prefix && *prefix ) + outs << prefix << ": "; + +// Tack appropriate canned message onto end of supplied message +// prefix. Note that we do a binary search here: gaErrorList must +// be sorted by error constant value. + + ErrorEntry Target( errorid ? errorid : WSAGetLastError() ); + ErrorEntry *pEnd = gaErrorList + kNumMessages; + ErrorEntry *it = std::lower_bound( gaErrorList, pEnd, Target ); + + if( it != pEnd && it->nID == Target.nID ) + outs << it->msg; + else + outs << "unknown error"; + + outs << " (" << Target.nID << ")"; + +// Terminate message + + outs << std::ends; + + return outs.str(); +} + +#else // UNIX + +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* UNIX ----------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#include + +/* ---------------------------------------------------------------- */ +/* pfxerr --------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +// if( prefix ) Return prefix + ": " + serr. +// else Return serr. +// +std::string pfxerr( const char *prefix, const char *serr ) +{ + std::ostringstream outs; + + if( prefix && *prefix ) + outs << prefix << ": "; + + outs << serr << std::ends; + + return outs.str(); +} + +#endif + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/Socket.h b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/Socket.h new file mode 100644 index 00000000..5e5b98f4 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/Socket.h @@ -0,0 +1,80 @@ +#ifndef SOCKET_H +#define SOCKET_H + +/* ---------------------------------------------------------------- */ +/* Includes ------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +#include +#include + +struct sockaddr_in; + +/* ---------------------------------------------------------------- */ +/* Types ---------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +typedef unsigned short uint16; +typedef unsigned int uint; +typedef unsigned long ulong; + +/* ---------------------------------------------------------------- */ +/* Socket --------------------------------------------------------- */ +/* ---------------------------------------------------------------- */ + +// NB: UDP is untested and probably doesn't work +class Socket +{ +private: + typedef int Sock_t; + + struct sockaddr_in *m_addr; + std::string m_host, + m_error; + Sock_t m_sock; + int m_type; + uint16 m_port; + bool m_tcpNDelay, + m_reuseAddr; + +public: + enum SocketType { TCP, UDP }; + enum SocketOption { TCPNoDelay, ReuseAddr }; + + Socket( int type = TCP ); + virtual ~Socket(); + + void setHost( const std::string &host ) {m_host = host;} + const std::string &host() const {return m_host;} + + void setPort( uint16 port ) {m_port = port;} + uint16 port() const {return m_port;} + + bool tcpConnect( const std::string &host = "", uint16 port = 0 ) noexcept(false); + void tcpDisconnect(); + + bool isValid() const {return m_sock > -1;} + std::string errorReason() const {return m_error;} + + void setSocketOption( int option, bool enable ); + bool bind( const std::string &iface = "0.0.0.0", uint16 port = 0 ) noexcept(false); + + virtual uint sendData( const void *src, uint srcBytes ) noexcept(false); + virtual uint receiveData( void *dst, uint dstBytes ) noexcept(false); + + bool waitData( uint waitMS = 10 ) noexcept(false); + uint nReadyForRead() noexcept(false); + +private: + void resolveHostAddr(); + static void resolveHostAddr( + struct sockaddr_in &addr, + const std::string &host, + uint16 port ); + void setTCPNDelay() const; + void setReuseAddr() const; +}; + +#endif // SOCKET_H + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/makemex64.bat b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/makemex64.bat new file mode 100644 index 00000000..c54f3352 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Build/makemex64.bat @@ -0,0 +1,2 @@ +"C:\Program Files\MATLAB\R2018b\bin\win64\mex" -DWIN32 -compatibleArrayDims -I. CalinsNetMex.cpp Socket.cpp NetClient.cpp -lWS2_32 -outdir ..\API +pause diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/DemoRemoteAPI.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/DemoRemoteAPI.m new file mode 100644 index 00000000..cc1aeb63 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/DemoRemoteAPI.m @@ -0,0 +1,141 @@ +% ----------------------------------------- +% Variety of remote calls with timing stats +% ----------------------------------------- +% +function DemoRemoteAPI + +% Create connection (edit the IP address) +hSGL = SpikeGL(); + +% Don't call repeatedly if not running +if IsRunning(hSGL) + numCalls = 100; +else + numCalls = 1; +end + +% Init the timing measurements +getTimes = zeros(numCalls, 1); + +% ----------------------------------------------- +% Init parameters outside timing loop (as needed) +% ----------------------------------------------- + +% subset = GetStreamSaveChans(hSGL, 2, 0); +% subset = subset(1:64); +% subset = [10,66,192,193,194,195]; + +% ------------------------ +% Start of the timing loop +% ------------------------ + +for i=1:numCalls + +% --------------------- +% tic = start the clock +% --------------------- + + t0 = tic; + +% ------------------------- +% Demo setting audio params +% ------------------------- + +% prm = struct(); +% prm.stream = 'nidq'; +% SetAudioParams(hSGL, 'AOCtl_All', prm); +% prm.left = 0; +% prm.right = 0; +% prm.loCut = 'OFF'; +% prm.hiCut = 'INF'; +% prm.volume = 4.0; +% SetAudioParams(hSGL, 'AOCtl_Stream_-1', prm); +% SetAudioEnable(hSGL, 1); + +% ---------------------- +% Demo setting meta data +% ---------------------- + +% meta = struct(); +% meta.animal = 'Mr. Mouse'; +% meta.code = 'llrj17W0'; +% meta.trial = 19; +% SetMetaData(hSGL, meta); + +% ------------------------------- +% Variety of set/get calls to try +% ------------------------------- + + param = GetParams(hSGL) +% SetParams(hSGL, param); + +% count = GetStreamSampleCount(hSGL, 2, 0) + file = GetDataDir(hSGL) +% IsConsoleHidden(hSGL) +% ConsoleShow(hSGL); + % EnumDataDir(hSGL) +% Par2(hSGL, 'v', 'C:/SGL_DATA/myRun_g0_t0.nidq.bin'); +% VerifySha1(hSGL, 'C:/SGL_DATA/myRun_g0_t0.nidq.bin') +% StopRun(hSGL); +% StartRun(hSGL); +% NI_DO_Set(hSGL, 'PXI1Slot4/port0/line4', hex2dec('FF')) +% OBX_AO_Set(hSGL, -1, 21, '(5,-1.25)') + SetRunName(hSGL, 'myRun_g5_t5'); +% SetRecordingEnable(hSGL, 1); +% IsSaving(hSGL) +% a = GetStreamAcqChans(hSGL, 0, 0) +% a = GetStreamSaveChans(hSGL, 0, 0) + +% ----------------------- +% Fetch data for graphing +% ----------------------- + +% mat = FetchLatest(hSGL, 2, 0, 2000); + +% -------------------- +% toc = stop the clock +% -------------------- + + getTimes(i) = toc(t0); + +% --------------- +% Progress report +% --------------- + + if mod(i, 50) == 0 + fprintf('Completed %d calls...\n', i); + end + +% ----------------------- +% Graph fetched data here +% ----------------------- + +% showdata(mat); + +end % timing loop + +% ------------ +% Timing stats +% ------------ + +fprintf('Execution time -- mean: %g ms\tstd: %g ms\n', ... + 1000*mean(getTimes), 1000*std(getTimes)); + +end % DemoRemoteAPI + + +% ---------------------- +% Tiny graphing function +% ---------------------- +% +function showdata(mat) + x = 1:size(mat,1); + y = mat(:,1); + figure(1); + % set(gcf, 'doublebuffer', 'on'); + p = plot(x, y); + set(p, 'XData', x, 'YData', y); + drawnow; +end % showdata + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/JWave/jwave.meta b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/JWave/jwave.meta new file mode 100644 index 00000000..45b372ac --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/JWave/jwave.meta @@ -0,0 +1,6 @@ +[WaveMeta] +sample_frequency_Hz_dbl=25000 +wave_Vmax_dbl=2.5 +device_Vmax_dbl=5 +data_type_txt_i16_f32=txt +num_samples_i32=0 diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/JWave/jwave.txt b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/JWave/jwave.txt new file mode 100644 index 00000000..44e4544e --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/JWave/jwave.txt @@ -0,0 +1,6 @@ +do 5 { + level( 0, 50 ) + ramp( 0, 0.5, 10 ) + level( 0.5, 100 ) + ramp( 0.5, 0, 10 ) +} \ No newline at end of file diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/LatencyTest.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/LatencyTest.m new file mode 100644 index 00000000..27965686 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/LatencyTest.m @@ -0,0 +1,59 @@ +% Continuously fetch LFP-band data from imec probe-0. +% Threshold channel 393 @ 0.45 mV. +% Send digital out command tracking threshold crossings. +% +% Runs until error or Ctrl-C. +% +% To measure closed-loop latency, immerse the probe in +% saline and give it a square wave signal (1 mV p-p, 1 Hz). +% We fetch all 384 channels of these data in a remote program. +% We analyze one of these channels looking for a rising edge. +% We then react to that threshold crossing by commanding an +% NI device to make another edge that is sent to the probe's +% SMA connector as a digital input. Now the separation between +% the LFP threshold event and the resulting NI event gives a +% direct readout of closed-loop latency. We measure the typical +% closed-loop latency to be 6.5 ms using the MATLAB API. +% +function LatencyTest + +hSGL = SpikeGL('127.0.0.1'); + +mv2i16 = 1.0/(1200.0/250/1024); % assume default gain of 250 for NP 1.0 +line = 'Dev4/port0/line5'; % edit for your setup +js = 2; +ip = 0; +id = 1 + (393 - 384); % add 1 for MATLAB +thresh = 0.45*mv2i16; +bits = 0; +channels = [384:768]; % zero-based for SpikeGLX + +fromCt = GetStreamSampleCount( hSGL, js, ip ); + +NI_DO_Set( hSGL, line, bits ); + +while 1 + + [M,headCt] = Fetch( hSGL, js, ip, fromCt, 120, channels ); + + [tpts,~] = size(M); + + if tpts > 1 + v_diff = M(tpts,id) - M(1,id); + + if v_diff > thresh && bits == 0 + bits = hex2dec('FF'); + NI_DO_Set( hSGL, line, bits ); + elseif v_diff < -thresh && bits == hex2dec('FF') + bits = 0; + NI_DO_Set( hSGL, line, bits ); + end + + fromCt = headCt + tpts; + end + +end + +end + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_ni_soft_start.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_ni_soft_start.m new file mode 100644 index 00000000..8c55e624 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_ni_soft_start.m @@ -0,0 +1,41 @@ +% Plays wave 'jwave' at NI channel AO-0. +% Playback is triggered by software command. +% +function wp_ni_soft_start + + % Handle to SpikeGLX + hSGL = SpikeGL( '127.0.0.1' ); + + % Load the wave plan, select infinite looping + wave_name = 'jwave'; + outChan = 'PXI1Slot6_2/ao0'; + looping = 1; + NI_Wave_Load( hSGL, outChan, wave_name, looping ); + + % Select software triggering + trigTerm = 'software'; + NI_Wave_Arm( hSGL, outChan, trigTerm ); + + % Start playback now + start = 1; + NI_Wave_StartStop( hSGL, outChan, start ); + + % This section demonstrates a method to capture your + % wave plan in action. The best pause parameters will + % depend upon the duration of your wave plan and how + % fast your SpikeGLX graphs are sweeping + % + % Let this play for 1 second + % Then pause the SpikeGLX Graphs Window for 2 seconds + % Then resume Graphs for 5 seconds + pause( 1.0 ); + PauseGraphs( hSGL, 1 ); + pause( 2.0 ); + PauseGraphs( hSGL, 0 ); + pause( 5.0 ); + + % Stop playback + start = 0; + NI_Wave_StartStop( hSGL, outChan, start ); + +end % wp_ni_soft_start diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_soft_start.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_soft_start.m new file mode 100644 index 00000000..ca26f6cb --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_soft_start.m @@ -0,0 +1,45 @@ +% Plays wave 'jwave' at OneBox channel AO-0. +% Playback is triggered by software command. +% +function wp_soft_start + + % Handle to SpikeGLX + hSGL = SpikeGL( '127.0.0.1' ); + + % For demo purposes we assume the OneBox is not recording... + % So we refer to it using its slot index + ip = -1; + slot = 21; + + % Load the wave plan + wave_name = 'jwave'; + OBX_Wave_Load( hSGL, ip, slot, wave_name ); + + % Select software triggering and infinite looping + trigger = -2; + looping = 1; + OBX_Wave_Arm( hSGL, ip, slot, trigger, looping ); + + % Start playback now, output is always at AO-0 + start = 1; + OBX_Wave_StartStop( hSGL, ip, slot, start ); + + % This section demonstrates a method to capture your + % wave plan in action. The best pause parameters will + % depend upon the duration of your wave plan and how + % fast your SpikeGLX graphs are sweeping + % + % Let this play for 1 second + % Then pause the SpikeGLX Graphs Window for 2 seconds + % Then resume Graphs for 5 seconds + pause( 1.0 ); + PauseGraphs( hSGL, 1 ); + pause( 2.0 ); + PauseGraphs( hSGL, 0 ); + pause( 5.0 ); + + % Stop playback + start = 0; + OBX_Wave_StartStop( hSGL, ip, slot, start ); + +end % wp_soft_start diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_trig_start.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_trig_start.m new file mode 100644 index 00000000..bab1fd56 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Demos/wp_trig_start.m @@ -0,0 +1,55 @@ +% Plays wave 'jwave' at OneBox channel AO-0. +% Playback is triggered when OneBox channel AI-1 goes high. +% User needs to list AI-1 in the XA box of OBX Setup tab. +% We will configure NI device to send TTL rising edge from line-4. +% User needs to wire NI line-4 to OneBox AI-1. +% +function wp_trig_start + + % Handle to SpikeGLX + hSGL = SpikeGL( '127.0.0.1' ); + + % OneBox assumed to be recording stream ip=0... + % So the slot number is ignored in this case + ip = 0; + slot = -1; + + % Load the wave plan + wave_name = 'jwave'; + OBX_Wave_Load( hSGL, ip, slot, wave_name ); + + % Select AI-1 triggering and no looping + trigger = 1; + looping = 0; + OBX_Wave_Arm( hSGL, ip, slot, trigger, looping ); + + % Configure NI line-4 + digOutTerm = '/PXI1Slot6_2/line4'; + digBits = 0x10; % binary 1 at bit-4, zero elsewhere + NI_DO_Set( hSGL, digOutTerm, 0 ); + + % Start playback now, output is always at AO-0 + NI_DO_Set( hSGL, digOutTerm, digBits ); + + % Reset trigger after 50 ms + pause( 0.05 ); + NI_DO_Set( hSGL, digOutTerm, 0 ); + + % This section demonstrates a method to capture your + % wave plan in action. The best pause parameters will + % depend upon the duration of your wave plan and how + % fast your SpikeGLX graphs are sweeping + % + % Let this play for 1 second + % Then pause the SpikeGLX Graphs Window for 2 seconds + % Then resume Graphs + pause( 1.0 ); + PauseGraphs( hSGL, 1 ); + pause( 2.0 ); + PauseGraphs( hSGL, 0 ); + + % Stop playback + start = 0; + OBX_Wave_StartStop( hSGL, ip, slot, start ); + +end % wp_trig_start diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/Contents.txt b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/Contents.txt new file mode 100644 index 00000000..0f757945 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/Contents.txt @@ -0,0 +1,44 @@ + +======== +Contents +======== + +API folder +========== + +API contains a complete prebuilt set of API components: + + + @SpikeGL // place in client MATLAB project directory + + CalinsNetMex.mexw64 // place in client MATLAB project directory + +Build folder +============ + +Note: Building is optional: the API components are prebuilt. + +Build contains the sources and make(*.bat) file to generate CalinsNetMex.mexw64: + + + CalinsNetMex.mexw64 // generated in API directory + +Demos folder +============ + +Tools demonstrating how to use the API. + +- JWave folder: This is a waveform you can play with the wp_XXX scripts. Copy these files into the _Waves folder at the top level of your SpikeGLX folder. + +- DemoRemoteAPI.m +- LatencyTest.m +- wp_ni_soft_start.m +- wp_soft_start.m +- wp_trig_start.m + +Docs folder +=========== + +- Contents.txt +- GettingStarted.txt +- Help.txt +- WhatsNew.txt + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/GettingStarted.txt b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/GettingStarted.txt new file mode 100644 index 00000000..46ea6a8a --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/GettingStarted.txt @@ -0,0 +1,65 @@ +==================================== +Talking live to SpikeGLX from MATLAB +==================================== + + +SpikeGLX Setup +============== + +1. Launch SpikeGLX on the data acquisition machine. This machine and the MATLAB client machine must be connected to the same network. + +2. Open SpikeGLX dialog 'Options/Command Server Settings...'. + +3. Click 'Enable Remote Command Server' and then click 'My Address'. + +4. Write down the IP address; you'll need to type that into the MATLAB client code. + +Note: You can run SpikeGLX and MATLAB on the same machine, and in this configuration you can either use the computer's actual network address (per step 3), or the 'loopback' address if you don't have a network connection on this computer. Every computer has an internal 'loopback' address set to '127.0.0.1'. Use that with default port number 4142. + +Note: If the script times out (about 10 seconds) when trying to connect to SpikeGLX, it may be Windows Firewall blocking access. You might try adding SpikeGLX to the allowed list of applications in the Firewall controls. + + +MATLAB Setup +============ + +Needed components are located within Release subfolder 'SpikeGLX-MATLAB-SDK/API'. + +1. Create a folder for your MATLAB client project, such as 'My_SGLX_MATLAB'. + +2. Copy '@SpikeGL' and 'CalinsNetMex.mexw64' from API into 'My_SGLX_MATLAB'. + +3. To get started with example code, copy 'DemoRemoteAPI.m' into 'My_SGLX_MATLAB'. + +In summary, your folder looks like: + + My_SGLX_MATLAB + @SpikeGL + CalinsNetMex.mexw64 + DemoRemoteAPI.m + + +Running MATLAB +============== + +1. Launch MATLAB, add 'My_SGLX_MATLAB' to your path. + +2. Navigate to 'My_SGLX_MATLAB' and open 'DemoRemoteAPI.m'. + +3. Edit the line 'hSGL = SpikeGL('10.101.20.29');' to reflect the correct IP address. + +4. Go to the section called 'Variety of set/get calls to try' and choose a trial test function by uncommenting it. For example, try 'file = GetDataDir(hSGL)' which will return the data directory whether SpikeGLX is running an acquisition or is currently idle. + +5. Experiment with other commands (only a few are demonstrated in this m file). + + +Next +==== + +The full set of commands is in file '@SpikeGL/Contents.m'. + +'LatencyTest.m' is a simple demonstration of real-time fetching and +processing of streaming imec probe data. + +{wp_XXX.m} demonstrate calling the waveplayer from a script. + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/Help.txt b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/Help.txt new file mode 100644 index 00000000..97bb55f3 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/Help.txt @@ -0,0 +1,74 @@ +======== +SYNOPSIS +======== + +The @SpikeGL API class is a MATLAB object with methods to access the SpikeGLX program via TCP/IP. On top of that, our message exchange protocol implements acknowledgement, validation and error reporting. SpikeGLX and MATLAB can run on the same machine (via loopback socket address 127.0.0.1) or across a network. + +The API provides extensive control over a running SpikeGLX process: starting and stopping a run, setting parameters, fetching live data, calling the Par2 and SHA1 tools, and so on. Everything you need to integate SpikeGLX into your workflow. + +Users (clients) of this class merely need to construct an instance of a @SpikeGL object and all network communication with the SpikeGLX server process is handled automatically. + +The network socket handle is used with the 'CalinsNetMex' mexFunction, which is a helper mexFunction that does all the actual socket communications for this class. + +Instances of @SpikeGL are weakly stateful: merely keeping a handle to a network socket. It is ok for MATLAB to create and destroy several of these objects. Each network connection cleans up after itself on the server side after 10 seconds of inactivity. By the way, if your script has paused longer than 10 seconds, and you reuse a handle that has timed out, the handle will automatically reestablish a connection and the script will likely continue without problems, but a warning will appear in the Command Window reflecting the timeout. Such warnings have a warningid, so you can suppress them by typing >> warning( 'off', 'CalinsNetMex:connectionClosed' ). + +======== +EXAMPLES +======== + +my_s = SpikeGL; // connect to SpikeGLX running on local machine + +prms = GetParams( my_s ); // optionally retrieve run params as struct + +SetParams( my_s, struct('niMNChans1','0:5','niDualDevMode','false',...) ); // make changes + +StartRun( my_s ); // start data acquisition run using last-set params + +StopRun( my_s ); // stop run and clean up + +======== +(js, ip) +======== + +The two integer values (js, ip) select a data stream. +js: stream type: {0=nidq, 1=obx, 2=imec-probe}. +ip: substream: {0=nidq (if js=0), 0+=which OneBox or imec probe}. +Examples (js, ip): +(0, 0) = nidq. // for nidq, ip is arbitrary but zero by convention +(1, 4) = obx4. +(2, 7) = imec7. +Note: ip has range [0..np-1], where, np is queried using GetStreamNP(). + +======================= +GetParams and SetParams +======================= + +Manual Pre-validation +===================== + +You'll find that several of the API functions to get or set run parameters complain if you haven't validated any parameters yet. To validate parameters, visit the Configure dialog in SpikeGLX and make choices for your experiment, then click either 'Run' or 'Verify|Save'. Either of these buttons apply a battery of self-consistency checks to your choices. The most recent set of Configuration settings that have passed all the sanity checks are saved in: + +- 'SpikeGLX/_Configs/daq.ini' +- 'SpikeGLX/_Calibration/imec_probe_settings.ini' +- 'SpikeGLX/_Calibration/imec_onebox_settings.ini'. + +The above ini files are used to initialize the Configure dialog each time you open it. Open the ini files in a text editor. You'll see several subgroups of settings. This is the best way to see the exact spelling, case, and value type of the items you can read and write via the API. Examples of accessing the subgroups: + +- Group [DAQSettings]: GetParams() +- Group [DAQ_Imec_All]: GetParamsImecCommon() +- Group [SerialNumberToProbe]/SNjjj: GetParamsImecProbe() +- Group [SerialNumberToOneBox]/SNjjj: GetParamsOneBox() + +Generally, follow this workflow: + +(1) Start SpikeGLX and make sure its Command Server is listening (you'll see a message to that effect in the Console/Log window). + +(2) Open the Configure dialog to elect which hardware streams you want to run. + +(3) Click 'Detect' and then 'Verify|Save'. + +Now you are ready to run from MATLAB. + +(4) Typically you will need to adjust just a few settings from within your script. + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/WhatsNew.txt b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/WhatsNew.txt new file mode 100644 index 00000000..3c99ab9e --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/Docs/WhatsNew.txt @@ -0,0 +1,344 @@ +============== +AS OF 20250325 +============== + +- Add waveplayer example scripts and waveform. + +============== +AS OF 20241215 +============== + +- GetParamsOneBox takes (... ip, slot). +- SetParamsOneBox takes (... ip, slot). +- SetDigOut replaced by NI_DO_Set. +- Add NI_Wave_Arm. +- Add NI_Wave_Load. +- Add NI_Wave_StartStop. +- Add OBX_AO_Set. +- Add OBX_Wave_Arm. +- Add OBX_Wave_Load. +- Add OBX_Wave_StartStop. +- Add PauseGraphs. + +============== +AS OF 20240129 +============== + +- Allow remote fetch of filtered IM stream. + +============== +AS OF 20230425 +============== + +New functions +------------- +GetGeomMap +GetProbeList +SetAnatomy_Pinpoint + +============== +AS OF 20230411 +============== + +- Fecthes returning zero samples do not generate warnings. + +============== +AS OF 20230202 +============== + +- GetStreamShankMap can only be used on NI stream (js = 0) because imec +ShankMaps are for internal use only. They will be replaced by geomMap. + +- "Onebox" -> "OneBox" + +============== +AS OF 20220101 +============== + +- Add 'LatencyTest.m' demo file. + +- Retire 32-bit MATLAB support. + +- Streamline CalinsNetMex. + +- Simplify error handling. + +- Users must reinstall @SpikeGL and CalinsNetMex.mexw64. + +- Formerly streams were referenced by the single parameter streamID: {-1=nidq, 0+=imec-probe}. +Henceforth two parameters (js, ip) are used to select an expanded variety of streams: +- js: stream type: {0=nidq, 1=obx, 2=imec-probe}. +- ip: substream: {0=nidq (if js=0), 0+=which OneBox or imec probe}. +Examples (js, ip): +(0, 0) = nidq. // for nidq, ip is arbitrary but zero by convention +(1, 4) = obx4. +(2, 7) = imec7. +Note: ip has range [0..np-1], where, np is queried using GetStreamNP(). + +Affected functions +------------------ +Fetch +FetchLatest +GetAcqChanCounts -> GetStreamAcqChans +GetFileStartCount -> GetStreamFileStart +GetImProbeCount -> GetStreamNP +GetImProbeSN -> GetStreamSN +GetImVoltageRange -> GetStreamVoltageRange +GetSampleRate -> GetStreamSampleRate +GetSaveChans -> GetStreamSaveChans +GetScanCount -> GetStreamSampleCount +IsUserOrder +MapSample + +New functions +------------- +GetImecChanGains +GetParamsImecCommon +SetParamsImecCommon +GetParamsImecProbe +SetParamsImecProbe +GetParamsOneBox +SetParamsOneBox +GetStreamI16ToVolts +GetStreamMaxInt +GetStreamShankMap + +Old -> New function replacements +-------------------------------- +SetMetaData -> SetMetadata + +Changed behavior +---------------- +Fetch, +FetchLatest -> default channel subset is all_acquired, rather than all_saved. + +Opto functions +-------------- +Opto_getAttenuations +Opto_emit + + +============== +AS OF 20200309 +============== + +New functions +------------- +SetMultiDriveEnable + +New parameters +-------------- +GetDataDir +SetDataDir +EnumDataDir + + +============== +AS OF 20190327 +============== + +New functions +------------- +GetImProbeCount +GetImVoltageRange +MapSample +SetTriggerOffBeep +SetTriggerOnBeep + + +============== +AS OF 20190305 +============== + +New functions +------------- +SetNextFileName + + +============== +AS OF 20190214 +============== + +Old -> New function replacements +-------------------------------- +GetRunDir -> GetDataDir +SetRunDir -> SetDataDir +EnumRunDir -> EnumDataDir + + +============== +AS OF 20180829 +============== + +Consolidate NI/IM functions using streamID as follows: +streamID = -1: NI, +streamID = 0: probe 0, +streamID = 1: probe 1, ... + +New functions +------------- +GetSampleRate + +Changed syntax +-------------- +Fetch +FetchLatest +IsUserOrder +GetFileStartCount +GetScanCount +GetSaveChans +GetAcqChanCounts + + +============== +AS OF 20180715 +============== + +New functions +------------- +GetImProbeSN + + +============== +AS OF 20170903 +============== + +Old -> New function replacements +-------------------------------- +GetAcqChanCounts -> GetAcqChanCountsNI, GetAcqChanCountsIm + + +Changed syntax +-------------- +GetAcqChanCountsIm requires a streamID. +GetSaveChansIm now requires a streamID. +GetFileStartCountIm now requires a streamID. +GetScanCountIm now requires a streamID. +FetchIm, FetchLatestIm now require a streamID. +SetAudioParams now requires a subgroup name and params struct. + + +============== +AS OF 20170724 +============== + +New functions +------------- +SetMetaData + + +============== +AS OF 20170501 +============== + +Old -> New function replacements +-------------------------------- +SetAOEnable -> SetAudioEnable +SetAOParams -> SetAudioParams + + +============== +AS OF 20160601 +============== + +New functions +------------- +GetFileStartCountIm, GetFileStartCountNi + + +Changed syntax +-------------- +FetchLatestNi, FetchLatestIm now return [matrix, headCt]. + + +============== +AS OF 20160502 +============== + +New functions +------------- +IsUserOrderIm, IsUserOrderNi + + +============== +AS OF 20160404 +============== + +Old -> New function replacements +-------------------------------- +SetTrgEnable -> SetRecordingEnable + + +============== +AS OF 20160120 +============== + +Old -> New function replacements +-------------------------------- +GetScanCount -> GetScanCountNi, GetScanCountIm +GetChannelSubset -> GetSaveChansNi, GetSaveChansIm +GetDAQData -> FetchNi, FetchIm +GetLastNDAQData -> FetchLatestNi, FetchLatestIm + + +Changed syntax +-------------- +GetAcqChanCounts now returns vector including {AP,LF,SY,MN,MA,XA,DW}. + + +All other functions +------------------- +Same syntax + + +IMPORTANT +--------- +Use new mex files. + + +============== +AS OF 20151231 +============== + +Contents.m documentation file is accurate. + + +Old retired functions +--------------------- +FastSettle +GetCurrentRunFile +GetCurrentSaveFile + + +Old -> New function replacements +-------------------------------- +ConsoleUnhide -> ConsoleShow +IsAcquiring -> IsRunning +DoQueryMatrix -> GetDAQData +GetDir -> EnumRunDir +GetSaveDir -> GetRunDir +GetSaveFile -> GetRunName +SetSaveDir -> SetRunDir +SetSaveFile -> SetRunName +SetSaving -> SetTrgEnable +StartACQ -> StartRun +StopACQ -> StopRun + + +New functions +------------- +GetAcqChanCounts +SetAOParams +SetAOEnable +SetDigOut + + +Changed syntax +-------------- +GetDAQData now returns two params [mat,headCt]; where headCt is the +zero-based index of the first timepoint in the matrix. This allows +consecutive fetches. + +GetAcqChanCounts now returns vector including {AP,LF,MN,MA,XA,DW}. + + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/LICENSE.txt b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/LICENSE.txt new file mode 100644 index 00000000..b6ac09c8 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/LICENSE.txt @@ -0,0 +1,12 @@ + +The Janelia Research Campus Software Copyright 1.2 + +Copyright (c) 2024, Howard Hughes Medical Institute, All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of the Howard Hughes Medical Institute nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, OR FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; REASONABLE ROYALTIES; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/MATLAB_latency.png b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/MATLAB_latency.png new file mode 100644 index 00000000..4251bb4b Binary files /dev/null and b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/MATLAB_latency.png differ diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/README.md b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/README.md new file mode 100644 index 00000000..00446adf --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX-MATLAB-SDK-main/README.md @@ -0,0 +1,39 @@ +SpikeGLX-MATLAB-SDK +=================== + +### What + +The SDK lets MATLAB applications communicate with SpikeGLX phase30 versions +20220101 and later: + +* Set/get parameters. +* Start/Stop runs. +* Start/stop writing. +* Fetch data in real time. +* Everything you need to integrate SpikeGLX into your workflow. + +There's an included closed-loop latency test program; used to obtain these +results on an i7-8850H, 2.6 GHz laptop: + +![MATLAB API Latency](MATLAB_latency.png) + +>*Note: Low latency mode is available with SpikeGLX 20230815 and later.* + +### Who + +The SDK is developed by [Bill Karsh](https://www.janelia.org/people/bill-karsh) +of the [Tim Harris Lab](https://www.janelia.org/lab/harris-lab-apig) at +HHMI/Janelia Research Campus. + +### Building in Windows + +I build using MS Visual Studio C++ 2015, and MATLAB R2018b. + +### Licensing + +Use is subject to Janelia Research Campus Software Copyright 1.2 license terms: +[http://license.janelia.org/license](http://license.janelia.org/license). + + +_fin_ + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/DemoReadSGLXData.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/DemoReadSGLXData.m new file mode 100644 index 00000000..9d43548d --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/DemoReadSGLXData.m @@ -0,0 +1,48 @@ +% ============================================================= +% Simple demo program calling functions from the +% SGLX_readMeta class. +% +function DemoReadSGLXData() + +% Ask user for binary file +[binName,path] = uigetfile('*.bin', 'Select Binary File'); + +% Parse the corresponding metafile +meta = SGLX_readMeta.ReadMeta(binName, path); + +% Get first one second of data +nSamp = floor(1.0 * SGLX_readMeta.SampRate(meta)); +dataArray = SGLX_readMeta.ReadBin(0, nSamp, meta, binName, path); +dataType = 'A'; %set to 'A' for analog, 'D' for digital data + +% For an analog channel: gain correct saved channel ch (1-based for MATLAB). +ch = 1; + +% For a digital channel: read this digital word dw in the saved file +% (1-based). For imec data there is never more than one saved digital word. +dw = 1; + +% Read these lines in dw (0-based). +% For 3B2 imec data: the sync pulse is stored in line 6. +% May be 1 or more line indices. +dLineList = [0,1,6]; + +if dataType == 'A' + switch meta.typeThis + case 'imec' + dataArray = SGLX_readMeta.GainCorrectIM(dataArray, [ch], meta); + case 'nidq' + dataArray = SGLX_readMeta.GainCorrectNI(dataArray, [ch], meta); + case 'obx' + dataArray = SGLX_readMeta.GainCorrectOBX(dataArray, [ch], meta); + end + plot(1e6*dataArray(ch,:)); +else + digArray = SGLX_readMeta.ExtractDigital(dataArray, meta, dw, dLineList); + for i = 1:numel(dLineList) + plot(digArray(i,:)); + hold on + end + hold off +end +end % DemoReadSGLXData \ No newline at end of file diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/DiagnoseSyncBits.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/DiagnoseSyncBits.m new file mode 100644 index 00000000..3e730ea2 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/DiagnoseSyncBits.m @@ -0,0 +1,117 @@ +function [stats] = DiagnoseSyncBits(binName, path, chunkSize, maxBits) +% DiagnoseSyncBits Scan SY word bits and report pulse counts and mean durations. +% stats = DiagnoseSyncBits(binName, path, chunkSize, maxBits) +% +% - binName: filename of .ap.bin (string) +% - path: folder containing the file (string) +% - chunkSize: number of samples per chunk (default 1e6) +% - maxBits: how many bits to scan (default 16) +% +% Returns a struct array stats(b) with fields: +% .bit bit number (1-based) +% .nPulses number of completed pulses detected +% .meanDur_s mean pulse duration (seconds) +% .medianDur_s median pulse duration (seconds) +% .durations_s vector of durations (seconds) [may be large] +% + if nargin < 3 || isempty(chunkSize), chunkSize = 1e6; end + if nargin < 4 || isempty(maxBits), maxBits = 16; end + + meta = SGLX_readMeta.ReadMeta(binName, path); + sRate = SGLX_readMeta.SampRate(meta); + nSavedChans = str2double(meta.nSavedChans); + syncCh = nSavedChans; + bytesPerSamp = 2; + + nFileBytes = str2double(meta.fileSizeBytes); + nSampTotal = nFileBytes / (nSavedChans * bytesPerSamp); + + fid = fopen(fullfile(path, binName), 'r'); + if fid < 0, error('Cannot open %s', fullfile(path, binName)); end + + % init + openStart = zeros(1, maxBits); % 0 if no open pulse; otherwise sample index of start + pulses = cell(1, maxBits); % will hold Nx2 arrays [startSample endSample] + for b=1:maxBits, pulses{b} = zeros(0,2); end + + nChunks = ceil(nSampTotal / chunkSize); + for c = 1:nChunks + startSamp0 = (c-1)*chunkSize; % 0-based sample index of chunk start + nRead = min(chunkSize, nSampTotal - startSamp0); + + % seek to sync channel first sample of chunk + offset = (startSamp0 * nSavedChans + (syncCh-1)) * bytesPerSamp; + fseek(fid, offset, 'bof'); + + % read nRead sync words with stride + raw = fread(fid, nRead, 'int16', (nSavedChans-1)*bytesPerSamp); + if isempty(raw), break; end + sy = uint16(raw(:)'); % row vector of uint16 + + % process each bit + for b = 1:maxBits + vec = logical(bitget(sy, b)); % 1..nRead logical vector + prev = openStart(b) > 0; % previous chunk state: 1 if currently inside a pulse + % detect rising positions and falling positions in this chunk + % prevVec is previous sample for first element + if nRead==0, continue; end + prevVec = [prev, vec(1:end-1)]; + risePos = find(prevVec==0 & vec==1); % indices in 1..nRead + fallPos = find(prevVec==1 & vec==0); + + % handle rises + for i = 1:numel(risePos) + if openStart(b) == 0 + openStart(b) = startSamp0 + risePos(i); % store 0-based sample index + else + % a new rise found while previous open exists; treat as restart: + % close previous at this rise (best-effort) then start new + pulses{b}(end+1, :) = [openStart(b), startSamp0 + risePos(i)]; + openStart(b) = startSamp0 + risePos(i); + end + end + + % handle falls + for i = 1:numel(fallPos) + if openStart(b) ~= 0 + pulses{b}(end+1, :) = [openStart(b), startSamp0 + fallPos(i)]; + openStart(b) = 0; + else + % fall without open start: ignore + end + end + end + end + + fclose(fid); + + % if any open starts remain, close them at end of file + for b = 1:maxBits + if openStart(b) ~= 0 + pulses{b}(end+1, :) = [openStart(b), nSampTotal]; + openStart(b) = 0; + end + end + + % compute stats + stats = struct(); + for b = 1:maxBits + pairs = pulses{b}; + if isempty(pairs) + stats(b).bit = b; + stats(b).nPulses = 0; + stats(b).meanDur_s = NaN; + stats(b).medianDur_s = NaN; + stats(b).durations_s = []; + else + durs = (pairs(:,2) - pairs(:,1)) / sRate; + stats(b).bit = b; + stats(b).nPulses = size(pairs,1); + stats(b).meanDur_s = mean(durs); + stats(b).medianDur_s = median(durs); + stats(b).durations_s = durs; + end + fprintf('Bit %2d : pulses = %4d, mean dur = %0.4f s, median = %0.4f s\n', ... + stats(b).bit, stats(b).nPulses, stats(b).meanDur_s, stats(b).medianDur_s); + end +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ExtractFixedPulsesByBit.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ExtractFixedPulsesByBit.m new file mode 100644 index 00000000..c608fabf --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ExtractFixedPulsesByBit.m @@ -0,0 +1,73 @@ +function pulseTimes = ExtractFixedPulsesByBit(binName, path, bit, chunkSize, pulseLen_s) +% ExtractFixedPulsesByBit Extract start/end times using a chosen SY bit. +% pulseTimes = ExtractFixedPulsesByBit(binName, path, bit, chunkSize, pulseLen_s) +% - bit is 1-based bit index (1..16) +% - pulseLen_s default 0.400 + if nargin < 4 || isempty(chunkSize), chunkSize = 1e6; end + if nargin < 5 || isempty(pulseLen_s), pulseLen_s = 0.400; end + + meta = SGLX_readMeta.ReadMeta(binName, path); + sRate = SGLX_readMeta.SampRate(meta); + nSavedChans = str2double(meta.nSavedChans); + syncCh = nSavedChans; + bytesPerSamp = 2; + nFileBytes = str2double(meta.fileSizeBytes); + nSampTotal = nFileBytes / (nSavedChans * bytesPerSamp); + + fid = fopen(fullfile(path, binName), 'r'); + if fid < 0, error('Cannot open file %s', fullfile(path, binName)); end + + openStart = 0; + pulseList = zeros(0,2); + + nChunks = ceil(nSampTotal / chunkSize); + for c = 1:nChunks + startSamp0 = (c-1)*chunkSize; + nRead = min(chunkSize, nSampTotal - startSamp0); + + offset = (startSamp0 * nSavedChans + (syncCh-1)) * bytesPerSamp; + fseek(fid, offset, 'bof'); + + raw = fread(fid, nRead, 'int16', (nSavedChans-1)*bytesPerSamp); + if isempty(raw), break; end + sy = uint16(raw(:)'); + + vec = logical(bitget(sy, bit)); + prevVec = [openStart>0, vec(1:end-1)]; + risePos = find(prevVec==0 & vec==1); + fallPos = find(prevVec==1 & vec==0); + + % rises + for i = 1:numel(risePos) + if openStart == 0 + openStart = startSamp0 + risePos(i); % 0-based + else + % unexpected, close old at this rise + pulseList(end+1,:) = [openStart, startSamp0 + risePos(i)]; + openStart = startSamp0 + risePos(i); + end + end + + % falls + for i = 1:numel(fallPos) + if openStart ~= 0 + pulseList(end+1,:) = [openStart, startSamp0 + fallPos(i)]; + openStart = 0; + end + end + end + + % close outstanding open pulse if any, use fixed pulse length if necessary + if openStart ~= 0 + endSample = min(nSampTotal, openStart + round(pulseLen_s * sRate)); + pulseList(end+1,:) = [openStart, endSample]; + openStart = 0; + end + + fclose(fid); + + % If pulses are fixed length, optionally override ends: + pulseTimes = [(pulseList(:,1)-1)/sRate, (pulseList(:,1)-1)/sRate + pulseLen_s]; + + fprintf('Extracted %d pulses (bit %d). Returned times in seconds.\n', size(pulseTimes,1), bit); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ExtractSyncPulsesChunked.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ExtractSyncPulsesChunked.m new file mode 100644 index 00000000..8b184f11 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ExtractSyncPulsesChunked.m @@ -0,0 +1,83 @@ +function pulseTimes = ExtractSyncPulsesChunked(binName, path, chunkSize) +% Extract start/end times of sync pulses without loading entire file. +% Works on very large SpikeGLX .ap.bin recordings. +% +% binName = filename of .ap.bin +% path = folder containing binName +% chunkSize = number of samples to read per chunk (e.g. 1e6) + + if nargin < 3 + chunkSize = 1e6; % default: 1 million samples (~33 s at 30 kHz) + end + + % --- Load metadata + meta = SGLX_readMeta.ReadMeta(binName, path); + sRate = SGLX_readMeta.SampRate(meta); + nSavedChans = str2double(meta.nSavedChans); + syncCh = nSavedChans; % last channel + bytesPerSamp = 2; % int16 + + % --- Figure out total samples + nFileBytes = str2double(meta.fileSizeBytes); + nSampTotal = nFileBytes / (nSavedChans * bytesPerSamp); + + % --- Open file + fid = fopen(fullfile(path, binName), 'r'); + if fid < 0, error('Cannot open file %s', binName); end + + % --- Loop through chunks + pulseTimes = []; + carryOver = 0; % last value of previous chunk + + nChunks = ceil(nSampTotal / chunkSize); + fprintf('Processing %d chunks...\n', nChunks); + + for c = 1:nChunks + startSamp = (c-1)*chunkSize; + nRead = min(chunkSize, nSampTotal - startSamp); + + % Position to sync channel for this chunk + offset = (startSamp * nSavedChans + (syncCh-1)) * bytesPerSamp; + fseek(fid, offset, 'bof'); + + % Read with stride to skip other channels + syncData = fread(fid, [1 nRead], ... + ['int16=>' 'double'], (nSavedChans-1)*bytesPerSamp); + + % Threshold + syncData = syncData > 0; + + % Add carry-over at the start + if carryOver + syncData = [carryOver syncData]; + startIdxOffset = startSamp - 1; % adjust for added sample + else + startIdxOffset = startSamp; + end + + % Find edges + dSync = diff([0 syncData 0]); + riseIdx = find(dSync == 1); + fallIdx = find(dSync == -1); + + % Convert to absolute sample indices + riseIdx = riseIdx + startIdxOffset; + fallIdx = fallIdx + startIdxOffset; + + % Convert to time in seconds + riseTime = (riseIdx - 1) / sRate; + fallTime = (fallIdx - 1) / sRate; + + % Store + nPulses = min(numel(riseTime), numel(fallTime)); + if nPulses > 0 + pulseTimes = [pulseTimes; [riseTime(1:nPulses)' fallTime(1:nPulses)']]; + end + + % Update carry-over + carryOver = syncData(end); + end + + fclose(fid); + fprintf('Found %d pulses total.\n', size(pulseTimes,1)); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/SGLX_readMeta.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/SGLX_readMeta.m new file mode 100644 index 00000000..6190b70e --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/SGLX_readMeta.m @@ -0,0 +1,495 @@ +% ============================================================= +% Simple class of MATLAB functions deomonstrating +% how to read and manipulate SpikeGLX meta and binary files. +% When this file is included in the MATLAB path, the individual methods +% are called with SGLX_readMeta. +% +% The most important method in the class is ReadMeta(). +% The functions for reading binary data are not optimized for speed, +% and are included as a useful starting point. +% +classdef SGLX_readMeta + +methods(Static) + +% ========================================================= +% Parse ini file returning a structure whose field names +% are the metadata left-hand-side tags, and whose right- +% hand-side values are MATLAB strings. We remove any +% leading '~' characters from tags because MATLAB uses +% '~' as an operator. +% +% If you're unfamiliar with structures, the benefit +% is that after calling this function you can refer +% to metafile items by name. For example: +% +% meta.fileCreateTime // file create date and time +% meta.nSavedChans // channels per timepoint +% +% All of the values are MATLAB strings, but you can +% obtain a numeric value using str2double(meta.nSavedChans). +% More complicated parsing of values is demonstrated in the +% utility functions below. +% +function [meta] = ReadMeta(binName, path) + + % Create the matching metafile name + [~,name,~] = fileparts(binName); + metaName = strcat(name, '.meta'); + + % Parse ini file into cell entries C{1}{i} = C{2}{i} + fid = fopen(fullfile(path, metaName), 'r'); +% ------------------------------------------------------------- +% Need 'BufSize' adjustment for MATLAB earlier than 2014 +% C = textscan(fid, '%[^=] = %[^\r\n]', 'BufSize', 32768); + C = textscan(fid, '%[^=] = %[^\r\n]'); +% ------------------------------------------------------------- + fclose(fid); + + % New empty struct + meta = struct(); + + % Convert each cell entry into a struct entry + for i = 1:length(C{1}) + tag = C{1}{i}; + if tag(1) == '~' + % remake tag excluding first character + tag = sprintf('%s', tag(2:end)); + end + meta.(tag) = C{2}{i}; + end +end % ReadMeta + + +% ========================================================= +% Read nSamp timepoints from the binary file, starting +% at timepoint offset samp0. The returned array has +% dimensions [nChan,nSamp]. Note that nSamp returned +% is the lesser of: {nSamp, timepoints available}. +% +% IMPORTANT: samp0 and nSamp must be integers. +% +function dataArray = ReadBin(samp0, nSamp, meta, binName, path) + + nChan = str2double(meta.nSavedChans); + + nFileSamp = str2double(meta.fileSizeBytes) / (2 * nChan); + samp0 = max(samp0, 0); + nSamp = min(nSamp, nFileSamp - samp0); + + sizeA = [nChan, nSamp]; + + fid = fopen(fullfile(path, binName), 'rb'); + fseek(fid, samp0 * 2 * nChan, 'bof'); + dataArray = fread(fid, sizeA, 'int16=>double'); + fclose(fid); +end % ReadBin + + +% ========================================================= +% Return an array [lines X timepoints] of uint8 values for +% a specified set of digital lines. +% +% - dwReq is the one-based index into the saved file of the +% 16-bit word that contains the digital lines of interest. +% - dLineList is a zero-based list of one or more lines/bits +% to scan from word dwReq. +% +function digArray = ExtractDigital(dataArray, meta, dwReq, dLineList) + % Get channel index of requested digital word dwReq + switch meta.typeThis + case 'imec' + [AP, LF, SY] = SGLX_readMeta.ChannelCountsIM(meta); + if SY == 0 + fprintf('No imec sync channel saved\n'); + digArray = []; + return; + else + digCh = AP + LF + dwReq; + end + case 'nidq' + [MN,MA,XA,DW] = SGLX_readMeta.ChannelCountsNI(meta); + if dwReq > DW + fprintf('Maximum digital word in file = %d\n', DW); + digArray = []; + return; + else + digCh = MN + MA + XA + dwReq; + end + case 'obx' + [XA,DW,SY] = SGLX_readMeta.ChannelCountsOBX(meta); + if dwReq > DW + fprintf('Maximum digital word in file = %d\n', DW); + digArray = []; + return; + else + digCh = XA + dwReq; + end + end + [~,nSamp] = size(dataArray); + digArray = zeros(numel(dLineList), nSamp, 'uint8'); + for i = 1:numel(dLineList) + digArray(i,:) = bitget(dataArray(digCh,:), dLineList(i)+1, 'int16'); + end +end % ExtractDigital + + +% ========================================================= +% Return sample rate as double. +% +function srate = SampRate(meta) + switch meta.typeThis + case 'imec' + srate = str2double(meta.imSampRate); + case 'nidq' + srate = str2double(meta.niSampRate); + case 'obx' + srate = str2double(meta.obSampRate); + end +end % SampRate + + +% ========================================================= +% Return a multiplicative factor for converting 16-bit +% file data to voltage. This does not take gain into +% account. The full conversion with gain is: +% +% dataVolts = dataInt * fI2V / gain. +% +% Note that each channel may have its own gain. +% +function fI2V = Int2Volts(meta) + switch meta.typeThis + case 'imec' + if isfield(meta,'imMaxInt') + maxInt = str2num(meta.imMaxInt); + else + maxInt = 512; + end + fI2V = str2double(meta.imAiRangeMax) / maxInt; + case 'nidq' + fI2V = str2double(meta.niAiRangeMax) / str2double(meta.niMaxInt); + case 'obx' + fI2V = str2double(meta.obAiRangeMax) / str2double(meta.obMaxInt); + end +end % Int2Volts + + +% ========================================================= +% Return array of original channel IDs. As an example, +% suppose we want the imec gain for the ith channel stored +% in the binary data. A gain array can be obtained using +% ChanGainsIM() but we need an original channel index to +% do the look-up. Because you can selectively save channels +% the ith channel in the file isn't necessarily the ith +% acquired channel, so use this function to convert from +% ith stored to original index. +% +% Note: In SpikeGLX channels are 0-based, but MATLAB uses +% 1-based indexing, so we add 1 to the original IDs here. +% +function chans = OriginalChans(meta) + if strcmp(meta.snsSaveChanSubset, 'all') + chans = (1:str2double(meta.nSavedChans)); + else + chans = str2num(meta.snsSaveChanSubset); + chans = chans + 1; + end +end % OriginalChans + + +% ========================================================= +% Return counts of each imec channel type that compose +% the timepoints stored in binary file. +% +function [AP,LF,SY] = ChannelCountsIM(meta) + M = str2num(meta.snsApLfSy); + AP = M(1); + LF = M(2); + SY = M(3); +end % ChannelCountsIM + +% ========================================================= +% Return counts of each nidq channel type that compose +% the timepoints stored in binary file. +% +function [MN,MA,XA,DW] = ChannelCountsNI(meta) + M = str2num(meta.snsMnMaXaDw); + MN = M(1); + MA = M(2); + XA = M(3); + DW = M(4); +end % ChannelCountsNI + +% ========================================================= +% Return counts of each obx channel type that compose +% the timepoints stored in binary file. +% +function [XA,DW,SY] = ChannelCountsOBX(meta) + M = str2num(meta.snsXaDwSy); + XA = M(1); + DW = M(2); + SY = M(3); +end % ChannelCountsOBX + +% ========================================================= +% Return gain for ith channel stored in the nidq file. +% +% ichan is a saved channel index, rather than an original +% (acquired) index. +% +% Note: there is no matching function for OBX, because +% the gain is fixed at 1. +% +function gain = ChanGainNI(ichan, savedMN, savedMA, meta) + if ichan <= savedMN + gain = str2double(meta.niMNGain); + elseif ichan <= savedMN + savedMA + gain = str2double(meta.niMAGain); + else + gain = 1; + end +end % ChanGainNI + + +% ========================================================= +% Return gain arrays for imec channels. +% +% Index into these with original (acquired) channel IDs. +% +function [APgain,LFgain, APChan0_to_uV, LFChan0_to_uV] = ChanGainsIM(meta) + % list of probe types with NP 1.0 imro format + np1_imro = [0,1020,1030,1200,1100,1120,1121,1122,1123,1300]; + % number of channels acquired + acqCountList = str2num(meta.acqApLfSy); + + APgain = zeros(acqCountList(1)); % default type = float64 + LFgain = zeros(acqCountList(2)); % empty array for 2.0 + + if isfield(meta,'imDatPrb_type') + probeType = str2double(meta.imDatPrb_type); + else + probeType = 0; + end + + if ismember(probeType, np1_imro) + % imro + probe allows setting gain independently for each channel + % 3A or 3B data? + % 3A metadata has field "typeEnabled" which was replaced + % with "typeImEnabled" and "typeNiEnabled" in 3B. + % The 3B imro table has an additional field for the + % high pass filter enabled/disabled + if isfield(meta,'typeEnabled') + % 3A data + C = textscan(meta.imroTbl, '(%*s %*s %*s %d %d', ... + 'EndOfLine', ')', 'HeaderLines', 1 ); + else + % 3B data + C = textscan(meta.imroTbl, '(%*s %*s %*s %d %d %*s', ... + 'EndOfLine', ')', 'HeaderLines', 1 ); + end + APgain = double(cell2mat(C(1))); + LFgain = double(cell2mat(C(2))); + else + % get gain from imChan0apGain, if present + if isfield(meta,'imChan0apGain') + APgain = APgain + str2num(meta.imChan0apGain); + if acqCountList(2) > 0 + LFgain = LFgain + str2num(meta.imChan0lfGain); + end + elseif (probeType == 1110) + % active UHD, for metadata lacking imChan0apGain, get gain from + % imro table header + currList = sscanf(meta.imroTbl, '(%d,%d,%d,%d,%d'); + APgain = APgain + currList(4); + LFgain = LFgain + currList(5); + elseif (probeType == 21) || (probeType == 24) + % development NP 2.0; APGain = 80 for all AP + % return 0 for LFgain (no LF channels) + APgain = APgain + 80; + elseif (probeType == 2013) + % commercial NP 2.0; APGain = 80 for all AP + APgain = APgain + 100; + else + fprintf('unknown gain, setting APgain to 1\n'); + APgain = APgain + 1; + end + end + fI2V = SGLX_readMeta.Int2Volts(meta); + APChan0_to_uV = 1e6*fI2V/APgain(1); + if size(LFgain) > 0 + LFChan0_to_uV = 1e6*fI2V/LFgain(1); + else + LFChan0_to_uV = 0; + end +end % ChanGainsIM + + +% ========================================================= +% Having acquired a block of raw nidq data using ReadBin(), +% convert values to gain-corrected voltages. The conversion +% is only applied to the saved-channel indices in chanList. +% Remember saved-channel indices are in range [1:nSavedChans]. +% The dimensions of the dataArray remain unchanged. ChanList +% examples: +% +% [1:MN] % all MN chans (MN from ChannelCountsNI) +% [2,6,20] % just these three channels +% +% +function dataArray = GainCorrectNI(dataArray, chanList, meta) + + [MN,MA] = SGLX_readMeta.ChannelCountsNI(meta); + fI2V = SGLX_readMeta.Int2Volts(meta); + + for i = 1:length(chanList) + j = chanList(i); % index into timepoint + conv = fI2V / SGLX_readMeta.ChanGainNI(j, MN, MA, meta); + dataArray(j,:) = dataArray(j,:) * conv; + end +end % GainCorrectNI + +% ========================================================= +% Having acquired a block of raw obx data using ReadBin(), +% convert values voltages. The conversion is only applied +% to the saved-channel indices in chanList. The conversion +% factor is the same for all channels, because the gain is fixed. +% Remember saved-channel indices are in range [1:nSavedChans]. +% The dimensions of the dataArray remain unchanged. ChanList +% examples: +% +% [2,6,20] % just these three channels +% +% +function dataArray = GainCorrectOBX(dataArray, chanList, meta) + + fI2V = SGLX_readMeta.Int2Volts(meta); + + for i = 1:length(chanList) + j = chanList(i); % index into timepoint + dataArray(j,:) = dataArray(j,:) * fI2V; + end +end % GainCorrectOBX + + +% ========================================================= +% Having acquired a block of raw imec data using ReadBin(), +% convert values to gain-corrected voltages. The conversion +% is only applied to the saved-channel indices in chanList. +% Remember saved-channel indices are in range [1:nSavedChans]. +% The dimensions of the dataArray remain unchanged. ChanList +% examples: +% +% [1:AP] % all AP chans (AP from ChannelCountsIM) +% [2,6,20] % just these three channels +% +function dataArray = GainCorrectIM(dataArray, chanList, meta) + + % Look up gain with acquired channel ID + chans = SGLX_readMeta.OriginalChans(meta); + [APgain,LFgain] = SGLX_readMeta.ChanGainsIM(meta); + nAP = length(APgain); + nNu = nAP * 2; + + % Common conversion factor + fI2V = SGLX_readMeta.Int2Volts(meta); + + for i = 1:length(chanList) + j = chanList(i); % index into timepoint + k = chans(j); % acquisition index + if k <= nAP + conv = fI2V / APgain(k); + elseif k <= nNu + conv = fI2V / LFgain(k - nAP); + else + continue; + end + dataArray(j,:) = dataArray(j,:) * conv; + end +end % GainCorrectIM + +% ========================================================= +% Return array of survey bank times +% +% +function bankTimes = svyBankTimes(meta) + + % Look up gain with acquired channel ID + C = textscan(meta.svySBTT, '(%d %d %d %d', ... + 'EndOfLine', ')' ); + + nBank = numel(C{1}) + 1; % bank0/shank0 is at time = 0 + srate = SGLX_readMeta.SampRate(meta); + + bankTimes = zeros([nBank,4], "double"); + bankTimes(2:nBank,1) = double(C{1}); + bankTimes(2:nBank,2) = double(C{2}); + bankTimes(2:nBank,3) = double(C{3})/srate; + bankTimes(2:nBank,4) = double(C{4})/srate; + +end % svyBankTimes + +% ========================================================= +% Write metadata file using values in meta structure +% +% +function writeMeta(meta, newPath) + + % Write out metadata file. Tag order matches order of addition to + % structure when read + fmeta = fopen( newPath, 'w'); + tildeTags{1} = 'muxTbl'; + tildeTags{2} = 'imroTbl'; + tildeTags{3} = 'snsChanMap'; + tildeTags{4} = 'snsGeomMap'; + tildeTags{5} = 'snsShankMap'; + + fn = fieldnames(meta); + for i = 1:numel(fieldnames(meta)) + currTag = fn{i}; + tagFound = find(strcmp(tildeTags, currTag)); + if isempty(tagFound) + currLine = sprintf('%s=%s',currTag,meta.(currTag)); + fprintf(fmeta,'%s\n',currLine); + else + currLine = sprintf('~%s=%s',currTag,meta.(currTag)); + fprintf(fmeta,'%s\n',currLine); + end + end + +end % writeMeta + +% =========================================================== +% parse SGLX imec filename with or without extension, return +% runName, +% gateStr, e.g. 'g0' +% triggerStr, e.g. 't0' or 'tcat' +% probeStr, e.g. 'imec0' +% streamStr, e.g. 'ap' or 'lf' +% +% +function [runName,gateStr,triggerStr,probeStr,streamStr] = parseFileName(fileName) + + % Remove extension, if present + if endsWith(fileName, '.bin') + fileName = fileName(1:length(fileName)-4); + elseif endsWith(fileName, '.meta') + fileName = fileName(1:length(fileName)-5); + end + + % Find periods and underscores + perPos = strfind(fileName,'.'); + usPos = strfind(fileName,'_'); + nPer = length(perPos); + nUS = length(usPos); + streamStr = fileName(perPos(nPer)+1:end); + probeStr = fileName(perPos(nPer-1)+1:perPos(nPer)-1); + triggerStr = fileName(usPos(nUS)+1:perPos(nPer-1)-1); + gateStr = fileName(usPos(nUS-1)+1:usPos(nUS)-1); + runName = fileName(1:usPos(nUS-1)-1); + +end % parseFileName + +end % SGLX_readMeta methods + +end % SGLX_readMeta classdef diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ks25_phy_toBinary.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ks25_phy_toBinary.m new file mode 100644 index 00000000..41ca862b --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/ks25_phy_toBinary.m @@ -0,0 +1,261 @@ +function ks25_phy_toBinary( varargin ) + +% Read drift corrected data from ks25, unwhiten and rescale for input +% into C_Waves. Requires a full phy folder of results. + +% IMPORTANT NOTE: the preprocessed file only contains channels that KS2.5 +% uses for sorting. Channels with connected=0 in the chanMap file OR +% eliminated due to low spike count (ops.minfr_goodchannels > 0) will not +% be included. The information in the phy output is used to generate a new +% SGLX metadata file which includes the correct channels. + +% Another important note: The correct information to unwhiten and rescale +% the KS2.5 drift-corrected output is ONLY available in the rez2.mat output +% file. The whitening.mat.npy and whitening_mat_inv.npy are just identity +% matrices. Voltage values will be incorrectly scaled and the spatial +% fields are post-whitening if the rez2.mat file is unavailable. + +% Requires: +% SGLX_readMeta class: https://github.com/jenniferColonell/SpikeGLX_Datafile_Tools/ +% C_Waves: https://billkarsh.github.io/SpikeGLX/#post-processing-tools +% npy-matlab: https://github.com/kwikteam/npy-matlab/tree/master/npy-matlab +% + +% user parameters +modStr = 'ksprocUW'; % will be added to the run name of the output binary and metadata +bRunCWaves = 0; % whether to run C_Waves +exePath = 'C:\Users\colonellj\Documents\C_Waves-win'; % path to C_Waves directory on local machine + +if isempty(varargin ) + phyDir = uigetdir([],'select phy directory'); + [binName,binPath] = uigetfile({'*.bin';'*.dat'},'Select PROCESSED binary file'); + fprocFullPath = fullfile(binPath,binName); + [metaName, mPath] = uigetfile('*.meta','Select ORIGINAL metadata file'); + metaName = metaName(1:length(metaName)-5); % remove extension +else + % called from another function + inputCell = varargin(1); + phyDir = inputCell{1}; + inputCell = varargin(2); + fprocFullPath = inputCell{1}; + inputCell = varargin(3); + origMetaFullpath = inputCell{1}; + [mPath, metaName, ~] = fileparts(origMetaFullpath); +end + + +[baseName,gateStr,triggerStr,probeStr,~] = SGLX_readMeta.parseFileName(metaName); + +outName = sprintf( '%s_%s_%s_%s.%s', baseName, modStr, gateStr, triggerStr, probeStr); +binOutName = sprintf( '%s%s', outName, '.ap.bin'); +metaOutName = sprintf( '%s%s', outName, '.ap.meta'); +outFullPath = fullfile(phyDir, binOutName); + + +% KS2.5 makes a readable binary from its datashifted data. Rather +% than overlapping the batches, it reads in some extra points for filtering +% and then trims them back off. ops.ntbuff = ntb +% read NTbuff = NT + 3*ntb points +% for a standard batch (neither first nor last) read: +% --ntb points overlapping the last batch +% --NT points that belong to this batch +% --2*ntb more points; first set of ntb will be blended with next +% batch, 2nd ntb just filtering buffer +% After fitering, points ntb+1:ntb are blended with "dat_prev" which is +% NT+ntb+1:NT+2*ntb saved from the previous batch. +% Batch zero gets ntb zeros prepended to its data and blended with the +% initialized dat_prev (also zeros). +% After filtering, the data is whitened and then transposed back to NChan +% rows by NT columns to save. When these batches are read for sorting in +% learnTemplates, the data is transposed after reading. + +% read whitening matrix and chanMap from phy directory +chanMap = readNPY(fullfile(phyDir,'channel_map.npy')); % 0 based indicies of the channels in the output file +Nchan = numel(chanMap); + +if isfile(fullfile(phyDir,'rez2.mat')) + rez = load(fullfile(phyDir,'rez2.mat')); + Wrot = rez.rez.Wrot; +else + fprintf('Wrot unavailable, will use identity matrix\n'); + fprintf('Voltages will NOT be correct.\n') + Wrot = eye(Nchan); + % In the release version of KS2.5, the whitening matrix saved in + % whitening_mat.npy is just the identity matrix/rez.ops.scaleProc. + % whitening_mat_inv.npy is the inverse of whitening_mat. + % Result: whitening_mat.npy is closer to being the inverse of the + % whitening matrix than whitening_mat_inv.npy, but NEITHER is correct. + % To obtain correctly scaled, unwhitened waveforms from the drift + % corrected data, it is necessary to save the rez2.mat file in the + % script calling KS2.5. +end + + +% get number of data points +fp = dir(fprocFullPath); +procSizeBytes = fp.bytes %double +Nchan +totNT = procSizeBytes/(Nchan*2) %total number of time points +fprintf('Total time points in binary %d\n', totNT); +if totNT ~= floor(totNT) + fprintf('binary doesn not match phy output'); + return; +end + +fid = fopen(fprocFullPath, 'r'); +fidW = fopen(outFullPath, 'w'); % open for writing processed data, transposed + +% NT is set here to the KS default, but the results are independent of the +% value of NT used by KS2.5. +% NOTE: this is not true for KS2.0. +NT = 65600; + +Nbatch = floor(totNT/NT); +batchstart = 0:NT:NT*Nbatch; % batches start at these timepoints +for ibatch = 1:Nbatch + offset = 2 * Nchan*batchstart(ibatch); % binary file offset in bytes + fseek(fid, offset, 'bof'); + dat = fread(fid, [Nchan NT], '*int16'); + dat = int16(single(dat')/Wrot); + fwrite(fidW, dat', 'int16'); % write transposed batch to binary, these chunks are in order +end +% get the end of the file +NTlast = totNT - Nbatch*NT; +offset = 2*Nchan*batchstart(Nbatch+1); +fseek(fid, offset, 'bof'); +dat = fread(fid, [Nchan NTlast], '*int16'); +dat = int16(single(dat')/Wrot); +fwrite(fidW, dat', 'int16'); +fclose(fid); +fclose(fidW); + +fp = dir(outFullPath); +newBytes = uint64(fp.bytes); + +% get binaary name and path from origMetaFullPath +origBinName = sprintf('%s.bin', metaName); +origMeta = SGLX_readMeta.ReadMeta(origBinName, mPath); + +newMeta = origMeta; +newMeta.fileName = sprintf('%s', outFullPath); +newMeta.fileSizeBytes = sprintf('%ld', newBytes); +newMeta.nSavedChans = sprintf('%d', Nchan); % read from phy channel_map.npy; +newMeta.snsApLfSy = sprintf('%d,0,0', Nchan); + +% map channels to original channels. This is required for correct +% intepretation of gains read from the imro table for NP 1.0-like probes + +origChans = SGLX_readMeta.OriginalChans(origMeta) - 1; % zero based indicies of channels in the original binary +origChansNew = origChans(chanMap+1); % zero based indicies of channels in the processed binary +newMeta.snsSaveChanSubset = build_sep_str(origChansNew,','); + +% snsChanMap, snsGeomMap and snsShankMap all include only the saved +% channels. Need to get the array of entries from the string, index to +% get the channels included in the output file, +mapTags = {'snsChanMap', 'snsGeomMap', 'snsShankMap'}; +for i = 1:numel(mapTags) + if isfield(origMeta,mapTags{i}) + currMap = origMeta.(mapTags{i}); + mapArr = split(currMap,')'); + mapArr(numel(mapArr)) = []; % removing last entry from final ')' + % mapArr is original Nchan + 1 long, first entry is the header + % each entry is '(' plus the string inside the parens. + % Entries in the new map are the header (entry 1) + chanMap entries + 2 + new_map_ind = zeros([Nchan+1,1]); + new_map_ind(1) = 1; % for header entry + new_map_ind(2:Nchan+1) = chanMap(1:Nchan) + 2; + mapArrNew = mapArr(new_map_ind); + if strcmp(mapTags{i},'snsChanMap') + % chanMap header to match saved entries + mapArrNew(1) = {sprintf('(%d,0,0)', Nchan)}; + end + mapNewStr = build_sep_str(mapArrNew,')'); + newMeta.(mapTags{i}) = sprintf('%s)',mapNewStr); % adding final close paren + end +end + +SGLX_readMeta.writeMeta(newMeta, fullfile(phyDir,metaOutName) ); +fprintf( 'Output file has %d channels\n', Nchan ); + +if bRunCWaves + spkFilePath = fullfile(phyDir,'spike_times.npy'); + spkCluPath = fullfile(phyDir,'spike_clusters.npy'); + clusTablePath = fullfile(phyDir,'clus_Table.npy'); + if ~isfile(clusTablePath) + phy_to_clusTable(phyDir, Wrot); + end + call_CWaves( phyDir, outFullPath, spkFilePath, spkCluPath, clusTablePath, exePath ); +end + +end + +function sepStr = build_sep_str(m, sep) + % for a 1D array m and separator setp, build the string + ne = numel(m); + sepStr = ''; + switch class(m) + case 'cell' + for i = 1:ne-1 + % get the contents of the cell + cellElem = m(i); + cellStr = cellElem{1}; + sepStr = sprintf('%s%s%s',sepStr,cellStr,sep); + end + % last element + cellElem = m(ne); + cellStr = cellElem{1}; + sepStr = sprintf('%s%s',sepStr,cellStr); + otherwise + for i = 1:ne-1 + sepStr = sprintf('%s%g%s',sepStr,m(i),sep); + end + % last element + sepStr = sprintf('%s%g',sepStr,m(ne)); + end +end + +function phy_to_clusTable(phyDir, Wrot) +% buld clusTable for C_Waves from information in phy directory +% clus table is an npy file containing: +% 2-col table (uint32): {num_spike, pk-chan} + templates = readNPY(fullfile(phyDir,'templates.npy')); + templates_unwh = zeros(size(templates)); + [nUnit,~,~] = size(templates); + for i = 1:nUnit + templates_unwh(i,:,:) = squeeze(templates(i,:,:))/Wrot; + end + pp_all = squeeze(max(templates_unwh,[],2) - min(templates_unwh,[],2)); + [~,maxChan] = max(pp_all,[],2); + spikeClusters = readNPY(fullfile(phyDir,'spike_clusters.npy')); + [counts,labels] = groupcounts(spikeClusters); + clu_arr = zeros([nUnit,2],'uint32'); + clu_arr(labels+1,1) = uint32(counts); + clu_arr(:,2) = uint32(maxChan); + writeNPY(clu_arr, fullfile(phyDir,'clus_Table.npy')) + +end + +function call_CWaves( inputPath, binPath, spkFilePath, spkCluPath, clusTablePath, exePath ) + +% build command line to call CWaves + + args = sprintf("-spikeglx_bin=%s", binPath); + args = sprintf("%s -clus_table_npy=%s", args, clusTablePath); + args = sprintf("%s -clus_time_npy=%s", args, spkFilePath); + args = sprintf("%s -clus_lbl_npy=%s", args, spkCluPath); + args = sprintf("%s -dest=%s", args, inputPath); + args = sprintf("%s -prefix=ksproc -samples_per_spike=82 -pre_samples=20 -num_spikes=1000 -snr_radius=8 -snr_radius_um=140", args); + + cwaves_cmd = sprintf("%s %s", fullfile(exePath,'runit.bat'), args); + fprintf("%s\n", cwaves_cmd); + status = system(cwaves_cmd) + +% typical command line: +% -spikeglx_bin=\\dm11\apig\C_waves_test_data\SC024_092319_NP1.0_Midbrain_g0_tcat.imec0.ap.bin ^ +% -clus_table_npy=\\dm11\apig\C_waves_test_data\clus_Table.npy ^ +% -clus_time_npy=\\dm11\apig\C_waves_test_data\spike_times.npy ^ +% -clus_lbl_npy=\\dm11\apig\C_waves_test_data\spike_clusters.npy ^ +% -dest=\\dm11\apig\C_waves_test_data\out ^ +% -samples_per_spike=82 -pre_samples=20 -num_spikes=1000 -snr_radius=8 -snr_radius_um=140) +end + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/loadSMAfromAP.m b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/loadSMAfromAP.m new file mode 100644 index 00000000..f747c080 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/MATLAB/loadSMAfromAP.m @@ -0,0 +1,42 @@ +function DemoReadSync_IMEC() + + % Ask user for binary file + [binName, path] = uigetfile('*.bin', 'Select .ap.bin File'); + if isequal(binName,0), return; end + + % Parse corresponding .meta file + meta = SGLX_readMeta.ReadMeta(binName, path); + + % Sampling rate + sRate = SGLX_readMeta.SampRate(meta); + + % Read first 2 seconds of data + nSamp = floor(2.0 * sRate); + dataArray = SGLX_readMeta.ReadBin(0, nSamp, meta, binName, path); + + % --- Channel indices --- + nSavedChans = str2double(meta.nSavedChans); % should be 385 + syncCh = nSavedChans; % last channel is sync + apCh = 1; % example: channel 1 (first electrode) + + % Gain-correct AP channel + apData = SGLX_readMeta.GainCorrectIM(dataArray(apCh,:), apCh, meta); + + % Sync channel (already digital, no gain correction) + syncData = dataArray(syncCh,:); + + % --- Plot --- + figure; + subplot(2,1,1); + plot((0:nSamp-1)/sRate, 1e6*apData); + xlabel('Time (s)'); + ylabel('Voltage (uV)'); + title('AP channel 1'); + + subplot(2,1,2); + plot((0:nSamp-1)/sRate, syncData); + xlabel('Time (s)'); + ylabel('Sync (raw)'); + title('Sync channel'); + +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/DemoReadSGLXData/__init__.py b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/DemoReadSGLXData/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/DemoReadSGLXData/readSGLX.py b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/DemoReadSGLXData/readSGLX.py new file mode 100644 index 00000000..4f8ef036 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/DemoReadSGLXData/readSGLX.py @@ -0,0 +1,460 @@ +# -*- coding: utf-8 -*- +""" +Requires python 3 + +The main() function at the bottom of this file can run from an +interpreter, or, the helper functions can be imported into a +new module or Jupyter notebook (an example is included). + +Simple helper functions and python dictionary demonstrating +how to read and manipulate SpikeGLX meta and binary files. + +The most important part of the demo is readMeta(). +Please read the comments for that function. Use of +the 'meta' dictionary will make your data handling +much easier! + +""" +import numpy as np +import matplotlib.pyplot as plt +from pathlib import Path +from tkinter import Tk +from tkinter import filedialog + + +# Parse ini file returning a dictionary whose keys are the metadata +# left-hand-side-tags, and values are string versions of the right-hand-side +# metadata values. We remove any leading '~' characters in the tags to match +# the MATLAB version of readMeta. +# +# The string values are converted to numbers using the "int" and "float" +# functions. Note that python 3 has no size limit for integers. +# +def readMeta(binFullPath): + metaName = binFullPath.stem + ".meta" + metaPath = Path(binFullPath.parent / metaName) + metaDict = {} + if metaPath.exists(): + # print("meta file present") + with metaPath.open() as f: + mdatList = f.read().splitlines() + # convert the list entries into key value pairs + for m in mdatList: + csList = m.split(sep='=') + if csList[0][0] == '~': + currKey = csList[0][1:len(csList[0])] + else: + currKey = csList[0] + metaDict.update({currKey: csList[1]}) + else: + print("no meta file") + return(metaDict) + + +# Return sample rate as python float. +# On most systems, this will be implemented as C++ double. +# Use python command sys.float_info to get properties of float on your system. +# +def SampRate(meta): + if meta['typeThis'] == 'imec': + srate = float(meta['imSampRate']) + elif meta['typeThis'] == 'nidq': + srate = float(meta['niSampRate']) + elif meta['typeThis'] == 'obx': + srate = float(meta['obSampRate']) + else: + print('Error: unknown stream type') + srate = 1 + + return(srate) + + +# Return a multiplicative factor for converting 16-bit file data +# to voltage. This does not take gain into account. The full +# conversion with gain is: +# dataVolts = dataInt * fI2V / gain +# Note that each channel may have its own gain. +# +def Int2Volts(meta): + if meta['typeThis'] == 'imec': + if 'imMaxInt' in meta: + maxInt = int(meta['imMaxInt']) + else: + maxInt = 512 + fI2V = float(meta['imAiRangeMax'])/maxInt + elif meta['typeThis'] == 'nidq': + maxInt = int(meta['niMaxInt']) + fI2V = float(meta['niAiRangeMax'])/maxInt + elif meta['typeThis'] == 'obx': + maxInt = int(meta['obMaxInt']) + fI2V = float(meta['obAiRangeMax'])/maxInt + else: + print('Error: unknown stream type') + fI2V = 1 + + return(fI2V) + + +# Return array of original channel IDs. As an example, suppose we want the +# imec gain for the ith channel stored in the binary data. A gain array +# can be obtained using ChanGainsIM(), but we need an original channel +# index to do the lookup. Because you can selectively save channels, the +# ith channel in the file isn't necessarily the ith acquired channel. +# Use this function to convert from ith stored to original index. +# Note that the SpikeGLX channels are 0 based. +# +def OriginalChans(meta): + if meta['snsSaveChanSubset'] == 'all': + # output = int32, 0 to nSavedChans - 1 + chans = np.arange(0, int(meta['nSavedChans'])) + else: + # parse the snsSaveChanSubset string + # split at commas + chStrList = meta['snsSaveChanSubset'].split(sep=',') + chans = np.arange(0, 0) # creates an empty array of int32 + for sL in chStrList: + currList = sL.split(sep=':') + if len(currList) > 1: + # each set of contiguous channels specified by + # chan1:chan2 inclusive + newChans = np.arange(int(currList[0]), int(currList[1])+1) + else: + newChans = np.arange(int(currList[0]), int(currList[0])+1) + chans = np.append(chans, newChans) + return(chans) + + +# Return counts of each nidq channel type that composes the timepoints +# stored in the binary file. +# +def ChannelCountsNI(meta): + chanCountList = meta['snsMnMaXaDw'].split(sep=',') + MN = int(chanCountList[0]) + MA = int(chanCountList[1]) + XA = int(chanCountList[2]) + DW = int(chanCountList[3]) + return(MN, MA, XA, DW) + + +# Return counts of each imec channel type that composes the timepoints +# stored in the binary files. +# +def ChannelCountsIM(meta): + chanCountList = meta['snsApLfSy'].split(sep=',') + AP = int(chanCountList[0]) + LF = int(chanCountList[1]) + SY = int(chanCountList[2]) + return(AP, LF, SY) + +# Return counts of each obx channel type that composes the timepoints +# stored in the binary files. +# +def ChannelCountsOBX(meta): + chanCountList = meta['snsXaDwSy'].split(sep=',') + XA = int(chanCountList[0]) + DW = int(chanCountList[1]) + SY = int(chanCountList[2]) + return(XA, DW, SY) + + +# Return gain for ith channel stored in nidq file. +# ichan is a saved channel index, rather than the original (acquired) index. +# +# Note: there is nomatching function for OBX, where the gain is fixed = 1 +def ChanGainNI(ichan, savedMN, savedMA, meta): + if ichan < savedMN: + gain = float(meta['niMNGain']) + elif ichan < (savedMN + savedMA): + gain = float(meta['niMAGain']) + else: + gain = 1 # non multiplexed channels have no extra gain + return(gain) + + +# Return gain for imec channels. +# Index into these with the original (acquired) channel IDs. +# +def ChanGainsIM(meta): + # list of probe types with NP 1.0 imro format + np1_imro = [0,1020,1030,1200,1100,1120,1121,1122,1123,1300] + # number of channels acquired + acqCountList = meta['acqApLfSy'].split(sep=',') + APgain = np.zeros(int(acqCountList[0])) # default type = float64 + LFgain = np.zeros(int(acqCountList[1])) # empty array for 2.0 + + if 'imDatPrb_type' in meta: + probeType = int(meta['imDatPrb_type']) + else: + probeType = 0 + + if sum(np.isin(np1_imro, probeType)): + # imro + probe allows setting gain independently for each channel + imroList = meta['imroTbl'].split(sep=')') + # One entry for each channel plus header entry, + # plus a final empty entry following the last ')' + for i in range(0, int(acqCountList[0])): + currList = imroList[i+1].split(sep=' ') + APgain[i] = float(currList[3]) + LFgain[i] = float(currList[4]) + else: + # get gain from imChan0apGain + if 'imChan0apGain' in meta: + APgain = APgain + float(meta['imChan0apGain']) + if int(acqCountList[1]) > 0: + LFgain = LFgain + float(meta['imChan0lfGain']) + elif (probeType == 1110): + # active UHD, for metadata lacking imChan0apGain, get gain from + # imro table header + imroList = meta['imroTbl'].split(sep=')') + currList = imroList[0].split(sep=',') + APgain = APgain + float(currList[3]) + LFgain = LFgain + float(currList[4]) + elif (probeType == 21) or (probeType == 24): + # development NP 2.0; APGain = 80 for all AP + # return 0 for LFgain (no LF channels) + APgain = APgain + 80 + elif (probeType == 2013): + # commercial NP 2.0; APGain = 100 for all AP + APgain = APgain + 100 + else: + print('unknown gain, setting APgain to 1') + APgain = APgain + 1 + fI2V = Int2Volts(meta) + APChan0_to_uV = 1e6*fI2V/APgain[0] + if LFgain.size > 0: + LFChan0_to_uV = 1e6*fI2V/LFgain[0] + else: + LFChan0_to_uV = 0 + return(APgain, LFgain, APChan0_to_uV, LFChan0_to_uV) + + +# Having accessed a block of raw nidq data using makeMemMapRaw, convert +# values to gain-corrected voltage. The conversion is only applied to the +# saved-channel indices in chanList. Remember, saved-channel indices are +# in the range [0:nSavedChans-1]. The dimensions of dataArray remain +# unchanged. ChanList examples: +# [0:MN-1] all MN channels (MN from ChannelCountsNI) +# [2,6,20] just these three channels (zero based, as they appear in SGLX). +# +def GainCorrectNI(dataArray, chanList, meta): + MN, MA, XA, DW = ChannelCountsNI(meta) + fI2V = Int2Volts(meta) + # print statements used for testing... + # print("NI fI2V: %.3e" % (fI2V)) + # print("NI ChanGainNI: %.3f" % (ChanGainNI(0, MN, MA, meta))) + + # make array of floats to return. dataArray contains only the channels + # in chanList, so output matches that shape + convArray = np.zeros(dataArray.shape, dtype=float) + for i in range(0, len(chanList)): + j = chanList[i] # index in saved data + conv = fI2V/ChanGainNI(j, MN, MA, meta) + # dataArray contains only the channels in chanList + convArray[i, :] = dataArray[i, :] * conv + return(convArray) + +# Having accessed a block of raw obx data using makeMemMapRaw, convert +# values to volts. The conversion is only applied to the +# saved-channel indices in chanList. Remember, saved-channel indices are +# in the range [0:nSavedChans-1]. The dimensions of dataArray remain +# [2,6,20] just these three channels (zero based, as they appear in SGLX). +# +def GainCorrectOBX(dataArray, chanList, meta): + + fI2V = Int2Volts(meta) + + # make array of floats to return. dataArray contains only the channels + # in chanList, so output matches that shape + convArray = np.zeros(dataArray.shape, dtype=float) + for i in range(0, len(chanList)): + # dataArray contains only the channels in chanList + convArray[i, :] = dataArray[i, :] * fI2V + return(convArray) + + +# Having accessed a block of raw imec data using makeMemMapRaw, convert +# values to gain corrected voltages. The conversion is only applied to +# the saved-channel indices in chanList. Remember saved-channel indices +# are in the range [0:nSavedChans-1]. The dimensions of the dataArray +# remain unchanged. ChanList examples: +# [0:AP-1] all AP channels +# [2,6,20] just these three channels (zero based) +# Remember that for an lf file, the saved channel indices (fetched by +# OriginalChans) will be in the range 384-767 for a standard 3A or 3B probe. +# +def GainCorrectIM(dataArray, chanList, meta): + # Look up gain with acquired channel ID + chans = OriginalChans(meta) + APgain, LFgain, _, _ = ChanGainsIM(meta) + nAP = len(APgain) + nNu = nAP * 2 + + # Common conversion factor + fI2V = Int2Volts(meta) + + # make array of floats to return. dataArray contains only the channels + # in chanList, so output matches that shape + convArray = np.zeros(dataArray.shape, dtype='float') + for i in range(0, len(chanList)): + j = chanList[i] # index into timepoint + k = chans[j] # acquisition index + if k < nAP: + conv = fI2V / APgain[k] + elif k < nNu: + conv = fI2V / LFgain[k - nAP] + else: + conv = 1 + # The dataArray contains only the channels in chanList + convArray[i, :] = dataArray[i, :]*conv + return(convArray) + +# Return memmap for the raw data +# Fortran ordering is used to match the MATLAB version +# of these tools. +# +def makeMemMapRaw(binFullPath, meta): + nChan = int(meta['nSavedChans']) + nFileSamp = int(int(meta['fileSizeBytes'])/(2*nChan)) + print("nChan: %d, nFileSamp: %d" % (nChan, nFileSamp)) + rawData = np.memmap(binFullPath, dtype='int16', mode='r', + shape=(nChan, nFileSamp), offset=0, order='F') + return(rawData) + + +# Return an array [lines X timepoints] of uint8 values for a +# specified set of digital lines. +# +# - dwReq is the zero-based index into the saved file of the +# 16-bit word that contains the digital lines of interest. +# - dLineList is a zero-based list of one or more lines/bits +# to scan from word dwReq. +# +def ExtractDigital(rawData, firstSamp, lastSamp, dwReq, dLineList, meta): + # Get channel index of requested digital word dwReq + if meta['typeThis'] == 'imec': + AP, LF, SY = ChannelCountsIM(meta) + if SY == 0: + print("No imec sync channel saved.") + digArray = np.zeros((0), 'uint8') + return(digArray) + else: + digCh = AP + LF + dwReq + elif meta['typeThis'] == 'nidq': + MN, MA, XA, DW = ChannelCountsNI(meta) + if dwReq > DW-1: + print("Maximum digital word in file = %d" % (DW-1)) + digArray = np.zeros((0), 'uint8') + return(digArray) + else: + digCh = MN + MA + XA + dwReq + elif meta['typeThis'] == 'obx': + XA, DW, SY = ChannelCountsOBX(meta) + if dwReq > DW-1: + print("Maximum digital word in file = %d" % (DW-1)) + digArray = np.zeros((0), 'uint8') + return(digArray) + else: + digCh = XA + dwReq + else: + print('unknown data stream') + + selectData = np.ascontiguousarray(rawData[digCh, firstSamp:lastSamp+1], 'int16') + nSamp = lastSamp-firstSamp + 1 + + # unpack bits of selectData; unpack bits works with uint8 + # original data is int16 + bitWiseData = np.unpackbits(selectData.view(dtype='uint8')) + # output is 1-D array, nSamp*16. Reshape and transpose + bitWiseData = np.transpose(np.reshape(bitWiseData, (nSamp, 16))) + + nLine = len(dLineList) + digArray = np.zeros((nLine, nSamp), 'uint8') + for i in range(0, nLine): + byteN, bitN = np.divmod(dLineList[i], 8) + targI = byteN*8 + (7 - bitN) + digArray[i, :] = bitWiseData[targI, :] + return(digArray) + + +# Sample calling program to get a file from the user, +# read metadata fetch sample rate, voltage conversion +# values for this file and channel, and plot a small range +# of voltages from a single channel. +# Note that this code merely demonstrates indexing into the +# data file, without any optimization for efficiency. +# +def main(): + + # Get file from user + root = Tk() # create the Tkinter widget + root.withdraw() # hide the Tkinter root window + + # Windows specific; forces the window to appear in front + root.attributes("-topmost", True) + + binFullPath = Path(filedialog.askopenfilename(title="Select binary file")) + root.destroy() # destroy the Tkinter widget + + # Other parameters about what data to read + tStart = 0 + tEnd = 2 + dataType = 'A' # 'A' for analog, 'D' for digital data + + # For analog channels: zero-based index of a channel to extract, + # gain correct and plot (plots first channel only) + chanList = [0] + + # For a digital channel: zero based index of the digital word in + # the saved file. For imec data there is never more than one digital word. + dw = 0 + + # Zero-based Line indices to read from the digital word and plot. + # For 3B2 imec data: the sync pulse is stored in line 6. + dLineList = [0, 1, 6] + + # Read in metadata; returns a dictionary with string for values + meta = readMeta(binFullPath) + + # parameters common to NI and imec data + sRate = SampRate(meta) + firstSamp = int(sRate*tStart) + lastSamp = int(sRate*tEnd) + # array of times for plot + tDat = np.arange(firstSamp, lastSamp+1, dtype='uint64') + tDat = 1000*tDat/sRate # plot time axis in msec + + rawData = makeMemMapRaw(binFullPath, meta) + + if dataType == 'A': + selectData = rawData[chanList, firstSamp:lastSamp+1] + if meta['typeThis'] == 'imec': + # apply gain correction and convert to uV + convData = 1e6*GainCorrectIM(selectData, chanList, meta) + elif meta['typeThis'] == 'nidq': + MN, MA, XA, DW = ChannelCountsNI(meta) + # print("NI channel counts: %d, %d, %d, %d" % (MN, MA, XA, DW)) + # apply gain correction and convert to mV + convData = 1e3*GainCorrectNI(selectData, chanList, meta) + elif meta['typeThis'] == 'obx': + # Gain correct is just conversion to volts + convData = 1e3*GainCorrectOBX(selectData, chanList, meta) + + # Plot the first of the extracted channels + fig, ax = plt.subplots() + ax.plot(tDat, convData[0, :]) + plt.show() + + else: + digArray = ExtractDigital(rawData, firstSamp, lastSamp, dw, + dLineList, meta) + + # Plot the first of the extracted channels + fig, ax = plt.subplots() + + for i in range(0, len(dLineList)): + ax.plot(tDat, digArray[i, :]) + plt.show() + + +if __name__ == "__main__": + main() diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/read_SGLX_analog.ipynb b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/read_SGLX_analog.ipynb new file mode 100644 index 00000000..dadca219 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/read_SGLX_analog.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example imports functions from the DemoReadSGLXData module to read\n", + "# analog data and convert it to volts based on the metadata information.\n", + "# The metadata file must be present in the same directory as the binary file.\n", + "# Works with both imec and nidq analog channels.\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from pathlib import Path\n", + "from tkinter import Tk\n", + "from tkinter import filedialog\n", + "from DemoReadSGLXData.readSGLX import readMeta, SampRate, makeMemMapRaw, GainCorrectIM, GainCorrectNI" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get bin file from user\n", + "root = Tk() # create the Tkinter widget\n", + "root.withdraw() # hide the Tkinter root window\n", + "\n", + "# Windows specific; forces the window to appear in front\n", + "root.attributes(\"-topmost\", True)\n", + "\n", + "binFullPath = Path(filedialog.askopenfilename(title=\"Select binary file\"))\n", + "\n", + "root.destroy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Other parameters about what data to read\n", + "tStart = 0 # in seconds\n", + "tEnd = 0.1\n", + "chanList = [0] # list of channels to extract, by index in saved file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "meta = readMeta(binFullPath)\n", + "sRate = SampRate(meta)\n", + "\n", + "firstSamp = int(sRate*tStart)\n", + "lastSamp = int(sRate*tEnd)\n", + "rawData = makeMemMapRaw(binFullPath, meta)\n", + "selectData = rawData[chanList, firstSamp:lastSamp+1]\n", + "\n", + "\n", + "if meta['typeThis'] == 'imec':\n", + " # apply gain correction and convert to uV\n", + " convData = 1e6*GainCorrectIM(selectData, chanList, meta)\n", + "else:\n", + " # apply gain correction and convert to mV\n", + " convData = 1e3*GainCorrectNI(selectData, chanList, meta)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the first of the extracted channels\n", + "tDat = np.arange(firstSamp, lastSamp+1, dtype='uint64')\n", + "tDat = 1000*tDat/sRate # plot time axis in msec\n", + "fig, ax = plt.subplots()\n", + "ax.plot(tDat, convData[0, :])\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/read_SGLX_digital.ipynb b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/read_SGLX_digital.ipynb new file mode 100644 index 00000000..b920f7b3 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/Python/read_SGLX_digital.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This example imports functions from the DemoReadSGLXData module to read\n", + "# digital data. The metadata file must be present in the same directory as the binary file.\n", + "# Works with both imec and nidq digital channels.\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from pathlib import Path\n", + "from tkinter import Tk\n", + "from tkinter import filedialog\n", + "from DemoReadSGLXData.readSGLX import readMeta, SampRate, makeMemMapRaw, ExtractDigital" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get file from user\n", + "root = Tk() # create the Tkinter widget\n", + "root.withdraw() # hide the Tkinter root window\n", + "\n", + "# Windows specific; forces the window to appear in front\n", + "root.attributes(\"-topmost\", True)\n", + "\n", + "binFullPath = Path(filedialog.askopenfilename(title=\"Select binary file\"))\n", + "\n", + "root.destroy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Other parameters about what data to read\n", + "tStart = 0 # in seconds\n", + "tEnd = 1\n", + "# Which digital word to read. \n", + "# For imec, there is only 1 digital word, dw = 0.\n", + "# For NI, digital lines 0-15 are in word 0, lines 16-31 are in word 1, etc.\n", + "dw = 0 \n", + "# Which lines within the digital word, zero-based\n", + "# Note that the SYNC line for PXI 3B is stored in line 6.\n", + "dLineList = [0,1,6]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "meta = readMeta(binFullPath)\n", + "sRate = SampRate(meta)\n", + "\n", + "firstSamp = int(sRate*tStart)\n", + "lastSamp = int(sRate*tEnd)\n", + "rawData = makeMemMapRaw(binFullPath, meta)\n", + "\n", + "# get digital data for the selected lines\n", + "digArray = ExtractDigital(rawData, firstSamp, lastSamp, dw, dLineList, meta)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the extracted digital channels\n", + "tDat = np.arange(firstSamp, lastSamp+1, dtype='uint64')\n", + "tDat = 1000*tDat/sRate # plot time axis in msec\n", + "fig, ax = plt.subplots()\n", + "for i in range(0, len(dLineList)):\n", + " ax.plot(tDat, digArray[i, :])\n", + "plt.show()\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/README.md b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/README.md new file mode 100644 index 00000000..0bb2681c --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/SpikeGLX_Datafile_Tools-main/README.md @@ -0,0 +1,11 @@ +# SpikeGLX_Datafile_Tools + +Simple helper functions and data structures demonstrating how to read and manipulate SpikeGLX meta and binary files. + +Works with 3A and all current probe types, as of 1/3/24. + +The same basic functionality is provided for MATLAB and Python. + +The Python material can be used from an interpreter or imported into your own modules. An example Jupyter notebook is included. + +The examples are functional and illustrate how to parse the data, but they are not optimized for speed or for memory usage. diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/constructNPYheader.m b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/constructNPYheader.m new file mode 100644 index 00000000..ca2840ec --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/constructNPYheader.m @@ -0,0 +1,88 @@ + + + +function header = constructNPYheader(dataType, shape, varargin) + + if ~isempty(varargin) + fortranOrder = varargin{1}; % must be true/false + littleEndian = varargin{2}; % must be true/false + else + fortranOrder = true; + littleEndian = true; + end + + dtypesMatlab = {'uint8','uint16','uint32','uint64','int8','int16','int32','int64','single','double', 'logical'}; + dtypesNPY = {'u1', 'u2', 'u4', 'u8', 'i1', 'i2', 'i4', 'i8', 'f4', 'f8', 'b1'}; + + magicString = uint8([147 78 85 77 80 89]); %x93NUMPY + + majorVersion = uint8(1); + minorVersion = uint8(0); + + % build the dict specifying data type, array order, endianness, and + % shape + dictString = '{''descr'': '''; + + if littleEndian + dictString = [dictString '<']; + else + dictString = [dictString '>']; + end + + dictString = [dictString dtypesNPY{strcmp(dtypesMatlab,dataType)} ''', ']; + + dictString = [dictString '''fortran_order'': ']; + + if fortranOrder + dictString = [dictString 'True, ']; + else + dictString = [dictString 'False, ']; + end + + dictString = [dictString '''shape'': (']; + +% if length(shape)==1 && shape==1 +% +% else +% for s = 1:length(shape) +% if s==length(shape) && shape(s)==1 +% +% else +% dictString = [dictString num2str(shape(s))]; +% if length(shape)>1 && s+1==length(shape) && shape(s+1)==1 +% dictString = [dictString ',']; +% elseif length(shape)>1 && s %s', tempFilename, inFilename, outFilename)); + + otherwise + fprintf(1, 'I don''t know how to concatenate files for your OS, but you can finish making the NPY youself by concatenating %s with %s.\n', tempFilename, inFilename); +end + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/readNPY.m b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/readNPY.m new file mode 100644 index 00000000..9095d003 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/readNPY.m @@ -0,0 +1,37 @@ + + +function data = readNPY(filename) +% Function to read NPY files into matlab. +% *** Only reads a subset of all possible NPY files, specifically N-D arrays of certain data types. +% See https://github.com/kwikteam/npy-matlab/blob/master/tests/npy.ipynb for +% more. +% + +[shape, dataType, fortranOrder, littleEndian, totalHeaderLength, ~] = readNPYheader(filename); + +if littleEndian + fid = fopen(filename, 'r', 'l'); +else + fid = fopen(filename, 'r', 'b'); +end + +try + + [~] = fread(fid, totalHeaderLength, 'uint8'); + + % read the data + data = fread(fid, prod(shape), [dataType '=>' dataType]); + + if length(shape)>1 && ~fortranOrder + data = reshape(data, shape(end:-1:1)); + data = permute(data, [length(shape):-1:1]); + elseif length(shape)>1 + data = reshape(data, shape); + end + + fclose(fid); + +catch me + fclose(fid); + rethrow(me); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/readNPYheader.m b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/readNPYheader.m new file mode 100644 index 00000000..7776de52 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/readNPYheader.m @@ -0,0 +1,72 @@ + + +function [arrayShape, dataType, fortranOrder, littleEndian, totalHeaderLength, npyVersion] = readNPYheader(filename) +% function [arrayShape, dataType, fortranOrder, littleEndian, ... +% totalHeaderLength, npyVersion] = readNPYheader(filename) +% +% parse the header of a .npy file and return all the info contained +% therein. +% +% Based on spec at http://docs.scipy.org/doc/numpy-dev/neps/npy-format.html + +fid = fopen(filename); + +% verify that the file exists +if (fid == -1) + if ~isempty(dir(filename)) + error('Permission denied: %s', filename); + else + error('File not found: %s', filename); + end +end + +try + + dtypesMatlab = {'uint8','uint16','uint32','uint64','int8','int16','int32','int64','single','double', 'logical'}; + dtypesNPY = {'u1', 'u2', 'u4', 'u8', 'i1', 'i2', 'i4', 'i8', 'f4', 'f8', 'b1'}; + + + magicString = fread(fid, [1 6], 'uint8=>uint8'); + + if ~all(magicString == [147,78,85,77,80,89]) + error('readNPY:NotNUMPYFile', 'Error: This file does not appear to be NUMPY format based on the header.'); + end + + majorVersion = fread(fid, [1 1], 'uint8=>uint8'); + minorVersion = fread(fid, [1 1], 'uint8=>uint8'); + + npyVersion = [majorVersion minorVersion]; + + headerLength = fread(fid, [1 1], 'uint16=>uint16'); + + totalHeaderLength = 10+headerLength; + + arrayFormat = fread(fid, [1 headerLength], 'char=>char'); + + % to interpret the array format info, we make some fairly strict + % assumptions about its format... + + r = regexp(arrayFormat, '''descr''\s*:\s*''(.*?)''', 'tokens'); + if isempty(r) + error('Couldn''t parse array format: "%s"', arrayFormat); + end + dtNPY = r{1}{1}; + + littleEndian = ~strcmp(dtNPY(1), '>'); + + dataType = dtypesMatlab{strcmp(dtNPY(2:3), dtypesNPY)}; + + r = regexp(arrayFormat, '''fortran_order''\s*:\s*(\w+)', 'tokens'); + fortranOrder = strcmp(r{1}{1}, 'True'); + + r = regexp(arrayFormat, '''shape''\s*:\s*\((.*?)\)', 'tokens'); + shapeStr = r{1}{1}; + arrayShape = str2num(shapeStr(shapeStr~='L')); + + + fclose(fid); + +catch me + fclose(fid); + rethrow(me); +end diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/writeNPY.m b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/writeNPY.m new file mode 100644 index 00000000..844dc35c --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/npy-matlab/writeNPY.m @@ -0,0 +1,25 @@ + + +function writeNPY(var, filename) +% function writeNPY(var, filename) +% +% Only writes little endian, fortran (column-major) ordering; only writes +% with NPY version number 1.0. +% +% Always outputs a shape according to matlab's convention, e.g. (10, 1) +% rather than (10,). + + +shape = size(var); +dataType = class(var); + +header = constructNPYheader(dataType, shape); + +fid = fopen(filename, 'w'); +fwrite(fid, header, 'uint8'); +fwrite(fid, var, dataType); +fclose(fid); + + +end + diff --git a/ExperPort/Modules/NeuropixelHelper_Modules/read_TTL_Events.m b/ExperPort/Modules/NeuropixelHelper_Modules/read_TTL_Events.m new file mode 100644 index 00000000..b1840902 --- /dev/null +++ b/ExperPort/Modules/NeuropixelHelper_Modules/read_TTL_Events.m @@ -0,0 +1,120 @@ +% This script demonstrates how to load and analyze TTL event data from an +% Open Ephys recording session saved in the binary (.npy) format. +% +% PRE-REQUISITES: +% 1. The 'npy-matlab' library must be downloaded from GitHub: +% https://github.com/kwikteam/npy-matlab +% 2. The path to the 'npy-matlab' folder must be added to your MATLAB path. +% e.g., addpath('C:\path\to\npy-matlab\npy-matlab') + +clear; clc; close all; + +%% --- User-Defined Parameters --- + +% 1. Set the path to the folder containing your TTL event files. +% This is typically a path like: +% ...\\events\Rhythm_FPGA-100.0\TTL_1 +% ttl_folder_path = ['C:\Ephys_Experiment_Data\sound_cat_rat\rawdata\sub-002_id-LP12_expmtr-lida\ses-01_date-20250707T151043_dtype-ephys\ephys\2025-07-07_15-11-36\Record Node 101\experiment1\recording2...' ... + % '\events\Neuropix-PXI-100.ProbeA\TTL']; + + ttl_folder_path = ['C:\Ephys_Experiment_Data\sound_cat_rat\rawdata\sub-003_id-LP12_expmtr-lida\ses-20_date-20250812T124748_dtype-ephys\ephys\2025-08-12_12-58-23\Record Node 101\experiment1\recording1\events\Neuropix-PXI-100.ProbeA-LFP\TTL']; +% 2. Define which TTL channel you want to analyze. +% (e.g., channel 1, 2, 3, etc.) +channel_to_analyze = 1; + + +%% --- Check for Required Files and Library --- + +% Verify that the npy-matlab library function exists +if ~exist('readNPY.m', 'file') + error(['The ''readNPY.m'' function was not found. ' ... + 'Please ensure the npy-matlab library is downloaded ' ... + 'and added to your MATLAB path.']); +end + +% Construct the full file paths +timestamps_file = fullfile(ttl_folder_path, 'timestamps.npy'); +states_file = fullfile(ttl_folder_path, 'states.npy'); + +% Verify that the data files exist +if ~exist(timestamps_file, 'file') || ~exist(states_file, 'file') + error('Could not find "timestamps.npy" or "states.npy" in the specified folder: %s', ttl_folder_path); +end + + +%% --- Load the Data using readNPY --- + +fprintf('Loading data from: %s\n', ttl_folder_path); + +try + % readNPY will read the .npy file and convert it into a MATLAB array. + % The data types are handled automatically. + timestamps = readNPY(timestamps_file); % Timestamps in seconds + channel_states = readNPY(states_file); % Channel state changes (+/- channel number) + + fprintf('Successfully loaded %d events.\n', length(timestamps)); + +catch ME + error('Failed to read .npy files. Error details: %s', ME.message); +end + + +%% --- Analyze and Extract Specific Events --- + +% Find the indices of all events related to the channel of interest. +% We are interested in both rising edges (ON state, positive number) and +% falling edges (OFF state, negative number). +indices_for_channel = find(abs(channel_states) == channel_to_analyze); + +if isempty(indices_for_channel) + fprintf('No events found for TTL channel %d.\n', channel_to_analyze); + return; +end + +% Extract the specific timestamps and states for our channel +channel_timestamps = timestamps(indices_for_channel); +channel_specific_states = channel_states(indices_for_channel); + +% --- Find Rising Edges (Pulse Starts) --- +% A rising edge corresponds to a positive channel number in the states array. +rising_edge_indices = find(channel_specific_states > 0); +rising_edge_timestamps = channel_timestamps(rising_edge_indices); + +% --- Find Falling Edges (Pulse Ends) --- +% A falling edge corresponds to a negative channel number. +falling_edge_indices = find(channel_specific_states < 0); +falling_edge_timestamps = channel_timestamps(falling_edge_indices); + +fprintf('Found %d rising edges (pulse starts) for channel %d.\n', ... + length(rising_edge_timestamps), channel_to_analyze); +fprintf('Found %d falling edges (pulse ends) for channel %d.\n', ... + length(falling_edge_timestamps), channel_to_analyze); + + +%% --- Visualization --- + +figure('Name', 'TTL Event Analysis', 'NumberTitle', 'off', 'Color', 'w'); +hold on; +grid on; + +% Plot all events for the specified channel as vertical lines +% Green for ON (rising edge), Red for OFF (falling edge) +for i = 1:length(rising_edge_timestamps) + plot([rising_edge_timestamps(i), rising_edge_timestamps(i)], [0, 1], 'g-', 'LineWidth', 1.5); +end + +for i = 1:length(falling_edge_timestamps) + plot([falling_edge_timestamps(i), falling_edge_timestamps(i)], [0, 1], 'r--', 'LineWidth', 1.5); +end + +% Make the plot informative +title(sprintf('TTL Events for Channel %d', channel_to_analyze)); +xlabel('Time (seconds)'); +ylabel('Event'); +ylim([-0.1, 1.1]); +set(gca, 'YTick', [0, 1], 'YTickLabel', {'OFF', 'ON'}); +legend({'Rising Edge (ON)', 'Falling Edge (OFF)'}, 'Location', 'best'); + +fprintf('Plot generated successfully.\n'); + +%% diff --git a/ExperPort/Modules/start_camera.m b/ExperPort/Modules/start_camera.m index deb3542f..fca5116e 100644 --- a/ExperPort/Modules/start_camera.m +++ b/ExperPort/Modules/start_camera.m @@ -20,13 +20,23 @@ '172.24.155.117', '172.24.155.118', '172.24.155.119', - '172.24.155.120' - }; + '172.24.155.120', + '172.24.155.121', + '172.24.155.122', + '172.24.155.123', + '172.24.155.124', + '172.24.155.125', + '172.24.155.126', + '172.24.155.127', + '172.24.155.128', + '172.24.155.129', + '172.24.155.130', + '172.24.155.131'}; rig_camIP = ips{rig_id}; if strcmp(flag,'start') filename = [rat_id,'_',datestr(date,'yymmdd')]; disp('created filename') - command_string = ['plink.exe -ssh pi@',rig_camIP,' -pw raspberry cd Pi_camera; python3 streamnrecord.py ', ' ',filename,' ',rat_id,' ',protocol,' "COMMAND >/dev/null &"']; + command_string = ['plink.exe -ssh pi@',rig_camIP,' -pw raspberry cd pi_camera; python3 streamnrecord.py ', ' ',filename,' ',rat_id,' ',protocol,' "COMMAND >/dev/null &"']; system(command_string); disp('sent starting command') diff --git a/ExperPort/Plugins/@bonsaicamera/.bonsai/Settings/Camera_Control.editor b/ExperPort/Plugins/@bonsaicamera/.bonsai/Settings/Camera_Control.editor new file mode 100644 index 00000000..bbd6dca8 --- /dev/null +++ b/ExperPort/Plugins/@bonsaicamera/.bonsai/Settings/Camera_Control.editor @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ExperPort/Plugins/@bonsaicamera/.bonsai/Settings/Camera_Control.layout b/ExperPort/Plugins/@bonsaicamera/.bonsai/Settings/Camera_Control.layout new file mode 100644 index 00000000..386f50b5 --- /dev/null +++ b/ExperPort/Plugins/@bonsaicamera/.bonsai/Settings/Camera_Control.layout @@ -0,0 +1,62 @@ + + + + false + + 94 + 95 + + + 333 + 63 + + Bonsai.Design.ObjectTextVisualizer + + + + + + false + + 472 + 106 + + + 333 + 63 + + Bonsai.Design.ObjectTextVisualizer + + + + + + + 15 + 705 + + + 333 + 277 + + Bonsai.Vision.Design.IplImageVisualizer + + + + + + false + + 95 + 246 + + + 333 + 177 + + Bonsai.Design.ObjectTextVisualizer + + + + + \ No newline at end of file diff --git a/ExperPort/Plugins/@bonsaicamera/BonsaiCameraInterface.m b/ExperPort/Plugins/@bonsaicamera/BonsaiCameraInterface.m new file mode 100644 index 00000000..a0cb8a64 --- /dev/null +++ b/ExperPort/Plugins/@bonsaicamera/BonsaiCameraInterface.m @@ -0,0 +1,309 @@ +%% BonsaiCameraInterface - MATLAB class interface to control USB camera via Bonsai using OSC messages. +% +% This class enables communication between MATLAB and a Bonsai workflow for controlling a USB-based camera. +% It uses Open Sound Control (OSC) messages transmitted over UDP to start and stop the camera feed, as well +% as to specify the location where video recordings should be saved. +% +% Dependencies: +% - Camera_Control.bonsai: Bonsai workflow file (must be saved in the same folder as this class file) +% - private/createOSCMessage.m: Helper function to format OSC messages +% - private/runBonsaiWorkflow.m: Helper function to launch Bonsai with a specific workflow +% +% Initialization ('init' action): +% Initialization ('init' action): +% - Requires exactly five input arguments in the following order: +% 1. x-position of the camera control toggle button (UI element in Solo GUI) +% 2. y-position of the camera control toggle button +% 3. Protocol name +% 4. Experimenter name +% 5. Rat name +% - If any of these are missing or fewer than five arguments are provided, the initialization will result in an error. + +% - Launches the Bonsai workflow using runBonsaiWorkflow.m This function is used to start a Bonsai workflow (.bonsai file) from within MATLAB. +% It builds and executes a command to launch the Bonsai executable with the selected workflow, +% allowing automated and optionally parameterized workflow startup. + +% - Sets up a UDP sender object to communicate via OSC. + +% - Automatically creates a structured directory for saving video files in the format: +% C:\ratter_Videos\\\video_@___[a-z] +% A suffix is appended if there are multiple sessions on the same date. + +% - Sends initial OSC messages to set the video save path and begin streaming. +% +% Switch-case structure: +% +% 'init' : Initializes UI toggle, starts Bonsai workflow, sets up save directory and UDP sender. +% 'camera_connection' : Sends OSC messages to start or stop the camera feed. +% 'record_start' : creates the directory to save videos initially declared +% in 'init' and then send the message to Bonsai to start recording. +% 'next_trial' : Sends a new video save path to Bonsai to separate trial recordings. +% 'close' : Stops the camera, deletes the UDP sender object, and terminates Bonsai and CMD processes. +% 'stop' : Sending OSC message to stop streaming and save last trial video +% Notes: +% - Uses IP 127.0.0.1 and port 9090 for sending OSC messages to Bonsai. +% - Messages use the OSC addresses "/camera" and "/record" with "start"/"stop" commands. + +function [varargout] = BonsaiCameraInterface(obj,action,varargin) + +% If creating an empty object, return without further ado: +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')) + return; +end + +GetSoloFunctionArgs(obj); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%% DO NOT CHANGE THEM UNLESS YOU MAKE THE SAME CHANGES IN BONSAI %%%%%%%%% + +% UDP Connection Parameters to connect to Bonsai +bonsaiComputerIP = '127.0.0.1'; % IP address of the PC running Bonsai (use '127.0.0.1' if same PC) +bonsaiComputer_name = 'Receiver'; +bonsaiUdpPort = 9090; % Port configured in Bonsai's UdpReceiver +% Sending the Command to Turn on the Camera. Remember to use this +% as in Bonsai I am using the address as /camera and using +% condition to compare the arriving message. The message could only +% be 'start' or 'stop' with '/camera' address for bonsai to react +startCommand = "start"; % Use string type. MUST MATCH Bonsai Filter Value +stopCommand = "stop"; % Use string type. MUST MATCH Bonsai Filter Value +camera_command_address = "/camera"; % Use string type. MUST MATCH Bonsai Address Value +recording_command_address = "/record"; % Use string type. MUST MATCH Bonsai Address Value + +% The location of Bonsai workflow in the system. In this case it is saved +% within the ratter > ExpertPort > Plugins > @bonsaicamera folder +scriptFullPath = mfilename('fullpath'); % Path of running the current script +scriptDirectory = fileparts(scriptFullPath); +bonsai_workflow_Path = fullfile(scriptDirectory,'Camera_Control.bonsai'); +foundworkflow = isfile(bonsai_workflow_Path); +if ~foundworkflow + warning('could not find bonsai executable, please insert it manually'); + [bonsai_fname, bonsai_fpath] = uigetfile( '*.bonsai', 'Provide the path to Bonsai executable'); + bonsai_workflow_Path = fullfile(bonsai_fpath, bonsai_fname); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +% Make the action case insensitive +action = lower(action); + +switch action + + case 'init' + + if length(varargin) < 2 + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end + + x = varargin{1}; + y = varargin{2}; + varargout{1} = x; + varargout{2} = y; + + ToggleParam(obj, 'CameraControl', 1, x, y, 'OnString', 'Camera ON', ... + 'OffString', 'Camera OFF', 'TooltipString', 'Turn On/Off the streaming and Saving of Camera Feed'); + set_callback(CameraControl, {mfilename, 'camera_connection'}); %#ok (Defined just above) + next_row(y); + + %% STEP 1: Start the Bonsai app to control the USB based Camera + + % Run the Bonsai App with the workflow + + % bonsai_path = bonsaiPath(32); + % command = sprintf('"%s" "%s" --start', bonsai_path, bonsai_file_path); + % system([command, ' &']); + + % Bonsai app and the workflow is being run by helper function + + runBonsaiWorkflow(bonsai_workflow_Path); + pause(5); % wait few seconds for software to open before continuing + + %% STEP 2: DECLARING FOLDER LOCATION FOR SAVING THE VIDEOS + + % The video files are saved in the folder format declared below + % C:\RatterVideos\ExpName\RateName\Videos_Protocolname_ExpName_RatName_date + % (the same format as Data file) + + switch length(varargin) + case 2 + protocol_name = lower(class(obj)); + experimenter_name_handle = get_sphandle('fullname', 'SavingSection_experimenter'); + if isempty(experimenter_name_handle) + experimenter_name = 'experimenter'; + else + experimenter_name = value(experimenter_name_handle{1}); + end + rat_name_handle = get_sphandle('fullname', 'SavingSection_ratname'); + if isempty(rat_name_handle) + rat_name = 'ratname'; + else + rat_name = value(rat_name_handle{1}); + end + + case 3 + protocol_name = varargin{3}; + experimenter_name_handle = get_sphandle('fullname', 'SavingSection_experimenter'); + if isempty(experimenter_name_handle) + experimenter_name = 'experimenter'; + else + experimenter_name = value(experimenter_name_handle{1}); + end + rat_name_handle = get_sphandle('fullname', 'SavingSection_ratname'); + if isempty(rat_name_handle) + rat_name = 'ratname'; + else + rat_name = value(rat_name_handle{1}); + end + + case 4 + protocol_name = varargin{3}; + experimenter_name = varargin{4}; + rat_name_handle = get_sphandle('fullname', 'SavingSection_ratname'); + if isempty(rat_name_handle) + rat_name = 'ratname'; + else + rat_name = value(rat_name_handle{1}); + end + + case 5 + protocol_name = varargin{3}; + experimenter_name = varargin{4}; + rat_name = varargin{5}; + end + + + % Changing the Video file save location from C:\ratter_Videos to + % C:\ratter\training_videos + + % main_dir_video = [current_dir 'ratter_Videos']; + current_dir = cd; + ratter_dir = extractBefore(current_dir,'ratter'); + main_dir_video = fullfile(ratter_dir, 'ratter','training_videos'); + date_str = regexprep(char(datetime('today','Format','yyyy-MM-dd')), '[^0-9]', ''); + video_foldername = sprintf('video_@%s_%s_%s_%s',protocol_name,experimenter_name,rat_name,date_str); + rat_dir = sprintf('%s\\%s\\%s',main_dir_video,experimenter_name,rat_name); + video_save_dir = sprintf('%s\\%s\\%s\\%s',main_dir_video,experimenter_name,rat_name,video_foldername); + % We have the general structure of folder save location, now need to + % check if there is any other folder for same date. We will add a + % alphabet in the end based upon the no. of files present. + if exist(rat_dir,'dir') == 7 + listing = dir(rat_dir); + folderNames_rat_dir = {listing(find([listing.isdir])).name}; + folderNames_rat_dir = folderNames_rat_dir(~ismember(folderNames_rat_dir,{'.','..'})); % Remove the '.' and '..' entries (current and parent directories) + sessions_today = length(find(contains(folderNames_rat_dir,video_foldername))); % number of folders containing the video foldername + video_save_dir = [video_save_dir char(sessions_today + 97)]; + else + video_save_dir = [video_save_dir char(97)]; + end + % mkdir(video_save_dir); + SoloParamHandle(obj, 'Video_Saving_Folder', 'value', video_save_dir); + + % --- Create UDP Port Object --- + % This object can send to any destination without prior connection + udpSender = udpport("datagram","IPV4",'LocalHost',bonsaiComputerIP,... + 'LocalPort',9091,'Tag','MATLABSender'); + + SoloParamHandle(obj, 'UDPSender', 'value', udpSender, 'saveable', 0); + + + + %% STEP 3: START STREAMING AND SAVING OF THE VIDEO + + BonsaiCameraInterface(obj,'camera_connection'); + + + + + %% Camera connection On/Off + case 'camera_connection' + + if CameraControl == 1 % User Selected to Reconnect & Restart the feed from the Camera + + % Now that we have created the UPD connection, we can send commands + % over OSC. + + % OSC message to start the camera + oscMsg_Camera_start = createOSCMessage(camera_command_address, startCommand); + % the command to send message to Bonsai + write(value(UDPSender), oscMsg_Camera_start, "uint8", bonsaiComputerIP,bonsaiUdpPort); + + % NOTE: Ideally I should start saving the trials once the experimenter presses + % Run either on dispatcher or Runrats. But, I dont want to make the changes there + % so would start recording as soon as the protocol is loaded and camera starts streaming + %% Fixed in runrats + % pause(3); + % write(value(UDPSender), oscMsg_file_directory, "uint8", bonsaiComputerIP,bonsaiUdpPort); + + else % User Stopped the streaming and saving OF VIDEO + + % this stops saving and streaming of the camera + oscMsg_Camera_stop = createOSCMessage("/camera", stopCommand); + write(value(UDPSender), oscMsg_Camera_stop, "uint8", bonsaiComputerIP,bonsaiUdpPort); + + end + + %% User pressed Run on runrats, so create the video direcctory folder and start recording + case 'record_start' + + % create the folder to save the videos + if not(isfolder(value(Video_Saving_Folder))) + mkdir(value(Video_Saving_Folder)); + end + pause(3); + % To be on safer side that the last streaming message reached lets + % resend it + % OSC message to start the camera + oscMsg_Camera_start = createOSCMessage(camera_command_address, startCommand); + % the command to send message to Bonsai + write(value(UDPSender), oscMsg_Camera_start, "uint8", bonsaiComputerIP,bonsaiUdpPort); + pause(2); + % Send the message to start recording + oscMsg_file_directory = createOSCMessage(recording_command_address,sprintf('%s\\BControlTrial%i_bonsaiTrial.avi',value(Video_Saving_Folder),n_done_trials+1)); + write(value(UDPSender), oscMsg_file_directory, "uint8", bonsaiComputerIP,bonsaiUdpPort); + + + case 'stop' % Stopping the streaming and saving last trial video + + % this stops saving and streaming of the camera + oscMsg_Camera_stop = createOSCMessage("/camera", stopCommand); + write(value(UDPSender), oscMsg_Camera_stop, "uint8", bonsaiComputerIP,bonsaiUdpPort); + + %% next trial + % SEND STRING TO BONSAI AT THE END OF THE TRIAL TO SAVE NEXT TRIAL IN + % NEW VIDEO FILE + case 'next_trial' + + % in this I send a command to bonsai so that it creates a new file + % for each trial + + oscMsg_file_directory = createOSCMessage(recording_command_address,sprintf('%s\\BControlTrial%i_bonsaiTrial.avi',value(Video_Saving_Folder),n_done_trials+1)); + write(value(UDPSender), oscMsg_file_directory, "uint8", bonsaiComputerIP,bonsaiUdpPort); + + + case 'get_video_filepath' + + varargout{1} = value(Video_Saving_Folder); + + case 'set_video_filepath' + + Video_Saving_Folder.value = varargin{1}; + + %% close bonsai and command window + case 'close' + + % this would stop the workflow + oscMsg_Camera_stop = createOSCMessage("/camera", stopCommand); + write(value(UDPSender), oscMsg_Camera_stop, "uint8", bonsaiComputerIP,bonsaiUdpPort); + + delete(value(UDPSender)); % delete the UDP Port + + % this is to kill the Bonsai app and cmd + system('taskkill /F /IM Bonsai.exe'); + system('taskkill /F /IM cmd.exe'); + + +end + +return diff --git a/ExperPort/Plugins/@bonsaicamera/Camera_Control.bonsai b/ExperPort/Plugins/@bonsaicamera/Camera_Control.bonsai new file mode 100755 index 00000000..2e796c2a --- /dev/null +++ b/ExperPort/Plugins/@bonsaicamera/Camera_Control.bonsai @@ -0,0 +1,190 @@ + + + + + + + Receiver + 9090 + MATLABSender + 0 + + + + + tcp://localhost:5557 + XSubscriber + + + + /camera + s + Receiver + + + Camera_Control + + + Camera + + + + jpg + + + + + + + + + tcp://localhost:5557 + + + + + 0 + + + + + Camera_Control + + + + + + Source1 + + + + start + + + + + + + + + + + + + + + Camera + + + Camera_Control + + + + + + Source1 + + + + stop + + + + + + + + + + + + + + + + Camera + + + /record + s + Receiver + + + + PT1S + + + + + + + + + + + + + + RecordTrials + + + + Source1 + + + + + + + C:\Users\Public\Public Videos\Trial.avi + FileCount + true + false + FMP4 + 30 + + 0 + 0 + + NearestNeighbor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ExperPort/Plugins/@bonsaicamera/bonsaicamera.m b/ExperPort/Plugins/@bonsaicamera/bonsaicamera.m new file mode 100644 index 00000000..c6b2a557 --- /dev/null +++ b/ExperPort/Plugins/@bonsaicamera/bonsaicamera.m @@ -0,0 +1,4 @@ +function [obj] = bonsaicamera(varargin) + +%Argument handling +obj = class(struct, mfilename, saveload); \ No newline at end of file diff --git a/ExperPort/Plugins/@bonsaicamera/private/createOSCMessage.m b/ExperPort/Plugins/@bonsaicamera/private/createOSCMessage.m new file mode 100644 index 00000000..0ddee6c1 --- /dev/null +++ b/ExperPort/Plugins/@bonsaicamera/private/createOSCMessage.m @@ -0,0 +1,30 @@ +function oscPacket = createOSCMessage(address, arg) +% Ensure address and arg are char (not string) +if isstring(address) + address = char(address); +end +if isstring(arg) + arg = char(arg); +end + +% Helper to pad with nulls to 4-byte alignment + function bytes = padNulls(str) + strBytes = uint8([str, 0]); % null-terminated + padding = mod(4 - mod(length(strBytes), 4), 4); + bytes = [strBytes, zeros(1, padding, 'uint8')]; + end + +% Address (e.g., "/camera", "/record") +addrBytes = padNulls(address); + +% Type Tag String (e.g., ",s" for a single string argument) +typeTag = ',s'; +tagBytes = padNulls(typeTag); + +% Argument (e.g., "start") +argBytes = padNulls(arg); + +% Combine all parts +oscPacket = [addrBytes, tagBytes, argBytes]; + +end diff --git a/ExperPort/Plugins/@bonsaicamera/private/runBonsaiWorkflow.m b/ExperPort/Plugins/@bonsaicamera/private/runBonsaiWorkflow.m new file mode 100644 index 00000000..2eb56086 --- /dev/null +++ b/ExperPort/Plugins/@bonsaicamera/private/runBonsaiWorkflow.m @@ -0,0 +1,103 @@ +function runBonsaiWorkflow(workflowPath, addOptArray, bonsaiExePath, external) + +%% addOptArray = {'field', 'val', 'field2', 'val2'}; +%% check the input +switch nargin + case 1 + bonsaiExePath = bonsaiPath(32); + addOptArray = ''; + addFlag = 0; + external = 1; + case 2 + bonsaiExePath = bonsaiPath(32); + addFlag = 1; + external = 1; + case 3 + addFlag = 1; + external = 1; + case 4 + if isempty(bonsaiExePath) + bonsaiExePath = bonsaiPath(32); + end + addFlag = 1; + + otherwise + error('Too many variables'); +end + + + +%% write the command +optSuffix = '-p:'; +startFlag = '--start'; +noEditorFlag = '--noeditor'; %use this instead of startFlag to start Bonsai without showing the editor +cmdsep = ' '; +if external + command = [['"' bonsaiExePath '"'] cmdsep ['"' workflowPath '"'] cmdsep startFlag]; +else + command = [['"' bonsaiExePath '"'] cmdsep ['"' workflowPath '"'] cmdsep noEditorFlag]; +end +ii = 1; +commandAppend = ''; +if addFlag + while ii < size(addOptArray,2) + commandAppend = [commandAppend cmdsep optSuffix addOptArray{ii} '="' num2str(addOptArray{ii+1}) '"' ]; + ii = ii+2; + end +end + +if external + command = [command commandAppend ' &']; +else + command = [command commandAppend]; +end +%% run the command +[sysecho] = system(command); + + + +%% parse the addOptArray + function out = parseAddOptArray(addOptArray) + addOpt = cell(length(addOptArray)/2,2); + for ii = 1:length(addOpt) + addOpt{ii,1} = addOptArray{2*ii-1}; %gets the propriety + addOpt{ii,2} = addOptArray{2*ii}; %gets the value + end + out = addOpt; + end + + + function pathout = bonsaiPath(x64x86) + + %%version of bonsai + if nargin <1; x64x86 = 64; end + switch x64x86 + case {'64', 64, 'x64', 1, '64bit'} + bonsaiEXE = 'Bonsai64.exe'; + case {32, '32', 'x86', 0, '32bit'} + bonsaiEXE = 'Bonsai.exe'; + end + + dirs = {'appdata', 'localappdata', 'programfiles', 'programfiles(x86)','USERPROFILE'}; + foundBonsai = 0; + dirIDX = 1; + while ~foundBonsai && (dirIDX <= length(dirs)) + pathout = fullfile(getenv(dirs{dirIDX}),'Bonsai', bonsaiEXE); + foundBonsai = exist(pathout, 'file'); + dirIDX = dirIDX +1; + end + + if ~foundBonsai + pathout = fullfile(getenv(dirs{5}),'Desktop',bonsaiEXE); + foundBonsai = exist(pathout, 'file'); + end + + if ~foundBonsai + warning('could not find bonsai executable, please insert it manually'); + [fname fpath] = uigetfile( '*.exe', 'Provide the path to Bonsai executable'); + pathout = fullfile(fpath, fname); + end + + end + +end \ No newline at end of file diff --git a/ExperPort/Plugins/@pokesplot2/PokesPlotSection.m b/ExperPort/Plugins/@pokesplot2/PokesPlotSection.m index e25487c1..fae5708a 100644 --- a/ExperPort/Plugins/@pokesplot2/PokesPlotSection.m +++ b/ExperPort/Plugins/@pokesplot2/PokesPlotSection.m @@ -355,10 +355,25 @@ %% Formatting graphics elements %myfig + original_width = 0.46875; + original_height = 0.815; + + max_size = min(original_width, original_height); + aspect_ratio = original_width / original_height; + new_width = max_size; + new_height = new_width / aspect_ratio; + + center_x = 0.5 - (new_width / 2); + center_y = 0.5 - (new_height / 2); + + position_vector = [center_x center_y new_width new_height]; + + + set(value(myfig), ... 'Units', 'normalized', ... 'Name', mfilename, ... - 'Position', [0.27031 0.0275 0.46875 0.815]); + 'Position', position_vector); %textHeader set(get_ghandle(textHeader), ... @@ -788,16 +803,24 @@ llim = ulim - value(ntrials) + 1; YLim = [llim ulim]; end + + % Panel for Axes + hndl_uipanelAxes = uipanel('Units', 'normalized','Parent', value(myfig), ... + 'Title', 'Pokes_Plot', ... + 'Tag', 'uipanelplot', ... + 'Position', [0.11,0.33,0.55,0.56]); + set(value(axpokesplot), ... 'Units', 'normalized', ... - 'Parent', value(myfig), ... + 'Parent', hndl_uipanelAxes, ... 'Color', [0.3 0.3 0.3], ... 'Tag', 'axpokesplot', ... 'Visible', 'on', ... 'ButtonDownFcn', [mfilename '(' class(obj) ', ''axpokesplot_callback'');'], ... 'YLim', [YLim(1)-0.5 YLim(2)+0.5], ... 'XLim', [value(t0) value(t1)], ... - 'Position', [0.11 0.33333 0.55667 0.56339]); + 'Position', [0.1 0.1 0.88 0.88]); + xlabel(value(axpokesplot), 'Time (seconds)'); if get(value(checkboxUseCustomPreferences), 'Value') ylabel(value(axpokesplot), ''); @@ -1769,7 +1792,8 @@ %% CASE btnShowHideLegendPanelCallback case 'btnShowHideLegendPanelCallback' handles = guihandles(value(myfig)); - off_axpokesplot_position = [0.11 0.33333 0.55667 0.56339]; + % off_axpokesplot_position = [0.11 0.33333 0.55667 0.56339]; + off_axpokesplot_position = [0.1 0.1 0.88 0.88]; legend_position = get(handles.uipanelLegend, 'Position'); on_axpokesplot_position = off_axpokesplot_position; on_axpokesplot_position(3) = on_axpokesplot_position(3) + legend_position(3); diff --git a/ExperPort/Plugins/@saveload/SavingSection.m b/ExperPort/Plugins/@saveload/SavingSection.m index 840ebda8..380b731f 100644 --- a/ExperPort/Plugins/@saveload/SavingSection.m +++ b/ExperPort/Plugins/@saveload/SavingSection.m @@ -88,25 +88,28 @@ SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)]); SoloParamHandle(obj, 'data_file', 'value', ''); - %Sundeep Tuteja, 22nd December, 2009: Adding a SoloParamHandle called - %settings_file to store the full path to the currently loaded settings file. + %Arpit, 09th May, 2025: Getting the experimenter name and rat name + %from runrats instead of initializing it. This is required to create + %the folder to save video files. If it runss into error or runrat is + %not running then as a default it sets values as 'experimenter and + %'ratname' + try - [dummy, settings_file_str] = runrats('get_settings_file_path'); - clear('dummy'); + [~,experimenter_name, rat_name] = runrats('exp_rat_names'); + [~, settings_file_str] = runrats('get_settings_file_path'); + [~, settings_file_load_time_num] = runrats('get_settings_file_load_time'); catch %#ok settings_file_str = ''; - end - SoloParamHandle(obj, 'settings_file', 'value', settings_file_str); - try - [dummy, settings_file_load_time_num] = runrats('get_settings_file_load_time'); - clear('dummy'); - catch %#ok + experimenter_name = 'experimenter'; + rat_name = 'ratname'; settings_file_load_time_num = 0; end + SoloParamHandle(obj, 'settings_file_load_time', 'value', settings_file_load_time_num); + SoloParamHandle(obj, 'settings_file', 'value', settings_file_str); - EditParam(obj, 'experimenter', 'experimenter', x, y); next_row(y, 1.5); - EditParam(obj, 'ratname', 'ratname', x, y); next_row(y, 1.5); + EditParam(obj, 'experimenter', experimenter_name, x, y); next_row(y, 1.5); + EditParam(obj, 'ratname', rat_name, x, y); next_row(y, 1.5); PushbuttonParam(obj, 'loadsets', x, y, 'label', 'Load Settings'); set_callback(loadsets, {mfilename, 'loadsets'}); @@ -156,7 +159,7 @@ return; - + case 'set' parname = x; parval = y; switch parname @@ -173,7 +176,11 @@ warning('SAVELOAD:InvalidParam', 'Don''t know how to set "%s", not doing anything', parname); end; - + case 'get_set_filename' + x = value(settings_file); + y = []; + return; + % ------------ CASE GET_ALL_INFO -------------------- %Sundeep Tuteja, 22nd December, 2009: Adding a case to get %experimenter name, rat name, settings file loaded, if any, and data file. Case @@ -205,8 +212,13 @@ case 'set_info', % ------------ CASE SET_INFO -------------------- ratname.value=y; %#ok experimenter.value=x; %#ok + y =[]; return; + case 'set_setting_info' + settings_file.value = x; + settings_file_load_time.value = y; + case 'savesets', % ------------ CASE SAVESETS -------------------- if nargin == 3, varargin = {x}; elseif nargin == 4, varargin = {x y}; @@ -263,7 +275,8 @@ case 'get_settings_file_load_time' [dummy, x1] = runrats('get_settings_file_load_time'); clear('dummy'); x2 = value(settings_file_load_time); - x = max(x1, x2); settings_file_load_time.value = x; + x = max(double(x1), double(x2)); + settings_file_load_time.value = x; y = []; z = []; return; diff --git a/ExperPort/Plugins/@sessionmodel2/CreateHelperVar.m b/ExperPort/Plugins/@sessionmodel2/CreateHelperVar.m index 08f163ad..51a6a937 100644 --- a/ExperPort/Plugins/@sessionmodel2/CreateHelperVar.m +++ b/ExperPort/Plugins/@sessionmodel2/CreateHelperVar.m @@ -7,8 +7,8 @@ function CreateHelperVar(obj, varname, varargin) % % 'value', varval: Sets the helper var to varval if the helper var does % not already exist. If this option is not specified, varval defaults to -% the empty matrix. -% +% the empty matrix. +% % 'force_init', true: Forces the helper var to be set to the specified % value. If this option is not specified, 'force_init' is assumed to be % false. @@ -21,11 +21,27 @@ function CreateHelperVar(obj, varname, varargin) elseif ~isvarname(varname) error('varname has to be a valid MATLAB variable name'); end - +%% ARPIT %% +% Changing the name from 'value' to 'var_value' because +% there already exist a function named value and cannot have a variable +% also named the same. +varargin_names = varargin(1:2:end); +value_idx = find(cellfun(@(x) contains(x,'value'),varargin_names)); +if ~isempty(value_idx) + varargin{(value_idx - 1) * 2 + 1} = 'var_value'; +end pairs = {'force_init', false; - 'value', []}; + 'var_value', []}; parseargs(varargin, pairs); -varval = value; clear('value'); +varval = var_value; + +% OLD CODE +% pairs = {'force_init', false; +% 'value', []}; +% parseargs(varargin, pairs); +% varval = value; clear('value'); + +%% try force_init = logical(force_init); %#ok diff --git a/ExperPort/Plugins/@sessionmodel2/SessionDefinition.m b/ExperPort/Plugins/@sessionmodel2/SessionDefinition.m index 3cffdeae..fc9a49ec 100644 --- a/ExperPort/Plugins/@sessionmodel2/SessionDefinition.m +++ b/ExperPort/Plugins/@sessionmodel2/SessionDefinition.m @@ -1956,10 +1956,13 @@ return_structure = value(GLOBAL_HELPER_VAR_NAME_LIST); if ~isempty(return_structure) - for ctr = 1:length(return_structure) - assert(logical(exist(return_structure(ctr).var_name, 'var')) && isa(eval(return_structure(ctr).var_name), 'SoloParamHandle')) - return_structure(ctr).current_value = value(eval(return_structure(ctr).var_name)); - return_structure(ctr).history = get_history(eval(return_structure(ctr).var_name)); + try + for ctr = 1:length(return_structure) + assert(logical(exist(return_structure(ctr).var_name, 'var')) && isa(eval(return_structure(ctr).var_name), 'SoloParamHandle')) + return_structure(ctr).current_value = value(eval(return_structure(ctr).var_name)); + return_structure(ctr).history = get_history(eval(return_structure(ctr).var_name)); + end + catch end end varargout{1} = return_structure; @@ -2014,7 +2017,7 @@ if ~isequal(SavingSection(obj, 'get_settings_file_load_time'), value(TEMPORARY_SETTINGS_FILE_LOAD_TIME)) feval(mfilename, obj, 'init'); end - + if n_done_trials >= 1 && ~isempty(value(CURRENT_TRAINING_STAGES_FILE_NAME)) diff --git a/ExperPort/Plugins/@sqlsummary/CentrePoketrainingsummary.m b/ExperPort/Plugins/@sqlsummary/CentrePoketrainingsummary.m new file mode 100644 index 00000000..e7e330f1 --- /dev/null +++ b/ExperPort/Plugins/@sqlsummary/CentrePoketrainingsummary.m @@ -0,0 +1,395 @@ +function [err] = CentrePoketrainingsummary(obj, varargin) + %% Function Description + % Sends session data to the bdata.sessions table + % By default, it tries to get data from standard plugins + % See the pairs structure below for details + + %% Initialize Error Logging + diary('sendsummary_error_log.txt'); + + try + %% Define Default Parameters + pairs = { + 'force_send', 0;... + 'savetime', get_savetime(obj);... + 'endtime', get_endtime(obj);... + 'sessiondate', get_sessiondate(obj);... + 'hostname', get_rigid;... + 'IP_addr', get_network_info;... + 'experimenter', get_val('SavingSection_experimenter');... + 'ratname', get_val('SavingSection_ratname');... + 'n_done_trials', get_val('n_completed_trials');... + 'protocol', class(obj);... + 'protocol_data', 'NULL';... + 'peh', get_parsed_events;... + 'last_comment', cleanup(CommentsSection(obj,'get_latest'));... + 'data_file', SavingSection(obj,'get_data_file');... + 'technotes', get_val('TechComment')... % Added technician comments field + }; + parseargs(varargin, pairs); + + %% Validate Rig ID + [rigID, e, m] = bSettings('get','RIGS','Rig_ID'); + if ~force_send && isnan(rigID) + err = 42; + return + end + + %% Handle Tech Notes + if ~ischar(technotes) || isempty(technotes) + technotes = ''; + end + + %% Extract Path Information + [pth, fl] = extract_path(data_file); + + %% Calculate Violation Percentage + % if isfield(protocol_data, 'violation_rate') + % percent_violations = perf.violation_rate; + % else + % percent_violations = []; + % end + % + %% Calculate Poke Counts + left_pokes = 0; + center_pokes = 0; + right_pokes = 0; + for px = 1:numel(peh) + left_pokes = left_pokes + numel(peh(px).pokes.L); + center_pokes = center_pokes + numel(peh(px).pokes.C); + right_pokes = right_pokes + numel(peh(px).pokes.R); + end + + %% Get Session ID and Start Time + sessid = getSessID(obj); + starttime = get_starttime(sessid); % added 20091214 + + if isempty(starttime) + % Compute start time if not found in sess_started table + starttime = datestr(datenum(savetime)-sess_length(obj)/60/60/24, 13); + else + % Update sess_started table indicating session end + bdata('call set_sess_ended("{Si}", "{Si}")', sessid, 1); + end + + %% Define SQL columns and placeholders + colstr = [ + 'sessid, ',... + 'sessiondate, '... DATE + 'starttime, '... TIME + 'endtime, '... TIME + 'ratname, '... VARCHAR + 'experimenter, '... VARCHAR + 'protocol, '... VARCHAR + 'hostname, '... VARCHAR + 'IP_address, '... VARCHAR + 'training_stage_no, '... INT + 'training_stage_name, '... VARCHAR + 'n_done_trials, '... INT + 'percent_violations, '... VARCHAR + 'percent_timeout, '... VARCHAR + 'stage1_trials_total, '... INT + 'stage1_trials_today, '... INT + 'stage1_trials_valid, '... INT + 'stage1_percent_violation, '... VARCHAR + 'stage1_percent_timeout, '... VARCHAR + 'stage2_trials_total, '... INT + 'stage2_trials_today, '... INT + 'stage2_trials_valid, '... INT + 'stage2_percent_violation, '... VARCHAR + 'stage2_percent_timeout, '... VARCHAR + 'stage3_trials_total, '... INT + 'stage3_trials_today, '... INT + 'stage3_trials_valid, '... INT + 'stage3_percent_violation, '... VARCHAR + 'stage3_percent_timeout, '... VARCHAR + 'stage4_trials_total, '... INT + 'stage4_trials_today, '... INT + 'stage4_trials_valid, '... INT + 'stage4_percent_violation, '... VARCHAR + 'stage4_percent_timeout, '... VARCHAR + 'stage5_trials_total, '... INT + 'stage5_trials_today, '... INT + 'stage5_trials_valid, '... INT + 'stage5_percent_violation, '... VARCHAR + 'stage5_percent_timeout, '... VARCHAR + 'stage6_trials_total, '... INT + 'stage6_trials_today, '... INT + 'stage6_trials_valid, '... INT + 'stage6_percent_violation, '... VARCHAR + 'stage6_percent_timeout, '... VARCHAR + 'stage7_trials_total, '... INT + 'stage7_trials_today, '... INT + 'stage7_trials_valid, '... INT + 'stage7_percent_violation, '... VARCHAR + 'stage7_percent_timeout, '... VARCHAR + 'stage8_trials_total, '... INT + 'stage8_trials_today, '... INT + 'stage8_trials_valid, '... INT + 'stage8_percent_violation, '... VARCHAR + 'stage8_percent_timeout, '... VARCHAR + 'datafile, '... VARCHAR + 'datapath, '... VARCHAR + 'videopath, '... VARCHAR + 'CP_Dur_reached, '... VARCHAR + 'centre_poke, '... VARCHAR + 'left_poke, '... VARCHAR + 'right_poke, '... VARCHAR + 'comments, '... VARCHAR + 'tech_notes']; % total 63 columns + + +valstr = [ + '"{Si}",', ... % sessid + '"{S}",', ... % sessiondate + '"{S}",', ... % starttime + '"{S}",', ... % endtime + '"{S}",', ... % ratname + '"{S}",', ... % experimenter + '"{S}",', ... % protocol + '"{S}",', ... % hostname + '"{S}",', ... % IP_address + '"{S}",', ... % training_stage_no + '"{S}",', ... % training_stage_name + '"{S}",', ... % n_done_trials + '"{S}",', ... % percent_violations + '"{S}",', ... % percent_timeout + '"{S}",', ... % stage1_trials_total + '"{S}",', ... % stage1_trials_today + '"{S}",', ... % stage1_trials_valid + '"{S}",', ... % stage1_percent_violation + '"{S}",', ... % stage1_percent_timeout + '"{S}",', ... % stage2_trials_total + '"{S}",', ... % stage2_trials_today + '"{S}",', ... % stage2_trials_valid + '"{S}",', ... % stage2_percent_violation + '"{S}",', ... % stage2_percent_timeout + '"{S}",', ... % stage3_trials_total + '"{S}",', ... % stage3_trials_today + '"{S}",', ... % stage3_trials_valid + '"{S}",', ... % stage3_percent_violation + '"{S}",', ... % stage3_percent_timeout + '"{S}",', ... % stage4_trials_total + '"{S}",', ... % stage4_trials_today + '"{S}",', ... % stage4_trials_valid + '"{S}",', ... % stage4_percent_violation + '"{S}",', ... % stage4_percent_timeout + '"{S}",', ... % stage5_trials_total + '"{S}",', ... % stage5_trials_today + '"{S}",', ... % stage5_trials_valid + '"{S}",', ... % stage5_percent_violation + '"{S}",', ... % stage5_percent_timeout + '"{S}",', ... % stage6_trials_total + '"{S}",', ... % stage6_trials_today + '"{S}",', ... % stage6_trials_valid + '"{S}",', ... % stage6_percent_violation + '"{S}",', ... % stage6_percent_timeout + '"{S}",', ... % stage7_trials_total + '"{S}",', ... % stage7_trials_today + '"{S}",', ... % stage7_trials_valid + '"{S}",', ... % stage7_percent_violation + '"{S}",', ... % stage7_percent_timeout + '"{S}",', ... % stage8_trials_total + '"{S}",', ... % stage8_trials_today + '"{S}",', ... % stage8_trials_valid + '"{S}",', ... % stage8_percent_violation + '"{S}",', ... % stage8_percent_timeout + '"{S}",', ... % datafile + '"{S}",', ... % datapath + '"{S}",', ... % video_path + '"{S}",', ... % videofile + '"{S}",', ... % left_pokes + '"{S}",', ... % center_pokes + '"{S}",', ... % right_pokes + '"{S}",', ... % comments + '"{S}"', ... % technotes + ]; + + + %% Construct SQL string + sqlstr = ['insert into CentrePokeTraining (' strtrim(colstr) ') values (' strtrim(valstr) ')']; + + + %% Execute SQL Query + bdata(sqlstr, ... + sessid, ... + sessiondate, ... + starttime, ... + endtime, ... + ratname, ... + experimenter, ... + protocol, ... + hostname, ... + IP_addr, ... + protocol_data.stage_no, ... + protocol_data.stage_name, ... + n_done_trials, ... + protocol_data.violation_percent, ... + protocol_data.timeout_percent, ... + protocol_data.stage1_trials_total, ... + protocol_data.stage1_trials_today, ... + protocol_data.stage1_trials_valid, ... + protocol_data.stage1_violationrate, ... + protocol_data.stage1_timeoutrate, ... + protocol_data.stage2_trials_total, ... + protocol_data.stage2_trials_today, ... + protocol_data.stage2_trials_valid, ... + protocol_data.stage2_violationrate, ... + protocol_data.stage2_timeoutrate, ... + protocol_data.stage3_trials_total, ... + protocol_data.stage3_trials_today, ... + protocol_data.stage3_trials_valid, ... + protocol_data.stage3_violationrate, ... + protocol_data.stage3_timeoutrate, ... + protocol_data.stage4_trials_total, ... + protocol_data.stage4_trials_today, ... + protocol_data.stage4_trials_valid, ... + protocol_data.stage4_violationrate, ... + protocol_data.stage4_timeoutrate, ... + protocol_data.stage5_trials_total, ... + protocol_data.stage5_trials_today, ... + protocol_data.stage5_trials_valid, ... + protocol_data.stage5_violationrate, ... + protocol_data.stage5_timeoutrate, ... + protocol_data.stage6_trials_total, ... + protocol_data.stage6_trials_today, ... + protocol_data.stage6_trials_valid, ... + protocol_data.stage6_violationrate, ... + protocol_data.stage6_timeoutrate, ... + protocol_data.stage7_trials_total, ... + protocol_data.stage7_trials_today, ... + protocol_data.stage7_trials_valid, ... + protocol_data.stage7_violationrate, ... + protocol_data.stage7_timeoutrate, ... + protocol_data.stage8_trials_total, ... + protocol_data.stage8_trials_today, ... + protocol_data.stage8_trials_valid, ... + protocol_data.stage8_violationrate, ... + protocol_data.stage8_timeoutrate, ... + pth, ... + fl, ... + protocol_data.video_filepath, ... + protocol_data.CP_Duration, .... + left_pokes, ... + center_pokes, ... + right_pokes, ... + last_comment, ... + technotes ... + ); + + % Log successful execution + fprintf('No errors encountered during sendsummary execution.\n'); + + + catch ME + fprintf(2, 'Failed to send summary to sql\n'); + disp(ME.message); + disp(ME.stack); + err = 1; + + % Log error details + fprintf('Error occurred during sendsummary execution:\n'); + fprintf('%s\n', ME.message); + fprintf('%s\n', ME.stack); + end + + diary off; +end + +%% Helper Functions +function y = get_val(x) + y = get_sphandle('fullname', x); + if isempty(y) + y = ''; + else + y = value(y{1}); + end +end + +function y = get_parsed_events + y = get_sphandle('fullname', 'ProtocolsSection_parsed_events'); + y = cell2mat(get_history(y{1})); +end + +function y = sess_length(obj) + % Estimate session length + GetSoloFunctionArgs(obj); + + try + st = parsed_events_history{1}.states; %#ok + ss = st.starting_state; + es = st.starting_state; + eval(['ST = min(min(st.', ss, '));']); + eval(['ET = max(max(st.', es, '));']); + + D1 = round(ET - ST); + + pt = get_sphandle('name', 'prot_title'); + [Ts, Te] = get_times_from_prottitle(value(pt{1})); + Ts = [Ts, ':00']; + Te = [Te, ':00']; + + Dt = timediff(Ts, Te, 2); + y = Dt + D1; + catch ME + showerror; % Assuming showerror is a function that displays errors + fprintf(2, 'Error calculating session length\n'); + disp(ME.message); + disp(ME.stack); + end +end + +function y = cleanup(M) + try + y = strtrim(sprintf('%s', M')); + catch + y = ''; + end +end + +function [p, f] = extract_path(s) + last_fs = find(s == filesep, 1, 'last' ); + p = s(1:last_fs); + f = s(last_fs+1:end); +end + +function y = get_savetime(obj) + [x, x, y] = SavingSection(obj, 'get_info'); + if y == '_' + y = datestr(now); + end +end + +function y = get_endtime(obj) + [x, x, savetime] = SavingSection(obj, 'get_info'); + if savetime == '_' + y = datestr(now, 13); + else + y = datestr(savetime, 13); + end +end + +function y = get_starttime(sessid) + y = bdata('select starttime from sess_started where sessid="{Si}"', sessid); + if ~isempty(y) + y = y{1}; + end +end + +function y = get_sessiondate(obj) + [x, x, savetime] = SavingSection(obj, 'get_info'); + if savetime == '_' + y = datestr(now, 29); + else + y = datestr(savetime, 29); + end +end + +function y = get_rigid + y = getRigID; + if isnan(y) + y = 'Unknown'; + elseif isnumeric(y) + y = sprintf('Rig%02d', y); + end +end diff --git a/ExperPort/Plugins/@sqlsummary/SoundCatContextSwitchSummary.m b/ExperPort/Plugins/@sqlsummary/SoundCatContextSwitchSummary.m new file mode 100644 index 00000000..587d03b5 --- /dev/null +++ b/ExperPort/Plugins/@sqlsummary/SoundCatContextSwitchSummary.m @@ -0,0 +1,384 @@ +function [err] = SoundCatContextSwitchSummary(obj, varargin) + %% Function Description + % Sends session data to the bdata.sessions table + % By default, it tries to get data from standard plugins + % See the pairs structure below for details + + %% Initialize Error Logging + diary('sendsummary_error_log.txt'); + + try + %% Define Default Parameters + pairs = { + 'force_send', 0;... + 'hits', get_val('hit_history');... + 'sides', get_val('previous_sides');... + 'violations' get_val('violation_history') + 'savetime', get_savetime(obj);... + 'endtime', get_endtime(obj);... + 'sessiondate', get_sessiondate(obj);... + 'hostname', get_rigid;... + 'IP_addr', get_network_info;... + 'experimenter', get_val('SavingSection_experimenter');... + 'ratname', get_val('SavingSection_ratname');... + 'n_done_trials', get_val('n_completed_trials');... + 'protocol', class(obj);... + 'protocol_data', 'NULL';... + 'peh', get_parsed_events;... + 'last_comment', cleanup(CommentsSection(obj,'get_latest'));... + 'data_file', SavingSection(obj,'get_data_file');... + 'technotes', get_val('TechComment')... % Added technician comments field + }; + parseargs(varargin, pairs); + + %% Validate Rig ID + [rigID, e, m] = bSettings('get','RIGS','Rig_ID'); + if ~force_send && isnan(rigID) + err = 42; + return + end + + %% Handle Tech Notes + if ~ischar(technotes) || isempty(technotes) + technotes = ''; + end + + %% Calculate Performance Metrics + hits = hits(1:n_done_trials); + sides = sides(1:n_done_trials); + total_correct = nanmean(hits); + + try + right_correct = nanmean(hits(sides=='r')); + left_correct = nanmean(hits(sides=='l')); + catch ME + fprintf(2, 'Error calculating correct pokes\n'); + disp(ME.message); + disp(ME.stack); + right_correct = -1; + left_correct = -1; + end + + %% Extract Path Information + [pth, fl] = extract_path(data_file); + + %% Calculate Violation Percentage + try + percent_violations = mean(isnan(hits)); + catch + percent_violations = -1; + end + + + %% Calculate Poke Counts + left_pokes = 0; + center_pokes = 0; + right_pokes = 0; + for px = 1:numel(peh) + left_pokes = left_pokes + numel(peh(px).pokes.L); + center_pokes = center_pokes + numel(peh(px).pokes.C); + right_pokes = right_pokes + numel(peh(px).pokes.R); + end + + %% Get Session ID and Start Time + sessid = getSessID(obj); + starttime = get_starttime(sessid); % added 20091214 + + if isempty(starttime) + % Compute start time if not found in sess_started table + starttime = datestr(datenum(savetime)-sess_length(obj)/60/60/24, 13); + else + % Update sess_started table indicating session end + bdata('call set_sess_ended("{Si}", "{Si}")', sessid, 1); + end + + % psych_result{n_context}.trial_start = trial_start; + % psych_result{n_context}.trial_end = trial_end; + % psych_result{n_context}.context = unique(category_distribution); + % psych_result{n_context}.percept_boundary = fitParams(1); + % psych_result{n_context}.total_correct = -1; + % psych_result{n_context}.violations = -1; + % psych_result{n_context}.right_correct = -1; + % psych_result{n_context}.left_correct = -1; + + %% Define SQL columns and placeholders + colstr = [ + 'sessid, ',... + 'sessiondate, '... DATE + 'starttime, '... TIME + 'endtime, '... TIME + 'ratname, '... VARCHAR + 'experimenter, '... VARCHAR + 'protocol, '... VARCHAR + 'hostname, '... VARCHAR + 'IP_address, '... VARCHAR + 'training_stage_no, '... INT + 'training_stage_name, '... VARCHAR + 'n_done_trials, '... INT + 'percent_violations, '... VARCHAR + 'percent_timeout, '... VARCHAR + 'stage1_trials_total, '... INT + 'stage1_trials_today, '... INT + 'stage1_trials_valid, '... INT + 'stage1_percent_violation, '... VARCHAR + 'stage1_percent_timeout, '... VARCHAR + 'stage2_trials_total, '... INT + 'stage2_trials_today, '... INT + 'stage2_trials_valid, '... INT + 'stage2_percent_violation, '... VARCHAR + 'stage2_percent_timeout, '... VARCHAR + 'stage3_trials_total, '... INT + 'stage3_trials_today, '... INT + 'stage3_trials_valid, '... INT + 'stage3_percent_violation, '... VARCHAR + 'stage3_percent_timeout, '... VARCHAR + 'stage4_trials_total, '... INT + 'stage4_trials_today, '... INT + 'stage4_trials_valid, '... INT + 'stage4_percent_violation, '... VARCHAR + 'stage4_percent_timeout, '... VARCHAR + 'datafile, '... VARCHAR + 'datapath, '... VARCHAR + 'videopath, '... VARCHAR + 'CP_Dur_reached, '... VARCHAR + 'centre_poke, '... VARCHAR + 'left_poke, '... VARCHAR + 'right_poke, '... VARCHAR + 'comments, '... VARCHAR + 'tech_notes']; % total 63 columns + + +valstr = [ + '"{Si}",', ... % sessid + '"{S}",', ... % sessiondate + '"{S}",', ... % starttime + '"{S}",', ... % endtime + '"{S}",', ... % ratname + '"{S}",', ... % experimenter + '"{S}",', ... % protocol + '"{S}",', ... % hostname + '"{S}",', ... % IP_address + '"{S}",', ... % training_stage_no + '"{S}",', ... % training_stage_name + '"{S}",', ... % n_done_trials + '"{S}",', ... % percent_violations + '"{S}",', ... % percent_timeout + '"{S}",', ... % stage1_trials_total + '"{S}",', ... % stage1_trials_today + '"{S}",', ... % stage1_trials_valid + '"{S}",', ... % stage1_percent_violation + '"{S}",', ... % stage1_percent_timeout + '"{S}",', ... % stage2_trials_total + '"{S}",', ... % stage2_trials_today + '"{S}",', ... % stage2_trials_valid + '"{S}",', ... % stage2_percent_violation + '"{S}",', ... % stage2_percent_timeout + '"{S}",', ... % stage3_trials_total + '"{S}",', ... % stage3_trials_today + '"{S}",', ... % stage3_trials_valid + '"{S}",', ... % stage3_percent_violation + '"{S}",', ... % stage3_percent_timeout + '"{S}",', ... % stage4_trials_total + '"{S}",', ... % stage4_trials_today + '"{S}",', ... % stage4_trials_valid + '"{S}",', ... % stage4_percent_violation + '"{S}",', ... % stage4_percent_timeout + '"{S}",', ... % stage5_trials_total + '"{S}",', ... % stage5_trials_today + '"{S}",', ... % stage5_trials_valid + '"{S}",', ... % stage5_percent_violation + '"{S}",', ... % stage5_percent_timeout + '"{S}",', ... % stage6_trials_total + '"{S}",', ... % stage6_trials_today + '"{S}",', ... % stage6_trials_valid + '"{S}",', ... % stage6_percent_violation + '"{S}",', ... % stage6_percent_timeout + '"{S}",', ... % stage7_trials_total + '"{S}",', ... % stage7_trials_today + '"{S}",', ... % stage7_trials_valid + '"{S}",', ... % stage7_percent_violation + '"{S}",', ... % stage7_percent_timeout + '"{S}",', ... % stage8_trials_total + '"{S}",', ... % stage8_trials_today + '"{S}",', ... % stage8_trials_valid + '"{S}",', ... % stage8_percent_violation + '"{S}",', ... % stage8_percent_timeout + '"{S}",', ... % datafile + '"{S}",', ... % datapath + '"{S}",', ... % video_path + '"{S}",', ... % videofile + '"{S}",', ... % left_pokes + '"{S}",', ... % center_pokes + '"{S}",', ... % right_pokes + '"{S}",', ... % comments + '"{S}"', ... % technotes + ]; + + + %% Construct SQL string + sqlstr = ['insert into CentrePokeTraining (' strtrim(colstr) ') values (' strtrim(valstr) ')']; + + + %% Execute SQL Query + bdata(sqlstr, ... + sessid, ... + sessiondate, ... + starttime, ... + endtime, ... + ratname, ... + experimenter, ... + protocol, ... + hostname, ... + IP_addr, ... + protocol_data.stage_no, ... + protocol_data.stage_name, ... + n_done_trials, ... + protocol_data.violation_percent, ... + protocol_data.timeout_percent, ... + protocol_data.stage1_trials_total, ... + protocol_data.stage1_trials_today, ... + protocol_data.stage1_trials_valid, ... + protocol_data.stage1_violationrate, ... + protocol_data.stage1_timeoutrate, ... + protocol_data.stage2_trials_total, ... + protocol_data.stage2_trials_today, ... + protocol_data.stage2_trials_valid, ... + protocol_data.stage2_violationrate, ... + protocol_data.stage2_timeoutrate, ... + protocol_data.stage3_trials_total, ... + protocol_data.stage3_trials_today, ... + protocol_data.stage3_trials_valid, ... + protocol_data.stage3_violationrate, ... + protocol_data.stage3_timeoutrate, ... + protocol_data.stage4_trials_total, ... + protocol_data.stage4_trials_today, ... + protocol_data.stage4_trials_valid, ... + protocol_data.stage4_violationrate, ... + protocol_data.stage4_timeoutrate, ... + pth, ... + fl, ... + protocol_data.video_filepath, ... + protocol_data.CP_Duration, .... + left_pokes, ... + center_pokes, ... + right_pokes, ... + last_comment, ... + technotes ... + ); + + % Log successful execution + fprintf('No errors encountered during sendsummary execution.\n'); + + + catch ME + fprintf(2, 'Failed to send summary to sql\n'); + disp(ME.message); + disp(ME.stack); + err = 1; + + % Log error details + fprintf('Error occurred during sendsummary execution:\n'); + fprintf('%s\n', ME.message); + fprintf('%s\n', ME.stack); + end + + diary off; +end + +%% Helper Functions +function y = get_val(x) + y = get_sphandle('fullname', x); + if isempty(y) + y = ''; + else + y = value(y{1}); + end +end + +function y = get_parsed_events + y = get_sphandle('fullname', 'ProtocolsSection_parsed_events'); + y = cell2mat(get_history(y{1})); +end + +function y = sess_length(obj) + % Estimate session length + GetSoloFunctionArgs(obj); + + try + st = parsed_events_history{1}.states; %#ok + ss = st.starting_state; + es = st.starting_state; + eval(['ST = min(min(st.', ss, '));']); + eval(['ET = max(max(st.', es, '));']); + + D1 = round(ET - ST); + + pt = get_sphandle('name', 'prot_title'); + [Ts, Te] = get_times_from_prottitle(value(pt{1})); + Ts = [Ts, ':00']; + Te = [Te, ':00']; + + Dt = timediff(Ts, Te, 2); + y = Dt + D1; + catch ME + showerror; % Assuming showerror is a function that displays errors + fprintf(2, 'Error calculating session length\n'); + disp(ME.message); + disp(ME.stack); + end +end + +function y = cleanup(M) + try + y = strtrim(sprintf('%s', M')); + catch + y = ''; + end +end + +function [p, f] = extract_path(s) + last_fs = find(s == filesep, 1, 'last' ); + p = s(1:last_fs); + f = s(last_fs+1:end); +end + +function y = get_savetime(obj) + [x, x, y] = SavingSection(obj, 'get_info'); + if y == '_' + y = datestr(now); + end +end + +function y = get_endtime(obj) + [x, x, savetime] = SavingSection(obj, 'get_info'); + if savetime == '_' + y = datestr(now, 13); + else + y = datestr(savetime, 13); + end +end + +function y = get_starttime(sessid) + y = bdata('select starttime from sess_started where sessid="{Si}"', sessid); + if ~isempty(y) + y = y{1}; + end +end + +function y = get_sessiondate(obj) + [x, x, savetime] = SavingSection(obj, 'get_info'); + if savetime == '_' + y = datestr(now, 29); + else + y = datestr(savetime, 29); + end +end + +function y = get_rigid + y = getRigID; + if isnan(y) + y = 'Unknown'; + elseif isnumeric(y) + y = sprintf('Rig%02d', y); + end +end diff --git a/ExperPort/Settings/_Settings_Custom.conf b/ExperPort/Settings/_Settings_Custom.conf index 6b80fd87..d4105e3b 100644 --- a/ExperPort/Settings/_Settings_Custom.conf +++ b/ExperPort/Settings/_Settings_Custom.conf @@ -11,7 +11,7 @@ GENERAL; Protocols_Directory; C:\ratter\Protocols ; % Directory for experime % Rig Settings %============================================================================== RIGS; fake_rp_box; 30; % 30 = Bpod state machine -RIGS; Rig_ID; 10; % ID of the rig box (NaN means no specific rig assigned) +RIGS; Rig_ID; NaN; % ID of the rig box (NaN means no specific rig assigned) RIGS; state_machine_server; localhost; % Server address for state machine control RIGS; sound_machine_server; localhost; % Server address for sound machine control RIGS; server_slot; 0; % Slot for server connection diff --git a/Protocols/@AltSoundCategorizationCatch/SoundCatSMA-old.m b/Protocols/@AltSoundCategorizationCatch/SoundCatSMA-old.m new file mode 100644 index 00000000..c96f9a77 --- /dev/null +++ b/Protocols/@AltSoundCategorizationCatch/SoundCatSMA-old.m @@ -0,0 +1,406 @@ + +function [varargout] = SoundCatSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + srate=SoundManagerSection(obj,'get_sample_rate'); + freq1=5; + dur1=1.5*1000; + Vol=1; + tw=Vol*(MakeBupperSwoop(srate,0, freq1 , freq1 , dur1/2 , dur1/2,0,0.1)); + SoundManagerSection(obj, 'declare_new_sound', 'LRewardSound', [tw ; zeros(1, length(tw))]) + SoundManagerSection(obj, 'declare_new_sound', 'RRewardSound', [zeros(1, length(tw));tw]) + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + case 'prepare_next_trial', + + %% Setup water + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + RLreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RewardSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + Lreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'LRewardSound'); + Rreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RRewardSound'); + + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); + + %% Declare variables + % These will get moved to other functions as SoloParamHandles. + + WaterAmount=maxasymp + (minasymp./(1+(n_done_trials/inflp).^slp).^assym); +% WaterValvesSection(obj, 'set_water_amounts', WaterAmount, WaterAmount); +% [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + WValveTimes = GetValveTimes(WaterAmount, [2 3]); + LeftWValveTime = WValveTimes(1); + RightWValveTime = WValveTimes(2); + [LeftWMult RightWMult] = SideSection(obj, 'get_water_mult'); + LeftWValveTime=LeftWValveTime*LeftWMult; + RightWValveTime=RightWValveTime*RightWMult; + + side = SideSection(obj, 'get_current_side'); + if side == 'l' + HitEvent = 'Lin'; ErrorEvent = 'Rin'; + HitState = 'lefthit'; SideLight = left1led; + SecondHitState = 'secondlefthit'; + + else + HitEvent = 'Rin'; ErrorEvent = 'Lin'; + HitState = 'righthit'; SideLight = right1led; + SecondHitState = 'secondrighthit'; + end; + + + if strcmp(reward_type, 'Always') + LEDOn=1; + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + else + LEDOn=0; + if strcmp(reward_type, 'NoReward') + AnyReward=0; + wait_for_second_hit=error_iti; + else + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + end + end; + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); + + + % to modify it for widefield imagine + if value(imaging)==1 && ~isnan(bSettings('get', 'DIOLINES', 'scope')); + trigscope = bSettings('get', 'DIOLINES', 'scope'); + else + trigscope = nan; + end + + if value(imaging)==1 + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', ... + 0.5, 'DOut', trigscope, 'loop', 0); + else + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', 0); %dummy wave. + end + + % ---BEGIN: for training stage 0 only--- + if side=='l', + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', LeftWValveTime, 'DOut', left1water); + reward_sound_id=Lreward_sound_id; + else + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', RightWValveTime, 'DOut', right1water); + reward_sound_id=Rreward_sound_id; + end; + % ---END: for training stage 0 only--- + + sma = add_scheduled_wave(sma, 'name', 'stimA1', 'preamble', PreStim_time, ... + 'sustain', A1_time, 'sound_trig', A1_sound_id); + + + + switch value(training_stage) + + case 0 %% learning the reward sound association -left or right led on -> poke -> sound+reward + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + + case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly -stimuli can be present + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + if stimuli_on ==0 || n_done_trials <1 + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + else + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time+0.00001, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period +stimA1'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + end + + + % nose is out and we're in "SettlingIn_time": + % if settling_legal_cbreak time elapses, go to violation state, + % if nose is put back in, go to copy of cp start + % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks + sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'settling_period_In', 'cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'settling_period_In','cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks + sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state+1', ... + 'Clo', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % nose is out and we're still in legal_cbreak: + % if legal_cbreak time elapses, go to violation_state, + % if nose is put back in, go to copy of cp start + sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', 30000, ... + 'output_actions', {'DOut', LEDOn*SideLight}, ... + 'input_to_statechange',{HitEvent, HitState, ErrorEvent, 'second_hit_state'}); + + % The two states that make a LeftHit: + %with reward sound + + sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight', 'SoundOut', Lreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound +% sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight'}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', SideLight+left1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a RightHit: + + %with reward sound + sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight', 'SoundOut', Rreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound +% sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight'}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', SideLight+right1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + + sma = add_state(sma,'name','second_hit_state','self_timer', wait_for_second_hit,... + 'output_actions',{'DOut', LEDOn*SideLight},... + 'input_to_statechange',{HitEvent, SecondHitState,'Tup','check_next_trial_ready'}); + + + % The two states that make a SecondLeftHit: + sma = add_state(sma,'name', 'secondlefthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Lreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+left1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a SecondRightHit: + sma = add_state(sma,'name', 'secondrighthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Rreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+right1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % and a common hit_state that we flick through + sma = add_state(sma, 'name', 'hit_state', 'self_timer', 0.0001, ... + 'input_to_statechange', {'Tup', 'drink_state'}); + + + end %end of swith for different training_stages + + +% sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... +% 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + if stimuli_on ==0 + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + else + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke-stimA1', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + end + + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','timeout_state','self_timer', timeout_duration,... + 'output_actions',{'SoundOut',to_sound_id},... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'output_actions',{ 'SchedWaveTrig','+TrigScope'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'lefthit', 'righthit', 'hit_state','second_hit_state', 'error_state', 'violation_state','timeout_state'}; + + sma = StimulatorSection(obj,'prepare_next_trial',sma); + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors', + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'righthit', [0.3 0.9 0], ... + 'lefthit', [0 0.9 0.3], ... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + + + case 'reinit', + currfig = gcf; + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end \ No newline at end of file diff --git a/Protocols/@AltSoundCategorizationCatch/SoundCatSMA.m b/Protocols/@AltSoundCategorizationCatch/SoundCatSMA.m index 2a5b94ab..46449b14 100644 --- a/Protocols/@AltSoundCategorizationCatch/SoundCatSMA.m +++ b/Protocols/@AltSoundCategorizationCatch/SoundCatSMA.m @@ -88,7 +88,8 @@ end end; - +% sma = StateMachineAssembler('full_trial_structure','use_happenings',1, ... +% 'n_input_lines',4,'line_names','CLRA'); sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... @@ -107,6 +108,7 @@ if value(imaging)==1 sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', ... 0.5, 'DOut', trigscope, 'loop', 0); + %99999, 'DOut', trigscope, 'loop', 0); %for Miniscope Camera else sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', 0); %dummy wave. end @@ -151,7 +153,7 @@ case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly -stimuli can be present sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... - 'output_actions', {'DOut', center1led}, ... + 'output_actions', {'DOut', center1led, 'SchedWaveTrig','+TrigScope'}, ... 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); if stimuli_on ==0 || n_done_trials <1 @@ -342,8 +344,8 @@ sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... - 'output_actions',{ 'SchedWaveTrig','+TrigScope'},... - 'input_to_statechange',{'Tup','check_next_trial_ready'}); + 'output_actions',{'SchedWaveTrig','-TrigScope'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); varargout{2} = {'check_next_trial_ready'}; @@ -387,7 +389,7 @@ case 'reinit', - currfig = double(gcf); + currfig = gcf; % Delete all SoloParamHandles who belong to this object and whose % fullname starts with the name of this mfile: diff --git a/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTraining.m b/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTraining.m new file mode 100644 index 00000000..527bcd27 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTraining.m @@ -0,0 +1,357 @@ +% ArpitCentrePokeTraining protocol +% Arpit, 12 March 2025 + +function [obj] = ArpitCentrePokeTraining(varargin) + +% Default object is of our own class (mfilename); +% we inherit only from Plugins + +obj = class(struct, mfilename, pokesplot2, saveload, sessionmodel2, soundmanager, soundui,antibias, ... + water, distribui,comments, soundtable, sqlsummary, bonsaicamera); + +%--------------------------------------------------------------- +% BEGIN SECTION COMMON TO ALL PROTOCOLS, DO NOT MODIFY +%--------------------------------------------------------------- + +% If creating an empty object, return without further ado: +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')) + return; +end + +if isa(varargin{1}, mfilename) % If first arg is an object of this class itself, we are + % Most likely responding to a callback from a SoloParamHandle defined in this mfile. + if length(varargin) < 2 || ~ischar(varargin{2}) + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else + action = varargin{2}; varargin = varargin(3:end); %#ok + end +else % Ok, regular call with first param being the action string. + action = varargin{1}; varargin = varargin(2:end); %#ok +end + +GetSoloFunctionArgs(obj); + + +%--------------------------------------------------------------- +% END OF SECTION COMMON TO ALL PROTOCOLS, MODIFY AFTER THIS LINE +%--------------------------------------------------------------- + +% ---- From here on is where you can put the code you like. +% +% Your protocol will be called, at the appropriate times, with the +% following possible actions: +% +% 'init' To initialize -- make figure windows, variables, etc. +% +% 'update' Called periodically within a trial +% +% 'prepare_next_trial' Called when a trial has ended and your protocol is expected +% to produce the StateMachine diagram for the next trial; +% i.e., somewhere in your protocol's response to this call, it +% should call "dispatcher('send_assembler', sma, +% prepare_next_trial_set);" where sma is the +% StateMachineAssembler object that you have prepared and +% prepare_next_trial_set is either a single string or a cell +% with elements that are all strings. These strings should +% correspond to names of states in sma. +% Note that after the prepare_next_trial call, further +% events may still occur while your protocol is thinking, +% before the new StateMachine diagram gets sent. These events +% will be available to you when 'state0' is called on your +% protocol (see below). +% +% 'trial_completed' Called when the any of the prepare_next_trial set +% of states is reached. +% +% 'close' Called when the protocol is to be closed. +% +% +% VARIABLES THAT DISPATCHER WILL ALWAYS INSTANTIATE FOR YOU AS READ_ONLY +% GLOBALS IN YOUR PROTOCOL: +% +% n_done_trials How many trials have been finished; when a trial reaches +% one of the prepare_next_trial states for the first +% time, this variable is incremented by 1. +% +% n_started_trials How many trials have been started. This variable gets +% incremented by 1 every time the state machine goes +% through state 0. +% +% parsed_events The result of running disassemble.m, with the +% parsed_structure flag set to 1, on all events from the +% start of the current trial to now. +% +% latest_parsed_events The result of running disassemble.m, with the +% parsed_structure flag set to 1, on all new events from +% the last time 'update' was called to now. +% +% raw_events All the events obtained in the current trial, not parsed +% or disassembled, but raw as gotten from the State +% Machine object. +% +% current_assembler The StateMachineAssembler object that was used to +% generate the State Machine diagram in effect in the +% current trial. +% +% Trial-by-trial history of parsed_events, raw_events, and +% current_assembler, are automatically stored for you in your protocol by +% dispatcher.m. + +switch action + + %% init + case 'init' + + hackvar = 10; SoloFunctionAddVars('SessionModel', 'ro_args', 'hackvar'); %#ok + SoloParamHandle(obj, 'myfig', 'saveable', 0); myfig.value = figure; + + % Make the title of the figure be the protocol name, and if someone tries + % to close this figure, call dispatcher's close_protocol function, so it'll know + % to take it off the list of open protocols. + name = mfilename; + set(value(myfig), 'Name', name, 'Tag', name, ... + 'closerequestfcn', 'dispatcher(''close_protocol'')', 'MenuBar', 'none'); + % At this point we have one SoloParamHandle, myfig + % Let's put the figure where we want it and give it a reasonable size: + set(value(myfig), 'Position', [485 144 850 680]); + + SoloParamHandle(obj, 'violation_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'violation_history'}); + SoloFunctionAddVars('ParamsSection', 'rw_args', 'violation_history'); + + SoloParamHandle(obj, 'timeout_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'timeout_history'}); + SoloFunctionAddVars('ParamsSection', 'rw_args', 'timeout_history'); + + SoloParamHandle(obj, 'stimulus_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'stimulus_history'}); + SoloFunctionAddVars('StimulusSection', 'rw_args', 'stimulus_history'); + + SoloParamHandle(obj, 'hit_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'hit_history'}); + SoloFunctionAddVars('ParamsSection', 'rw_args', 'hit_history'); + + SoundManagerSection(obj, 'init'); + x = 5; y = 5; % Initial position on main GUI window + [x, y] = SavingSection(obj, 'init', x, y); + + %% slow ramp up of water amount + %%the water volume is controlled by a 5-parameter logistic function: WaterAmount(t) = maxasymp + (minasymp/(1+(t/inflp)^slp).^assym) + NumeditParam(obj, 'maxasymp', 38, x,y,'label','maxasymp','TooltipString',... + 'the water volume is controlled by a 5-parameter logistic function: WaterAmount(trialnum) = maxasymp + (minasymp/(1+(trialnum/inflp)^slp).^assym)'); + next_row(y); + NumeditParam(obj, 'slp', 3, x,y,'label','slp','TooltipString','Water Modulation: Slope of the logistic function'); + next_row(y); + NumeditParam(obj, 'inflp', 350, x,y,'label','inflp','TooltipString','Water Modulation: concentration at the inflection point'); + next_row(y); + NumeditParam(obj, 'minasymp', -21, x,y,'label','inflp','TooltipString','Water Modulation: minimum asymptote'); + next_row(y); + NumeditParam(obj, 'assym', 0.7, x,y,'label','assym','TooltipString','Water Modulation: asymmetry factor'); + next_row(y); + DispParam(obj, 'trial_1', 0, x, y, 'TooltipString', 'uL on first trial'); + next_row(y); + DispParam(obj, 'trial_150', 0, x, y, 'TooltipString', 'uL on trial 150'); + next_row(y); + DispParam(obj, 'trial_300', 0, x, y, 'TooltipString', 'uL on trial 300'); + next_row(y); + set_callback({maxasymp;slp;inflp;minasymp;assym}, {mfilename, 'change_water_modulation_params'}); + feval(mfilename, obj, 'change_water_modulation_params'); + + SoloFunctionAddVars('ParamsSection', 'ro_args', ... + {'maxasymp';'slp';'inflp';'minasymp';'assym'}); + + figpos = get(double(gcf), 'Position'); + [expmtr, rname]=SavingSection(obj, 'get_info'); + HeaderParam(obj, 'prot_title', [mfilename ': ' expmtr ', ' rname], x, y, 'position', [10 figpos(4)-25, 800 20]); + + [x, y] = WaterValvesSection(obj, 'init', x, y); next_row(y); + [x, y] = PokesPlotSection(obj, 'init', x, y); + next_row(y); + [x, y] = CommentsSection(obj, 'init', x, y); + next_row(y); + oldx=x; oldy=y; + + next_column(x); y=5; + + [x, y] = ParamsSection(obj, 'init', x, y); %#ok + [x, y] = SoundSection(obj,'init',x,y); + [x, y] = StimulusSection(obj,'init',x,y); + + next_row(y);next_row(y); + + [x, y] = PerformanceSummarySection(obj, 'init', x, y);next_row(y); + [x, y] = SessionPerformanceSection(obj, 'init', x, y); + + next_row(y);next_row(y); + + [x, y] = BonsaiCameraInterface(obj,'init',x,y,name,expmtr,rname); + + % Before the TrainingStageParamsSection, let's first check if + % the setting file exist for this rat and only load the training stage + % from there. + % Problem: Loading settings in later stages fails because the stage-dependent + % TrainingStageParamsSection's solohandles are not visible after initial setup. + % Solution: This section handles a specific scenario for loading setting files during initialization. + % Although also invoked in Runrats, calling it here is crucial. The + % TrainingStageParamsSection's parameters are initially set at stage 1 and are visible. + % However, they become stage-dependent. Subsequent stages lack visible solohandles for + % these parameters, preventing proper loading. This initialization provides a workaround + % to avoid significant changes required to load stage-specific solohandles and maintain + % broader compatibility. + + set_training_stage_last_setting_file(name,expmtr,rname) + + next_column(x); y=5; + [stage_fig_x,stage_fig_y] = TrainingStageParamsSection(obj, 'init', x, y); + SoloParamHandle(obj, 'stage_fig_x', 'value', stage_fig_x); + SoloFunctionAddVars('ParamsSection', 'rw_args', 'stage_fig_x'); + SoloParamHandle(obj, 'stage_fig_y', 'value', stage_fig_y); + SoloFunctionAddVars('ParamsSection', 'rw_args', 'stage_fig_y'); + + ArpitCentrePokeTrainingSMA(obj, 'init'); + + + x=oldx; y=oldy; + SessionDefinition(obj, 'init', x, y, value(myfig)); %#ok + + %%% + +% Problem: Loading settings in later stages fails because the stage-dependent +% TrainingStageParamsSection's solohandles are not visible after initial setup. +% Solution: This section handles a specific scenario for loading setting files during initialization. +% Although also invoked in Runrats, calling it here is crucial. The +% TrainingStageParamsSection's parameters are initially set at stage 1 and are visible. +% However, they become stage-dependent. Subsequent stages lack visible solohandles for +% these parameters, preventing proper loading. This initialization provides a workaround +% to avoid significant changes required to load stage-specific solohandles and maintain +% broader compatibility. + +% try +% [~, ~]=load_solouiparamvalues(rname,'experimenter',expmtr,... +% 'owner',name,'interactive',0); +% catch +% end + %% + % feval(mfilename, obj, 'prepare_next_trial'); % Commented out because it is also run by Runrats(while loading the protocol) + + %%% + + + %% change_water_modulation_params + case 'change_water_modulation_params' + display_guys = [1 150 300]; + for i=1:numel(display_guys) + t = display_guys(i); + + myvar = eval(sprintf('trial_%d', t)); + myvar.value = maxasymp + (minasymp/(1+(t/inflp)^slp).^assym); + end + + %% when user presses run on runrats then then is called + case 'start_recording' + + BonsaiCameraInterface(obj,'record_start'); + + %% prepare next trial + case 'prepare_next_trial' + + ParamsSection(obj, 'prepare_next_trial'); + + % push_helper_vars_tosql(obj,n_done_trials); + + SessionDefinition(obj, 'next_trial'); + + StimulusSection(obj,'prepare_next_trial'); + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + [~, ~] = ArpitCentrePokeTrainingSMA(obj, 'prepare_next_trial'); + + % Default behavior of following call is that every 20 trials, the data + % gets saved, not interactive, no commit to CVS. + SavingSection(obj, 'autosave_data'); + + CommentsSection(obj, 'clear_history'); % Make sure we're not storing unnecessary history + if n_done_trials==1 % Auto-append date for convenience. + CommentsSection(obj, 'append_date'); CommentsSection(obj, 'append_line', ''); + end + + %% trial_completed + case 'trial_completed' + + % Change the video trial + BonsaiCameraInterface(obj,'next_trial'); + + % Update the Metrics Calculated, Instead being Calculated in SessionDefinition and commented out + + % PerformanceSummarySection(obj,'evaluate'); + % SessionPerformanceSection(obj, 'evaluate'); + + % Do any updates in the protocol that need doing: + feval(mfilename, 'update'); + + %% update + case 'update' + % PokesPlotSection(obj, 'update'); + if n_done_trials==1 + [expmtr, rname]=SavingSection(obj, 'get_info'); + prot_title.value=[mfilename ' on rig ' get_hostname ' : ' expmtr ', ' rname '. Started at ' datestr(now, 'HH:MM')]; + end + + %% close + case 'close' + PokesPlotSection(obj, 'close'); + ParamsSection(obj, 'close'); + StimulusSection(obj,'close'); + BonsaiCameraInterface(obj,'close'); + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)) %#ok + delete(value(myfig)); + end + delete_sphandle('owner', ['^@' class(obj) '$']); + + + %% end_session + case 'end_session' + prot_title.value = [value(prot_title) ', Ended at ' datestr(now, 'HH:MM')]; + BonsaiCameraInterface(obj,'stop') % Stopping the cameras + + %% pre_saving_settings + case 'pre_saving_settings' + + StimulusSection(obj,'hide'); + SessionDefinition(obj, 'run_eod_logic_without_saving'); + + % Sending protocol data as structure is causing error in saving bdata, + % instead not sending it asif required can be taken from session data + + % sendsummary(obj); + + % Sending Summary Statistics to SQL Database + + perf = struct([]); + perf = PerformanceSummarySection(obj, 'evaluate'); + [perf.violation_percent,perf.timeout_percent] = SessionPerformanceSection(obj, 'evaluate'); + + [perf.stage_no,perf.CP_Duration] = ParamsSection(obj,'get_stage'); + stage_name_list = {'Familiarize with Reward Side Pokes','Timeout Rewarded Side Pokes'... + 'Introduce Centre Poke','Introduce Violation for Centre Poke',... + 'Introduce Stimuli Sound during Centre Poke','Vary Stimuli location during Centre Poke'... + 'Variable Stimuli Go Cue location during Centre Poke','User Setting'}; + perf.stage_name = stage_name_list{perf.stage_no}; + + perf.video_filepath = BonsaiCameraInterface(obj,'video_filepath'); + + CentrePoketrainingsummary(obj,'protocol_data',perf); + +% CommentsSection(obj, 'append_line', ... +% sprintf(['ntrials = %d, violations = %.2f, timeouts=%.2f, hits = %.2f\n', ... +% 'pre-Go cue went from %.3f to %.3f (delta=%.3f)\n', ... +% 'Low = %.2f, High = %.2f'], ... +% perf(1), perf(2), perf(3), perf(6), cp_durs(1), cp_durs(end), cp_durs(end)-cp_durs(1), classperf(1),classperf(2))); + + %% otherwise + otherwise + warning('Unknown action! "%s"\n', action); +end + +return; + diff --git a/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTrainingSMA.m b/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTrainingSMA.m new file mode 100644 index 00000000..bcd9f8b8 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTrainingSMA.m @@ -0,0 +1,444 @@ +function [varargout] = ArpitCentrePokeTrainingSMA(obj, action) + +%%%%%%%%%%%%%%%%%%% TRAINING STAGES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Stage 1: Light Chasing to introduce the Reward Location +% The rat learns to associate the side pokes as the reward location. The +% light is always ON on the chosen side until the rat pokes and gets a reward immediately by playing +% reward sound and water delivery. On wrong side play punishment sound. + +%% Stage 2: Light Chasing continues, introducing Timeout Punishment +% The rat still gets reward on the side pokes but there is a timeout with sound and the +% rat is punished by additional time. The training continues as previous +% stage. + +%% Stage 3: Introduce Centre Nose Poke and play Reward Sound as Go Cue until settling in time +% Centre Poke LED on starting with 1 ms and increment by 1 percent until it reaches the max cp time. (should take around 700-800 trials). Take into +% account when rat removes poke before go Cue and dynamically change the cp_time if rats is failing many times. Timeout is now when the rat doesn't +% centre poke within the set time. Side lights still directs towards reward location. This stage is basically the settling_In stage +% where the time period is the entire length of the cp_time. This is further subdivided based upon the centre poke time. In sub-stage +% 1 until settling in time, there is no violation as the rat is fidgety intitially when starting to poke. + +%% Stage 4: Continue Centre Poke Training with increasing CP Time, and introduce violation +% Once the rat reliably holds centre poke for settling time, introduce +% violation (around after 200 ms) whereby if rat pokes out before the cp_time then its a violation. If many +% violations then continue training here till the performance in violation trial is below 20 percent for set trials. +% This stage continue till rat learns to poke for atleast CP = 1sec. + +%% Stage 5: Continue Centre Poke Training with violation and increasing CP Time, and introduce a fixed Sound between cp start and Go Cue +% In substage 3, which is CP > 1 sec, play a fixed sound between the poke start and Go Cue with the goal of conditioning the rat +% to expect a sound in between a Go Cue and poke start. This could help in later training stages by reducing Violations. +% In this stage the pre stim and stim time is fixed and only the delay between stim end and go cue increases. + +%% Stage 6: Continue Centre Poke Training with variable pre-stim time +% After reaching the max CP, vary the pre stim value and fixing the total CP. + +%% Stage 7: Continue Centre Poke Training with variable pre-stim time and variable delay time (both within a range of [0.2 - 2 secs]). +% This variable time is introduced so that rat doesn't learn the +% time between each of the parameters + +%% Stage 8: Same as previous Stage but now using user defined values. + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + + case 'prepare_next_trial' + + %% Setup water + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); % fixed sound for 1 side + stwo_sound_id = SoundManagerSection(obj, 'get_sound_id', 'STwoSound'); % fixed sound for other side + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); % distribution based sound + sound_duration = value(A1_time); % SoundManagerSection(obj, 'get_sound_duration', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = value(time_go_cue); % SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + + [LeftWValveTime,RightWValveTime] = ParamsSection(obj, 'get_water_amount'); + side = ParamsSection(obj, 'get_current_side'); + + if side == 'l' + HitEvent = 'Lin'; + ErrorEvent = 'Rin'; + sound_id = sone_sound_id; + SideLight = left1led; + WValveTime = LeftWValveTime; + WValveSide = left1water; + else + HitEvent = 'Rin'; + ErrorEvent = 'Lin'; + sound_id = stwo_sound_id; + SideLight = right1led; + WValveTime = RightWValveTime; + WValveSide = right1water; + end + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + % scheduled wave for stimuli/fixed sound, based upon side + if stimuli_on + sma = add_scheduled_wave(sma, 'name', 'stimplay', 'preamble', PreStim_time, ... + 'sustain', sound_duration, 'sound_trig', A1_sound_id); % to play a sound before Go Cue + else + sma = add_scheduled_wave(sma, 'name', 'stimplay', 'preamble', PreStim_time, ... + 'sustain', sound_duration, 'sound_trig', sound_id); % to play a sound before Go Cue + end + % Scheduled wave for CP Duration + if CP_duration <= (SettlingIn_time + legal_cbreak) + sma = add_scheduled_wave(sma, 'name', 'CP_Duration_wave', 'preamble', CP_duration); % total length of centre poke to consider success + else + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); % intial fidgety period without violation + sma = add_scheduled_wave(sma, 'name', 'CP_Duration_wave', 'preamble', CP_duration - SettlingIn_time); % total length of centre poke minus the inital fidgety time to consider success + end + + if value(Go_Sound) == 1 + sma = add_scheduled_wave(sma, 'name', 'Go_Cue', 'preamble', 0.001, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); % to play the Go Cue/Reward Sound + else + sma = add_scheduled_wave(sma, 'name', 'Go_Cue', 'preamble', 0.001, ... + 'sustain', go_cue_duration); % to play the Go Cue/Reward Sound + end + + % scheduled wave for rewarded side either of the side + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', WValveTime, 'DOut', WValveSide); % water delivery side + sma = add_scheduled_wave(sma, 'name', 'reward_collection_dur', 'preamble', SideLed_duration + RewardCollection_duration); % time to collect the reward + + switch value(training_stage) + + % For Training Stage 1 + + case 1 % LEARNING THE REWARD SOUND ASSOCIATION -LEFT OR RIGHT LED ON -> POKE -> SOUND+REWARD GIVEN + % INFINITE TIME AND CHANCES TO SELF CORRECT + + sma = add_state(sma, 'name', 'side_led_wait_RewardCollection', 'self_timer', SideLed_duration + RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state';'Tup','side_led_wait_RewardCollection'; ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state'; HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'SchedWaveTrig','reward_delivery+Go_Cue'},... + 'input_to_statechange',{'Tup','drink_state'}); + + sma = add_state(sma,'name','drink_state','self_timer',drink_time,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma, 'name', 'timeout_state'); + sma = add_state(sma, 'name', 'violation_state'); + + % For Training Stage 2 + + case 2 %% STILL LEARNING THE REWARD SOUND ASSOCIATION -LEFT OR RIGHT LED ON -> POKE -> SOUND+REWARD BUT + % GIVEN LIMITED TIME TO SELF CORRECT OTHERWISE ITS A TIMEOUT AND A TIMEOUT SOUND IS PLAYED + + sma = add_state(sma, 'name', 'side_led_wait_RewardCollection', 'self_timer', SideLed_duration + RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight; 'SchedWaveTrig', 'reward_collection_dur'}, ... + 'input_to_statechange',{HitEvent,'hit_state';'Tup','timeout_state'; ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'reward_collection_dur_In', 'timeout_state'; 'Tup','timeout_state'; HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'SchedWaveTrig','reward_delivery+Go_Cue'},... + 'input_to_statechange',{'Tup','drink_state'}); + + sma = add_state(sma,'name','drink_state','self_timer',drink_time,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + % For Timeout + + sma = add_state(sma,'name','timeout_state','self_timer',timeout_snd_duration,... + 'output_actions', {'SoundOut',to_sound_id; 'SchedWaveTrig', '-Go_Cue'},... + 'input_to_statechange',{'Tup','current_state+1'}); + sma = add_state(sma, 'self_timer', max(0.001, timeout_iti-timeout_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma, 'name', 'violation_state'); + + % Training Stage 3 + + case 3 % INTRODUCE CENTRE NOSE POKE AND PLAY REWARD SOUND AS GO CUE ONLY + % UNTIL CP_DUR <= SETTLING IN TIME (NO VIOLATIONS) + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',cp_timeout, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','settling_in_state'; 'Tup','timeout_state'}); + + sma = add_state(sma,'name','settling_in_state','self_timer',CP_duration, ... + 'output_actions', {'SchedWaveTrig', 'CP_Duration_wave'}, ... + 'input_to_statechange', {'Cout','current_state + 1';'Tup','side_led_wait_RewardCollection'}); + + % Intermediate State + + % This intermediate state is considering the poke is out before the end of settling time / at the start of this state + sma = add_state(sma,'self_timer',CP_duration,'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection';'Cin','current_state + 1';'Tup','side_led_wait_RewardCollection'}); + + % The state jump to here when the nose is still in then go + % directly to give reward + sma = add_state(sma,'self_timer',CP_duration,... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection'; 'Cout','current_state - 1';'Tup','side_led_wait_RewardCollection'}); + + sma = add_state(sma, 'name', 'side_led_wait_RewardCollection', 'self_timer', SideLed_duration + RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight; 'SchedWaveTrig', 'reward_collection_dur+Go_Cue'}, ... + 'input_to_statechange',{HitEvent,'hit_state';'Tup','timeout_state'; ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'reward_collection_dur_In', 'timeout_state'; 'Tup','timeout_state'; HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'SchedWaveTrig','reward_delivery'},... + 'input_to_statechange',{'Tup','drink_state'}); + + sma = add_state(sma,'name','drink_state','self_timer',drink_time,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + % For Timeout + + sma = add_state(sma,'name','timeout_state','self_timer',timeout_snd_duration,... + 'output_actions', {'SoundOut',to_sound_id; 'SchedWaveTrig', '-Go_Cue'},... + 'input_to_statechange',{'Tup','current_state+1'}); + sma = add_state(sma, 'self_timer', max(0.001, timeout_iti-timeout_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + + sma = add_state(sma, 'name', 'violation_state'); + + case {4,5,6,7,8} % STAGE 4 - LEARN TO NOSE POKE BEYOND SETTLING TIME WITH THE INTRODUCTION OF VIOLATION, THIS IS UNTIL CP = 1 SEC + % STAGE 5 ONWARDS - THE STIMULI IS INTRODUCED FROM THE STAGE 5 ONWARDS + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',cp_timeout, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','settling_in_state';'Tup','timeout_state'}); + + %%%%%%%%%%%%% SETTLING IN STATE START %%%%%%%%%%%%%%%%%%%% + % Before progressing check if its still centre poking or pokes within legal c_break other wise its a violation + + if CP_duration <= SettlingIn_time + legal_cbreak % state machine during initial warm-up when starting a new session + + sma = add_state(sma,'name','settling_in_state','self_timer',CP_duration, ... + 'output_actions', {'SchedWaveTrig', 'CP_Duration_wave'}, ... + 'input_to_statechange', {'Cout','current_state + 1';'Tup','side_led_wait_RewardCollection'}); + + % Intermediate State + + % This intermediate state is considering the poke is out before the end of settling time / at the start of this state + sma = add_state(sma,'self_timer',CP_duration,'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection';'Cin','current_state + 1';'Tup','side_led_wait_RewardCollection'}); + + % The state jump to here when the nose is still in then go + % directly to give reward + sma = add_state(sma,'self_timer',CP_duration,... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection'; 'Cout','current_state - 1';'Tup','side_led_wait_RewardCollection'}); + + else % the usual state machine + + sma = add_state(sma,'name','settling_in_state','self_timer',SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'settling_period'}, ... + 'input_to_statechange', {'Cout','current_state + 1';'Tup','soft_cp'}); + + % Intermediate State + + % This intermediate state is considering the poke is out before the end of settling time / at the start of this state + sma = add_state(sma,'self_timer',SettlingIn_time,'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'settling_period_In','legal_poke_start_state';'Cin','current_state + 1';'Tup','legal_poke_start_state';... + 'Rin', 'violation_state';'Rout', 'violation_state'; 'Lin', 'violation_state';'Lout', 'violation_state'}); + + % The state jump to here when the nose is still in then go directly to soft_cp + sma = add_state(sma,'self_timer',SettlingIn_time,... + 'input_to_statechange', {'settling_period_In','soft_cp'; 'Cout','current_state - 1';'Tup','soft_cp';... + 'Rin', 'violation_state'; 'Rout', 'violation_state'; 'Lin', 'violation_state'; 'Lout', 'violation_state'}); + + %%%%%%%%%%%%% SETTLING IN STATE END %%%%%%%%%%%%%%%%%%%%%% + + % STATE TO CHECK BEFORE START OF LEGAL POKE PERIOD + + sma = add_state(sma,'name','legal_poke_start_state','self_timer',legal_cbreak/2, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','soft_cp'; 'Tup','violation_state';... + 'Rin', 'violation_state'; 'Rout', 'violation_state'; 'Lin', 'violation_state'; 'Lout', 'violation_state'}); %more stringent by giving half the legal cp time + + + %%%%%%%%%%%% LEGAL SOFT POKE STATE START %%%%%%%%%%%%%%%%% + + if value(training_stage) == 4 + + % Soft poke with violation + + sma = add_state(sma,'name','soft_cp','self_timer',CP_duration - SettlingIn_time, ... CP_Duration_wave + 'output_actions', {'SchedWaveTrig', 'CP_Duration_wave'},... + 'input_to_statechange', {'Cout','current_state + 1'; 'Tup','side_led_wait_RewardCollection';... + 'Rin', 'violation_state'; 'Rout', 'violation_state'; 'Lin', 'violation_state'; 'Lout', 'violation_state'}); %more stringent by giving half the legal cp time + + else % Soft poke with violation. The difference between this and previous stage is introduction of + % sound before the go cue so a new scheduled wave + + sma = add_state(sma,'name','soft_cp','self_timer',CP_duration - SettlingIn_time, ... CP_Duration_wave + 'output_actions', {'SchedWaveTrig', 'CP_Duration_wave+stimplay'},... + 'input_to_statechange', {'Cout','current_state + 1'; 'Tup','side_led_wait_RewardCollection';... + 'Rin', 'violation_state'; 'Rout', 'violation_state'; 'Lin', 'violation_state'; 'Lout', 'violation_state'}); %more stringent by giving half the legal cp time + + end + + % Intermediate State + + % This intermediate state is considering the poke is out before the end of settling time / at the start of this state + sma = add_state(sma,'self_timer',legal_cbreak,'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'CP_Duration_wave_In','legal_poke_end_state';'Cin','current_state + 1';'Tup','violation_state';... + 'Rin', 'violation_state';'Rout', 'violation_state'; 'Lin', 'violation_state';'Lout', 'violation_state'}); + + % The state jump to here when the nose is still in then go directly to soft_cp + sma = add_state(sma,'self_timer',CP_duration - SettlingIn_time, ... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection';'Cout','current_state - 1';'Tup','side_led_wait_RewardCollection';... + 'Rin', 'violation_state';'Rout', 'violation_state'; 'Lin', 'violation_state';'Lout', 'violation_state'}); + + %%%%%%%%%%%% LEGAL SOFT POKE STATE END %%%%%%%%%%%%%%%%% + + % Before giving the reward check if its still centre poking or pokes + % within legal c_break other wise its a violation + + sma = add_state(sma,'name','legal_poke_end_state','self_timer',legal_cbreak/2, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','side_led_wait_RewardCollection'; 'Tup','violation_state'}); + + end + + %%%%%%%%%%%%%%% REWARD COLLECTION STATE START %%%%%%%%%%%%%%% + + sma = add_state(sma, 'name', 'side_led_wait_RewardCollection', 'self_timer', SideLed_duration + RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight; 'SchedWaveTrig', 'reward_collection_dur+Go_Cue'}, ... + 'input_to_statechange',{HitEvent,'hit_state'; 'Tup','timeout_state'; ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'reward_collection_dur_In', 'timeout_state'; 'Tup','timeout_state'; HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'SchedWaveTrig','reward_delivery'},... + 'input_to_statechange',{'Tup','drink_state'}); + + sma = add_state(sma,'name','drink_state','self_timer',drink_time,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + % For Timeout + + sma = add_state(sma,'name','timeout_state','self_timer',timeout_snd_duration,... + 'output_actions', {'SoundOut',to_sound_id; 'SchedWaveTrig', '-Go_Cue'},... + 'input_to_statechange',{'Tup','current_state+1'}); + sma = add_state(sma, 'self_timer', max(0.001, timeout_iti-timeout_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + % For Violations + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SoundOut',viol_sound_id; 'DOut', center1led; 'SchedWaveTrig', '-Go_Cue-stimplay-CP_Duration_wave'},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + end + + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'side_led_wait_RewardCollection','hit_state','second_hit_state','drink_state', 'violation_state','timeout_state','preclean_up_state'}; + + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors' + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'settling_in_state', [0.63 1 0.94], ... + 'legal_poke_start_state', [0.63 1 0.94]*0.8, ... + 'legal_poke_end_state', [1 0.79 0.63], ... + 'soft_cp', [0.3 0.9 0], ... + 'side_led_wait_RewardCollection', [0.53 0.78 1.00],... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + + + % 'lefthit', [0 0.9 0.3], ... + % 'error_state', [1 0.54 0.54], ... + + + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + case 'close' + + + case 'reinit' + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end + +end + + + diff --git a/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTraining_SessionDefinition_AutoTrainingStages.m b/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTraining_SessionDefinition_AutoTrainingStages.m new file mode 100644 index 00000000..694b6686 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/ArpitCentrePokeTraining_SessionDefinition_AutoTrainingStages.m @@ -0,0 +1,1480 @@ +%Training stage file. +%Please use the session automator window exclusively +%to edit this file. + +function varargout = ArpitCentrePokeTraining_SessionDefinition_AutoTrainingStages(obj, action, varargin) + +GetSoloFunctionArgs('func_owner', ['@' class(obj)], 'func_name', 'SessionModel'); + +pairs = {'helper_vars_eval', true; + 'stage_algorithm_eval', true; + 'completion_test_eval', false; + 'eod_logic_eval', false}; +parseargs(varargin, pairs); + +switch action + + +%% Familiarize with Reward Side Pokes + +% +case 'Familiarize with Reward Side Pokes' + + +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +CreateHelperVar(obj,'stage_start_completed_trial','value',n_done_trials,'force_init',true); +% +end + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); + + +% + +% Update ParamsSection +ParamsSection_MaxSame.value = 4; +callback(ParamsSection_MaxSame); +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end + +% Introduce Go/Reward Sound intensity after the rat did some trials and also increase +% it gradually until the max sound played at around 0.05. We will start +% with 0.001 +if value(PerformanceSummarySection_stage_1_TrialsValid) < value(TrainingStageParamsSection_Go_Sound_Start) + ParamsSection_Go_Sound.Value = 0; +elseif value(PerformanceSummarySection_stage_1_TrialsValid) == value(TrainingStageParamsSection_Go_Sound_Start)% start gradual increase + ParamsSection_Go_Sound.Value = 1; + SoundInterface_GoSoundVol.value = 0.001; +else + ParamsSection_Go_Sound.Value = 1; + SoundInterface_GoSoundVol.value = value(SoundInterface_GoSoundVol) + ((0.05 - 0.001) / (value(TrainingStageParamsSection_total_trials) - value(TrainingStageParamsSection_Go_Sound_Start))); +end +callback(SoundInterface_GoSoundVol); +callback(ParamsSection_Go_Sound); + +% Update TrainingStageParamsSection +if n_done_trials >= 2 + if previous_sides(end) ~= previous_sides(end-1) % last and present trials should also be a valid trial + TrainingStageParamsSection_trial_oppSide.value = value(TrainingStageParamsSection_trial_oppSide) + 1; % updating value for variable in TrainingParams_Section + callback(TrainingStageParamsSection_trial_oppSide); + end +end + +% Updating Disp Values for Training_Peformance_Summary +% Performance section updates +if n_done_trials > 0 + if n_done_trials == 1 + for k = 1:8 + eval(sprintf('PerformanceSummarySection_stage_%d_TrialsToday.value = 0;', k)); + eval(sprintf('callback(PerformanceSummarySection_stage_%d_TrialsToday);', k)); + end + end + + PerformanceSummarySection_stage_1_Trials.value = value(PerformanceSummarySection_stage_1_Trials) + 1; + PerformanceSummarySection_stage_1_TrialsToday.value = value(PerformanceSummarySection_stage_1_TrialsToday) + 1; + PerformanceSummarySection_stage_1_ViolationRate.value = ... + ((value(PerformanceSummarySection_stage_1_ViolationRate) * (value(PerformanceSummarySection_stage_1_Trials) - 1)) + double(violation_history(end))) ... + / value(PerformanceSummarySection_stage_1_Trials); + PerformanceSummarySection_stage_1_TimeoutRate.value = ... + ((value(PerformanceSummarySection_stage_1_TimeoutRate) * (value(PerformanceSummarySection_stage_1_Trials) - 1)) + double(timeout_history(end))) ... + / value(PerformanceSummarySection_stage_1_Trials); + + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_1_TrialsValid.value = value(PerformanceSummarySection_stage_1_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_1_Trials); + callback(PerformanceSummarySection_stage_1_TrialsToday); + callback(PerformanceSummarySection_stage_1_ViolationRate); + callback(PerformanceSummarySection_stage_1_TimeoutRate); + callback(PerformanceSummarySection_stage_1_TrialsValid); + + % Session-wide stats + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = numel(find(violation_history)) / n_done_trials; + SessionPerformanceSection_timeout_percent.value = numel(find(timeout_history)) / n_done_trials; + + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = numel(find(violation_history(end-19:end))) / 20; + SessionPerformanceSection_timeout_recent.value = numel(find(timeout_history(end-19:end))) / 20; + else + SessionPerformanceSection_violation_recent.value = nan; + SessionPerformanceSection_timeout_recent.value = nan; + end + + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_1_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_1_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_1_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_1_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today); + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end +% +end + + +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% +if ParamsSection_use_auto_train % do completion check if auto training + stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); + % only run it if its the start of the day, number of trials is small + if n_done_trials < 100 + if value(PerformanceSummarySection_stage_1_TrialsValid) > value(TrainingStageParamsSection_total_trials) && ... + value(TrainingStageParamsSection_trial_oppSide) > value(TrainingStageParamsSection_total_trials_opp) + ParamsSection_training_stage.value = stage_no + 1; + callback(ParamsSection_training_stage); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'Timeout Rewarded Side Pokes'); + end + end +end +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if value(PerformanceSummarySection_stage_1_TrialsValid) > value(TrainingStageParamsSection_total_trials) && ... + value(TrainingStageParamsSection_trial_oppSide) > value(TrainingStageParamsSection_total_trials_opp) + ParamsSection_training_stage.value = stage_no + 1; + callback(ParamsSection_training_stage); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'Timeout Rewarded Side Pokes'); +end + +% +end + +% + + + + + +%% Timeout Rewarded Side Pokes + +% + +case 'Timeout Rewarded Side Pokes' + + +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +CreateHelperVar(obj,'this_stage_opp_side_trials', 'value', 0, 'force_init',true); +% +end + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Change ParamsSection Vars +ParamsSection_MaxSame.value = 4; +callback(ParamsSection_MaxSame); +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end +% Update the reward collection time based upon behav +if length(timeout_history) > 5 + if all(timeout_history(end-1:end)) && value(this_stage_opp_side_trials) >= 2 + ParamsSection_RewardCollection_duration.value = min([value(ParamsSection_RewardCollection_duration) + 1,... + value(TrainingStageParamsSection_max_rColl_dur)]); + callback(ParamsSection_RewardCollection_duration); + this_stage_opp_side_trials.value = 0; + end + if ~any(timeout_history(end-1:end)) && value(this_stage_opp_side_trials) >= 2 + ParamsSection_RewardCollection_duration.value = max([value(ParamsSection_RewardCollection_duration) - 1,... + value(TrainingStageParamsSection_min_rColl_dur)]); + callback(ParamsSection_RewardCollection_duration); + this_stage_opp_side_trials.value = 0; + end +end +if length(timeout_history) > 20 + if all(timeout_history(end-19:end)) + ParamsSection_RewardCollection_duration.value = 120; + callback(ParamsSection_RewardCollection_duration); + end +end + +% Update TrainingStageParamsSection +if n_done_trials >= 2 + if previous_sides(end) ~= previous_sides(end-1) && all(~isnan(hit_history(end-1:end)))% last and present trials should also be a valid trial + TrainingStageParamsSection_trial_oppSide.value = value(TrainingStageParamsSection_trial_oppSide) + 1; % updating value for variable in TrainingParams_Section + this_stage_opp_side_trials.value = value(this_stage_opp_side_trials) + 1; % updating value to change the reward_Collection_Dur + callback(TrainingStageParamsSection_trial_oppSide); + end +end + + +% Updating Disp Values for Training_Peformance_Summary +% Performance section updates +if n_done_trials > 0 + if n_done_trials == 1 + for k = 1:8 + eval(sprintf('PerformanceSummarySection_stage_%d_TrialsToday.value = 0;', k)); + eval(sprintf('callback(PerformanceSummarySection_stage_%d_TrialsToday);', k)); + end + end + + PerformanceSummarySection_stage_2_Trials.value = value(PerformanceSummarySection_stage_2_Trials) + 1; + PerformanceSummarySection_stage_2_TrialsToday.value = value(PerformanceSummarySection_stage_2_TrialsToday) + 1; + PerformanceSummarySection_stage_2_ViolationRate.value = ... + ((value(PerformanceSummarySection_stage_2_ViolationRate) * (value(PerformanceSummarySection_stage_2_Trials) - 1)) + double(violation_history(end))) ... + / value(PerformanceSummarySection_stage_2_Trials); + PerformanceSummarySection_stage_2_TimeoutRate.value = ... + ((value(PerformanceSummarySection_stage_2_TimeoutRate) * (value(PerformanceSummarySection_stage_2_Trials) - 1)) + double(timeout_history(end))) ... + / value(PerformanceSummarySection_stage_2_Trials); + + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_2_TrialsValid.value = value(PerformanceSummarySection_stage_2_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_2_Trials); + callback(PerformanceSummarySection_stage_2_TrialsToday); + callback(PerformanceSummarySection_stage_2_ViolationRate); + callback(PerformanceSummarySection_stage_2_TimeoutRate); + callback(PerformanceSummarySection_stage_2_TrialsValid); + + % Session-wide stats + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = numel(find(violation_history)) / n_done_trials; + SessionPerformanceSection_timeout_percent.value = numel(find(timeout_history)) / n_done_trials; + + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = numel(find(violation_history(end-19:end))) / 20; + SessionPerformanceSection_timeout_recent.value = numel(find(timeout_history(end-19:end))) / 20; + else + SessionPerformanceSection_violation_recent.value = nan; + SessionPerformanceSection_timeout_recent.value = nan; + end + + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_2_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_2_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_2_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_2_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today); + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end + +% +end + + +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% +if ParamsSection_use_auto_train % do completion check if auto training + stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); + % only run it if its the start of the day, number of trials is small + if n_done_trials > 50 + if value(PerformanceSummarySection_stage_2_TrialsValid) > value(TrainingStageParamsSection_total_trials) && ... + value(TrainingStageParamsSection_trial_oppSide) > value(TrainingStageParamsSection_total_trials_opp) + ParamsSection_RewardCollection_duration.value = 40; + callback(ParamsSection_RewardCollection_duration); + ParamsSection_training_stage.value = stage_no + 1; + callback(ParamsSection_training_stage); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'Introduce Centre Poke'); + end + end +end +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end +% + + + + +%% Introduce Centre Poke + +% +case 'Introduce Centre Poke' +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Maximum & Minimum duration of center poke, in secs: +cp_max = value(ParamsSection_SettlingIn_time) + value(ParamsSection_legal_cbreak); +cp_min = value(ParamsSection_init_CP_duration); +% Minimum increment (in secs) in center poke duration every time there is a non-cp-violation trial: +cp_minimum_increment = 0.001; +ParamsSection_MaxSame.value = Inf; +callback(ParamsSection_MaxSame); + +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end +% Change the value of CP Duration +if value(TrainingStageParamsSection_last_session_CP) == 0 && value(PerformanceSummarySection_stage_3_Trials) < 2 + ParamsSection_CP_duration.value = cp_min; % initialize to min_CP +end + +if n_done_trials < 1 % intialize to min value at the start of each session/day + ParamsSection_CP_duration.value = value(ParamsSection_init_CP_duration); +elseif n_done_trials == 1 + ParamsSection_CP_duration.value = value(TrainingStageParamsSection_last_session_CP); +else + if ~timeout_history(end) && value(ParamsSection_CP_duration) < cp_max + increment = value(ParamsSection_CP_duration) * value(TrainingStageParamsSection_CPfraction_inc); + if increment < cp_minimum_increment + increment = cp_minimum_increment; + end + ParamsSection_CP_duration.value = value(ParamsSection_CP_duration) + increment; + end +end +callback(ParamsSection_CP_duration); + +% Performance section updates +if n_done_trials > 0 + if n_done_trials == 1 + for k = 1:8 + eval(sprintf('PerformanceSummarySection_stage_%d_TrialsToday.value = 0;', k)); + eval(sprintf('callback(PerformanceSummarySection_stage_%d_TrialsToday);', k)); + end + end + + PerformanceSummarySection_stage_3_Trials.value = value(PerformanceSummarySection_stage_3_Trials) + 1; + PerformanceSummarySection_stage_3_TrialsToday.value = value(PerformanceSummarySection_stage_3_TrialsToday) + 1; + PerformanceSummarySection_stage_3_ViolationRate.value = ... + ((value(PerformanceSummarySection_stage_3_ViolationRate) * (value(PerformanceSummarySection_stage_3_Trials) - 1)) + double(violation_history(end))) ... + / value(PerformanceSummarySection_stage_3_Trials); + PerformanceSummarySection_stage_3_TimeoutRate.value = ... + ((value(PerformanceSummarySection_stage_3_TimeoutRate) * (value(PerformanceSummarySection_stage_3_Trials) - 1)) + double(timeout_history(end))) ... + / value(PerformanceSummarySection_stage_3_Trials); + + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_3_TrialsValid.value = value(PerformanceSummarySection_stage_3_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_3_Trials); + callback(PerformanceSummarySection_stage_3_TrialsToday); + callback(PerformanceSummarySection_stage_3_ViolationRate); + callback(PerformanceSummarySection_stage_3_TimeoutRate); + callback(PerformanceSummarySection_stage_3_TrialsValid); + + % Session-wide stats + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = numel(find(violation_history)) / n_done_trials; + SessionPerformanceSection_timeout_percent.value = numel(find(timeout_history)) / n_done_trials; + + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = numel(find(violation_history(end-19:end))) / 20; + SessionPerformanceSection_timeout_recent.value = numel(find(timeout_history(end-19:end))) / 20; + else + SessionPerformanceSection_violation_recent.value = nan; + SessionPerformanceSection_timeout_recent.value = nan; + end + + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_3_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_3_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_3_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_3_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today); + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end + +% +end + + +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% +if ParamsSection_use_auto_train % do completion check if auto training + cp_max = value(ParamsSection_SettlingIn_time) + value(ParamsSection_legal_cbreak); + if value(ParamsSection_CP_duration) >= cp_max + TrainingStageParamsSection_last_session_CP.value = value(ParamsSection_CP_duration); + ParamsSection_RewardCollection_duration.value = 8; + callback(ParamsSection_RewardCollection_duration); + callback(TrainingStageParamsSection_last_session_CP); + ParamsSection_training_stage.value = 4; + callback(ParamsSection_training_stage); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'Introduce Violation for Centre Poke'); + end +end +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Update the CP duration reached in this session +TrainingStageParamsSection_last_session_CP.value = value(ParamsSection_CP_duration); +callback(TrainingStageParamsSection_last_session_CP); +% +end +% + + +%% Introduce Violation for Centre Poke + +% +case 'Introduce Violation for Centre Poke' +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end + + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% Maximum & Minimum duration of center poke, in secs: +cp_min = value(ParamsSection_SettlingIn_time) + value(ParamsSection_legal_cbreak); +cp_max = value(TrainingStageParamsSection_max_CP); +% Fractional increment in center poke duration every time there is a non-cp-violation trial: +cp_fraction = value(TrainingStageParamsSection_CPfraction_inc); +% Minimum increment (in secs) in center poke duration every time there is a non-cp-violation trial: +cp_minimum_increment = 0.001; + +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end + +% Change the value of CP Duration +if value(PerformanceSummarySection_stage_4_Trials) < 2 + ParamsSection_CP_duration.value = cp_min; % initialize to min_CP +end + +if n_done_trials == 0 + ParamsSection_CP_duration.value = value(ParamsSection_init_CP_duration); +elseif n_done_trials == 1 + ParamsSection_CP_duration.value = value(TrainingStageParamsSection_last_session_CP); +else + if ~violation_history(end) && ~timeout_history(end) && value(ParamsSection_CP_duration) < cp_max + increment = value(ParamsSection_CP_duration) * cp_fraction; + if increment < cp_minimum_increment + increment = cp_minimum_increment; + end + ParamsSection_CP_duration.value = value(ParamsSection_CP_duration) + increment; + end +end +if value(ParamsSection_CP_duration) > cp_max + ParamsSection_CP_duration.value = cp_max; +end + +callback(ParamsSection_CP_duration); + +% Performance section updates +if n_done_trials > 0 + if n_done_trials == 1 + for k = 1:8 + eval(sprintf('PerformanceSummarySection_stage_%d_TrialsToday.value = 0;', k)); + eval(sprintf('callback(PerformanceSummarySection_stage_%d_TrialsToday);', k)); + end + end + + PerformanceSummarySection_stage_4_Trials.value = value(PerformanceSummarySection_stage_4_Trials) + 1; + PerformanceSummarySection_stage_4_TrialsToday.value = value(PerformanceSummarySection_stage_4_TrialsToday) + 1; + PerformanceSummarySection_stage_4_ViolationRate.value = ... + ((value(PerformanceSummarySection_stage_4_ViolationRate) * (value(PerformanceSummarySection_stage_4_Trials) - 1)) + double(violation_history(end))) ... + / value(PerformanceSummarySection_stage_4_Trials); + PerformanceSummarySection_stage_4_TimeoutRate.value = ... + ((value(PerformanceSummarySection_stage_4_TimeoutRate) * (value(PerformanceSummarySection_stage_4_Trials) - 1)) + double(timeout_history(end))) ... + / value(PerformanceSummarySection_stage_4_Trials); + + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_4_TrialsValid.value = value(PerformanceSummarySection_stage_4_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_4_Trials); + callback(PerformanceSummarySection_stage_4_TrialsToday); + callback(PerformanceSummarySection_stage_4_ViolationRate); + callback(PerformanceSummarySection_stage_4_TimeoutRate); + callback(PerformanceSummarySection_stage_4_TrialsValid); + + % Session-wide stats + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = numel(find(violation_history)) / n_done_trials; + SessionPerformanceSection_timeout_percent.value = numel(find(timeout_history)) / n_done_trials; + + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = numel(find(violation_history(end-19:end))) / 20; + SessionPerformanceSection_timeout_recent.value = numel(find(timeout_history(end-19:end))) / 20; + else + SessionPerformanceSection_violation_recent.value = nan; + SessionPerformanceSection_timeout_recent.value = nan; + end + + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_4_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_4_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_4_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_4_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today); + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end + +% +end + + +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% +if ParamsSection_use_auto_train % do completion check if auto training + cp_max = value(TrainingStageParamsSection_max_CP); + if value(ParamsSection_CP_duration) >= cp_max && n_done_trials > 100 && ... + value(SessionPerformanceSection_violation_recent) < value(TrainingStageParamsSection_recent_violation) && ... + value(SessionPerformanceSection_timeout_recent) < value(TrainingStageParamsSection_recent_timeout) && ... + value(SessionPerformanceSection_violation_stage) < value(TrainingStageParamsSection_stage_violation) + + ParamsSection_training_stage.value = 5; + callback(ParamsSection_training_stage); + ParamsSection_RewardCollection_duration.value = 8; % Although done in previous stage as well but still to be sure + callback(ParamsSection_RewardCollection_duration); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'Introduce Stimuli Sound during Centre Poke'); + TrainingStageParamsSection_last_session_CP.value = value(ParamsSection_CP_duration); + callback(TrainingStageParamsSection_last_session_CP); + end +end +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Update the CP duration reached in this session +TrainingStageParamsSection_last_session_CP.value = value(ParamsSection_CP_duration); +callback(TrainingStageParamsSection_last_session_CP); +% +end +% + + + + + +%% Introduce Stimuli Sound during Centre Poke + +% +case 'Introduce Stimuli Sound during Centre Poke' +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Initial parameter fetch +cp_max = value(TrainingStageParamsSection_max_CP); +cp_min = value(TrainingStageParamsSection_min_CP); +cp_fraction = value(TrainingStageParamsSection_CPfraction_inc); +cp_minimum_increment = 0.001; +starting_cp = value(TrainingStageParamsSection_starting_CP) + value(ParamsSection_SettlingIn_time); +n_trial_warmup = value(TrainingStageParamsSection_warm_up_trials); +cp_range = cp_max - cp_min; +a1_min = 0.1; +a1_max = 0.4; + +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end + +% CP duration logic +init_CP_duration = value(ParamsSection_init_CP_duration); +last_CP = value(TrainingStageParamsSection_last_session_CP); +curr_CP = value(ParamsSection_CP_duration); + +if n_done_trials == 0 + new_CP = init_CP_duration; + +elseif n_done_trials <= n_trial_warmup + cp_delta = (last_CP - init_CP_duration) / n_trial_warmup; + new_CP = init_CP_duration + cp_delta * n_done_trials; + if new_CP < starting_cp + new_CP = starting_cp; + end +else + if ~violation_history(end) && ~timeout_history(end) + increment = curr_CP * cp_fraction; + if increment < cp_minimum_increment + increment = cp_minimum_increment; + end + new_CP = curr_CP + increment; + else + new_CP = curr_CP; + end +end +ParamsSection_CP_duration.value = min(new_CP, cp_max); +callback(ParamsSection_CP_duration); + +% Timing parameter adjustments based on CP_duration +if new_CP >= starting_cp && new_CP ~= curr_CP + if new_CP >= cp_min + % Scale A1_time linearly between 0.1 to 0.4 + scale = (new_CP - cp_min) / cp_range; + scale = min(max(scale, 0), 1); + ParamsSection_A1_time.value = a1_min + scale * (a1_max - a1_min); + else + ParamsSection_A1_time.value = a1_min; + end + + if new_CP >= 1 + ParamsSection_PreStim_time.value = 0.4; + else + ParamsSection_PreStim_time.value = 0.1; + end + + ParamsSection_SettlingIn_time.value = 0.2; + callback(ParamsSection_PreStim_time); + callback(ParamsSection_A1_time); + callback(ParamsSection_SettlingIn_time); + + % Update time between Auditory and GoCue + ParamsSection_time_bet_aud1_gocue.value = new_CP - value(ParamsSection_SettlingIn_time) - value(ParamsSection_A1_time) - value(ParamsSection_PreStim_time); + callback(ParamsSection_time_bet_aud1_gocue); +end + +% Performance section updates +if n_done_trials > 0 + if n_done_trials == 1 + for k = 1:8 + eval(sprintf('PerformanceSummarySection_stage_%d_TrialsToday.value = 0;', k)); + eval(sprintf('callback(PerformanceSummarySection_stage_%d_TrialsToday);', k)); + end + end + + PerformanceSummarySection_stage_5_Trials.value = value(PerformanceSummarySection_stage_5_Trials) + 1; + PerformanceSummarySection_stage_5_TrialsToday.value = value(PerformanceSummarySection_stage_5_TrialsToday) + 1; + PerformanceSummarySection_stage_5_ViolationRate.value = ... + ((value(PerformanceSummarySection_stage_5_ViolationRate) * (value(PerformanceSummarySection_stage_5_Trials) - 1)) + double(violation_history(end))) ... + / value(PerformanceSummarySection_stage_5_Trials); + PerformanceSummarySection_stage_5_TimeoutRate.value = ... + ((value(PerformanceSummarySection_stage_5_TimeoutRate) * (value(PerformanceSummarySection_stage_5_Trials) - 1)) + double(timeout_history(end))) ... + / value(PerformanceSummarySection_stage_5_Trials); + + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_5_TrialsValid.value = value(PerformanceSummarySection_stage_5_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_5_Trials); + callback(PerformanceSummarySection_stage_5_TrialsToday); + callback(PerformanceSummarySection_stage_5_ViolationRate); + callback(PerformanceSummarySection_stage_5_TimeoutRate); + callback(PerformanceSummarySection_stage_5_TrialsValid); + + % Session-wide stats + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = numel(find(violation_history)) / n_done_trials; + SessionPerformanceSection_timeout_percent.value = numel(find(timeout_history)) / n_done_trials; + + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = numel(find(violation_history(end-19:end))) / 20; + SessionPerformanceSection_timeout_recent.value = numel(find(timeout_history(end-19:end))) / 20; + else + SessionPerformanceSection_violation_recent.value = nan; + SessionPerformanceSection_timeout_recent.value = nan; + end + + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_5_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_5_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_5_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_5_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today); + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end +% +end + + + +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% +if ParamsSection_use_auto_train % do completion check if auto training + stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); + if value(ParamsSection_CP_duration) >= value(TrainingStageParamsSection_max_CP) && value(PerformanceSummarySection_stage_5_TrialsValid) > value(TrainingStageParamsSection_total_trials) + if value(SessionPerformanceSection_violation_recent) < value(TrainingStageParamsSection_recent_violation) && value(SessionPerformanceSection_timeout_recent) < value(TrainingStageParamsSection_recent_timeout) && ... + value(SessionPerformanceSection_violation_stage) < value(TrainingStageParamsSection_stage_violation) && n_done_trials > 100 + ParamsSection_training_stage.value = stage_no + 1; + callback(ParamsSection_training_stage); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'Vary Stimuli location during Centre Poke'); + TrainingStageParamsSection_last_session_CP.value = value(ParamsSection_CP_duration); + end + end +end +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Update the CP duration reached in this session +TrainingStageParamsSection_last_session_CP.value = value(ParamsSection_CP_duration); +callback(TrainingStageParamsSection_last_session_CP); +% +end + + + +% + +%% Vary Stimuli location during Centre Poke + +% +case 'Vary Stimuli location during Centre Poke' +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end + + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +cp_max = value(TrainingStageParamsSection_max_CP); +% Starting total center poke duration: +starting_cp = value(TrainingStageParamsSection_starting_CP) + value(ParamsSection_SettlingIn_time); +% number of warm-up trials +n_trial_warmup = value(TrainingStageParamsSection_warm_up_trials); +prestim_min = value(TrainingStageParamsSection_min_prestim); +prestim_max = value(TrainingStageParamsSection_max_prestim); +stim_dur = value(TrainingStageParamsSection_stim_dur); +init_CP_duration = value(ParamsSection_init_CP_duration); +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end + +if n_done_trials == 0 + new_CP = init_CP_duration; +elseif n_done_trials <= n_trial_warmup + cp_delta = (cp_max - init_CP_duration) / n_trial_warmup; + new_CP = init_CP_duration + cp_delta * n_done_trials; + if new_CP < starting_cp + new_CP = starting_cp; + end + if new_CP > cp_max + new_CP = cp_max; + end +else + new_CP = cp_max; +end + +ParamsSection_CP_duration.value = new_CP; +callback(ParamsSection_CP_duration); + +if new_CP >= starting_cp + + ParamsSection_SettlingIn_time.value = 0.2; + callback(ParamsSection_SettlingIn_time); + + if n_done_trials <= n_trial_warmup % during the warm up phase + ParamsSection_PreStim_time.value = 0.1; + ParamsSection_A1_time.value = 0.1; + else + ParamsSection_A1_time.value = stim_dur; % actual training stage + time_range_PreStim_time = prestim_min : 0.01 : prestim_max; + ParamsSection_PreStim_time.value = time_range_PreStim_time(randi([1, numel(time_range_PreStim_time)],1,1)); + end + + ParamsSection_time_bet_aud1_gocue.value = value(ParamsSection_CP_duration) - value(ParamsSection_SettlingIn_time) - value(ParamsSection_A1_time) - value(ParamsSection_PreStim_time); + callback(ParamsSection_time_bet_aud1_gocue) + callback(ParamsSection_PreStim_time); + callback(ParamsSection_A1_time); +end + +% --- Performance Logging --- +if n_done_trials > 0 + if n_done_trials == 1 + for i = 1:8 + eval(sprintf('PerformanceSummarySection_stage_%d_TrialsToday.value = 0;', i)); + eval(sprintf('callback(PerformanceSummarySection_stage_%d_TrialsToday);', i)); + end + end + + % Example using stage 6 + PerformanceSummarySection_stage_6_Trials.value = value(PerformanceSummarySection_stage_6_Trials) + 1; + PerformanceSummarySection_stage_6_TrialsToday.value = value(PerformanceSummarySection_stage_6_TrialsToday) + 1; + PerformanceSummarySection_stage_6_ViolationRate.value = ... + ((value(PerformanceSummarySection_stage_6_ViolationRate) * (value(PerformanceSummarySection_stage_6_Trials) - 1)) + ... + double(violation_history(end))) / value(PerformanceSummarySection_stage_6_Trials); + PerformanceSummarySection_stage_6_TimeoutRate.value = ... + ((value(PerformanceSummarySection_stage_6_TimeoutRate) * (value(PerformanceSummarySection_stage_6_Trials) - 1)) + ... + double(timeout_history(end))) / value(PerformanceSummarySection_stage_6_Trials); + + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_6_TrialsValid.value = value(PerformanceSummarySection_stage_6_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_6_Trials); + callback(PerformanceSummarySection_stage_6_TrialsToday); + callback(PerformanceSummarySection_stage_6_ViolationRate); + callback(PerformanceSummarySection_stage_6_TimeoutRate); + callback(PerformanceSummarySection_stage_6_TrialsValid); + + % SessionPerformance updates + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = mean(violation_history); + SessionPerformanceSection_timeout_percent.value = mean(timeout_history); + + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = mean(violation_history(end-19:end)); + SessionPerformanceSection_timeout_recent.value = mean(timeout_history(end-19:end)); + else + SessionPerformanceSection_violation_recent.value = nan; + SessionPerformanceSection_timeout_recent.value = nan; + end + + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_6_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_6_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_6_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_6_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today); + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end + +% +end +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% +if ParamsSection_use_auto_train % do completion check if auto training + stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); + if value(PerformanceSummarySection_stage_6_TrialsValid) > value(TrainingStageParamsSection_total_trials) + if value(SessionPerformanceSection_violation_recent) < value(TrainingStageParamsSection_recent_violation) && value(SessionPerformanceSection_timeout_recent) < value(TrainingStageParamsSection_recent_timeout) && ... + value(SessionPerformanceSection_violation_stage) < value(TrainingStageParamsSection_stage_violation) && n_done_trials > 100 + ParamsSection_training_stage.value = stage_no + 1; + callback(ParamsSection_training_stage); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'Variable Stimuli Go Cue location during Centre Poke'); + end + end +end +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end +% + + + + +%% Variable Stimuli Go Cue location during Centre Poke + +% +case 'Variable Stimuli Go Cue location during Centre Poke' +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Variables for warmup stage +cp_max = value(TrainingStageParamsSection_max_CP); +% Starting total center poke duration: +starting_cp = value(TrainingStageParamsSection_starting_CP) + value(ParamsSection_SettlingIn_time); +% number of warm-up trials +n_trial_warmup = value(TrainingStageParamsSection_warm_up_trials); +prestim_min = value(TrainingStageParamsSection_min_prestim); +prestim_max = value(TrainingStageParamsSection_max_prestim); +prestim_time = value(TrainingStageParamsSection_min_prestim); +a1_time = value(TrainingStageParamsSection_stim_dur); +a1_time_min = value(TrainingStageParamsSection_stim_dur); +a1_time_max = value(TrainingStageParamsSection_stim_dur + 0.1); +prego_min = value(TrainingStageParamsSection_min_prego); +prego_max = value(TrainingStageParamsSection_max_prego); +prego_time = value(TrainingStageParamsSection_min_prego); +warmup_completed = 0; +warm_up_on = 1; +random_prestim = 1; +random_prego = 1; +random_A1 = 0; + +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end + +% Warm Up If starting a new session +if warm_up_on == 1 + if n_done_trials == 0 + ParamsSection_CP_duration.value = value(ParamsSection_init_CP_duration); + warmup_completed = 0; + elseif n_done_trials == 1 + ParamsSection_CP_duration.value = starting_cp; + else + if value(ParamsSection_CP_duration) <= cp_max % warm up stage + if ~violation_history(end) && ~timeout_history(end) + increment = (cp_max - value(ParamsSection_init_CP_duration))/ (n_trial_warmup - 1); + ParamsSection_CP_duration.value = value(ParamsSection_CP_duration) + increment; + % Check if the values are within the required range + if value(ParamsSection_CP_duration) < starting_cp + ParamsSection_CP_duration.value = starting_cp; + end + if value(ParamsSection_CP_duration) >= cp_max + ParamsSection_CP_duration.value = cp_max; + warmup_completed = 1; + end + end + end + end +else + warmup_completed = 1; +end + +if n_done_trials >= 1 + cp_length = value(ParamsSection_CP_duration) - value(ParamsSection_SettlingIn_time); + [ParamsSection_PreStim_time.value ,ParamsSection_A1_time.value,ParamsSection_time_bet_aud1_gocue.value] = param_time_within_range(warmup_completed,... + cp_length,prestim_min,prestim_max, random_prestim, prestim_time,... + a1_time_min,a1_time_max, random_A1, a1_time,prego_min,prego_max, random_prego, prego_time); + + callback(ParamsSection_PreStim_time); + callback(ParamsSection_A1_time); + callback(ParamsSection_time_bet_aud1_gocue); + ParamsSection_CP_duration.value = value(ParamsSection_SettlingIn_time) + value(ParamsSection_PreStim_time) + value(ParamsSection_A1_time) + value(ParamsSection_time_bet_aud1_gocue); +end +callback(ParamsSection_CP_duration); + +% Performance section updates +if n_done_trials > 0 + if n_done_trials == 1 + for k = 1:8 + eval(sprintf('PerformanceSummarySection_stage_%d_TrialsToday.value = 0;', k)); + eval(sprintf('callback(PerformanceSummarySection_stage_%d_TrialsToday);', k)); + end + end + + PerformanceSummarySection_stage_7_Trials.value = value(PerformanceSummarySection_stage_7_Trials) + 1; + PerformanceSummarySection_stage_7_TrialsToday.value = value(PerformanceSummarySection_stage_7_TrialsToday) + 1; + PerformanceSummarySection_stage_7_ViolationRate.value = ... + ((value(PerformanceSummarySection_stage_7_ViolationRate) * (value(PerformanceSummarySection_stage_7_Trials) - 1)) + double(violation_history(end))) ... + / value(PerformanceSummarySection_stage_7_Trials); + PerformanceSummarySection_stage_7_TimeoutRate.value = ... + ((value(PerformanceSummarySection_stage_7_TimeoutRate) * (value(PerformanceSummarySection_stage_7_Trials) - 1)) + double(timeout_history(end))) ... + / value(PerformanceSummarySection_stage_7_Trials); + + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_7_TrialsValid.value = value(PerformanceSummarySection_stage_7_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_7_Trials); + callback(PerformanceSummarySection_stage_7_TrialsToday); + callback(PerformanceSummarySection_stage_7_ViolationRate); + callback(PerformanceSummarySection_stage_7_TimeoutRate); + callback(PerformanceSummarySection_stage_7_TrialsValid); + + % Session-wide stats + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = numel(find(violation_history)) / n_done_trials; + SessionPerformanceSection_timeout_percent.value = numel(find(timeout_history)) / n_done_trials; + + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = numel(find(violation_history(end-19:end))) / 20; + SessionPerformanceSection_timeout_recent.value = numel(find(timeout_history(end-19:end))) / 20; + else + SessionPerformanceSection_violation_recent.value = nan; + SessionPerformanceSection_timeout_recent.value = nan; + end + + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_7_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_7_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_7_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_7_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today); + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end + +% +end + + + +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); + +if value(PerformanceSummarySection_stage_7_TrialsValid) > value(TrainingStageParamsSection_total_trials) + if value(SessionPerformanceSection_violation_recent) < value(TrainingStageParamsSection_recent_violation) && value(SessionPerformanceSection_timeout_recent) < value(TrainingStageParamsSection_recent_timeout) && ... + value(SessionPerformanceSection_violation_stage) < value(TrainingStageParamsSection_stage_violation) + ParamsSection_training_stage.value = stage_no + 1; + callback(ParamsSection_training_stage); + ParamsSection(obj, 'Changed_Training_Stage'); + SessionDefinition(obj, 'jump_to_stage', 'User Setting'); + end +end +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end +% + +%% User Setting + +% + case 'User Setting' + + +if helper_vars_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end + + + +if stage_algorithm_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% +% Variables for warmup stage +cp_max = 3; +n_trial_warmup = 20; +starting_cp = 0.5; +warmup_completed = 0; +warm_up_on = value(ParamsSection_warmup_on); +random_prestim = value(ParamsSection_random_PreStim_time); +random_prego = value(ParamsSection_random_prego_time); +random_A1 = value(ParamsSection_random_A1_time); +prestim_min = value(ParamsSection_PreStim_time_Min); +prestim_max = value(ParamsSection_PreStim_time_Max); +prestim_time = value(ParamsSection_PreStim_time); +prego_min = value(ParamsSection_time_bet_aud1_gocue_Min); +prego_max = value(ParamsSection_time_bet_aud1_gocue_Max); +prego_time = value(ParamsSection_time_bet_aud1_gocue); +a1_time = value(ParamsSection_A1_time); +a1_time_min = value(ParamsSection_A1_time_Min); +a1_time_max = value(ParamsSection_A1_time_Max); + +stage_no = value(SessionDefinition_CURRENT_ACTIVE_STAGE); +if stage_no ~= value(ParamsSection_training_stage) + ParamsSection_training_stage.value = stage_no; + callback(ParamsSection_training_stage); +end + +% Warm Up If starting a new session +if warm_up_on == 1 + if n_done_trials == 0 + ParamsSection_CP_duration.value = value(ParamsSection_init_CP_duration); + warmup_completed = 0; + elseif n_done_trials == 1 + ParamsSection_CP_duration.value = starting_cp; + else + if value(ParamsSection_CP_duration) <= cp_max % warm up stage + if ~violation_history(end) && ~timeout_history(end) + increment = (cp_max - value(ParamsSection_init_CP_duration))/ (n_trial_warmup - 1); + ParamsSection_CP_duration.value = value(ParamsSection_CP_duration) + increment; + % Check if the values are within the required range + if value(ParamsSection_CP_duration) < starting_cp + ParamsSection_CP_duration.value = starting_cp; + end + if value(ParamsSection_CP_duration) >= cp_max + ParamsSection_CP_duration.value = cp_max; + warmup_completed = 1; + end + end + end + end +else + warmup_completed = 1; +end + +if n_done_trials >= 1 + cp_length = value(ParamsSection_CP_duration) - value(ParamsSection_SettlingIn_time); + [ParamsSection_PreStim_time.value ,ParamsSection_A1_time.value,ParamsSection_time_bet_aud1_gocue.value] = param_time_within_range(warmup_completed,... + cp_length,prestim_min,prestim_max, random_prestim, prestim_time,... + a1_time_min,a1_time_max, random_A1, a1_time,prego_min,prego_max, random_prego, prego_time); + + callback(ParamsSection_PreStim_time); + callback(ParamsSection_A1_time); + callback(ParamsSection_time_bet_aud1_gocue); + ParamsSection_CP_duration.value = value(ParamsSection_SettlingIn_time) + value(ParamsSection_PreStim_time) + value(ParamsSection_A1_time) + value(ParamsSection_time_bet_aud1_gocue); +end +callback(ParamsSection_CP_duration); + +if n_done_trials > 0 + + if n_done_trials == 1 + PerformanceSummarySection_stage_1_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_1_TrialsToday); + PerformanceSummarySection_stage_2_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_2_TrialsToday); + PerformanceSummarySection_stage_3_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_3_TrialsToday); + PerformanceSummarySection_stage_4_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_4_TrialsToday); + PerformanceSummarySection_stage_5_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_5_TrialsToday); + PerformanceSummarySection_stage_6_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_6_TrialsToday); + PerformanceSummarySection_stage_7_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_7_TrialsToday); + PerformanceSummarySection_stage_8_TrialsToday.value = 0; + callback(PerformanceSummarySection_stage_8_TrialsToday); + end + + % Updating Disp Values for Training_Peformance_Summary + PerformanceSummarySection_stage_8_Trials.value = value(PerformanceSummarySection_stage_8_Trials) + 1; + PerformanceSummarySection_stage_8_TrialsToday.value = value(PerformanceSummarySection_stage_8_TrialsToday) + 1; + PerformanceSummarySection_stage_8_ViolationRate.value = ((value(PerformanceSummarySection_stage_8_ViolationRate) * (value(PerformanceSummarySection_stage_8_Trials) - 1)) + double(violation_history(end))) / value(PerformanceSummarySection_stage_8_Trials); + PerformanceSummarySection_stage_8_TimeoutRate.value = ((value(PerformanceSummarySection_stage_8_TimeoutRate) * (value(PerformanceSummarySection_stage_8_Trials) - 1)) + double(timeout_history(end))) / value(PerformanceSummarySection_stage_8_Trials); + if ~isnan(hit_history(end)) + PerformanceSummarySection_stage_8_TrialsValid.value = value(PerformanceSummarySection_stage_8_TrialsValid) + 1; + end + + callback(PerformanceSummarySection_stage_8_Trials); + callback(PerformanceSummarySection_stage_8_TrialsToday) + callback(PerformanceSummarySection_stage_8_ViolationRate); + callback(PerformanceSummarySection_stage_8_TimeoutRate); + callback(PerformanceSummarySection_stage_8_TrialsValid); + + % Updating Disp Values for SessionPerformanceSection + SessionPerformanceSection_ntrials.value = n_done_trials; + SessionPerformanceSection_violation_percent.value = numel(find(violation_history))/n_done_trials; + SessionPerformanceSection_timeout_percent.value = numel(find(timeout_history))/n_done_trials; + if n_done_trials >= 20 + SessionPerformanceSection_violation_recent.value = numel(find(violation_history(end-19:end)))/20; + SessionPerformanceSection_timeout_recent.value = numel(find(timeout_history(end-19:end)))/20; + else + SessionPerformanceSection_timeout_recent.value = nan; + SessionPerformanceSection_violation_recent.value = nan; + end + SessionPerformanceSection_violation_stage.value = value(PerformanceSummarySection_stage_8_ViolationRate); + SessionPerformanceSection_ntrials_stage.value = value(PerformanceSummarySection_stage_8_Trials); + SessionPerformanceSection_ntrials_stage_today.value = value(PerformanceSummarySection_stage_8_TrialsToday); + SessionPerformanceSection_timeout_stage.value = value(PerformanceSummarySection_stage_8_TimeoutRate); + + callback(SessionPerformanceSection_ntrials); + callback(SessionPerformanceSection_ntrials_stage); + callback(SessionPerformanceSection_ntrials_stage_today) + callback(SessionPerformanceSection_violation_percent); + callback(SessionPerformanceSection_timeout_percent); + callback(SessionPerformanceSection_violation_recent); + callback(SessionPerformanceSection_timeout_recent); + callback(SessionPerformanceSection_violation_stage); + callback(SessionPerformanceSection_timeout_stage); +end + +% +end + + + +if completion_test_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +clear('ans'); +% + +% +if exist('ans', 'var') +varargout{1}=logical(ans); clear('ans'); +else +varargout{1}=false; +end +end + + + +if eod_logic_eval +GetSoloFunctionArgs(obj); +ClearHelperVarsNotOwned(obj); +% + +% +end +% + + +end + +end + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% + +function [prestim,A1,prego] = param_time_within_range(not_fixed_length,cp_length,range_min_prestim,range_max_prestim, is_random_prestim, provided_time_prestim,... + range_min_A1,range_max_A1, is_random_A1, provided_time_A1,range_min_prego,range_max_prego, is_random_prego, provided_time_prego) + +if not_fixed_length == 0 % warm up stage where cp length is increasing +% then calculate the range/typical value + if cp_length <= 0.3 + prestim = 0.1; + A1 = 0.1; + prego = 0.1; + else + range_size = round(0.3 * cp_length,1); + if range_size > 0.4 + step_size = 0.1; + else + step_size = 0.01; + end + + timerange = 0.1:step_size:range_size; + + if is_random_prestim == 1 + prestim = timerange(randi([1, numel(timerange)],1,1)); + else + if provided_time_prestim <= range_size + prestim = provided_time_prestim; + else + prestim = range_size; + end + + end + + if is_random_A1 == 1 + A1 = timerange(randi([1, numel(timerange)],1,1)); + else + if provided_time_A1 <= range_size + A1 = provided_time_A1; + else + A1 = range_size; + end + end + + prego = cp_length - prestim - A1; + + end + +else + + if is_random_prestim == 1 + range_size_prestim = range_max_prestim - range_min_prestim; + if range_size_prestim > 0.4 + step_size_prestim = 0.1; + else + step_size_prestim = 0.01; + end + time_range_prestim = range_min_prestim:step_size_prestim:range_max_prestim; + prestim = time_range_prestim(randi([1, numel(time_range_prestim)],1,1)); + else + prestim = provided_time_prestim; + end + + if is_random_A1 == 1 + range_size_A1 = range_max_A1 - range_min_A1; + if range_size_A1 > 0.4 + step_size_A1 = 0.1; + else + step_size_A1 = 0.01; + end + time_range_A1 = range_min_A1:step_size_A1:range_max_A1; + A1 = time_range_A1(randi([1, numel(time_range_A1)],1,1)); + else + A1 = provided_time_A1; + end + + if is_random_prego == 1 + range_size_prego = range_max_prego - range_min_prego; + if range_size_prego > 0.4 + step_size_prego = 0.1; + else + step_size_prego = 0.01; + end + time_range_prego = range_min_prego:step_size_prego:range_max_prego; + prego = time_range_prego(randi([1, numel(time_range_prego)],1,1)); + else + prego = provided_time_prego; + end + +end +end + + +% + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/Protocols/@ArpitCentrePokeTraining/LOGplotPairs.m b/Protocols/@ArpitCentrePokeTraining/LOGplotPairs.m new file mode 100644 index 00000000..275f736e --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/LOGplotPairs.m @@ -0,0 +1,71 @@ +function LOGplotPairs(x,y,marker,markersize,markeredgecolor,thislinewidth,FONTSIZE) + + +% delete(gca) +% load('NEWHOT','HOTETOBOKHORAM') + +% position = [16 124 1019 761]; +% % +% figure('Position',position); + +% if nargin <8 +% LogOrLin='linear'; +% end + +if nargin <4 + marker='.'; +end + + +% Plot the points +hold on +x=log(x); +y=log(y); +for i=1:length(x) + +% loglog(x(i),y(i),marker,'color',map(in,:),'markerfacecolor',map(in,:),'MarkerSize',markersize) +% plot3(x(i),y(i),v(i),marker,'MarkerSize',markersize,'MarkerEdgeColor',markeredgecolor,'LineWidth',thislinewidth) + plot(x(i),y(i),marker,'MarkerSize',markersize,'MarkerEdgeColor',markeredgecolor,'LineWidth',thislinewidth) + +end + +xlim([min([x ;y])-min([x ;y])/2 max([x ;y])+min([x ;y])/2]) +ylim([min([x; y])-min([x ;y])/2 max([x; y])+-min([x ;y])/2]) +hold off + +% figure('Position',[1 scrsz(4)/2 scrsz(3)/2 scrsz(4)/2]) + +Ylabel('log_e \sigma_2','FontSize',FONTSIZE,'FontName','Cambria Math'); +set(double(gca),'Fontsize',15) +Xlabel('log_e \sigma_1','FontSize',FONTSIZE,'FontName','Cambria Math') + +% grid on +setyticklabels=1 + +if setyticklabels==1 + +Ytick=get(double(gca),'YtickLabel'); +Xtick=get(double(gca),'XtickLabel'); +% +% Ytick=num2str((3:0.5:6)'); +% Xtick=num2str((3:0.5:6)'); + +% set(gca,'ytick',[],'xtick',[]); +end +% +% axis square +% HUMANORRAT=2 +% if HUMANORRAT==2 +% ylim([2.5 6]) +% xlim([2.2 6.3]) +% else +% ylim([3.5 6]) +% xlim([3.5 6.5]) +% end +if setyticklabels==1 +set(double(gca),'ytick',str2num(Ytick),'xtick',str2num(Xtick)); +set(double(gca),'yticklabel',num2str(round(round(exp(str2num(Ytick)).*100)./100)),'xticklabel',num2str(round(round(exp(str2num(Xtick)).*100)./100))); +end + +% set(gca,'yticklabel',num2str(round(exp(str2num(Ytick)).*100)./100),'xticklabel',num2str(round(exp(str2num(Xtick)).*100)./100)); +view(2) diff --git a/Protocols/@ArpitCentrePokeTraining/ParamsSection.m b/Protocols/@ArpitCentrePokeTraining/ParamsSection.m new file mode 100644 index 00000000..86991688 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/ParamsSection.m @@ -0,0 +1,511 @@ +function [x, y] = ParamsSection(obj, action, x,y) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + y0 = y; + NumeditParam(obj, 'LeftProb', 0.5, x, y); next_row(y); + MenuParam(obj, 'MaxSame', {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Inf}, Inf, x, y, ... + 'TooltipString', sprintf(['\nMaximum number of consecutive trials where correct\n' ... + 'response is on the same side. Overrides antibias. Thus, for\n' ... + 'example, if MaxSame=5 and there have been 5 Left trials, the\n' ... + 'next trial is guaranteed to be Right'])); next_row(y); + + DispParam(obj, 'ThisTrial', 'LEFT', x, y); next_row(y); + SoloParamHandle(obj, 'previous_sides', 'value', []); + DeclareGlobals(obj, 'ro_args', 'previous_sides'); + + + SubheaderParam(obj, 'title', 'Params Section', x, y); + % next_row(y, 1.5); + % next_column(x); y = 5; + % next_row(y); + NumeditParam(obj, 'reward_delay', 0.01, x,y,'label','Reward Delay','TooltipString','Delay between side poke and reward delivery'); + next_row(y); + NumeditParam(obj, 'drink_time', 1, x,y,'label','Drink Time','TooltipString','waits to finish water delivery'); + next_row(y); + NumeditParam(obj, 'timeout_iti', 1, x,y,'label','Timeout ITI','TooltipString','ITI on timeout trials'); + next_row(y); + NumeditParam(obj, 'violation_iti', 1, x,y,'label','Violation ITI','TooltipString','Center poke violation duration'); + % Reward Collection + next_row(y); + NumeditParam(obj, 'RewardCollection_duration', 300, x,y,'label','RewardCollection_dur','TooltipString','Wait until rat collects the reward else a timeout'); + next_row(y); + NumeditParam(obj, 'SideLed_duration', 1, x,y,'label','Side LED duration','TooltipString','Duration of SideLed'); + next_row(y); + % Centre Poke + next_row(y); + NumeditParam(obj, 'cp_timeout', 120, x,y, 'TooltipString','Time from trial start for rat to centre poke else timeout'); + next_row(y); + NumeditParam(obj, 'legal_cbreak', 0.1, x,y, 'TooltipString','Time in sec for which it is ok to be outside the center port before a violation occurs.'); + next_row(y); + NumeditParam(obj, 'SettlingIn_time', 0.2, x,y, 'TooltipString','Initial settling period during which there is no violation'); + next_row(y); + % PreStim + NumeditParam(obj, 'PreStim_time_Min', 0.20, x,y,'label','Pre-Stim Min','TooltipString','Min Time in NIC before starting the stimulus'); + set_callback(PreStim_time_Min, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'PreStim_time', 0.20, x,y,'label','Pre-Stim time','TooltipString','Actual Time in NIC before starting the stimulus'); + set_callback(PreStim_time, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'PreStim_time_Max', 0.40, x,y,'label','Pre-Stim Max','TooltipString','Max Time in NIC before starting the stimulus'); + set_callback(PreStim_time_Max, {mfilename, 'new_CP_duration'}); + next_row(y); + % A1 Time + NumeditParam(obj, 'A1_time_Min', 0.1, x,y,'label','Min time AUD1','TooltipString','Min value to select the Duration of fixed stimulus'); + set_callback(A1_time_Min, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'A1_time', 0.4, x,y,'label','AUD1 Time','TooltipString','Actual Duration of fixed stimulus'); + set_callback(A1_time, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'A1_time_Max', 0.4, x,y,'label','Max time AUD1','TooltipString','Max value to select the Duration of fixed stimulus'); + set_callback(A1_time_Max, {mfilename, 'new_CP_duration'}); + next_row(y); + % Time b/w stim and Go Cue + NumeditParam(obj, 'time_bet_aud1_gocue_Min', 0.2, x,y,'label','Min A1-GoCue time','TooltipString','Min time between the end of the stimulus and the go cue '); + set_callback(time_bet_aud1_gocue_Min, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_bet_aud1_gocue', 0.2, x,y,'label','A1-GoCue time','TooltipString','Actual time between the end of the stimulus and the go cue '); + set_callback(time_bet_aud1_gocue, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_bet_aud1_gocue_Max', 2, x,y,'label','Max A1-GoCue time','TooltipString','Max time between the end of the stimulus and the go cue '); + set_callback(time_bet_aud1_gocue_Max, {mfilename, 'new_CP_duration'}); + next_row(y); + DispParam(obj, 'init_CP_duration', 0.01, x,y,'label','init_CP duration','TooltipString','Duration of Nose in Central Poke before Go cue starts (see Total_CP_duration)'); + next_row(y); + NumeditParam(obj, 'CP_duration', PreStim_time+A1_time+time_bet_aud1_gocue, x,y,'label','CP duration', 'TooltipString','Duration of Nose in Central Poke before Go cue starts (see Total_CP_duration)'); + % set_callback(CP_duration, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_go_cue' ,0.2, x,y,'label','Go Cue Duration','TooltipString','duration of go cue (see Total_CP_duration)'); + set_callback(time_go_cue, {mfilename, 'new_time_go_cue'}); + next_row(y); + DispParam(obj, 'Total_CP_duration', CP_duration+time_go_cue, x, y,'save_with_settings', 1, 'TooltipString', 'Total expected(rat can poke out anytime after Go cue onset) nose in center port time, in secs. Sum of CP_duration and Go Cue duration'); %#ok<*NODEF> + + next_row(y); + ToggleParam(obj, 'Go_Sound', 0, x, y, 'OnString', 'Play Reward Sound', 'OffString', 'No Reward Sound','TooltipString', ... + 'If 1 (black), sound is played for intial 2 stages of light chasing; if 0 (brown), leave sound off'); + next_row(y); + ToggleParam(obj, 'stimuli_on', 0, x,y,... + 'OnString', 'Use Stimuli',... + 'OffString', 'Fixed Sound',... + 'TooltipString', sprintf('If on (black) then it enables training with stimuli else using a fixed sound from Stage 5')); + next_row(y); + ToggleParam(obj, 'warmup_on', 1, x,y,... + 'OnString', 'Warmup ON',... + 'OffString', 'Warmup OFF',... + 'TooltipString', sprintf(['If on (Yellow) then it applies the initial warming up phase, during which the\n',... + 'CP_duration starts small and gradually grows to last_session_max_cp_duration'])); + next_row(y); + ToggleParam(obj, 'random_PreStim_time', 0, x,y,... + 'OnString', 'random PreStim_time ON',... + 'OffString', 'random PreStim_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random time between the user given range')); + set_callback(random_PreStim_time, {mfilename, 'new_CP_duration'}); + next_row(y); + ToggleParam(obj, 'random_A1_time', 0, x,y,... + 'OnString', 'random A1_time ON',... + 'OffString', 'random A1_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random sampling of A1_time')); + set_callback(random_A1_time, {mfilename, 'new_CP_duration'}); + next_row(y); + ToggleParam(obj, 'random_prego_time', 0, x,y,... + 'OnString', 'random prego_time ON',... + 'OffString', 'random prego_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random sampling of time between the end of the stimulus and the go cue')); + set_callback(random_prego_time, {mfilename, 'new_CP_duration'}); + + next_column(x); + y=5; + MenuParam(obj, 'training_stage', {'1'; '2'; '3';... + '4'; '5'; '6'; '7';'8'}, 1, x, y, ... + 'label', 'Active Stage', 'TooltipString', 'the current training stage'); + % NumeditParam(obj,'training_stage',1,x,y,'label','Training Stage'); + set_callback(training_stage, {mfilename, 'Changed_Training_Stage'}); + disable(training_stage); + next_row(y); + ToggleParam(obj,'use_auto_train',1,x,y,'OnString','Using Autotrain','OffString','Manual Settings'); + set_callback(use_auto_train, {mfilename, 'Changed_Training_Stage'}); + + next_row(y); + NumeditParam(obj, 'ntrial_correct_bias', 0, x, y, ... + 'TooltipString', 'antibias starts from trial=ntrial_correct_bias'); + next_row(y); + NumeditParam(obj, 'right_left_diff', .12, x, y, ... + 'TooltipString', 'antibias applies if difference between right and left sides is bigger than this number'); + next_row(y); + NumeditParam(obj, 'max_wtr_mult', 4, x, y, ... + 'TooltipString', 'wtr_mult will be min(max_wtr_mult,right_hit/left_hit)'); + next_row(y); + NumeditParam(obj, 'left_wtr_mult', 1, x, y, ... + 'TooltipString', 'all left reward times are multiplied by this number'); + next_row(y); + NumeditParam(obj, 'right_wtr_mult', 1, x, y, ... + 'TooltipString', 'all right reward times are multiplied by this number'); + next_row(y); + ToggleParam(obj, 'antibias_wtr_mult', 0, x,y,... + 'OnString', 'AntiBias Water ON',... + 'OffString', 'AntiBias Water OFF',... + 'TooltipString', sprintf(['If on (black) then it disables the wtr_mult entries\n'... + 'and uses hitfrac to adjust the water times'])); + + next_row(y); + SoloFunctionAddVars('ArpitCentrePokeTrainingSMA', 'ro_args', ... + {'CP_duration';'SideLed_duration'; 'stimuli_on';... + 'RewardCollection_duration';'training_stage'; ... + 'legal_cbreak' ; 'SettlingIn_time'; 'time_go_cue'; ... + 'A1_time';'time_bet_aud1_gocue' ; 'PreStim_time'; + 'drink_time';'reward_delay';'antibias_wtr_mult';... + 'cp_timeout';'timeout_iti';'violation_iti';'Go_Sound'}); + + + SoloFunctionAddVars('SessionPerformanceSection', 'ro_args', ... + {'training_stage'}); + + SoloFunctionAddVars('StimulusSection', 'ro_args', ... + {'training_stage';'stimuli_on';'ThisTrial';'A1_time';... + 'time_bet_aud1_gocue' ;'PreStim_time'}); + + SoloFunctionAddVars('TrainingStageParamsSection', 'ro_args', ... + {'training_stage'}); + + SoloFunctionAddVars('PerformanceSummarySection', 'ro_args', ... + {'training_stage'}); + + % SoloParamHandle(obj, 'previous_parameters', 'value', []); + + + + %%%%%%%%% Switch b/w Actions within ParamSection %%%%%%%%%%%%% + + case 'new_CP_duration' + + if random_PreStim_time == 1 && (value(PreStim_time) < value(PreStim_time_Min) || value(PreStim_time) > value(PreStim_time_Max)) + PreStim_time.value = value(PreStim_time_Min); + else + PreStim_time.value = value(PreStim_time); + end + + if random_A1_time == 1 && (value(A1_time) < value(A1_time_Min) || value(A1_time) > value(A1_time_Max)) + A1_time.value = value(A1_time_Min); + else + A1_time.value = value(A1_time); + end + + if random_prego_time == 1 && (value(time_bet_aud1_gocue) < value(time_bet_aud1_gocue_Min) || value(time_bet_aud1_gocue) > value(time_bet_aud1_gocue_Max)) + time_bet_aud1_gocue.value = value(time_bet_aud1_gocue_Min); + else + time_bet_aud1_gocue.value = value(time_bet_aud1_gocue); + end + + CP_duration.value= value(SettlingIn_time) + value(PreStim_time) + value(A1_time) + value(time_bet_aud1_gocue); + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); %#ok<*NASGU> + + case 'new_time_go_cue' + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); + + case 'Changed_Training_Stage' + + if value(use_auto_train) == 1 + disable(training_stage); % user cannot change the training stages + else + Training_Stages_List = SessionDefinition(obj, 'get_stagelist'); + stage_name = Training_Stages_List{value(training_stage)}; + enable(training_stage); % user can change the training stages + SessionDefinition(obj, 'jump_to_stage',stage_name); + end + + [stage_fig_x,stage_fig_y] = TrainingStageParamsSection(obj, 'reinit', value(stage_fig_x),value(stage_fig_y)); % update the training params as well + ArpitCentrePokeTrainingSMA(obj,'reinit'); + SessionPerformanceSection(obj, 'evaluate'); + + switch value(training_stage) + + case {1,2} %% learning the reward sound association -left or right led on -> poke -> sound+reward + + make_invisible(SettlingIn_time); make_invisible(legal_cbreak); make_invisible(cp_timeout); + make_invisible(PreStim_time); make_invisible(PreStim_time_Min); make_invisible(PreStim_time_Max); + make_invisible(A1_time); make_invisible(A1_time_Min); make_invisible(A1_time_Max); + make_invisible(time_bet_aud1_gocue);make_invisible(time_bet_aud1_gocue_Min);make_invisible(time_bet_aud1_gocue_Max); + make_invisible(CP_duration); make_invisible(Total_CP_duration); make_invisible(init_CP_duration); + + case {3,4} % Centre poke without the A1_Stim but has violation in 4 + + make_visible(SettlingIn_time); make_visible(legal_cbreak); make_visible(cp_timeout); + make_invisible(PreStim_time); make_invisible(PreStim_time_Min); make_invisible(PreStim_time_Max); + make_invisible(A1_time); make_invisible(A1_time_Min); make_invisible(A1_time_Max); + make_invisible(time_bet_aud1_gocue);make_invisible(time_bet_aud1_gocue_Min);make_invisible(time_bet_aud1_gocue_Max); + make_visible(CP_duration); make_visible(Total_CP_duration); make_visible(init_CP_duration); + + if n_done_trials <1 && value(warmup_on) ==1 + CP_duration.value = value(init_CP_duration); + end + + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); %#ok<*NASGU> + + case {5,6,7} % + + make_visible(SettlingIn_time); make_visible(legal_cbreak); make_visible(cp_timeout); + make_visible(PreStim_time); make_invisible(PreStim_time_Min); make_invisible(PreStim_time_Max); + make_visible(A1_time); make_invisible(A1_time_Min); make_invisible(A1_time_Max); + make_visible(time_bet_aud1_gocue);make_invisible(time_bet_aud1_gocue_Min);make_invisible(time_bet_aud1_gocue_Max); + make_visible(CP_duration); make_visible(Total_CP_duration); make_visible(init_CP_duration); + + if n_done_trials <1 && warmup_on ==1 + CP_duration.value = value(init_CP_duration); + end + + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); %#ok<*NASGU> + + case 8 + + make_visible(SettlingIn_time); make_visible(legal_cbreak); make_visible(cp_timeout); + make_visible(PreStim_time); make_visible(PreStim_time_Min); make_visible(PreStim_time_Max); + make_visible(A1_time); make_visible(A1_time_Min); make_visible(A1_time_Max); + make_visible(time_bet_aud1_gocue);make_visible(time_bet_aud1_gocue_Min);make_visible(time_bet_aud1_gocue_Max); + make_visible(CP_duration); make_visible(Total_CP_duration); make_visible(init_CP_duration); + + if random_prego_time == 1 + time_range_go_cue = value(time_bet_aud1_gocue_Min):0.01:value(time_bet_aud1_gocue_Max); + time_bet_aud1_gocue.value = time_range_go_cue(randi([1, numel(time_range_go_cue)],1,1)); + end + + if random_A1_time == 1 + time_range_A1_time = value(A1_time_Min): 0.01 : value(A1_time_Max); + A1_time.value = time_range_A1_time(randi([1, numel(time_range_A1_time)],1,1)); + end + + if random_PreStim_time == 1 + time_range_PreStim_time = value(PreStim_time_Min) : 0.01 : value(PreStim_time_Max); + PreStim_time.value = time_range_PreStim_time(randi([1, numel(time_range_PreStim_time)],1,1)); + end + + if n_done_trials <1 && warmup_on ==1 + CP_duration.value = value(init_CP_duration); + else + CP_duration.value = value(SettlingIn_time) + value(A1_time) + value(PreStim_time) + value(time_bet_aud1_gocue); + end + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); %#ok<*NASGU> + + end + + case 'prepare_next_trial' + + % change the reward collection duration once we start with centre + % poke + if value(training_stage) >= 3 + Go_Sound.value = 1; + else + CP_duration.value = 0; + end + + if value(training_stage) == 8 % user setting + + make_visible(SettlingIn_time); + make_visible(PreStim_time); make_visible(PreStim_time_Min); make_visible(PreStim_time_Max); + make_visible(A1_time); make_visible(A1_time_Min); make_visible(A1_time_Max); + make_visible(time_bet_aud1_gocue);make_visible(time_bet_aud1_gocue_Min);make_visible(time_bet_aud1_gocue_Max); + make_visible(CP_duration); make_visible(Total_CP_duration); make_visible(init_CP_duration); + + if random_prego_time == 1 + time_range_go_cue = value(time_bet_aud1_gocue_Min):0.01:value(time_bet_aud1_gocue_Max); + time_bet_aud1_gocue.value = time_range_go_cue(randi([1, numel(time_range_go_cue)],1,1)); + end + + if random_A1_time == 1 + time_range_A1_time = value(A1_time_Min): 0.01 : value(A1_time_Max); + A1_time.value = time_range_A1_time(randi([1, numel(time_range_A1_time)],1,1)); + end + + if random_PreStim_time == 1 + time_range_PreStim_time = value(PreStim_time_Min) : 0.01 : value(PreStim_time_Max); + PreStim_time.value = time_range_PreStim_time(randi([1, numel(time_range_PreStim_time)],1,1)); + end + + if n_done_trials <1 && warmup_on ==1 + CP_duration.value = value(init_CP_duration); + else + CP_duration.value = value(SettlingIn_time) + value(A1_time) + value(PreStim_time) + value(time_bet_aud1_gocue); + end + + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); %#ok<*NASGU> + + end + + %% update violation, timeout, previous_sides, etc + was_viol=false; + was_timeout=false; + was_hit=false; + if n_done_trials>0 + if ~isempty(parsed_events) + if isfield(parsed_events,'states') + if isfield(parsed_events.states,'timeout_state') + was_timeout=rows(parsed_events.states.timeout_state)>0; + end + if isfield(parsed_events.states,'violation_state') + was_viol=rows(parsed_events.states.violation_state)>0; + end + end + + end + + violation_history.value=[violation_history(:); was_viol]; + timeout_history.value=[timeout_history(:); was_timeout]; + + ParamsSection(obj,'update_side_history'); + % Update Hit History + if ~was_viol && ~was_timeout + was_hit=rows(parsed_events.states.second_hit_state)==0; + hit_history.value=[hit_history(:); was_hit]; + + else + % There was a violation or timeout + hit_history.value=[hit_history(:); nan]; + end + end + + %% Choose Side for the Next Trial + + if ~isinf(MaxSame) && length(previous_sides) > MaxSame && ... + all(previous_sides(n_done_trials-MaxSame+1:n_done_trials) == previous_sides(n_done_trials)) %#ok + if previous_sides(end)=='l' + ThisTrial.value = 'RIGHT'; + else + ThisTrial.value = 'LEFT'; + end + + if previous_sides(end)=='r' + ThisTrial.value = 'LEFT'; + else + ThisTrial.value = 'RIGHT'; + end + + else + + if (rand(1)<=LeftProb) + ThisTrial.value='LEFT'; + + else + ThisTrial.value='RIGHT'; + end + + end + + +% %% Do the anti-bias with changing reward delivery +% % reset anti-bias +% left_wtr_mult.value=1; +% right_wtr_mult.value=1; +% if n_done_trials>ntrial_correct_bias && antibias_wtr_mult==1 +% hh=hit_history(n_done_trials-ntrial_correct_bias:n_done_trials); +% ps=previous_sides(n_done_trials-ntrial_correct_bias:n_done_trials); +% +% right_hit=nanmean(hh(ps=='r')); +% left_hit=nanmean(hh(ps=='l')); +% +% if abs(right_hit-left_hit) + + case 'get_left_prob' + x = value(LeftProb); + + case 'get_cp_history' + x = cell2mat(get_history(CP_duration)); + + case 'get_stimdur_history' + x = cell2mat(get_history(A1_time)); + + case 'update_side_history' + if strcmp(ThisTrial, 'LEFT') + ps=value(previous_sides); + ps(n_done_trials)='l'; + previous_sides.value=ps; + + else + ps=value(previous_sides); + ps(n_done_trials)='r'; + previous_sides.value=ps; + end + + case 'get_current_side' + if strcmp(ThisTrial, 'LEFT') + x = 'l'; %#ok + else + x = 'r'; + end + + + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'reinit' + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + +end + + diff --git a/Protocols/@ArpitCentrePokeTraining/PerformanceSummarySection.m b/Protocols/@ArpitCentrePokeTraining/PerformanceSummarySection.m new file mode 100644 index 00000000..b5ef9d0e --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/PerformanceSummarySection.m @@ -0,0 +1,173 @@ +function [x, y] = PerformanceSummarySection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2 + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end + x = varargin{1}; y = varargin{2}; + + % SoloParamHandle(obj, 'my_xyfig', 'value', [x y double(gcf)]); + + ToggleParam(obj, 'SummaryShow', 0, x, y, 'OnString', 'Summary Show', ... + 'OffString', 'Summary Hidden', 'TooltipString', 'Show/Hide Summary panel'); + set_callback(SummaryShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + oldx=x; oldy=y; parentfig=double(gcf); + + SoloParamHandle(obj, 'myfig', 'value', figure('Position', [ 226 122 1406 400], ... + 'closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', mfilename), 'saveable', 0); + set(double(gcf), 'Visible', 'off'); + + + % Create stage names + N_Stages = 8; + for i = 1:N_Stages + rowNames{i, 1} = sprintf('Stage %d', i); + end + % Create column names + N_params = 5; + columnNames = {'Stage/Param','Trials','TrialsToday','TrialsValid','ViolationRate','TimeoutRate'}; + + % Create Variable Names for each Edit Box + count = 0; + variable_names = cell(1,N_params * N_Stages); + for j = 1:N_params + for i = 1:N_Stages + count = count + 1; + variable_names(count) = {sprintf('stage_%i_%s',N_Stages - i + 1,columnNames{j+1})}; + end + end + + count = 0; + x = 100; y=100; + + for column_n = 1 : N_params + 1 + for rows_n = 1 : N_Stages + 1 + if column_n == 1 && rows_n < N_Stages + 1 + SubheaderParam(obj, 'title', sprintf('Stage%i',(N_Stages - rows_n + 1)), x, y); + next_row(y); + end + if column_n == 1 && rows_n == N_Stages + 1 + SubheaderParam(obj, 'title', columnNames{1}, x, y); + next_row(y); + end + if column_n > 1 && rows_n < N_Stages + 1 + count = count + 1; + NumeditParam(obj, variable_names{count}, 0, x, y); + next_row(y); + end + if rows_n == N_Stages + 1 && column_n > 1 + SubheaderParam(obj, 'title', columnNames{column_n}, x, y); + next_row(y); + end + end + next_column(x); y=100; + end + + % + x=oldx; y=oldy; + figure(parentfig); + + % ------------------------------------------------------------------ + % evaluate + % ------------------------------------------------------------------ + + case 'evaluate' + + pd.stage1_trials_total = value(stage_1_Trials); + pd.stage1_trials_today = value(stage_1_TrialsToday); + pd.stage1_trials_valid = value(stage_1_TrialsValid); + pd.stage1_violationrate = value(stage_1_ViolationRate); + pd.stage1_timeoutrate = value(stage_1_TimeoutRate); + + pd.stage2_trials_total = value(stage_2_Trials); + pd.stage2_trials_today = value(stage_2_TrialsToday); + pd.stage2_trials_valid = value(stage_2_TrialsValid); + pd.stage2_violationrate = value(stage_2_ViolationRate); + pd.stage2_timeoutrate = value(stage_2_TimeoutRate); + + pd.stage3_trials_total = value(stage_3_Trials); + pd.stage3_trials_today = value(stage_3_TrialsToday); + pd.stage3_trials_valid = value(stage_3_TrialsValid); + pd.stage3_violationrate = value(stage_3_ViolationRate); + pd.stage3_timeoutrate = value(stage_3_TimeoutRate); + + pd.stage4_trials_total = value(stage_4_Trials); + pd.stage4_trials_today = value(stage_4_TrialsToday); + pd.stage4_trials_valid = value(stage_4_TrialsValid); + pd.stage4_violationrate = value(stage_4_ViolationRate); + pd.stage4_timeoutrate = value(stage_4_TimeoutRate); + + pd.stage5_trials_total = value(stage_5_Trials); + pd.stage5_trials_today = value(stage_5_TrialsToday); + pd.stage5_trials_valid = value(stage_5_TrialsValid); + pd.stage5_violationrate = value(stage_5_ViolationRate); + pd.stage5_timeoutrate = value(stage_5_TimeoutRate); + + pd.stage6_trials_total = value(stage_6_Trials); + pd.stage6_trials_today = value(stage_6_TrialsToday); + pd.stage6_trials_valid = value(stage_6_TrialsValid); + pd.stage6_violationrate = value(stage_6_ViolationRate); + pd.stage6_timeoutrate = value(stage_6_TimeoutRate); + + pd.stage7_trials_total = value(stage_7_Trials); + pd.stage7_trials_today = value(stage_7_TrialsToday); + pd.stage7_trials_valid = value(stage_7_TrialsValid); + pd.stage7_violationrate = value(stage_7_ViolationRate); + pd.stage7_timeoutrate = value(stage_7_TimeoutRate); + + pd.stage8_trials_total = value(stage_8_Trials); + pd.stage8_trials_today = value(stage_8_TrialsToday); + pd.stage8_trials_valid = value(stage_8_TrialsValid); + pd.stage8_violationrate = value(stage_8_ViolationRate); + pd.stage8_timeoutrate = value(stage_8_TimeoutRate); + + + if nargout > 0 + x = pd; + end + + +%% Case close + case 'close' + set(value(myfig), 'Visible', 'off'); + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)) %#ok + delete(value(myfig)); + end + + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + %% Case hide + case 'hide' + SummaryShow.value = 0; + set(value(myfig), 'Visible', 'off'); + + %% Case show + case 'show' + SummaryShow.value = 1; + set(value(myfig), 'Visible', 'on'); + + %% Case Show_hide + case 'show_hide' + if value(SummaryShow) == 1 + set(value(myfig), 'Visible', 'on'); + else + set(value(myfig), 'Visible', 'off'); + end + +end + +end + diff --git a/Protocols/@ArpitCentrePokeTraining/SessionPerformanceSection.m b/Protocols/@ArpitCentrePokeTraining/SessionPerformanceSection.m new file mode 100644 index 00000000..f938825b --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/SessionPerformanceSection.m @@ -0,0 +1,127 @@ +% [x, y] = SessionPerformanceSection(obj, action, x,y) +% +% Reports overall performance. Uses training_stage from ParamsSection. +% +% PARAMETERS: +% ----------- +% +% action One of: +% 'init' To initialise the section and set up the GUI +% for it +% +% 'close' Delete all of this section's GUIs and data +% +% 'reinit' Delete all of this section's GUIs and data, +% and reinit, at the same position on the same +% figure as the original section GUI was placed. +% +% 'evaluate' Look at history and compute hit fraction, etc. +% +% x, y Only relevant to action = 'init'; they indicate the initial +% position to place the GUI at, in the current figure window +% +% RETURNS: +% -------- +% +% perf When action == 'init', returns x and y, pixel positions on +% the current figure, updated after placing of this section's GUI. +% When action == 'evaluate', returns a vector with elements +% [ntrials, violation_rate, left_hit_frac, right_hit_frac, hit_frac] +% +% + +% CDB, 23-March-2013 + +function [x, y] = SessionPerformanceSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + x = varargin{1}; y = varargin{2}; + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + + DispParam(obj, 'ntrials', 0, x, y,'label','Session Trials', 'TooltipString', ... + 'trials done in this session'); + next_row(y); + DispParam(obj, 'ntrials_stage', 0, x, y,'label','Stage Trials', 'TooltipString', ... + 'trials completed in this training stage'); + next_row(y); + DispParam(obj, 'ntrials_stage_today', 0, x, y,'label','Stage Trials Today', 'TooltipString', ... + 'trials completed in this training stage'); + next_row(y); + DispParam(obj, 'violation_percent', 0, x, y,'label','Session Violation', 'TooltipString', ... + 'Fraction of trials with a center poke violation in this session'); + next_row(y); + DispParam(obj, 'timeout_percent', 0, x, y,'label','Session Timeout', 'TooltipString', ... + 'Fraction of trials with timeout in this session'); + next_row(y); + DispParam(obj, 'violation_recent', 0, x, y,'label','Recent Violation', 'TooltipString', ... + 'Fraction of trials with a center poke violation in the past 20 trials'); + next_row(y); + DispParam(obj, 'timeout_recent', 0, x, y,'label','Recent Timeout', 'TooltipString', ... + 'Fraction of trials with a center poke violation in the past 20 trials'); + next_row(y); + DispParam(obj, 'violation_stage', 0, x, y,'label','Stage Violation', 'TooltipString', ... + 'Fraction of violations in this training stage'); + next_row(y); + DispParam(obj, 'timeout_stage', 0, x, y,'label','Stage Timeout', 'TooltipString', ... + 'Fraction of timeouts in this training stage'); + next_row(y); + SubheaderParam(obj, 'title', 'Overall Performance', x, y); + next_row(y, 1.5); + % SoloParamHandle(obj, 'previous_parameters', 'value', []); + + % ------------------------------------------------------------------ + % evaluate + % ------------------------------------------------------------------ + + case 'evaluate' + + if nargout > 0 + x = value(violation_percent); + y = value(timeout_percent); + end + + % ------------------------------------------------------------------ + % close + % ------------------------------------------------------------------ + + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % ------------------------------------------------------------------ + % reinit + % ------------------------------------------------------------------ + + case 'reinit' + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + +end + + diff --git a/Protocols/@ArpitCentrePokeTraining/SoundSection.m b/Protocols/@ArpitCentrePokeTraining/SoundSection.m new file mode 100644 index 00000000..0fd22e60 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/SoundSection.m @@ -0,0 +1,62 @@ + + +function [x, y] = SoundSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2 + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'SoundsShow', 0, x, y, 'OnString', 'Sounds', ... + 'OffString', 'Sounds', 'TooltipString', 'Show/Hide Sounds panel'); + set_callback(SoundsShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + oldx=x; oldy=y; parentfig=double(gcf); + + SoloParamHandle(obj, 'myfig', 'value', figure('Position', [100 100 560 440], ... + 'closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', mfilename), 'saveable', 0); + set(double(gcf), 'Visible', 'off'); + x=10;y=10; + + [x,y]=SoundInterface(obj,'add','ViolationSound',x,y,'Volume',0.01,'Freq',1000,'Duration',0.5); +% [x,y]=SoundInterface(obj,'add','ViolationSound',x,y,'Style','WhiteNoise','Volume',0.01); + [x,y]=SoundInterface(obj,'add','TimeoutSound',x,y,'Style','WhiteNoise','Volume',0.08,'Duration',0.5); + [x,y]=SoundInterface(obj,'add','RewardSound',x,y,'Style','Bups','Volume',1,'Freq',5,'Duration',1.5); + [x,y]=SoundInterface(obj,'add','ErrorSound',x,y,'Style','WhiteNoise','Volume',0.08); + next_column(x); + y=10; + [x,y]=SoundInterface(obj,'add','GoSound',x,y,'Style','Tone','Volume',0.01,'Freq',3000,'Duration',0.2); + SoundInterface(obj, 'disable', 'GoSound', 'Dur1'); + [x,y]=SoundInterface(obj,'add','SOneSound',x,y,'Style','WhiteNoise','Volume',0.007); + [x,y]=SoundInterface(obj,'add','STwoSound',x,y,'Style','WhiteNoise','Volume',0.07); + + x=oldx; y=oldy; + figure(parentfig); + + case 'hide' + SoundsShow.value = 0; + set(value(myfig), 'Visible', 'off'); + + case 'show' + SoundsShow.value = 1; + set(value(myfig), 'Visible', 'on'); + + case 'show_hide' + if SoundsShow == 1 + set(value(myfig), 'Visible', 'on'); % (defined by GetSoloFunctionArgs) + else + set(value(myfig), 'Visible', 'off'); + end + +end + \ No newline at end of file diff --git a/Protocols/@ArpitCentrePokeTraining/StimulusSection.m b/Protocols/@ArpitCentrePokeTraining/StimulusSection.m new file mode 100644 index 00000000..6ac08a0d --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/StimulusSection.m @@ -0,0 +1,588 @@ + + +function [x, y] = StimulusSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2 + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'StimulusShow', 0, x, y, 'OnString', 'Stimuli Show', ... + 'OffString', 'Stimuli Hidden', 'TooltipString', 'Show/Hide Stimulus panel'); + set_callback(StimulusShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + + oldx=x; oldy=y; parentfig=double(gcf); + + SoloParamHandle(obj, 'myfig', 'value', figure('closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'],... + 'MenuBar', 'none', 'Name', mfilename), 'saveable', 0); + screen_size = get(0, 'ScreenSize'); + set(value(myfig),'Position',[1 screen_size(4)-740, 400 400]); % put fig at top right + set(double(gcf), 'Visible', 'off'); + + SoundManagerSection(obj, 'declare_new_sound', 'StimAUD1') + SoloParamHandle(obj, 'thisstim', 'value', []); + SoloParamHandle(obj, 'thisstimlog', 'value', []); + SoloParamHandle(obj, 'h1', 'value', []); + + x = 10; y=5; + + next_row(y); + next_row(y); + PushbuttonParam(obj, 'refresh_stimuli', x,y , 'TooltipString', 'Instantiates the stimuli given the new set of parameters'); + set_callback(refresh_stimuli, {mfilename, 'plot_stimuli'}); + + + next_row(y); + next_row(y); + MenuParam(obj, 'Rule', {'S1>S_boundary Left','S1>S_boundary Right'}, ... + 'S1>S_boundary Left', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nThis buttom determines the rule\n', ... + '\n''S1>S_boundary Left'' means if Aud1 > Aud_boundry then reward will be delivered from the left water spout and if Aud1 < Aud_boundry then water comes from right\n',... + '\n''S1>S_boundary Right'' means if Aud1 < Aud_boundry then reward will be delivered from the left water spout and if Aud1 > Aud_boundry then water comes from right\n'])); + next_row(y, 1);next_row(y, 1); + + MenuParam(obj, 'Prob_Dist_Left', {'Uniform','Exponential','Half Normal','Normal','Sinusoidal','Anti Exponential','Anti Half Normal','Anti Sinusoidal'}, ... + 'Uniform', x, y,'label','Left Dist', 'labelfraction', 0.35, 'TooltipString', sprintf(['\n Different Probability Distributions for Category A.\n', ... + '\n''Normal - the mean is at mid point of range. Half Normal - truncated normal with mean at boundary.\n',... + '\n''Anti Half Normal - the mean/max is at the side edge of the range.\n',... + '\n''Sinosidal - using sine function instead of half normal and Anti Sinusoidal is when max is at the edge, same as anti half normal.\n'])); + set_callback(Prob_Dist_Left, {mfilename, 'Cal_Mean'}); + next_row(y); + DispParam(obj, 'mean_Left', 0.01, x,y,'label','μ Left','TooltipString','mean/max log stim value for the left side distribution'); + next_row(y); + DispParam(obj, 'sigma_Left', 0.01, x,y,'label','σ Left','TooltipString','sigma value(log) for normal distribution for the left side distribution'); + next_row(y); + NumeditParam(obj, 'sigma_range_Left', 1, x,y,'label','3σ Left','TooltipString',sprintf(['\n A way to reduce the range and increase more distribution towards mean\n', ... + '\n''signifying 3 Sigma (99.7%%) value for the left side distribution, \n',... + '\n''A value b/w range [0.2 - 1] is acceptable.'])); + set_callback(sigma_range_Left, {mfilename, 'Cal_Sigma'}); + next_row(y); next_row(y); + + MenuParam(obj, 'Prob_Dist_Right', {'Uniform','Exponential','Half Normal','Normal','Sinusoidal','Anti Exponential','Anti Half Normal','Anti Sinusoidal'}, ... + 'Uniform', x, y, 'label','Right Dist', 'labelfraction', 0.35, 'TooltipString', sprintf(['\n Different Probability Distributions for Category A.\n', ... + '\n''Normal - the mean is at mid point of range (side edge - boundary). Half Normal - truncated normal with mean at boundary.\n',... + '\n''Anti Half Normal - the mean/max is at the side edge of the range.\n',... + '\n''Sinosidal - using sine function instead of half normal and Anti Sinusoidal is when max is at the edge, same as anti half normal'])); + set_callback(Prob_Dist_Right, {mfilename, 'Cal_Mean'}); + next_row(y); + DispParam(obj, 'mean_Right', 0.01, x,y,'label','μ Right','TooltipString','mean/max log stim value for the right side distribution'); + next_row(y); + DispParam(obj, 'sigma_Right', 0.01, x,y,'label','σ Right','TooltipString','sigma value (log) for normal distribution for the right side distribution'); + next_row(y); + NumeditParam(obj, 'sigma_range_Right', 1, x,y,'label','3σ Right','TooltipString',sprintf(['\n A way to reduce the range and increase more distribution towards mean\n', ... + '\n''signifying 3 Sigma (99.7 %%) value for the right side distribution, \n',... + '\n''A value b/w range [0.2 - 1] is acceptable.'])); + set_callback(sigma_range_Right, {mfilename, 'Cal_Sigma'}); + next_row(y); + + next_column(x); + y=5; + next_row(y, 1) + MenuParam(obj, 'filter_type', {'GAUS','LPFIR', 'FIRLS','BUTTER','MOVAVRG','KAISER','EQUIRIP','HAMMING'}, ... + 'GAUS', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent filters. ''LPFIR'': lowpass FIR ''FIRLS'': Least square linear-phase FIR filter design\n', ... + '\n''BUTTER'': IIR Butterworth lowpass filter ''GAUS'': Gaussian filter (window)\n', ... + '\n''MOVAVRG'': Moving average FIR filter ''KAISER'': Kaiser-window FIR filtering\n', ... + '\n''EQUIRIP'':Eqiripple FIR filter ''HAMMING'': Hamming-window based FIR'])); + next_row(y); + NumeditParam(obj,'fcut',110,x,y,'label','fcut','TooltipString','Cut off frequency on the original white noise'); + next_row(y); + NumeditParam(obj,'lfreq',2000,x,y,'label','Modulator_LowFreq','TooltipString','Lower bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'hfreq',20000,x,y,'label','Modulator_HighFreq','TooltipString','Upper bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'minS1',0.007,x,y,'label','minS1','TooltipString','min sigma value for AUD1'); + set_callback(minS1, {mfilename, 'Cal_Boundary'}); + next_row(y); + NumeditParam(obj,'maxS1',0.05,x,y,'label','maxS1','TooltipString','max sigma value for AUD1'); + set_callback(maxS1, {mfilename, 'Cal_Boundary'}); + next_row(y); + DispParam(obj, 'A1_sigma', 0.01, x,y,'label','A1_sigma','TooltipString','Sigma value for the first stimulus'); + next_row(y); + NumeditParam(obj,'minF1',4,x,y,'label','minF1','TooltipString','min frequency value for AUD1'); + set_callback(minF1, {mfilename, 'Cal_Boundary'}); + next_row(y); + NumeditParam(obj,'maxF1',10,x,y,'label','maxF1','TooltipString','max frequency value for AUD1'); + set_callback(maxF1, {mfilename, 'Cal_Boundary'}); + next_row(y); + NumeditParam(obj,'volumeF1',0.007,x,y,'label','VolumeF1','TooltipString','volume of tone for AUD1'); + next_row(y); + DispParam(obj, 'A1_freq', 0.01, x,y,'label','A1_freq','TooltipString','Sigma value for the first stimulus'); + next_row(y); + DispParam(obj,'boundary',-3.9,x,y,'label','boundary(log)','TooltipString','decision boundary for categorisation (log)'); + next_row(y); + MenuParam(obj, 'mu_location', {'center', 'side'}, ... + 'center', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf('\nLocation of boundary')); + set_callback(mu_location, {mfilename, 'Cal_Boundary'}); + next_row(y); + ToggleParam(obj, 'frequency_categorization', 0, x,y,... + 'OnString', 'Frequency(Tone)',... + 'OffString', 'Amplitude(Noise)',... + 'TooltipString', sprintf('If on (black) then it enables the presentation of pure tones')); + set_callback(frequency_categorization, {mfilename, 'FrequencyCategorization'}); + make_invisible(maxF1);make_invisible(minF1);make_invisible(A1_freq);make_invisible(volumeF1); + next_row(y); + + % next_column(y) + SoloParamHandle(obj, 'stim_dist_fig', 'value', figure('closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', 'StimulusPlot'), 'saveable', 0); + set(double(gcf), 'Visible', 'off'); + ax = axes(value(stim_dist_fig),'Position',[0.1 0.1 0.8 0.8]); + ylabel('log_e A','FontSize',16,'FontName','Cambria Math'); + set(ax,'Fontsize',15) + xlabel('Sound Categorization','FontSize',16,'FontName','Cambria Math') + SoloParamHandle(obj, 'ax', 'saveable', 0, 'value', ax); + + StimulusSection(obj,'plot_stimuli'); + + x=oldx; y=oldy; + figure(parentfig); + + case 'prepare_next_trial' + if value(training_stage) > 4 && stimuli_on + StimulusSection(obj,'pick_current_stimulus'); + srate=SoundManagerSection(obj,'get_sample_rate'); + Fs=srate; + T=value(A1_time); + + if frequency_categorization + % produce the tone + A1_freq.value = value(thisstim); + A1 = value(thisstimlog(n_done_trials+1)); + dur1 = A1_time*1000; + bal=0; + freq1=A1_freq*1000; + vol=value(volumeF1); + RVol=vol*min(1,(1+bal)); + LVol=vol*min(1,(1-bal)); + t=0:(1/srate):(dur1/1000); + t = t(1:end-1); + tw=sin(t*2*pi*freq1); + RW=RVol*tw; + %w=[LW;RW]; + AUD1 = RW; + else + % produce noise pattern + A1_sigma.value = value(thisstim); + A1 = value(thisstimlog(n_done_trials+1)); + [rawA1, rawA2, normA1, normA2]=noisestim(1,1,T,value(fcut),Fs,value(filter_type)); + modulator=singlenoise(1,T,[value(lfreq) value(hfreq)],Fs,'BUTTER'); + AUD1=normA1(1:A1_time*srate).*modulator(1:A1_time*srate).*A1_sigma; + end + + if ~isempty(AUD1) + SoundManagerSection(obj, 'set_sound', 'StimAUD1', [AUD1'; AUD1']) + end + + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + % Plot current stimulus and move to saving stimulus history + + % if value(thisstimlog(n_done_trials+1)) > value(boundary)%value(numClass) + % set(value(h1), 'YData', value(A1), 'color',[0.4 0.8 0.1],'markerfacecolor',[0.4 0.8 0.1]); + % else + % set(value(h1), 'YData', value(A1), 'color',[0.8 0.4 0.1],'markerfacecolor',[0.8 0.4 0.1]); + % end + + if n_done_trials > 0 + if ~violation_history(n_done_trials) && ~timeout_history(n_done_trials) + StimulusSection(obj,'update_stimulus_history'); + else + StimulusSection(obj,'update_stimulus_history_nan'); + end + end + end + %% Case pick_current_stimulus + case 'pick_current_stimulus' + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + + if value(training_stage) == 4 % playing fixed sound during this stage + if (strcmpi(ThisTrial, 'LEFT') & strcmp(Rule,'S1>S_boundary Left')) | ... + (strcmpi(ThisTrial, 'RIGHT') & strcmp(Rule,'S1>S_boundary Right')) + + stim_i_log = stim_min_log; + else + stim_i_log = stim_max_log; + end + + else % will be playing stimuli from the distribution + + if strcmpi(ThisTrial, 'LEFT') + dist_type = value(Prob_Dist_Left); + dist_mean = value(mean_Left); + dist_sigma = value(sigma_Left); + dist_range_multiplier = value(sigma_range_Left); + if strcmp(Rule,'S1>S_boundary Left') + edge_max = stim_max_log; + edge_min = value(boundary); + edge_max = edge_min + dist_range_multiplier * (edge_max - edge_min); + else % the rule is S1>S_boundary Right + edge_min = stim_min_log; + edge_max = value(boundary); + edge_min = edge_max - dist_range_multiplier * (edge_max - edge_min); + end + + else % trial is Right + dist_type = value(Prob_Dist_Right); + dist_mean = value(mean_Right); + dist_sigma = value(sigma_Right); + dist_range_multiplier = value(sigma_range_Right); + if strcmp(Rule,'S1>S_boundary Right') + edge_max = stim_max_log; + edge_min = value(boundary); + edge_max = edge_min + dist_range_multiplier * (edge_max - edge_min); + else % the rule is S1>S_boundary Left + edge_min = stim_min_log; + edge_max = value(boundary); + edge_min = edge_max - dist_range_multiplier * (edge_max - edge_min); + end + end + + % Create a Stimuli with the selected Distribution and Side + switch dist_type + + case 'Uniform' % uniform distribution + stim_i_log = random('Uniform',edge_min,edge_max); + + case 'Exponential' + + lambda = 2.153 * (edge_max - edge_min); % In mice they are using 2.153 for normalized stim range [0 1] + stim_i_log = edge_min - 1; % preinitialize for while loop + while stim_i_log < edge_min || stim_i_log > edge_max + U = rand(1); + if edge_min == value(boundary) % exponentially decreasing + stim_i_log = edge_min + (-(1/lambda)*log(U)); % the distribution would be between range [0 1], so added the edge_min + else + stim_i_log = edge_max - (-(1/lambda)*log(U)); + end + end + + case 'Anti Exponential' + + lambda = 2.153 * (edge_max - edge_min); % In mice they are using 2.153 for normalized stim range [0 1] + stim_i_log = edge_min - 1; % preinitialize for while loop + while stim_i_log < edge_min || stim_i_log > edge_max + U = rand(1); + if edge_min == value(boundary) % exponentially decreasing + stim_i_log = edge_max - (-(1/lambda)*log(U)); % the distribution would be between range [0 1], so added the edge_min + else + stim_i_log = edge_min - ((1/lambda)*log(U)); + end + end + + case 'Half Normal' + if edge_min == value(boundary) + stim_i_log = random('Half Normal',dist_mean,dist_sigma); + while stim_i_log < edge_min || stim_i_log > edge_max + stim_i_log = random('Half Normal',dist_mean,dist_sigma); + end + else + stim_i_log = CreateSamples_from_Distribution('normal',dist_mean,dist_sigma,edge_min,edge_max,1); + end + + case 'Anti Half Normal' + + stim_i_log = CreateSamples_from_Distribution('normal',dist_mean,dist_sigma,edge_min,edge_max,1); + + case 'Normal' + stim_i_log = random('Normal',dist_mean,dist_sigma); + while stim_i_log < edge_min || stim_i_log > edge_max + stim_i_log = random('Normal',dist_mean,dist_sigma); + end + + case 'Sinusoidal' | 'Anti Sinusoidal' + + stim_i_log = CreateSamples_from_Distribution('Sinusoidal',dist_mean,dist_sigma,edge_min,edge_max,1); + + end + end + + thisstim.value=exp(stim_i_log); + thisstimlog(n_done_trials+1) = stim_i_log; + + %% Case plot stimuli distribution + case 'plot_stimuli' + + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + + dist_range_multiplier_left = value(sigma_range_Left); + dist_range_multiplier_right = value(sigma_range_Right); + + if strcmp(Rule,'S1>S_boundary Left') + edge_max_left = stim_max_log; + edge_min_left = value(boundary); + edge_max_left = edge_min_left + dist_range_multiplier_left * (edge_max_left - edge_min_left); + edge_max_right = value(boundary); + edge_min_right = stim_min_log; + edge_min_right = edge_max_right - dist_range_multiplier_right * (edge_max_right - edge_min_right); + + else % the rule is S1>S_boundary Right + + edge_min_left = stim_min_log; + edge_max_left = value(boundary); + edge_min_left = edge_max_left - dist_range_multiplier_left * (edge_max_left - edge_min_left); + edge_max_right = stim_max_log; + edge_min_right = value(boundary); + edge_max_right = edge_min_right + dist_range_multiplier_right * (edge_max_right - edge_min_right); + end + + + cla(value(ax)) + + StimuliDistribution_plot(value(ax),[stim_min_log, value(boundary), stim_max_log], Rule, ... + value(Prob_Dist_Left),value(mean_Left),value(sigma_Left),[edge_min_left edge_max_left], ... + value(Prob_Dist_Right),value(mean_Right),value(sigma_Right),[edge_min_right edge_max_right]); + + hold (value(ax),'on') + xline([stim_min_log value(boundary) stim_max_log],'-',{'Stim Min','Boundary','Stim Max'}); + + ylabel('log_e A','FontSize',16,'FontName','Cambria Math'); + set(value(ax),'Fontsize',15) + xlabel('Sound Categorization','FontSize',16,'FontName','Cambria Math') + + % plot(xd,stim_min_log,'s','MarkerSize',15,'MarkerEdgeColor',[0 0 0],'LineWidth',2) + % hold on + % plot(xd,stim_max_log,'s','MarkerSize',15,'MarkerEdgeColor',[0 0 0],'LineWidth',2) + % line([0,2], [value(boundary),value(boundary)]); + % axis square + % set(value(ax),'ytick',([stim_min_log, stim_max_log]),'xtick',xd); + % set(value(ax),'yticklabel',([stim_min, stim_max]),'xticklabel','S1'); + % ylabel('\sigma_1 in log scale','FontSize',16,'FontName','Cambria Math'); + % set(value(ax),'Fontsize',15) + % xlabel('S1','FontSize',16,'FontName','Cambria Math') + + + %% Boundary Calculate + case 'Cal_Boundary' + if frequency_categorization + val_boundary = (log(value(minF1)) + log(value(maxF1)))/2; + min_val = log(value(minF1)); + else + val_boundary = (log(value(minS1)) + log(value(maxS1)))/2; + min_val = log(value(minS1)); + end + if strcmp(mu_location,'center') + boundary.value = val_boundary; + elseif strcmp(mu_location,'side') + boundary.value = (min_val + val_boundary)/2; + end + + StimulusSection(obj,'Cal_Mean'); % update the mean and sigma values for each side + + %% Updated Mean/Max for Each Side based upon Distribution Selected + case 'Cal_Mean' + + if frequency_categorization + edge_max = log(value(maxF1)); + edge_min = log(value(minF1)); + else + edge_max = log(value(maxS1)); + edge_min = log(value(minS1)); + end + + % Calculation for Left Side + + % Sigma + + dist_sigma_multiplier = value(sigma_range_Left); + if dist_sigma_multiplier < 0.2 + dist_sigma_multiplier = 0.2; + end + if dist_sigma_multiplier > 1 + dist_sigma_multiplier = 1; + end + + if strcmp(Rule,'S1>S_boundary Left') + edge_min_left = value(boundary); + edge_max_left = edge_min_left + dist_sigma_multiplier * (edge_max - edge_min_left); + else % the rule is S1>S_boundary Right + edge_max_left = value(boundary); + edge_min_left = edge_max_left - dist_sigma_multiplier * (edge_max_left - edge_min); + end + + sigma_Left.value = (edge_max_left - edge_min_left) / 3; % as we asked user to provide 3 sigma + + % Mean + if matches(value(Prob_Dist_Left),{'Uniform','Half Normal','Sinusoidal','Exponential'}) + mean_Left.value = value(boundary); + else + if strcmp(Rule,'S1>S_boundary Left') + if matches(Prob_Dist_Left,{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Left.value = edge_max_left; + elseif matches(Prob_Dist_Left,'Normal') + mean_Left.value = (edge_max_left + value(boundary))/2; + end + else + if matches(value(Prob_Dist_Left),{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Left.value = edge_min_left; + elseif matches(value(Prob_Dist_Left),'Normal') + mean_Left.value = (edge_min_left + value(boundary))/2; + end + end + end + + % Calculation for Right Side + + % Sigma + dist_sigma_multiplier = value(sigma_range_Right); + if dist_sigma_multiplier < 0.2 + dist_sigma_multiplier = 0.2; + end + if dist_sigma_multiplier > 1 + dist_sigma_multiplier = 1; + end + + if strcmp(Rule,'S1>S_boundary Right') + edge_min_right = value(boundary); + edge_max_right = edge_min_right + dist_sigma_multiplier * (edge_max - edge_min_right); + else % the rule is S1>S_boundary Right + edge_max_right = value(boundary); + edge_min_right = edge_max_right - dist_sigma_multiplier * (edge_max_right - edge_min); + end + + sigma_Right.value = (edge_max_right - edge_min_right) / 3; % as we asked user to provide 3 sigma + + + % Mean + if matches(value(Prob_Dist_Right),{'Uniform','Half Normal','Sinusoidal','Exponential'}) + mean_Right.value = value(boundary); + else + if strcmp(Rule,'S1>S_boundary Right') + if matches(value(Prob_Dist_Right),{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Right.value = edge_max_right; + elseif matches(value(Prob_Dist_Right),'Normal') + mean_Right.value = (edge_max_right + value(boundary))/2; + end + else + if matches(value(Prob_Dist_Right),{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Right.value = edge_min_right; + elseif matches(Prob_Dist_Right,'Normal') + mean_Right.value = (edge_min_right + value(boundary))/2; + end + end + end + + %% Calculate Sigma + case 'Cal_Sigma' + + if frequency_categorization + edge_max = log(value(maxF1)); + edge_min = log(value(minF1)); + else + edge_max = log(value(maxS1)); + edge_min = log(value(minS1)); + end + + % Calculation for Left Side Sigma + dist_sigma_multiplier = value(sigma_range_Left); + if dist_sigma_multiplier < 0.2 + dist_sigma_multiplier = 0.2; + end + if dist_sigma_multiplier > 1 + dist_sigma_multiplier = 1; + end + sigma_Left.value = (dist_sigma_multiplier * (edge_max - edge_min)) / 3; % as we asked user to provide 3 sigma + + % Calculation for Right Side Sigma + dist_sigma_multiplier = value(sigma_range_Right); + if dist_sigma_multiplier < 0.2 + dist_sigma_multiplier = 0.2; + end + if dist_sigma_multiplier > 1 + dist_sigma_multiplier = 1; + end + sigma_Right.value = (dist_sigma_multiplier * (edge_max - edge_min)) / 3; % as we asked user to provide 3 sigma + + + %% Case frequency ON + case 'FrequencyCategorization' + if frequency_categorization == 1 + make_visible(maxF1);make_visible(minF1);make_visible(A1_freq);make_visible(volumeF1); + make_invisible(maxS1);make_invisible(minS1);make_invisible(A1_sigma); + make_invisible(fcut);make_invisible(lfreq);make_invisible(hfreq); make_invisible(filter_type); + else + make_visible(maxS1);make_visible(minS1);make_visible(A1_sigma); + make_visible(fcut);make_visible(lfreq);make_visible(hfreq); make_visible(filter_type); + make_invisible(maxF1);make_invisible(minF1);make_invisible(A1_freq); make_visible(volumeF1); + end + + StimulusSection(obj,'Cal_Boundary'); % update the boundary + StimulusSection(obj,'plot_stimuli'); + + %% Case get_stimuli + % case 'get_stimuli' + % if nargout>0 + % x=value(S1); + % end + + + %% Case close + case 'close' + set(value(myfig), 'Visible', 'off'); + set(value(stim_dist_fig), 'Visible', 'off'); + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)) %#ok + delete(value(myfig)); + end + if exist('stim_dist_fig', 'var') && isa(stim_dist_fig, 'SoloParamHandle') && ishandle(value(stim_dist_fig)) %#ok + delete(value(stim_dist_fig)); + end + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'update_stimulus_history' + ps=value(stimulus_history); + ps(n_done_trials)=value(thisstimlog(n_done_trials)); + stimulus_history.value=ps; + + case 'update_stimulus_history_nan' + ps=value(stimulus_history); + ps(n_done_trials)=value(thisstimlog(n_done_trials));%nan; + stimulus_history.value=ps; + + %% Case hide + case 'hide' + StimulusShow.value = 0; + set(value(myfig), 'Visible', 'off'); + set(value(stim_dist_fig), 'Visible', 'off'); + + %% Case show + case 'show' + StimulusShow.value = 1; + set(value(myfig), 'Visible', 'on'); + set(value(stim_dist_fig), 'Visible', 'on'); + + %% Case Show_hide + case 'show_hide' + if StimulusShow == 1 + set(value(myfig), 'Visible', 'on'); + set(value(stim_dist_fig), 'Visible', 'on');%#ok (defined by GetSoloFunctionArgs) + else + set(value(myfig), 'Visible', 'off'); + set(value(stim_dist_fig), 'Visible', 'off'); + end + +end + +end diff --git a/Protocols/@ArpitCentrePokeTraining/TrainingStageParamsSection.m b/Protocols/@ArpitCentrePokeTraining/TrainingStageParamsSection.m new file mode 100644 index 00000000..f8870d8f --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/TrainingStageParamsSection.m @@ -0,0 +1,140 @@ +function [x, y] = TrainingStageParamsSection(obj, action, x,y) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + next_row(y); next_row(y); next_row(y);next_row(y); + + switch value(training_stage) + + case 1 + % STAGE RUNNING PARAMETERS + NumeditParam(obj, 'trial_oppSide', 0, x, y,'TooltipString','total trials in opposide side this stage'); next_row(y); + NumeditParam(obj, 'Go_Sound_Start', 200, x, y,'TooltipString','trials after which Go/Reward Sound should start'); next_row(y); + SubheaderParam(obj, 'title', 'Stage Params', x, y); + % next_row(y); + % COMPLETION TEST PARAMETERS + NumeditParam(obj, 'total_trials', 400, x, y,'label','Trials','TooltipString','total trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'total_trials_opp', 200, x, y,'label','Trials_Opp','TooltipString','total trials with opposide side option in this stage for its completion'); next_row(y); + SubheaderParam(obj, 'title', 'Completion Params', x, y); next_row(y); + + case 2 + % STAGE RUNNING PARAMETERS + NumeditParam(obj, 'trial_oppSide', 0, x, y,'TooltipString','total trials in opposide side this stage'); next_row(y); + NumeditParam(obj, 'max_rColl_dur', 200, x, y,'label','T_Max_RCollect','TooltipString','User given max water collect time(code will optimize for value below this)'); next_row(y); + NumeditParam(obj, 'min_rColl_dur', 60, x, y,'label','T_Min_RCollect','TooltipString','User given min water collect time(code will optimize for value above this)'); next_row(y); + SubheaderParam(obj, 'title', 'Stage Params', x, y); next_row(y); + % COMPLETION TEST PARAMETERS + NumeditParam(obj, 'total_trials', 400, x, y,'label','Trials','TooltipString','total trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'total_trials_opp', 200, x, y,'label','Trials_Opp','TooltipString','total trials with opposide side option in this stage for its completion'); next_row(y); + SubheaderParam(obj, 'title', 'Completion Params', x, y);next_row(y); + + case 3 % no completion test required + % STAGE RUNNING PARAMETERS + NumeditParam(obj, 'last_session_CP', 0, x, y,'TooltipString','total cp reached by last session'); next_row(y); + DispParam(obj, 'max_CP', 0.3, x, y,'label','CP_Dur_Max','TooltipString','max CP duration being trained in this stage'); next_row(y); + NumeditParam(obj, 'CPfraction_inc', 0.001, x, y,'label','CP_frac_Increase','TooltipString','CP duration is increased by this fraction'); next_row(y); + SubheaderParam(obj, 'title', 'Stage Params', x, y); next_row(y); + + case 4 + % STAGE RUNNING PARAMETERS + NumeditParam(obj, 'last_session_CP', 0, x, y,'TooltipString','total cp reached by last session'); next_row(y); + NumeditParam(obj, 'max_CP', 1.5, x, y,'label','CP_Dur_Max','TooltipString','max CP duration being trained in this stage'); next_row(y); + NumeditParam(obj, 'CPfraction_inc', 0.001, x, y,'label','CP_frac_Increase','TooltipString','CP duration is increased by this fraction'); next_row(y); + SubheaderParam(obj, 'title', 'Stage Params', x, y); next_row(y); + % COMPLETION TEST PARAMETERS + NumeditParam(obj, 'recent_violation', 0.15, x, y,'label','Recent_ViolateRate','TooltipString','violation rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'recent_timeout', 0.15, x, y,'label','Recent_TimeoutRate','TooltipString','timeout rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'stage_violation', 0.35, x, y,'label','Stage_ViolationRate','TooltipString','overall violation rate in this stage for its completion'); next_row(y); + SubheaderParam(obj, 'title', 'Completion Params', x, y);next_row(y); + + case 5 + % STAGE RUNNING PARAMETERS + NumeditParam(obj, 'last_session_CP', 0, x, y,'TooltipString','total cp reached by last session'); next_row(y); + NumeditParam(obj, 'max_CP', 3, x, y,'label','CP_Dur_Max','TooltipString','max CP duration being trained in this stage'); next_row(y); + NumeditParam(obj, 'CPfraction_inc', 0.002, x, y,'label','CP_frac_Increase','TooltipString','CP duration is increased by this fraction'); next_row(y); + NumeditParam(obj, 'min_CP', 1.5, x, y,'label','CP_Dur_Min','TooltipString','min CP duration being trained in this stage'); next_row(y); + NumeditParam(obj, 'starting_CP', 0.3, x, y,'TooltipString','min CP duration (minus the settling-in time) during warm up'); next_row(y); + NumeditParam(obj, 'warm_up_trials', 10, x, y,'label','Warmup Trials','TooltipString','N trials for warmup'); next_row(y); + NumeditParam(obj, 'stim_dur', 0.4, x, y,'label','A1 Dur','TooltipString','This stage A1 duration'); next_row(y); + SubheaderParam(obj, 'title', 'Stage Params', x, y); next_row(y); + % COMPLETION TEST PARAMETERS + NumeditParam(obj, 'recent_violation', 0.15, x, y,'label','Recent_ViolateRate','TooltipString','violation rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'recent_timeout', 0.15, x, y,'label','Recent_TimeoutRate','TooltipString','timeout rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'stage_violation', 0.35, x, y,'label','Stage_ViolationRate','TooltipString','overall violation rate in this stage for its completion'); next_row(y); + NumeditParam(obj, 'total_trials', 600, x, y,'label','Trials','TooltipString','total trials in this stage for its completion'); next_row(y); + SubheaderParam(obj, 'title', 'Completion Params', x, y);next_row(y); + + case 6 + % STAGE RUNNING PARAMETERS + NumeditParam(obj, 'last_session_CP', 0, x, y,'TooltipString','total cp reached by last session'); next_row(y); + NumeditParam(obj, 'max_CP', 3, x, y,'label','CP_Dur_Max','TooltipString','max CP duration being trained in this stage'); next_row(y); + NumeditParam(obj, 'starting_CP', 0.3, x, y,'TooltipString','min CP duration (minus the settling-in time) during warm up'); next_row(y); + NumeditParam(obj, 'warm_up_trials', 20, x, y,'label','Warmup Trials','TooltipString','N trials for warmup'); next_row(y); + NumeditParam(obj, 'max_prestim', 1, x, y,'label','Pre-Stim Max','TooltipString','This stage Max Time, before starting the stimulus'); next_row(y); + NumeditParam(obj, 'min_prestim', 0.2, x, y,'label','Pre-Stim Min','TooltipString','This stage Min Time, before starting the stimulus'); next_row(y); + NumeditParam(obj, 'stim_dur', 0.4, x, y,'label','A1 Dur','TooltipString','This stage A1 duration'); next_row(y); + SubheaderParam(obj, 'title', 'Stage Params', x, y); next_row(y); + % COMPLETION TEST PARAMETERS + NumeditParam(obj, 'recent_violation', 0.15, x, y,'label','Recent_ViolateRate','TooltipString','violation rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'recent_timeout', 0.15, x, y,'label','Recent_TimeoutRate','TooltipString','timeout rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'stage_violation', 0.25, x, y,'label','Stage_ViolationRate','TooltipString','overall violation rate in this stage for its completion'); next_row(y); + NumeditParam(obj, 'total_trials', 600, x, y,'label','Trials','TooltipString','total trials in this stage for its completion'); next_row(y); + SubheaderParam(obj, 'title', 'Completion Params', x, y);next_row(y); + + case 7 + % STAGE RUNNING PARAMETERS + NumeditParam(obj, 'last_session_CP', 0, x, y,'TooltipString','total cp reached by last session'); next_row(y); + NumeditParam(obj, 'max_CP', 3, x, y,'label','CP_Dur_Max','TooltipString','max CP duration being trained in this stage'); next_row(y); + NumeditParam(obj, 'starting_CP', 0.3, x, y,'TooltipString','min CP duration (minus the settling-in time) during warm up'); next_row(y); + NumeditParam(obj, 'warm_up_trials', 20, x, y,'label','Warmup Trials','TooltipString','N trials for warmup'); next_row(y); + NumeditParam(obj, 'max_prestim', 1, x, y,'label','Pre-Stim Max','TooltipString','This stage Max Time, before starting the stimulus'); next_row(y); + NumeditParam(obj, 'min_prestim', 0.2, x, y,'label','Pre-Stim Min','TooltipString','This stage Min Time, before starting the stimulus'); next_row(y); + NumeditParam(obj, 'max_prego', 1, x, y,'label','Max A1-GoCue time','TooltipString','This stage Max time, between the end of the stimulus and the go cue'); next_row(y); + NumeditParam(obj, 'min_prego', 0.2, x, y,'label','Min A1-GoCue time','TooltipString','This stage Min time, between the end of the stimulus and the go cue'); next_row(y); + NumeditParam(obj, 'stim_dur', 0.4, x, y,'label','A1 Dur','TooltipString','This stage A1 duration'); next_row(y); + SubheaderParam(obj, 'title', 'Stage Params', x, y); next_row(y); + % COMPLETION TEST PARAMETERS + NumeditParam(obj, 'recent_violation', 0.15, x, y,'label','Recent_ViolateRate','TooltipString','violation rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'recent_timeout', 0.15, x, y,'label','Recent_TimeoutRate','TooltipString','timeout rate for last 20 trials in this stage for its completion'); next_row(y); + NumeditParam(obj, 'stage_violation', 0.20, x, y,'label','Stage_ViolationRate','TooltipString','overall violation rate in this stage for its completion'); next_row(y); + NumeditParam(obj, 'total_trials', 600, x, y,'label','Trials','TooltipString','total trials in this stage for its completion'); next_row(y); + SubheaderParam(obj, 'title', 'Completion Params', x, y); next_row(y); + end + SubheaderParam(obj, 'title', 'AUTOMATED TRAINING STAGE', x, y); + + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'reinit' + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + + +end + +end diff --git a/Protocols/@ArpitCentrePokeTraining/poke_colors.m b/Protocols/@ArpitCentrePokeTraining/poke_colors.m new file mode 100644 index 00000000..a235c9b5 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/poke_colors.m @@ -0,0 +1,8 @@ +function PC = poke_colors(obj) + + +PC = struct( ... + 'L', 0.6*[1 0.66 0], ... + 'C', [0 0 0], ... + 'R', 0.9*[1 0.66 0]); + diff --git a/Protocols/@ArpitCentrePokeTraining/private/CreateSamples_from_Distribution.m b/Protocols/@ArpitCentrePokeTraining/private/CreateSamples_from_Distribution.m new file mode 100644 index 00000000..23cdd90b --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/CreateSamples_from_Distribution.m @@ -0,0 +1,142 @@ +function samples = CreateSamples_from_Distribution(distributiontype,mu_val,sigma_val,range_min,range_max,n_samples) +%% + + +% samples = CreateSamples_from_Distribution('Anti_Half_Normal',log(4),log(4),log(10),1000,) + +% pairs = { ... +% 'Distribution_Type' 'Sinusoidal'; ... +% 'Range' [log(0.007) log(0.05)] ; ... +% 'Mean_val' mean([log(0.007),log(0.05)]) ; ... +% 'N_Samples' 1; ... +% 'sigma_val' (log(0.05) - log(0.007))/3 +% }; parseargs(varargin, pairs); + +%% + + +% Ensure the range is valid +if range_max <= range_min + error('Upper bound range_max must be greater than lower bound range_min.'); +end + +if contains(distributiontype,'normal','IgnoreCase',true) + if mu_val == range_min + distributiontype = 'Anti_Half_Normal'; + else + distributiontype = 'Half_Normal'; + end + +elseif contains(distributiontype,'sinusoidal','IgnoreCase',true) + if mu_val == range_min + distributiontype = 'Anti_Sinusoidal'; + else + distributiontype = 'Sinusoidal'; + end +end + +switch distributiontype + + case 'Sinusoidal' + + pdf_func = @(x) sin((pi / 2) * ((x - range_min) / (range_max - range_min))); % Define the PDF (Peak at max value) + A_val = 1 / integral(pdf_func, range_min, range_max); % Compute Normalization Constant A + pdf_func = @(x) A_val * sin((pi / 2) * ((x - range_min) / (range_max - range_min))); % the normalized PDF + cdf_func = @(x) integral(@(t) pdf_func(t), range_min, x); % Define the CDF as the cumulative integral of the normalized PDF + inv_cdf_func = @(u) range_min + (range_max - range_min) * (2/pi) * acos(1 - u); % the Inverse CDF + sample_single_point = @() inv_cdf_func(rand()); % Function for Sampling a Single Point + samples = arrayfun(@(x) sample_single_point(), 1:n_samples);% Sample Random Point + + + case 'Anti_Sinusoidal' + + pdf_func = @(x) sin((pi / 2) * ((range_max - x) / (range_max - range_min))); % Define the PDF (Peak at min value) + A_val = 1 / integral(pdf_func, range_min, range_max); % Compute Normalization Constant A + pdf_func = @(x) A_val * sin((pi / 2) * ((range_max - x) / (range_max - range_min))); % Define the normalized PDF + cdf_func = @(x) integral(@(t) pdf_func(t), range_min, x); % Define the CDF as the cumulative integral of the normalized PDF + inv_cdf_func = @(u) range_max - (range_max - range_min) * (2/pi) * acos(1 - u); % the Inverse CDF + sample_single_point = @() inv_cdf_func(rand()); % Function for Sampling a Single Point + samples = arrayfun(@(x) sample_single_point(), 1:n_samples); % Sample Random Points + + + case 'Anti_Half_Normal' + + pdf_func = @(x, A) A * exp(- (x - mu_val).^2 / (2 * sigma_val^2)); % Define the Half-Normal PDF (Peak at min value) + A_den = integral(@(x) exp(- (x - mu_val).^2 / (2 * sigma_val^2)), range_min, range_max); % Compute Normalization Constant A + A_sol = 1 / A_den; % Ensuring total probability integrates to 1 + + pdf_func = @(x) A_sol * exp(- (x - mu_val).^2 / (2 * sigma_val^2)); % Define the normalized PDF function + + cdf_func = @(x) (erf((x - mu_val) / (sqrt(2) * sigma_val)) - erf((range_min - mu_val) / (sqrt(2) * sigma_val))) ... % CDF formula for truncated normal distribution + / (erf((range_max - mu_val) / (sqrt(2) * sigma_val)) - erf((range_min - mu_val) / (sqrt(2) * sigma_val))); + + inv_cdf_func = @(u) mu_val + sqrt(2) * sigma_val * erfinv( ... % Inverse CDF function (solving for x in terms of U) + u * (erf((range_max - mu_val) / (sqrt(2) * sigma_val)) - erf((range_min - mu_val) / (sqrt(2) * sigma_val))) ... + + erf((range_min - mu_val) / (sqrt(2) * sigma_val))); + + sample_single_point = @() inv_cdf_func(rand()); % Function for Sampling a Single Point + samples = arrayfun(@(x) sample_single_point(), 1:n_samples); % Sample Random Points + + + case 'Half_Normal' + + pdf_func = @(x, A) A * exp(- (range_max - x).^2 / (2 * sigma_val^2)); % Define the Half-Normal PDF (Peak at max value) + A_den = integral(@(x) exp(- (range_max - x).^2 / (2 * sigma_val^2)), range_min, range_max); % Compute Normalization Constant A + A_sol = 1 / A_den; % Ensuring total probability integrates to 1 + + pdf_func = @(x) A_sol * exp(- (range_max - x).^2 / (2 * sigma_val^2)); % Define the final normalized PDF function + + cdf_func = @(x) (erf((x - range_max) / (sqrt(2) * sigma_val)) - erf((range_min - range_max) / (sqrt(2) * sigma_val))) ... % CDF formula for truncated normal distribution + / (erf((range_max - range_max) / (sqrt(2) * sigma_val)) - erf((range_min - range_max) / (sqrt(2) * sigma_val))); + + inv_cdf_func = @(u) range_max - sqrt(2) * sigma_val * erfinv( ... % Inverse CDF function (solving for x in terms of U) + u * (erf((range_max - range_min) / (sqrt(2) * sigma_val)))); + + sample_single_point = @() inv_cdf_func(rand()); % Function for Sampling a Single Point + samples = arrayfun(@(x) sample_single_point(), 1:n_samples); % Sample Random Points + +end + +% check all the samples are within the range (excluding the range) +out_of_range_samples = find(samples <= range_min & samples >= range_max); +if length(out_of_range_samples) >= 1 + samples_replacement = arrayfun(@(x) sample_single_point(), 1:length(out_of_range_samples)); % + samples(out_of_range_samples) = samples_replacement; +end +% Recheck and if its again out of range then replace it in while loop ( was +% trying to avoid it) +out_of_range_samples_new = find(samples <= range_min & samples >= range_max); +if length(out_of_range_samples) >= 1 + for i = length(out_of_range_samples_new) + sample_new = arrayfun(@(x) sample_single_point(), 1); + while sample_new <= range_min || sample_new >= range_max + sample_new = arrayfun(@(x) sample_single_point(), 1); + end + samples(out_of_range_samples_new(i)) = sample_new; + end +end + +% %% Debugging - Validate CDF and Inverse CDF + + % % Create a fine grid of x values + % x_vals = linspace(range_min, range_max, 1000); + % % Evaluate PDF and CDF + % pdf_vals = pdf_func(x_vals); + % cdf_vals = cdf_func(x_vals); + % % Generate uniform random samples and apply inverse CDF + % U = rand(10000,1); + % sampled_x = inv_cdf_func(U); + % %% Plot PDF and CDF + % figure; + % % PDF Plot + % subplot(2,1,1); + % plot(x_vals, pdf_vals, 'b', 'LineWidth', 2); + % xlabel('x'); ylabel('PDF'); + % title('Half-Normal PDF'); + % grid on; + % % CDF Plot + % subplot(2,1,2); + % plot(x_vals, cdf_vals, 'r', 'LineWidth', 2); + % xlabel('x'); ylabel('CDF'); + % title('Cumulative Distribution Function (CDF)'); + % grid on; \ No newline at end of file diff --git a/Protocols/@ArpitCentrePokeTraining/private/LoadSettingGUI.m b/Protocols/@ArpitCentrePokeTraining/private/LoadSettingGUI.m new file mode 100644 index 00000000..84cf7e1a --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/LoadSettingGUI.m @@ -0,0 +1,196 @@ +% REMOVE THE SETTINGS/PRE OPENED GUI + if exist('BpodSystem','var') == 1 + if ~isempty(BpodSystem) + flush + end + end + +% START FRESH & SET THE REQUIRED DIRECTORY + +home_drive = getenv('HOMEDRIVE'); +current_dir = cd; +ratter_dir = extractBefore(current_dir,'ratter'); +ratter_modules_dir1 = fullfile(ratter_dir, 'ratter', 'ExperPort'); +ratter_modules_dir2 = fullfile(home_drive, 'ratter', 'ExperPort'); +if exist("ratter_modules_dir1",'dir') == 7 + cd(ratter_modules_dir1); +else + cd(ratter_modules_dir2); +end + +% START BPOD +try + Bpod('COM5') +catch + + % Identify the bpod port before starting + bpodPort = getOrSetCOMPort(); + disp(['Using COM Port: ' bpodPort]); + % Bpod(); + Bpod(bpodPort); +end + +newstartup; + +% PRESENT THE USER WITH THE CHOICE OF EXPERIMENTER/RAT + +% Let's identify all the experimenter and their respective rats +try + Experimenter_Name = bdata('select distinct experimenter from rats where extant=1 order by experimenter'); +catch %#ok + disp('ERROR: Unable to connect to MySQL Server'); + Experimenter_Name = ''; +end + +rig_id = bSettings('get','RIGS','Rig_ID'); % the ID of this rig + +if ~isempty(Experimenter_Name) + for n_exp = 1:numel(Experimenter_Name) + % Finding all training rat for this experimenter + % ratnames = bdata(['select ratname from rats where experimenter="',Experimenter_Name{n_exp},'" and extant=1']); + ratnames = bdata(['select ratname from rats where experimenter="',Experimenter_Name{n_exp},'" and in_training=1']); + Rat_Name_all{n_exp} = sortrows(strtrim(ratnames)); + + % Additionaally finding rats for this experimenter with its schedule on this rig + [rats,slots] = bdata(['select ratname, timeslot from scheduler where experimenter="',... + Experimenter_Name{n_exp},'" and rig="',num2str(rig_id),'"']); + + + end +end + +Exp_Rat_Map = containers.Map(Experimenter_Name, Rat_Name_all); + + +% Create figure +fig = figure('Name', 'Dynamic Button GUI', ... + 'Position', [500, 300, 600, 400], ... + 'MenuBar', 'none', ... + 'NumberTitle', 'off', ... + 'Color', [0.9 0.9 0.9]); + +% Create main buttons with resize callback +buttonHandles = createButtons(fig, Experimenter_Name, @(src, ~) mainButtonCallback(src, Exp_Rat_Map, fig)); + +% Setup resize behavior +set(fig, 'ResizeFcn', @(src, ~) resizeButtons(src, buttonHandles)); + +% === Callback for first-level buttons === +function mainButtonCallback(src, map, figHandle) +experimenter = src.String; +subOptions = map(experimenter); +clf(figHandle); % Clear previous buttons + +% Create new buttons and assign second-level callback +newButtons = createButtons(figHandle, subOptions, @(src2, ~) subButtonCallback(src2,experimenter,figHandle)); + +% Update resize function +set(figHandle, 'ResizeFcn', @(src, ~) resizeButtons(src, newButtons)); +end + +% === Final action when second-level button is clicked === +function subButtonCallback(src,experimenter_name,figHandle) +rat_name = src.String; +close(figHandle); +runrats('init'); +pause(3); +runrats('update exp_rat_userclick',experimenter_name,rat_name); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% HELPER FUNCTIONS + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% === Create buttons dynamically and return handles === +function btns = createButtons(figHandle, optionList, callbackFcn) +delete(findall(figHandle, 'Type', 'uicontrol')); % Clear any existing buttons + +n = numel(optionList); +btns = gobjects(1, n); + +for i = 1:n + btns(i) = uicontrol(figHandle, ... + 'Style', 'pushbutton', ... + 'Units', 'normalized', ... + 'String', optionList{i}, ... + 'FontSize', 14, ... + 'FontWeight', 'bold', ... + 'BackgroundColor', [0.7 0.8 1], ... + 'Callback', callbackFcn); +end + +resizeButtons(figHandle, btns); % Initial layout +end + +% === Resize button layout responsively === +function resizeButtons(figHandle, buttons) +n = numel(buttons); +spacing = 0.02; +totalSpacing = spacing * (n + 1); +btnHeight = (1 - totalSpacing) / n; +btnWidth = 0.8; +x = (1 - btnWidth) / 2; + +for i = 1:n + y = 1 - spacing - i * (btnHeight + spacing) + spacing; + set(buttons(i), 'Position', [x, y, btnWidth, btnHeight]); +end +end + + +function comPort = getOrSetCOMPort() +configFile = fullfile(fileparts(mfilename('fullpath')), 'com_config.mat'); +maxAttempts = 3; +success = false; + +% Try existing config or prompt +if exist(configFile, 'file') + data = load(configFile, 'comPort'); + comPort = data.comPort; +else + comPort = promptForPort(); +end + +% Try to validate and possibly retry +for attempt = 1:maxAttempts + try + s = serialport(comPort, 9600); % test connection + clear s; % close it immediately + success = true; + break; + catch + fprintf('[Warning] Failed to open %s. Please select a different COM port.\n', comPort); + comPort = promptForPort(); + end +end + +if ~success + error('Failed to find a valid COM port after %d attempts.', maxAttempts); +end + +% Save the working port +save(configFile, 'comPort'); +end + +function comPort = promptForPort() +availablePorts = serialportlist("available"); + +if isempty(availablePorts) + warning('No serial ports detected. Enter manually.'); + comPort = input('Enter COM port manually (e.g., COM3): ', 's'); +else + fprintf('Available COM ports:\n'); + for i = 1:numel(availablePorts) + fprintf(' %d: %s\n', i, availablePorts(i)); + end + idx = input('Select COM port number: '); + if isnumeric(idx) && idx >= 1 && idx <= numel(availablePorts) + comPort = availablePorts(idx); + else + comPort = input('Enter COM port manually (e.g., COM3): ', 's'); + end +end +end diff --git a/Protocols/@ArpitCentrePokeTraining/private/StimuliDistribution_plot.m b/Protocols/@ArpitCentrePokeTraining/private/StimuliDistribution_plot.m new file mode 100644 index 00000000..8c3c100d --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/StimuliDistribution_plot.m @@ -0,0 +1,170 @@ +function StimuliDistribution_plot(ax,plot_x_range, Rule, distribution_left,mean_left,sigma_left,... + range_left,distribution_right,mean_right,sigma_right,range_right) + +if strcmp(Rule,'S1>S_boundary Right') + x_left = plot_x_range(1):.01:plot_x_range(2); + x_right = plot_x_range(2):.01:plot_x_range(3); +else + x_right = plot_x_range(1):.01:plot_x_range(2); + x_left = plot_x_range(2):.01:plot_x_range(3); +end + +switch distribution_left + + case 'Uniform' + + pdf_funct_left = makedist('Uniform','lower',range_left(1),'upper',range_left(2)); + pd_left = pdf(pdf_funct_left,x_left); + + case 'Exponential' + + lambda = 2.153 * (range_left(2) - range_left(1)); + lambda = 1/lambda; + if range_left(1) == plot_x_range(2) % decreasing function + Z = exp(-lambda * range_left(1)) - exp(-lambda * range_left(2)); % Define the normalization constant + pdf_func_left = @(x) (lambda * exp(-lambda * x)) / Z; % Normalized truncated exponential PDF + else + Z = exp(lambda * range_left(2)) - exp(lambda * range_left(1));% Normalization constant + A = lambda / Z; + pdf_func_left = @(x) A * exp(lambda * x); + end + + pd_left = pdf_func_left(x_left); + + case 'Normal' + + pdf_funct_left = makedist('Normal','mu',mean_left,'sigma',sigma_left); + pd_left = pdf(pdf_funct_left,x_left); + + case 'Half Normal' + + pdf_funct_init = @(x, A) A * exp(- (range_left(2) - x).^2 / (2 * sigma_left^2)); % Define the Half-Normal PDF (Peak at max value) + A_den = integral(@(x) exp(- (range_left(2) - x).^2 / (2 * sigma_left^2)), range_left(1), range_left(2)); % Compute Normalization Constant A + A_sol = 1 / A_den; % Ensuring total probability integrates to 1 + pdf_funct_left = @(x) A_sol * exp(- (range_left(2) - x).^2 / (2 * sigma_left^2)); % Define the final normalized PDF function + pd_left = pdf_funct_left(x_left); + + case 'Anti Exponential' + + lambda = 2.153 * (range_left(2) - range_left(1)); + + if range_left(2) == plot_x_range(2) % decreasing function + Z = exp(-lambda * range_left(1)) - exp(-lambda * range_left(2)); % Define the normalization constant + pdf_func_left = @(x) (lambda * exp(-lambda * x)) / Z; % Normalized truncated exponential PDF + else + Z = exp(lambda * range_left(2)) - exp(lambda * range_left(1));% Normalization constant + A = lambda / Z; + pdf_func_left = @(x) A * exp(lambda * x); + end + + pd_left = pdf_func_left(x_left); + + case 'Anti Half Normal' + + pdf_funct_left = makedist('HalfNormal','mu',mean_left,'sigma',sigma_left); + pd_left = pdf(pdf_funct_left,x_left); + + case 'Anti Sinusoidal' + + pdf_funct_init = @(x) sin((pi / 2) * ((x - range_left(1)) / (range_left(2) - range_left(1)))); + A_val = 1 / integral(pdf_funct_init, range_left(1), range_left(2)); % Compute Normalization Constant A + pdf_funct_left = @(x) A_val * sin((pi / 2) * ((x - range_left(1)) / (range_left(2) - range_left(1)))); % the normalized PDF + pd_left = pdf_funct_left(x_left); + + case 'Sinusoidal' + + pdf_funct_init = @(x) sin((pi / 2) * ((range_left(2) - x) / (range_left(2) - range_left(1)))); % Define the PDF (Peak at min value) + A_val = 1 / integral(pdf_funct_init, range_left(1), range_left(2)); % Compute Normalization Constant A + pdf_funct_left = @(x) A_val * sin((pi / 2) * ((range_left(2) - x) / (range_left(2) - range_left(1)))); % Define the normalized PDF + pd_left = pdf_funct_left(x_left); + + case 'Monotonic Increase' + +end + +switch distribution_right + + case 'Uniform' + + pdf_funct_right = makedist('Uniform','lower',range_right(1),'upper',range_right(2)); + pd_right = pdf(pdf_funct_right,x_right); + + case 'Exponential' + + lambda = 2.153 * (range_right(2) - range_right(1)); + lambda = 1/lambda; + if range_right(1) == plot_x_range(2) % decreasing function + Z = exp(-lambda * range_right(1)) - exp(-lambda * range_right(2)); % Define the normalization constant + pdf_func_right = @(x) (lambda * exp(-lambda * x)) / Z; % Normalized truncated exponential PDF + else + Z = exp(lambda * range_right(2)) - exp(lambda * range_right(1));% Normalization constant + A = lambda / Z; + pdf_func_right = @(x) A * exp(lambda * x); + end + + pd_right = pdf_func_right(x_right); + + case 'Normal' + + pdf_funct_right = makedist('Normal','mu',mean_right,'sigma',sigma_right); + pd_right = pdf(pdf_funct_right,x_right); + + case 'Half Normal' + + pdf_funct_right = makedist('HalfNormal','mu',mean_right,'sigma',sigma_right); + pd_right = pdf(pdf_funct_right,x_right); + + case 'Anti Half Normal' + + pdf_funct_init = @(x, A) A * exp(- (range_right(2) - x).^2 / (2 * sigma_right^2)); % Define the Half-Normal PDF (Peak at max value) + A_den = integral(@(x) exp(- (range_right(2) - x).^2 / (2 * sigma_right^2)), range_right(1), range_right(2)); % Compute Normalization Constant A + A_sol = 1 / A_den; % Ensuring total probability integrates to 1 + pdf_funct_right = @(x) A_sol * exp(- (range_right(2) - x).^2 / (2 * sigma_right^2)); % Define the final normalized PDF function + pd_right = pdf_funct_right(x_right); + + case 'Anti Exponential' + + lambda = 2.153 * (range_right(2) - range_right(1)); + + if range_right(2) == plot_x_range(2) % decreasing function + Z = exp(-lambda * range_right(1)) - exp(-lambda * range_right(2)); % Define the normalization constant + pdf_func_right = @(x) (lambda * exp(-lambda * x)) / Z; % Normalized truncated exponential PDF + else + Z = exp(lambda * range_right(2)) - exp(lambda * range_right(1));% Normalization constant + A = lambda / Z; + pdf_func_right = @(x) A * exp(lambda * x); + end + + pd_right = pdf_func_right(x_right); + + case 'Anti Sinusoidal' + + pdf_funct_init = @(x) sin((pi / 2) * ((range_right(2) - x) / (range_right(2) - range_right(1)))); % Define the PDF (Peak at min value) + A_val = 1 / integral(pdf_funct_init, range_right(1), range_right(2)); % Compute Normalization Constant A + pdf_funct_right = @(x) A_val * sin((pi / 2) * ((range_right(2) - x) / (range_right(2) - range_right(1)))); % Define the normalized PDF + pd_right = pdf_funct_right(x_right); + + + case 'Sinusoidal' + + pdf_funct_init = @(x) sin((pi / 2) * ((x - range_right(1)) / (range_right(2) - range_right(1)))); + A_val = 1 / integral(pdf_funct_init, range_right(1), range_right(2)); % Compute Normalization Constant A + pdf_funct_right = @(x) A_val * sin((pi / 2) * ((x - range_right(1)) / (range_right(2) - range_right(1)))); % the normalized PDF + pd_right = pdf_funct_right(x_right); + +end + +% Plot the distribution +if strcmp(Rule,'S1>S_boundary Right') + plot_x = [x_left,x_right]; + plot_y = [pd_left,pd_right]; +else + plot_x = [x_right,x_left]; + plot_y = [pd_right,pd_left]; +end + +plot(ax,plot_x,plot_y,'b','LineWidth', 2); +ylim(ax,[0 max(plot_y)]); +xlim(ax,[min(plot_x) max(plot_x)]) + +end \ No newline at end of file diff --git a/Protocols/@ArpitCentrePokeTraining/private/create_ArpitCentrePokeTraining_SettingFile.m b/Protocols/@ArpitCentrePokeTraining/private/create_ArpitCentrePokeTraining_SettingFile.m new file mode 100644 index 00000000..05a556d4 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/create_ArpitCentrePokeTraining_SettingFile.m @@ -0,0 +1,91 @@ +function create_ArpitCentrePokeTraining_SettingFile() + +templateDir = 'C:/ratter/'; + +templatePath = [templateDir '/Protocols/@ArpitCentrePokeTraining/private/pipeline_ArpitCentrePokeTraining_arpit_AR05_250516.m']; +setting_filename = 'settings_@ArpitCentrePokeTraining_arpit_AR05_250516a.mat'; +outputRootDir = [templateDir '/SoloData/Settings/']; + +experimenter = input('Enter experimenter name: ', 's'); +ratname = input('Enter rat name: ', 's'); + +% Get today's date +newDate = datestr(now, 'yymmdd'); + +% Read the template file +if ~isfile(templatePath) + error('Template file not found: %s', templatePath); +end +code = fileread(templatePath); +lines = splitlines(code); + +% Find and update the function definition line +newFuncName = ''; +for i = 1:length(lines) + line = strtrim(lines{i}); + if startsWith(line, 'function') && contains(line, 'pipeline_') + % Extract argument list + pattern = 'function\s+varargout\s*=\s*pipeline_.*?\((.*?)\)'; + tokens = regexp(line, pattern, 'tokens'); + if ~isempty(tokens) + args = tokens{1}{1}; + newFuncName = sprintf('pipeline_ArpitCentrePokeTraining_%s_%s_%s', ... + experimenter, ratname, newDate); + lines{i} = sprintf('function varargout = %s(%s)', newFuncName, args); + break; + end + end +end + +if isempty(newFuncName) + error('Could not find valid function definition line in template.'); +end + +% Create output directory if needed +outputDir = fullfile(outputRootDir, experimenter, ratname); +if ~exist(outputDir, 'dir') + mkdir(outputDir); +end + +% Build full new script path +newScriptPath = fullfile(outputDir, [newFuncName, '.m']); + +% Save modified content +newCode = strjoin(lines, newline); +fid = fopen(newScriptPath, 'w'); +if fid == -1 + error('Failed to open new file for writing: %s', newScriptPath); +end +fwrite(fid, newCode); +fclose(fid); + +fprintf('New Session Definition file saved to: %s\n', newScriptPath); + +% Load and modify .mat file +[templateFolder, ~, ~] = fileparts(templatePath); +matTemplatePath = fullfile(templateFolder, setting_filename); + +if ~isfile(matTemplatePath) + warning('No setting_file found in template directory: %s', matTemplatePath); + newMatPath = ''; + return; +end + +load(matTemplatePath,'saved','saved_autoset','fig_position'); % Load struct + +saved.SavingSection_experimenter = experimenter; +saved.SavingSection_ratname = ratname; +saved.SessionDefinition_textTrainingStageFile = newScriptPath; +saved.ArpitCentrePokeTraining_prot_title = sprintf('ArpitCentrePokeTraining: %s, %s',experimenter,ratname); +saved.PokesPlotSection_textHeader = sprintf('PokesPlotSection(%s, %s',experimenter,ratname); +saved.SessionDefinition_textHeader = sprintf('SESSION AUTOMATOR WINDOW: %s, %s',experimenter,ratname); + +new_MATfile = sprintf('settings_@ArpitCentrePokeTraining__%s_%s_%sa.mat', ... + experimenter, ratname, newDate); + +newMatPath = fullfile(outputDir, new_MATfile); + +save(newMatPath,'saved','saved_autoset','fig_position'); +fprintf('New setting file saved to: %s\n', newMatPath); + +end diff --git a/Protocols/@ArpitCentrePokeTraining/private/filt.m b/Protocols/@ArpitCentrePokeTraining/private/filt.m new file mode 100644 index 00000000..e4c572ff --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/filt.m @@ -0,0 +1,60 @@ +function filtsignal=filt(signal,fcut,Fs,filter_type) +a=2; % wp/ws used in butterworth method and LS linear FIR method +N=200; % filter order used in lowpass FIR method +rp=3; % passband ripple in dB used in butterworth method +rs=60; % stopband attenuation in dB used in butterworth method +beta=0.1102*(rs-8.8); %used in Kaiser window to obtain sidelobe attenuation of rs dB +if strcmp(filter_type, 'GAUS') || strcmp(filter_type, 'MOVAVRG') +window = fix(Fs/fcut); % window size used in Gaussian and moving average methods +end +wp=2*fcut/Fs; % normalized passband corner frequency wp, the cutoff frequency +ws=a*wp; % normalized stopband corner frequency + + +switch filter_type + case 'BUTTER' %Butterworth IIR filter + if length(wp)>1 + ws(1)=2*(fcut(1)/2)/Fs; + ws(2)=2*(fcut(2)+fcut(1)/2)/Fs; + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'bandpass'); + else + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'low'); + end + filtsignal=filter(b,a,signal);%conventional filtering + case 'LPFIR' %Lowpass FIR filter + d=fdesign.lowpass('N,Fc',N,fcut,Fs); % Fc is the 6-dB down point, N is the filter order(N+1 filter coefficients) + Hd = design(d); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'FIRLS' %Least square linear-phase FIR filter design + b=firls(255,[0 2*fcut/Fs a*2*fcut/Fs 1],[1 1 0 0]); + filtsignal=filter(b,1,signal); %conventional filtering + case 'EQUIRIP' %Eqiripple FIR filter + d=fdesign.lowpass('Fp,Fst,Ap,Ast',wp,ws,rp,rs); + Hd=design(d,'equiripple'); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'MOVAVRG' % Moving average FIR filtering, Rectangular window + h = ones(window,1)/window; + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'HAMMING' % Hamming-window based FIR filtering + b = fir1(150,wp); + filtsignal = filter(b, 1, signal); + filtsignal = filter(h, 1, signal); + case 'GAUS' % Gaussian-window FIR filtering + h = normpdf(1:window, 0, fix(window/2)); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'GAUS1' % Gaussian-window FIR filtering + b = fir1(window-1,wp,gausswin(window,2)/window); + filtsignal = filter(b, 1, signal); + case 'KAISER' %Kaiser-window FIR filtering + h=kaiser(window,beta); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + + otherwise + sprintf('filter_type is wrong!! havaset kojast!!') +end + diff --git a/Protocols/@ArpitCentrePokeTraining/private/noisestim.m b/Protocols/@ArpitCentrePokeTraining/private/noisestim.m new file mode 100644 index 00000000..7f074ab0 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/noisestim.m @@ -0,0 +1,47 @@ +function [base,target,normbase,normtarget]=noisestim(sigma_1,sigma_2,T,fcut,Fs,filter_type) + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +replace=1; +L=floor(T*Fs); % Length of signal +t=L*linspace(0,1,L)/Fs; % time in miliseconds +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1*randn(Fs,1); +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +pos2 =sigma_2*randn(Fs,1); +% pos2(pos2>outband)=[]; +% pos2(pos2<-outband)=[]; +base = randsample(pos1,L,replace); +target = randsample(pos2,L,replace); +%%%% Filter the original position values %%%%%% +filtbase=filt(base,fcut,Fs,filter_type); +filttarget=filt(target,fcut,Fs,filter_type); +normbase=filtbase./(max(abs(filtbase))); +normtarget=filttarget./(max(abs(filttarget))); +end + +%%%%%% plot the row and filtered position values %%%%%%%%% +% subplot(2,2,1) +% plot(t,base,'r'); +% ylabel('base') +% xlabel('Time (ms)') +% subplot(2,2,2) +% plot(t,target,'g'); +% ylabel('target') +% xlabel('Time (ms)') +% subplot(2,2,3) +% plot(t,filtbase) +% ylabel('filtbase') +% xlabel('Time (ms)') +% subplot(2,2,4) +% plot(t,filttarget) +% ylabel('filttarget') +% xlabel('Time (ms)') diff --git a/Protocols/@ArpitCentrePokeTraining/private/set_training_stage_last_setting_file.m b/Protocols/@ArpitCentrePokeTraining/private/set_training_stage_last_setting_file.m new file mode 100644 index 00000000..92dd3cd0 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/set_training_stage_last_setting_file.m @@ -0,0 +1,51 @@ +function set_training_stage_last_setting_file(owner,experimenter,ratname) + +try + +global Solo_datadir; + +rat_dir = sprintf('%s\\Settings\\%s\\%s\\',Solo_datadir,experimenter,ratname); +% Get the latest setting file + +u = dir([rat_dir 'settings_@' owner '_' experimenter '_' ratname '*.mat']); +if ~isempty(u) + [filenames{1:length(u)}] = deal(u.name); + filenames = sort(filenames'); %#ok (can't use dimension argument with cell sort) + today_date_str = regexprep(char(datetime('today','Format','yy-MM-dd')), '[^0-9]', ''); + for i=length(u):-1:1 % search from the end back + file_date_num = textscan(filenames{i},['settings_@' owner '_' experimenter '_' ratname '_%n%*c.mat']); + + if ~isempty(file_date_num{1}) && file_date_num{1} <= str2double(today_date_str) + fullname = [rat_dir filenames{i}]; % We've found it. + break + end + end +end + +try + loaded_data = load(fullname); +catch + return; +end + +% Lets find the Handle for Training Stage +handles = get_sphandle('owner', owner); +stage_handle_name = 'ParamsSection_training_stage'; +for hi= 1:length(handles) + sph_fullname=get_fullname(handles{hi}); + if strcmpi(sph_fullname,stage_handle_name) + handles{hi}.value = loaded_data.saved.(sph_fullname); + callback(handles{i}); + break + end +end + +return + +catch + + return + +end + +end \ No newline at end of file diff --git a/Protocols/@ArpitCentrePokeTraining/private/settings_@ArpitCentrePokeTraining_arpit_AR05_250516a.mat b/Protocols/@ArpitCentrePokeTraining/private/settings_@ArpitCentrePokeTraining_arpit_AR05_250516a.mat new file mode 100644 index 00000000..7e70e094 Binary files /dev/null and b/Protocols/@ArpitCentrePokeTraining/private/settings_@ArpitCentrePokeTraining_arpit_AR05_250516a.mat differ diff --git a/Protocols/@ArpitCentrePokeTraining/private/singlenoise.m b/Protocols/@ArpitCentrePokeTraining/private/singlenoise.m new file mode 100644 index 00000000..0cf201f3 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/private/singlenoise.m @@ -0,0 +1,32 @@ +function [normbase]=singlenoise(sigma_1,T,fcut,Fs,filter_type) +%GetSoloFunctionArgs(obj); + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +sigma_1=1; +%T=10000; +%fcut=[3000 4000]; +%Fs=200000; +filter_type='BUTTER'; +outband=60; +replace=1; +L=floor(T*Fs); % Length of signal +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1*randn(Fs,1); +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +base = randsample(pos1,L,replace); +%%%% Filter the original position values %%%%%% +%filtbase=filt(base,fcut,Fs,filter_type); +hf = design(fdesign.bandpass('N,F3dB1,F3dB2',10,fcut(1),fcut(2),Fs)); +filtbase=filter(hf,base); +normbase=filtbase./(max(abs(filtbase))); +end + diff --git a/Protocols/@ArpitCentrePokeTraining/state_colors.m b/Protocols/@ArpitCentrePokeTraining/state_colors.m new file mode 100644 index 00000000..1e3b1737 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/state_colors.m @@ -0,0 +1,14 @@ +function SC = state_colors(obj) %#ok + +SC = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'settling_in_state', [0.63 1 0.94], ... + 'legal_poke_start_state', [0.63 1 0.94]*0.8, ... + 'legal_poke_end_state', [1 0.79 0.63], ... + 'soft_cp', [0.3 0.9 0], ... + 'side_led_wait_RewardCollection', [0.53 0.78 1.00],... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); diff --git a/Protocols/@ArpitCentrePokeTraining/wave_colors.m b/Protocols/@ArpitCentrePokeTraining/wave_colors.m new file mode 100644 index 00000000..0d5a7339 --- /dev/null +++ b/Protocols/@ArpitCentrePokeTraining/wave_colors.m @@ -0,0 +1,11 @@ +function WC = wave_colors(obj) %#ok + +WC = struct(... + 'stim_wave', [1 1 1 ], ... + 'prestim_wave', [1 1 1 ], ... + 'stimulator_wave', [1 1 0 ], ... + 'stimulator_wave1', [1 1 0 ], ... + 'stimulator_wave2', [1 1 0 ], ... + 'stimulator_wave3', [1 1 0 ], ... + 'stimulator_wave4', [1 1 0 ], ... + 'stimulator_wave5', [1 1 0 ]); \ No newline at end of file diff --git a/Protocols/@ArpitSoundCalibration/ArpitSoundCalibration.m b/Protocols/@ArpitSoundCalibration/ArpitSoundCalibration.m new file mode 100644 index 00000000..70b8c595 --- /dev/null +++ b/Protocols/@ArpitSoundCalibration/ArpitSoundCalibration.m @@ -0,0 +1,733 @@ + +% MATLAB script for calibrating sound pressure level using Arduino and serial communication in real-time. +% +% This script automates the process of calibrating a speaker using an Arduino and a sound level meter. +% It communicates with an Arduino to control speaker output and read sound pressure level (SPL) measurements. +% The script performs the following steps: +% 1. Establishes serial communication with the Arduino. +% 2. Creates a figure window with a table to display real-time SPL readings. +% 3. Iterates through a set of speaker output values, sending each value to the speaker (via a user-defined function). +% 4. At each speaker output level, the script reads the corresponding SPL from the Arduino. +% 5. The measured SPL values are displayed in the table in real-time. +% 6. After completing the measurements, the script displays the final calibration data and optionally plots the +% relationship between speaker output and SPL, including a polynomial fit. +% 7. Includes error handling to ensure robust communication with the Arduino. + +% Written by Arpit 2024 + +% Make sure you ran newstartup, then dispatcher('init'), and you're good to +% go! +% + +function [obj] = Arpit_SoundCalibration(varargin) + +% Default object is of our own class (mfilename); in this simplest of +% protocols, we inherit only from Plugins/@pokesplot + +obj = class(struct, mfilename, soundmanager, soundui); + +%--------------------------------------------------------------- +% BEGIN SECTION COMMON TO ALL PROTOCOLS, DO NOT MODIFY +%--------------------------------------------------------------- + +% If creating an empty object, return without further ado: +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')), + return; +end; + +if isa(varargin{1}, mfilename), % If first arg is an object of this class itself, we are + % Most likely responding to a callback from + % a SoloParamHandle defined in this mfile. + if length(varargin) < 2 || ~ischar(varargin{2}), + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else action = varargin{2}; varargin = varargin(3:end); %#ok + end; +else % Ok, regular call with first param being the action string. + action = varargin{1}; varargin = varargin(2:end); %#ok +end; +if ~ischar(action), error('The action parameter must be a string'); end; + +GetSoloFunctionArgs(obj); + +%--------------------------------------------------------------- +% END OF SECTION COMMON TO ALL PROTOCOLS, MODIFY AFTER THIS LINE +%--------------------------------------------------------------- + + +% ---- From here on is where you can put the code you like. +% +% Your protocol will be called, at the appropriate times, with the +% following possible actions: +% +% 'init' To initialize -- make figure windows, variables, etc. +% +% 'update' Called periodically within a trial +% +% 'prepare_next_trial' Called when a trial has ended and your protocol +% is expected to produce the StateMachine diagram for the next +% trial; i.e., somewhere in your protocol's response to this +% call, it should call "dispatcher('send_assembler', sma, +% prepare_next_trial_set);" where sma is the +% StateMachineAssembler object that you have prepared and +% prepare_next_trial_set is either a single string or a cell +% with elements that are all strings. These strings should +% correspond to names of states in sma. +% Note that after the 'prepare_next_trial' call, further +% events may still occur in the RTLSM while your protocol is thinking, +% before the new StateMachine diagram gets sent. These events +% will be available to you when 'trial_completed' is called on your +% protocol (see below). +% +% 'trial_completed' Called when 'state_0' is reached in the RTLSM, +% marking final completion of a trial (and the start of +% the next). +% +% 'close' Called when the protocol is to be closed. +% +% +% VARIABLES THAT DISPATCHER WILL ALWAYS INSTANTIATE FOR YOU IN YOUR +% PROTOCOL: +% +% (These variables will be instantiated as regular Matlab variables, +% not SoloParamHandles. For any method in your protocol (i.e., an m-file +% within the @your_protocol directory) that takes "obj" as its first argument, +% calling "GetSoloFunctionArgs(obj)" will instantiate all the variables below.) +% +% +% n_done_trials How many trials have been finished; when a trial reaches +% one of the prepare_next_trial states for the first +% time, this variable is incremented by 1. +% +% n_started trials How many trials have been started. This variable gets +% incremented by 1 every time the state machine goes +% through state 0. +% +% parsed_events The result of running disassemble.m, with the +% parsed_structure flag set to 1, on all events from the +% start of the current trial to now. +% +% latest_events The result of running disassemble.m, with the +% parsed_structure flag set to 1, on all new events from +% the last time 'update' was called to now. +% +% raw_events All the events obtained in the current trial, not parsed +% or disassembled, but raw as gotten from the State +% Machine object. +% +% current_assembler The StateMachineAssembler object that was used to +% generate the State Machine diagram in effect in the +% current trial. +% +% Trial-by-trial history of parsed_events, raw_events, and +% current_assembler, are automatically stored for you in your protocol by +% dispatcher.m. See the wiki documentation for information on how to access +% those histories from within your protocol and for information. +% +% + + +switch action + + %--------------------------------------------------------------- + % CASE INIT + %--------------------------------------------------------------- + + case 'init' + + % Make default figure. We remember to make it non-saveable; on next run + % the handle to this figure might be different, and we don't want to + % overwrite it when someone does load_data and some old value of the + % fig handle was stored as SoloParamHandle "myfig" + SoloParamHandle(obj, 'myfig', 'saveable', 0); myfig.value = double(figure); + + % Make the title of the figure be the protocol name, and if someone tries + % to close this figure, call dispatcher's close_protocol function, so it'll know + % to take it off the list of open protocols. + name = mfilename; + set(value(myfig), 'Name', name, 'Tag', name, ... + 'closerequestfcn', [mfilename,'(''close'');'], 'MenuBar', 'none'); + + + % Generate the sounds we need. + soundserver = bSettings('get','RIGS','sound_machine_server'); + if ~isempty(soundserver) + sr = SoundManagerSection(obj,'get_sample_rate'); + Fs=sr; + lfreq=2000; + hfreq=20000; + freq = 100; + T = 5; + fcut = 110; + filter_type = 'GAUS'; + A1_sigma = 0.0500; + A2_sigma = 0.0306; %0.1230;%0.0260; + A3_sigma = 0.0187; %0.0473;%0.0135; + A4_sigma = 0.0114; %0.0182;%0.0070; + A5_sigma = 0.0070; + [rawA1 rawA2 normA1 normA2]=noisestim(1,1,T,fcut,Fs,filter_type); + modulator=singlenoise(1,T,[lfreq hfreq],Fs,'BUTTER'); + AUD1=normA1(1:T*sr).*modulator(1:T*sr).*A1_sigma; + AUD2=normA1(1:T*sr).*modulator(1:T*sr).*A2_sigma; + AUD3=normA1(1:T*sr).*modulator(1:T*sr).*A3_sigma; + AUD4=normA1(1:T*sr).*modulator(1:T*sr).*A4_sigma; + AUD5=normA1(1:T*sr).*modulator(1:T*sr).*A5_sigma; + + if ~isempty(AUD2) + SoundManagerSection(obj, 'declare_new_sound', 'left_sound', [AUD2'; AUD2']) + end + if ~isempty(AUD1) + SoundManagerSection(obj, 'declare_new_sound', 'center_sound', [AUD1'; AUD1']) + end + if ~isempty(AUD3) + SoundManagerSection(obj, 'declare_new_sound', 'right_sound', [AUD3'; AUD3']) + end + if ~isempty(AUD4) + SoundManagerSection(obj, 'declare_new_sound', 'fourth_sound', [AUD4'; AUD4']) + end + if ~isempty(AUD5) + SoundManagerSection(obj, 'declare_new_sound', 'fifth_sound', [AUD5'; AUD5']) + end + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + end + + % Get monitor dimensions for dynamic GUI sizing + MP = get(0,'MonitorPositions'); + + % Calculate individual group width based on screen dimensions and number of groups + groupwidth = floor((MP(3)/2)/numel(linegroups)); + + padding = 10; % padding around GUI elements + + % Calculate total width needed for all groups combined + total_width = floor(numel(linegroups) * groupwidth+padding); + total_height = 400; % total height of GUI + + label_height = 140; % height of port label + button_height = 25; + + % Center the GUI horizontally on screen + left_pos = floor((MP(3) - total_width) / 2); + + % Set figure position with centered alignment and fixed height of 400 pixels + % position vector: [left-right, down-up, width, height], the origo is + % the bottom left corner + set(value(myfig), 'Position', [left_pos, floor((MP(4)-total_height)/2), total_width, total_height]); + + % Initialize array to store line names + line_names = []; + + % Iterate through each line group + + % Check if current group exists and has valid parameters + if ~isempty(linegroups{i}) && ~isempty(linegroups{i}{1,2}) + % port label position + SubheaderParam( ... + obj, ... + ['Input',linegroups{i}{1,2}], ... + {linegroups{i}{1,2};0},0,0, ... + 'position',[((i-1)*groupwidth)+padding, ... + total_height-padding-label_height, ... + groupwidth-padding, ... + label_height ... + ]); + + % port label aesthetics + set(get_ghandle(eval(['Input',linegroups{i}{1,2}])), ... + 'FontSize',32, ... % Large font size for visibility + 'BackgroundColor',[0,1,0], ... % Green background + 'HorizontalAlignment','center'); % Center align text + + % Store line name for later reference + line_names(end+1) = linegroups{i}{1,2}; %#ok + + % Add sound controls based on line identifier + if strcmp(linegroups{i}{1,2},'L') && ~isempty(soundserver) + % Left channel sound toggle + ToggleParam(obj, ... + 'LeftSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Two ON', ... + 'OffString', 'Sound Two OFF'); + set_callback(LeftSound,{mfilename,'play_left_sound'}); + + elseif strcmp(linegroups{i}{1,2},'R') && ~isempty(soundserver) + % Right channel sound toggle + ToggleParam(obj, ... + 'RightSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Three ON', ... + 'OffString', 'Sound Three OFF'); + set_callback(RightSound,{mfilename,'play_right_sound'}); + + elseif strcmp(linegroups{i}{1,2},'C') && ~isempty(soundserver) + % Center channel sound toggle + ToggleParam(obj, ... + 'CenterSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound One ON', ... + 'OffString', 'Sound One OFF'); + set_callback(CenterSound,{mfilename,'play_center_sound'}); + + elseif strcmp(linegroups{i}{1,2},'A') && ~isempty(soundserver) + % Additional channel sound toggle + ToggleParam(obj, ... + 'FourthSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Four ON', ... + 'OffString', 'Sound Four OFF'); + set_callback(FourthSound,{mfilename,'play_fourth_sound'}); + end + + % Handle case for empty line identifier but existing group + elseif ~isempty(linegroups{i}) && isempty(linegroups{i}{1,2}) + ToggleParam(obj, ... + 'FifthSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Five ON', ... + 'OffString', 'Sound Five OFF'); + set_callback(FifthSound,{mfilename,'play_fifth_sound'}); + end + + + + SoloParamHandle(obj,'LineGroups','value',linegroups); + SoloParamHandle(obj,'LineNames','value',line_names); + + scr = timer; + set(scr,'Period', 0.2,'ExecutionMode','FixedRate','TasksToExecute',Inf,... + 'BusyMode','drop','TimerFcn',[mfilename,'(''close_continued'')']); + SoloParamHandle(obj, 'stopping_complete_timer', 'value', scr); + + Arpit_SoundCalibration(obj,'prepare_next_trial'); + + dispatcher('Run'); + + + case 'play_left_sound' + %% play_left_sound + if value(LeftSound) == 1 + SoundManagerSection(obj,'play_sound','left_sound'); + else + SoundManagerSection(obj,'stop_sound','left_sound'); + end + + case 'play_right_sound' + %% play_right_sound + if value(RightSound) == 1 + SoundManagerSection(obj,'play_sound','right_sound'); + else + SoundManagerSection(obj,'stop_sound','right_sound'); + end + + case 'play_center_sound' + %% play_center_sound + if value(CenterSound) == 1 + SoundManagerSection(obj,'play_sound','center_sound'); + else + SoundManagerSection(obj,'stop_sound','center_sound'); + end + + case 'play_fourth_sound' + %% play_fourth_sound + if value(FourthSound) == 1 + SoundManagerSection(obj,'play_sound','fourth_sound'); + else + SoundManagerSection(obj,'stop_sound','fourth_sound'); + end + case 'play_fifth_sound' + %% play_fifth_sound + if value(FifthSound) == 1 + SoundManagerSection(obj,'play_sound','fifth_sound'); + else + SoundManagerSection(obj,'stop_sound','fifth_sound'); + end + + case 'toggle16_2' + %% case toggle1_2 + linegroups = value(LineGroups); + dispatcher('toggle_bypass',log2(linegroups{16}{3,1})); + + %--------------------------------------------------------------- + % CASE PREPARE_NEXT_TRIAL + %--------------------------------------------------------------- + case 'prepare_next_trial' + line_names = value(LineNames); + sma = StateMachineAssembler('full_trial_structure','use_happenings',1); %,'n_input_lines',numel(line_names),'line_names',line_names); + sma = add_state(sma, 'name', 'the_only_state', 'self_timer',1e4, 'input_to_statechange', {'Tup', 'final_state'}); + sma = add_state(sma, 'name', 'final_state', 'self_timer',1e4, 'input_to_statechange', {'Tup', 'check_next_trial_ready'}); + dispatcher('send_assembler', sma, 'final_state'); + + %--------------------------------------------------------------- + % CASE TRIAL_COMPLETED + %--------------------------------------------------------------- + case 'trial_completed' + + + %--------------------------------------------------------------- + % CASE UPDATE + %--------------------------------------------------------------- + case 'update' + pe = parsed_events; %#ok + linenames = value(LineNames); + + for i = 1:numel(linenames) + poketimes = eval(['pe.pokes.',linenames(i)]); + if ~isempty(poketimes) && isnan(poketimes(end,2)) + set(get_ghandle(eval(['Input',linenames(i)])),'BackgroundColor',[1,0,0]); + + str = get(get_ghandle(eval(['Input',linenames(i)])),'string'); + str{2} = size(poketimes,1); + set(get_ghandle(eval(['Input',linenames(i)])),'string',str); + + else + set(get_ghandle(eval(['Input',linenames(i)])),'BackgroundColor',[0,1,0]); + end + end + + %--------------------------------------------------------------- + % CASE CLOSE + %--------------------------------------------------------------- + case 'close' + + dispatcher('Stop'); + + %Let's pause until we know dispatcher is done running + set(value(stopping_complete_timer),'TimerFcn',[mfilename,'(''close_continued'');']); + start(value(stopping_complete_timer)); + + case 'close_continued' + + if value(stopping_process_completed) + stop(value(stopping_complete_timer)); %Stop looping. + %dispatcher('set_protocol',''); + + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)), %#ok + delete(value(myfig)); + end + delete_sphandle('owner', ['^@' class(obj) '$']); + dispatcher('set_protocol',''); + end + + otherwise + + warning('Unknown action! "%s"\n', action); %#ok + end + +return; + +end + + + + + + + + + + + + + + + + +function [normbase]=singlenoise(sigma_1,T,fcut,Fs,filter_type) + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +% filter_type='BUTTER'; + +outband=60; +replace=1; +L=floor(T*Fs);% Length of signal + +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1 * randn(Fs,1); + +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +base = randsample(pos1,L,replace); +%%%% Filter the original position values %%%%%% +%filtbase=filt(base,fcut,Fs,filter_type); +hf = design(fdesign.bandpass('N,F3dB1,F3dB2',10,fcut(1),fcut(2),Fs)); +filtbase=filter(hf,base); +normbase=filtbase./(max(abs(filtbase))); + +end + + +function [base,target,normbase,normtarget]=noisestim(sigma_1,sigma_2,T,fcut,Fs,filter_type) + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +replace=1; +L=floor(T*Fs); % Length of signal +% t=L*linspace(0,1,L)/Fs; % time in miliseconds +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1*randn(Fs,1); +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +pos2 =sigma_2*randn(Fs,1); +% pos2(pos2>outband)=[]; +% pos2(pos2<-outband)=[]; +base = randsample(pos1,L,replace); +target = randsample(pos2,L,replace); +%%%% Filter the original position values %%%%%% +filtbase=filt(base,fcut,Fs,filter_type); +filttarget=filt(target,fcut,Fs,filter_type); +normbase=filtbase./(max(abs(filtbase))); +normtarget=filttarget./(max(abs(filttarget))); + +end + + + + +function filtsignal=filt(signal,fcut,Fs,filter_type) +a=2; % wp/ws used in butterworth method and LS linear FIR method +N=200; % filter order used in lowpass FIR method +rp=3; % passband ripple in dB used in butterworth method +rs=60; % stopband attenuation in dB used in butterworth method +beta=0.1102*(rs-8.8); %used in Kaiser window to obtain sidelobe attenuation of rs dB +if strcmp(filter_type, 'GAUS') || strcmp(filter_type, 'MOVAVRG') +window = fix(Fs/fcut); % window size used in Gaussian and moving average methods +end +wp=2*fcut/Fs; % normalized passband corner frequency wp, the cutoff frequency +ws=a*wp; % normalized stopband corner frequency + +switch filter_type + case 'BUTTER' %Butterworth IIR filter + if length(wp)>1 + ws(1)=2*(fcut(1)/2)/Fs; + ws(2)=2*(fcut(2)+fcut(1)/2)/Fs; + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'bandpass'); + else + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'low'); + end + filtsignal=filter(b,a,signal);%conventional filtering + case 'LPFIR' %Lowpass FIR filter + d=fdesign.lowpass('N,Fc',N,fcut,Fs); % Fc is the 6-dB down point, N is the filter order(N+1 filter coefficients) + Hd = design(d); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'FIRLS' %Least square linear-phase FIR filter design + b=firls(255,[0 2*fcut/Fs a*2*fcut/Fs 1],[1 1 0 0]); + filtsignal=filter(b,1,signal); %conventional filtering + case 'EQUIRIP' %Eqiripple FIR filter + d=fdesign.lowpass('Fp,Fst,Ap,Ast',wp,ws,rp,rs); + Hd=design(d,'equiripple'); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'MOVAVRG' % Moving average FIR filtering, Rectangular window + h = ones(window,1)/window; + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'HAMMING' % Hamming-window based FIR filtering + b = fir1(150,wp); + filtsignal = filter(b, 1, signal); + filtsignal = filter(h, 1, signal); + case 'GAUS' % Gaussian-window FIR filtering + h = normpdf(1:window, 0, fix(window/2)); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'GAUS1' % Gaussian-window FIR filtering + b = fir1(window-1,wp,gausswin(window,2)/window); + filtsignal = filter(b, 1, signal); + case 'KAISER' %Kaiser-window FIR filtering + h=kaiser(window,beta); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + + otherwise + sprintf('filter_type is wrong!! havaset kojast!!') +end + +end + + + +% MATLAB script for calibrating sound pressure level using Arduino and serial communication in real-time + +% --- Configuration --- +arduinoPort = 'COM3'; % Replace with the actual COM port of your Arduino +baudRate = 115200; +speakerOutputValues = 0:10:100; % Example range of speaker output values +numRepetitions = 3; % Number of times to repeat each measurement +tableTitle = 'Speaker Calibration Data'; % Title for the table + +% --- Initialize Serial Communication with Arduino --- +try + % Create serial port object + arduinoSerial = serial(arduinoPort, 'BaudRate', baudRate); + % Open the serial port + fopen(arduinoSerial); + % Set a timeout to prevent MATLAB from waiting indefinitely + arduinoSerial.Timeout = 10; % in seconds + % Read the "Arduino Ready" message + arduinoReady = fgetl(arduinoSerial); + if ~strcmp(arduinoReady, 'Arduino Ready') + error('MATLAB:ArduinoNotReady', 'Arduino did not send the "Ready" signal. Check the Arduino code and connection.'); + end + disp('Arduino connected and ready.'); + + % --- Create Figure and Table --- + fig = figure('Name', tableTitle, 'NumberTitle', 'off'); + % Create an empty table in the figure + hTable = uitable(fig, 'Data', zeros(0, numRepetitions + 1), ... + 'ColumnName', [{'SpeakerValue'}, arrayfun(@(i) ['SPL_Rep' num2str(i)], 1:numRepetitions, 'UniformOutput', false)], ... + 'RowName', [], ... + 'Position', [20 20 400 300]); % Adjust position as needed + + % --- Preallocate Data Storage (for efficiency) --- + numValues = length(speakerOutputValues); + dataMatrix = zeros(numValues * numRepetitions, numRepetitions + 1); % +1 for speakerValue + + % --- Calibration Loop --- + disp('Starting calibration...'); + for i = 1:numValues + speakerValue = speakerOutputValues(i); + disp(['Setting speaker output to: ' num2str(speakerValue)]); + + % Store the speaker value in the data matrix + dataMatrix((i - 1) * numRepetitions + 1:i * numRepetitions, 1) = speakerValue; + + for j = 1:numRepetitions + fprintf(' Repetition %d: ', j); + % *** Replace this with your function to control the speaker in MATLAB *** + setSpeakerLevel(speakerValue); % Call the speaker control function + % ---------------------------------------------------------------------- + + % Read the SPL value from Arduino + try + splString = fgetl(arduinoSerial); + splValue = str2double(splString); + if isnan(splValue) + error('MATLAB:InvalidSPLValue', 'Received non-numeric SPL value from Arduino.'); + end + disp(['Received SPL: ' num2str(splValue) ' dB']); + dataMatrix((i - 1) * numRepetitions + j, j + 1) = splValue; % Store in matrix + + catch ME + % Handle errors, such as timeout or non-numeric data + disp(['Error: ' ME.message]); + if (strcmp(ME.identifier, 'MATLAB:serial:fread:timeout')) + disp(' Timeout occurred while waiting for data from Arduino.'); + elseif (strcmp(ME.identifier, 'MATLAB:InvalidSPLValue')) + disp(' Non-numeric data received from Arduino.'); + end + % Consider adding a 'continue' here to proceed to the next repetition + % even if one fails. Otherwise, the script will stop. + continue; % Add this to continue to the next iteration + end + pause(0.5); % Short pause + + % --- Update the Table in the Figure --- + % Create a temporary table and update the figure's table + tempTable = array2table(dataMatrix(1:i * numRepetitions, :), 'VariableNames', [{'SpeakerValue'}, arrayfun(@(i) ['SPL_Rep' num2str(i)], 1:numRepetitions, 'UniformOutput', false)]); + set(hTable, 'Data', tempTable); + drawnow; % Force the figure to update + end + end + + % --- Close Serial Port --- + fclose(arduinoSerial); + delete(arduinoSerial); + clear arduinoSerial; + + % --- Display Results in a Table --- + calibrationTable = array2table(dataMatrix, 'VariableNames', [{'SpeakerValue'}, arrayfun(@(i) ['SPL_Rep' num2str(i)], 1:numRepetitions, 'UniformOutput', false)]); + disp(tableTitle); + disp(calibrationTable); + + % --- Optional: Plot Calibration Data --- + figure; % Create a new figure for the plot + plot(calibrationTable.SpeakerValue, calibrationTable{:, 2:end}, '-o'); % Plot all repetitions + xlabel('Speaker Output Value'); + ylabel('SPL (dB)'); + title('Speaker Calibration Curve'); + legend([columnNames{2:end}]); + grid on; + + % --- Optional: Polynomial Fit and Display Equation --- + degree = 2; % You can change the degree of the polynomial + p = polyfit(calibrationTable.SpeakerValue, mean(calibrationTable{:, 2:end}, 2), degree); % Fit to the *mean* SPL + fittedSPL = polyval(p, calibrationTable.SpeakerValue); + hold on; + plot(calibrationTable.SpeakerValue, fittedSPL, 'r-', 'LineWidth', 2); % Plot the fit + hold off; + % Display the polynomial equation + equationString = poly2str(p, 'x'); % Use a helper function (defined below) + disp(['Polynomial fit equation: SPL = ' equationString]); + +catch ME + % Handle any errors that occur during the process + disp(['An error occurred: ' ME.message]); + % Clean up the serial port if it was opened + if exist('arduinoSerial', 'var') && isvalid(arduinoSerial) + fclose(arduinoSerial); + delete(arduinoSerial); + clear arduinoSerial; + end +end + +% --- Function to simulate setting the speaker level (REPLACE THIS) --- +function setSpeakerLevel(value) + % Replace this with the actual commands or functions to control the speaker. + % This is just a placeholder for your specific speaker control mechanism. + disp(['(Simulating setting speaker level to: ' num2str(value), ')']); + pause(1); % Simulate speaker level change +end + +% --- Helper function to convert polynomial coefficients to a string --- +function equationString = poly2str(p, varName) + % Converts a polynomial coefficient vector (as returned by polyfit) + % to a string representation of the polynomial equation. + % + % Example: + % p = [1 2 3]; % Coefficients for 1x^2 + 2x + 3 + % varName = 'x'; + % equationString = poly2str(p, varName); % Returns '1x^2 + 2x + 3' + + n = length(p); + equationString = ''; + for i = 1:n + coeff = p(i); + if coeff ~= 0 + if ~isempty(equationString) + equationString = [equationString, ' + ']; % Use '+' sign (except for the first term) + end + if coeff == 1 && i < n %suppress 1 + % equationString = [equationString, varName]; + elseif coeff ~= 1 + equationString = [equationString, num2str(coeff)]; + end + + if i < n + equationString = [equationString, varName]; + if i < n - 1 + equationString = [equationString, '^', num2str(n - i)]; + end + elseif coeff ~= 0 + equationString = [equationString, num2str(coeff)]; + end + end + end + % Replace "+ -" with "-" + equationString = strrep(equationString, '+ -', '-'); +end diff --git a/Protocols/@ArpitSoundCatContinuous/AntibiasSection.m b/Protocols/@ArpitSoundCatContinuous/AntibiasSection.m new file mode 100644 index 00000000..30af9f02 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/AntibiasSection.m @@ -0,0 +1,408 @@ +% [x, y] = AntibiasSection(obj, action, [arg1], [arg2], [arg3]) +% +% Section that calculates biases and calculates probability of choosing a stimulus +% given the previous history. +% +% Antibias assumes that trials are of two classes, Left desired answer +% and Right desired answer, and that their outcome is either Correct or +% Incorrect. Given the history of previous trial classes, and the history +% of previous corrects/incorrects, Antibias makes a local estimate of +% fraction correct for each class, combines that with a prior probability +% of making the next trial Left, and produces a recommended probability +% for choosing the next trial as Left. Antibias will tend to make the +% class with the smaller frac correct the one with the higher probability. +% The strength of that tendency is quantified by a parameter, beta. +% (See probabilistic_trial_selector.m for details on the math of how the +% tendency is generated.) +% +% Local estimates of fraction correct are computed using an exponential +% kernel, most recent trial the most strongly weighted. The tau of this +% kernel is a GUI parameter. Two different estimates are computed: one for +% use in computing Left probability and Right probability; and a second +% simply for GUI display purposes. The two estimates can have different +% taus for their kernels. +% +% GUI DISPLAY: When initialized, this plugin will put up two panels and a +% title. In each panel, there is a slider that controls the tau of the +% recent-trials exponential kernel. One panel will display the percent +% corrects for Right and Left, as computed with its kernel. The second panel +% will display the a posteriori probabilities of making the next trial a +% "Left" trial or making the next trial a "Right" trial. This second panel +% has its own tau slider, and it also has a GUI parameter, beta, that +% controls how strongly the history matters. If beta=0, history doesn't +% matter, and the a priori LeftProbability dominates. If beta=Inf, then +% history matters above all: the next trial will be of the type with lowest +% fraction correct, for sure. +% +% See the bottom of this help file for examples of usage. +% +% arg1, arg2, arg3 are optional and their meaning depends on action (see +% below). +% +% PARAMETERS: +% ----------- +% +% obj Default object argument. +% +% action One of: +% +% 'init' To initialise the plugin and set up the GUI for it. This +% action requires two more arguments: The bottom left +% x y position (in units of pixels) of where to start placing +% the GUI elements of this plugin. +% +% 'update' This call will recompute both local estimates of +% fraction correct, and will recompute the recommended +% LeftProb, p(Left). This action requires three more arguments: +% HitHist, LProb, a scalar b/w 0 and 1; HitHist, a vector of 1s +% SidesHist and 0s and of length n_done_trials where 1 represents +% correct and 0 represents incorrect, first element +% corresponds to first trial; and SidesHist, a vector of +% 'l's and 'r's and of length n_done_trials where 'l' +% represents 'left', 'r' represents 'right' first element +% corresponds to first trial. +% +% 'get_posterior_probs' Returns a vector with two components, +% [p(Left) p(Right)]. +% +% +% 'update_biashitfrac' This call will recompute the local estimate of fraction +% Left correct and fraction Right correct used for +% LeftProb, antibiasing, and will also recompute the recommended +% HitHist, Left probability. This action +% SidesHist, requires three more arguments: LProb, a scalar b/w 0 +% and 1; HitHist, a vector of 1s and 0s and of length +% n_done_trials where 1 represents correct and 0 +% represents incorrect, first element corresponds to +% first trial; and SidesHist, a vector of 'l's and 'r's +% and of length n_done_trials where 'l' represents +% 'left', 'r' represents 'right' first element +% corresponds to first trial. +% +% 'update_hitfrac' This call is not related to computing the posterior +% Left probability, but will recompute only the local estimate +% HitHist, of fraction correct that is not used for antibiasing. +% SidesHist This action requires two more arguments: HitHist, a +% vector of 1s and 0s and of length n_done_trials where 1 +% represents correct and 0 represents incorrect, first +% element corresponds to first trial; and SidesHist, a vector of +% 'l's and 'r's and of length n_done_trials where 'l' +% represents 'left', 'r' represents 'right' first element +% corresponds to first trial. +% +% 'get' Needs one extra parameter, either 'Beta' or +% 'antibias_tau', and returns the corresponding scalar. +% +% 'reinit' Delete all of this section's GUIs and data, +% and reinit, at the same position on the same +% figure as the original section GUI was placed. +% +% +% x, y Relevant to action = 'init'; they indicate the initial +% position to place the GUI at, in the current figure window +% +% RETURNS: +% -------- +% +% if action == 'init' : +% +% [x1, y1, w, h] When action == 'init', Antibias will put up GUIs and take +% up a certain amount of space of the figure that was current when +% AntiBiasSection(obj, 'init', x, y) was called. On return, [x1 y1] +% will be the top left corner of the space used; [x y] (as passed +% to Antibias in the init call) will be the bottom left corner; +% [x+w y1] will be the top right; and [x+w y] will be the bottom +% right. h = y1-y. All these are in units of pixels. +% +% +% if action == 'get_posterior_probs' : +% +% [L R] When action == 'get_posterior_probs', a two-component vector is +% returned, with p(Left) and p(Right). If beta=0, then p(Left) +% will be the same as the last LeftProb that was passed in. +% +% +% USAGE: +% ------ +% +% To use this plugin, the typical calls would be: +% +% 'init' : On initializing your protocol, call +% AntibiasSection(obj, 'init', x, y); +% +% 'update' : After a trial is completed, call +% AntibiasSection(obj, 'update', LeftProb, HitHist, SidesHist) +% +% 'get_posterior_probs' : After a trial is completed, and when you are +% deciding what kind of trial to make the next trial, get the plugins +% opinion on whether the next trial should be Left or Right by calling +% AntibiasSection(obj, 'get_posterior_probs') +% +% See PARAMETERS section above for the documentation of each of these calls. +% + + +function [x, y, w, h] = AntibiasSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + case 'init' % ------------ CASE INIT ---------------- + x = varargin{1}; y = varargin{2}; y0 = y; + % Save the figure and the position in the figure where we are + % going to start adding GUI elements: + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)]); + + LogsliderParam(obj, 'HitFracTau', 30, 10, 400, x, y, 'label', 'hits frac tau', ... + 'TooltipString', ... + sprintf(['\nnumber of trials back over which to compute fraction of correct trials.\n' ... + 'This is just for displaying info-- for the bias calculation, see BiasTau above'])); + set_callback(HitFracTau, {mfilename, 'update_hitfrac'}); + next_row(y); + DispParam(obj, 'LtHitFrac', 0, x, y); next_row(y); + DispParam(obj, 'RtHitFrac', 0, x, y); next_row(y); + DispParam(obj, 'HitFrac', 0, x, y); next_row(y); + + next_row(y, 0.5); + + LogsliderParam(obj, 'BiasTau', 30, 10, 400, x, y, 'label', 'antibias tau', ... + 'TooltipString', ... + sprintf(['\nnumber of trials back over\nwhich to compute fraction of correct trials\n' ... + 'for the antibias function.'])); next_row(y); + NumeditParam(obj, 'Beta', 0, x, y, ... + 'TooltipString', ... + sprintf(['When this is 0, past performance doesn''t affect choice\n' ... + 'of next trial. When this is large, the next trial is ' ... + 'almost guaranteed\nto be the one with smallest %% correct'])); next_row(y); + set_callback({BiasTau, Beta}, {mfilename, 'update_biashitfrac'}); + DispParam(obj, 'LtProb', 0, x, y); next_row(y); + DispParam(obj, 'RtProb', 0, x, y); next_row(y); + SoloParamHandle(obj, 'BiasLtHitFrac', 'value', 0); + SoloParamHandle(obj, 'BiasRtHitFrac', 'value', 0); + + SoloParamHandle(obj, 'LocalLeftProb', 'value', 0.5); + SoloParamHandle(obj, 'LocalHitHistory', 'value', []); + SoloParamHandle(obj, 'LocalPrevSides', 'value', []); + + + SubheaderParam(obj, 'title', mfilename, x, y); + next_row(y, 1.5); + + w = gui_position('get_width'); + h = y-y0; + + + case 'update' % --- CASE UPDATE ------------------- + if ~isempty(varargin) + LocalLeftProb.value = varargin{1}; + end + if length(varargin)>1 + LocalHitHistory.value = varargin{2}; + end + if length(varargin)>2 + LocalPrevSides.value = varargin{3}; + end + % Protect against somebody passing in SPHs, not actual values, by mistake: + if isa(value(LocalLeftProb), 'SoloParamHandle') + LocalLeftProb.value = value(value(LocalLeftProb)); + end + if isa(value(LocalHitHistory), 'SoloParamHandle') + LocalHitHistory.value = value(value(LocalHitHistory)); + end + if isa(value(LocalPrevSides), 'SoloParamHandle') + LocalPrevSides.value = value(value(LocalPrevSides)); + end + + feval(mfilename, obj, 'update_hitfrac'); + feval(mfilename, obj, 'update_biashitfrac'); + + + case 'update_hitfrac' % ------- CASE UPDATE_HITFRAC ------------- + if ~isempty(varargin), LocalHitHistory.value = varargin{1}; end + if length(varargin)>1, LocalPrevSides.value = varargin{2}; end + % Protect against somebody passing in SPHs, not actual values, by mistake: + if isa(value(LocalHitHistory), 'SoloParamHandle') + LocalHitHistory.value = value(value(LocalHitHistory)); + end + if isa(value(LocalPrevSides), 'SoloParamHandle') + LocalPrevSides.value = value(value(LocalPrevSides)); + end + + hit_history = value(LocalHitHistory); + hit_history = colvec(hit_history); + previous_sides = value(LocalPrevSides); + + + if ~isempty(hit_history) + kernel = exp(-(0:length(hit_history)-1)/HitFracTau)'; + kernel = kernel(end:-1:1); + HitFrac.value = sum(hit_history .* kernel)/sum(kernel); + + prevs = previous_sides(1:length(hit_history))'; + u = find(prevs == 'l'); + + if isempty(u) + LtHitFrac.value = NaN; + else + LtHitFrac.value = sum(hit_history(u) .* kernel(u))/sum(kernel(u)); + end + + u = find(prevs == 'r'); + + if isempty(u) + RtHitFrac.value = NaN; + else + RtHitFrac.value = sum(hit_history(u) .* kernel(u))/sum(kernel(u)); + end + + end + + + + case 'update_biashitfrac' % ------- CASE UPDATE_BIASHITFRAC ------------- + if ~isempty(varargin) + LocalLeftProb.value = varargin{1}; + end + if length(varargin)>1 + LocalHitHistory.value = varargin{2}; + end + if length(varargin)>2 + LocalPrevSides.value = varargin{3}; + end + + % Protect against somebody passing in SPHs, not actual values, by mistake: + if isa(value(LocalLeftProb), 'SoloParamHandle') + LocalLeftProb.value = value(value(LocalLeftProb)); + end + if isa(value(LocalHitHistory), 'SoloParamHandle') + LocalHitHistory.value = value(value(LocalHitHistory)); + end + if isa(value(LocalPrevSides), 'SoloParamHandle') + LocalPrevSides.value = value(value(LocalPrevSides)); + end + + LeftProb = value(LocalLeftProb); + hit_history = value(LocalHitHistory); + hit_history = colvec(hit_history); + previous_sides = value(LocalPrevSides); + + kernel = exp(-(0:length(hit_history)-1)/BiasTau)'; + kernel = kernel(end:-1:1); + + prevs = previous_sides(1:length(hit_history))'; + ul = find(prevs == 'l'); + if isempty(ul), BiasLtHitFrac.value = 1; + else + BiasLtHitFrac.value = sum(hit_history(ul) .* kernel(ul))/sum(kernel(ul)); + end + + ur = find(prevs == 'r'); + if isempty(ur), BiasRtHitFrac.value = 1; + else + BiasRtHitFrac.value = sum(hit_history(ur) .* kernel(ur))/sum(kernel(ur)); + end + + if isempty(ul) && ~isempty(ur) + BiasLtHitFrac.value = value(BiasRtHitFrac); + end + if isempty(ur) && ~isempty(ul) + BiasRtHitFrac.value = value(BiasLtHitFrac); + end + + choices = probabilistic_trial_selector([value(BiasLtHitFrac), value(BiasRtHitFrac)], ... + [LeftProb, 1-LeftProb], value(Beta)); + LtProb.value = choices(1); + RtProb.value = choices(2); + + + case 'get_posterior_probs' % ------- CASE GET_POSTERIOR_PROBS ------------- + x = [value(LtProb) ; value(RtProb)]; %#ok + + + case 'get' % ------- CASE GET ------------- + if length(varargin)~=1 + error('AntibiasSection:Invalid', '''get'' needs one extra param'); + end + switch varargin{1} + case 'Beta' + x = value(Beta); + case 'antibias_tau' + x = value(BiasTau); + otherwise + error('AntibiasSection:Invalid', 'Don''t know how to get %s', varargin{1}); + end + + + case 'reinit' % ------- CASE REINIT ------------- + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); +end + + + + +function [x] = colvec(x) + + if size(x,2) > size(x,1) + x = x'; + end + + +function [p] = probabilistic_trial_selector(frac, priors, beta) + +%[probs] = probabilistic_trial_selector(fractions_correct, priors, beta) +% +% Softmax trial selection, based on performance and prior distribution. +% +% Given a vector representing the fraction of correct trials (each element +% of the vector represents one stimulus type), and a prior distribution of +% desired probabilities, computes the probabilities for choosing the next +% stimulus trial. When beta=0, the probabilities returned are just the +% priors, regardless of performance. When beta is large (much bigger than +% 1), the trial type with the lowest per cent correct gets higher, all the +% others get lower. (A prior of 0, however, ensures a prob of 0). This +% function is most useful, then, for values of beta within the range 0 to +% 10. beta around 2 or 3 seems reasonable +% +% To get a single sample from probs, use Matlab's randsample.m like this: +% randsample(1:length(probs), 1, true, probs) +% This will return an index from 1 to length(probs) sampled according +% to the indicated probability. +% +% +% EXAMPLES: +% --------- +% +% >> probabilistic_trial_selector([1, 0.67, 0.3], [0.33 0.67 0], 0) +% probs = [0.33 0.67 0] +% +% >> probabilistic_trial_selector([1, 0.67, 0.3], [0.33 0.67 0], 0.5) +% probs = [0.295 0.705 0] +% +% >> probabilistic_trial_selector([1, 0.67, 0.3], [0.33 0.67 0], 10) +% probs = [0.017 0.982 0] +% + + if rows(frac)==cols(priors) + priors = priors'; + end + + p = exp(-frac*beta); + + p = p.*priors; + + p = p./sum(p); \ No newline at end of file diff --git a/Protocols/@ArpitSoundCatContinuous/ArpitSoundCatContinuous.m b/Protocols/@ArpitSoundCatContinuous/ArpitSoundCatContinuous.m new file mode 100644 index 00000000..b4e0035f --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/ArpitSoundCatContinuous.m @@ -0,0 +1,263 @@ + +function [obj] = ArpitSoundCatContinuous(varargin) + +% Default object is of our own class (mfilename); +% we inherit only from Plugins + +obj = class(struct, mfilename, pokesplot2, saveload, sessionmodel2, soundmanager, soundui, antibias, ... + water, distribui,soundtable, comments, sqlsummary, bonsaicamera); + +%--------------------------------------------------------------- +% BEGIN SECTION COMMON TO ALL PROTOCOLS, DO NOT MODIFY +%--------------------------------------------------------------- + +% If creating an empty object, return without further ado: +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')) + return; +end + +if isa(varargin{1}, mfilename) % If first arg is an object of this class itself, we are + % Most likely responding to a callback from + % a SoloParamHandle defined in this mfile. + if length(varargin) < 2 || ~ischar(varargin{2}) + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else + action = varargin{2}; varargin = varargin(3:end); %#ok + end +else % Ok, regular call with first param being the action string. + action = varargin{1}; varargin = varargin(2:end); %#ok +end + +GetSoloFunctionArgs(obj); + +switch action + + %% init + case 'init' + % dispatcher('set_trialnum_indicator_flag'); + hackvar = 10; SoloFunctionAddVars('SessionModel', 'ro_args', 'hackvar'); %#ok + SoloParamHandle(obj, 'myfig', 'saveable', 0); myfig.value = figure; + + % Make the title of the figure be the protocol name, and if someone tries + % to close this figure, call dispatcher's close_protocol function, so it'll know + % to take it off the list of open protocols. + name = mfilename; + set(value(myfig), 'Name', name, 'Tag', name, ... + 'closerequestfcn', 'dispatcher(''close_protocol'')', 'MenuBar', 'none'); + % At this point we have one SoloParamHandle, myfig + % Let's put the figure where we want it and give it a reasonable size: + set(value(myfig), 'Position', [485 144 850 680]); + + SoloParamHandle(obj, 'nsessions_healthy_number_of_pokes', 'value', 0, 'save_with_settings', 1); + SoloParamHandle(obj, 'post_DelComp_protocol', 'value', '', 'save_with_settings', 1); + SoloParamHandle(obj, 'post_DelComp_settings_filename', 'value', '', 'save_with_settings', 1); + + + SoloParamHandle(obj, 'hit_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'hit_history'}); + SoloFunctionAddVars('SideSection', 'rw_args', 'hit_history'); + + %pair_history changed to stimulus_history (from AthenaDelayComp) + SoloParamHandle(obj, 'stimulus_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'stimulus_history'}); + SoloFunctionAddVars('StimulusSection', 'rw_args', 'stimulus_history'); + + SoloParamHandle(obj, 'stimulus_distribution_history', 'value', cell(0)); + DeclareGlobals(obj, 'ro_args', {'stimulus_distribution_history'}); + SoloFunctionAddVars('StimulusSection', 'rw_args', 'stimulus_distribution_history'); + + SoloParamHandle(obj, 'stimulus_left_distribution_history', 'value', cell(0)); + DeclareGlobals(obj, 'ro_args', {'stimulus_left_distribution_history'}); + SoloFunctionAddVars('StimulusSection', 'rw_args', 'stimulus_left_distribution_history'); + + SoloParamHandle(obj, 'stimulus_right_distribution_history', 'value', cell(0)); + DeclareGlobals(obj, 'ro_args', {'stimulus_right_distribution_history'}); + SoloFunctionAddVars('StimulusSection', 'rw_args', 'stimulus_right_distribution_history'); + + SoloParamHandle(obj, 'violation_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'violation_history'}); + SoloFunctionAddVars('SideSection', 'rw_args', 'violation_history'); + + SoloParamHandle(obj, 'timeout_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'timeout_history'}); + SoloFunctionAddVars('SideSection', 'rw_args', 'timeout_history'); + + + SoundManagerSection(obj, 'init'); + + x = 5; y = 5; % Initial position on main GUI window + [x, y] = SavingSection(obj, 'init', x, y); + + %% slow ramp up of water amount + %%the water volume is controlled by a 5-parameter logistic function: WaterAmount(t) = maxasymp + (minasymp/(1+(t/inflp)^slp).^assym) + NumeditParam(obj, 'maxasymp', 30, x,y,'label','maxasymp','TooltipString',... + 'the water volume is controlled by a 5-parameter logistic function: WaterAmount(trialnum) = maxasymp + (minasymp/(1+(trialnum/inflp)^slp).^assym)'); + next_row(y); + NumeditParam(obj, 'slp', 3, x,y,'label','slp','TooltipString','Water Modulation: Slope of the logistic function'); + next_row(y); + NumeditParam(obj, 'inflp', 350, x,y,'label','inflp','TooltipString','Water Modulation: concentration at the inflection point'); + next_row(y); + NumeditParam(obj, 'minasymp', -13, x,y,'label','minasymp','TooltipString','Water Modulation: minimum asymptote'); + next_row(y); + NumeditParam(obj, 'assym', 0.7, x,y,'label','assym','TooltipString','Water Modulation: asymmetry factor'); + next_row(y); + DispParam(obj, 'trial_1', 0, x, y, 'TooltipString', 'uL on first trial'); + next_row(y); + DispParam(obj, 'trial_150', 0, x, y, 'TooltipString', 'uL on trial 150'); + next_row(y); + DispParam(obj, 'trial_300', 0, x, y, 'TooltipString', 'uL on trial 300'); + next_row(y); + set_callback({maxasymp;slp;inflp;minasymp;assym}, {mfilename, 'change_water_modulation_params'}); + feval(mfilename, obj, 'change_water_modulation_params'); + + SoloFunctionAddVars('SideSection', 'ro_args', ... + {'maxasymp';'slp';'inflp';'minasymp';'assym'}); + + figpos = get(double(gcf), 'Position'); + [expmtr, rname]=SavingSection(obj, 'get_info'); + HeaderParam(obj, 'prot_title', [mfilename ': ' expmtr ', ' rname], x, y, 'position', [10 figpos(4)-25, 800 20]); + + [x, y] = WaterValvesSection(obj, 'init', x, y);next_row(y); + % [x, y] = PokesPlotSection(obj, 'init', x, y);next_row(y); + [x, y] = CommentsSection(obj, 'init', x, y);next_row(y); + [x, y] = BonsaiCameraInterface(obj,'init',x,y,name,expmtr,rname);next_row(y); + + oldx=x; oldy=y; + next_column(x); y=5; + + [x, y] = SideSection(obj, 'init', x, y); %#ok + next_row(y, 1.3); + [x, y] = PerformanceSection(obj, 'init', x, y); + next_row(y, 1.3); + [x, y] = PsychometricSection(obj, 'init', x, y); + x=oldx; y=oldy; + SessionDefinition(obj, 'init', x, y, value(myfig)); next_row(y, 2); %#ok + + % saving auto frequency to 20 just in case changed running ephys + % experiment. If so then it will again change back to 1; + SavingSection(obj,'set_autosave_frequency',20); + + ArpitSoundCatContinuousSMA(obj, 'init'); + + % feval(mfilename, obj, 'prepare_next_trial'); + + case 'change_water_modulation_params' + display_guys = [1 150 300]; + for i=1:numel(display_guys) + t = display_guys(i); + + myvar = eval(sprintf('trial_%d', t)); + myvar.value = maxasymp + (minasymp/(1+(t/inflp)^slp).^assym); + end + + %% when user presses run on runrats then then is called + case 'start_recording' + + BonsaiCameraInterface(obj,'record_start'); + + case 'set_setting_params' % special case used during ephys experiments when not using runrats + + SavingSection(obj,'set','ratname',varargin{1}); + SavingSection(obj,'set','experimenter',varargin{2}); + SavingSection(obj,'set_setting_info',varargin{3},varargin{4}); + SavingSection(obj,'set_autosave_frequency',1); % saving setting every trial instead of 20 + BonsaiCameraInterface(obj,'set_video_filepath',varargin{5}); + + case 'set_stim_distribution' + + if strcmpi(varargin{1},'random') + rand_num = rand(1); + if rand_num > 0.5 + StimulusSection(obj,'Pushbutton_SwitchDistribution','Hard B'); + else + StimulusSection(obj,'Pushbutton_SwitchDistribution','Hard A'); + end + else + StimulusSection(obj,'Pushbutton_SwitchDistribution',varargin{1}); + end + + case 'psychometricUpdate_aftercrash' + + PsychometricSection(obj, 'reload_after_crash'); + + %% prepare next trial + case 'prepare_next_trial' + + SideSection(obj, 'prepare_next_trial'); + % Run SessionDefinition *after* SideSection so we know whether the + % trial was a violation or not + % SessionDefinition(obj, 'next_trial'); + StimulatorSection(obj, 'update_values'); + StimulusSection(obj,'prepare_next_trial'); + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + [sma, prepare_next_trial_states] = ArpitSoundCatContinuousSMA(obj, 'prepare_next_trial'); + + % Default behavior of following call is that every 20 trials, the data + % gets saved, not interactive, no commit to CVS. + SavingSection(obj, 'autosave_data'); + + CommentsSection(obj, 'clear_history'); % Make sure we're not storing unnecessary history + if n_done_trials==1 % Auto-append date for convenience. + CommentsSection(obj, 'append_date'); CommentsSection(obj, 'append_line', ''); + end + + %% trial_completed + case 'trial_completed' + + % Change the video trial + BonsaiCameraInterface(obj,'next_trial'); + + % Update the Metrics Calculated + PerformanceSection(obj,'evaluate'); + PsychometricSection(obj, 'update'); + + % Do any updates in the protocol that need doing: + feval(mfilename, 'update'); + + %% update + case 'update' + % PokesPlotSection(obj, 'update'); + if n_done_trials==1 + [expmtr, rname]=SavingSection(obj, 'get_info'); + prot_title.value=[mfilename ' on rig ' get_hostname ' : ' expmtr ', ' rname '. Started at ' datestr(now, 'HH:MM')]; + end + + %% close + case 'close' + % PokesPlotSection(obj, 'close'); + SideSection(obj, 'close'); + StimulusSection(obj,'close'); + BonsaiCameraInterface(obj,'close'); + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)) %#ok + delete(value(myfig)); + end + delete_sphandle('owner', ['^@' class(obj) '$']); + + + %% end_session + case 'end_session' + prot_title.value = [value(prot_title) ', Ended at ' datestr(now, 'HH:MM')]; + BonsaiCameraInterface(obj,'stop') % Stopping the cameras + + + %% pre_saving_settings + case 'pre_saving_settings' + + StimulusSection(obj,'hide'); + PsychometricSection(obj,'hide'); + SessionDefinition(obj, 'run_eod_logic_without_saving'); + + % Sending Summary Statistics to SQL Database + %perf = PsychometricSection(obj, 'evaluate'); + + % SoundCatContextSwitchSummary(obj,'protocol_data',perf); + + %% otherwise + otherwise + warning('Unknown action! "%s"\n', action); +end + +return; + diff --git a/Protocols/@ArpitSoundCatContinuous/ArpitSoundCatContinuousSMA.m b/Protocols/@ArpitSoundCatContinuous/ArpitSoundCatContinuousSMA.m new file mode 100644 index 00000000..10ccb91a --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/ArpitSoundCatContinuousSMA.m @@ -0,0 +1,358 @@ +function [varargout] = ArpitSoundCatContinuousSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + + case 'prepare_next_trial' + + %% Setup water + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); % distribution based sound + sound_duration = value(A1_time); % SoundManagerSection(obj, 'get_sound_duration', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = value(time_go_cue); % SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + + [LeftWValveTime,RightWValveTime] = SideSection(obj, 'get_water_amount'); + side = SideSection(obj, 'get_current_side'); + + if strcmpi(side_lights,'none') + Side_LED = 0; + else + Side_LED = 1; + end + + if side == 'l' + HitEvent = 'Lin'; + ErrorEvent = 'Rin'; + % sound_id = sone_sound_id; + if strcmpi(side_lights,'correct side') | strcmpi(side_lights,'none') + SideLight = left1led; + elseif strcmpi(side_lights,'anti side') + SideLight = right1led; + elseif strcmpi(side_lights,'both') + SideLight = left1led+right1led; + end + WValveTime = LeftWValveTime; + WValveSide = left1water; + second_hit_light = left1led; + else + HitEvent = 'Rin'; + ErrorEvent = 'Lin'; + % sound_id = stwo_sound_id; + if strcmpi(side_lights,'correct side') | strcmpi(side_lights,'none') + SideLight = right1led; + elseif strcmpi(side_lights,'anti side') + SideLight = left1led; + elseif strcmpi(side_lights,'both') + SideLight = left1led+right1led; + end + second_hit_light = right1led; + WValveTime = RightWValveTime; + WValveSide = right1water; + end + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + %%%%%%%%%%%%%%%% SCHEDULED WAVES %%%%%%%%%%%%%%%%%%%%%%% + + % scheduled wave for stimuli / fixed (No) sound, based upon side + if value(stimuli_on) + sma = add_scheduled_wave(sma, 'name', 'stimplay', 'preamble', PreStim_time, ... + 'sustain', sound_duration, 'sound_trig', A1_sound_id); % to play a sound before Go Cue + else + % sma = add_scheduled_wave(sma, 'name', 'stimplay', 'preamble', PreStim_time, ... + % 'sustain', sound_duration, 'sound_trig', sound_id); % to play a fixed sound before Go Cue + sma = add_scheduled_wave(sma, 'name', 'stimplay', 'preamble', PreStim_time, ... + 'sustain', sound_duration); % to play No sound before Go Cue + end + + % Scheduled Wave for Go Sound + + % if value(Go_Sound) == 1 + + sma = add_scheduled_wave(sma, 'name', 'Go_Cue', 'preamble', 0.001, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); % to play the Go Cue/Reward Sound + % else + % sma = add_scheduled_wave(sma, 'name', 'Go_Cue', 'preamble', 0.001, ... + % 'sustain', go_cue_duration); % to play the Go Cue/Reward Sound + % end + + % Scheduled wave for CP Duration + if CP_duration <= (SettlingIn_time + legal_cbreak) + sma = add_scheduled_wave(sma, 'name', 'CP_Duration_wave', 'preamble', CP_duration); % total length of centre poke to consider success + else + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); % intial fidgety period without violation + sma = add_scheduled_wave(sma, 'name', 'CP_Duration_wave', 'preamble', CP_duration - SettlingIn_time); % total length of centre poke minus the inital fidgety time to consider success + end + + % scheduled wave for rewarded side either of the side + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', 0.01, ... + 'sustain', WValveTime, 'DOut', WValveSide); % water delivery side + sma = add_scheduled_wave(sma, 'name', 'reward_collection_dur', 'preamble', SideLed_duration + RewardCollection_duration); % time to collect the reward + + % Scheduled Wave for Ephys Trial + + if ~isnan(bSettings('get', 'DIOLINES', 'trialnum_indicator')) + trigephys = bSettings('get', 'DIOLINES', 'trialnum_indicator'); + else + trigephys = nan; + end + + if strcmpi(value(StimLine),'Ephys') + sma = add_scheduled_wave(sma, 'name', 'EphysTrig', 'preamble', 0, 'sustain', ... + 0.4, 'DOut', trigephys, 'loop', 0); %for Ephys + else + sma = add_scheduled_wave(sma, 'name', 'EphysTrig', 'preamble', 0, 'sustain', 0); %dummy wave. + end + + %% + %%%%%%%%%%% % *STATES* %%%%%%%%%%%%%%%%%%% + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','settling_in_state';'Tup','timeout_state'}); + + %%%%%%%%%%%%% SETTLING IN STATE START %%%%%%%%%%%%%%%%%%%% + % Before progressing check if its still centre poking or pokes within legal c_break other wise its a violation + + if CP_duration <= SettlingIn_time + legal_cbreak % state machine during initial warm-up when starting a new session + + sma = add_state(sma,'name','settling_in_state','self_timer',CP_duration, ... + 'output_actions', {'SchedWaveTrig', 'CP_Duration_wave'}, ... + 'input_to_statechange', {'Cout','current_state + 1';'Tup','side_led_wait_RewardCollection'}); + + % Intermediate State + + % This intermediate state is considering the poke is out before the end of settling time / at the start of this state + sma = add_state(sma,'self_timer',CP_duration,'output_actions', {'DOut', center1led * LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection';'Cin','current_state + 1';'Tup','side_led_wait_RewardCollection'}); + + % The state jump to here when the nose is still in then go + % directly to give reward + sma = add_state(sma,'self_timer',CP_duration,... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection'; 'Cout','current_state - 1';'Tup','side_led_wait_RewardCollection'}); + + else % the usual state machine + + sma = add_state(sma,'name','settling_in_state','self_timer',SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'settling_period'}, ... + 'input_to_statechange', {'Cout','current_state + 1';'Tup','soft_cp'}); + + % Intermediate State + + % This intermediate state is considering the poke is out before the end of settling time / at the start of this state + sma = add_state(sma,'self_timer',SettlingIn_time,'output_actions', {'DOut', center1led * LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'settling_period_In','legal_poke_start_state';'Cin','current_state + 1';'Tup','legal_poke_start_state';... + 'Rin', 'violation_state';'Rout', 'violation_state'; 'Lin', 'violation_state';'Lout', 'violation_state'}); + + % The state jump to here when the nose is still in then go directly to soft_cp + sma = add_state(sma,'self_timer',SettlingIn_time,... + 'input_to_statechange', {'settling_period_In','soft_cp'; 'Cout','current_state - 1';'Tup','soft_cp';... + 'Rin', 'violation_state'; 'Rout', 'violation_state'; 'Lin', 'violation_state'; 'Lout', 'violation_state'}); + + %%%%%%%%%%%%% SETTLING IN STATE END %%%%%%%%%%%%%%%%%%%%%% + + % STATE TO CHECK BEFORE START OF LEGAL POKE PERIOD + + sma = add_state(sma,'name','legal_poke_start_state','self_timer',legal_cbreak/2, ... + 'output_actions', {'DOut', center1led * LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Cin','soft_cp'; 'Tup','violation_state';... + 'Rin', 'violation_state'; 'Rout', 'violation_state'; 'Lin', 'violation_state'; 'Lout', 'violation_state'}); %more stringent by giving half the legal cp time + + %%%%%%%%%%%% LEGAL SOFT POKE STATE START %%%%%%%%%%%%%%%%% + + sma = add_state(sma,'name','soft_cp','self_timer',CP_duration - SettlingIn_time, ... CP_Duration_wave + 'output_actions', {'SchedWaveTrig', 'CP_Duration_wave+stimplay'},... + 'input_to_statechange', {'Cout','current_state + 1'; 'Tup','side_led_wait_RewardCollection';... + 'Rin', 'violation_state'; 'Rout', 'violation_state'; 'Lin', 'violation_state'; 'Lout', 'violation_state'}); %more stringent by giving half the legal cp time + + end + + % Intermediate State + + % This intermediate state is considering the poke is out before the end of settling time / at the start of this state + sma = add_state(sma,'self_timer',legal_cbreak,'output_actions', {'DOut', center1led * LED_during_legal_cbreak}, ... + 'input_to_statechange', {'CP_Duration_wave_In','legal_poke_end_state';'Cin','current_state + 1';'Tup','violation_state';... + 'Rin', 'violation_state';'Rout', 'violation_state'; 'Lin', 'violation_state';'Lout', 'violation_state'}); + + % The state jump to here when the nose is still in then go directly to soft_cp + sma = add_state(sma,'self_timer',CP_duration - SettlingIn_time, ... + 'input_to_statechange', {'CP_Duration_wave_In','side_led_wait_RewardCollection';'Cout','current_state - 1';'Tup','side_led_wait_RewardCollection';... + 'Rin', 'violation_state';'Rout', 'violation_state'; 'Lin', 'violation_state';'Lout', 'violation_state'}); + + %%%%%%%%%%%% LEGAL SOFT POKE STATE END %%%%%%%%%%%%%%%%% + + % Before giving the reward check if its still centre poking or pokes + % within legal c_break other wise its a violation + + sma = add_state(sma,'name','legal_poke_end_state','self_timer',legal_cbreak/2, ... + 'output_actions', {'DOut', center1led * LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Cin','side_led_wait_RewardCollection'; 'Tup','violation_state'}); + + + %%%%%%%%%%%%%%% REWARD COLLECTION STATE START %%%%%%%%%%%%%%% + + sma = add_state(sma, 'name', 'side_led_wait_RewardCollection', 'self_timer', SideLed_duration + RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight * Side_LED; 'SchedWaveTrig', 'reward_collection_dur+Go_Cue'}, ... + 'input_to_statechange',{HitEvent,'hit_state'; 'Tup','timeout_state'; ErrorEvent,'second_hit_state'}); + + % Based upon whether the user has selected reward to be given + % always or with delay or no reward at all for error event + + if strcmp(reward_type, 'Always') + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', second_hit_light},... + 'input_to_statechange',{'reward_collection_dur_In', 'timeout_state'; 'Tup','timeout_state'; HitEvent,'hit_state'}); + + elseif strcmp(reward_type, 'DelayedReward') + + sma = add_state(sma,'name','second_hit_state','self_timer',secondhit_delay,... + 'input_to_statechange',{'Tup','current_state + 1'}); + + sma = add_state(sma,'self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', second_hit_light},... + 'input_to_statechange',{'Tup','timeout_state'; HitEvent,'hit_state'}); + + else % no reward but a punishment iti + + sma = add_state(sma,'name','second_hit_state','self_timer',2,... + 'output_actions',{'DOut', second_hit_light},... + 'input_to_statechange',{'Tup','current_state+1'}); + + sma = add_state(sma,'self_timer',max(0.001,error_iti - 2),... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + % sma = add_state(sma, 'name', 'hit_state'); + % sma = add_state(sma, 'name', 'drink_state'); + + end + + sma = add_state(sma,'name','hit_state','self_timer',reward_delay,... + 'output_actions', {'DOut', second_hit_light; 'SchedWaveTrig','reward_delivery'},... + 'input_to_statechange',{'Tup','drink_state'}); + + sma = add_state(sma,'name','drink_state','self_timer',drink_time,... + 'output_actions', {'DOut', second_hit_light},... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + %%%%%%%%%%%%%%% FAILURE TO CENTRE POKE %%%%%%%%%%%%%%%%%%%%%% + + % For Timeout + + sma = add_state(sma,'name','timeout_state','self_timer',timeout_snd_duration,... + 'output_actions', {'SoundOut',to_sound_id; 'SchedWaveTrig', '-Go_Cue'},... + 'input_to_statechange',{'Tup','current_state+1'}); + sma = add_state(sma, 'self_timer', max(0.001, timeout_iti-timeout_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + % For Violations + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SoundOut',viol_sound_id; 'DOut', center1led; 'SchedWaveTrig', '-Go_Cue-stimplay-CP_Duration_wave'},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.8,... + 'output_actions', {'SchedWaveTrig','EphysTrig'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'side_led_wait_RewardCollection','hit_state','second_hit_state','drink_state', 'violation_state','timeout_state'}; + + % After defining the states for behavior, adding states for + % electrophysiology or LED stimulator. + + if strcmpi(value(StimLine),'Opto') + sma = StimulatorSection(obj,'prepare_next_trial',sma); + % elseif strcmpi(value(StimLine),'Ephys') + % sma = add_trialnum_indicator(sma, n_done_trials+1); + end + + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + + case 'get_state_colors' + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'settling_in_state', [0.63 1 0.94], ... + 'legal_poke_start_state', [0.63 1 0.94]*0.8, ... + 'legal_poke_end_state', [1 0.79 0.63], ... + 'soft_cp', [0.3 0.9 0], ... + 'side_led_wait_RewardCollection', [0.53 0.78 1.00],... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + + + % 'lefthit', [0 0.9 0.3], ... + % 'error_state', [1 0.54 0.54], ... + + + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + case 'close' + + + case 'reinit' + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end + + +end + + + diff --git a/Protocols/@ArpitSoundCatContinuous/PerformanceSection.m b/Protocols/@ArpitSoundCatContinuous/PerformanceSection.m new file mode 100644 index 00000000..2dd7ee80 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/PerformanceSection.m @@ -0,0 +1,133 @@ +% [x, y] = PerformanceSection(obj, action, x,y) +% +% Reports overall performance. Uses training_stage from SideSection. +% +% PARAMETERS: +% ----------- +% +% action One of: +% 'init' To initialise the section and set up the GUI +% for it +% +% 'close' Delete all of this section's GUIs and data +% +% 'reinit' Delete all of this section's GUIs and data, +% and reinit, at the same position on the same +% figure as the original section GUI was placed. +% +% 'evalueta' Look at history and compute hit fraction, etc. +% +% x, y Only relevant to action = 'init'; they indicate the initial +% position to place the GUI at, in the current figure window +% +% RETURNS: +% -------- +% +% perf When action == 'init', returns x and y, pixel positions on +% the current figure, updated after placing of this section's GUI. +% When action == 'evaluate', returns a vector with elements +% [ntrials, violation_percent, left_hit_frac, right_hit_frac, hit_frac] +% +% + +% CDB, 23-March-2013 + +function [x, y] = PerformanceSection(obj, action, x,y) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + + DispParam(obj, 'ntrials', 0, x, y); next_row(y); + DispParam(obj, 'ntrials_valid', 0, x, y); next_row(y); + DispParam(obj, 'violation_percent', 0, x, y, 'TooltipString', ... + 'Fraction of trials with a center poke violation'); next_row(y); + DispParam(obj, 'timeout_percent', 0, x, y, 'TooltipString', ... + 'Fraction of trials with timeout'); next_row(y); + DispParam(obj, 'Left_hit_frac', 0, x, y, 'TooltipString', ... + 'Fraction of correct Left trials'); next_row(y); + DispParam(obj, 'Right_hit_frac', 0, x, y, 'TooltipString', ... + 'Fraction of correct Right trials'); next_row(y); + DispParam(obj, 'hit_frac', 0, x, y, 'TooltipString', ... + 'Fraction of correct trials'); next_row(y); + + SubheaderParam(obj, 'title', 'Overall Performance', x, y); + next_row(y, 1.5); + % SoloParamHandle(obj, 'previous_parameters', 'value', []); + + + % ------------------------------------------------------------------ + % evaluate + % ------------------------------------------------------------------ + + case 'evaluate' + + + if n_done_trials > 1 + try + ntrials.value = n_done_trials; + ntrials_valid.value = numel(find(~isnan(hit_history))); + violation_percent.value = numel(find(violation_history))/n_done_trials; + timeout_percent.value = numel(find(timeout_history))/n_done_trials; + goods = ~isnan(hit_history(1:n_done_trials)); + lefts = previous_sides(1:n_done_trials)=='l'; + rights = previous_sides(1:n_done_trials)=='r'; + goods = goods(:); + lefts = lefts(:); + rights = rights(:); + Left_hit_frac.value = mean(hit_history(goods & lefts)); + Right_hit_frac.value = mean(hit_history(goods & rights)); + hit_frac.value = mean(hit_history(goods)); + catch + end + end + + if nargout > 0 + x = [n_done_trials, value(violation_percent), value(timeout_percent), value(Left_hit_frac), ... + value(Right_hit_frac), value(hit_frac)]; + end + + + % ------------------------------------------------------------------ + % close + % ------------------------------------------------------------------ + + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % ------------------------------------------------------------------ + % reinit + % ------------------------------------------------------------------ + + case 'reinit' + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + +end + + diff --git a/Protocols/@ArpitSoundCatContinuous/PsychometricSection.m b/Protocols/@ArpitSoundCatContinuous/PsychometricSection.m new file mode 100644 index 00000000..76c1a73e --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/PsychometricSection.m @@ -0,0 +1,500 @@ +function varargout = PsychometricSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + + case 'init' + if length(varargin) < 2 + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end + x = varargin{1}; y = varargin{2}; + + % Display on the main GUI + + % Context 1 + DispParam(obj, 'Context3_trialStart', 1,x,y, 'position', [x, y, 100 20],'label','t_start','TooltipString','3rd context started at this trial'); + DispParam(obj, 'Context3_trialEnd', 1,x,y, 'position', [x + 101, y, 100 20],'label','t_end','TooltipString','3rd context ended at this trial'); + next_row(y); + DispParam(obj, 'Context3_Dist', Category_Dist, x,y,'label','Context3_Distr','TooltipString','stim distribution for 3rd context'); + make_invisible(Context3_Dist); make_invisible(Context3_trialStart);make_invisible(Context3_trialEnd); + next_row(y); + + % Context 2 + DispParam(obj, 'Context2_trialStart', 1,x,y, 'position', [x, y, 100 20],'label','t_start','TooltipString','2nd context started at this trial'); + DispParam(obj, 'Context2_trialEnd', 1,x,y, 'position', [x + 101, y, 100 20],'label','t_end','TooltipString','2nd context ended at this trial'); + next_row(y); + DispParam(obj, 'Context2_Dist', Category_Dist, x,y,'label','Context2_Distr','TooltipString','stim distribution for 2nd context'); + make_invisible(Context2_Dist); make_invisible(Context2_trialStart);make_invisible(Context2_trialEnd); + next_row(y); + + % Context 3 + DispParam(obj, 'Context1_trialStart', 1,x,y, 'position', [x, y, 100 20],'label','Trial_start','TooltipString','1st context started at this trial'); + DispParam(obj, 'Context1_trialEnd', 1,x,y, 'position', [x + 101, y, 100 20],'label','Trial_end','TooltipString','1st context ended at this trial'); + next_row(y); + DispParam(obj, 'Context1_Dist', Category_Dist, x,y,'label','Context1_Distr','TooltipString','stim distribution for 1st context'); + next_row(y); + + PushbuttonParam(obj, 'Switch_Distr', x, y, 'label', 'Change Stim Distribution','TooltipString', 'Change the context by switching distribution'); + set_callback(Switch_Distr, {mfilename, 'PushButton_Distribution_Switch'}); %#ok (Defined just above) + next_row(y,2) + NumeditParam(obj,'trial_plot',30,x,y,'label','Trails 2 Plot','TooltipString','update psychometric curve after these many valid trials'); + next_row(y); + ToggleParam(obj, 'PsychometricShow', 0, x, y, 'OnString', 'Psychometric Show', ... + 'OffString', 'Psychometric Hidden', 'TooltipString', 'Show/Hide Psychometric panel'); + set_callback(PsychometricShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + SubheaderParam(obj, 'title', 'Psychometric Section', x, y);next_row(y); + + oldx=x; oldy=y; parentfig=double(gcf); + + % --- State Management with SoloParamHandles --- + state.last_analyzed_valid_trial = 0; + state.block_count = 0; + state.context_blocks = 0; + state.table_row_editable = []; + state.blockStatsHistory = struct('indices', {}, 'hitRates', {}, 'stimCounts', {}); + SoloParamHandle(obj, 'states_value', 'value', state); + SoloParamHandle(obj, 'thiscontext', 'value', 1); + SoloParamHandle(obj, 'last_trial_plotted', 'value', 0); + + vars = ["Select","Rule","DistributionLeft","DistributionRight","Start_trial","End_trial","Slope","TrueBoundary",... + "CalBoundary","LapseA","LapseB","fit_Method", "Overall Hit %", "Left Hit %", "Right Hit %"]; + vars_type = ["logical","string","string","string","double","double","double","double","double",... + "double","double","string","double","double","double"]; + t = table('Size', [0, numel(vars)], 'VariableTypes', vars_type, 'VariableNames', vars); + + + % --- GUI Figure and Component Handles --- + + % Main analysis figure (web-based uifigure) + SoloParamHandle(obj, 'myfig', 'value', figure('closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'],... + 'Name', 'Real-Time Analysis', 'Units', 'normalized','Position', [0.3, 0.1, 0.6, 0.8],'Visible', 'off'), 'saveable', false); + + % --- pre-Define Controls for the Bottom Panel later we will arrange it into panel. This is because if not done before it + % removes all the previous created axes panel. --- + + % Edit Boxes + NumeditParam(obj, 'Plot_Trial_Start', 1, 1, 1,'TooltipString', 'Start trial for custom plot'); + NumeditParam(obj, 'Plot_Trial_End', 30, 1, 1, 'TooltipString', 'End trial for custom plot'); + % Push Buttons + ToggleParam(obj, 'Show_Table_Toggle', 0, 1, 1, 'OnString', 'Table Shown', 'OffString', 'Show Table'); + PushbuttonParam(obj, 'Plot_Custom_Button', 1, 1, 'label', 'Plot Custom Range'); + PushbuttonParam(obj, 'Plot_Context_Button', 1, 1, 'label', 'Plot Context'); + + % --- Create Panels for Layout --- + % Top panel for live plots + hndl_live_plots_panel = uipanel('Parent', value(myfig), 'Title', 'Live Update Plots', ... + 'Units', 'normalized', 'Position', [0.05, 0.55, 0.9, 0.43]); + + % Middle panel for custom plots + hndl_custom_plots_panel = uipanel('Parent', value(myfig), 'Title', 'Custom Range Plots', ... + 'Units', 'normalized', 'Position', [0.05, 0.2, 0.9, 0.33]); + + % Bottom panel for controls + hndl_controls_panel = uipanel('Parent', value(myfig), 'Title', 'Controls', ... + 'Units', 'normalized', 'Position', [0.05, 0.02, 0.9, 0.16]); + + % --- Define the 6 Axes and store in a struct --- + axes_h.live_psych = axes('Parent', hndl_live_plots_panel, 'Units', 'normalized', 'Position', [0.06, 0.1, 0.28, 0.8]); + title(axes_h.live_psych, 'Live Psychometric'); ylabel(axes_h.live_psych, 'P(Right)'); + + axes_h.live_hitrate = axes('Parent', hndl_live_plots_panel, 'Units', 'normalized', 'Position', [0.38, 0.1, 0.28, 0.8]); + title(axes_h.live_hitrate, 'Live Hit Rate'); xlabel(axes_h.live_hitrate, 'Block'); + + axes_h.live_stim = axes('Parent', hndl_live_plots_panel, 'Units', 'normalized', 'Position', [0.7, 0.1, 0.28, 0.8]); + title(axes_h.live_stim, 'Live Stimulus Dist.'); + + axes_h.custom_psych = axes('Parent', hndl_custom_plots_panel, 'Units', 'normalized', 'Position', [0.06, 0.1, 0.28, 0.8]); + title(axes_h.custom_psych, 'Custom Psychometric'); ylabel(axes_h.custom_psych, 'P(Right)'); + + axes_h.custom_hitrate = axes('Parent', hndl_custom_plots_panel, 'Units', 'normalized', 'Position', [0.38, 0.1, 0.28, 0.8]); + title(axes_h.custom_hitrate, 'Custom Hit Rate'); + + axes_h.custom_stim = axes('Parent', hndl_custom_plots_panel, 'Units', 'normalized', 'Position', [0.7, 0.1, 0.28, 0.8]); + title(axes_h.custom_stim, 'Custom Stimulus Dist.'); + + % Store the entire struct of handles in a single SoloParamHandle + SoloParamHandle(obj, 'PlotAxes', 'value', axes_h, 'saveable', false); + + % --- Arrange Controls in the Bottom Panel --- + + % Left Column: Show Table Toggle + set(get_ghandle(Show_Table_Toggle), 'Parent', hndl_controls_panel, 'Units', 'normalized', 'Position', [0.03, 0.25, 0.18, 0.5]); + + % Center Column: Custom Trial Plotting + uicontrol('Parent', hndl_controls_panel, 'Style','text', 'String','Start Trial', 'Units','normalized', 'Position',[0.25, 0.7, 0.1, 0.2]); + set(get_ghandle(Plot_Trial_Start), 'Parent', hndl_controls_panel, 'Units', 'normalized', 'Position', [0.35, 0.65, 0.1, 0.3]); + delete(get_lhandle(Plot_Trial_Start)); % remove bcontrol label + uicontrol('Parent', hndl_controls_panel, 'Style','text', 'String','End Trial', 'Units','normalized', 'Position',[0.47, 0.7, 0.1, 0.2]); + set(get_ghandle(Plot_Trial_End), 'Parent', hndl_controls_panel, 'Units', 'normalized', 'Position', [0.57, 0.65, 0.1, 0.3]); + delete(get_lhandle(Plot_Trial_End)); % remove bcontrol label + set(get_ghandle(Plot_Custom_Button), 'Parent', hndl_controls_panel, 'Units', 'normalized', 'Position', [0.35, 0.15, 0.22, 0.4]); + + % Right Column: Context Plot Button + set(get_ghandle(Plot_Context_Button), 'Parent', hndl_controls_panel, 'Units', 'normalized', 'Position', [0.68, 0.25, 0.18, 0.5]); + + % Far Right Column: Live Update Checkboxes + SoloParamHandle(obj, 'Update_Psychometric', 'value', uicontrol('Parent', hndl_controls_panel, 'Style', 'checkbox', 'Units', 'normalized', 'String', 'Psych', 'Value', 1, 'Position', [0.9, 0.65, 0.08, 0.3]),'saveable', false); + SoloParamHandle(obj, 'Update_HitRate', 'value', uicontrol('Parent', hndl_controls_panel, 'Style', 'checkbox', 'Units', 'normalized', 'String', 'HitRate', 'Value', 1, 'Position', [0.9, 0.35, 0.08, 0.3]),'saveable', false); + SoloParamHandle(obj, 'Update_Stimulus', 'value', uicontrol('Parent', hndl_controls_panel, 'Style', 'checkbox', 'Units', 'normalized', 'String', 'Stim', 'Value', 1, 'Position', [0.9, 0.05, 0.08, 0.3]),'saveable', false); + + % --- Set Callbacks --- + set_callback(Show_Table_Toggle, {mfilename, 'show_hide_table'}); + set_callback(Plot_Custom_Button, {mfilename, 'PushButton_SelectedTrial'}); + set_callback(Plot_Context_Button, {mfilename, 'PushButton_Context'}); % To be defined later + + % --- Separate, Initially Invisible Figure for the Table --- + + SoloParamHandle(obj, 'myfig_table', 'value', uifigure('closerequestfcn', [mfilename '(' class(obj) ', ''hide_table'');'],... + 'Name', 'Psychometric Summary Table', 'Units', 'normalized','Visible', 'off','Position', [0.1, 0.2, 0.4, 0.6]), 'saveable', false); + + SoloParamHandle(obj, 'uit', 'value', uitable(value(myfig_table), 'Data', t, ... + 'Units', 'normalized', 'Position', [0.02 0.02 0.96 0.96], ... + 'ColumnEditable', [true, false, false, false, false, false, false, false, false, false, false, false], ... + 'CellEditCallback',@(src, evt) PsychometricSection(obj, 'check_box_table', src, evt)), ... % end of uitable definition + 'saveable', true); + + SoloFunctionAddVars('SideSection', 'rw_args', ... + {'Switch_Distr'}); + + % Returning the x , y position for the main callback GUI + varargout{1} = oldx; + varargout{2} = oldy; + + %% Calculate Parameters + case 'Calculate_Params' + + try + % figure out if psychometric, change the sides from ascii 'l' or 'r' to 0 and 1 + sides = zeros(size(previous_sides)); + sides(previous_sides == 114) = 1; + + data.hit_history = hit_history(1:n_done_trials); + data.previous_sides = sides(1:n_done_trials); + data.stim_history = stimulus_history(1:n_done_trials); + data.full_rule_history = Rule; + data.full_dist_right = stimulus_right_distribution_history(1:n_done_trials); + data.full_dist_left = stimulus_left_distribution_history(1:n_done_trials); + + handles.ui_table = value(uit); + handles.main_fig = value(myfig); + handles.axes_h = value(PlotAxes); + + Stim_Params = StimulusSection(obj,'stim_params'); + config.trials_per_block = value(trial_plot); + config.true_mu = Stim_Params(2); + config.stimuli_range = [Stim_Params(1), Stim_Params(3)]; + config.debug = false; + + state = value(states_value); + + % make the table_row of same size as table rows + required_length = height(handles.ui_table.Data); % Get the required length from the table height + % Case 1: The field doesn't exist yet. Initialize it. + if ~isfield(state, 'table_row_editable') + % Create a column vector of 'false' values matching the table height. + state.table_row_editable = false(required_length, 1); + % Case 2: The field exists. Check if it needs resizing. + elseif numel(state.table_row_editable) ~= required_length + current_length = numel(state.table_row_editable); + if required_length > current_length + % The table is LONGER, so append 'false' values. + num_to_add = required_length - current_length; + state.table_row_editable = [state.table_row_editable(:); false(num_to_add, 1)]; + else + % The table is SHORTER, so truncate the vector. This is more readable. + state.table_row_editable = state.table_row_editable(1:required_length); + end + end + % Optional: Ensure it's a column vector for consistency + state.table_row_editable = state.table_row_editable(:); + + % psychometric, hit rate and stim correct/incorrect histogram to be plotted or not + psych_obj = value(Update_Psychometric); + flags.psych = logical(psych_obj.Value); + + hit_obj = value(Update_HitRate); + flags.hit = logical(hit_obj.Value); + + stim_obj = value(Update_Stimulus); + flags.stim = logical(stim_obj.Value); + + varargout{1} = state; + varargout{2} = data; + varargout{3} = handles; + varargout{4} = config; + varargout{5} = flags; + + catch + varargout{1} = []; + varargout{2} = []; + varargout{3} = []; + varargout{4} = []; + varargout{5} = []; + end + + + %% Other actions + + case 'PushButton_Distribution_Switch' + + % --- Confirmation Dialog --- + % Display a dialog box to ask the user for confirmation before proceeding. + % 'questdlg' creates a dialog with a question, title, and button options. + choice = questdlg('Are you sure you want to switch the distribution?', ... + 'Confirm Action', ... + 'Yes', 'No', 'No'); % The last 'No' sets it as the default button. + + % --- Handle User's Response --- + % The code proceeds only if the user clicks the 'Yes' button. + % The strcmp function compares the 'choice' variable with 'Yes'. + if strcmp(choice, 'Yes') + + % --- Original Code Execution --- + % This is your original code, which will run after confirmation. + + % eval(sprintf('present_context_dist = value(Context%i_Dist)',value(thiscontext))); + eval(sprintf('present_context_start = value(Context%i_trialStart);',value(thiscontext))); + eval(sprintf('present_context_end = value(Context%i_trialEnd);',value(thiscontext))); + + if ~strcmpi(Category_Dist,'Uniform') && present_context_end > present_context_start + + if value(thiscontext) < 3 % & ~strcmpi(present_context_dist,Category_Dist) + thiscontext.value = value(thiscontext) + 1; + eval(sprintf('Context%i_Dist.value = Category_Dist;',value(thiscontext))); + eval(sprintf('Context%i_trialStart.value = n_done_trials + 1;',value(thiscontext))); + eval(sprintf('Context%i_trialEnd.value = n_done_trials + 1;',value(thiscontext))); + eval(sprintf('make_visible(Context%i_Dist);',value(thiscontext))); + eval(sprintf('make_visible(Context%i_trialStart);',value(thiscontext))); + eval(sprintf('make_visible(Context%i_trialEnd);',value(thiscontext))); + end + if strcmpi(Category_Dist,'Hard A') + StimulusSection(obj,'Pushbutton_SwitchDistribution','Hard B'); + elseif strcmpi(Category_Dist,'Hard B') + StimulusSection(obj,'Pushbutton_SwitchDistribution','Hard A'); + end + + % Also update the table and make changes to plot + [state, data, handles, config, flags] = PsychometricSection(obj,'Calculate_Params'); + % Calling the function to update the table and plot + state = RealTimeAnalysis('context_switch', state, data, handles, config, flags); + states_value.value = state; + end + + end % This 'end' closes the 'if strcmp(choice, 'Yes')' block. + % If the user clicks 'No' or closes the dialog, the code inside the if-block is skipped. + + + case 'StimSection_Distribution_Switch' + + eval(sprintf('present_context_start = value(Context%i_trialStart);',value(thiscontext))); + eval(sprintf('present_context_end = value(Context%i_trialEnd);',value(thiscontext))); + + if present_context_end > present_context_start + if value(thiscontext) < 3 % & ~strcmpi(present_context_dist,Category_Dist) + thiscontext.value = value(thiscontext) + 1; + eval(sprintf('Context%i_Dist.value = Category_Dist;',value(thiscontext))); + eval(sprintf('Context%i_trialStart.value = n_done_trials + 1;',value(thiscontext))); + eval(sprintf('Context%i_trialEnd.value = n_done_trials + 1;',value(thiscontext))); + eval(sprintf('make_visible(Context%i_Dist);',value(thiscontext))); + eval(sprintf('make_visible(Context%i_trialStart);',value(thiscontext))); + eval(sprintf('make_visible(Context%i_trialEnd);',value(thiscontext))); + + % Firstly make sure that at least 20 trials happened in this + % context if so then update the table and make changes to plot + if present_context_end - present_context_start >= 20 + [state, data, handles, config, flags] = PsychometricSection(obj,'Calculate_Params'); + % Calling the function to update the table and plot + state = RealTimeAnalysis('context_switch', state, data, handles, config, flags); + states_value.value = state; + end + end + else + eval(sprintf('Context%i_Dist.value = Category_Dist;',value(thiscontext))); + end + + case 'PushButton_SelectedTrial' + [state, data, handles, config, flags] = PsychometricSection(obj,'Calculate_Params'); + % Calling the function to update the table and plot + state = RealTimeAnalysis('custom', state, data, handles, config, flags,value(Plot_Trial_Start),value(Plot_Trial_End)); + states_value.value = state; + + case 'PushButton_Context' + [state, data, handles, config, flags] = PsychometricSection(obj,'Calculate_Params'); + % create a cell array containing the start and end of each context + context_trials = cell(1,value(thiscontext)); + contexts_name = cell(1,value(thiscontext)); + for n_plot = 1:value(thiscontext) + eval(sprintf('trial_start = value(Context%i_trialStart);',n_plot)); + eval(sprintf('trial_end = value(Context%i_trialEnd);',n_plot)); + eval(sprintf('context_name = value(Context%i_Dist);',n_plot)); + context_trials{1,n_plot} = [trial_start, trial_end]; + contexts_name{1,n_plot} = context_name; + end + % Calling the function to update the table and plot + state = RealTimeAnalysis('context', state, data, handles, config, flags, context_trials,contexts_name); + states_value.value = state; + + case 'reload_after_crash' + % make sure that the context values are visible + for n_contexts = 1:value(thiscontext) + eval(sprintf('make_visible(Context%i_Dist);',n_contexts)); + eval(sprintf('make_visible(Context%i_trialStart);',n_contexts)); + eval(sprintf('make_visible(Context%i_trialEnd);',n_contexts)); + end + % set the fig value for uitable as we didn't save the table + ui_table_handle = value(uit); + table_fig = value(myfig_table); + ui_table_handle.Parent = table_fig; + if isempty(ui_table_handle.CellEditCallback) + ui_table_handle.CellEditCallback = @(src, evt) PsychometricSection(obj, 'check_box_table', src, evt); + end + uit.value = ui_table_handle; + + %% update after each trial + case 'update' + % update the trial end for this context + if n_done_trials > 1 + eval(sprintf('Context%i_trialEnd.value = n_done_trials;',value(thiscontext))); + [state, data, handles, config, flags] = PsychometricSection(obj,'Calculate_Params'); + % Calling the function to update the table and plot + state = RealTimeAnalysis('live', state, data, handles, config, flags); + states_value.value = state; + end + + %% update the figure if user opened the figure window + case 'update_plot' + % if n_done_trials > 1 + % PsychometricSection(obj, 'reload_after_crash'); + % end + if n_done_trials > 30 + [state, data, handles, config, flags] = PsychometricSection(obj,'Calculate_Params'); + % Calling the function to update the table and plot + state = RealTimeAnalysis('redraw', state, data, handles, config, flags); + states_value.value = state; + end + + case 'evaluate' + if n_done_trials > 1 + try + % create a cell array containing the start and end of each context + context_trials = cell(1,value(thiscontext)); + psych_result = cell(1,value(thiscontext)); + + for n_context = 1:value(thiscontext) + eval(sprintf('trial_start = value(Context%i_trialStart);',n_context)); + eval(sprintf('trial_end = value(Context%i_trialEnd);',n_context)); + context_trials{1,n_context} = [trial_start, trial_end]; + + psych_result{1,n_context}.start_trial = trial_start; + psych_result{1,n_context}.end_trial = trial_end; + psych_result{1,n_context}.distribution_type = char(unique(category_distribution)); + psych_result{1,n_context}.calculated_boundary = nan; + psych_result{1,n_context}.total_hit_percent = -1; + psych_result{1,n_context}.total_violations_percent = -1; + psych_result{1,n_context}.right_correct_percent = -1; + psych_result{1,n_context}.left_correct_percent = -1; + end + + [state, data, handles, config, flags] = PsychometricSection(obj,'Calculate_Params'); + % Calling the function to update the table and plot + psych_result = RealTimeAnalysis('evaluate', state, data, handles, config, flags, context_trials); + + catch + fprintf(2, 'Error calculating correct pokes\n'); + disp(ME.message); + disp(ME.stack); + end + + end + + varargout{1} = psych_result; + + %% cases related to figure handles + + case 'check_box_table' + try + if numel(varargin) < 2 + warning('check_box_table action called without event data.'); + return; + end + source = varargin{1}; + event = varargin{2}; + editedRow = event.Indices(1); + editedCol = event.Indices(2); + % Rule 1: Only act on our "Select" column (column 1) + if editedCol ~= 1, return; end + % Rule 2: Only act when the user tries to CHECK a box to TRUE + if ~event.NewData, return; end + % Rule 3: Check if the edited row is a "locked" row + state = value(states_value); + isRowEditable = state.table_row_editable; + if editedRow > numel(isRowEditable) + warning('Row editability status is out of sync with the table data.'); + return; + end + % If the row is NOT editable, then it's locked. + if ~isRowEditable(editedRow) + source.Data.Select(editedRow) = false; + % warndlg('This row is protected and cannot be selected.', 'Selection Blocked'); + end + catch + end + + case 'close' + set(value(myfig), 'Visible', 'off'); + set(value(myfig_table), 'Visible', 'off'); + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)) %#ok + delete(value(myfig)); + end + if exist('myfig_table', 'var') && isa(myfig_table, 'SoloParamHandle') && ishandle(value(myfig_table)) %#ok + delete(value(myfig_table)); + end + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'hide' + PsychometricShow.value = 0; + set(value(myfig), 'Visible', 'off'); + set(value(myfig_table), 'Visible', 'off'); + + %% Case Show_hide main psychometric GUI + case 'show_hide' + if PsychometricShow == 1 + set(value(myfig), 'Visible', 'on'); + + % Update the plot for live update + PsychometricSection(obj,'update_plot'); + + if Show_Table_Toggle == 1 + set(value(myfig_table), 'Visible', 'on'); + else + set(value(myfig_table), 'Visible', 'off'); + end + else + set(value(myfig), 'Visible', 'off'); + set(value(myfig_table), 'Visible', 'off'); + end + + %% case for table toggle + case 'show_hide_table' + if Show_Table_Toggle == 1 + set(value(myfig_table), 'Visible', 'on'); + else + set(value(myfig_table), 'Visible', 'off'); + end + + case 'hide_table' + Show_Table_Toggle.value = 0; + set(value(myfig_table), 'Visible', 'off'); +end + +end \ No newline at end of file diff --git a/Protocols/@ArpitSoundCatContinuous/SideSection.m b/Protocols/@ArpitSoundCatContinuous/SideSection.m new file mode 100644 index 00000000..384a5e6a --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/SideSection.m @@ -0,0 +1,609 @@ + + +function [x, y] = SideSection(obj, action, x,y) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + y0 = y; + + [x, y] = AntibiasSection(obj, 'init', x, y); + + ToggleParam(obj, 'antibias_LRprob', 0, x,y,... + 'OnString', 'AB_Prob ON',... + 'OffString', 'AB_Prob OFF',... + 'TooltipString', sprintf(['If on (Yellow) then it enables the AntiBias algorithm\n'... + 'based on changing the probablity of Left vs Right'])); + next_row(y); + NumeditParam(obj, 'LeftProb', 0.5, x, y); next_row(y); + set_callback(LeftProb, {mfilename, 'new_leftprob'}); + MenuParam(obj, 'MaxSame', {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Inf}, Inf, x, y, ... + 'TooltipString', sprintf(['\nMaximum number of consecutive trials where correct\n' ... + 'response is on the same side. Overrides antibias. Thus, for\n' ... + 'example, if MaxSame=5 and there have been 5 Left trials, the\n' ... + 'next trial is guaranteed to be Right'])); next_row(y); + + next_row(y); + NumeditParam(obj, 'ntrial_correct_bias', 0, x, y, ... + 'TooltipString', 'antibias starts from trial=ntrial_correct_bias'); + next_row(y); + NumeditParam(obj, 'right_left_diff', .12, x, y, ... + 'TooltipString', 'antibias applies if difference between right and left sides is bigger than this number'); + next_row(y); + NumeditParam(obj, 'max_wtr_mult', 4, x, y, ... + 'TooltipString', 'wtr_mult will be min(max_wtr_mult,right_hit/left_hit)'); + next_row(y); + NumeditParam(obj, 'left_wtr_mult', 1, x, y, ... + 'TooltipString', 'all left reward times are multiplied by this number'); + next_row(y); + NumeditParam(obj, 'right_wtr_mult', 1, x, y, ... + 'TooltipString', 'all right reward times are multiplied by this number'); + next_row(y); + ToggleParam(obj, 'antibias_wtr_mult', 0, x,y,... + 'OnString', 'AB_Water ON',... + 'OffString', 'AB_Water OFF',... + 'TooltipString', sprintf(['If on (black) then it disables the wtr_mult entries\n'... + 'and uses hitfrac to adjust the water times'])); + next_row(y); + DispParam(obj, 'ThisTrial', 'LEFT', x, y); next_row(y); + + SubheaderParam(obj, 'title', 'Sides Section', x, y);next_row(y); + + %% Stimulator Section + [x, y] = StimulatorSection(obj, 'init', x, y); next_row(y, 1.3); + %% + SoloParamHandle(obj, 'previous_sides', 'value', []); + DeclareGlobals(obj, 'ro_args', 'previous_sides'); + + next_row(y, 1.5); + next_column(x); y = 5; + + % Reward Collection + MenuParam(obj, 'reward_type', {'Always','DelayedReward', 'NoReward'}, ... + 'Always', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nThis menu is to determine the Reward delivery on wrong-hit trials\n',... + '\nIf ''Always'': reward will be available on each trial no matter which side rat goes first\n',... + '\n If rat pokes first on the wrong side, then reward will be delivered with a delay (if DelayedReward) or not delivered at all (if NoReward)'])); + set_callback(reward_type, {mfilename, 'new_reward_type'}); + next_row(y); + NumeditParam(obj,'secondhit_delay',0,x,y,'label','SecondHit Delay','TooltipString','Reward will be delayed with this amount if reward_type=DelayedReward'); + next_row(y); + NumeditParam(obj, 'RewardCollection_duration', 10, x,y,'label','RColl_dur','TooltipString','Wait until rat collects the reward'); + next_row(y); + NumeditParam(obj, 'SideLed_duration', 0.5, x,y,'label','Side LED duration','TooltipString','Duration of SideLed'); + next_row(y); + MenuParam(obj, 'side_lights' ,{'none','both','correct side','anti side'},1, x,y,'label','Side Lights','TooltipString','Controls the side LEDs during wait_for_spoke'); + next_row(y); + NumeditParam(obj, 'reward_delay', 0.01, x,y,'label','Reward Delay','TooltipString','Delay between side poke and reward delivery'); + next_row(y); + NumeditParam(obj, 'drink_time', 1, x,y,'label','Drink Time','TooltipString','waits to finish water delivery'); + next_row(y); + NumeditParam(obj, 'error_iti', 3, x,y,'label','Error Timeout','TooltipString','ITI on error trials(valid only for NoReward Condition)'); + next_row(y); + + % Centre Poke + NumeditParam(obj, 'CenterLed_duration', 200, x,y,'label','C_LED dur','TooltipString','Duration of poke before Timeout'); + next_row(y); + NumeditParam(obj, 'violation_iti', 1, x,y,'label','Violation Timeout','TooltipString','Center poke violation duration'); + next_row(y); + NumeditParam(obj, 'timeout_iti', 1, x,y,'label','Timeout ITI','TooltipString','ITI on timeout trials'); + next_row(y); + NumeditParam(obj, 'legal_cbreak', 0.1, x,y, 'position', [x, y, 175 20], 'TooltipString','Time in sec for which it is ok to be outside the center port before a violation occurs.'); + ToggleParam(obj, 'LED_during_legal_cbreak', 1, x, y, 'OnString', 'LED ON LcB', 'OffString', 'LED off LcB', ... + 'position', [x+180 y 20 20], 'TooltipString', ... + 'If 1 (black), turn center port LED back on during legal_cbreak; if 0 (brown), leave LED off'); + next_row(y); + NumeditParam(obj, 'SettlingIn_time', 0.2, x,y, 'position', [x, y, 175 20], 'TooltipString','Initial settling period during which "legal cbreak period" can be longer than the usual "legal_cbreak"'); + ToggleParam(obj, 'LED_during_settling_legal_cbreak', 0, x, y, 'OnString', 'LED ON SetLcB', 'OffString', 'LED OFF setLcB', ... + 'position', [x+180 y 20 20], 'TooltipString', ... + 'If 1 (black), turn center port LED back on during settling_legal_cbreak; if 0 (brown), leave LED off'); + next_row(y); + + % PreStim + NumeditParam(obj, 'PreStim_time_Min', 0.20, x,y,'label','Pre-Stim Min','TooltipString','Min Time in NIC before starting the stimulus'); + set_callback(PreStim_time_Min, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'PreStim_time', 0.20,x,y, 'position', [x, y, 175 20],'label','Pre-Stim time','TooltipString','Actual Time in NIC before starting the stimulus'); + set_callback(PreStim_time, {mfilename, 'new_CP_duration'}); + % Toggle Buttons To Control Parameters + ToggleParam(obj, 'random_PreStim_time', 1, x,y, 'position',[x+180 y 20 20],... + 'OnString', 'random PreStim_time ON',... + 'OffString', 'random PreStim_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random time between the user given range')); + set_callback(random_PreStim_time, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'PreStim_time_Max', 1, x,y,'label','Pre-Stim Max','TooltipString','Max Time in NIC before starting the stimulus'); + set_callback(PreStim_time_Max, {mfilename, 'new_CP_duration'}); + next_row(y); + % A1 Time + NumeditParam(obj, 'A1_time_Min', 0.1, x,y,'label','Min time AUD1','TooltipString','Min value to select the Duration of fixed stimulus'); + set_callback(A1_time_Min, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'A1_time', 0.4,x,y, 'position', [x, y, 175 20],'label','AUD1 Time','TooltipString','Actual Duration of fixed stimulus'); + set_callback(A1_time, {mfilename, 'new_CP_duration'}); + % Toggle Buttons To Control Parameters + ToggleParam(obj, 'random_A1_time', 1, x,y, 'position',[x+180 y 20 20],... + 'OnString', 'random A1_time ON',... + 'OffString', 'random A1_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random sampling of A1_time')); + set_callback(random_A1_time, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'A1_time_Max', 0.4, x,y,'label','Max time AUD1','TooltipString','Max value to select the Duration of fixed stimulus'); + set_callback(A1_time_Max, {mfilename, 'new_CP_duration'}); + next_row(y); + % Time b/w stim and Go Cue + NumeditParam(obj, 'time_bet_aud1_gocue_Min', 0.2, x,y,'label','Min A1-GoCue time','TooltipString','Min time between the end of the stimulus and the go cue '); + set_callback(time_bet_aud1_gocue_Min, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_bet_aud1_gocue', 0.4,x,y, 'position', [x, y, 175 20],'label','A1-GoCue time','TooltipString','Actual time between the end of the stimulus and the go cue '); + set_callback(time_bet_aud1_gocue, {mfilename, 'new_CP_duration'}); + % Toggle Buttons To Control Parameters + ToggleParam(obj, 'random_prego_time', 1, x,y, 'position',[x+180 y 20 20],... + 'OnString', 'random prego_time ON',... + 'OffString', 'random prego_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random sampling of time between the end of the stimulus and the go cue')); + set_callback(random_prego_time, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_bet_aud1_gocue_Max', 1, x,y,'label','Max A1-GoCue time','TooltipString','Max time between the end of the stimulus and the go cue '); + set_callback(time_bet_aud1_gocue_Max, {mfilename, 'new_CP_duration'}); + next_row(y); + set_callback(time_bet_aud1_gocue, {mfilename, 'new_CP_duration'}); + + + DispParam(obj, 'init_CP_duration', 0.01, x,y,'label','init_CP duration','TooltipString','Duration of Nose in Central Poke before Go cue starts (see Total_CP_duration)'); + next_row(y); + DispParam(obj, 'CP_duration', PreStim_time+A1_time+time_bet_aud1_gocue, x,y,'label','CP duration','TooltipString','Duration of Nose in Central Poke before Go cue starts (see Total_CP_duration)'); + set_callback(CP_duration, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_go_cue' ,0.2, x,y,'label','Go Cue Duration','TooltipString','duration of go cue (see Total_CP_duration)'); + set_callback(time_go_cue, {mfilename, 'new_time_go_cue'}); + next_row(y); + DispParam(obj, 'Total_CP_duration', CP_duration+time_go_cue, x, y, 'TooltipString', 'Total nose in center port time, in secs. Sum of CP_duration and Go Cue duration'); %#ok<*NODEF> + next_row(y); next_row(y); + + % Toggle Buttons To Control Parameters + ToggleParam(obj, 'stimuli_on', 1, x,y,... + 'OnString', 'Stimuli ON',... + 'OffString', 'Stimuli OFF',... + 'TooltipString', sprintf('If on (black) then it enables training with stimuli else using a fixed sound from Stage 5')); + next_row(y);next_row(y); + + %% Sound and Stimulus Section + [x, y] = SoundSection(obj,'init',x,y); + [x, y] = StimulusSection(obj,'init',x,y); next_row(y); + + next_column(x); + y=5; + + ToggleParam(obj, 'control_active', 0, x,y,... + 'OnString', 'Buttons Deative',... + 'OffString', 'Buttons Active',... + 'TooltipString', sprintf(['If on (Yellow) then the user can change parameters.\n',... + 'This is to prevent any unwanted button press'])); + set_callback(control_active, {mfilename, 'User_Control'}); + next_row(y); + + % Toggle for WarmUp + ToggleParam(obj, 'warmup_on', 0, x,y,... + 'OnString', 'Warmup ON',... + 'OffString', 'Warmup OFF',... + 'TooltipString', sprintf(['If on (Yellow) then it applies the initial warming up phase, during which the\n',... + 'CP_duration starts small and gradually grows to last_session_max_cp_duration. Used during training'])); + next_row(y); + + % Training for Centre Poke Increase + ToggleParam(obj,'increase_CP_training',0,x,y,'OnString','Training Increasing CP','OffString','No Training'); + set_callback(increase_CP_training, {mfilename, 'CP_training'}); + next_row(y); + NumeditParam(obj, 'cp_start' ,0.5, x,y,'label','CPDur_Start','TooltipString','CP start time during training'); + next_row(y); + NumeditParam(obj, 'cp_end' ,2, x,y,'label','CPDur_End','TooltipString','CP end time during training'); + next_row(y); + NumeditParam(obj, 'fraction_increase' ,0.001, x,y,'label','Frac_Increment','TooltipString','fraction CP added'); + next_row(y); + NumeditParam(obj, 'cp_reached' ,value(cp_start), x,y,'label','CPDur_Reached','TooltipString','CP dur reached in last session'); + next_row(y); + make_invisible(cp_start); make_invisible(cp_end); make_invisible(fraction_increase);make_invisible(cp_reached); + + + SoloFunctionAddVars('ArpitSoundCatContinuousSMA', 'ro_args', ... + {'CP_duration';'SideLed_duration';'CenterLed_duration';'side_lights' ; ... + 'RewardCollection_duration'; ... + 'legal_cbreak' ; 'LED_during_legal_cbreak' ; ... + 'SettlingIn_time' ; 'LED_during_settling_legal_cbreak' ; ... + 'time_go_cue';'timeout_iti'; ... + 'stimuli_on';'A1_time';'time_bet_aud1_gocue' ; ... + 'PreStim_time';'warmup_on';'time_go_cue'; ... + 'drink_time';'reward_delay';'left_wtr_mult';... + 'right_wtr_mult';'antibias_wtr_mult';... + 'reward_type';'secondhit_delay';'error_iti';'violation_iti'}); + + SoloFunctionAddVars('StimulusSection', 'ro_args', ... + {'ThisTrial';'stimuli_on';'A1_time';'time_bet_aud1_gocue' ; ... + 'PreStim_time';'LeftProb'}); + + SoloFunctionAddVars('StimulatorSection', 'ro_args', ... + {'A1_time';'time_bet_aud1_gocue';'time_go_cue'; ... + 'PreStim_time';'CP_duration';'Total_CP_duration'}); + + % History of hit/miss: + SoloParamHandle(obj, 'deltaf_history', 'value', []); + SoloParamHandle(obj, 'previous_parameters', 'value', []); + + case 'User_Control' + + if value(control_active) == 0 + disable(antibias_LRprob); disable(stimuli_on); disable(Switch_Distr); disable(antibias_wtr_mult); + disable(random_PreStim_time); disable(random_A1_time); disable(random_prego_time); + disable(warmup_on); disable(increase_CP_training); + else + enable(antibias_LRprob); enable(stimuli_on); enable(Switch_Distr); enable(antibias_wtr_mult); + enable(random_PreStim_time); enable(random_A1_time); enable(random_prego_time); + enable(warmup_on); enable(increase_CP_training); + end + + + case 'new_leftprob' + AntibiasSection(obj, 'update_biashitfrac', value(LeftProb)); + + + case 'new_CP_duration' + + if random_PreStim_time == 1 + make_visible(PreStim_time_Min); make_visible(PreStim_time_Max); + else + make_invisible(PreStim_time_Min); make_invisible(PreStim_time_Max); + end + + if random_A1_time == 1 + make_visible(A1_time_Min); make_visible(A1_time_Max); + else + make_invisible(A1_time_Min); make_invisible(A1_time_Max); + end + + if random_prego_time == 1 + make_visible(time_bet_aud1_gocue_Min);make_visible(time_bet_aud1_gocue_Max); + else + make_invisible(time_bet_aud1_gocue_Min);make_invisible(time_bet_aud1_gocue_Max); + end + + + if random_PreStim_time == 1 && (value(PreStim_time) < value(PreStim_time_Min) || value(PreStim_time) > value(PreStim_time_Max)) + PreStim_time.value = value(PreStim_time_Min); + else + PreStim_time.value = value(PreStim_time); + end + + if random_A1_time == 1 && (value(A1_time) < value(A1_time_Min) || value(A1_time) > value(A1_time_Max)) + A1_time.value = value(A1_time_Min); + else + A1_time.value = value(A1_time); + end + + if random_prego_time == 1 && (value(time_bet_aud1_gocue) < value(time_bet_aud1_gocue_Min) || value(time_bet_aud1_gocue) > value(time_bet_aud1_gocue_Max)) + time_bet_aud1_gocue.value = value(time_bet_aud1_gocue_Min); + else + time_bet_aud1_gocue.value = value(time_bet_aud1_gocue); + end + + CP_duration.value= value(SettlingIn_time) + value(PreStim_time) + value(A1_time) + value(time_bet_aud1_gocue); + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); %#ok<*NASGU> + + case 'CP_training' + + if increase_CP_training + make_visible(cp_start); make_visible(cp_end); make_visible(fraction_increase);make_visible(cp_reached); + else + make_invisible(cp_start); make_invisible(cp_end); make_invisible(fraction_increase);make_invisible(cp_reached); + end + + case 'new_time_go_cue' + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); + SoundInterface(obj, 'set', 'GoSound', 'Dur1', value(time_go_cue)); + + case 'new_reward_type' + if strcmp(reward_type,'DelayedReward') + enable(secondhit_delay) + else + secondhit_delay=0; + disable(secondhit_delay) + + end + + case 'prepare_next_trial' + + %% Calculate the CP Params Time + + % Warm Up If starting a new session irrespective of training + if warmup_on == 1 && n_done_trials <= 10 % warm up trials + if increase_CP_training == 1 %% Training to increase CP + last_CP = value(cp_reached); + else + last_CP = 3; + end + if value(SettlingIn_time) + value(legal_cbreak) < 0.5 + cp_min = 0.5; + else + cp_min = value(SettlingIn_time) + value(legal_cbreak); + end + + fixed_CP_length = 1; + if n_done_trials == 0 + CP_duration.value = value(init_CP_duration); + elseif n_done_trials > 0 && n_done_trials <= 10 % warm up trials + cp_delta = (last_CP - value(init_CP_duration)) / 10; + CP_duration.value = value(init_CP_duration) + cp_delta * n_done_trials; + if value(CP_duration) < cp_min + CP_duration.value = cp_min; + end + end + + cp_length = value(CP_duration) - value(SettlingIn_time); + + else % Non Warmup Trials with Increased CP Training + if increase_CP_training == 1 && value(CP_duration) <= value(cp_end) %% Training to increase CP + if ~violation_history(end) && ~timeout_history(end) + increment = value(CP_duration) * value(fraction_increase); + CP_duration.value = value(CP_duration) + increment; + % Check if the values are within the required range + if value(CP_duration) < value(cp_start) + CP_duration.value = value(cp_start); + end + if value(CP_duration) >= value(cp_end) + CP_duration.value = value(cp_end); + end + end + + fixed_CP_length = 1; + cp_length = value(CP_duration) - value(SettlingIn_time); + + else % Usual Case During Experiment + fixed_CP_length = 0; + cp_length = 3; % doesn't matter as this wont be used + end + end + + % Calculate new values of Params + + if cp_length >= 0.3 + [PreStim_time.value ,A1_time.value,time_bet_aud1_gocue.value] = Calculate_CentrePoke_Params(fixed_CP_length,... + cp_length,value(PreStim_time_Min),value(PreStim_time_Max), random_PreStim_time, value(PreStim_time),... + value(A1_time_Min),value(A1_time_Max), random_A1_time, value(A1_time),value(time_bet_aud1_gocue_Min),... + value(time_bet_aud1_gocue_Max),random_prego_time, value(time_bet_aud1_gocue)); + + CP_duration.value = value(SettlingIn_time) + value(PreStim_time) + value(A1_time) + value(time_bet_aud1_gocue); + end + + Total_CP_duration.value = value(CP_duration) + value(time_go_cue); %#ok<*NASGU> + + %% update hit_history, previous_sides, etc + was_viol=false; + was_hit=false; + was_timeout=false; + if n_done_trials>0 + if ~isempty(parsed_events) + if isfield(parsed_events,'states') + if isfield(parsed_events.states,'timeout_state') + was_timeout=rows(parsed_events.states.timeout_state)>0; + end + if isfield(parsed_events.states,'violation_state') + was_viol=rows(parsed_events.states.violation_state)>0; + end + end + + end + + vio_history = value(violation_history); + vio_history(n_done_trials) = was_viol; + violation_history.value= vio_history; + tout_history = value(timeout_history); + tout_history(n_done_trials) = was_timeout; + timeout_history.value= tout_history; + + SideSection(obj,'update_side_history'); + + hit_his = value(hit_history); + if ~was_viol && ~was_timeout + %was_hit=rows(parsed_events.states.hit_state)>0; + was_hit=rows(parsed_events.states.second_hit_state)==0; + hit_his(n_done_trials) = was_hit; + else + % There was a violation or timeout + hit_his(n_done_trials) = nan; + end + hit_history.value = hit_his; + + % % Now calculate the deltaF and sides - this maybe interesting + % % even in a violation or timeout case. + % + % fn=fieldnames(parsed_events.states); + % led_states=find(strncmp('led',fn,3)); + % deltaF=0; + % n_l=0; + % n_r=0; + % for lx=1:numel(led_states) + % lind=led_states(lx); + % if rows(parsed_events.states.(fn{lind}))>0 + % if fn{lind}(end)=='l' + % deltaF=deltaF-1; + % n_l=n_l+1; + % elseif fn{lind}(end)=='r' + % deltaF=deltaF+1; + % n_r=n_r+1; + % elseif fn{lind}(end)=='b' + % n_l=n_l+1; + % n_r=n_r+1; + % + % end + % end + % + % end + + % if deltaF>0 then a right poke is a hit + % if deltaF<0 then a left poke is a hit + + % deltaf_history.value=[deltaf_history(:); deltaF]; + + end + + if antibias_LRprob ==1 + if n_done_trials >ntrial_correct_bias && ~was_viol && ~was_timeout + nonan_hit_history=value(hit_history); + nonan_hit_history(isnan(nonan_hit_history))=[]; + nonan_previous_sides=value(previous_sides); + nan_history=value(hit_history); + nonan_previous_sides(isnan(nan_history))=[]; + AntibiasSection(obj, 'update', value(LeftProb), nonan_hit_history(:)',nonan_previous_sides(:)); % <~> Transposed hit history so that it is the expected column vector. (Antibias errors out otherwise.) 2007.09.05 01:39 + end + + + if ~isinf(MaxSame) && length(previous_sides) > MaxSame && ... + all(previous_sides(n_done_trials-MaxSame+1:n_done_trials) == previous_sides(n_done_trials)) %#ok + if previous_sides(end)=='l' + ThisTrial.value = 'RIGHT'; + else + ThisTrial.value = 'LEFT'; + end + else + choiceprobs = AntibiasSection(obj, 'get_posterior_probs'); + if rand(1) <= choiceprobs(1) + ThisTrial.value = 'LEFT'; + else + ThisTrial.value = 'RIGHT'; + end + end + + else + if (rand(1)<=LeftProb) + ThisTrial.value='LEFT'; + + else + ThisTrial.value='RIGHT'; + end + + end + + %% Do the anti-bias with changing water vreward delivery + % reset anti-bias + + left_wtr_mult.value=1; + right_wtr_mult.value=1; + + % if n_done_trials>ntrial_correct_bias && antibias_wtr_mult==1 + % hh=hit_history(n_done_trials-ntrial_correct_bias:n_done_trials); + % ps=previous_sides(n_done_trials-ntrial_correct_bias:n_done_trials); + % + % right_hit=nanmean(hh(ps=='r')); + % left_hit=nanmean(hh(ps=='l')); + % + % if abs(right_hit-left_hit) + + case 'get_left_prob' + x = value(LeftProb); + + case 'get_cp_history' + x = cell2mat(get_history(CP_duration)); + + case 'get_stimdur_history' + x = cell2mat(get_history(A1_time)); + + case 'update_side_history' + + if strcmp(ThisTrial, 'LEFT') + ps=value(previous_sides); + ps(n_done_trials)='l'; + previous_sides.value=ps; + + else + ps=value(previous_sides); + ps(n_done_trials)='r'; + previous_sides.value=ps; + end + + case 'get_current_side' + if strcmp(ThisTrial, 'LEFT') + x = 'l'; %#ok + else + x = 'r'; + end + + + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'reinit' + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + +end + + diff --git a/Protocols/@ArpitSoundCatContinuous/SoundSection.m b/Protocols/@ArpitSoundCatContinuous/SoundSection.m new file mode 100644 index 00000000..0fd22e60 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/SoundSection.m @@ -0,0 +1,62 @@ + + +function [x, y] = SoundSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2 + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'SoundsShow', 0, x, y, 'OnString', 'Sounds', ... + 'OffString', 'Sounds', 'TooltipString', 'Show/Hide Sounds panel'); + set_callback(SoundsShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + oldx=x; oldy=y; parentfig=double(gcf); + + SoloParamHandle(obj, 'myfig', 'value', figure('Position', [100 100 560 440], ... + 'closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', mfilename), 'saveable', 0); + set(double(gcf), 'Visible', 'off'); + x=10;y=10; + + [x,y]=SoundInterface(obj,'add','ViolationSound',x,y,'Volume',0.01,'Freq',1000,'Duration',0.5); +% [x,y]=SoundInterface(obj,'add','ViolationSound',x,y,'Style','WhiteNoise','Volume',0.01); + [x,y]=SoundInterface(obj,'add','TimeoutSound',x,y,'Style','WhiteNoise','Volume',0.08,'Duration',0.5); + [x,y]=SoundInterface(obj,'add','RewardSound',x,y,'Style','Bups','Volume',1,'Freq',5,'Duration',1.5); + [x,y]=SoundInterface(obj,'add','ErrorSound',x,y,'Style','WhiteNoise','Volume',0.08); + next_column(x); + y=10; + [x,y]=SoundInterface(obj,'add','GoSound',x,y,'Style','Tone','Volume',0.01,'Freq',3000,'Duration',0.2); + SoundInterface(obj, 'disable', 'GoSound', 'Dur1'); + [x,y]=SoundInterface(obj,'add','SOneSound',x,y,'Style','WhiteNoise','Volume',0.007); + [x,y]=SoundInterface(obj,'add','STwoSound',x,y,'Style','WhiteNoise','Volume',0.07); + + x=oldx; y=oldy; + figure(parentfig); + + case 'hide' + SoundsShow.value = 0; + set(value(myfig), 'Visible', 'off'); + + case 'show' + SoundsShow.value = 1; + set(value(myfig), 'Visible', 'on'); + + case 'show_hide' + if SoundsShow == 1 + set(value(myfig), 'Visible', 'on'); % (defined by GetSoloFunctionArgs) + else + set(value(myfig), 'Visible', 'off'); + end + +end + \ No newline at end of file diff --git a/Protocols/@ArpitSoundCatContinuous/StimulatorSection.m b/Protocols/@ArpitSoundCatContinuous/StimulatorSection.m new file mode 100644 index 00000000..a636fce4 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/StimulatorSection.m @@ -0,0 +1,353 @@ +% +% PARAMETERS: +% ----------- +% +% obj Default object argument. +% +% action One of: +% +% 'prepare_next_trial' +% +% 'init' +% +% +% RETURNS: +% -------- + +function [varargout] = StimulatorSection(obj, action, x, y) + +GetSoloFunctionArgs(obj); + +switch action + +%% init +% ---------------------------------------------------------------- +% +% INIT +% +% ---------------------------------------------------------------- + + case 'init' + + % diolines = bSettings('get','DIOLINES', 'all'); + % for i = 1:size(diolines,1); dionames{i} = diolines{i,1}; dionums(i) = diolines{i,2}; end %#ok + % [dionums order] = sort(dionums); + % dionames2 = cell(0); + % for i = 1:length(dionums); if ~isnan(dionums(i)); dionames2{end+1} = dionames{order(i)}; end; end %#ok + + NumeditParam(obj,'StimStates', 1,x,y,'position',[x y 100 20],'labelfraction',0.60); + NumeditParam(obj,'StimLines', 1,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); + + NumeditParam(obj,'StartDelay', 0,x,y,'position',[x y 100 20],'labelfraction',0.60); + NumeditParam(obj,'StimFreq', 20,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); + NumeditParam(obj,'PulseWidth',15,x,y,'position',[x y 100 20],'labelfraction',0.60); + NumeditParam(obj,'NumPulses', 1,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); + + DispParam(obj,'SD',0 ,x,y,'position',[x y 50 20],'labelfraction',0.4); + DispParam(obj,'SF',20,x,y,'position',[x+50 y 50 20],'labelfraction',0.4); + DispParam(obj,'PW',15,x,y,'position',[x+100 y 50 20],'labelfraction',0.4); + DispParam(obj,'NP',1,x,y,'position',[x+150 y 50 20],'labelfraction',0.4); next_row(y); + + MenuParam(obj,'StimInterval',{'WholeCP_Duration','CP_DurationAfterPrestim','Prestim','S1','DelayDur','GoCue'},1,x,y,'labelfraction',0.30); next_row(y); + set_callback(StimInterval, {mfilename, 'StimInterval'}); + + MenuParam(obj,'StimOnSide',{'both','left','right'},1,x,y,'labelfraction',0.3); next_row(y); + + SC = state_colors(obj); + WC = wave_colors(obj); + states = fieldnames(SC); + waves = fieldnames(WC); + states(2:end+1) = states; + states{1} = 'cp'; + states(end+1:end+length(waves)) = waves; + + MenuParam(obj,'StimState',states,1,x,y,'labelfraction',0.30); next_row(y); + + NumeditParam(obj,'StimProb', 0,x,y,'position',[x y 100 20],'labelfraction',0.65); + ToggleParam( obj,'ShuffleValues',0,x,y,'position',[x+100 y 100 20],'OnString','Shuffle','OffString','Lock'); next_row(y); + + dionames = {'none','Opto','Ephys'}; + + MenuParam(obj,'StimLine',dionames,1,x,y,'labelfraction',0.30); next_row(y); + set_callback(StimLine, {mfilename, 'StimSelected'}); + + SoloParamHandle(obj, 'stimulator_history', 'value', []); + + make_invisible(StimStates); make_invisible(StimLines); make_invisible(StartDelay); make_invisible(StimFreq); + make_invisible(PulseWidth); make_invisible(NumPulses); make_invisible(SD); make_invisible(SF); + make_invisible(PW); make_invisible(NP); make_invisible(StimInterval); make_invisible(StimOnSide); + make_invisible(StimState); make_invisible(StimProb); make_invisible(ShuffleValues); + + SubheaderParam(obj, 'title', 'Stimulator Section', x, y); next_row(y); + + SoloFunctionAddVars('ArpitSoundCatContinuousSMA', 'ro_args',{'StimLine'}); + + varargout{1} = x; + varargout{2} = y; + +%% update_values +% ----------------------------------------------------------------------- +% +% UPDATE_VALUES +% +% ----------------------------------------------------------------------- + + case 'StimSelected' + + if strcmpi(value(StimLine),'Opto') + make_visible(StimStates); make_visible(StimLines); make_visible(StartDelay); make_visible(StimFreq); + make_visible(PulseWidth); make_visible(NumPulses); make_visible(SD); make_visible(SF); + make_visible(PW); make_visible(NP); make_visible(StimInterval); make_visible(StimOnSide); + make_visible(StimState); make_visible(StimProb); make_visible(ShuffleValues); + else + make_invisible(StimStates); make_invisible(StimLines); make_invisible(StartDelay); make_invisible(StimFreq); + make_invisible(PulseWidth); make_invisible(NumPulses); make_invisible(SD); make_invisible(SF); + make_invisible(PW); make_invisible(NP); make_invisible(StimInterval); make_invisible(StimOnSide); + make_invisible(StimState); make_invisible(StimProb); make_invisible(ShuffleValues); + end + + if strcmpi(value(StimLine),'Ephys') + dispatcher('set_trialnum_indicator_flag'); + else + dispatcher('unset_trialnum_indicator_flag'); + end + + case 'update_values' + + if strcmpi(value(StimLine),'Opto') + + StimulatorSection(obj,'StimInterval'); + sh = value(stimulator_history); %#ok + + if ~dispatcher('is_running') + %dispatcher is not running, last stim_hist not used, lop it off + sh = sh(1:end-1); + end + + if value(StimProb) == 0 + stimulator_history.value = [sh, 0]; + elseif rand(1) <= value(StimProb) + stimulator_history.value = [sh, 1]; + else + stimulator_history.value = [sh, 0]; + end + + end + + if strcmpi(value(StimLine),'Ephys') & n_done_trials == 0 + dispatcher('set_trialnum_indicator_flag'); + end + + + +%% prepare_next_trial +% ----------------------------------------------------------------------- +% +% PREPARE_NEXT_TRIAL +% +% ----------------------------------------------------------------------- + + case 'prepare_next_trial' + sh = value(stimulator_history); %#ok + + sma = x; + + sd = value(StartDelay); + sf = value(StimFreq); + pw = value(PulseWidth); + np = value(NumPulses); + ss = value(StimStates); + sl = value(StimLines); + + if value(ShuffleValues) == 1 + sd = sd(ceil(rand(1) * length(sd))); + sf = sf(ceil(rand(1) * length(sf))); + pw = pw(ceil(rand(1) * length(pw))); + np = np(ceil(rand(1) * length(np))); + ss = ss(ceil(rand(1) * length(ss))); + sl = sl(ceil(rand(1) * length(sl))); + else + if length(unique([length(sd) length(sf) length(pw) length(np) length(ss) length(sl)])) > 1 + disp('Warning: param values in StimulatorSection have different lengths. Only first value will be used.'); + temp = 1; + else + temp = ceil(rand(1) * length(sd)); + end + sd = sd(temp); sf = sf(temp); pw = pw(temp); np = np(temp); ss = ss(temp); sl = sl(temp); + end + + pss = get(get_ghandle(StimState),'String'); %#ok + psl = get(get_ghandle(StimLine), 'String'); %#ok + if ss > length(pss) + disp('StimState value greater than list of possible stim states'); + else + StimState.value = ss; + % disp('test ss'); + value(ss); + end + + if sl > length(psl) + slc = ['0',num2str(sl),'0']; + z = find(slc == '0'); + if length(z) > 2 + sln = []; + for i = 1:length(z)-1 + sln(end+1) = str2num(slc(z(i)+1:z(i+1)-1)); %#ok + end + if any(sln > length(psl)) + disp('StimLine value greater than list of possible stim lines'); + else + slname = psl{sln(1)}; + for i=2:length(sln) + slname = [slname,'+',psl{sln(i)}]; %#ok + end + if sum(strcmp(psl,slname)) == 0 + psl{end+1} = slname; + set(get_ghandle(StimLine),'String',psl) + end + StimLine.value = find(strcmp(psl,slname)==1,1,'first'); + sl = sln; + end + else + disp('StimLine value greater than list of possible stim lines'); + end + else + StimLine.value = sl; + end + + for i = 1:length(sl) + stimline = bSettings('get','DIOLINES',psl{sl(i)}); + + sma = add_scheduled_wave(sma,... + 'name', ['stimulator_wave',num2str(i)],... + 'preamble', (1/sf)-(pw/1000),... %%%% Remember: change it such that if this is negative makes it 0 + 'sustain' , pw/1000,... + 'DOut', stimline,... + 'loop', np-1); + + if sd ~= 0 + sma = add_scheduled_wave(sma,... + 'name',['stimulator_wave_pause',num2str(i)],... + 'preamble',sd,... + 'trigger_on_up',['stimulator_wave',num2str(i)]); + else + sma = add_scheduled_wave(sma,... + 'name',['stimulator_wave_pause',num2str(i)],... + 'preamble',1,... + 'trigger_on_up',['stimulator_wave',num2str(i)]); + end + end + + for i = 1:length(sl) + if sh(end) == 1 + if strcmp(value(StimState),'none') == 0 + if sd ~= 0 + sma = add_stimulus(sma,['stimulator_wave_pause',num2str(i)],value(StimState)); + else + sma = add_stimulus(sma,['stimulator_wave',num2str(i)],value(StimState)); + end + + SD.value = sd; SF.value = sf; PW.value = pw; NP.value = np; + end + else + SD.value = 0; SF.value = 0; PW.value = 0; NP.value = 0; + end + end + + varargout{1} = sma; + + + %% Case StimInterval + case 'StimInterval' + + if strcmp(StimInterval, 'WholeCP_Duration') + PulseWidth.value = Total_CP_duration*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = 0; + elseif strcmp(StimInterval, 'CP_DurationAfterPrestim') + PulseWidth.value = (Total_CP_duration - PreStim_time)*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time; + elseif strcmp(StimInterval, 'Prestim') + PulseWidth.value = PreStim_time*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = 0; + elseif strcmp(StimInterval, 'S1') + PulseWidth.value = A1_time*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time; + elseif strcmp(StimInterval, 'DelayDur') + PulseWidth.value = time_bet_aud1_gocue*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time + A1_time; + elseif strcmp(StimInterval, 'GoCue') + PulseWidth.value = time_go_cue*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time + A1_time + time_bet_aud1_gocue; + end +%% set +% ----------------------------------------------------------------------- +% +% SET +% +% ----------------------------------------------------------------------- + case 'set' + varname = x; + newval = y; + + try + temp = 'SoloParamHandle'; %#ok + eval(['test = isa(',varname,',temp);']); + if test == 1 + eval([varname,'.value = newval;']); + end + catch %#ok + showerror; + warning(['Unable to assign value: ',num2str(newval),' to SoloParamHandle: ',varname]); %#ok + end + + +%% get +% ----------------------------------------------------------------------- +% +% GET +% +% ----------------------------------------------------------------------- + case 'get' + varname = x; + + try + temp = 'SoloParamHandle'; %#ok + eval(['test = isa(',varname,',temp);']); + if test == 1 + eval(['varargout{1} = value(',varname,');']); + end + catch %#ok + showerror; + warning(['Unable to get value from SoloParamHandle: ',varname]); %#ok + end + + +%% reinit +% ----------------------------------------------------------------------- +% +% REINIT +% +% ----------------------------------------------------------------------- + case 'reinit' + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); +end + + diff --git a/Protocols/@ArpitSoundCatContinuous/StimulusSection.m b/Protocols/@ArpitSoundCatContinuous/StimulusSection.m new file mode 100644 index 00000000..a8e82eed --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/StimulusSection.m @@ -0,0 +1,819 @@ + + +function varargout = StimulusSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2 + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'StimulusShow', 0, x, y, 'OnString', 'Stimuli Show', ... + 'OffString', 'Stimuli Hidden', 'TooltipString', 'Show/Hide Stimulus panel'); + set_callback(StimulusShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + + oldx=x; oldy=y; parentfig=double(gcf); + + SoloParamHandle(obj, 'myfig', 'value', figure('closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'],... + 'MenuBar', 'none', 'Name', mfilename, 'Units', 'normalized'), 'saveable', false); + SoundManagerSection(obj, 'declare_new_sound', 'StimAUD1') + SoloParamHandle(obj, 'thisstim', 'value', []); + SoloParamHandle(obj, 'thisstimlog', 'value', []); + + %% Formatting graphics elements + + %myfig + original_width = 0.45; + original_height = 0.75; + max_size = min(original_width, original_height); + aspect_ratio = original_width / original_height; + new_width = max_size; + new_height = new_width / aspect_ratio; + + center_x = 0.5 - (new_width / 2); + center_y = 0.5 - (new_height / 2); + + position_vector = [center_x center_y new_width new_height]; + + set(value(myfig), 'Units', 'normalized', 'Name', mfilename, 'Position', position_vector); + set(double(gcf), 'visible', 'off'); + + + x = 10; y=5; + next_row(y); + MenuParam(obj, 'Rule', {'S1>S_boundary Left','S1>S_boundary Right'}, ... + 'S1>S_boundary Left', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nThis buttom determines the rule\n', ... + '\n''S1>S_boundary Left'' means if Aud1 > Aud_boundry then reward will be delivered from the left water spout and if Aud1 < Aud_boundry then water comes from right\n',... + '\n''S1>S_boundary Right'' means if Aud1 < Aud_boundry then reward will be delivered from the left water spout and if Aud1 > Aud_boundry then water comes from right\n'])); + + next_row(y, 1);next_row(y, 1); + NumeditParam(obj,'P_centre_region',0,x,y,'label','P_centre','TooltipString','probability for choosing stim near boundary. 0 = same probability as the rest, 1 = only choose from centre'); + next_row(y); + NumeditParam(obj,'centre_region_width',0.1,x,y,'label','Centre Width','TooltipString','total width around boundary to be considered as central region'); + next_row(y);next_row(y); + + MenuParam(obj, 'Prob_Dist_Left', {'Uniform','Exponential','Half Normal','Normal','Sinusoidal','Anti Exponential','Anti Half Normal','Anti Sinusoidal'}, ... + 'Uniform', x, y,'label','Left Dist', 'labelfraction', 0.35, 'TooltipString', sprintf(['\n Different Probability Distributions for Category A.\n', ... + '\n''Normal - the mean is at mid point of range. Half Normal - truncated normal with mean at boundary.\n',... + '\n''Anti Half Normal - the mean/max is at the side edge of the range.\n',... + '\n''Sinosidal - using sine function instead of half normal and Anti Sinusoidal is when max is at the edge, same as anti half normal.\n'])); + set_callback(Prob_Dist_Left, {mfilename, 'Cal_Mean'}); + next_row(y); + DispParam(obj, 'mean_Left', 0.01, x,y,'label','μ Left','TooltipString','mean/max log stim value for the left side distribution'); + next_row(y); + NumeditParam(obj, 'sigma_Left', 0.15, x,y,'label','σ Left','TooltipString','sigma value for normal/half normal distribution or decay rate for exponential for the left side distribution'); + next_row(y); + NumeditParam(obj, 'sigma_range_Left', 1, x,y,'label','3σ Left','TooltipString',sprintf(['\n A way to reduce the range and increase more distribution towards mean\n', ... + '\n''signifying 3 Sigma (99.7%%) value for the left side distribution, \n',... + '\n''A value b/w range [0.2 - 1] is acceptable.'])); + set_callback(sigma_range_Left, {mfilename, 'Cal_Sigma'}); + next_row(y); next_row(y); + + MenuParam(obj, 'Prob_Dist_Right', {'Uniform','Exponential','Half Normal','Normal','Sinusoidal','Anti Exponential','Anti Half Normal','Anti Sinusoidal'}, ... + 'Uniform', x, y, 'label','Right Dist', 'labelfraction', 0.35, 'TooltipString', sprintf(['\n Different Probability Distributions for Category A.\n', ... + '\n''Normal - the mean is at mid point of range (side edge - boundary). Half Normal - truncated normal with mean at boundary.\n',... + '\n''Anti Half Normal - the mean/max is at the side edge of the range.\n',... + '\n''Sinosidal - using sine function instead of half normal and Anti Sinusoidal is when max is at the edge, same as anti half normal'])); + set_callback(Prob_Dist_Right, {mfilename, 'Cal_Mean'}); + next_row(y); + DispParam(obj, 'mean_Right', 0.01, x,y,'label','μ Right','TooltipString','mean/max log stim value for the right side distribution'); + next_row(y); + NumeditParam(obj, 'sigma_Right', 0.15, x,y,'label','σ Right','TooltipString','sigma value for normal/half normal distribution or decay rate for exponential for the left side distribution'); + next_row(y); + NumeditParam(obj, 'sigma_range_Right', 1, x,y,'label','3σ Right','TooltipString',sprintf(['\n A way to reduce the range and increase more distribution towards mean\n', ... + '\n''signifying 3 Sigma (99.7 %%) value for the right side distribution, \n',... + '\n''A value b/w range [0.2 - 1] is acceptable.'])); + set_callback(sigma_range_Right, {mfilename, 'Cal_Sigma'}); + next_row(y);next_row(y); + MenuParam(obj, 'Category_Dist', {'Uniform','Hard A','Hard B'}, ... + 'Uniform', x, y, 'label','Category Dist', 'labelfraction', 0.35, 'TooltipString', sprintf(['\n Different Distributions for Category.\n', ... + '\n''Depending upon the rule it will change switch the distributions for Left and Right.\n',... + '\n''If its uniform on both then it will change the distribution to Exponential on one of the side\n',... + '\n''depending upon the choice of rule'])); + set_callback(Category_Dist, {mfilename, 'Distribution_Switch'}); + next_row(y);next_row(y); + PushbuttonParam(obj, 'plot_stim_dist', x,y , 'TooltipString', 'Plots the distribution with the new set of parameters'); + set_callback(plot_stim_dist, {mfilename, 'plot_stim_distribution'}); + next_column(x); + y=5; + next_row(y, 1) + MenuParam(obj, 'filter_type', {'GAUS','LPFIR', 'FIRLS','BUTTER','MOVAVRG','KAISER','EQUIRIP','HAMMING'}, ... + 'GAUS', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent filters. ''LPFIR'': lowpass FIR ''FIRLS'': Least square linear-phase FIR filter design\n', ... + '\n''BUTTER'': IIR Butterworth lowpass filter ''GAUS'': Gaussian filter (window)\n', ... + '\n''MOVAVRG'': Moving average FIR filter ''KAISER'': Kaiser-window FIR filtering\n', ... + '\n''EQUIRIP'':Eqiripple FIR filter ''HAMMING'': Hamming-window based FIR'])); + next_row(y); + NumeditParam(obj,'fcut',110,x,y,'label','fcut','TooltipString','Cut off frequency on the original white noise'); + next_row(y); + NumeditParam(obj,'lfreq',2000,x,y,'label','Modulator_LowFreq','TooltipString','Lower bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'hfreq',20000,x,y,'label','Modulator_HighFreq','TooltipString','Upper bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'minS1',0.007,x,y,'label','minS1','TooltipString','min sigma value for AUD1'); + set_callback(minS1, {mfilename, 'Cal_Boundary'}); + next_row(y); + NumeditParam(obj,'maxS1',0.05,x,y,'label','maxS1','TooltipString','max sigma value for AUD1'); + set_callback(maxS1, {mfilename, 'Cal_Boundary'}); + next_row(y); + DispParam(obj, 'A1_sigma', 0.01, x,y,'label','A1_sigma','TooltipString','Sigma value for the first stimulus'); + next_row(y); + NumeditParam(obj,'minF1',4,x,y,'label','minF1','TooltipString','min frequency value for AUD1'); + set_callback(minF1, {mfilename, 'Cal_Boundary'}); + next_row(y); + NumeditParam(obj,'maxF1',10,x,y,'label','maxF1','TooltipString','max frequency value for AUD1'); + set_callback(maxF1, {mfilename, 'Cal_Boundary'}); + next_row(y); + NumeditParam(obj,'volumeF1',0.007,x,y,'label','VolumeF1','TooltipString','volume of tone for AUD1'); + next_row(y); + DispParam(obj, 'A1_freq', 0.01, x,y,'label','A1_freq','TooltipString','Sigma value for the first stimulus'); + next_row(y); + DispParam(obj,'boundary',-3.9,x,y,'label','boundary(log)','TooltipString','decision boundary for categorisation (log)'); + next_row(y); + MenuParam(obj, 'mu_location', {'center', 'side'}, ... + 'center', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf('\nLocation of boundary')); + set_callback(mu_location, {mfilename, 'Cal_Boundary'}); + next_row(y); + ToggleParam(obj, 'frequency_categorization', 0, x,y,... + 'OnString', 'Frequency(Tone)',... + 'OffString', 'Amplitude(Noise)',... + 'TooltipString', sprintf('If on (black) then it enables the presentation of pure tones')); + set_callback(frequency_categorization, {mfilename, 'FrequencyCategorization'}); + make_invisible(maxF1);make_invisible(minF1);make_invisible(A1_freq);make_invisible(volumeF1); + next_row(y); + + % Axes for Plotting + hndl_uipanelplotaxes = uipanel('Units', 'normalized'); + set(hndl_uipanelplotaxes, ... + 'Units', 'normalized', ... + 'Parent', value(myfig), ... + 'Title', 'Stimuli Distribution', ... + 'Tag', 'uipanelstimplot', ... + 'Position', [0.06,0.52,0.8,0.42]); + + SoloParamHandle(obj, 'axstimplot', 'value', axes(hndl_uipanelplotaxes,'Units', 'normalized','Position', [0.07,0.07, ... + 0.8,0.85]), 'saveable', false); + + SoloParamHandle(obj, 'checkboxHist', 'value', ... + uicontrol('Parent', hndl_uipanelplotaxes, ... + 'Units', 'normalized', ... + 'Style', 'checkbox', ... + 'String', 'Histogram', ... + 'TooltipString', 'Show/hide histogram', ... + 'Value', 0, ... + 'Tag', 'checkboxHist', ... + 'Position', [0.9 0.3 0.1 0.12]), ... + 'saveable', false); + + SoloParamHandle(obj, 'checkboxLegends', 'value', ... + uicontrol('Parent', hndl_uipanelplotaxes, ... + 'Units', 'normalized', ... + 'Style', 'checkbox', ... + 'String', 'Legends', ... + 'TooltipString', 'Show/hide legends', ... + 'Value', 0, ... + 'Tag', 'checkboxlegend', ... + 'Position', [0.9 0.5 0.1 0.12]), ... + 'saveable', false); + + SoloParamHandle(obj, 'checkboxStim', 'value', ... + uicontrol('Parent', hndl_uipanelplotaxes, ... + 'Units', 'normalized', ... + 'Style', 'checkbox', ... + 'String', 'ThisStim', ... + 'TooltipString', 'Show/hide present stim', ... + 'Value', 0, ... + 'Tag', 'checkboxStim', ... + 'Position', [0.9 0.7 0.1 0.12]), ... + 'saveable', false); + + SoloParamHandle(obj, 'plot_h', 'value', struct(), 'saveable', false); % Initialize output + + % Plot the Distribution + StimulusSection(obj,'plot_stim_distribution'); + set(value(myfig), 'visible', 'off'); + + x=oldx; y=oldy; + figure(parentfig); + + SoloFunctionAddVars('PsychometricSection', 'ro_args',{'Category_Dist';'Rule';'boundary'}); + + varargout{1} = x; + varargout{2} = y; + + case 'prepare_next_trial' + if stimuli_on + StimulusSection(obj,'pick_current_stimulus'); + srate=SoundManagerSection(obj,'get_sample_rate'); + Fs=srate; + T=value(A1_time); + + if frequency_categorization + % produce the tone + A1_freq.value = value(thisstim); + A1 = value(thisstimlog(n_done_trials+1)); + dur1 = A1_time*1000; + bal=0; + freq1=A1_freq*1000; + vol=value(volumeF1); + RVol=vol*min(1,(1+bal)); + LVol=vol*min(1,(1-bal)); + t=0:(1/srate):(dur1/1000); + t = t(1:end-1); + tw=sin(t*2*pi*freq1); + RW=RVol*tw; + %w=[LW;RW]; + AUD1 = RW; + else + % produce noise pattern + A1_length = round(A1_time * srate); + A1_sigma.value = value(thisstim); + A1 = value(thisstimlog(n_done_trials+1)); + [rawA1, rawA2, normA1, normA2]=noisestim(1,1,T,value(fcut),Fs,value(filter_type)); + modulator=singlenoise(1,T,[value(lfreq) value(hfreq)],Fs,'BUTTER'); + AUD1=normA1(1:A1_length) .* modulator(1:A1_length).*A1_sigma; + end + + if ~isempty(AUD1) + SoundManagerSection(obj, 'set_sound', 'StimAUD1', [AUD1'; AUD1']) + end + + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + if n_done_trials > 0 + + StimulusSection(obj,'update_stimulus_history'); + + % Update the stim histogram and show chosen stimuli + + if value(StimulusShow) == 1 % only run if the figure window is open + + stim_values = value(thisstimlog); + stim_present = stim_values(n_done_trials); + stim_history = stim_values(1:end-1); + + % figure out if legends,present stim and histogram to be plotted or not + legend_obj = value(checkboxLegends); + legend_draw = logical(legend_obj.Value); + + if n_done_trials < 10 + hist_draw = false; + else + hist_obj = value(checkboxHist); + hist_draw = logical(hist_obj.Value); + end + + stim_obj = value(checkboxStim); + stim_draw = logical(stim_obj.Value); + + if hist_draw || stim_draw + [~,~, handle_h] = CreateSamples_from_Distribution(... + 'Mode', 'update_plot', ... + 'ax_handle', value(axstimplot), ... % Get current axes handle + 'plot_handles_in', value(plot_h), ... + 'current_stimulus', stim_present,... + 'samples_history_in',stim_history,... + 'plot_histogram', hist_draw, ... + 'plot_chosen_stimuli', stim_draw, ... + 'plot_distribution', true, ... + 'plot_legend',legend_draw... + ); + plot_h.value = handle_h; + drawnow; + pause(0.01); + end + + end + end + end + + %% Case pick_current_stimulus + case 'pick_current_stimulus' + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + + dist_sigma_left_multiplier = value(sigma_Left) * value(sigma_range_Left); + dist_sigma_right_multiplier = value(sigma_Right) * value(sigma_range_Right); + + % The left-right side according the animal is not the same as left-right of + % stim distribution because it depends upon the Rule whether Stim should be + % considered left/right (below/up of boundary) based upon this rule. + % Changing animals side reference to stimuli reference depending upon Rule + % Even the setting in StimulusSection is based upon side not on the + % basis of whether left or right of boundary + + if strcmp(Rule,'S1>S_boundary Left') % side left is right to stim boundary + if strcmpi(ThisTrial, 'LEFT') + stim_side = 'right'; + else + stim_side = 'left'; + end + dist_left = value(Prob_Dist_Right); + dist_right = value(Prob_Dist_Left); + range_percent_left = value(sigma_range_Right) * 100; + range_percent_right = value(sigma_range_Left) * 100; + dist_mean_left = value(mean_Right); + dist_mean_right = value(mean_Left); + dist_sigma_left = dist_sigma_right_multiplier * (value(boundary) - stim_min_log); + dist_sigma_right = dist_sigma_left_multiplier * (stim_max_log - value(boundary)); + exp_decay_left = value(sigma_Right); + exp_decay_right = value(sigma_Left); + + else % the rule is S1>S_boundary Right + if strcmpi(ThisTrial, 'LEFT') + stim_side = 'left'; + else + stim_side = 'right'; + end + dist_right = value(Prob_Dist_Right); + dist_left = value(Prob_Dist_Left); + range_percent_right = value(sigma_range_Right) * 100; + range_percent_left = value(sigma_range_Left) * 100; + dist_mean_right = value(mean_Right); + dist_mean_left = value(mean_Left); + dist_sigma_left = dist_sigma_left_multiplier * (value(boundary) - stim_min_log); + dist_sigma_right = dist_sigma_right_multiplier * (stim_max_log - value(boundary)); + exp_decay_left = value(sigma_Left); + exp_decay_right = value(sigma_Right); + end + + if ~exist('LeftProb','var') + LeftProb = SideSection(obj,'get_left_prob'); + end + + % Generate a single sample, explicitly chosen side 'left' + [stim_i_log,~,~] = CreateSamples_from_Distribution(... + 'Mode', 'generate_single_sample', ... + 'chosen_side', stim_side, ... % Force pick from chosen side + 'left_edge_value', stim_min_log,... + 'boundary_value', value(boundary),... + 'right_edge_value', stim_max_log,... + 'left_probability', LeftProb, ... % These still define the overall PDF, but this pick is forced chosen side + 'right_probability', 1 - LeftProb, ... + 'left_dist_type', dist_left, ... + 'decay_rate_magnitude_left', exp_decay_left,... + 'normal_mean_left', dist_mean_left,... + 'normal_std_dev_left', dist_sigma_left,... + 'half_normal_std_dev_left', dist_sigma_left,... + 'range_percentage_left',range_percent_left , ... + 'right_dist_type', dist_right, ... + 'decay_rate_magnitude_right', exp_decay_right,... + 'normal_mean_right', dist_mean_right,... + 'normal_std_dev_right', dist_sigma_right,... + 'half_normal_std_dev_right', dist_sigma_right,... + 'range_percentage_right',range_percent_right, ... + 'P_central_region', value(P_centre_region), 'central_region_width', value(centre_region_width) ... + ); + + thisstim.value=exp(stim_i_log); + thisstimlog(n_done_trials+1) = stim_i_log; + + %% Case plot stimuli distribution + case 'plot_stim_distribution' + + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + + dist_sigma_left_multiplier = value(sigma_Left) * value(sigma_range_Left); + dist_sigma_right_multiplier = value(sigma_Right) * value(sigma_range_Right); + + % The left-right side according the animal is not the same as left-right of + % stim distribution because it depends upon the Rule whether Stim should be + % considered left/right (below/up of boundary) based upon this rule. + % Changing animals side reference to stimuli reference depending upon Rule + + if strcmp(Rule,'S1>S_boundary Left') % side left is right to stim boundary + dist_left = value(Prob_Dist_Right); + dist_right = value(Prob_Dist_Left); + range_percent_left = value(sigma_range_Right) * 100; + range_percent_right = value(sigma_range_Left) * 100; + dist_mean_left = value(mean_Right); + dist_mean_right = value(mean_Left); + dist_sigma_left = dist_sigma_right_multiplier * (value(boundary) - stim_min_log); + dist_sigma_right = dist_sigma_left_multiplier * (stim_max_log - value(boundary)); + exp_decay_right = value(sigma_Left); + exp_decay_left = value(sigma_Right); + + else % the rule is S1>S_boundary Right + dist_right = value(Prob_Dist_Right); + dist_left = value(Prob_Dist_Left); + range_percent_right = value(sigma_range_Right) * 100; + range_percent_left = value(sigma_range_Left) * 100; + dist_mean_right = value(mean_Right); + dist_mean_left = value(mean_Left); + dist_sigma_left = dist_sigma_left_multiplier * (value(boundary) - stim_min_log); + dist_sigma_right = dist_sigma_right_multiplier * (stim_max_log - value(boundary)); + exp_decay_left = value(sigma_Left); + exp_decay_right = value(sigma_Right); + end + + if ~exist('LeftProb','var') + LeftProb = SideSection(obj,'get_left_prob'); + end + + % figure out if legends to be plotted or not + legend_obj = value(checkboxLegends); + legend_draw = logical(legend_obj.Value); + + + [~,~, handle_h] = CreateSamples_from_Distribution(... + 'Mode', 'initialize_plot', ... + 'ax_handle', value(axstimplot), ... % Get current axes handle + 'plot_handles_in', value(plot_h), ... + 'plot_histogram', false, ... + 'plot_chosen_stimuli', false, ... + 'plot_distribution', true, ... + 'left_edge_value', stim_min_log,... + 'boundary_value', value(boundary),... + 'right_edge_value', stim_max_log,... + 'left_probability', LeftProb, ... % These still define the overall PDF, but this pick is forced chosen side + 'right_probability', 1 - LeftProb, ... + 'left_dist_type', dist_left, ... + 'normal_mean_left', dist_mean_left,... + 'decay_rate_magnitude_left', exp_decay_left,... + 'normal_std_dev_left', dist_sigma_left,... + 'half_normal_std_dev_left', dist_sigma_left,... + 'range_percentage_left',range_percent_left , ... + 'right_dist_type', dist_right, ... + 'decay_rate_magnitude_right', exp_decay_right,... + 'normal_mean_right', dist_mean_right,... + 'normal_std_dev_right', dist_sigma_right,... + 'half_normal_std_dev_right', dist_sigma_right,... + 'range_percentage_right',range_percent_right, ... + 'P_central_region', value(P_centre_region), 'central_region_width', value(centre_region_width), ... + 'plot_legend',legend_draw... + ); + + plot_h.value = handle_h; + drawnow; + + + + %% Boundary Calculate + case 'Cal_Boundary' + if frequency_categorization + val_boundary = (log(value(minF1)) + log(value(maxF1)))/2; + min_val = log(value(minF1)); + else + val_boundary = (log(value(minS1)) + log(value(maxS1)))/2; + min_val = log(value(minS1)); + end + if strcmp(mu_location,'center') + boundary.value = val_boundary; + elseif strcmp(mu_location,'side') + boundary.value = (min_val + val_boundary)/2; + end + + StimulusSection(obj,'Cal_Mean'); % update the mean and sigma values for each side + + %% Updated Mean/Max for Each Side based upon Distribution Selected + case 'Cal_Mean' + + if frequency_categorization + edge_max = log(value(maxF1)); + edge_min = log(value(minF1)); + else + edge_max = log(value(maxS1)); + edge_min = log(value(minS1)); + end + + % Calculation for Left Side + + % Sigma + + dist_sigma_multiplier = value(sigma_range_Left); + if dist_sigma_multiplier < 0.2 + dist_sigma_multiplier = 0.2; + end + if dist_sigma_multiplier > 1 + dist_sigma_multiplier = 1; + end + + if strcmp(Rule,'S1>S_boundary Left') + edge_min_left = value(boundary); + edge_max_left = edge_min_left + dist_sigma_multiplier * (edge_max - edge_min_left); + else % the rule is S1>S_boundary Right + edge_max_left = value(boundary); + edge_min_left = edge_max_left - dist_sigma_multiplier * (edge_max_left - edge_min); + end + + make_invisible(sigma_Left); + switch value(Prob_Dist_Left) + case {'Half Normal', 'Anti Half Normal'} + make_visible(sigma_Left); + sigma_Left.value = 0.25; + case 'Normal' + make_visible(sigma_Left); + sigma_Left.value = 0.25; + case {'Exponential','Anti Exponential'} + make_visible(sigma_Left); + sigma_Left.value = 2.153; + end + + % Mean + if matches(value(Prob_Dist_Left),{'Uniform','Half Normal','Sinusoidal','Exponential'}) + mean_Left.value = value(boundary); + else + if strcmp(Rule,'S1>S_boundary Left') + if matches(value(Prob_Dist_Left),{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Left.value = edge_max_left; + elseif matches(value(Prob_Dist_Left),'Normal') + mean_Left.value = (edge_max_left + value(boundary))/2; + end + else + if matches(value(Prob_Dist_Left),{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Left.value = edge_min_left; + elseif matches(value(Prob_Dist_Left),'Normal') + mean_Left.value = (edge_min_left + value(boundary))/2; + end + end + end + + % Calculation for Right Side + + % Sigma + dist_sigma_multiplier = value(sigma_range_Right); + if dist_sigma_multiplier < 0.2 + dist_sigma_multiplier = 0.2; + end + if dist_sigma_multiplier > 1 + dist_sigma_multiplier = 1; + end + + if strcmp(Rule,'S1>S_boundary Right') + edge_min_right = value(boundary); + edge_max_right = edge_min_right + dist_sigma_multiplier * (edge_max - edge_min_right); + else % the rule is S1>S_boundary Right + edge_max_right = value(boundary); + edge_min_right = edge_max_right - dist_sigma_multiplier * (edge_max_right - edge_min); + end + + make_invisible(sigma_Right); + switch value(Prob_Dist_Right) + case {'Half Normal', 'Anti Half Normal'} + make_visible(sigma_Right); + sigma_Right.value = 0.25; + case 'Normal' + make_visible(sigma_Right); + sigma_Right.value = 0.25; + case {'Exponential','Anti Exponential'} + make_visible(sigma_Right); + sigma_Right.value = 2.153; + end + + + % Mean + if matches(value(Prob_Dist_Right),{'Uniform','Half Normal','Sinusoidal','Exponential'}) + mean_Right.value = value(boundary); + else + if strcmp(Rule,'S1>S_boundary Right') + if matches(value(Prob_Dist_Right),{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Right.value = edge_max_right; + elseif matches(value(Prob_Dist_Right),'Normal') + mean_Right.value = (edge_max_right + value(boundary))/2; + end + else + if matches(value(Prob_Dist_Right),{'Anti Half Normal','Anti Sinusoidal','Anti Exponential'}) + mean_Right.value = edge_min_right; + elseif matches(value(Prob_Dist_Right),'Normal') + mean_Right.value = (edge_min_right + value(boundary))/2; + end + end + end + + StimulusSection(obj,'plot_stim_distribution'); + + %% Calculate Sigma + case 'Cal_Sigma' + + if frequency_categorization + edge_max = log(value(maxF1)); + edge_min = log(value(minF1)); + else + edge_max = log(value(maxS1)); + edge_min = log(value(minS1)); + end + + % Calculation for Left Side Sigma + dist_sigma_multiplier = value(sigma_range_Left); + if dist_sigma_multiplier < 0.2 + sigma_range_Left.value = 0.2; + end + if dist_sigma_multiplier > 1 + sigma_range_Left.value = 1; + end + + make_invisible(sigma_Left); + switch value(Prob_Dist_Left) + case {'Half Normal', 'Anti Half Normal'} + make_visible(sigma_Left); + sigma_Left.value = 0.25; + case 'Normal' + make_visible(sigma_Left); + sigma_Left.value = 0.25; + case {'Exponential','Anti Exponential'} + make_visible(sigma_Left); + sigma_Left.value = 2.153; + end + + % Calculation for Right Side Sigma + dist_sigma_multiplier = value(sigma_range_Right); + if dist_sigma_multiplier < 0.2 + sigma_range_Right.value = 0.2; + end + if dist_sigma_multiplier > 1 + sigma_range_Right.value = 1; + end + + make_invisible(sigma_Right); + switch value(Prob_Dist_Right) + case {'Half Normal', 'Anti Half Normal'} + make_visible(sigma_Right); + sigma_Right.value = 0.25; + case 'Normal' + make_visible(sigma_Right); + sigma_Right.value = 0.25; + case {'Exponential','Anti Exponential'} + make_visible(sigma_Right); + sigma_Right.value = 2.153; + end + + case 'stim_params' + + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + + dist_range_multiplier_left = value(sigma_range_Left); + dist_range_multiplier_right = value(sigma_range_Right); + + if strcmp(Rule,'S1>S_boundary Left') + edge_max_left = stim_max_log; + edge_min_left = value(boundary); + edge_max_left = edge_min_left + dist_range_multiplier_left * (edge_max_left - edge_min_left); + edge_max_right = value(boundary); + edge_min_right = stim_min_log; + edge_min_right = edge_max_right - dist_range_multiplier_right * (edge_max_right - edge_min_right); + + varargout{1} = [edge_min_right, value(boundary), edge_max_left]; + + else % the rule is S1>S_boundary Right + + edge_min_left = stim_min_log; + edge_max_left = value(boundary); + edge_min_left = edge_max_left - dist_range_multiplier_left * (edge_max_left - edge_min_left); + edge_max_right = stim_max_log; + edge_min_right = value(boundary); + edge_max_right = edge_min_right + dist_range_multiplier_right * (edge_max_right - edge_min_right); + + varargout{1} = [edge_min_left, value(boundary), edge_max_right]; + end + + %% Case frequency ON + case 'FrequencyCategorization' + if frequency_categorization == 1 + make_visible(maxF1);make_visible(minF1);make_visible(A1_freq);make_visible(volumeF1); + make_invisible(maxS1);make_invisible(minS1);make_invisible(A1_sigma); + make_invisible(fcut);make_invisible(lfreq);make_invisible(hfreq); make_invisible(filter_type); + else + make_visible(maxS1);make_visible(minS1);make_visible(A1_sigma); + make_visible(fcut);make_visible(lfreq);make_visible(hfreq); make_visible(filter_type); + make_invisible(maxF1);make_invisible(minF1);make_invisible(A1_freq); make_invisible(volumeF1); + end + + StimulusSection(obj,'Cal_Boundary'); % update the boundary + StimulusSection(obj,'plot_stim_distribution'); + + %% Case get_stimuli + % case 'get_stimuli' + % if nargout>0 + % x=value(S1); + % end + + case 'Distribution_Switch' + + switch value(Category_Dist) + + case 'Uniform' + Prob_Dist_Right.value = 'Uniform'; + make_invisible(sigma_Right); + Prob_Dist_Left.value = 'Uniform'; + make_invisible(sigma_Left); + + case 'Hard A' + if strcmp(Rule,'S1>S_boundary Right') + Prob_Dist_Right.value = 'Uniform'; + make_invisible(sigma_Right); + Prob_Dist_Left.value = 'Exponential'; + sigma_Left.value = 2.153; + make_visible(sigma_Left); + else + Prob_Dist_Right.value = 'Exponential'; + sigma_Right.value = 2.153; + make_visible(sigma_Right); + Prob_Dist_Left.value = 'Uniform'; + make_invisible(sigma_Left); + end + + case 'Hard B' + if strcmp(Rule,'S1>S_boundary Right') + Prob_Dist_Left.value = 'Uniform'; + make_invisible(sigma_Left); + Prob_Dist_Right.value = 'Exponential'; + make_visible(sigma_Right); + sigma_Right.value = 2.153; + else + Prob_Dist_Left.value = 'Exponential'; + make_visible(sigma_Left); + sigma_Left.value = 2.153; + Prob_Dist_Right.value = 'Uniform'; + make_invisible(sigma_Right); + end + end + StimulusSection(obj,'plot_stim_distribution'); + PsychometricSection(obj,'StimSection_Distribution_Switch'); + + case 'Pushbutton_SwitchDistribution' + dist = varargin{1}; + Category_Dist.value = dist; + StimulusSection(obj,'Distribution_Switch'); + + %% Case close + case 'close' + set(value(myfig), 'visible', 'off'); + % set(value(stim_dist_fig), 'visible', 'off'); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)) %#ok + delete(value(myfig)); + end + if exist('stim_dist_fig', 'var') && isa(stim_dist_fig, 'SoloParamHandle') && ishandle(value(stim_dist_fig)) %#ok + delete(value(stim_dist_fig)); + end + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'update_stimulus_history' + ps = value(stimulus_history); + ps1 = value(stimulus_distribution_history); + ps2 = value(stimulus_right_distribution_history); + ps3 = value(stimulus_left_distribution_history); + + ps(n_done_trials)=value(thisstimlog(n_done_trials)); + ps1{n_done_trials}=value(Category_Dist); + ps2{n_done_trials}=value(Prob_Dist_Right); + ps3{n_done_trials}=value(Prob_Dist_Left); + + stimulus_history.value=ps; + stimulus_distribution_history.value = ps1; + stimulus_right_distribution_history.value = ps2; + stimulus_left_distribution_history.value = ps3; + + %% Case hide + case 'hide' + StimulusShow.value = 0; + set(value(myfig), 'visible', 'off'); + % set(value(stim_dist_fig), 'visible', 'off'); + + %% Case show + case 'show' + StimulusShow.value = 1; + set(value(myfig), 'visible', 'on'); + % set(value(stim_dist_fig), 'visible', 'on'); + + %% Case Show_hide + case 'show_hide' + if StimulusShow == 1 + set(value(myfig), 'visible', 'on'); + % set(value(stim_dist_fig), 'visible', 'on');%#ok (defined by GetSoloFunctionArgs) + else + set(value(myfig), 'visible', 'off'); + % set(value(stim_dist_fig), 'visible', 'off'); + end + +end + +return; diff --git a/Protocols/@ArpitSoundCatContinuous/private/Calculate_CentrePoke_Params.m b/Protocols/@ArpitSoundCatContinuous/private/Calculate_CentrePoke_Params.m new file mode 100644 index 00000000..9fb2352c --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/Calculate_CentrePoke_Params.m @@ -0,0 +1,87 @@ +function [prestim,A1,prego] = Calculate_CentrePoke_Params(fixed_length,cp_length,range_min_prestim,range_max_prestim, is_random_prestim, provided_time_prestim,... + range_min_A1,range_max_A1, is_random_A1, provided_time_A1,range_min_prego,range_max_prego, is_random_prego, provided_time_prego) + +if fixed_length == 1 % warm up stage where cp length is increasing +% then calculate the range/typical value + if cp_length <= 0.3 + prestim = 0.1; + A1 = 0.1; + prego = 0.1; + else + range_size = round(0.3 * cp_length,1); + if range_size > 0.4 + step_size = 0.1; + else + step_size = 0.01; + end + + timerange = 0.1:step_size:range_size; + + if is_random_prestim == 1 + prestim = timerange(randi([1, numel(timerange)],1,1)); + else + if provided_time_prestim <= range_size + prestim = provided_time_prestim; + else + prestim = range_size; + end + + end + + if is_random_A1 == 1 + A1 = timerange(randi([1, numel(timerange)],1,1)); + else + if provided_time_A1 <= range_size + A1 = provided_time_A1; + else + A1 = range_size; + end + end + + prego = cp_length - prestim - A1; + + end + +else + + if is_random_prestim == 1 + range_size_prestim = range_max_prestim - range_min_prestim; + if range_size_prestim > 0.4 + step_size_prestim = 0.1; + else + step_size_prestim = 0.01; + end + time_range_prestim = range_min_prestim:step_size_prestim:range_max_prestim; + prestim = time_range_prestim(randi([1, numel(time_range_prestim)],1,1)); + else + prestim = provided_time_prestim; + end + + if is_random_A1 == 1 + range_size_A1 = range_max_A1 - range_min_A1; + if range_size_A1 > 0.4 + step_size_A1 = 0.1; + else + step_size_A1 = 0.01; + end + time_range_A1 = range_min_A1:step_size_A1:range_max_A1; + A1 = time_range_A1(randi([1, numel(time_range_A1)],1,1)); + else + A1 = provided_time_A1; + end + + if is_random_prego == 1 + range_size_prego = range_max_prego - range_min_prego; + if range_size_prego > 0.4 + step_size_prego = 0.1; + else + step_size_prego = 0.01; + end + time_range_prego = range_min_prego:step_size_prego:range_max_prego; + prego = time_range_prego(randi([1, numel(time_range_prego)],1,1)); + else + prego = provided_time_prego; + end + +end +end \ No newline at end of file diff --git a/Protocols/@ArpitSoundCatContinuous/private/CreateSamples_from_Distribution.m b/Protocols/@ArpitSoundCatContinuous/private/CreateSamples_from_Distribution.m new file mode 100644 index 00000000..50a3a00b --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/CreateSamples_from_Distribution.m @@ -0,0 +1,806 @@ +function [samples_history_out, h_fig_out, plot_handles_out] = CreateSamples_from_Distribution(varargin) + +% CreateSamples_from_Distribution +% +% This function is specifically called within StimulusSection to perform +% the actions + +% This function generates random samples from a customizable bimodal +% distribution and can optionally plot the distribution, a history histogram, +% and dynamic current picks. +% +% The overall distribution spans a user-defined range [left_edge_value, right_edge_value] +% with a user-defined boundary at 'boundary_value'. +% The actual distribution (PDF) for each side is generated over a percentage +% of the range between the overall edge and the boundary. +% A central sampling bias mechanism is added to draw more picks near the +% boundary without altering the PDF shape. +% +% Inputs (Name-Value Pairs): +% 'Mode' - (char) 'run_simulation' (default), 'generate_single_sample', 'initialize_plot', or 'update_plot'. +% 'left_probability' - (double) Relative probability for the left side. +% 'right_probability' - (double) Relative probability for the right side. +% 'chosen_side' - (char, optional) For 'generate_single_sample' mode: 'left', 'right', or 'auto' (default). +% If 'left' or 'right', the sample is forced from that side. +% +% 'left_edge_value' - (double) The absolute value for the leftmost boundary of the overall plot range. +% 'boundary_value' - (double) The absolute value for the central boundary between left and right distributions. +% 'right_edge_value' - (double) The absolute value for the rightmost boundary of the overall plot range. +% +% 'left_dist_type' - (char) Type for left side: 'Uniform', 'Exponential', 'Anti_Exponential', +% 'Normal', 'Half Normal', 'Anti Half Normal', 'Sinusoidal', 'Anti_Sinusoidal'. +% 'decay_rate_magnitude_left' - (double) Positive magnitude for 'Exponential' types. +% 'normal_mean_left' - (double) Mean for 'Normal' distribution. +% 'normal_std_dev_left' - (double) Standard deviation for 'Normal' distribution. +% 'half_normal_std_dev_left' - (double) Standard deviation for 'Half Normal' types. +% 'sinusoidal_amplitude_factor_left' - (double) Amplitude (0 to 1) for 'Sinusoidal' types. +% 'sinusoidal_frequency_factor_left' - (double) Frequency for 'Sinusoidal' types. +% 'range_percentage_left' - (double) Percentage (0 to 100) of the [left_edge_value, boundary_value] range +% that the left distribution is active over (e.g., 70 for inner 70%). +% +% 'right_dist_type' - (char) Type for right side (same options as left_dist_type). +% 'decay_rate_magnitude_right' - (double) Positive magnitude for 'Exponential' types. +% 'normal_mean_right' - (double) Mean for 'Normal' distribution. +% 'normal_std_dev_right' - (double) Standard deviation for 'Normal' distribution. +% 'half_normal_std_dev_right' - (double) Standard deviation for 'Half Normal' types. +% 'sinusoidal_amplitude_factor_right' - (double) Amplitude (0 to 1) for 'Sinusoidal' types. +% 'sinusoidal_frequency_factor_right' - (double) Frequency for 'Sinusoidal' types. +% 'range_percentage_right' - (double) Percentage (0 to 100) of the [boundary_value, right_edge_value] range +% that the right distribution is active over (e.g., 70 for inner 70%). +% +% 'P_central_region' - (double) Probability (0 to 1) of applying central sampling bias. +% 'central_region_width' - (double) Width of the central zone for biased sampling. +% +% 'num_simulations' - (int) Number of random picks to simulate (only in 'run_simulation' mode). +% 'pause_duration' - (double) Pause duration between updates in seconds (only in 'run_simulation' mode). +% +% For 'initialize_plot' mode: +% 'ax_handle' - (axes handle, optional) Handle to the axes to plot on. If not provided, a new figure/axes is created. +% +% For 'update_plot' mode: +% 'ax_handle' - (axes handle) Handle to the axes to plot on. +% 'current_stimulus' - (double) The most recent stimulus value to plot. +% 'samples_history_in' - (double array) The current history of all samples. +% 'plot_handles_in' - (struct) Struct containing handles of existing plot elements (from initialize_static_plot). +% 'plot_histogram' - (logical) True to plot/update histogram. +% 'plot_chosen_stimuli' - (logical) True to plot/update current stimulus marker. +% 'plot_distribution' - (logical) True to plot/show the PDF curve. +% 'plot_legend' - (logical) True to plot/show the legends. +% +% Outputs: +% samples_history_out - (double array) Array of generated samples. +% If 'generate_single_sample' mode, it's a single value. +% h_fig_out - (figure handle) Handle to the plot figure. +% plot_handles_out - (struct) Struct containing handles of plot elements. + +% --- Input Parsing --- +p = inputParser; +p.KeepUnmatched = true; % Allow unmatched for future flexibility + +% Define parameters and their default values +addParameter(p, 'Mode', 'run_simulation', @(x) ismember(x, {'run_simulation', 'generate_single_sample', 'initialize_plot', 'update_plot'})); + +% Side Probabilities +addParameter(p, 'left_probability', 0.5, @(x) isscalar(x) && x >= 0); +addParameter(p, 'right_probability', 0.5, @(x) isscalar(x) && x >= 0); +addParameter(p, 'chosen_side', 'auto', @(x) ismember(lower(x), {'left', 'right', 'auto'})); % For single sample mode + +% Custom Boundary and Edge Values +addParameter(p, 'left_edge_value', -1.0, @(x) isscalar(x) && isnumeric(x)); +addParameter(p, 'boundary_value', 0.0, @(x) isscalar(x) && isnumeric(x)); +addParameter(p, 'right_edge_value', 1.0, @(x) isscalar(x) && isnumeric(x)); + +% Left Side Parameters +addParameter(p, 'left_dist_type', 'Half Normal', @ischar); +addParameter(p, 'decay_rate_magnitude_left', 2.153, @(x) isscalar(x) && x >= 0); +addParameter(p, 'normal_mean_left', -0.5, @isnumeric); +addParameter(p, 'normal_std_dev_left', 0.15, @(x) isscalar(x) && x > 0); +addParameter(p, 'half_normal_std_dev_left', 0.2, @(x) isscalar(x) && x > 0); +addParameter(p, 'sinusoidal_amplitude_factor_left', 0.8, @(x) isscalar(x) && x >= 0 && x <= 1); +addParameter(p, 'sinusoidal_frequency_factor_left', 1, @(x) isscalar(x) && x >= 0); +% Removed sinusoidal_peak_location_percentage_left as it's now derived +addParameter(p, 'range_percentage_left', 100, @(x) isscalar(x) && x >= 0 && x <= 100); + +% Right Side Parameters +addParameter(p, 'right_dist_type', 'Sinusoidal', @ischar); +addParameter(p, 'decay_rate_magnitude_right', 5, @(x) isscalar(x) && x >= 0); +addParameter(p, 'normal_mean_right', 0.5, @isnumeric); +addParameter(p, 'normal_std_dev_right', 0.15, @(x) isscalar(x) && x > 0); +addParameter(p, 'half_normal_std_dev_right', 0.2, @(x) isscalar(x) && x > 0); +addParameter(p, 'sinusoidal_amplitude_factor_right', 0.8, @(x) isscalar(x) && x >= 0 && x <= 1); +addParameter(p, 'sinusoidal_frequency_factor_right', 1, @(x) isscalar(x) && x >= 0); +% Removed sinusoidal_peak_location_percentage_right as it's now derived +addParameter(p, 'range_percentage_right', 100, @(x) isscalar(x) && x >= 0 && x <= 100); + +% Central Sampling Bias Parameters +addParameter(p, 'P_central_region', 0.3, @(x) isscalar(x) && x >= 0 && x <= 1); +addParameter(p, 'central_region_width', 0.2, @(x) isscalar(x) && x > 0); + +% Simulation Parameters +addParameter(p, 'num_simulations', 200, @(x) isscalar(x) && x > 0 && mod(x,1)==0); +addParameter(p, 'pause_duration', 0.05, @(x) isscalar(x) && x >= 0); + +% Plot Update Specific Parameters +addParameter(p, 'ax_handle', [], @(x) isempty(x) || isgraphics(x, 'axes')); +addParameter(p, 'current_stimulus', NaN, @isnumeric); +addParameter(p, 'samples_history_in', [], @isnumeric); +addParameter(p, 'plot_handles_in', struct(), @isstruct); +addParameter(p, 'plot_histogram', true, @islogical); +addParameter(p, 'plot_chosen_stimuli', true, @islogical); +addParameter(p, 'plot_distribution', true, @islogical); +addParameter(p, 'plot_legend', true, @islogical); + + +% Parse inputs +parse(p, varargin{:}); +params = p.Results; + +% --- Derived Parameters and Input Validation --- + +% Overall edges and main boundary +overall_left_edge = params.left_edge_value; +overall_right_edge = params.right_edge_value; +main_boundary_actual = params.boundary_value; + +% Validate custom edge and boundary values +if overall_left_edge >= main_boundary_actual + error('left_edge_value must be less than boundary_value.'); +end +if main_boundary_actual >= overall_right_edge + error('boundary_value must be less than right_edge_value.'); +end +if params.central_region_width > (overall_right_edge - overall_left_edge) + error('central_region_width cannot exceed the total range (right_edge_value - left_edge_value).'); +end + +% Calculate P_left_derived from left_probability and right_probability +total_prob = params.left_probability + params.right_probability; +if total_prob == 0 + error('left_probability and right_probability cannot both be zero.'); +end +P_left_derived = params.left_probability / total_prob; + +% --- Determine Peak Behavior Flags for each distribution type --- + +% For Exponential: true means peak at main_boundary_actual, false means peak at respective edge +params.left_exp_peak_at_boundary_flag = contains(params.left_dist_type, 'Exponential', 'IgnoreCase', true) && ~contains(params.left_dist_type, 'Anti', 'IgnoreCase', true); +params.right_exp_peak_at_boundary_flag = contains(params.right_dist_type, 'Exponential', 'IgnoreCase', true) && ~contains(params.right_dist_type, 'Anti', 'IgnoreCase', true); + +% For Half Normal: true means peak at main_boundary_actual, false means peak at respective edge +params.left_hn_peak_at_boundary_flag = contains(params.left_dist_type, 'Half Normal', 'IgnoreCase', true) && ~contains(params.left_dist_type, 'Anti', 'IgnoreCase', true); +params.right_hn_peak_at_boundary_flag = contains(params.right_dist_type, 'Half Normal', 'IgnoreCase', true) && ~contains(params.right_dist_type, 'Anti', 'IgnoreCase', true); + +% For Sinusoidal: true means peak at main_boundary_actual, false means peak at respective edge +params.left_sin_peak_at_boundary_flag = contains(params.left_dist_type, 'Sinusoidal', 'IgnoreCase', true) && ~contains(params.left_dist_type, 'Anti', 'IgnoreCase', true); +params.right_sin_peak_at_boundary_flag = contains(params.right_dist_type, 'Sinusoidal', 'IgnoreCase', true) && ~contains(params.right_dist_type, 'Anti', 'IgnoreCase', true); + + +% Determine actual lambda values for exponential based on derived flags +params.lambda_left_actual = params.decay_rate_magnitude_left; +if contains(params.left_dist_type, 'Anti Exponential', 'IgnoreCase', true) + params.lambda_left_actual = -params.decay_rate_magnitude_left; +end + +params.lambda_right_actual = params.decay_rate_magnitude_right; +if contains(params.right_dist_type, 'Anti Exponential', 'IgnoreCase', true) + params.lambda_right_actual = -params.decay_rate_magnitude_right; +end + +% These are the *active* ranges for the PDF definition and unbiased sampling +active_left_dist_start = overall_left_edge + (main_boundary_actual - overall_left_edge) * (1 - params.range_percentage_left / 100); +active_right_dist_end = main_boundary_actual + (overall_right_edge - main_boundary_actual) * (params.range_percentage_right / 100); + +% These are the boundaries for the central sampling zone +min_central_sampling_range = main_boundary_actual - params.central_region_width / 2; +max_central_sampling_range = main_boundary_actual + params.central_region_width / 2; + + +% --- Helper Functions (Nested for parameter access) --- + +function pdf_val = get_pdf_general(x, dist_type_full_name, current_params, min_val, max_val, is_exp_peak_at_boundary_flag, is_hn_peak_at_boundary_flag, is_sin_peak_at_boundary_flag, current_main_boundary) + % current_params: struct with lambda_actual, normal_mean, normal_std_dev, etc. + % current_main_boundary: The main boundary value (e.g., 0.0 or user-defined) + + pdf_val = zeros(size(x)); + idx_in_range = (x >= min_val & x <= max_val); + x_in_range = x(idx_in_range); + range_length = max_val - min_val; + + if range_length <= 0 || isempty(x_in_range) + return; + end + + switch lower(dist_type_full_name) + case 'uniform' + pdf_val(idx_in_range) = 1 / range_length; + + case {'exponential', 'anti exponential'} + lambda = current_params.lambda_actual; + if abs(lambda) < 1e-6 + pdf_val(idx_in_range) = 1 / range_length; + else + % Adjust lambda sign based on peak_at_boundary_flag for consistency + if is_exp_peak_at_boundary_flag % Peak at current_main_boundary + if min_val < current_main_boundary % Left side + lambda_eff = abs(lambda); % Increase towards main_boundary_actual + else % Right side + lambda_eff = -abs(lambda); % Decrease towards main_boundary_actual + end + else % Peak at edge (min_val or max_val) + if min_val < current_main_boundary % Left side + lambda_eff = -abs(lambda); % Decrease towards main_boundary_actual (peak at min_val) + else % Right side + lambda_eff = abs(lambda); % Increase towards max_val (peak at max_val) + end + end + + denominator = (exp(lambda_eff * max_val) - exp(lambda_eff * min_val)); + if denominator == 0 + pdf_val(idx_in_range) = 0; + else + normalization_factor = lambda_eff / denominator; + pdf_val(idx_in_range) = normalization_factor .* exp(lambda_eff .* x_in_range); + end + end + + case 'normal' + mu = current_params.normal_mean; + sigma = current_params.normal_std_dev; + cdf_min = normcdf(min_val, mu, sigma); + cdf_max = normcdf(max_val, mu, sigma); + trunc_prob = cdf_max - cdf_min; + if trunc_prob <= 0 + pdf_val(idx_in_range) = 0; + else + pdf_val(idx_in_range) = normpdf(x_in_range, mu, sigma) / trunc_prob; + end + + case {'half normal', 'anti half normal'} + sigma = current_params.half_normal_std_dev; + + % Determine mu_half_normal based on is_hn_peak_at_boundary_flag and range + if is_hn_peak_at_boundary_flag % Peak at the main boundary + mu_half_normal = current_main_boundary; + else % Peak at edge of the current sub-range (min_val or max_val) + if max_val == current_main_boundary % Left side distribution (peaks at min_val) + mu_half_normal = min_val; + else % Right side distribution (peaks at max_val) + mu_half_normal = max_val; + end + end + + integral_over_range = normcdf(max_val, mu_half_normal, sigma) - normcdf(min_val, mu_half_normal, sigma); + if integral_over_range <= 0 + pdf_val(idx_in_range) = 0; + else + pdf_val(idx_in_range) = 2 * normpdf(x_in_range, mu_half_normal, sigma) / integral_over_range; + end + + case {'sinusoidal', 'anti sinusoidal'} + amplitude = current_params.sinusoidal_amplitude_factor; + frequency = current_params.sinusoidal_frequency_factor; + + % Determine target x_peak based on is_sin_peak_at_boundary_flag + if is_sin_peak_at_boundary_flag % Regular Sinusoidal: peaks at boundary + if min_val < current_main_boundary && max_val == current_main_boundary % Left side + target_x_peak = max_val; % Peak at boundary_value + else % Right side + target_x_peak = min_val; % Peak at boundary_value + end + else % Anti_Sinusoidal: peaks at outer edge + if min_val < current_main_boundary && max_val == current_main_boundary % Left side + target_x_peak = min_val; % Peak at active_left_dist_start + else % Right side + target_x_peak = max_val; % Peak at active_right_dist_end + end + end + + % Calculate phase for this target_x_peak + % We want (frequency * pi * (target_x_peak - min_val) / range_length + calculated_phase) = pi/2 (for a positive peak) + calculated_phase = pi/2 - frequency * pi * (target_x_peak - min_val) / range_length; + + unnormalized_func = @(val) (1 + amplitude * sin(frequency * pi * (val - min_val) / range_length + calculated_phase)); + + if frequency == 0 + integral_val = (1 + amplitude * sin(calculated_phase)) * range_length; + else + k_norm = frequency * pi / range_length; + integral_val = (range_length * (1 + amplitude * sin(calculated_phase)) - ... + (amplitude / k_norm) * (cos(frequency * pi + calculated_phase) - cos(calculated_phase))); + end + + if integral_val <= 0 + normalization_constant = 0; + else + normalization_constant = 1 / integral_val; + end + pdf_val(idx_in_range) = normalization_constant .* unnormalized_func(x_in_range); + end +end + +function rand_val = generate_rand_general(dist_type_full_name, current_params, min_val, max_val, is_exp_peak_at_boundary_flag, is_hn_peak_at_boundary_flag, is_sin_peak_at_boundary_flag, current_main_boundary) + % current_params: struct with lambda_actual, normal_mean, normal_std_dev, etc. + % current_main_boundary: The main boundary value (e.g., 0.0 or user-defined) + + range_length = max_val - min_val; + if range_length <= 0 + rand_val = min_val; % Fallback for zero or negative range + return; + end + + % Nested helper to encapsulate the generation logic for one attempt + function [val, U_val_used] = generate_single_attempt(dist_type_full_name, current_params, min_val, max_val, is_exp_peak_at_boundary_flag, is_hn_peak_at_boundary_flag, is_sin_peak_at_boundary_flag, current_main_boundary,range_length) + U_val_used = rand(); % Generate a uniform random number in (0,1) + + switch lower(dist_type_full_name) + case 'uniform' + val = min_val + U_val_used * range_length; + case {'exponential', 'anti exponential'} + lambda = current_params.lambda_actual; + if abs(lambda) < 1e-6 + val = min_val + U_val_used * range_length; + else + % Adjust lambda_eff based on peak_at_boundary_flag + if is_exp_peak_at_boundary_flag + if min_val < current_main_boundary + lambda_eff = abs(lambda); + else + lambda_eff = -abs(lambda); + end + else + if min_val < current_main_boundary + lambda_eff = -abs(lambda); + else + lambda_eff = abs(lambda); + end + end + term_inside_log = U_val_used * (exp(lambda_eff * max_val) - exp(lambda_eff * min_val)) + exp(lambda_eff * min_val); + if term_inside_log <= 0 + val = min_val + U_val_used * range_length; % Fallback + else + val = (1/lambda_eff) * log(term_inside_log); + end + end + + case 'normal' + mu = current_params.normal_mean; + sigma = current_params.normal_std_dev; + cdf_min = normcdf(min_val, mu, sigma); + cdf_max = normcdf(max_val, mu, sigma); + val = norminv(cdf_min + U_val_used * (cdf_max - cdf_min), mu, sigma); + + case {'half normal', 'anti half normal'} + sigma = current_params.half_normal_std_dev; + if is_hn_peak_at_boundary_flag + mu_half_normal = current_main_boundary; + else + if max_val == current_main_boundary + mu_half_normal = min_val; + else + mu_half_normal = max_val; + end + end + cdf_min_trunc = normcdf(min_val, mu_half_normal, sigma); + cdf_max_trunc = normcdf(max_val, mu_half_normal, sigma); + rand_val_candidate = norminv(cdf_min_trunc + U_val_used * (cdf_max_trunc - cdf_min_trunc), mu_half_normal, sigma); + if is_hn_peak_at_boundary_flag + if min_val < current_main_boundary + val = current_main_boundary - abs(rand_val_candidate - current_main_boundary); + else + val = current_main_boundary + abs(rand_val_candidate - current_main_boundary); + end + else + if max_val == current_main_boundary + val = mu_half_normal + abs(rand_val_candidate - mu_half_normal); + else + val = mu_half_normal - abs(rand_val_candidate - mu_half_normal); + end + end + % Apply clamping to ensure it's within [min_val, max_val] before the strict check + val = max(min_val, min(max_val, val)); + + case {'sinusoidal', 'anti sinusoidal'} + amplitude = current_params.sinusoidal_amplitude_factor; + frequency = current_params.sinusoidal_frequency_factor; + if is_sin_peak_at_boundary_flag + if min_val < current_main_boundary && max_val == current_main_boundary + target_x_peak = max_val; + else + target_x_peak = min_val; + end + else + if min_val < current_main_boundary && max_val == current_main_boundary + target_x_peak = min_val; + else + target_x_peak = max_val; + end + end + calculated_phase = pi/2 - frequency * pi * (target_x_peak - min_val) / range_length; + if frequency == 0 + integral_val = (1 + amplitude * sin(calculated_phase)) * range_length; + else + k_norm = frequency * pi / range_length; + integral_val = (range_length * (1 + amplitude * sin(calculated_phase)) - ... + (amplitude / k_norm) * (cos(frequency * pi + calculated_phase) - cos(calculated_phase))); + end + if integral_val <= 0 + val = min_val + U_val_used * range_length; % Fallback + else + normalization_constant = 1 / integral_val; + cdf_func_sinusoidal = @(x_val) (... + normalization_constant * (... + (x_val - min_val) + ... + (amplitude / (frequency * pi / range_length)) * (... + -cos(frequency * pi * (x_val - min_val) / range_length + calculated_phase) + ... + cos(calculated_phase) ... + )... + )... + ); + func_to_solve = @(x_val) cdf_func_sinusoidal(x_val) - U_val_used; + try + val = fzero(func_to_solve, [min_val, max_val]); + catch ME + val = min_val + U_val_used * range_length; % Fallback + end + end + end + end + + % Initial generation attempt + rand_val = generate_single_attempt(dist_type_full_name, current_params, min_val, max_val, is_exp_peak_at_boundary_flag, is_hn_peak_at_boundary_flag, is_sin_peak_at_boundary_flag, current_main_boundary,range_length); + + % Ensure rand_val is strictly between min_val and max_val + % Re-sample if it falls exactly on an edge due to floating point precision + while rand_val <= min_val || rand_val >= max_val + rand_val = generate_single_attempt(dist_type_full_name, current_params, min_val, max_val, is_exp_peak_at_boundary_flag, is_hn_peak_at_boundary_flag, is_sin_peak_at_boundary_flag, current_main_boundary,range_length); % Re-generate + end +end + +% --- Plot Static Elements Helper Function --- + function plot_handles = initialize_static_plot(ax, total_pdf_vals, x_plot, overall_left_edge, overall_right_edge, main_boundary_actual, min_central_sampling_range, max_central_sampling_range, active_left_dist_start, active_right_dist_end,plot_legend) + + axes(ax); + cla(ax); % Clear axes content if already exists + hold on; + + % Plot the Bimodal Probability Density Function (PDF) + plot_handles.h_pdf = plot(x_plot, total_pdf_vals, 'LineWidth', 2, 'Color', 'b'); + plot_handles.total_pdf_vals_max = max(total_pdf_vals); + + % Add a dashed red line at the main boundary (x=0) for clarity + plot_handles.h_main_boundary_line = plot([main_boundary_actual main_boundary_actual], [0 max(total_pdf_vals)*1.1], 'r--', 'LineWidth', 1.5); + + % Plot the central sampling region boundaries + plot_handles.h_central_left_line = plot([min_central_sampling_range min_central_sampling_range], [0 max(total_pdf_vals)*1.1], 'g:', 'LineWidth', 1); + plot_handles.h_central_right_line = plot([max_central_sampling_range max_central_sampling_range], [0 max(total_pdf_vals)*1.1], 'g:', 'LineWidth', 1); + plot_handles.h_central_text_left = text(min_central_sampling_range, max(total_pdf_vals)*1.15, sprintf('CB Start (%.2f)', min_central_sampling_range), ... + 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center', 'Color', 'g', 'FontSize', 8); + plot_handles.h_central_text_right = text(max_central_sampling_range, max(total_pdf_vals)*1.15, sprintf('CB End (%.2f)', max_central_sampling_range), ... + 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center', 'Color', 'g', 'FontSize', 8); + + % Plot the overall range boundaries (user-defined edges) + plot_handles.h_overall_left_edge_line = plot([overall_left_edge overall_left_edge], [0 max(total_pdf_vals)*1.1], 'k--', 'LineWidth', 1); + plot_handles.h_overall_left_edge_text = text(overall_left_edge, max(total_pdf_vals)*1.15, sprintf('Overall Left Edge (%.2f)', overall_left_edge), ... + 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center', 'Color', 'k', 'FontSize', 8); + plot_handles.h_overall_right_edge_line = plot([overall_right_edge overall_right_edge], [0 max(total_pdf_vals)*1.1], 'k--', 'LineWidth', 1); + plot_handles.h_overall_right_edge_text = text(overall_right_edge, max(total_pdf_vals)*1.15, sprintf('Overall Right Edge (%.2f)', overall_right_edge), ... + 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center', 'Color', 'k', 'FontSize', 8); + + % Plot the active distribution range boundaries (derived from percentage) + plot_handles.h_active_left_dist_line = plot([active_left_dist_start active_left_dist_start], [0 max(total_pdf_vals)*1.1], 'm:', 'LineWidth', 1); + plot_handles.h_active_left_dist_text = text(active_left_dist_start, max(total_pdf_vals)*1.10, sprintf('Left Active Start (%.2f)', active_left_dist_start), ... + 'VerticalAlignment', 'top', 'HorizontalAlignment', 'center', 'Color', 'm', 'FontSize', 8); + plot_handles.h_active_right_dist_line = plot([active_right_dist_end active_right_dist_end], [0 max(total_pdf_vals)*1.1], 'm:', 'LineWidth', 1); + plot_handles.h_active_right_dist_text = text(active_right_dist_end, max(total_pdf_vals)*1.10, sprintf('Right Active End (%.2f)', active_right_dist_end), ... + 'VerticalAlignment', 'top', 'HorizontalAlignment', 'center', 'Color', 'm', 'FontSize', 8); + + % Customize plot appearance (static elements) + title(ax, 'Bimodal Probability Density Function with Central Sampling Bias and Sample History'); + xlabel(ax, 'Value (x)'); + ylabel(ax, 'Probability Density'); + grid(ax, 'on'); + xlim(ax, [overall_left_edge - 0.1*(overall_right_edge - overall_left_edge), overall_right_edge + 0.1*(overall_right_edge - overall_left_edge)]); + ylim(ax, [0 max(total_pdf_vals)*1.2]); % Set initial Y-limit based on PDF + + % Dynamically build legend entries + % Create dynamic plot objects here to get their handles for the legend + plot_handles.h_hist = histogram(ax, [], 'Normalization', 'pdf', 'BinLimits', [overall_left_edge, overall_right_edge], 'NumBins', 50, ... + 'FaceColor', [0.7 0.7 0.7], 'EdgeColor', [0.5 0.5 0.5], 'FaceAlpha', 0.5, 'Visible', 'off'); % Initially off + plot_handles.h_pick_marker = plot(ax, NaN, NaN, 'go', 'MarkerSize', 10, 'MarkerFaceColor', 'g', 'Visible', 'off'); % Initially off + plot_handles.h_pick_text = text(ax, NaN, NaN, '', 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center', ... + 'Color', 'g', 'FontWeight', 'bold', 'Visible', 'off'); % Initially off + + legend_handles = [plot_handles.h_pdf, plot_handles.h_main_boundary_line, ... + plot_handles.h_central_left_line, plot_handles.h_overall_left_edge_line, ... + plot_handles.h_active_left_dist_line, ... + plot_handles.h_hist, plot_handles.h_pick_marker]; % Use actual handles + legend_strings = {'Overall PDF', 'Main Boundary', 'Central Bias Zone Boundary', 'Overall Range Edge', 'Active Dist. Range', 'Sample History Histogram', 'Current Pick'}; + + plot_handles.h_legend = legend(ax, legend_handles, legend_strings, 'Location', 'best', 'AutoUpdate', 'off'); + + if plot_legend + set(plot_handles.h_legend,'Visible','on') + else + set(plot_handles.h_legend,'Visible','off') + end + + hold off; +end + +% --- Update Dynamic Plot Elements Helper Function --- +function [samples_history_out, plot_handles_out] = update_dynamic_plot(ax, current_stimulus, samples_history_in, plot_handles_in, plot_histogram_flag, plot_chosen_stimuli_flag, plot_legend_flag,plot_distribution_flag) + % params_passed_for_limits: contains overall_left_edge, overall_right_edge for histogram bins, and other static params + + samples_history_out = samples_history_in; % Initialize output with input history + plot_handles_out = plot_handles_in; % Initialize output with input handles (including static ones) + + % Set current axes + axes(ax); + hold on; + + % Add new stimulus to history if valid + if ~isnan(current_stimulus) + samples_history_out = [samples_history_in, current_stimulus]; %#ok + end + + % Update histogram + if plot_histogram_flag + set(plot_handles_out.h_hist, 'Data', samples_history_out, 'Visible', 'on'); + else + set(plot_handles_out.h_hist, 'Visible', 'off'); + end + + % Update current stimulus marker + if plot_chosen_stimuli_flag && ~isnan(current_stimulus) + set(plot_handles_out.h_pick_marker, 'XData', current_stimulus, 'YData', 0, 'Visible', 'on'); + set(plot_handles_out.h_pick_text, 'Position', [current_stimulus, plot_handles_out.total_pdf_vals_max*0.05, 0], ... + 'String', sprintf('Picked: %.4f', current_stimulus), 'Visible', 'on'); + else + set(plot_handles_out.h_pick_marker, 'Visible', 'off'); + set(plot_handles_out.h_pick_text, 'Visible', 'off'); + end + + % Show/hide legend + if plot_legend_flag + set(plot_handles_out.h_legend,'Visible','on') + else + set(plot_handles_out.h_legend,'Visible','off') + end + + % Update static distribution elements visibility based on plot_distribution_flag + % (assuming plot_handles_out already contains these from initialize_static_plot) + static_elements_visibility = 'off'; + if plot_distribution_flag + static_elements_visibility = 'on'; + end + + % Iterate through known static handles and set visibility + static_handle_names = {'h_pdf', 'h_main_boundary_line', 'h_central_left_line', 'h_central_right_line', ... + 'h_central_text_left', 'h_central_text_right', 'h_overall_left_edge_line', ... + 'h_overall_left_edge_text', 'h_overall_right_edge_line', 'h_overall_right_edge_text', ... + 'h_active_left_dist_line', 'h_active_left_dist_text', 'h_active_right_dist_line', ... + 'h_active_right_dist_text'}; + + for i = 1:numel(static_handle_names) + handle_name = static_handle_names{i}; + if isfield(plot_handles_out, handle_name) && isvalid(plot_handles_out.(handle_name)) + set(plot_handles_out.(handle_name), 'Visible', static_elements_visibility); + end + end + + hold off; +end + + +% --- Main Function Logic --- +samples_history_out = []; % Initialize output +h_fig_out = []; % Initialize output +plot_handles_out = struct(); % Initialize output + +% Prepare parameters for helper functions (structs for left and right sides) +current_params_left.lambda_actual = params.lambda_left_actual; +current_params_left.normal_mean = params.normal_mean_left; +current_params_left.normal_std_dev = params.normal_std_dev_left; +current_params_left.half_normal_std_dev = params.half_normal_std_dev_left; +current_params_left.sinusoidal_amplitude_factor = params.sinusoidal_amplitude_factor_left; +current_params_left.sinusoidal_frequency_factor = params.sinusoidal_frequency_factor_left; + +current_params_right.lambda_actual = params.lambda_right_actual; +current_params_right.normal_mean = params.normal_mean_right; +current_params_right.normal_std_dev = params.normal_std_dev_right; +current_params_right.half_normal_std_dev = params.half_normal_std_dev_right; +current_params_right.sinusoidal_amplitude_factor = params.sinusoidal_amplitude_factor_right; +current_params_right.sinusoidal_frequency_factor = params.sinusoidal_frequency_factor_right; + + +% Handle different modes of operation +if strcmp(params.Mode, 'generate_single_sample') + % Generate a single sample and return + + % Determine the side for this specific sample + actual_side_for_sample = lower(params.chosen_side); + if strcmp(actual_side_for_sample, 'auto') + U_side_choice = rand(); + if U_side_choice < P_left_derived + actual_side_for_sample = 'left'; + else + actual_side_for_sample = 'right'; + end + end + + U_bias_decision = rand(); + if U_bias_decision < params.P_central_region + % Biased Sampling: Try to get a sample from the central zone + current_pick_found = false; + while ~current_pick_found + if strcmp(actual_side_for_sample, 'left') + % Sample from the full left overall range + candidate_pick = generate_rand_general(params.left_dist_type, current_params_left, overall_left_edge, main_boundary_actual, ... + params.left_exp_peak_at_boundary_flag, params.left_hn_peak_at_boundary_flag, params.left_sin_peak_at_boundary_flag, main_boundary_actual); + else % 'right' + % Sample from the full right overall range + candidate_pick = generate_rand_general(params.right_dist_type, current_params_right, main_boundary_actual, overall_right_edge, ... + params.right_exp_peak_at_boundary_flag, params.right_hn_peak_at_boundary_flag, params.right_sin_peak_at_boundary_flag, main_boundary_actual); + end + if candidate_pick >= min_central_sampling_range && candidate_pick <= max_central_sampling_range + samples_history_out = candidate_pick; + current_pick_found = true; + end + end + else + % Unbiased Sampling: Pick from the determined side's ACTIVE range + if strcmp(actual_side_for_sample, 'left') + samples_history_out = generate_rand_general(params.left_dist_type, current_params_left, active_left_dist_start, main_boundary_actual, ... + params.left_exp_peak_at_boundary_flag, params.left_hn_peak_at_boundary_flag, params.left_sin_peak_at_boundary_flag, main_boundary_actual); + else % 'right' + samples_history_out = generate_rand_general(params.right_dist_type, current_params_right, main_boundary_actual, active_right_dist_end, ... + params.right_exp_peak_at_boundary_flag, params.right_hn_peak_at_boundary_flag, params.right_sin_peak_at_boundary_flag, main_boundary_actual); + end + end + + % Outputs for 'generate_single_sample' mode + h_fig_out = []; + plot_handles_out = struct(); + +elseif strcmp(params.Mode, 'run_simulation') + % --- Plot Initialization (Static) --- + h_fig_out = figure; + ax = gca; % Get current axes handle + + % Generate a fine grid of x values for smooth plotting across the overall range + x_plot = linspace(overall_left_edge, overall_right_edge, 500); + + % Calculate PDF values for the left and right components. These define the overall PDF shape. + pdf_left_component = get_pdf_general(x_plot, params.left_dist_type, current_params_left, active_left_dist_start, main_boundary_actual, ... + params.left_exp_peak_at_boundary_flag, params.left_hn_peak_at_boundary_flag, params.left_sin_peak_at_boundary_flag, main_boundary_actual); + pdf_right_component = get_pdf_general(x_plot, params.right_dist_type, current_params_right, main_boundary_actual, active_right_dist_end, ... + params.right_exp_peak_at_boundary_flag, params.right_hn_peak_at_boundary_flag, params.right_sin_peak_at_boundary_flag, main_boundary_actual); + + % Combine the components to form the total bimodal PDF (this is the theoretical distribution) + total_pdf_vals = P_left_derived * pdf_left_component + (1 - P_left_derived) * pdf_right_component; + + % Initialize static plot elements + plot_handles_out = initialize_static_plot(ax, total_pdf_vals, x_plot, overall_left_edge, overall_right_edge, main_boundary_actual, min_central_sampling_range, max_central_sampling_range, active_left_dist_start, active_right_dist_end,params.plot_legend); + + % Initialize dynamic plot handles (will be updated by update_dynamic_plot) + plot_handles_out.h_hist = []; % Initialize as empty/null + plot_handles_out.h_pick_marker = []; + plot_handles_out.h_pick_text = []; + + fprintf('Starting simulation of %d random picks...\n', params.num_simulations); + + for k = 1:params.num_simulations + % Generate a uniform random number to decide if we apply central sampling bias + U_bias_decision = rand(); + + current_pick_side_str = ''; % To store 'LEFT' or 'RIGHT' for fprintf + + if U_bias_decision < params.P_central_region + % --- Biased Sampling: Try to get a sample from the central zone --- + current_pick_found = false; + while ~current_pick_found + % Decide if the underlying pick is from the left or right distribution + U_side_choice = rand(); + if U_side_choice < P_left_derived + % Sample from the full left overall range + candidate_pick = generate_rand_general(params.left_dist_type, current_params_left, overall_left_edge, main_boundary_actual, ... + params.left_exp_peak_at_boundary_flag, params.left_hn_peak_at_boundary_flag, params.left_sin_peak_at_boundary_flag, main_boundary_actual); + current_pick_side_str = 'LEFT'; + else + % Sample from the full right overall range + candidate_pick = generate_rand_general(params.right_dist_type, current_params_right, main_boundary_actual, overall_right_edge, ... + params.right_exp_peak_at_boundary_flag, params.right_hn_peak_at_boundary_flag, params.right_sin_peak_at_boundary_flag, main_boundary_actual); + current_pick_side_str = 'RIGHT'; + end + + % Check if the candidate falls within the central sampling zone + if candidate_pick >= min_central_sampling_range && candidate_pick <= max_central_sampling_range + current_pick = candidate_pick; + current_pick_found = true; + fprintf('Random pick (BIASED) from %s distribution: %.4f\n', current_pick_side_str, current_pick); + end + % If not found, the loop continues to re-sample + end + else + % --- Unbiased Sampling: Pick from the overall bimodal distribution's ACTIVE range --- + U_side_choice = rand(); + if U_side_choice < P_left_derived + current_pick = generate_rand_general(params.left_dist_type, current_params_left, active_left_dist_start, main_boundary_actual, ... + params.left_exp_peak_at_boundary_flag, params.left_hn_peak_at_boundary_flag, params.left_sin_peak_at_boundary_flag, main_boundary_actual); + current_pick_side_str = 'LEFT'; + fprintf('Random pick (UNBIASED) from LEFT side (active range [%.2f, %.2f]): %.4f\n', active_left_dist_start, main_boundary_actual, current_pick); + else + current_pick = generate_rand_general(params.right_dist_type, current_params_right, main_boundary_actual, active_right_dist_end, ... + params.right_exp_peak_at_boundary_flag, params.right_hn_peak_at_boundary_flag, params.right_sin_peak_at_boundary_flag, main_boundary_actual); + current_pick_side_str = 'RIGHT'; + fprintf('Random pick (UNBIASED) from RIGHT side (active range [%.2f, %.2f]): %.4f\n', main_boundary_actual, active_right_dist_end, current_pick); + end + end + + % Update the plot elements + [samples_history_out, plot_handles_out] = update_dynamic_plot(ax, current_pick, samples_history_out, plot_handles_out, ... + true, true, true,true);% Always show all during simulation + + drawnow; + pause(params.pause_duration); + + fprintf('Pick %d: %.4f\n', k, current_pick); + end + + fprintf('Simulation complete. Total picks: %d\n', params.num_simulations); + +elseif strcmp(params.Mode, 'initialize_plot') + % --- Initialize Static Plot --- + % Get axes handle, create if not provided + if isempty(params.ax_handle) || ~isgraphics(params.ax_handle, 'axes') + h_fig_out = figure; + ax = gca; + else + ax = params.ax_handle; + h_fig_out = get(ax, 'Parent'); % Get figure handle from axes + end + + % Generate a fine grid of x values for smooth plotting across the overall range + x_plot = linspace(overall_left_edge, overall_right_edge, 500); + + % Calculate PDF values for the left and right components. These define the overall PDF shape. + pdf_left_component = get_pdf_general(x_plot, params.left_dist_type, current_params_left, active_left_dist_start, main_boundary_actual, ... + params.left_exp_peak_at_boundary_flag, params.left_hn_peak_at_boundary_flag, params.left_sin_peak_at_boundary_flag, main_boundary_actual); + pdf_right_component = get_pdf_general(x_plot, params.right_dist_type, current_params_right, main_boundary_actual, active_right_dist_end, ... + params.right_exp_peak_at_boundary_flag, params.right_hn_peak_at_boundary_flag, params.right_sin_peak_at_boundary_flag, main_boundary_actual); + + % Combine the components to form the total bimodal PDF (this is the theoretical distribution) + total_pdf_vals = P_left_derived * pdf_left_component + (1 - P_left_derived) * pdf_right_component; + + % Call helper to initialize static plot elements + plot_handles_out = initialize_static_plot(ax, total_pdf_vals, x_plot, overall_left_edge, overall_right_edge, main_boundary_actual, min_central_sampling_range, max_central_sampling_range, active_left_dist_start, active_right_dist_end,params.plot_legend); + + % No samples history returned for this mode + samples_history_out = []; + +elseif strcmp(params.Mode, 'update_plot') + % --- Update existing plot elements --- + if isempty(params.ax_handle) || ~isgraphics(params.ax_handle, 'axes') + % error('In ''update_plot'' mode, an existing axes handle (''ax_handle'') must be provided.'); + return; + end + + % % Re-calculate PDF values as they are needed for scaling and text placement + % % (This is necessary because total_pdf_vals is used for text positioning, etc.) + % x_plot = linspace(overall_left_edge, overall_right_edge, 500); + % pdf_left_component = get_pdf_general(x_plot, params.left_dist_type, current_params_left, active_left_dist_start, main_boundary_actual, ... + % params.left_exp_peak_at_boundary_flag, params.left_hn_peak_at_boundary_flag, params.left_sin_peak_at_boundary_flag, main_boundary_actual); + % pdf_right_component = get_pdf_general(x_plot, params.right_dist_type, current_params_right, main_boundary_actual, active_right_dist_end, ... + % params.right_exp_peak_at_boundary_flag, params.right_hn_peak_at_boundary_flag, params.right_sin_peak_at_boundary_flag, main_boundary_actual); + % total_pdf_vals = P_left_derived * pdf_left_component + (1 - P_left_derived) * pdf_right_component; + + [samples_history_out, plot_handles_out] = update_dynamic_plot(params.ax_handle, params.current_stimulus, params.samples_history_in, params.plot_handles_in, ... + params.plot_histogram, params.plot_chosen_stimuli, params.plot_legend,params.plot_distribution); + + h_fig_out = get(params.ax_handle, 'Parent'); % Return figure handle of the updated axes + drawnow; +else + return;% error('Invalid Mode specified. Use ''run_simulation'', ''generate_single_sample'', ''initialize_plot'', or ''update_plot''.'); +end + +end % End of main function diff --git a/Protocols/@ArpitSoundCatContinuous/private/RealTimeAnalysis.m b/Protocols/@ArpitSoundCatContinuous/private/RealTimeAnalysis.m new file mode 100644 index 00000000..9fb5049f --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/RealTimeAnalysis.m @@ -0,0 +1,1040 @@ +function state = RealTimeAnalysis(action, state, data, handles, config, varargin) +% RealTimeAnalysis function to process and plot trial data for BControl. +% This function is called repeatedly from a parent script +% (e.g., Psychometric.m) and is made stateless by passing all required +% information in and out on every call. It uses a try/catch block to +% prevent crashes during live experiments. +% +% Args: +% action (char): The operation to perform. Can be: +% 'live': Standard update after a block of trials. +% 'redraw': Redraws live plots, typically when a figure becomes visible. +% 'context_switch': Special handling for leftover trials when a context changes. +% 'custom': Plots a single, user-defined range of trials and adds a row to the table. +% 'context': Plots multiple, user-defined ranges for comparison and adds rows to the table. +% 'evaluate': Computes key performance metrics for given trial ranges without plotting. +% +% state (struct): Contains variables that are modified and passed back. +% .blockStatsHistory (struct array): Stores analysis results for each block. +% .block_count (double): Counter for the number of blocks analyzed. +% .last_analyzed_valid_trial (double): Counter for the last valid trial included in an analysis. +% .context_blocks (double): array to keep track of the blocks when the context was switched +% .table_row_editable (double): array same size as the number of rows in table decides which row the user can +% edit. Its a sanity check so that user doesn't select custom or context rows +% +% +% data (struct): Contains the complete, read-only data histories for the session. +% .hit_history (vector): History of hits (1), misses (0), or NaN. +% .previous_sides (vector): History of sides presented (e.g., 0 for left, 1 for right). +% .stim_history (vector): History of stimulus values. +% .full_rule_history (string): psychometric rule. +% .full_dist_right, .full_dist_left: Histories of stimulus distributions. +% +% handles (struct): Contains handles to all necessary GUI components. +% .main_fig (handle): Handle to the main analysis figure. +% .axes_h (struct): A struct containing handles to the 6 plot axes. +% .ui_table (handle): Handle to the results table. +% +% config (struct): Contains session-wide constants. +% .trials_per_block (double): Number of valid trials needed for a live update. +% .true_mu (double): The true boundary of the stimulus categories. +% .stimuli_range (1x2 vector): The [min, max] of possible stimulus values. +% .debug (logical): If true, errors will be rethrown; if false, they will be caught and displayed as warnings. +% +% varargin: Action-specific arguments based on the 'action' string. +% For 'live', 'redraw', 'context_switch': +% varargin{1} (struct): A flags struct with logical values. +% .psych (logical): Toggles the psychometric plot. +% .hit (logical): Toggles the hit rate plot. +% .stim (logical): Toggles the stimulus histogram. +% For 'custom': +% varargin{1} (struct): The flags struct (as above). +% varargin{2} (double): The start trial for the custom range. +% varargin{3} (double): The end trial for the custom range. +% For 'context': +% varargin{1} (struct): The flags struct (as above). +% varargin{2} (cell): A cell array of trial ranges, e.g., {[s1,e1], [s2,e2]}. +% varargin{3} (cell): A cell array of context names, e.g., {'Hard A', 'Uniform', 'Hard B'}. +% For 'evaluate': +% varargin{1} (cell): A cell array of trial ranges, e.g., {[s1,e1], [s2,e2]}. +% +% Returns: +% state (struct): For most actions, this is the updated state struct. +% For 'evaluate', this is a struct array with performance metrics: +% .start_trial +% .end_trial +% .distribution_type +% .calculated_boundary +% .total_hit_percent +% .total_violations_percent +% .right_correct_percent +% .left_correct_percent + +% Ensure that the core data vectors are of the same length to prevent indexing errors. +% This can happen if the session is interrupted mid-trial. + try + len_hit = numel(data.hit_history); + len_sides = numel(data.previous_sides); + len_stim = numel(data.stim_history); + + min_len = min([len_hit, len_sides, len_stim]); + + if len_hit > min_len || len_sides > min_len || len_stim > min_len + warning('RealTimeAnalysis:DataMismatch', ... + 'Data history vectors have mismatched lengths. Truncating to the shortest length (%d).', min_len); + + data.hit_history = data.hit_history(1:min_len); + data.previous_sides = data.previous_sides(1:min_len); + data.stim_history = data.stim_history(1:min_len); + end + catch ME + warning('RealTimeAnalysis:DataIntegrityError', 'Could not perform data integrity check: %s', ME.message); + end + % --- End of Data Integrity Check --- + + + try + switch lower(action) + % ================================================================= + % LIVE UPDATE ACTION + % ================================================================= + case 'live' + if numel(varargin) < 1, error('Live action requires a flags struct.'); end + flags = varargin{1}; + + lastAnalyzed = state.last_analyzed_valid_trial; + validTrials = sum(~isnan(data.hit_history)); + + if (validTrials - lastAnalyzed) >= config.trials_per_block + valid_indices = find(~isnan(data.hit_history)); + buffer_indices = valid_indices(end - config.trials_per_block + 1 : end); + + dataBuffer.stim = data.stim_history(buffer_indices); + dataBuffer.hit = data.hit_history(buffer_indices); + dataBuffer.side = data.previous_sides(buffer_indices); + dataBuffer.indices = buffer_indices; + + state = analyzeLiveChunk(state, data, handles, config, dataBuffer, flags); + end + + % ================================================================= + % CONTEXT SWITCH ACTION + % ================================================================= + case 'context_switch' + if numel(varargin) < 1, error('context_switch action requires a flags struct.'); end + flags = varargin{1}; + + merge_threshold = round(2 * config.trials_per_block / 3); + + lastAnalyzed = state.last_analyzed_valid_trial; + validTrials = sum(~isnan(data.hit_history)); + remaining_valid_trials = validTrials - lastAnalyzed; + state.context_blocks(end + 1) = state.block_count; + + if remaining_valid_trials > 0 + valid_indices = find(~isnan(data.hit_history)); + new_valid_indices = valid_indices(lastAnalyzed + 1 : end); + + if remaining_valid_trials < merge_threshold && state.block_count > 0 + last_block_indices = state.blockStatsHistory(end).indices; + combined_indices = [last_block_indices, new_valid_indices']; + + dataBuffer.stim = data.stim_history(combined_indices); + dataBuffer.hit = data.hit_history(combined_indices); + dataBuffer.side = data.previous_sides(combined_indices); + dataBuffer.indices = combined_indices; + + state = reAnalyzeLastChunk(state, data, handles, config, dataBuffer, flags); + + elseif remaining_valid_trials >= merge_threshold + dataBuffer.stim = data.stim_history(new_valid_indices); + dataBuffer.hit = data.hit_history(new_valid_indices); + dataBuffer.side = data.previous_sides(new_valid_indices); + dataBuffer.indices = new_valid_indices; + + state = analyzeLiveChunk(state, data, handles, config, dataBuffer, flags); + end + end + + % ================================================================= + % REDRAW ACTION + % ================================================================= + case 'redraw' + if numel(varargin) < 1, error('Redraw action requires a flags struct.'); end + flags = varargin{1}; + + updateLivePlots(state, data, handles, config, flags); + + % ================================================================= + % CUSTOM PLOT ACTION + % ================================================================= + case 'custom' + if numel(varargin) < 3, error('Custom action requires flags, start_trial, and end_trial.'); end + flags = varargin{1}; + start_idx = varargin{2}; + end_idx = varargin{3}; + + if start_idx >= end_idx || start_idx < 1 || end_idx > numel(data.hit_history), warning('Invalid trial range for custom plot.'); return; end + + indices = start_idx:end_idx; + hit_chunk = data.hit_history(indices); + valid_mask = ~isnan(hit_chunk); + if sum(valid_mask) < 10, warning('Not enough valid trials in custom range to plot.'); return; end + + stim_fit = data.stim_history(indices(valid_mask)); + side_fit = data.previous_sides(indices(valid_mask)); + hit_fit = hit_chunk(valid_mask); + + current_rule = data.full_rule_history; + + % Add results to table, but don't select for live plotting + [~, newRow] = processBlock(data, config, struct('stim', stim_fit, 'hit', hit_fit, 'side', side_fit, 'indices', indices)); + newRow{1} = false; % Ensure it's not selected + updateTable(handles, newRow); + state.table_row_editable(end+1) = false; + + if flags.psych, plotCustomPsychometric(handles.axes_h.custom_psych, stim_fit, side_fit, hit_fit, config, current_rule); end + if flags.hit, plotCustomHitRates(handles.axes_h.custom_hitrate, hit_fit, side_fit, [start_idx, end_idx], state.blockStatsHistory); end + if flags.stim, plotCustomStimulusHistogram(handles.axes_h.custom_stim, stim_fit, hit_fit, [start_idx, end_idx], state.blockStatsHistory, config); end + + % ================================================================= + % CONTEXT PLOT ACTION + % ================================================================= + case 'context' + if numel(varargin) < 2, error('Context action requires flags and a cell array of contexts.'); end + flags = varargin{1}; + contexts = varargin{2}; + contexts_names = varargin{3}; + + if ~iscell(contexts) || isempty(contexts), error('Contexts must be a non-empty cell array of [start, end] pairs.'); end + + num_contexts = numel(contexts); + context_colors = createTemporalColormap(num_contexts); + + psych_data = cell(1, num_contexts); + hit_rate_data = zeros(num_contexts, 3); + hit_rate_std = zeros(num_contexts, 3); + stim_hist_data = cell(1, num_contexts); + + for i = 1:num_contexts + start_idx = contexts{i}(1); + end_idx = contexts{i}(2); + + if start_idx >= end_idx || start_idx < 1 || end_idx > numel(data.hit_history), continue; end + + indices = start_idx:end_idx; + hit_chunk = data.hit_history(indices); + valid_mask = ~isnan(hit_chunk); + if sum(valid_mask) < 10, continue; end + + stim_fit = data.stim_history(indices(valid_mask)); + side_fit = data.previous_sides(indices(valid_mask)); + hit_fit = hit_chunk(valid_mask); + + current_rule = data.full_rule_history; + + % Add results to table, but don't select for live plotting + [~, newRow] = processBlock(data, config, struct('stim', stim_fit, 'hit', hit_fit, 'side', side_fit, 'indices', indices)); + newRow{1} = false; % Ensure it's not selected + updateTable(handles, newRow); + state.table_row_editable(end+1) = false; + + physical_response = zeros(size(hit_fit)); + physical_response(hit_fit == 1) = side_fit(hit_fit == 1); + physical_response(hit_fit == 0) = 1 - side_fit(hit_fit == 0); + + response_for_fitting = physical_response; + if contains(string(current_rule), 'Left', 'IgnoreCase', true), response_for_fitting = 1 - response_for_fitting; end + + options.MinTrials = 10; + [y_pred, fitParams, ~, ~] = realtimepsychometricFit(stim_fit, response_for_fitting, config.stimuli_range, options); + psych_data{i} = struct('y_pred', y_pred, 'fitParams', fitParams, 'stim_fit', stim_fit, 'physical_response', physical_response, 'rule', current_rule); + + hit_rate_data(i, 1) = 100 * sum(hit_fit == 1) / numel(hit_fit); + hit_rate_data(i, 2) = 100 * sum(hit_fit(side_fit==0)==1) / sum(side_fit==0); + hit_rate_data(i, 3) = 100 * sum(hit_fit(side_fit==1)==1) / sum(side_fit==1); + + left_edges = linspace(min(config.stimuli_range), config.true_mu, 6); + right_edges = linspace(config.true_mu, max(config.stimuli_range), 6); + bin_edges = unique([left_edges, right_edges]); + stim_hist_data{i}.correct = histcounts(stim_fit(hit_fit == 1), bin_edges); + stim_hist_data{i}.incorrect = histcounts(stim_fit(hit_fit == 0), bin_edges); + + relevant_blocks = []; + for j = 1:numel(state.blockStatsHistory) + block_indices = state.blockStatsHistory(j).indices; + if min(block_indices) >= start_idx && max(block_indices) <= end_idx + relevant_blocks = [relevant_blocks, state.blockStatsHistory(j)]; + end + end + + if ~isempty(relevant_blocks) + hit_rate_std(i, 1) = std(arrayfun(@(blk) blk.hitRates.overall, relevant_blocks), 'omitnan'); + hit_rate_std(i, 2) = std(arrayfun(@(blk) blk.hitRates.left, relevant_blocks), 'omitnan'); + hit_rate_std(i, 3) = std(arrayfun(@(blk) blk.hitRates.right, relevant_blocks), 'omitnan'); + correct_cells = arrayfun(@(s) s.stimCounts.correct(:)', relevant_blocks, 'UniformOutput', false); + counts_matrix_corr = cell2mat(correct_cells); + incorrect_cells = arrayfun(@(s) s.stimCounts.incorrect(:)', relevant_blocks, 'UniformOutput', false); + counts_matrix_incorr = cell2mat(incorrect_cells); + stim_hist_data{i}.mean_corr = mean(counts_matrix_corr, 1); + stim_hist_data{i}.std_corr = std(counts_matrix_corr, 0, 1); + stim_hist_data{i}.mean_incorr = mean(counts_matrix_incorr, 1); + stim_hist_data{i}.std_incorr = std(counts_matrix_incorr, 0, 1); + else + hit_rate_std(i, :) = 0; + end + end + + if flags.psych, plotContextPsychometric(handles.axes_h.custom_psych, psych_data, config, context_colors,contexts_names); end + if flags.hit, plotContextHitRates(handles.axes_h.custom_hitrate, hit_rate_data, hit_rate_std, context_colors,contexts_names); end + if flags.stim, plotContextStimulusHistogram(handles.axes_h.custom_stim, stim_hist_data, config, context_colors,contexts_names); end + + % ================================================================= + % EVALUATE ACTION + % ================================================================= + case 'evaluate' + if numel(varargin) < 1, error('Evaluate action requires a cell array of contexts.'); end + contexts = varargin{1}; + if ~iscell(contexts) || isempty(contexts), error('Contexts must be a non-empty cell array of [start, end] pairs.'); end + + num_contexts = numel(contexts); + results = struct('start_trial', [], 'end_trial', [], 'distribution_type', [], 'calculated_boundary', [], 'total_hit_percent', [], 'total_violations_percent', [], 'right_correct_percent', [], 'left_correct_percent', []); + results = repmat(results, 1, num_contexts); + + for i = 1:num_contexts + start_idx = contexts{i}(1); + end_idx = contexts{i}(2); + + if start_idx >= end_idx || start_idx < 1 || end_idx > numel(data.hit_history), continue; end + + indices = start_idx:end_idx; + hit_chunk = data.hit_history(indices); + side_chunk = data.previous_sides(indices); + stim_chunk = data.stim_history(indices); + + valid_mask = ~isnan(hit_chunk); + if sum(valid_mask) < 10, continue; end + + stim_fit = stim_chunk(valid_mask); + side_fit = side_chunk(valid_mask); + hit_fit = hit_chunk(valid_mask); + + current_rule = data.full_rule_history; + + physical_response = zeros(size(hit_fit)); + physical_response(hit_fit == 1) = side_fit(hit_fit == 1); + physical_response(hit_fit == 0) = 1 - side_fit(hit_fit == 0); + + response_for_fitting = physical_response; + if contains(string(current_rule), 'Left', 'IgnoreCase', true), response_for_fitting = 1 - response_for_fitting; end + + options.MinTrials = 10; + [~, fitParams, ~, ~] = realtimepsychometricFit(stim_fit, response_for_fitting, config.stimuli_range, options); + + results(i).start_trial = start_idx; + results(i).end_trial = end_idx; + results(i).distribution_type = getDistributionType(data, indices, current_rule); + results(i).calculated_boundary = fitParams(1); + results(i).total_hit_percent = 100 * mean(hit_fit); + results(i).total_violations_percent = 100 * mean(isnan(data.hit_history(indices))); + + right_trials = (side_fit == 1); + left_trials = (side_fit == 0); + results(i).right_correct_percent = 100 * mean(hit_fit(right_trials)); + results(i).left_correct_percent = 100 * mean(hit_fit(left_trials)); + end + state = results; % Override the return value for this action + return; % Exit early + + otherwise + error('Unknown action: "%s". Use "live", "custom", or "context".', action); + end + catch ME + % Create a more detailed error message including the line number. + if ~isempty(ME.stack) + + % safe_filename = strrep(ME.stack(1).file, '\', '\\'); + % errorLocation = sprintf('File: %s, Function: %s, Line: %d', ... + % safe_filename, ME.stack(1).name, ME.stack(1).line); + + errorLocation = ['File: ' ME.stack(1).file ... + ', Function: ' ME.stack(1).name ... + ', Line: ' num2str(ME.stack(1).line)]; + else + errorLocation = 'Location not available in error stack.'; + end + + % fullErrorMessage = sprintf('An error occurred in RealTimeAnalysis:\n Error: %s\n %s', ... + % ME.message, errorLocation); + + fullErrorMessage = ['An error occurred in RealTimeAnalysis:' newline ... + ' Error: ' ME.message newline ... + ' ' errorLocation]; + + fullErrorMsg = strrep(fullErrorMessage, '\', '\\'); + warning('RealTimeAnalysis:Error', fullErrorMsg); + + if config.debug, rethrow(ME); end % In experiment mode, the function will gracefully return the original state. + end + + % ================================================================= + % NESTED HELPER FUNCTIONS + % ================================================================= + + %% LIVE ANALYSIS HELPERS + function state = analyzeLiveChunk(state, data, handles, config, dataBuffer, flags) + state.block_count = state.block_count + 1; + [newBlockStat, newRow] = processBlock(data, config, dataBuffer); + state.blockStatsHistory = [state.blockStatsHistory, newBlockStat]; + state.last_analyzed_valid_trial = sum(~isnan(data.hit_history)); + updateTable(handles, newRow); + state.table_row_editable(end+1) = true; + + if strcmp(get(handles.main_fig, 'Visible'), 'on') + updateLivePlots(state, data, handles, config, flags); + end + + + end + + function state = reAnalyzeLastChunk(state, data, handles, config, dataBuffer, flags) + [newBlockStat, newRow] = processBlock(data, config, dataBuffer); + state.blockStatsHistory(end) = newBlockStat; + state.last_analyzed_valid_trial = sum(~isnan(data.hit_history)); + replaceLastEditableTableRow(handles, newRow, state.table_row_editable); + + if strcmp(get(handles.main_fig, 'Visible'), 'on') + updateLivePlots(state, data, handles, config, flags); + end + + + end + + function [blockStat, tableRow] = processBlock(data, config, dataBuffer) + physical_response = zeros(size(dataBuffer.hit)); + physical_response(dataBuffer.hit == 1) = dataBuffer.side(dataBuffer.hit == 1); + physical_response(dataBuffer.hit == 0) = 1 - dataBuffer.side(dataBuffer.hit == 0); + + current_rule = data.full_rule_history; + + response_for_fitting = physical_response; + if contains(string(current_rule), 'Left', 'IgnoreCase', true) + response_for_fitting = 1 - physical_response; + end + + options.MinTrials = 20; + [~, fitParams, methodUsed, fitStatus] = realtimepsychometricFit(dataBuffer.stim, response_for_fitting, config.stimuli_range, options); + + hr.overall = 100 * sum(dataBuffer.hit == 1) / numel(dataBuffer.hit); + left_mask = (dataBuffer.side == 0); + if any(left_mask), hr.left = 100 * sum(dataBuffer.hit(left_mask)==1) / sum(left_mask); else, hr.left = NaN; end + right_mask = (dataBuffer.side == 1); + if any(right_mask), hr.right = 100 * sum(dataBuffer.hit(right_mask)==1) / sum(right_mask); else, hr.right = NaN; end + + blockStat.indices = dataBuffer.indices; + blockStat.hitRates = hr; + + left_edges = linspace(min(config.stimuli_range), config.true_mu, 6); + right_edges = linspace(config.true_mu, max(config.stimuli_range), 6); + bin_edges = unique([left_edges, right_edges]); + stim_correct = dataBuffer.stim(dataBuffer.hit == 1); + stim_incorrect = dataBuffer.stim(dataBuffer.hit == 0); + blockStat.stimCounts.correct = histcounts(stim_correct, bin_edges); + blockStat.stimCounts.incorrect = histcounts(stim_incorrect, bin_edges); + + start_trial = min(dataBuffer.indices); end_trial = max(dataBuffer.indices); + dist_right = strjoin(string(unique(data.full_dist_right(dataBuffer.indices))), ', '); + dist_left = strjoin(string(unique(data.full_dist_left(dataBuffer.indices))), ', '); + + select_status = ismember(methodUsed, {'ridge', 'robust'}); + if select_status + tableRow = {select_status, current_rule, dist_left, dist_right, start_trial, end_trial, fitParams(2), config.true_mu, fitParams(1), fitParams(3), fitParams(4), string(methodUsed) + " (" + fitStatus + ")", hr.overall, hr.left, hr.right}; + else + tableRow = {select_status, current_rule, dist_left, dist_right, start_trial, end_trial, NaN, config.true_mu, NaN, NaN, NaN, string(methodUsed) + " (" + fitStatus + ")", hr.overall, hr.left, hr.right}; + end + end + + function updateTable(handles, newRow) + currentData = get(handles.ui_table, 'Data'); + set(handles.ui_table, 'Data', [currentData; newRow]); + end + + function replaceLastTableRow(handles, newRow, table_row_editable) + currentData = get(handles.ui_table, 'Data'); + if isempty(currentData) + return; + end + + % Find the index of the last 'live' block (which is editable) + last_editable_row_index = find(table_row_editable, 1, 'last'); + + if ~isempty(last_editable_row_index) + newRow{1} = true; % Select the new row + + % Replace the correct row, not just the last one + currentData(last_editable_row_index, :) = newRow; + + set(handles.ui_table, 'Data', currentData); + else + % Fallback in case no editable row is found (should not happen) + updateTable(handles, newRow); + end + end + + function updateLivePlots(state, data, handles, config, flags) + if flags.psych, updatePsychometricPlot(handles.axes_h.live_psych, handles.ui_table, config); end + if flags.hit, updateHitRatePlot(handles.axes_h.live_hitrate,state.context_blocks, handles.ui_table); end + if flags.stim, updateStimulusHistogram(handles.axes_h.live_stim, state, data, config,handles.ui_table); end + end + + function updatePsychometricPlot(ax, ui_table_handle, config) + allData = get(ui_table_handle, 'Data'); + if isempty(allData) + cla(ax, 'reset'); + return; + end + + logical_indices = allData.Select == 1; + selectedData = allData(logical_indices, :); + + cla(ax, 'reset'); hold(ax, 'on'); + + xline(ax, config.true_mu, '--k', 'LineWidth', 1.5, 'HandleVisibility', 'off'); + yline(ax, 0.5, ':', 'Color', [0.5 0.5 0.5], 'HandleVisibility', 'off'); + + if ~isempty(selectedData) + num_to_plot = height(selectedData); + colors = createTemporalColormap(num_to_plot); + + psychometricFun = @(params, x) params(3) + (1 - params(3) - params(4)) ./ (1 + exp(-(x - params(1)) / params(2))); + xGrid = linspace(config.stimuli_range(1), config.stimuli_range(2), 300)'; + + for i = 1:num_to_plot + row = selectedData(i, :); + + fitParams = [row.CalBoundary, row.Slope, row.LapseA, row.LapseB]; + if any(isnan(fitParams)), continue; end + + y_curve = psychometricFun(fitParams, xGrid); + + alpha = 0.4; width = 1.5; + + if i == num_to_plot, alpha = 0.9; width = 2.5; end + + plot(ax, xGrid, y_curve, 'Color', [colors(i,:), alpha], 'LineWidth', width, 'DisplayName', sprintf('Block (T %d)', row.Start_trial)); + xline(ax, row.CalBoundary, '-', 'Color', [colors(i,:), alpha], 'LineWidth', width-0.5, 'HandleVisibility', 'off'); + end + legend(ax, 'show', 'Location', 'southeast'); + end + grid(ax, 'on'); hold(ax, 'off'); + ylabel(ax, 'P(Choice)'); title(ax, 'Live Psychometric Curves'); + end + + function updateHitRatePlot(ax, context_blocks, ui_table_handle) + allData = get(ui_table_handle, 'Data'); + if isempty(allData) + cla(ax, 'reset'); + return; + end + + logical_mask = allData.Select == 1; + selectedData = allData(logical_mask, :); + + cla(ax, 'reset'); + if isempty(selectedData) + title(ax, 'Live Hit Rate per Block (Nothing Selected)'); + xlim(ax, [0.5, 10.5]); ylim(ax, [0 100]); grid(ax, 'on'); + return; + end + + hr_overall = selectedData.("Overall Hit %"); + hr_left = selectedData.("Left Hit %"); + hr_right = selectedData.("Right Hit %"); + + hold(ax, 'on'); + x_axis = 1:height(selectedData); + plot(ax, x_axis, hr_overall, '-ok', 'LineWidth', 2, 'DisplayName', 'Overall'); + plot(ax, x_axis, hr_left, '--ob', 'LineWidth', 1.5, 'DisplayName', 'Left'); + plot(ax, x_axis, hr_right, '--or', 'LineWidth', 1.5, 'DisplayName', 'Right'); + % plotting context change + if length(context_blocks) > 1 + for k = 2:length(context_blocks) + xline(ax, context_blocks(k), '--k', 'LineWidth', 1.5, 'HandleVisibility', 'off'); + end + end + hold(ax, 'off'); legend(ax, 'show', 'Location', 'southeast'); + xlim(ax, [0.5, max(10, height(selectedData) + 0.5)]); ylim(ax, [0 100]); + xlabel(ax, 'Selected Block Number'); ylabel(ax, 'Hit %'); + title(ax, 'Live Hit Rate for Selected Blocks'); + grid(ax, 'on'); + end + + function updateStimulusHistogram(ax, state, data, config, ui_table_handle) + + allData = get(ui_table_handle, 'Data'); + if isempty(allData) || state.block_count == 0 + cla(ax, 'reset'); + return; + end + + logical_mask = allData.Select == 1; + selected_indices = find(logical_mask); % Get row numbers of selected blocks + + + cla(ax, 'reset'); + if isempty(selected_indices) + title(ax, 'Live Choice Distribution (Nothing Selected)'); + return; + end + + n_selected_blocks = numel(selected_indices); + red_map = interp1([0 1], [1 0.7 0.7; 0.9 0.2 0.1], linspace(0, 1, n_selected_blocks)); + green_map = interp1([0 1], [0.7 1 0.7; 0 0.65 0], linspace(0, 1, n_selected_blocks)); + + left_edges = linspace(min(config.stimuli_range), config.true_mu, 6); + right_edges = linspace(config.true_mu, max(config.stimuli_range), 6); + bin_edges = unique([left_edges, right_edges]); + bin_centers = (bin_edges(1:end-1) + bin_edges(2:end)) / 2; + + yyaxis(ax, 'left'); + hold(ax, 'on'); + yyaxis(ax, 'right'); + hold(ax, 'on'); + + max_count = 0; + + for p = 1:n_selected_blocks + block_idx = selected_indices(p); % Use the index of the selected row + block_stat = state.blockStatsHistory(block_idx); + + multiplier = 1; + if p == n_selected_blocks, multiplier = 2; end + + yyaxis(ax, 'left'); + plot(ax, bin_centers, block_stat.stimCounts.incorrect, '-', 'Color', red_map(p,:), 'LineWidth', multiplier * 1.5); + plot(ax, bin_centers, block_stat.stimCounts.correct, '-', 'Color', green_map(p,:), 'LineWidth', multiplier * 1.5); + max_count = max([max_count, block_stat.stimCounts.correct, block_stat.stimCounts.incorrect]); + + yyaxis(ax, 'right'); + block_indices_raw = block_stat.indices; + valid_mask = ~isnan(data.hit_history(block_indices_raw)); + stim_valid = data.stim_history(block_indices_raw(valid_mask)); + hit_valid = data.hit_history(block_indices_raw(valid_mask)); + stim_correct = stim_valid(hit_valid == 1); + stim_incorrect = stim_valid(hit_valid == 0); + + jitter_base = (p - 1) * 0.2; + jitter_incorrect = jitter_base + 0.08 * rand(size(stim_incorrect)); + jitter_correct = jitter_base + 0.08 * rand(size(stim_correct)); + scatter(ax, stim_incorrect, jitter_incorrect, multiplier * 25, red_map(p,:), 'filled', 'MarkerFaceAlpha', multiplier * 0.4); + scatter(ax, stim_correct, jitter_correct, multiplier * 25, green_map(p,:), 'filled', 'MarkerFaceAlpha', multiplier * 0.4); + end + + yyaxis(ax, 'left'); + ylabel(ax, 'Trial Count (Binned)'); + ax.YColor = 'k'; + ylim(ax, [0, max(1, max_count * 1.1)]); + + yyaxis(ax, 'right'); + ylim(ax, [0, n_selected_blocks * 0.2 + 0.1]); + ax.YTick = []; + ax.YColor = 'none'; + + hold(ax, 'off'); + xline(ax, config.true_mu, '--k', 'Boundary', 'LineWidth', 2); + xlabel(ax, 'Stimulus Value'); + title(ax, 'Live Choice Distribution for Selected Blocks'); + yyaxis(ax, 'left'); + end + + %% CUSTOM PLOTTING HELPERS + function plotCustomPsychometric(ax, stim_fit, side_fit, hit_fit, config, rule) + physical_response = zeros(size(hit_fit)); + physical_response(hit_fit == 1) = side_fit(hit_fit == 1); + physical_response(hit_fit == 0) = 1 - side_fit(hit_fit == 0); + + response_for_fitting = physical_response; + y_label = 'P(Right)'; + + if contains(string(rule), 'Left', 'IgnoreCase', true) + response_for_fitting = 1 - physical_response; + y_label = 'P(Left)'; + end + + options.MinTrials = 10; + [y_pred, fitParams, ~, ~] = realtimepsychometricFit(stim_fit, response_for_fitting, config.stimuli_range, options); + + cla(ax, 'reset'); hold(ax, 'on'); + xGrid = linspace(config.stimuli_range(1), config.stimuli_range(2), 300)'; + plot(ax, xGrid, y_pred, 'r-', 'LineWidth', 2, 'DisplayName', 'Fitted Curve'); + xline(ax, config.true_mu, '--k', 'LineWidth', 1.5, 'DisplayName', 'True Boundary'); + xline(ax, fitParams(1), '--b', 'LineWidth', 1.5, 'DisplayName', 'Calculated'); + yline(ax, 0.5, ':', 'Color', [0.5 0.5 0.5],'HandleVisibility', 'off'); + grid(ax, 'on'); hold(ax, 'off'); + legend(ax); title(ax, sprintf('Custom Fit (Mu=%.2f)', fitParams(1))); + xlabel(ax, 'Stimulus'); ylabel(ax, y_label); + end + + function plotCustomHitRates(ax, hit_fit, side_fit, custom_range, blockStats) + cla(ax, 'reset'); hold(ax, 'on'); + + hr_custom.overall = 100 * sum(hit_fit == 1) / numel(hit_fit); + hr_custom.left = 100 * sum(hit_fit(side_fit==0)==1) / sum(side_fit==0); + hr_custom.right = 100 * sum(hit_fit(side_fit==1)==1) / sum(side_fit==1); + + relevant_blocks = []; + for i = 1:numel(blockStats) + block_indices = blockStats(i).indices; + if min(block_indices) >= custom_range(1) && max(block_indices) <= custom_range(2) + relevant_blocks = [relevant_blocks, blockStats(i)]; + end + end + + if ~isempty(relevant_blocks) + overall_values = arrayfun(@(blk) blk.hitRates.overall, relevant_blocks); + left_values = arrayfun(@(blk) blk.hitRates.left, relevant_blocks); + right_values = arrayfun(@(blk) blk.hitRates.right, relevant_blocks); + + % Now calculate the standard deviation on the resulting vectors + std_dev.overall = std(overall_values, 'omitnan'); + std_dev.left = std(left_values, 'omitnan'); + std_dev.right = std(right_values, 'omitnan'); + else + std_dev.overall = 0; std_dev.left = 0; std_dev.right = 0; + end + + cats = categorical({'Overall', 'Left', 'Right'}); + errorbar(ax, cats, [hr_custom.overall, hr_custom.left, hr_custom.right], ... + [std_dev.overall, std_dev.left, std_dev.right], ... + 'o', 'MarkerSize', 8, 'CapSize', 15, 'LineWidth', 1.5); + + ylabel(ax, 'Hit %'); title(ax, 'Hit Rates (w/ Block STD)'); + ylim(ax, [0 105]); grid(ax, 'on'); + end + + function plotCustomStimulusHistogram(ax, stim_fit, hit_fit, custom_range, blockStats, config) + cla(ax, 'reset'); hold(ax, 'on'); + + left_edges = linspace(min(config.stimuli_range), config.true_mu, 6); + right_edges = linspace(config.true_mu, max(config.stimuli_range), 6); + bin_edges = unique([left_edges, right_edges]); + bin_centers = (bin_edges(1:end-1) + bin_edges(2:end)) / 2; + + counts_custom.correct = histcounts(stim_fit(hit_fit == 1), bin_edges); + counts_custom.incorrect = histcounts(stim_fit(hit_fit == 0), bin_edges); + + relevant_blocks = []; + for i = 1:numel(blockStats) + block_indices = blockStats(i).indices; + if min(block_indices) >= custom_range(1) && max(block_indices) <= custom_range(2) + relevant_blocks = [relevant_blocks, blockStats(i)]; + end + end + + if ~isempty(relevant_blocks) + correct_cells = arrayfun(@(blk) blk.stimCounts.correct, relevant_blocks,'UniformOutput',false); + incorrect_cells = arrayfun(@(blk) blk.stimCounts.incorrect, relevant_blocks,'UniformOutput',false); + + counts_matrix_corr = cell2mat(correct_cells); + counts_matrix_incorr = cell2mat(incorrect_cells); + + mean_corr = mean(counts_matrix_corr, 1); + std_corr = std(counts_matrix_corr, 0, 1); + mean_incorr = mean(counts_matrix_incorr, 1); + std_incorr = std(counts_matrix_incorr, 0, 1); + + if sum(std_corr) > eps + fill(ax, [bin_centers, fliplr(bin_centers)], [mean_corr - std_corr, fliplr(mean_corr + std_corr)], ... + [0 0.65 0], 'FaceAlpha', 0.2, 'EdgeColor', 'none', 'DisplayName', 'Correct (Block STD)'); + end + + % --- Plot the fill area for INCORRECT trials, only if there is variance --- + if sum(std_incorr) > eps + fill(ax, [bin_centers, fliplr(bin_centers)], [mean_incorr - std_incorr, fliplr(mean_incorr + std_incorr)], ... + [0.9 0.2 0.1], 'FaceAlpha', 0.2, 'EdgeColor', 'none', 'DisplayName', 'Incorrect (Block STD)'); + end + end + + plot(ax, bin_centers, counts_custom.correct, '-o', 'Color', [0 0.65 0], 'LineWidth', 2, 'DisplayName', 'Correct (Custom)'); + plot(ax, bin_centers, counts_custom.incorrect, '-o', 'Color', [0.9 0.2 0.1], 'LineWidth', 2, 'DisplayName', 'Incorrect (Custom)'); + + xline(ax, config.true_mu, '--k', 'Boundary', 'LineWidth', 2,'HandleVisibility', 'off'); + hold(ax, 'off'); legend(ax, 'Location', 'northwest'); + title(ax, 'Choice Distribution (w/ Block STD)'); + xlabel(ax, 'Stimulus Value'); ylabel(ax, 'Trial Count'); + end + + %% CONTEXT PLOTTING HELPERS + function plotContextPsychometric(ax, psych_data, config, colors,context_names) + cla(ax, 'reset'); hold(ax, 'on'); + xGrid = linspace(config.stimuli_range(1), config.stimuli_range(2), 300)'; + + xline(ax, config.true_mu, '--k', 'LineWidth', 1.5, 'DisplayName', 'True Boundary'); + yline(ax, 0.5, ':', 'Color', [0.5 0.5 0.5], 'HandleVisibility', 'off'); + + contains_left_rule = false; + contains_right_rule = false; + + for i = 1:numel(psych_data) + if isempty(psych_data{i}), continue; end + + if contains(string(psych_data{i}.rule), 'Left', 'IgnoreCase', true) + contains_left_rule = true; + else + contains_right_rule = true; + end + + plot(ax, xGrid, psych_data{i}.y_pred, '-', 'Color', colors(i,:), 'LineWidth', 2, 'DisplayName', context_names{i}); + xline(ax, psych_data{i}.fitParams(1), '--', 'Color', colors(i,:), 'LineWidth', 1.5, 'HandleVisibility', 'off'); + end + + if contains_left_rule && ~contains_right_rule, ylabel(ax, 'P(Left)'); + elseif ~contains_left_rule && contains_right_rule, ylabel(ax, 'P(Right)'); + else, ylabel(ax, 'P(Choice)'); end + + grid(ax, 'on'); hold(ax, 'off'); + legend(ax, 'show', 'Location', 'southeast'); + title(ax, 'Contextual Psychometric Fits'); + xlabel(ax, 'Stimulus'); + end + + function plotContextHitRates(ax, hit_rate_data, hit_rate_std, colors,context_names) + cla(ax, 'reset'); hold(ax, 'on'); + + if isempty(hit_rate_data), return; end + + num_contexts = size(hit_rate_data, 1); + num_groups = size(hit_rate_data, 2); % Should be 3 for Overall, Left, Right + + b = bar(ax, hit_rate_data', 'grouped'); + + % Set colors for each context + for i = 1:num_contexts + b(i).FaceColor = colors(i,:); + end + + % Calculate the x-positions for error bars + % For grouped bars, we need to calculate the offset for each group + group_width = min(0.8, num_contexts/(num_contexts + 1.5)); + + for i = 1:num_contexts + % Calculate x-coordinates for this context across all groups + x_offset = (-(num_contexts-1)/2 + (i-1)) * group_width/num_contexts; + x_coords = (1:num_groups) + x_offset; + + % Plot error bars for this context + errorbar(ax, x_coords, hit_rate_data(i,:), hit_rate_std(i,:), ... + 'k', 'linestyle', 'none', 'CapSize', 4, 'LineWidth', 1); + end + + ax.XTick = 1:num_groups; + ax.XTickLabel = {'Overall', 'Left', 'Right'}; + ylabel(ax, 'Hit %'); + title(ax, 'Contextual Hit Rates (w/ Block STD)'); + ylim(ax, [0 105]); + grid(ax, 'on'); + + % legend_labels = arrayfun(@(x) sprintf('Context %d', x), 1:num_contexts, 'UniformOutput', false); + % legend_labels = context_names + % legend(ax, legend_labels, 'Location', 'northeastoutside'); + hold(ax, 'off'); + end + + function plotContextStimulusHistogram(ax, stim_hist_data, config, colors,context_names) + cla(ax, 'reset'); hold(ax, 'on'); + + left_edges = linspace(min(config.stimuli_range), config.true_mu, 6); + right_edges = linspace(config.true_mu, max(config.stimuli_range), 6); + bin_edges = unique([left_edges, right_edges]); + bin_centers = (bin_edges(1:end-1) + bin_edges(2:end)) / 2; + + for i = 1:numel(stim_hist_data) + if isempty(stim_hist_data{i}), continue; end + + if isfield(stim_hist_data{i}, 'mean_corr') + if sum(stim_hist_data{i}.std_corr) > eps + fill(ax, [bin_centers, fliplr(bin_centers)], [stim_hist_data{i}.mean_corr - stim_hist_data{i}.std_corr, fliplr(stim_hist_data{i}.mean_corr + stim_hist_data{i}.std_corr)], ... + colors(i,:), 'FaceAlpha', 0.15, 'EdgeColor', 'none'); + end + if sum(stim_hist_data{i}.std_incorr) > eps + fill(ax, [bin_centers, fliplr(bin_centers)], [stim_hist_data{i}.mean_incorr - stim_hist_data{i}.std_incorr, fliplr(stim_hist_data{i}.mean_incorr + stim_hist_data{i}.std_incorr)], ... + colors(i,:), 'FaceAlpha', 0.15, 'EdgeColor', 'none'); + end + end + + plot(ax, bin_centers, stim_hist_data{i}.correct, '-o', 'Color', colors(i,:), 'LineWidth', 2, 'DisplayName', sprintf('Correct C%d', i)); + plot(ax, bin_centers, stim_hist_data{i}.incorrect, ':x', 'Color', colors(i,:), 'LineWidth', 1.5, 'DisplayName', sprintf('Incorrect C%d', i)); + end + + xline(ax, config.true_mu, '--k', 'Boundary', 'LineWidth', 2); + hold(ax, 'off'); + % legend(ax, 'show', 'Location', 'northwest'); + title(ax, 'Contextual Choice Distributions'); + xlabel(ax, 'Stimulus Value'); + ylabel(ax, 'Trial Count'); + end + + %% GENERAL UTILITY FUNCTIONS + function dist_type = getDistributionType(data, indices, rule) + % Determines the distribution type based on the rule and the + % distributions for left and right sides within the given indices. + + dist_left_cell = unique(data.full_dist_left(indices)); + dist_right_cell = unique(data.full_dist_right(indices)); + + dist_left = dist_left_cell{1}; + dist_right = dist_right_cell{1}; + + hard_dists = {'exponential', 'half-normal', 'sinusoidal'}; + + if strcmp(dist_left, dist_right) + dist_type = dist_left; + return; + end + + is_left_hard = ismember(dist_left, hard_dists); + is_right_hard = ismember(dist_right, hard_dists); + + if contains(string(rule), 'Right', 'IgnoreCase', true) % High stimulus values correspond to Right + if is_right_hard && ~is_left_hard + dist_type = 'hard high'; + elseif ~is_right_hard && is_left_hard + dist_type = 'hard low'; + else + dist_type = 'mixed'; + end + else % High stimulus values correspond to Left + if is_left_hard && ~is_right_hard + dist_type = 'hard high'; + elseif ~is_left_hard && is_right_hard + dist_type = 'hard low'; + else + dist_type = 'mixed'; + end + end + end + + function [y_pred, fitParams, methodUsed, fitStatus] = realtimepsychometricFit(stim, resp, rangeStim, options) + % realtimepsychometricFit Robust real-time psychometric fitting and plotting. + % + % This function fits a 4-parameter logistic psychometric function. It includes + % internal checks for data quality and fitting stability. + % + % Inputs: + % stim - vector of stimulus values. + % response - binary response vector (0 or 1). + % rangeStim - 1x2 or 1x3 vector for the stimulus grid [min, max]. + % options - (Optional) struct with fields: + % .MinTrials - Min trials to attempt fit (default: 15). + + % .LapseUB - Upper bound for lapse rates (default: 0.1). + % .StdTol - Tolerance for stimulus std dev (default: 1e-6). + % .SlopeTol - Tolerance for slope parameter (default: 1e-5). + % + % Outputs: + % y_pred - Predicted y-values on a grid across rangeStim. + % fitParams - [mu, sigma, lapseL, lapseR] fitted parameters. + % methodUsed - String indicating the final fitting method used. + % fitStatus - String providing information on the fit quality/outcome. + + %% 1. Argument Handling & Pre-computation Guard Clauses + if nargin < 4, options = struct(); end + if ~isfield(options, 'MinTrials'), options.MinTrials = 15; end + if ~isfield(options, 'LapseUB'), options.LapseUB = 0.1; end + if ~isfield(options, 'StdTol'), options.StdTol = 1e-6; end + if ~isfield(options, 'SlopeTol'), options.SlopeTol = 1e-5; end + fitParams = [nan,nan,nan,nan]; y_pred = []; fitStatus = 'Success'; % Assume success initially + + % Ensure both inputs are vectors and have the same number of elements + + if ~isvector(stim) || ~isvector(resp) + methodUsed = 'Fit Canceled'; + fitStatus = 'Inputs `stim` and `resp` must be vectors.'; + return; + end + if numel(stim) ~= numel(resp) + methodUsed = 'Fit Canceled'; + fitStatus = 'Inputs `stim` and `resp` must have the same number of elements.'; + return; + end + + % Enforce column vector orientation for consistency with fitting functions. + % The (:) operator robustly reshapes any vector into a column vector. + stim = stim(:); + resp = resp(:); + + % GUARD: Check for minimum number of trials + if numel(stim) < options.MinTrials, methodUsed = 'Fit Canceled'; fitStatus = sprintf('Insufficient trials (n=%d, min=%d)', numel(stim), options.MinTrials); return; end + % GUARD: Check for stimulus variance + if std(stim) < options.StdTol, methodUsed = 'Fit Canceled'; fitStatus = 'Insufficient stimulus variance'; return; end + + %% 2. Initial Fit (Ridge) + stim_std = (stim - mean(stim)) / std(stim); methodUsed = 'ridge'; + try + % --- Ridge logistic fit for mu and sigma --- + [B, FitInfo] = lassoglm(stim_std, resp, 'binomial', 'Alpha', 1e-6, 'Lambda', 0.1); + b0 = FitInfo.Intercept; b1 = B(1); + % GUARD: Check for near-zero or excessively large slope from ridge fit + if abs(b1) < options.SlopeTol, throw(MException('MyFit:ZeroSlope', 'Initial ridge fit found no slope.')); end + if abs(b1) > 10 % Check for quasi-perfect separation + throw(MException('MyFit:SteepSlope', 'Initial ridge fit is too steep.')); + end + mu = -b0 / b1 * std(stim) + mean(stim); sigma = std(stim) / b1; + % Residual lapse estimate for initialization + predTrain = 1 ./ (1 + exp(-(b0 + b1 * stim_std))); + lapseEstimate = mean(abs(predTrain - resp)); + lapseL = min(max(lapseEstimate * 1.2, 0), options.LapseUB); lapseR = lapseL; + catch ME + % --- Robust fallback if ridge fit fails for any reason --- + methodUsed = 'robust'; fitStatus = sprintf('Switched to robust fit. Reason: %s', ME.message); + try + brob = robustfit(stim, resp, 'logit'); + % GUARD: Check for near-zero slope from robust fit + if abs(brob(2)) < options.SlopeTol, methodUsed = 'Fit Failed'; fitStatus = 'Could not find a slope.'; return; end + mu = -brob(1) / brob(2); sigma = 1 / brob(2); + lapseL = 0.02; lapseR = 0.02; % Use fixed lapse guesses for robust fallback + catch, methodUsed = 'Fit Failed'; fitStatus = 'Robustfit also failed.'; return; + end + end + %% 3. Final Nonlinear Fit (lsqcurvefit) + psychometricFun = @(params, x) params(3) + (1 - params(3) - params(4)) ./ (1 + exp(-(x - params(1)) / params(2))); + % Use a slightly wider range for bounds to avoid railing issues + stim_min = min(rangeStim); stim_max = max(rangeStim); range_width = stim_max - stim_min; + init = [mu, sigma, lapseL, lapseR]; + lb = [stim_min - 0.1*range_width, 0.1, 0, 0]; + ub = [stim_max + 0.1*range_width, 15, options.LapseUB, options.LapseUB]; + % Constrain initial guess to be within bounds + init(1) = max(min(init(1), ub(1)), lb(1)); init(2) = max(min(init(2), ub(2)), lb(2)); + optimOpts = optimset('Display', 'off'); + fitParams = lsqcurvefit(psychometricFun, init, stim, resp, lb, ub, optimOpts); + + %% 4. Post-Fit Sanity Checks & Prediction + % CHECK: Did the fit "rail" against the stimulus range bounds? + bound_tolerance = 0.01 * range_width; + % CHECK: Did the lapse rates hit their upper bound? + if (fitParams(1) <= lb(1) + bound_tolerance) || (fitParams(1) >= ub(1) - bound_tolerance), fitStatus = 'Warning: Threshold at edge of range.'; end + if (fitParams(3) >= options.LapseUB*0.99) || (fitParams(4) >= options.LapseUB*0.99), fitStatus = 'Warning: Lapse rate at upper bound.'; end + xGrid = linspace(stim_min, stim_max, 300)'; + y_pred = psychometricFun(fitParams, xGrid); + end + + function cmap = createTemporalColormap(n_colors) + if n_colors == 0, cmap = []; return; end + if n_colors == 1, cmap = [0.8 0 0]; return; end % A single dark red + % Create a high-contrast colormap for a few items + if n_colors <= 5 + cmap = [0.8 0.1 0.1; % Red + 0.1 0.5 0.8; % Blue + 0.1 0.7 0.2; % Green + 0.7 0.2 0.7; % Purple + 0.9 0.6 0.0]; % Orange + cmap = cmap(1:n_colors, :); + else % Fallback for more colors + h = linspace(0.6, 0, n_colors)'; + s = linspace(0.8, 1, n_colors)'; + v = linspace(0.7, 1, n_colors)'; + cmap = hsv2rgb([h, s, v]); + end + end + +end diff --git a/Protocols/@ArpitSoundCatContinuous/private/createTemporalColormap.m b/Protocols/@ArpitSoundCatContinuous/private/createTemporalColormap.m new file mode 100644 index 00000000..163f89f9 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/createTemporalColormap.m @@ -0,0 +1,24 @@ +function cmap = createTemporalColormap(n_colors) + % createTemporalColormap Generates a colormap for sequential plotting. + % - Newest colors are bright and "hot" (e.g., yellow/red). + % - Oldest colors are darker and "cool" (e.g., deep blue/purple). + % - Hue, saturation, and brightness are all varied for maximum distinction. + + % Define the path in HSV color space + hue_start = 0.6; % Start at blue + hue_end = 0; % End at red + + sat_start = 0.8; % Start slightly desaturated + sat_end = 1.0; % End fully saturated + + val_start = 0.7; % Start dark + val_end = 1.0; % End at full brightness + + % Create linearly spaced vectors for Hue, Saturation, and Value + h = linspace(hue_start, hue_end, n_colors)'; + s = linspace(sat_start, sat_end, n_colors)'; + v = linspace(val_start, val_end, n_colors)'; + + % Convert the HSV values to an RGB colormap + cmap = hsv2rgb([h, s, v]); +end \ No newline at end of file diff --git a/Protocols/@ArpitSoundCatContinuous/private/create_ArpitCentrePokeTraining_SettingFile.m b/Protocols/@ArpitSoundCatContinuous/private/create_ArpitCentrePokeTraining_SettingFile.m new file mode 100644 index 00000000..05a556d4 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/create_ArpitCentrePokeTraining_SettingFile.m @@ -0,0 +1,91 @@ +function create_ArpitCentrePokeTraining_SettingFile() + +templateDir = 'C:/ratter/'; + +templatePath = [templateDir '/Protocols/@ArpitCentrePokeTraining/private/pipeline_ArpitCentrePokeTraining_arpit_AR05_250516.m']; +setting_filename = 'settings_@ArpitCentrePokeTraining_arpit_AR05_250516a.mat'; +outputRootDir = [templateDir '/SoloData/Settings/']; + +experimenter = input('Enter experimenter name: ', 's'); +ratname = input('Enter rat name: ', 's'); + +% Get today's date +newDate = datestr(now, 'yymmdd'); + +% Read the template file +if ~isfile(templatePath) + error('Template file not found: %s', templatePath); +end +code = fileread(templatePath); +lines = splitlines(code); + +% Find and update the function definition line +newFuncName = ''; +for i = 1:length(lines) + line = strtrim(lines{i}); + if startsWith(line, 'function') && contains(line, 'pipeline_') + % Extract argument list + pattern = 'function\s+varargout\s*=\s*pipeline_.*?\((.*?)\)'; + tokens = regexp(line, pattern, 'tokens'); + if ~isempty(tokens) + args = tokens{1}{1}; + newFuncName = sprintf('pipeline_ArpitCentrePokeTraining_%s_%s_%s', ... + experimenter, ratname, newDate); + lines{i} = sprintf('function varargout = %s(%s)', newFuncName, args); + break; + end + end +end + +if isempty(newFuncName) + error('Could not find valid function definition line in template.'); +end + +% Create output directory if needed +outputDir = fullfile(outputRootDir, experimenter, ratname); +if ~exist(outputDir, 'dir') + mkdir(outputDir); +end + +% Build full new script path +newScriptPath = fullfile(outputDir, [newFuncName, '.m']); + +% Save modified content +newCode = strjoin(lines, newline); +fid = fopen(newScriptPath, 'w'); +if fid == -1 + error('Failed to open new file for writing: %s', newScriptPath); +end +fwrite(fid, newCode); +fclose(fid); + +fprintf('New Session Definition file saved to: %s\n', newScriptPath); + +% Load and modify .mat file +[templateFolder, ~, ~] = fileparts(templatePath); +matTemplatePath = fullfile(templateFolder, setting_filename); + +if ~isfile(matTemplatePath) + warning('No setting_file found in template directory: %s', matTemplatePath); + newMatPath = ''; + return; +end + +load(matTemplatePath,'saved','saved_autoset','fig_position'); % Load struct + +saved.SavingSection_experimenter = experimenter; +saved.SavingSection_ratname = ratname; +saved.SessionDefinition_textTrainingStageFile = newScriptPath; +saved.ArpitCentrePokeTraining_prot_title = sprintf('ArpitCentrePokeTraining: %s, %s',experimenter,ratname); +saved.PokesPlotSection_textHeader = sprintf('PokesPlotSection(%s, %s',experimenter,ratname); +saved.SessionDefinition_textHeader = sprintf('SESSION AUTOMATOR WINDOW: %s, %s',experimenter,ratname); + +new_MATfile = sprintf('settings_@ArpitCentrePokeTraining__%s_%s_%sa.mat', ... + experimenter, ratname, newDate); + +newMatPath = fullfile(outputDir, new_MATfile); + +save(newMatPath,'saved','saved_autoset','fig_position'); +fprintf('New setting file saved to: %s\n', newMatPath); + +end diff --git a/Protocols/@ArpitSoundCatContinuous/private/filt.m b/Protocols/@ArpitSoundCatContinuous/private/filt.m new file mode 100644 index 00000000..e4c572ff --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/filt.m @@ -0,0 +1,60 @@ +function filtsignal=filt(signal,fcut,Fs,filter_type) +a=2; % wp/ws used in butterworth method and LS linear FIR method +N=200; % filter order used in lowpass FIR method +rp=3; % passband ripple in dB used in butterworth method +rs=60; % stopband attenuation in dB used in butterworth method +beta=0.1102*(rs-8.8); %used in Kaiser window to obtain sidelobe attenuation of rs dB +if strcmp(filter_type, 'GAUS') || strcmp(filter_type, 'MOVAVRG') +window = fix(Fs/fcut); % window size used in Gaussian and moving average methods +end +wp=2*fcut/Fs; % normalized passband corner frequency wp, the cutoff frequency +ws=a*wp; % normalized stopband corner frequency + + +switch filter_type + case 'BUTTER' %Butterworth IIR filter + if length(wp)>1 + ws(1)=2*(fcut(1)/2)/Fs; + ws(2)=2*(fcut(2)+fcut(1)/2)/Fs; + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'bandpass'); + else + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'low'); + end + filtsignal=filter(b,a,signal);%conventional filtering + case 'LPFIR' %Lowpass FIR filter + d=fdesign.lowpass('N,Fc',N,fcut,Fs); % Fc is the 6-dB down point, N is the filter order(N+1 filter coefficients) + Hd = design(d); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'FIRLS' %Least square linear-phase FIR filter design + b=firls(255,[0 2*fcut/Fs a*2*fcut/Fs 1],[1 1 0 0]); + filtsignal=filter(b,1,signal); %conventional filtering + case 'EQUIRIP' %Eqiripple FIR filter + d=fdesign.lowpass('Fp,Fst,Ap,Ast',wp,ws,rp,rs); + Hd=design(d,'equiripple'); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'MOVAVRG' % Moving average FIR filtering, Rectangular window + h = ones(window,1)/window; + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'HAMMING' % Hamming-window based FIR filtering + b = fir1(150,wp); + filtsignal = filter(b, 1, signal); + filtsignal = filter(h, 1, signal); + case 'GAUS' % Gaussian-window FIR filtering + h = normpdf(1:window, 0, fix(window/2)); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'GAUS1' % Gaussian-window FIR filtering + b = fir1(window-1,wp,gausswin(window,2)/window); + filtsignal = filter(b, 1, signal); + case 'KAISER' %Kaiser-window FIR filtering + h=kaiser(window,beta); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + + otherwise + sprintf('filter_type is wrong!! havaset kojast!!') +end + diff --git a/Protocols/@ArpitSoundCatContinuous/private/noisestim.m b/Protocols/@ArpitSoundCatContinuous/private/noisestim.m new file mode 100644 index 00000000..7f074ab0 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/noisestim.m @@ -0,0 +1,47 @@ +function [base,target,normbase,normtarget]=noisestim(sigma_1,sigma_2,T,fcut,Fs,filter_type) + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +replace=1; +L=floor(T*Fs); % Length of signal +t=L*linspace(0,1,L)/Fs; % time in miliseconds +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1*randn(Fs,1); +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +pos2 =sigma_2*randn(Fs,1); +% pos2(pos2>outband)=[]; +% pos2(pos2<-outband)=[]; +base = randsample(pos1,L,replace); +target = randsample(pos2,L,replace); +%%%% Filter the original position values %%%%%% +filtbase=filt(base,fcut,Fs,filter_type); +filttarget=filt(target,fcut,Fs,filter_type); +normbase=filtbase./(max(abs(filtbase))); +normtarget=filttarget./(max(abs(filttarget))); +end + +%%%%%% plot the row and filtered position values %%%%%%%%% +% subplot(2,2,1) +% plot(t,base,'r'); +% ylabel('base') +% xlabel('Time (ms)') +% subplot(2,2,2) +% plot(t,target,'g'); +% ylabel('target') +% xlabel('Time (ms)') +% subplot(2,2,3) +% plot(t,filtbase) +% ylabel('filtbase') +% xlabel('Time (ms)') +% subplot(2,2,4) +% plot(t,filttarget) +% ylabel('filttarget') +% xlabel('Time (ms)') diff --git a/Protocols/@ArpitSoundCatContinuous/private/realtimepsychometricFit.m b/Protocols/@ArpitSoundCatContinuous/private/realtimepsychometricFit.m new file mode 100644 index 00000000..b926d233 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/realtimepsychometricFit.m @@ -0,0 +1,155 @@ +function [y_pred, fitParams, methodUsed, fitStatus] = realtimepsychometricFit(stim, resp, rangeStim, options) +% realtimepsychometricFit Robust real-time psychometric fitting and plotting. +% +% This function fits a 4-parameter logistic psychometric function. It includes +% internal checks for data quality and fitting stability. +% +% Inputs: +% stim - vector of stimulus values. +% response - binary response vector (0 or 1). +% rangeStim - 1x2 or 1x3 vector for the stimulus grid [min, max]. +% options - (Optional) struct with fields: +% .MinTrials - Min trials to attempt fit (default: 15). +% .LapseUB - Upper bound for lapse rates (default: 0.1). +% .StdTol - Tolerance for stimulus std dev (default: 1e-6). +% .SlopeTol - Tolerance for slope parameter (default: 1e-5). +% +% Outputs: +% y_pred - Predicted y-values on a grid across rangeStim. +% fitParams - [mu, sigma, lapseL, lapseR] fitted parameters. +% methodUsed - String indicating the final fitting method used. +% fitStatus - String providing information on the fit quality/outcome. + +%% 1. Argument Handling & Pre-computation Guard Clauses +if nargin < 4, options = struct(); end +if ~isfield(options, 'MinTrials'), options.MinTrials = 30; end +if ~isfield(options, 'LapseUB'), options.LapseUB = 0.1; end +if ~isfield(options, 'StdTol'), options.StdTol = 1e-6; end +if ~isfield(options, 'SlopeTol'), options.SlopeTol = 1e-5; end + +fitParams = [nan,nan,nan,nan]; +y_pred = []; +fitStatus = 'Success'; % Assume success initially +% Ensure both inputs are vectors and have the same number of elements +if ~isvector(stim) || ~isvector(resp) + methodUsed = 'Fit Canceled'; + fitStatus = 'Inputs `stim` and `resp` must be vectors.'; + return; +end +if numel(stim) ~= numel(resp) + methodUsed = 'Fit Canceled'; + fitStatus = 'Inputs `stim` and `resp` must have the same number of elements.'; + return; +end + +% Enforce column vector orientation for consistency with fitting functions. +% The (:) operator robustly reshapes any vector into a column vector. +stim = stim(:); +resp = resp(:); + +% GUARD: Check for minimum number of trials +if numel(stim) < options.MinTrials + methodUsed = 'Fit Canceled'; + fitStatus = sprintf('Insufficient trials (n=%d, min=%d)', numel(stim), options.MinTrials); + return; +end + +% GUARD: Check for stimulus variance +if std(stim) < options.StdTol + methodUsed = 'Fit Canceled'; + fitStatus = 'Insufficient stimulus variance'; + return; +end + +%% 2. Initial Fit (Ridge) +stim_std = (stim - mean(stim)) / std(stim); +methodUsed = 'ridge'; + +try + % --- Ridge logistic fit for mu and sigma --- + [B, FitInfo] = lassoglm(stim_std, resp, 'binomial', 'Alpha', 1e-6, 'Lambda', 0.1); % Alpha near 0 for ridge + b0 = FitInfo.Intercept; + b1 = B(1); + + % GUARD: Check for near-zero or excessively large slope from ridge fit + if abs(b1) < options.SlopeTol + throw(MException('MyFit:ZeroSlope', 'Initial ridge fit found no slope.')); + end + if abs(b1) > 10 % Check for quasi-perfect separation + throw(MException('MyFit:SteepSlope', 'Initial ridge fit is too steep.')); + end + + mu = -b0 / b1 * std(stim) + mean(stim); + sigma = std(stim) / b1; + + % Residual lapse estimate for initialization + predTrain = 1 ./ (1 + exp(-(b0 + b1 * stim_std))); + lapseEstimate = mean(abs(predTrain - resp)); + lapseL = min(max(lapseEstimate * 1.2, 0), options.LapseUB); + lapseR = lapseL; + +catch ME + % --- Robust fallback if ridge fit fails for any reason --- + methodUsed = 'robust'; + fitStatus = sprintf('Switched to robust fit. Reason: %s', ME.message); + + try + brob = robustfit(stim, resp, 'logit'); + + % GUARD: Check for near-zero slope from robust fit + if abs(brob(2)) < options.SlopeTol + methodUsed = 'Fit Failed'; + fitStatus = 'Could not find a slope with either method.'; + return; + end + + mu = -brob(1) / brob(2); + sigma = 1 / brob(2); + lapseL = 0.02; % Use fixed lapse guesses for robust fallback + lapseR = 0.02; + catch + methodUsed = 'Fit Failed'; + fitStatus = 'Robustfit also failed to converge.'; + return; + end +end + +%% 3. Final Nonlinear Fit (lsqcurvefit) +psychometricFun = @(params, x) params(3) + (1 - params(3) - params(4)) ./ ... + (1 + exp(-(x - params(1)) / params(2))); + +% Use a slightly wider range for bounds to avoid railing issues +stim_min = min(rangeStim); +stim_max = max(rangeStim); +range_width = stim_max - stim_min; + +init = [mu, sigma, lapseL, lapseR]; +lb = [stim_min - 0.1*range_width, 0.1, 0, 0]; +ub = [stim_max + 0.1*range_width, 15, options.LapseUB, options.LapseUB]; + +% Constrain initial guess to be within bounds +init(1) = max(min(init(1), ub(1)), lb(1)); +init(2) = max(min(init(2), ub(2)), lb(2)); + +optimOpts = optimset('Display', 'off'); +fitParams = lsqcurvefit(psychometricFun, init, stim, resp, lb, ub, optimOpts); + +%% 4. Post-Fit Sanity Checks & Prediction +% CHECK: Did the fit "rail" against the stimulus range bounds? +bound_tolerance = 0.01 * range_width; +if (fitParams(1) <= lb(1) + bound_tolerance) || (fitParams(1) >= ub(1) - bound_tolerance) + fitStatus = 'Warning: Threshold is at the edge of the stimulus range.'; + warning('realtimepsychometricFit:%s', fitStatus); +end + +% CHECK: Did the lapse rates hit their upper bound? +if (fitParams(3) >= options.LapseUB*0.99) || (fitParams(4) >= options.LapseUB*0.99) + fitStatus = 'Warning: Lapse rate may be underestimated (at upper bound).'; + warning('realtimepsychometricFit:%s', fitStatus); +end + +% Predict on a fine grid +xGrid = linspace(stim_min, stim_max, 300)'; +y_pred = psychometricFun(fitParams, xGrid); + +end \ No newline at end of file diff --git a/Protocols/@ArpitSoundCatContinuous/private/singlenoise.m b/Protocols/@ArpitSoundCatContinuous/private/singlenoise.m new file mode 100644 index 00000000..0cf201f3 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/private/singlenoise.m @@ -0,0 +1,32 @@ +function [normbase]=singlenoise(sigma_1,T,fcut,Fs,filter_type) +%GetSoloFunctionArgs(obj); + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +sigma_1=1; +%T=10000; +%fcut=[3000 4000]; +%Fs=200000; +filter_type='BUTTER'; +outband=60; +replace=1; +L=floor(T*Fs); % Length of signal +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1*randn(Fs,1); +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +base = randsample(pos1,L,replace); +%%%% Filter the original position values %%%%%% +%filtbase=filt(base,fcut,Fs,filter_type); +hf = design(fdesign.bandpass('N,F3dB1,F3dB2',10,fcut(1),fcut(2),Fs)); +filtbase=filter(hf,base); +normbase=filtbase./(max(abs(filtbase))); +end + diff --git a/Protocols/@ArpitSoundCatContinuous/state_colors.m b/Protocols/@ArpitSoundCatContinuous/state_colors.m new file mode 100644 index 00000000..1e3b1737 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/state_colors.m @@ -0,0 +1,14 @@ +function SC = state_colors(obj) %#ok + +SC = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'settling_in_state', [0.63 1 0.94], ... + 'legal_poke_start_state', [0.63 1 0.94]*0.8, ... + 'legal_poke_end_state', [1 0.79 0.63], ... + 'soft_cp', [0.3 0.9 0], ... + 'side_led_wait_RewardCollection', [0.53 0.78 1.00],... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); diff --git a/Protocols/@ArpitSoundCatContinuous/wave_colors.m b/Protocols/@ArpitSoundCatContinuous/wave_colors.m new file mode 100644 index 00000000..0d5a7339 --- /dev/null +++ b/Protocols/@ArpitSoundCatContinuous/wave_colors.m @@ -0,0 +1,11 @@ +function WC = wave_colors(obj) %#ok + +WC = struct(... + 'stim_wave', [1 1 1 ], ... + 'prestim_wave', [1 1 1 ], ... + 'stimulator_wave', [1 1 0 ], ... + 'stimulator_wave1', [1 1 0 ], ... + 'stimulator_wave2', [1 1 0 ], ... + 'stimulator_wave3', [1 1 0 ], ... + 'stimulator_wave4', [1 1 0 ], ... + 'stimulator_wave5', [1 1 0 ]); \ No newline at end of file diff --git a/Protocols/@AthenaDelayComp/AthenaDelayComp.m b/Protocols/@AthenaDelayComp/AthenaDelayComp.m index 42523992..148b64fb 100644 --- a/Protocols/@AthenaDelayComp/AthenaDelayComp.m +++ b/Protocols/@AthenaDelayComp/AthenaDelayComp.m @@ -47,8 +47,21 @@ set(value(myfig), 'Name', name, 'Tag', name, ... 'closerequestfcn', 'dispatcher(''close_protocol'')', 'MenuBar', 'none'); % At this point we have one SoloParamHandle, myfig + % Let's put the figure where we want it and give it a reasonable size: - set(value(myfig), 'Position', [485 144 850 680]); + original_width = 850; + original_height = 680; + + % Get screen size + scrsz = get(0,'ScreenSize'); + + % Center the figure while maintaining original size + center_x = (scrsz(3) - original_width) / 2; + center_y = (scrsz(4) - original_height) / 2; + + position_vector = [center_x center_y original_width original_height]; + + set(value(myfig), 'Position', position_vector); SoloParamHandle(obj, 'nsessions_healthy_number_of_pokes', 'value', 0, 'save_with_settings', 1); SoloParamHandle(obj, 'post_DelComp_protocol', 'value', '', 'save_with_settings', 1); diff --git a/Protocols/@AthenaDelayComp/StimulatorSection.m b/Protocols/@AthenaDelayComp/StimulatorSection.m index 73f43b29..972a4bb4 100644 --- a/Protocols/@AthenaDelayComp/StimulatorSection.m +++ b/Protocols/@AthenaDelayComp/StimulatorSection.m @@ -49,6 +49,8 @@ [dionums order] = sort(dionums); dionames2 = cell(0); for i = 1:length(dionums); if ~isnan(dionums(i)); dionames2{end+1} = dionames{order(i)}; end; end %#ok + dionames2 = cell(0); + dionames2{1} = 'opto'; MenuParam(obj,'StimLine',dionames2,1,x,y,'labelfraction',0.30); next_row(y); @@ -57,7 +59,7 @@ states = fieldnames(SC); waves = fieldnames(WC); states(2:end+1) = states; - states{1} = 'none'; + states{1} = 'cp'; states(end+1:end+length(waves)) = waves; MenuParam(obj,'StimState',states,1,x,y,'labelfraction',0.30); next_row(y); @@ -68,14 +70,14 @@ NumeditParam(obj,'StartDelay', 0,x,y,'position',[x y 100 20],'labelfraction',0.60); NumeditParam(obj,'StimFreq', 20,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); NumeditParam(obj,'PulseWidth',15,x,y,'position',[x y 100 20],'labelfraction',0.60); - NumeditParam(obj,'NumPulses', 10,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); + NumeditParam(obj,'NumPulses', 1,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); DispParam(obj,'SD',0 ,x,y,'position',[x y 50 20],'labelfraction',0.4); DispParam(obj,'SF',20,x,y,'position',[x+50 y 50 20],'labelfraction',0.4); DispParam(obj,'PW',15,x,y,'position',[x+100 y 50 20],'labelfraction',0.4); DispParam(obj,'NP',1,x,y,'position',[x+150 y 50 20],'labelfraction',0.4); next_row(y); - NumeditParam(obj,'StimProb', 0,x,y,'position',[x y 100 20],'labelfraction',0.65); + NumeditParam(obj,'StimProb', 1,x,y,'position',[x y 100 20],'labelfraction',0.65); ToggleParam( obj,'ShuffleValues',0,x,y,'position',[x+100 y 100 20],'OnString','Shuffle','OffString','Lock'); next_row(y); SoloParamHandle(obj, 'stimulator_history', 'value', []); @@ -159,6 +161,8 @@ disp('StimState value greater than list of possible stim states'); else StimState.value = ss; + disp('test ss') + value(ss) end if sl > length(psl) @@ -192,7 +196,12 @@ for i = 1:length(sl) stimline = bSettings('get','DIOLINES',psl{sl(i)}); - + + disp('stimlinevalue') + psl{sl(i)} + disp('stimlinevalue2') + stimline + sma = add_scheduled_wave(sma,... 'name', ['stimulator_wave',num2str(i)],... 'preamble', (1/sf)-(pw/1000),... %%%% Remember: change it such that if this is negative makes it 0 diff --git a/Protocols/@AthenaDelayComp/StimulusSection.m b/Protocols/@AthenaDelayComp/StimulusSection.m index fc021aaa..f6d94551 100644 --- a/Protocols/@AthenaDelayComp/StimulusSection.m +++ b/Protocols/@AthenaDelayComp/StimulusSection.m @@ -14,31 +14,71 @@ if length(varargin) < 2 error('Need at least two arguments, x and y position, to initialize %s', mfilename); end - x = varargin{1}; y = varargin{2}; - - ToggleParam(obj, 'StimulusShow', 0, x, y, 'OnString', 'Stimuli', ... - 'OffString', 'Stimuli', 'TooltipString', 'Show/Hide Stimulus panel'); - set_callback(StimulusShow, {mfilename, 'show_hide'}); %#ok (Defined just above) - next_row(y); - - SoloParamHandle(obj, 'myfig', 'value', figure('closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... - 'Name', mfilename), 'saveable', 0); + % x = varargin{1}; y = varargin{2}; + + % creating pairs and performance figure window with axes + SoloParamHandle( ... + obj, 'pairs_plot_figure', ... + 'value', figure( ... + 'CloseRequestFcn', {@hide, obj}, ... + 'MenuBar', 'none', ... + 'Name', 'Pairs and performance'), ... + 'saveable', 0); screen_size = get(0, 'ScreenSize'); - set(value(myfig),'Position',[1 screen_size(4)-740, 1000 1000]); % put fig at top right - set(double(gcf), 'Visible', 'off'); - x=10;y=10; - - SoloParamHandle(obj, 'ax', 'saveable', 0, ... - 'value', axes('Position', [0.01 0.5 0.45 0.45])); - ylabel('log_e \sigma_2','FontSize',16,'FontName','Cambria Math'); - set(value(ax),'Fontsize',15) - xlabel('log_e \sigma_1','FontSize',16,'FontName','Cambria Math') - + set(double(value(pairs_plot_figure)), ... + 'Position',[ ... + (screen_size(3)-800)/2 ... + (screen_size(4)-600)/2 800 600]); + + SoloParamHandle( ... + obj, 'ax2', ... + 'saveable', 0, ... + 'value', axes('Position',[0.05 0.3 0.45 0.45])); + ylabel('log_e \sigma_2'); + xlabel('log_e \sigma_1'); + axis square; + + SoloParamHandle( ... + obj, 'axperf2', ... + 'saveable', 0, ... + 'value', axes('Position',[0.55 0.3 0.45 0.45])); + ylabel('log_e \sigma_2'); + xlabel('log_e \sigma_1'); + axis square; + + + + % creating StimulusSection figure window for GUI elements + SoloParamHandle( ... + obj, 'myfig', ... + 'value', figure( ... + 'CloseRequestFcn', {@hide, obj}, ... + 'MenuBar', 'none', ... + 'Name', mfilename), ... + 'saveable', 0); + screen_size = get(0, 'ScreenSize'); + set(double(value(myfig)), ... + 'Position',[ ... + (screen_size(3)-1100)/2 ... + (screen_size(4)-300)/2 1100 300] ... + ); + + % adding plot + SoloParamHandle( ... + obj, 'ax', ... + 'saveable', 0, ... + 'value', axes('Position',[0.05 0.3 0.45 0.45])); + ylabel('log_e \sigma_2'); + xlabel('log_e \sigma_1'); + axis square; + + % adding performance plot SoloParamHandle(obj, 'axperf', 'saveable', 0, ... - 'value', axes('Position', [0.5 0.5 0.45 0.45])); - ylabel('log_e \sigma_2','FontSize',16,'FontName','Cambria Math'); - set(value(axperf),'Fontsize',15) - xlabel('log_e \sigma_1','FontSize',16,'FontName','Cambria Math') + 'value', axes('Position',[0.55 0.3 0.45 0.45])); + ylabel('log_e \sigma_2'); + xlabel('log_e \sigma_1'); + axis square; + SoundManagerSection(obj, 'declare_new_sound', 'StimAUD1') SoundManagerSection(obj, 'declare_new_sound', 'StimAUD2') @@ -50,26 +90,47 @@ SoloParamHandle(obj, 'h1', 'value', []); SoloParamHandle(obj, 'thisclass', 'value', []); - y=5; - MenuParam(obj, 'StimulusType', {'library', 'new'}, ... - 'new', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nnew means at each trial, a new noise pattern will be generated,\n' ... - '"library" means for each trial stimulus is loaded from a library with limited number of noise patterns'])); next_row(y, 1.3) + x=5; y=5; + % Create toggle parameter for stimulus visibility + ToggleParam( ... + obj, ... + 'StimulusShow', 0, ... + x, y, ... + 'OnString', 'Stimuli', ... + 'OffString', 'Stimuli', ... + 'TooltipString', 'Show/Hide Stimulus panel'); + set_callback(StimulusShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + + MenuParam( ... + obj, 'StimulusType', ... + {'library', 'new'}, 'new', ... + x, y, ... + 'labelfraction', 0.35, ... + 'TooltipString', ... + sprintf(['\n"new" means at each trial, a new noise pattern will be generated,\n' ... + '"library" means for each trial stimulus is loaded from a library with limited number of noise patterns']) ... + ); + next_row(y, 1.3) set_callback(StimulusType, {mfilename, 'StimulusType'}); - NumeditParam(obj,'nPatt',50,x,y,'label','Num Nois Patt','TooltipString','Number of Noise Patters for the library'); + NumeditParam(obj,'nPatt',50,x,y,'label','Num Nois Patt','TooltipString','Number of Noise Patters for the library'); next_row(y); next_row(y); - PushbuttonParam(obj, 'refresh_pairs', x,y , 'TooltipString', 'Instantiates the pairs given the new set of parameters'); + PushbuttonParam(obj, 'refresh_pairs', x,y , ... + 'TooltipString', 'Instantiates the pairs given the new set of parameters'); set_callback(refresh_pairs, {mfilename, 'plot_pairs'}); next_row(y); - PushbuttonParam(obj, 'plot_performance', x,y , 'TooltipString', 'Plots the class design with mean performance for each class'); + PushbuttonParam(obj, 'plot_performance', x,y , ... + 'TooltipString', 'Plots the class design with mean performance for each class'); set_callback(plot_performance, {mfilename, 'plot_perf'}); next_row(y); next_row(y); MenuParam(obj, 'Rule', {'S2>S1 Left','S2>S1 Right'}, ... - 'S2>S1 Left', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nThis bottom determines the rule\n', ... + 'S2>S1 Left', x, y, 'labelfraction', 0.35, ... + 'TooltipString', sprintf(['\nThis bottom determines the rule\n', ... '\n''S2>S1 Left'' means if Aud2 > Aud1 then reward will be delivered from the left water spout and if Aud2 < Aud1 then water comes form right\n',... '\n''S2>S1 Right'' means if Aud2 < Aud1 then reward will be delivered from the left water spout and if Aud2 > Aud1 then water comes from right\n'])); next_row(y, 1) @@ -82,52 +143,92 @@ next_column(x); y=5; MenuParam(obj, 'filter_type', {'GAUS','LPFIR', 'FIRLS','BUTTER','MOVAVRG','KAISER','EQUIRIP','HAMMING'}, ... - 'GAUS', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent filters. ''LPFIR'': lowpass FIR ''FIRLS'': Least square linear-phase FIR filter design\n', ... + 'GAUS', x, y, 'labelfraction', 0.35, ... + 'TooltipString', sprintf(['\nDifferent filters. ''LPFIR'': lowpass FIR ''FIRLS'': Least square linear-phase FIR filter design\n', ... '\n''BUTTER'': IIR Butterworth lowpass filter ''GAUS'': Gaussian filter (window)\n', ... '\n''MOVAVRG'': Moving average FIR filter ''KAISER'': Kaiser-window FIR filtering\n', ... '\n''EQUIRIP'':Eqiripple FIR filter ''HAMMING'': Hamming-window based FIR'])); next_row(y, 1) - DispParam(obj, 'A1_sigma', 0.01, x,y,'label','A1_sigma','TooltipString','Sigma value for the first stimulus'); - next_row(y); - DispParam(obj, 'A2_sigma', 0.01, x,y,'label','A2_sigma','TooltipString','Sigma value for the first stimulus'); - next_row(y); - NumeditParam(obj,'fcut',110,x,y,'label','fcut','TooltipString','Cut off frequency on the original white noise'); + + DispParam(obj, 'A1_sigma', 0.01, x,y, ... + 'label','A1_sigma', ... + 'TooltipString','Sigma value for the first stimulus'); next_row(y); - NumeditParam(obj,'lfreq',2000,x,y,'label','Modulator_LowFreq','TooltipString','Lower bound for the frequency modulator'); - next_row(y); - NumeditParam(obj,'hfreq',20000,x,y,'label','Modulator_HighFreq','TooltipString','Upper bound for the frequency modulator'); + + DispParam(obj, 'A2_sigma', 0.01, x,y, ... + 'label','A2_sigma', ... + 'TooltipString','Sigma value for the first stimulus'); + next_row(y); + + NumeditParam(obj,'fcut',110,x,y, ... + 'label','fcut', ... + 'TooltipString','Cut off frequency on the original white noise'); + next_row(y); + + NumeditParam(obj,'lfreq',2000,x,y, ... + 'label','Modulator_LowFreq', ... + 'TooltipString','Lower bound for the frequency modulator'); + next_row(y); + + NumeditParam(obj,'hfreq',20000,x,y, ... + 'label','Modulator_HighFreq', ... + 'TooltipString','Upper bound for the frequency modulator'); next_row(y); - % NumeditParam(obj,'outband',60,x,y,'label','Outband','TooltipString','outband on the distribution from which white noise is produced'); + + % NumeditParam(obj,'outband',60,x,y,'label','Outband', ... + % 'TooltipString','outband on the distribution from which white noise is produced'); % next_row(y); - NumeditParam(obj,'minS1',0.007,x,y,'label','minS1','TooltipString','min sigma value for AUD1'); + + NumeditParam(obj,'minS1',0.007,x,y, ... + 'label','minS1','TooltipString','min sigma value for AUD1'); next_row(y); - DispParam(obj,'maxS',40,x,y,'label','maxS','TooltipString','max sigma value for AUD1'); - NumeditParam(obj,'s2_s1_ratio',2.6,x,y,'label','s2_s1_ratio','TooltipString','Intensity index i.e. Ind=(S1-S2)/(S1+S2)'); + + DispParam(obj,'maxS',40,x,y,'label','maxS','TooltipString','max sigma value for AUD1'); + + NumeditParam(obj,'s2_s1_ratio',2.6,x,y,'label','s2_s1_ratio', ... + 'TooltipString','Intensity index i.e. Ind=(S1-S2)/(S1+S2)'); next_row(y); + ToggleParam(obj, 'psych_pairs', 0, x,y,... 'OnString', 'Psych Pairs ON',... 'OffString', 'Psych Pairs OFF',... 'TooltipString', sprintf('If on (black) then it disable the presentation of psychometric pairs')); - next_row(y); - NumeditParam(obj,'nPsych',6,x,y,'label','Num Psych Pairs','TooltipString','Number of psychometric pairs'); next_row(y); - NumeditParam(obj,'from',0.047,x,y,'label','lowest pair','TooltipString','Psychometric pairs will be put between this pair, and an upper pair based on Ratio'); + + NumeditParam(obj,'nPsych',6,x,y, ... + 'label','Num Psych Pairs','TooltipString','Number of psychometric pairs'); next_row(y); + + NumeditParam(obj,'from',0.047,x,y, ... + 'label','lowest pair', ... + 'TooltipString','Psychometric pairs will be put between this pair, and an upper pair based on Ratio'); + next_row(y); + MenuParam(obj, 'psych_type', {'horizpairs', 'vertpairs'}, ... - 'horizpairs', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nhorizpairs means psychometric pairs will be built with "from" as their fixed S2 while S1 will increase,\n' ... - '"vertpairs" means psychometric pairs will be built with "from" as their fixed S1 while S2 will increase'])); next_row(y, 1.3) - NumeditParam(obj,'probpsych',0.9,x,y,'label','probpsych','TooltipString','probability of having a psychometric pair as the stimulus pair at each trial'); + 'horizpairs', x, y, 'labelfraction', 0.35, ... + 'TooltipString', sprintf(['\n"horizpairs" means psychometric pairs will be built with "from" as their fixed S2 while S1 will increase,\n' ... + '"vertpairs" means psychometric pairs will be built with "from" as their fixed S1 while S2 will increase'])); + next_row(y, 1.3) + + NumeditParam(obj,'probpsych',0.9,x,y, ... + 'label','probpsych', ... + 'TooltipString','probability of having a psychometric pair as the stimulus pair at each trial'); disable(nPatt); next_column(x); y=5; + SoloParamHandle(obj, 'existing_numClassPsych', 'value', 0, 'saveable', 0); SoloParamHandle(obj, 'existing_numClass', 'value', 0, 'saveable', 0); - SoloParamHandle(obj, 'my_window_info', 'value', [x, y, double(value(myfig))], 'saveable', 0); - NumeditParam(obj,'numClass',4,x,y,'label','numClass','TooltipString','Number of stimulus pairs'); + SoloParamHandle(obj, 'my_window_info', ... + 'value', [x, y, double(value(myfig))], 'saveable', 0); + + NumeditParam(obj,'numClass',4,x,y, ... + 'label','numClass','TooltipString','Number of stimulus pairs'); set_callback_on_load(numClass, 4); %#ok set_callback(numClass, {mfilename, 'numClass'}); - numClass.value = 4; callback(numClass); + numClass.value = 4; + callback(numClass); StimulusSection(obj,'plot_pairs'); set_callback(psych_pairs, {mfilename, 'PsychPairs'}); @@ -466,123 +567,170 @@ %% Case plot_pais case 'plot_pairs' + % Generate pairs and calculate max value StimulusSection(obj,'make_pairs'); - maxS.value=max(thesepairs(:)); - %% plot the pair set - cla(value(ax)) - xd=log(thesepairs(:,1)); - yd=log(thesepairs(:,2)); - for ii=1:length(xd) + maxS.value = max(thesepairs(:)); + + %% Plot the pair set + cla(value(ax)); + cla(value(ax2)); + + % Transform data to log scale + xd = log(thesepairs(:,1)); + yd = log(thesepairs(:,2)); + + + % Plot individual points + for ii = 1:length(xd) axes(value(ax)); - plot(xd(ii),yd(ii),'s','MarkerSize',15,'MarkerEdgeColor',[0 0 0],'LineWidth',2) - hold on - eval(sprintf('hperf%d=text(xd(ii),yd(ii),num2str(ii));',ii)); - hold on + plot(xd(ii), yd(ii), 's', 'MarkerSize', 15, 'MarkerEdgeColor', [0 0 0], 'LineWidth', 2); + hold on; + eval(sprintf('hperf%d=text(xd(ii),yd(ii),num2str(ii));', ii)); + hold on; end - axis square + + % Adjust plot appearance + axis square; + + % Calculate and set tick values % Ytick=get(value(ax),'YtickLabel'); % Xtick=get(value(ax),'XtickLabel'); - set(value(ax),'ytick',((yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2))),'xtick',((xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)))); - set(value(ax),'yticklabel',num2str(exp(yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)),2),'xticklabel',num2str(exp(xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)),2)); - ylabel('\sigma_2 in log scale','FontSize',16,'FontName','Cambria Math'); - set(value(ax),'Fontsize',15) - xlabel('\sigma_1 in log scale','FontSize',16,'FontName','Cambria Math') + set(value(ax), 'ytick', ((yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)))); + set(value(ax), 'xtick', ((xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)))); + set(value(ax), 'yticklabel', num2str(exp(yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)), 2)); + set(value(ax), 'xticklabel', num2str(exp(xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)), 2)); + + % Set labels and font sizes + ylabel('log_e \sigma_2'); + xlabel('log_e \sigma_1'); + + + %% Select side and generate pair based on rule and trial SideSection(obj,'get_current_side'); - if strcmp(Rule,'S2>S1 Left') + + if strcmp(Rule, 'S2>S1 Left') + % Handle LEFT trial case if strcmp(ThisTrial, 'LEFT') - bag=[]; - for cc=1:value(numClass)+(numClass-1)*value(midclass_pairs) - eval(sprintf('pr=value(probClass%d);',cc+value(numClass))); - bag=[bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; + bag = []; + for cc = 1:value(numClass)+(numClass-1)*value(midclass_pairs) + eval(sprintf('pr=value(probClass%d);', cc+value(numClass))); + bag = [bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; end - if midclass_pairs ==1 - for cc=1:midclass_pairs - bag=[bag value(cc)]; + % Handle midclass pairs + if midclass_pairs == 1 + for cc = 1:midclass_pairs + bag = [bag value(cc)]; end end - pp=1; - if psych_pairs ==1 - for cc=value(numClass)+1:value(numClass)+value(nPsych)/2 + % Handle psych pairs + pp = 1; + if psych_pairs == 1 + for cc = value(numClass)+1:value(numClass)+value(nPsych)/2 bag = [bag ones(1,(probpsych*10))*value(cc)]; end end - pp=randsample(bag,length(bag)); - thispair=[pairs_u(pp(1),1) pairs_u(pp(1),2)]; + + % Select pair + pp = randsample(bag, length(bag)); + thispair = [pairs_u(pp(1),1) pairs_u(pp(1),2)]; + + % Determine class if pp(1) > value(numClass*2) - thisclass(n_done_trials+1)=pp(1)+numClass*2; + thisclass(n_done_trials+1) = pp(1)+numClass*2; else - thisclass(n_done_trials+1)=pp(1)+numClass; + thisclass(n_done_trials+1) = pp(1)+numClass; end else - - bag=[]; - for cc=1:value(numClass)+(numClass-1)*value(midclass_pairs) - eval(sprintf('pr=value(probClass%d);',cc)); - bag=[bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; + % Handle RIGHT trial case + bag = []; + for cc = 1:value(numClass)+(numClass-1)*value(midclass_pairs) + eval(sprintf('pr=value(probClass%d);', cc)); + bag = [bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; end - pp=1; - pp=randsample(bag,length(bag)); - thispair=[pairs_d(pp(1),1) pairs_d(pp(1),2)]; + + % Select pair + pp = randsample(bag, length(bag)); + thispair = [pairs_d(pp(1),1) pairs_d(pp(1),2)]; + + % Determine class if pp(1) > value(numClass) - thisclass(n_done_trials+1)=pp(1)+numClass; + thisclass(n_done_trials+1) = pp(1)+numClass; else - thisclass(n_done_trials+1)=pp(1); + thisclass(n_done_trials+1) = pp(1); end end + elseif strcmp(Rule,'S2>S1 Right') + % Handle RIGHT rule case if strcmp(ThisTrial, 'RIGHT') - bag=[]; - for cc=1:value(numClass)+(numClass-1)*value(midclass_pairs) - eval(sprintf('pr=value(probClass%d);',cc+value(numClass))); - bag=[bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; + bag = []; + for cc = 1:value(numClass)+(numClass-1)*value(midclass_pairs) + eval(sprintf('pr=value(probClass%d);', cc+value(numClass))); + bag = [bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; end - if psych_pairs ==1 - for cc=value(numClass)+1:value(numClass)+value(nPsych)/2 + % Handle psych pairs + if psych_pairs == 1 + for cc = value(numClass)+1:value(numClass)+value(nPsych)/2 bag = [bag ones(1,(probpsych*10))*(value(cc)+numClass*2)]; end end - pp=randsample(bag,length(bag)); - thispair=[pairs_u(pp(1),1) pairs_u(pp(1),2)]; + % Select pair + pp = randsample(bag, length(bag)); + thispair = [pairs_u(pp(1),1) pairs_u(pp(1),2)]; + + % Determine class if pp(1) > value(numClass*2) - thisclass(n_done_trials+1)=pp(1)+numClass*2; + thisclass(n_done_trials+1) = pp(1)+numClass*2; else - thisclass(n_done_trials+1)=pp(1)+numClass; + thisclass(n_done_trials+1) = pp(1)+numClass; end - else - bag=[]; - for cc=1:value(numClass)+(numClass-1)*value(midclass_pairs) - eval(sprintf('pr=value(probClass%d);',cc)); - bag=[bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; + % Handle LEFT trial case + bag = []; + for cc = 1:value(numClass)+(numClass-1)*value(midclass_pairs) + eval(sprintf('pr=value(probClass%d);', cc)); + bag = [bag ones(1,(10-10*value(probpsych))*pr)*value(cc)]; end - if psych_pairs ==1 - for cc=value(numClass)+1:value(numClass)+value(nPsych)/2 + % Handle psych pairs + if psych_pairs == 1 + for cc = value(numClass)+1:value(numClass)+value(nPsych)/2 bag = [bag ones(1,(probpsych*10))*(value(cc)+numClass)]; end end - pp=randsample(bag,length(bag)); - thispair=[pairs_d(pp(1),1) pairs_d(pp(1),2)]; + % Select pair + pp = randsample(bag, length(bag)); + thispair = [pairs_d(pp(1),1) pairs_d(pp(1),2)]; + % Determine class if pp(1) > value(numClass) - thisclass(n_done_trials+1)=pp(1)+numClass; + thisclass(n_done_trials+1) = pp(1)+numClass; else - thisclass(n_done_trials+1)=pp(1); + thisclass(n_done_trials+1) = pp(1); end end end - A1_sigma.value=thispair(1); - A2_sigma.value=thispair(2); - %% plot the pair - h1.value=plot(log(value(A1_sigma)),log(value(A2_sigma)),'s','color',[0.8 0.4 0.1],'markerfacecolor',[0.8 0.4 0.1],'MarkerSize',15,'LineWidth',3); - %LOGplotPairs(thesepairs(:,1),thesepairs(:,2),'s',15,'k',1,16,thispair(1),thispair(2),value(ax),'init') + % Set sigma values + A1_sigma.value = thispair(1); + A2_sigma.value = thispair(2); + + %% Plot the selected pair + h1.value = plot(log(value(A1_sigma)),log(value(A2_sigma)),'s','color',[0.8 0.4 0.1],'markerfacecolor',[0.8 0.4 0.1],'MarkerSize',15,'LineWidth',3); + + axChil = ax.Children; + copyobj(axChil, value(ax2)); + + % Commented-out alternative plotting function + % LOGplotPairs(thesepairs(:,1),thesepairs(:,2),'s',15,'k',1,16,thispair(1),thispair(2),value(ax),'init') + + %% Case Plot_perf case 'plot_perf' @@ -645,23 +793,30 @@ end %% plot the pair set - cla(value(axperf)) + cla(value(axperf)); + cla(value(axperf2)); + xd=log(thesepairs(:,1)); yd=log(thesepairs(:,2)); + + axes(value(axperf)); + for ii=1:length(xd) - axes(value(axperf)); plot(xd(ii),yd(ii),'s','MarkerSize',31,'MarkerEdgeColor',[0 0 0],'LineWidth',1.5) hold on eval(sprintf('perf=value(perfClass%d);',ii)) text(xd(ii)-0.14,yd(ii),num2str(round(perf*1000)/10)); hold on end + axis square - set(value(ax),'ytick',((yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2))),'xtick',((xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)))); - set(value(ax),'yticklabel',num2str(exp(yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)),2),'xticklabel',num2str(exp(xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)),2)); - ylabel('\sigma_2 in log scale','FontSize',16,'FontName','Cambria Math'); - set(value(axperf),'Fontsize',15) - xlabel('\sigma_1 in log scale','FontSize',16,'FontName','Cambria Math') + set(value(axperf),'ytick',((yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2))),'xtick',((xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)))); + set(value(axperf),'yticklabel',num2str(exp(yd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)),2),'xticklabel',num2str(exp(xd(1:1+value(midclass_pairs):(end-nPsych*psych_pairs)/2)),2)); + ylabel('\sigma_2 in log scale'); + xlabel('\sigma_1 in log scale'); + + axperfChil = axperf.Children; + copyobj(axperfChil, value(axperf2)); %% Case psych_pairs @@ -758,6 +913,7 @@ %% Case hide case 'hide' StimulusShow.value = 0; set(value(myfig), 'Visible', 'off'); + set(value(pairs_plot_figure), 'Visible', 'off'); %% Case show case 'show' StimulusShow.value = 1; set(value(myfig), 'Visible', 'on'); diff --git a/Protocols/@CalibrationMeister/CalibrationMeister.m b/Protocols/@CalibrationMeister/CalibrationMeister.m index ee51c314..30c24f43 100644 --- a/Protocols/@CalibrationMeister/CalibrationMeister.m +++ b/Protocols/@CalibrationMeister/CalibrationMeister.m @@ -291,32 +291,32 @@ set(mh,'Position', [main_header_pos main_header_size],'FontSize', main_header_font_size,... 'BackgroundColor', main_header_bgcolor,'ForegroundColor',main_header_fgcolor); - SubHeaderParam(obj, 'sub_header_3',sub_header_3_string,20,20,'TooltipString','Change them at your own risk!!!'); + SubheaderParam(obj, 'sub_header_3',sub_header_3_string,20,20,'TooltipString','Change them at your own risk!!!'); mh=get_ghandle(sub_header_3); set(mh,'Position', [sub_header_3_pos sub_header_3_size],'FontSize', sub_header_3_font_size,... 'BackgroundColor', sub_header_3_bgcolor,'ForegroundColor',sub_header_3_fgcolor); - SubHeaderParam(obj, 'sub_header_2',sub_header_2_string,20,20); + SubheaderParam(obj, 'sub_header_2',sub_header_2_string,20,20); mh=get_ghandle(sub_header_2); set(mh,'Position', [sub_header_2_pos sub_header_2_size],'FontSize', sub_header_2_font_size,... 'BackgroundColor', sub_header_2_bgcolor,'ForegroundColor',sub_header_2_fgcolor); - SubHeaderParam(obj, 'sub_header_1',sub_header_1_string,20,20); + SubheaderParam(obj, 'sub_header_1',sub_header_1_string,20,20); mh=get_ghandle(sub_header_1); set(mh,'Position', [sub_header_1_pos sub_header_1_size],'FontSize', sub_header_1_font_size,... 'BackgroundColor', sub_header_1_bgcolor,'ForegroundColor',sub_header_1_fgcolor); - SubHeaderParam(obj, 'sub_header_4',sub_header_4_string,20,20); + SubheaderParam(obj, 'sub_header_4',sub_header_4_string,20,20); mh=get_ghandle(sub_header_4); set(mh,'Position', [sub_header_4_pos sub_header_4_size],'FontSize', sub_header_4_font_size,... 'BackgroundColor', sub_header_4_bgcolor,'ForegroundColor',sub_header_4_fgcolor); - SubHeaderParam(obj, 'sub_header_5',sub_header_5_string,20,20,'TooltipString','These are the estimates of pulse times which will be used to achieve the next target under consideration'); + SubheaderParam(obj, 'sub_header_5',sub_header_5_string,20,20,'TooltipString','These are the estimates of pulse times which will be used to achieve the next target under consideration'); mh=get_ghandle(sub_header_5); set(mh,'Position', [sub_header_5_pos sub_header_5_size],'FontSize', sub_header_5_font_size,... 'BackgroundColor', sub_header_5_bgcolor,'ForegroundColor',sub_header_5_fgcolor); - SubHeaderParam(obj, 'sub_header_6',sub_header_6_string,20,20,'TooltipString','Tells the current status of calibration for this rig'); + SubheaderParam(obj, 'sub_header_6',sub_header_6_string,20,20,'TooltipString','Tells the current status of calibration for this rig'); mh=get_ghandle(sub_header_6); set(mh,'Position', [sub_header_6_pos sub_header_6_size],'FontSize', sub_header_6_font_size,'FontWeight',sub_header_6_font_wieght,... 'BackgroundColor', sub_header_6_bgcolor,'ForegroundColor',sub_header_6_fgcolor); @@ -370,45 +370,45 @@ % Buttons and their callbacks: Start % Settings: Start - NumEditParam(obj, 'inter_valve_pause',value(inter_valve_pause_default),10,200,'label',INTER_VALVE_PAUSE_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'inter_valve_pause',value(inter_valve_pause_default),10,200,'label',INTER_VALVE_PAUSE_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(inter_valve_pause); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 200 220 20]); set_callback(inter_valve_pause,{mfilename,'verify_inter_valve_pause'}); - NumEditParam(obj, 'num_pulses',value(num_pulses_default),10,180,'label',NUM_PULSES_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'num_pulses',value(num_pulses_default),10,180,'label',NUM_PULSES_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(num_pulses); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 180 220 20]); set_callback(num_pulses,{mfilename,'verify_num_pulses'}); - NumEditParam(obj, 'left_pulse_time',value(pulse_time_default),10,160,'label',LEFT_PULSE_TIME_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'left_pulse_time',value(pulse_time_default),10,160,'label',LEFT_PULSE_TIME_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(left_pulse_time); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 160 220 20]); set_callback(left_pulse_time,{mfilename,'verify_pulse_times'}); if ~valves_used(1); disable(left_pulse_time); end; - NumEditParam(obj, 'center_pulse_time',value(pulse_time_default),10,140,'label',CENTER_PULSE_TIME_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'center_pulse_time',value(pulse_time_default),10,140,'label',CENTER_PULSE_TIME_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(center_pulse_time); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 140 220 20]); set_callback(center_pulse_time,{mfilename,'verify_pulse_times'}); if ~valves_used(2); disable(center_pulse_time); end; - NumEditParam(obj, 'right_pulse_time',value(pulse_time_default),10,120,'label',RIGHT_PULSE_TIME_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'right_pulse_time',value(pulse_time_default),10,120,'label',RIGHT_PULSE_TIME_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(right_pulse_time); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 120 220 20]); set_callback(right_pulse_time,{mfilename,'verify_pulse_times'}); if ~valves_used(3); disable(right_pulse_time); end; - NumEditParam(obj, 'low_target_dispense',value(low_target_dispense_default),10,100,'label',LOW_TARGET_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'low_target_dispense',value(low_target_dispense_default),10,100,'label',LOW_TARGET_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(low_target_dispense); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 100 220 20]); set_callback(low_target_dispense,{mfilename,'verify_target_dispense'}); - NumEditParam(obj, 'high_target_dispense',value(high_target_dispense_default),10,80,'label',HIGH_TARGET_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'high_target_dispense',value(high_target_dispense_default),10,80,'label',HIGH_TARGET_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(high_target_dispense); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 80 220 20]); set_callback(high_target_dispense,{mfilename,'verify_target_dispense'}); - NumEditParam(obj, 'error_tolerance',value(error_tolerance_default),10,60,'label',ERROR_TOLERANCE_LABEL,'labelfraction',universal_label_fraction); + NumeditParam(obj, 'error_tolerance',value(error_tolerance_default),10,60,'label',ERROR_TOLERANCE_LABEL,'labelfraction',universal_label_fraction); lh=get_glhandle(error_tolerance); set(lh(2),'FontSize', universal_label_font_size, 'BackgroundColor', universal_label_color,'Position',[60 60 220 20]); set_callback(error_tolerance,{mfilename,'verify_error_tolerance'}); diff --git a/Protocols/@Rigtest_singletrial/Rigtest_singletrial.m b/Protocols/@Rigtest_singletrial/Rigtest_singletrial.m index c663c272..8bf794ed 100644 --- a/Protocols/@Rigtest_singletrial/Rigtest_singletrial.m +++ b/Protocols/@Rigtest_singletrial/Rigtest_singletrial.m @@ -132,9 +132,7 @@ set(value(myfig), 'Name', name, 'Tag', name, ... 'closerequestfcn', 'dispatcher(''close_protocol'')', 'MenuBar', 'none'); - - - % At this point we have one SoloParamHandle, myfig + % At this point we have one SoloParamHandle, myfig % Let's put the figure where we want it and give it a reasonable size: set(value(myfig), 'Position', [485 144 300 400]); @@ -148,25 +146,26 @@ next_row(y); DispParam(obj, 'nTrials', 0, x, y); next_row(y); - % For plotting with the pokesplot plugin, we need to tell it what - % colors to plot with: - my_state_colors = struct( ... - 'wait_for_left', [0.2 0.5 0.2], ... - 'in_left', [0.5 0.5 1], ... - 'in_center', [0.5 1 0.5], ... - 'in_right', [1 0.5 0.5], ... - 'state_0', [1 1 1], ... - 'final_state', [0.5 0 0], ... - 'check_next_trial_ready', [0.7 0.7 0.7]); - % In pokesplot, the poke colors have a default value, so we don't need - % to specify them, but here they are so you know how to change them. - my_poke_colors = struct( ... - 'L', 0.6*[1 0.66 0], ... - 'C', [0 0 0], ... - 'R', 0.9*[1 0.66 0]); - - [x, y] = PokesPlotSection(obj, 'init', x, y, ... - struct('states', my_state_colors, 'pokes', my_poke_colors)); + + % % For plotting with the pokesplot plugin, we need to tell it what + % % colors to plot with: + % my_state_colors = struct( ... + % 'wait_for_left', [0.2 0.5 0.2], ... + % 'in_left', [0.5 0.5 1], ... + % 'in_center', [0.5 1 0.5], ... + % 'in_right', [1 0.5 0.5], ... + % 'state_0', [1 1 1], ... + % 'final_state', [0.5 0 0], ... + % 'check_next_trial_ready', [0.7 0.7 0.7]); + % % In pokesplot, the poke colors have a default value, so we don't need + % % to specify them, but here they are so you know how to change them. + % my_poke_colors = struct( ... + % 'L', 0.6*[1 0.66 0], ... + % 'C', [0 0 0], ... + % 'R', 0.9*[1 0.66 0]); + % + % [x, y] = PokesPlotSection(obj, 'init', x, y, ... + % struct('states', my_state_colors, 'pokes', my_poke_colors)); next_row(y); SubheaderParam(obj, 'title', 'Poke in lights', x, y); next_row(y); @@ -215,6 +214,16 @@ Rigtest_singletrial(obj, 'prepare_next_trial'); + case 'set_callback' + + if not(isempty(varargin)) + callback_info = varargin{1}; + else + callback_info = []; + end + SoloParamHandle(obj, 'ManualTestCompletionCallback', 'value', callback_info); + + %--------------------------------------------------------------- % CASE PREPARE_NEXT_TRIAL @@ -225,10 +234,39 @@ nTrials.value = n_done_trials; % <~> If we've completed our trial, tell RunRats that we're done. - if nTrials > 0 && runrats('is_running'), - runrats('rigtest_singletrial_is_complete'); - return; - end; + + %% Modified by Arpit to run without Runrats + % <~> If we've completed our trial, tell RunRats that we're done. + if n_done_trials > 0 + + if nTrials > 0 && runrats('is_running') + runrats('rigtest_singletrial_is_complete'); + return; + + elseif nTrials > 0 && NeuropixelNeuroblueprint('is_running') + NeuropixelNeuroblueprint('manual_test_stopping'); + return; + + else % probably called by another function so need to stop the dispatcher from here itself + % Send a dummy state machine that does nothing and ends quickly. + % This prevents the protocol from running a second trial. + + % Find the callback handle created by the main GUI calling it. + h_callback = get_sphandle('owner', ['@' class(obj) '$'], 'name', 'ManualTestCompletionCallback'); + + if ~isempty(h_callback) + callback_info = value(h_callback{1}); + if ~isempty(callback_info) && isa(callback_info, 'cell') && numel(callback_info) == 2 + % Execute the callback: feval(function_handle, arguments...) + % This directly calls 'continue_load_after_manual_test' in the main GUI. + feval(callback_info{1}, callback_info{2}); + end + end + + return; % IMPORTANT: Exit here to prevent building the real state machine again. + end + end + % <~> end adaptation for single-trial use :P @@ -326,9 +364,6 @@ 'self_timer', 0.2, 'input_to_statechange', {'Tup', 'check_next_trial_ready'}); dispatcher('send_assembler', sma, 'final_state'); - % Defaul behavior of following call is that every 20 trials, the data - % gets saved, not interactive, no commit to CVS. -% SavingSection(obj, 'autosave_data'); %--------------------------------------------------------------- % CASE TRIAL_COMPLETED @@ -337,20 +372,20 @@ % Do any updates in the protocol that need doing: feval(mfilename, 'update'); % And PokesPlot needs completing the trial: - PokesPlotSection(obj, 'trial_completed'); + % PokesPlotSection(obj, 'trial_completed'); %--------------------------------------------------------------- % CASE UPDATE %--------------------------------------------------------------- case 'update' - PokesPlotSection(obj, 'update'); + % PokesPlotSection(obj, 'update'); %--------------------------------------------------------------- % CASE CLOSE %--------------------------------------------------------------- case 'close' - PokesPlotSection(obj, 'close'); + % PokesPlotSection(obj, 'close'); if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)), %#ok delete(value(myfig)); end; diff --git a/Protocols/@SoundCalibration/SoundCalibration.m b/Protocols/@SoundCalibration/SoundCalibration.m index bd1aef55..06b4b3d1 100644 --- a/Protocols/@SoundCalibration/SoundCalibration.m +++ b/Protocols/@SoundCalibration/SoundCalibration.m @@ -3,7 +3,7 @@ %through dispatcher. This is treated like any other protocol. It is %designed to be plastic, looking at a rigs settings files, determining %the input and output lines, and building a GUI specific to that rigs -%componants. +%componants. % %Written by Chuck 2017 @@ -23,20 +23,20 @@ %--------------------------------------------------------------- % If creating an empty object, return without further ado: -if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')), - return; +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')), + return; end; -if isa(varargin{1}, mfilename), % If first arg is an object of this class itself, we are - % Most likely responding to a callback from - % a SoloParamHandle defined in this mfile. - if length(varargin) < 2 || ~ischar(varargin{2}), - error(['If called with a "%s" object as first arg, a second arg, a ' ... - 'string specifying the action, is required\n']); - else action = varargin{2}; varargin = varargin(3:end); %#ok - end; +if isa(varargin{1}, mfilename), % If first arg is an object of this class itself, we are + % Most likely responding to a callback from + % a SoloParamHandle defined in this mfile. + if length(varargin) < 2 || ~ischar(varargin{2}), + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else action = varargin{2}; varargin = varargin(3:end); %#ok + end; else % Ok, regular call with first param being the action string. - action = varargin{1}; varargin = varargin(2:end); %#ok + action = varargin{1}; varargin = varargin(2:end); %#ok end; if ~ischar(action), error('The action parameter must be a string'); end; @@ -72,16 +72,16 @@ % protocol (see below). % % 'trial_completed' Called when 'state_0' is reached in the RTLSM, -% marking final completion of a trial (and the start of +% marking final completion of a trial (and the start of % the next). % % 'close' Called when the protocol is to be closed. % % -% VARIABLES THAT DISPATCHER WILL ALWAYS INSTANTIATE FOR YOU IN YOUR +% VARIABLES THAT DISPATCHER WILL ALWAYS INSTANTIATE FOR YOU IN YOUR % PROTOCOL: % -% (These variables will be instantiated as regular Matlab variables, +% (These variables will be instantiated as regular Matlab variables, % not SoloParamHandles. For any method in your protocol (i.e., an m-file % within the @your_protocol directory) that takes "obj" as its first argument, % calling "GetSoloFunctionArgs(obj)" will instantiate all the variables below.) @@ -116,203 +116,293 @@ % dispatcher.m. See the wiki documentation for information on how to access % those histories from within your protocol and for information. % -% +% switch action, - %--------------------------------------------------------------- - % CASE INIT - %--------------------------------------------------------------- - - case 'init' - - % Make default figure. We remember to make it non-saveable; on next run - % the handle to this figure might be different, and we don't want to - % overwrite it when someone does load_data and some old value of the - % fig handle was stored as SoloParamHandle "myfig" - SoloParamHandle(obj, 'myfig', 'saveable', 0); myfig.value = double(figure); - - % Make the title of the figure be the protocol name, and if someone tries - % to close this figure, call dispatcher's close_protocol function, so it'll know - % to take it off the list of open protocols. - name = mfilename; - set(value(myfig), 'Name', name, 'Tag', name, ... - 'closerequestfcn', [mfilename,'(''close'');'], 'MenuBar', 'none'); - - alldio = bSettings('get','DIOLINES','ALL'); - allinp = bSettings('get','INPUTLINES','ALL'); - dionums = cell2mat(alldio(:,2)); - inpnums = cell2mat(allinp(:,2)); - - for i = 1:16 - inp1 = find(inpnums == i,1,'first'); - dio1 = find(dionums == 2^(( i-1)*2), 1,'first'); - dio2 = find(dionums == 2^(((i-1)*2)+1),1,'first'); - - if isempty(inp1) && isempty(dio1) && isempty(dio2) - continue; - end - - if ~isempty(inp1) - linegroups{i}{1,1} = allinp{inp1,2}; %#ok - linegroups{i}{1,2} = allinp{inp1,1}; %#ok - else - linegroups{i}{1,1} = []; %#ok - linegroups{i}{1,2} = []; %#ok - end - if ~isempty(dio1) - linegroups{i}{2,1} = alldio{dio1,2}; %#ok - linegroups{i}{2,2} = alldio{dio1,1}; %#ok - else - linegroups{i}{2,1} = []; %#ok - linegroups{i}{2,2} = []; %#ok - end - if ~isempty(dio2) - linegroups{i}{3,1} = alldio{dio2,2}; %#ok - linegroups{i}{3,2} = alldio{dio2,1}; %#ok - else - linegroups{i}{3,1} = []; %#ok - linegroups{i}{3,2} = []; %#ok - end - end - - % Generate the sounds we need. - soundserver = bSettings('get','RIGS','sound_machine_server'); - if ~isempty(soundserver) - sr = SoundManagerSection(obj,'get_sample_rate'); - Fs=sr; - lfreq=2000; - hfreq=20000; - freq = 100; - T = 1; - fcut = 110; - filter_type = 'GAUS'; - A1_sigma = 0.0500; - A2_sigma = 0.0260; - A3_sigma = 0.0135; - A4_sigma = 0.0070; - [rawA1 rawA2 normA1 normA2]=noisestim(1,1,T,fcut,Fs,filter_type); - modulator=singlenoise(1,T,[lfreq hfreq],Fs,'BUTTER'); - AUD1=normA1(1:T*sr).*modulator(1:T*sr).*A1_sigma; - AUD2=normA1(1:T*sr).*modulator(1:T*sr).*A2_sigma; - AUD3=normA1(1:T*sr).*modulator(1:T*sr).*A3_sigma; - AUD4=normA1(1:T*sr).*modulator(1:T*sr).*A4_sigma; -% snd = MakeBupperSwoop(sr,0,freq,freq, len/2, len/2, 0, 0.1, 'F1_volume_factor',0.07,'F2_volume_factor',0.07); -% silence_length = 0; -% presound_silence = zeros(1,sr*silence_length/1000); -% snd = [presound_silence, snd]; -% -% SoundManagerSection(obj,'declare_new_sound','left_sound', [snd; zeros(1,size(snd,2))]); -% SoundManagerSection(obj,'declare_new_sound','right_sound', [zeros(1,size(snd,2)); snd]); -% -% SoundManagerSection(obj,'loop_sound','left_sound',1); -% SoundManagerSection(obj,'loop_sound','right_sound',1); -% -% SoundManagerSection(obj,'declare_new_sound','center_sound',[snd;snd([ceil((sr / freq) / 2):end,1:ceil((sr / freq) / 2)-1])]); -% SoundManagerSection(obj,'loop_sound','center_sound',1); -% - if ~isempty(AUD2) - SoundManagerSection(obj, 'declare_new_sound', 'left_sound', [AUD2'; AUD2']) - end - if ~isempty(AUD1) - SoundManagerSection(obj, 'declare_new_sound', 'center_sound', [AUD1'; AUD1']) - end - if ~isempty(AUD3) - SoundManagerSection(obj, 'declare_new_sound', 'right_sound', [AUD3'; AUD3']) - end - if ~isempty(AUD4) - SoundManagerSection(obj, 'declare_new_sound', 'fourth_sound', [AUD4'; AUD4']) - end - SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); - end - - MP = get(0,'MonitorPositions'); - groupwidth = floor(MP(3)/numel(linegroups)); - - % At this point we have one SoloParamHandle, myfig - % Let's put the figure where we want it and give it a reasonable size: - set(value(myfig), 'Position', [1,floor((MP(4)-600)/2),numel(linegroups)*groupwidth,600]); - - line_names = []; - for i = 1:numel(linegroups) - if ~isempty(linegroups{i}) && ~isempty(linegroups{i}{1,2}) - SubheaderParam(obj,['Input',linegroups{i}{1,2}],{linegroups{i}{1,2};0},0,0,'position',[((i-1)*groupwidth)+1, 300, groupwidth-2,290]); - set(get_ghandle(eval(['Input',linegroups{i}{1,2}])),'FontSize',32,'BackgroundColor',[0,1,0],'HorizontalAlignment','center'); - line_names(end+1) = linegroups{i}{1,2}; %#ok - - if strcmp(linegroups{i}{1,2},'L') && ~isempty(soundserver) - ToggleParam(obj,'LeftSound', 0,0,0,'position',[((i-1)*groupwidth)+1, 10, groupwidth-2,50],'OnString', 'Sound Two ON', 'OffString', 'Sound Two OFF'); - set_callback(LeftSound,{mfilename,'play_left_sound'}); - - elseif strcmp(linegroups{i}{1,2},'R') && ~isempty(soundserver) - ToggleParam(obj,'RightSound', 0,0,0,'position',[((i-1)*groupwidth)+1, 10, groupwidth-2,50],'OnString', 'Sound Three ON', 'OffString', 'Sound Three OFF'); - set_callback(RightSound,{mfilename,'play_right_sound'}); - - elseif strcmp(linegroups{i}{1,2},'C') && ~isempty(soundserver) - ToggleParam(obj,'CenterSound', 0,0,0,'position',[((i-1)*groupwidth)+1, 10, groupwidth-2,50],'OnString', 'Sound One ON', 'OffString', 'Sound One OFF'); - set_callback(CenterSound,{mfilename,'play_center_sound'}); - - elseif strcmp(linegroups{i}{1,2},'A') && ~isempty(soundserver) - ToggleParam(obj,'FourthSound', 0,0,0,'position',[((i-1)*groupwidth)+1, 10, groupwidth-2,50],'OnString', 'Sound Four ON', 'OffString', 'Sound Four OFF'); - set_callback(FourthSound,{mfilename,'play_fourth_sound'}); + %--------------------------------------------------------------- + % CASE INIT + %--------------------------------------------------------------- + + case 'init' + + % Make default figure. We remember to make it non-saveable; on next run + % the handle to this figure might be different, and we don't want to + % overwrite it when someone does load_data and some old value of the + % fig handle was stored as SoloParamHandle "myfig" + SoloParamHandle(obj, 'myfig', 'saveable', 0); myfig.value = double(figure); + + % Make the title of the figure be the protocol name, and if someone tries + % to close this figure, call dispatcher's close_protocol function, so it'll know + % to take it off the list of open protocols. + name = mfilename; + set(value(myfig), 'Name', name, 'Tag', name, ... + 'closerequestfcn', [mfilename,'(''close'');'], 'MenuBar', 'none'); + + alldio = bSettings('get','DIOLINES','ALL'); + allinp = bSettings('get','INPUTLINES','ALL'); + dionums = cell2mat(alldio(:,2)); + inpnums = cell2mat(allinp(:,2)); + + for i = 1:16 + inp1 = find(inpnums == i,1,'first'); + dio1 = find(dionums == 2^(( i-1)*2), 1,'first'); + dio2 = find(dionums == 2^(((i-1)*2)+1),1,'first'); + + if isempty(inp1) && isempty(dio1) && isempty(dio2) + continue; + end + + if ~isempty(inp1) + linegroups{i}{1,1} = allinp{inp1,2}; %#ok + linegroups{i}{1,2} = allinp{inp1,1}; %#ok + else + linegroups{i}{1,1} = []; %#ok + linegroups{i}{1,2} = []; %#ok + end + if ~isempty(dio1) + linegroups{i}{2,1} = alldio{dio1,2}; %#ok + linegroups{i}{2,2} = alldio{dio1,1}; %#ok + else + linegroups{i}{2,1} = []; %#ok + linegroups{i}{2,2} = []; %#ok + end + if ~isempty(dio2) + linegroups{i}{3,1} = alldio{dio2,2}; %#ok + linegroups{i}{3,2} = alldio{dio2,1}; %#ok + else + linegroups{i}{3,1} = []; %#ok + linegroups{i}{3,2} = []; %#ok end end - if ~isempty(linegroups{i}) && ~isempty(linegroups{i}{2,2}) - ToggleParam(obj,['DIO',num2str(i),'_1'],0,0,0,'position',[((i-1)*groupwidth)+1, 210, groupwidth-2,50],'OnString', [linegroups{i}{2,2},' ON'], 'OffString', [linegroups{i}{2,2},' OFF']); - set_callback(eval(['DIO',num2str(i),'_1']),{mfilename,['toggle',num2str(i),'_1']}); - - end - if ~isempty(linegroups{i}) && ~isempty(linegroups{i}{3,2}) - ToggleParam(obj,['DIO',num2str(i),'_2'],0,0,0,'position',[((i-1)*groupwidth)+1, 110, groupwidth-2,50],'OnString', [linegroups{i}{3,2},' ON'], 'OffString', [linegroups{i}{3,2},' OFF']); - set_callback(eval(['DIO',num2str(i),'_2']),{mfilename,['toggle',num2str(i),'_2']}); + + % Generate the sounds we need. + soundserver = bSettings('get','RIGS','sound_machine_server'); + if ~isempty(soundserver) + sr = SoundManagerSection(obj,'get_sample_rate'); + Fs=sr; + lfreq=2000; + hfreq=20000; + freq = 100; + T = 5; + fcut = 110; + filter_type = 'GAUS'; + A1_sigma = 0.0500; + A2_sigma = 0.0306 %0.1230;%0.0260; + A3_sigma = 0.0187 %0.0473;%0.0135; + A4_sigma = 0.0114 %0.0182;%0.0070; + A5_sigma = 0.0070; + [rawA1 rawA2 normA1 normA2]=noisestim(1,1,T,fcut,Fs,filter_type); + modulator=singlenoise(1,T,[lfreq hfreq],Fs,'BUTTER'); + AUD1=normA1(1:T*sr).*modulator(1:T*sr).*A1_sigma; + AUD2=normA1(1:T*sr).*modulator(1:T*sr).*A2_sigma; + AUD3=normA1(1:T*sr).*modulator(1:T*sr).*A3_sigma; + AUD4=normA1(1:T*sr).*modulator(1:T*sr).*A4_sigma; + AUD5=normA1(1:T*sr).*modulator(1:T*sr).*A5_sigma; + % snd = MakeBupperSwoop(sr,0,freq,freq, len/2, len/2, 0, 0.1, 'F1_volume_factor',0.07,'F2_volume_factor',0.07); + % silence_length = 0; + % presound_silence = zeros(1,sr*silence_length/1000); + % snd = [presound_silence, snd]; + % + % SoundManagerSection(obj,'declare_new_sound','left_sound', [snd; zeros(1,size(snd,2))]); + % SoundManagerSection(obj,'declare_new_sound','right_sound', [zeros(1,size(snd,2)); snd]); + % + % SoundManagerSection(obj,'loop_sound','left_sound',1); + % SoundManagerSection(obj,'loop_sound','right_sound',1); + % + % SoundManager Section(obj,'declare_new_sound','center_sound',[snd;snd([ceil((sr / freq) / 2):end,1:ceil((sr / freq) / 2)-1])]); + % SoundManagerSection(obj,'loop_sound','center_sound',1); + % + if ~isempty(AUD2) + SoundManagerSection(obj, 'declare_new_sound', 'left_sound', [AUD2'; AUD2']) + end + if ~isempty(AUD1) + SoundManagerSection(obj, 'declare_new_sound', 'center_sound', [AUD1'; AUD1']) + end + if ~isempty(AUD3) + SoundManagerSection(obj, 'declare_new_sound', 'right_sound', [AUD3'; AUD3']) + end + if ~isempty(AUD4) + SoundManagerSection(obj, 'declare_new_sound', 'fourth_sound', [AUD4'; AUD4']) + end + if ~isempty(AUD5) + SoundManagerSection(obj, 'declare_new_sound', 'fifth_sound', [AUD5'; AUD5']) + end + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); end - end - - SoloParamHandle(obj,'LineGroups','value',linegroups); - SoloParamHandle(obj,'LineNames','value',line_names); - - scr = timer; - set(scr,'Period', 0.2,'ExecutionMode','FixedRate','TasksToExecute',Inf,... - 'BusyMode','drop','TimerFcn',[mfilename,'(''close_continued'')']); - SoloParamHandle(obj, 'stopping_complete_timer', 'value', scr); + + % Get monitor dimensions for dynamic GUI sizing + MP = get(0,'MonitorPositions'); + + % Calculate individual group width based on screen dimensions and number of groups + groupwidth = floor((MP(3)/2)/numel(linegroups)); + + padding = 10 % padding around GUI elements + + % Calculate total width needed for all groups combined + total_width = floor(numel(linegroups) * groupwidth+padding); + total_height = 400 % total height of GUI - SoundCalibration(obj,'prepare_next_trial'); - - dispatcher('Run'); + label_height = 140 % height of port label + button_height = 25 + + % Center the GUI horizontally on screen + left_pos = floor((MP(3) - total_width) / 2); + + % Set figure position with centered alignment and fixed height of 400 pixels + % position vector: [left-right, down-up, width, height], the origo is + % the bottom left corner + set(value(myfig), 'Position', [left_pos, floor((MP(4)-total_height)/2), total_width, total_height]); + + % Initialize array to store line names + line_names = []; + + % Iterate through each line group + for i = 1:numel(linegroups) + % Check if current group exists and has valid parameters + if ~isempty(linegroups{i}) && ~isempty(linegroups{i}{1,2}) + % port label position + SubheaderParam( ... + obj, ... + ['Input',linegroups{i}{1,2}], ... + {linegroups{i}{1,2};0},0,0, ... + 'position',[((i-1)*groupwidth)+padding, ... + total_height-padding-label_height, ... + groupwidth-padding, ... + label_height ... + ]); + + % port label aesthetics + set(get_ghandle(eval(['Input',linegroups{i}{1,2}])), ... + 'FontSize',32, ... % Large font size for visibility + 'BackgroundColor',[0,1,0], ... % Green background + 'HorizontalAlignment','center'); % Center align text + + % Store line name for later reference + line_names(end+1) = linegroups{i}{1,2}; %#ok + + % Add sound controls based on line identifier + if strcmp(linegroups{i}{1,2},'L') && ~isempty(soundserver) + % Left channel sound toggle + ToggleParam(obj, ... + 'LeftSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Two ON', ... + 'OffString', 'Sound Two OFF'); + set_callback(LeftSound,{mfilename,'play_left_sound'}); + + elseif strcmp(linegroups{i}{1,2},'R') && ~isempty(soundserver) + % Right channel sound toggle + ToggleParam(obj, ... + 'RightSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Three ON', ... + 'OffString', 'Sound Three OFF'); + set_callback(RightSound,{mfilename,'play_right_sound'}); + + elseif strcmp(linegroups{i}{1,2},'C') && ~isempty(soundserver) + % Center channel sound toggle + ToggleParam(obj, ... + 'CenterSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound One ON', ... + 'OffString', 'Sound One OFF'); + set_callback(CenterSound,{mfilename,'play_center_sound'}); + + elseif strcmp(linegroups{i}{1,2},'A') && ~isempty(soundserver) + % Additional channel sound toggle + ToggleParam(obj, ... + 'FourthSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Four ON', ... + 'OffString', 'Sound Four OFF'); + set_callback(FourthSound,{mfilename,'play_fourth_sound'}); + end + + % Handle case for empty line identifier but existing group + elseif ~isempty(linegroups{i}) && isempty(linegroups{i}{1,2}) + ToggleParam(obj, ... + 'FifthSound', 0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, padding, groupwidth-padding,button_height], ... + 'OnString', 'Sound Five ON', ... + 'OffString', 'Sound Five OFF'); + set_callback(FifthSound,{mfilename,'play_fifth_sound'}); + end + + % Add first digital I/O control if available + if ~isempty(linegroups{i}) && ~isempty(linegroups{i}{2,2}) + ToggleParam(obj, ... + ['DIO',num2str(i),'_1'],0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, 2*button_height+padding, groupwidth-padding,button_height], ... + 'OnString', [linegroups{i}{2,2},' ON'], ... + 'OffString', [linegroups{i}{2,2},' OFF']); + set_callback(eval(['DIO',num2str(i),'_1']),{mfilename,['toggle',num2str(i),'_1']}); + end + + % Add second digital I/O control if available + if ~isempty(linegroups{i}) && ~isempty(linegroups{i}{3,2}) + ToggleParam(obj, ... + ['DIO',num2str(i),'_2'],0,0,0, ... + 'position',[((i-1)*groupwidth)+padding, button_height+padding, groupwidth-padding,button_height], ... + 'OnString', [linegroups{i}{3,2},' ON'], ... + 'OffString', [linegroups{i}{3,2},' OFF']); + set_callback(eval(['DIO',num2str(i),'_2']),{mfilename,['toggle',num2str(i),'_2']}); + end + end + + + SoloParamHandle(obj,'LineGroups','value',linegroups); + SoloParamHandle(obj,'LineNames','value',line_names); - - case 'play_left_sound' + scr = timer; + set(scr,'Period', 0.2,'ExecutionMode','FixedRate','TasksToExecute',Inf,... + 'BusyMode','drop','TimerFcn',[mfilename,'(''close_continued'')']); + SoloParamHandle(obj, 'stopping_complete_timer', 'value', scr); + + SoundCalibration(obj,'prepare_next_trial'); + + dispatcher('Run'); + + + case 'play_left_sound' %% play_left_sound if value(LeftSound) == 1 SoundManagerSection(obj,'play_sound','left_sound'); else SoundManagerSection(obj,'stop_sound','left_sound'); end - - case 'play_right_sound' + + case 'play_right_sound' %% play_right_sound if value(RightSound) == 1 SoundManagerSection(obj,'play_sound','right_sound'); else SoundManagerSection(obj,'stop_sound','right_sound'); - end - - case 'play_center_sound' + end + + case 'play_center_sound' %% play_center_sound if value(CenterSound) == 1 SoundManagerSection(obj,'play_sound','center_sound'); else SoundManagerSection(obj,'stop_sound','center_sound'); - end + end - case 'play_fourth_sound' + case 'play_fourth_sound' %% play_fourth_sound if value(FourthSound) == 1 SoundManagerSection(obj,'play_sound','fourth_sound'); else SoundManagerSection(obj,'stop_sound','fourth_sound'); - end + end + case 'play_fifth_sound' + %% play_fifth_sound + if value(FifthSound) == 1 + SoundManagerSection(obj,'play_sound','fifth_sound'); + else + SoundManagerSection(obj,'stop_sound','fifth_sound'); + end case 'toggle1_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -322,7 +412,7 @@ %% case toggle1_2 linegroups = value(LineGroups); dispatcher('toggle_bypass',log2(linegroups{1}{3,1})); - + case 'toggle2_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -332,7 +422,7 @@ %% case toggle1_2 linegroups = value(LineGroups); dispatcher('toggle_bypass',log2(linegroups{2}{3,1})); - + case 'toggle3_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -342,7 +432,7 @@ %% case toggle1_2 linegroups = value(LineGroups); dispatcher('toggle_bypass',log2(linegroups{3}{3,1})); - + case 'toggle4_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -352,7 +442,7 @@ %% case toggle1_2 linegroups = value(LineGroups); dispatcher('toggle_bypass',log2(linegroups{4}{3,1})); - + case 'toggle5_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -362,7 +452,7 @@ %% case toggle1_2 linegroups = value(LineGroups); dispatcher('toggle_bypass',log2(linegroups{5}{3,1})); - + case 'toggle6_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -372,7 +462,7 @@ %% case toggle1_2 linegroups = value(LineGroups); dispatcher('toggle_bypass',log2(linegroups{6}{3,1})); - + case 'toggle7_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -382,7 +472,7 @@ %% case toggle1_2 linegroups = value(LineGroups); dispatcher('toggle_bypass',log2(linegroups{7}{3,1})); - + case 'toggle8_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -391,8 +481,8 @@ case 'toggle8_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{8}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{8}{3,1})); + case 'toggle9_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -401,8 +491,8 @@ case 'toggle9_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{9}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{9}{3,1})); + case 'toggle10_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -411,8 +501,8 @@ case 'toggle10_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{10}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{10}{3,1})); + case 'toggle11_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -421,8 +511,8 @@ case 'toggle11_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{11}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{11}{3,1})); + case 'toggle12_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -431,8 +521,8 @@ case 'toggle12_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{12}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{12}{3,1})); + case 'toggle13_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -441,8 +531,8 @@ case 'toggle13_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{13}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{13}{3,1})); + case 'toggle14_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -451,8 +541,8 @@ case 'toggle14_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{14}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{14}{3,1})); + case 'toggle15_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -461,8 +551,8 @@ case 'toggle15_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{15}{3,1})); - + dispatcher('toggle_bypass',log2(linegroups{15}{3,1})); + case 'toggle16_1' %% case toggle1_1 linegroups = value(LineGroups); @@ -471,70 +561,70 @@ case 'toggle16_2' %% case toggle1_2 linegroups = value(LineGroups); - dispatcher('toggle_bypass',log2(linegroups{16}{3,1})); - - %--------------------------------------------------------------- - % CASE PREPARE_NEXT_TRIAL - %--------------------------------------------------------------- - case 'prepare_next_trial' - line_names = value(LineNames); - sma = StateMachineAssembler('full_trial_structure','use_happenings',1,'n_input_lines',numel(line_names),'line_names',line_names); - sma = add_state(sma, 'name', 'the_only_state', 'self_timer',1e4, 'input_to_statechange', {'Tup', 'final_state'}); - sma = add_state(sma, 'name', 'final_state', 'self_timer',1e4, 'input_to_statechange', {'Tup', 'check_next_trial_ready'}); - dispatcher('send_assembler', sma, 'final_state'); - - %--------------------------------------------------------------- - % CASE TRIAL_COMPLETED - %--------------------------------------------------------------- - case 'trial_completed' - - - %--------------------------------------------------------------- - % CASE UPDATE - %--------------------------------------------------------------- - case 'update' - pe = parsed_events; %#ok - linenames = value(LineNames); - - for i = 1:numel(linenames) - poketimes = eval(['pe.pokes.',linenames(i)]); - if ~isempty(poketimes) && isnan(poketimes(end,2)) - set(get_ghandle(eval(['Input',linenames(i)])),'BackgroundColor',[1,0,0]); - - str = get(get_ghandle(eval(['Input',linenames(i)])),'string'); - str{2} = size(poketimes,1); - set(get_ghandle(eval(['Input',linenames(i)])),'string',str); - - else - set(get_ghandle(eval(['Input',linenames(i)])),'BackgroundColor',[0,1,0]); - end - end - - %--------------------------------------------------------------- - % CASE CLOSE - %--------------------------------------------------------------- - case 'close' - - dispatcher('Stop'); - - %Let's pause until we know dispatcher is done running - set(value(stopping_complete_timer),'TimerFcn',[mfilename,'(''close_continued'');']); - start(value(stopping_complete_timer)); - - case 'close_continued' - - if value(stopping_process_completed) - stop(value(stopping_complete_timer)); %Stop looping. - %dispatcher('set_protocol',''); - - if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)), %#ok - delete(value(myfig)); - end; - delete_sphandle('owner', ['^@' class(obj) '$']); - dispatcher('set_protocol',''); - end - otherwise, - warning('Unknown action! "%s"\n', action); %#ok + dispatcher('toggle_bypass',log2(linegroups{16}{3,1})); + + %--------------------------------------------------------------- + % CASE PREPARE_NEXT_TRIAL + %--------------------------------------------------------------- + case 'prepare_next_trial' + line_names = value(LineNames); + sma = StateMachineAssembler('full_trial_structure','use_happenings',1,'n_input_lines',numel(line_names),'line_names',line_names); + sma = add_state(sma, 'name', 'the_only_state', 'self_timer',1e4, 'input_to_statechange', {'Tup', 'final_state'}); + sma = add_state(sma, 'name', 'final_state', 'self_timer',1e4, 'input_to_statechange', {'Tup', 'check_next_trial_ready'}); + dispatcher('send_assembler', sma, 'final_state'); + + %--------------------------------------------------------------- + % CASE TRIAL_COMPLETED + %--------------------------------------------------------------- + case 'trial_completed' + + + %--------------------------------------------------------------- + % CASE UPDATE + %--------------------------------------------------------------- + case 'update' + pe = parsed_events; %#ok + linenames = value(LineNames); + + for i = 1:numel(linenames) + poketimes = eval(['pe.pokes.',linenames(i)]); + if ~isempty(poketimes) && isnan(poketimes(end,2)) + set(get_ghandle(eval(['Input',linenames(i)])),'BackgroundColor',[1,0,0]); + + str = get(get_ghandle(eval(['Input',linenames(i)])),'string'); + str{2} = size(poketimes,1); + set(get_ghandle(eval(['Input',linenames(i)])),'string',str); + + else + set(get_ghandle(eval(['Input',linenames(i)])),'BackgroundColor',[0,1,0]); + end + end + + %--------------------------------------------------------------- + % CASE CLOSE + %--------------------------------------------------------------- + case 'close' + + dispatcher('Stop'); + + %Let's pause until we know dispatcher is done running + set(value(stopping_complete_timer),'TimerFcn',[mfilename,'(''close_continued'');']); + start(value(stopping_complete_timer)); + + case 'close_continued' + + if value(stopping_process_completed) + stop(value(stopping_complete_timer)); %Stop looping. + %dispatcher('set_protocol',''); + + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)), %#ok + delete(value(myfig)); + end; + delete_sphandle('owner', ['^@' class(obj) '$']); + dispatcher('set_protocol',''); + end + otherwise, + warning('Unknown action! "%s"\n', action); %#ok end; return; diff --git a/Protocols/@SoundCatContinuous/AntibiasSectionAthena.m b/Protocols/@SoundCatContinuous/AntibiasSectionAthena.m new file mode 100644 index 00000000..cb34d14a --- /dev/null +++ b/Protocols/@SoundCatContinuous/AntibiasSectionAthena.m @@ -0,0 +1,315 @@ +% [x, y] = AntibiasSection(obj, action, [arg1], [arg2], [arg3]) +% +% Section that calculates biases and calculates probability of choosing a stimulus +% given the previous history. +% +% Antibias assumes that trials are of two classes, Left desired answer +% and Right desired answer, and that their outcome is either Correct or +% Incorrect. Given the history of previous trial classes, and the history +% of previous corrects/incorrects, Antibias makes a local estimate of +% fraction correct for each class, combines that with a prior probability +% of making the next trial Left, and produces a recommended probability +% for choosing the next trial as Left. Antibias will tend to make the +% class with the smaller frac correct the one with the higher probability. +% The strength of that tendency is quantified by a parameter, beta. +% (See probabilistic_trial_selector.m for details on the math of how the +% tendency is generated.) +% +% Local estimates of fraction correct are computed using an exponential +% kernel, most recent trial the most strongly weighted. The tau of this +% kernel is a GUI parameter. Two different estimates are computed: one for +% use in computing Left probability and Right probability; and a second +% simply for GUI display purposes. The two estimates can have different +% taus for their kernels. +% +% GUI DISPLAY: When initialized, this plugin will put up two panels and a +% title. In each panel, there is a slider that controls the tau of the +% recent-trials exponential kernel. One panel will display the percent +% corrects for Right and Left, as computed with its kernel. The second panel +% will display the a posteriori probabilities of making the next trial a +% "Left" trial or making the next trial a "Right" trial. This second panel +% has its own tau slider, and it also has a GUI parameter, beta, that +% controls how strongly the history matters. If beta=0, history doesn't +% matter, and the a priori LeftProbability dominates. If beta=Inf, then +% history matters above all: the next trial will be of the type with lowest +% fraction correct, for sure. +% +% See the bottom of this help file for examples of usage. +% +% arg1, arg2, arg3 are optional and their meaning depends on action (see +% below). +% +% PARAMETERS: +% ----------- +% +% obj Default object argument. +% +% action One of: +% +% 'init' To initialise the plugin and set up the GUI for it. This +% action requires two more arguments: The bottom left +% x y position (in units of pixels) of where to start placing +% the GUI elements of this plugin. +% +% 'update' This call will recompute both local estimates of +% fraction correct, and will recompute the recommended +% LeftProb, p(Left). This action requires three more arguments: +% HitHist, LProb, a scalar b/w 0 and 1; HitHist, a vector of 1s +% SidesHist and 0s and of length n_done_trials where 1 represents +% correct and 0 represents incorrect, first element +% corresponds to first trial; and SidesHist, a vector of +% 'l's and 'r's and of length n_done_trials where 'l' +% represents 'left', 'r' represents 'right' first element +% corresponds to first trial. +% +% 'get_posterior_probs' Returns a vector with two components, +% [p(Left) p(Right)]. +% +% +% 'update_biashitfrac' This call will recompute the local estimate of fraction +% Left correct and fraction Right correct used for +% LeftProb, antibiasing, and will also recompute the recommended +% HitHist, Left probability. This action +% SidesHist, requires three more arguments: LProb, a scalar b/w 0 +% and 1; HitHist, a vector of 1s and 0s and of length +% n_done_trials where 1 represents correct and 0 +% represents incorrect, first element corresponds to +% first trial; and SidesHist, a vector of 'l's and 'r's +% and of length n_done_trials where 'l' represents +% 'left', 'r' represents 'right' first element +% corresponds to first trial. +% +% 'update_hitfrac' This call is not related to computing the posterior +% Left probability, but will recompute only the local estimate +% HitHist, of fraction correct that is not used for antibiasing. +% SidesHist This action requires two more arguments: HitHist, a +% vector of 1s and 0s and of length n_done_trials where 1 +% represents correct and 0 represents incorrect, first +% element corresponds to first trial; and SidesHist, a vector of +% 'l's and 'r's and of length n_done_trials where 'l' +% represents 'left', 'r' represents 'right' first element +% corresponds to first trial. +% +% 'get' Needs one extra parameter, either 'Beta' or +% 'antibias_tau', and returns the corresponding scalar. +% +% 'reinit' Delete all of this section's GUIs and data, +% and reinit, at the same position on the same +% figure as the original section GUI was placed. +% +% +% x, y Relevant to action = 'init'; they indicate the initial +% position to place the GUI at, in the current figure window +% +% RETURNS: +% -------- +% +% if action == 'init' : +% +% [x1, y1, w, h] When action == 'init', Antibias will put up GUIs and take +% up a certain amount of space of the figure that was current when +% AntiBiasSection(obj, 'init', x, y) was called. On return, [x1 y1] +% will be the top left corner of the space used; [x y] (as passed +% to Antibias in the init call) will be the bottom left corner; +% [x+w y1] will be the top right; and [x+w y] will be the bottom +% right. h = y1-y. All these are in units of pixels. +% +% +% if action == 'get_posterior_probs' : +% +% [L R] When action == 'get_posterior_probs', a two-component vector is +% returned, with p(Left) and p(Right). If beta=0, then p(Left) +% will be the same as the last LeftProb that was passed in. +% +% +% USAGE: +% ------ +% +% To use this plugin, the typical calls would be: +% +% 'init' : On initializing your protocol, call +% AntibiasSection(obj, 'init', x, y); +% +% 'update' : After a trial is completed, call +% AntibiasSection(obj, 'update', LeftProb, HitHist, SidesHist) +% +% 'get_posterior_probs' : After a trial is completed, and when you are +% deciding what kind of trial to make the next trial, get the plugins +% opinion on whether the next trial should be Left or Right by calling +% AntibiasSection(obj, 'get_posterior_probs') +% +% See PARAMETERS section above for the documentation of each of these calls. +% + + +function [x, y, w, h] = AntibiasSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action + + case 'init', % ------------ CASE INIT ---------------- + x = varargin{1}; y = varargin{2}; y0 = y; + % Save the figure and the position in the figure where we are + % going to start adding GUI elements: + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)]); + + LogsliderParam(obj, 'HitFracTau', 30, 10, 400, x, y, 'label', 'hits frac tau', ... + 'TooltipString', ... + sprintf(['\nnumber of trials back over which to compute fraction of correct trials.\n' ... + 'This is just for displaying info-- for the bias calculation, see BiasTau above'])); + set_callback(HitFracTau, {mfilename, 'update_hitfrac'}); + next_row(y); + DispParam(obj, 'LtHitFrac', 0, x, y); next_row(y); + DispParam(obj, 'RtHitFrac', 0, x, y); next_row(y); + DispParam(obj, 'HitFrac', 0, x, y); next_row(y); + + next_row(y, 0.5); + LogsliderParam(obj, 'BiasTau', 30, 10, 400, x, y, 'label', 'antibias tau', ... + 'TooltipString', ... + sprintf(['\nnumber of trials back over\nwhich to compute fraction of correct trials\n' ... + 'for the antibias function.'])); next_row(y); + NumeditParam(obj, 'Beta', 0, x, y, ... + 'TooltipString', ... + sprintf(['When this is 0, past performance doesn''t affect choice\n' ... + 'of next trial. When this is large, the next trial is ' ... + 'almost guaranteed\nto be the one with smallest %% correct'])); next_row(y); + set_callback({BiasTau, Beta}, {mfilename, 'update_biashitfrac'}); + DispParam(obj, 'LtProb', 0, x, y); next_row(y); + DispParam(obj, 'RtProb', 0, x, y); next_row(y); + SoloParamHandle(obj, 'BiasLtHitFrac', 'value', 0); + SoloParamHandle(obj, 'BiasRtHitFrac', 'value', 0); + + SoloParamHandle(obj, 'LocalLeftProb', 'value', 0.5); + SoloParamHandle(obj, 'LocalHitHistory', 'value', []); + SoloParamHandle(obj, 'LocalPrevSides', 'value', []); + + + SubheaderParam(obj, 'title', mfilename, x, y); + next_row(y, 1.5); + + w = gui_position('get_width'); + h = y-y0; + + + case 'update', % --- CASE UPDATE ------------------- + if length(varargin)>0, LocalLeftProb.value = varargin{1}; end; + if length(varargin)>1, LocalHitHistory.value = varargin{2}; end; + if length(varargin)>2, LocalPrevSides.value = varargin{3}; end; + % Protect against somebody passing in SPHs, not actual values, by mistake: + if isa(value(LocalLeftProb), 'SoloParamHandle'), LocalLeftProb.value = value(value(LocalLeftProb)); end; + if isa(value(LocalHitHistory), 'SoloParamHandle'), LocalHitHistory.value = value(value(LocalHitHistory)); end; + if isa(value(LocalPrevSides), 'SoloParamHandle'), LocalPrevSides.value = value(value(LocalPrevSides)); end; + + feval(mfilename, obj, 'update_hitfrac'); + feval(mfilename, obj, 'update_biashitfrac'); + + + + case 'update_biashitfrac', % ------- CASE UPDATE_BIASHITFRAC ------------- + if length(varargin)>0, LocalLeftProb.value = varargin{1}; end; + if length(varargin)>1, LocalHitHistory.value = varargin{2}; end; + if length(varargin)>2, LocalPrevSides.value = varargin{3}; end; + % Protect against somebody passing in SPHs, not actual values, by mistake: + if isa(value(LocalLeftProb), 'SoloParamHandle'), LocalLeftProb.value = value(value(LocalLeftProb)); end; + if isa(value(LocalHitHistory), 'SoloParamHandle'), LocalHitHistory.value = value(value(LocalHitHistory)); end; + if isa(value(LocalPrevSides), 'SoloParamHandle'), LocalPrevSides.value = value(value(LocalPrevSides)); end; + + LeftProb = value(LocalLeftProb); + hit_history = value(LocalHitHistory); hit_history = colvec(hit_history); + previous_sides = value(LocalPrevSides); + + kernel = exp(-(0:length(hit_history)-1)/BiasTau)'; + kernel = kernel(end:-1:1); + + prevs = previous_sides(1:length(hit_history))'; + ul = find(prevs == 'l'); + if isempty(ul), BiasLtHitFrac.value = 1; + else BiasLtHitFrac.value = sum(hit_history(ul) .* kernel(ul))/sum(kernel(ul)); + end; + + ur = find(prevs == 'r'); + if isempty(ur), BiasRtHitFrac.value = 1; + else BiasRtHitFrac.value = sum(hit_history(ur) .* kernel(ur))/sum(kernel(ur)); + end; + + if isempty(ul) && ~isempty(ur), BiasLtHitFrac.value = value(BiasRtHitFrac); end; + if isempty(ur) && ~isempty(ul), BiasRtHitFrac.value = value(BiasLtHitFrac); end; + + choices = probabilistic_trial_selector([value(BiasLtHitFrac), value(BiasRtHitFrac)], ... + [LeftProb, 1-LeftProb], value(Beta)); + LtProb.value = choices(1); + RtProb.value = choices(2); + + + case 'get_posterior_probs', % ------- CASE GET_POSTERIOR_PROBS ------------- + x = [value(LtProb) ; value(RtProb)]; %#ok + + + case 'update_hitfrac', % ------- CASE UPDATE_HITFRAC ------------- + if length(varargin)>0, LocalHitHistory.value = varargin{1}; end; + if length(varargin)>1, LocalPrevSides.value = varargin{2}; end; + % Protect against somebody passing in SPHs, not actual values, by mistake: + if isa(value(LocalHitHistory), 'SoloParamHandle'), LocalHitHistory.value = value(value(LocalHitHistory)); end; + if isa(value(LocalPrevSides), 'SoloParamHandle'), LocalPrevSides.value = value(value(LocalPrevSides)); end; + + hit_history = value(LocalHitHistory); hit_history = colvec(hit_history); + previous_sides = value(LocalPrevSides); + + + if length(hit_history)>0, + kernel = exp(-(0:length(hit_history)-1)/HitFracTau)'; + kernel = kernel(end:-1:1); + HitFrac.value = sum(hit_history .* kernel)/sum(kernel); + + prevs = previous_sides(1:length(hit_history))'; + u = find(prevs == 'l'); + if isempty(u), LtHitFrac.value = NaN; + else LtHitFrac.value = sum(hit_history(u) .* kernel(u))/sum(kernel(u)); + end; + + u = find(prevs == 'r'); + if isempty(u), RtHitFrac.value = NaN; + else RtHitFrac.value = sum(hit_history(u) .* kernel(u))/sum(kernel(u)); + end; + end; + + + case 'get', % ------- CASE GET ------------- + if length(varargin)~=1, + error('AntibiasSection:Invalid', '''get'' needs one extra param'); + end; + switch varargin{1}, + case 'Beta', + x = value(Beta); + case 'antibias_tau', + x = value(BiasTau); + otherwise + error('AntibiasSection:Invalid', 'Don''t know how to get %s', varargin{1}); + end; + + + case 'reinit', % ------- CASE REINIT ------------- + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); +end; + + + + +function [x] = colvec(x) + if size(x,2) > size(x,1), x = x'; end; + \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/AthenaSMA_aa.m b/Protocols/@SoundCatContinuous/AthenaSMA_aa.m new file mode 100644 index 00000000..4e6f72e3 --- /dev/null +++ b/Protocols/@SoundCatContinuous/AthenaSMA_aa.m @@ -0,0 +1,392 @@ + +function [varargout] = AthenaSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + srate=SoundManagerSection(obj,'get_sample_rate'); + freq1=5; + dur1=1.5*1000; + Vol=1; + tw=Vol*(MakeBupperSwoop(srate,0, freq1 , freq1 , dur1/2 , dur1/2,0,0.1)); + SoundManagerSection(obj, 'declare_new_sound', 'LRewardSound', [tw ; zeros(1, length(tw))]) + SoundManagerSection(obj, 'declare_new_sound', 'RRewardSound', [zeros(1, length(tw));tw]) + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + case 'prepare_next_trial', + + %% Setup water + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); + stwo_sound_id = SoundManagerSection(obj, 'get_sound_id', 'STwoSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + RLreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RewardSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + Lreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'LRewardSound'); + Rreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RRewardSound'); + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); + A2_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD2'); + + %% Declare variables + % These will get moved to other functions as SoloParamHandles. + + [LeftWMult RightWMult] = SideSection(obj, 'get_water_mult'); + [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + LeftWValveTime=LeftWValveTime*LeftWMult; + RightWValveTime=RightWValveTime*RightWMult; + + + side = SideSection(obj, 'get_current_side'); + if side == 'l' + HitEvent = 'Lin'; HitState = 'LeftHit'; WaterTime = LeftWValveTime; WaterValve = left1water; SideLight = left1led; + RightWValveTime=0; correct_response='Lin'; error_response='Rin'; reward_sound_id=Lreward_sound_id; first_wrong='Rin'; + + else + HitEvent = 'Rin'; HitState = 'RightHit'; WaterTime = RightWValveTime; WaterValve = right1water; SideLight = right1led; + LeftWValveTime=0; correct_response='Rin'; error_response='Lin'; reward_sound_id=Rreward_sound_id; first_wrong='Lin'; + end; + +% +% maxasymp=38; +% slp=3; +% inflp=300; +% minasymp=-20; +% assym=0.7; +% WaterTime=maxasymp + (minasymp./(1+(n_done_trials/inflp).^slp).^assym); + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', WaterTime, 'DOut', WaterValve); + sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); + + + sma = add_scheduled_wave(sma, 'name', 'stimA1', 'preamble', PreStim_time, ... + 'sustain', A1_time, 'sound_trig', A1_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'stimA2', 'preamble', PreStim_time+A1_time+Del_time, ... + 'sustain', A2_time,'sound_trig', A2_sound_id); + + switch value(training_stage) + + case 0 %% learning the reward sound association -left or right led on -> poke -> sound+reward + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',first_wrong,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + + case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + + % nose is out and we're in "SettlingIn_time": + % if settling_legal_cbreak time elapses, go to violation state, + % if nose is put back in, go to copy of cp start + % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks + sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'settling_period_In', 'cp_legal_cbreak_period', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'settling_period_In','cp_legal_cbreak_period', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks + sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state+1', ... + 'Clo', 'current_state+1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % nose is out and we're still in legal_cbreak: + % if legal_cbreak time elapses, go to violation_state, + % if nose is put back in, go to copy of cp start + sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',first_wrong,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + case 2 % like stage 1 - now passive exposure to the stimuli - reward comes anyway + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period+stimA1+stimA2'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + + % nose is out and we're in "SettlingIn_time": + % if settling_legal_cbreak time elapses, go to violation state, + % if nose is put back in, go to copy of cp start + % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks + sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'settling_period_In', 'cp_legal_cbreak_period', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'settling_period_In','cp_legal_cbreak_period', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks + sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state+1', ... + 'Clo', 'current_state+1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % nose is out and we're still in legal_cbreak: + % if legal_cbreak time elapses, go to violation_state, + % if nose is put back in, go to copy of cp start + sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'center_poke_Out', 'sideled_on', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',first_wrong,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + case 3 % like stage 2, + + + case 4 %% now reward comes only if rat goes to the correct side + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{correct_response, 'hit_state'; error_response,'error_state';... + 'Tup','timeout_state'}); + + sma = add_state(sma,'name','error_state','self_timer',error_iti,... + 'output_actions',{'SoundOut',err_sound_id},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','hit_state','self_timer',WaterTime+0.1,... + 'output_actions',{'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + end %end of swith for different training_stages + + + sma = add_state(sma,'name','drink_state','self_timer',drink_time,... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke-stimA1-stimA2', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','timeout_state','self_timer', timeout_duration,... + 'output_actions',{'SoundOut',to_sound_id},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'hit_state','second_hit_state', 'error_state', 'violation_state','timeout_state'}; + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors', + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_time', [0 1 0], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + + + case 'reinit', + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/DelayComp.m b/Protocols/@SoundCatContinuous/DelayComp.m new file mode 100644 index 00000000..e4dd2ffe --- /dev/null +++ b/Protocols/@SoundCatContinuous/DelayComp.m @@ -0,0 +1,62 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% ------------ STAGE SEPARATOR ------- (do not edit this line) +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% --- STAGE NAME: --- (do not edit this line) +Growing nose in center time + +% --- VAR NAMES: --- (do not edit this line) +% Maximum duration of center poke, in secs: +cp_max 5 forceinit=1 +% Maximum value of CP_duration reached during the last session +cp_pre 2 +% Fractional increment in center poke duration every time there is a non-cp-violation trial: +cp_fraction 0.002 +% Minimum increment (in secs) in center poke duration every time there is a non-cp-violation trial: +cp_minimum_increment 0.001 + +% Minimum CP_duration at which a settling_in legal_cbreak is different to +% the regular legal_cbreak: +start_settling_in_at 0.5 +% Once we've reached CP_duration > start_settling_in_at, the parameters of +% the settling in: +settling_in_time 0.25 +settling_in_legal_cbreak 0.2 + +% --- TRAINING STRING: --- (do not edit this line) +if n_done_trials ==1 + cp_pre.value=value(SideSection_CP_duration); + SideSection_CP_duration.value = 0.5; +elseif n_done_trials > 1 && n_done_trials <20 ... + && ~violation_history(end) && SideSection_CP_duration < cp_max + slope = (value(cp_pre)-0.5)/19; + SideSection_CP_duration.value = n_done_trials*slope + 0.5; + end +elseif n_done_trials>20 + if ~violation_history(end) && SideSection_CP_duration < cp_max, + increment = SideSection_CP_duration*cp_fraction; + if increment < cp_minimum_increment, + increment = value(cp_minimum_increment); + end; + SideSection_CP_duration.value = SideSection_CP_duration + increment; + end; + + if SideSection_CP_duration >= start_settling_in_at + SideSection_SettlingIn_time.value = value(settling_in_time); + SideSection_settling_legal_cbreak.value = value(settling_in_legal_cbreak); + else + SideSection_SettlingIn_time.value = 0; + SideSection_settling_legal_cbreak.value = value(SideSection_legal_cbreak); + end; +end + +% --- END-OF-DAY LOGIC: -- (do not edit this line) + + + +% --- COMPLETION STRING: --- (do not edit this line) +0 + + diff --git a/Protocols/@SoundCatContinuous/DelayComp1.m b/Protocols/@SoundCatContinuous/DelayComp1.m new file mode 100644 index 00000000..6ad3acd2 --- /dev/null +++ b/Protocols/@SoundCatContinuous/DelayComp1.m @@ -0,0 +1,124 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% ------------ STAGE SEPARATOR ------- (do not edit this line) +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% --- STAGE NAME: --- (do not edit this line) +Growing nose in center time + +% --- VAR NAMES: --- (do not edit this line) +% Maximum duration of center poke (including Go cue), in secs: +max_total_cp 6.2 forceinit=1 +% Standard Go cue duration +target_go_cue_duration 0.2 +% CP_duration reached at the end of the last session +last_session_total_cp 0 +% Fractional increment in center poke duration every time there is a non-cp-violation trial: +cp_fraction 0.001 +% Minimum increment (in secs) in center poke duration every time there is a non-cp-violation trial: +cp_minimum_increment 0.001 + + +% Initial startup trials over which to gradually grow cp duration +% Only do the initial startup stuff if cp_duration is longer than: +cp_duration_threshold_for_initial_trials 1.5 +% Number of initial trials over which to gradually grow cp duration: +n_initial_trials 10 +% Starting total center poke duration: +starting_total_cp 0.5 + + +% Minimum CP_duration at which a settling_in legal_cbreak is different to +% the regular legal_cbreak: +start_settling_in_at 1000 +% Once we've reached CP_duration > start_settling_in_at, the parameters of +% the settling in: +settling_in_time 0 +settling_in_legal_cbreak 0.05 + + + + +% --- TRAINING STRING: --- (do not edit this line) + +% Check to see whether we're doing the initial startup stuff. Assumes that +% we're NOT changing the Go cue duration. +if SideSection_Total_CP_duration+0.0001 < last_session_total_cp & ... + last_session_total_cp > cp_duration_threshold_for_initial_trials, + % Check to see whether we're done with initial stuff within numerical + % rounding error: + if abs(last_session_total_cp - SideSection_Total_CP_duration) < 0.0001, + SideSection_CP_duration.value = last_session_total_cp - SideSection_time_go_cue; + elseif ~violation_history(end), + increment = ... + (last_session_total_cp - starting_total_cp)/(n_initial_trials - 1); + SideSection_CP_duration.value = SideSection_CP_duration + increment; + end; +elseif ~violation_history(end) && SideSection_Total_CP_duration < max_total_cp, + % We're in regular increasing territory + increment = SideSection_Total_CP_duration*cp_fraction; + if increment < cp_minimum_increment, + increment = value(cp_minimum_increment); + end; + % If we're growing the CP duration, grow the Go cue duration first + % until it reaches its target; after that, grow CP_duration, the + % pre-Go cue time. + if SideSection_time_go_cue < target_go_cue_duration, + SideSection_time_go_cue.value = SideSection_time_go_cue + increment; + else + SideSection_CP_duration.value = SideSection_CP_duration + increment; + end; +end; +% make sure the total reflects all the changes: +callback(SideSection_CP_duration); + +% Double-check that we don't go over the desired max value: +if SideSection_Total_CP_duration > max_total_cp, + SideSection_CP_duration.value = max_total_cp - SideSection_time_go_cue; + % once again, make sure the total reflects any the changes: + callback(SideSection_CP_duration); +end; + +% Settling in code: +if SideSection_CP_duration >= start_settling_in_at + SideSection_SettlingIn_time.value = value(settling_in_time); + SideSection_settling_legal_cbreak.value = value(settling_in_legal_cbreak); +else + SideSection_SettlingIn_time.value = 0; + SideSection_settling_legal_cbreak.value = value(SideSection_legal_cbreak); +end; + + + + +% --- END-OF-DAY LOGIC: -- (do not edit this line) + +% Store the value of the total cp duration reached: +last_session_total_cp.value = value(SideSection_Total_CP_duration); + +% Check whether we're going to do the initial startup trials on the next +% day: +if SideSection_Total_CP_duration > cp_duration_threshold_for_initial_trials, + % Yup, doing initial startup. Set CP_duration to the necessary duration: + SideSection_CP_duration.value = starting_total_cp - SideSection_time_go_cue; + % Error check, make sure we don't set it to something nonsensical: + if SideSection_CP_duration < 0.001, + SideSection_CP_duration = 0.001; + end; + % Callback to make sure the calculation of Total_CP_duration is made. + callback(SideSection_CP_duration); +end; + + + + + + +% --- COMPLETION STRING: --- (do not edit this line) +0 + + + + + diff --git a/Protocols/@SoundCatContinuous/LOGplotPairs.m b/Protocols/@SoundCatContinuous/LOGplotPairs.m new file mode 100644 index 00000000..275f736e --- /dev/null +++ b/Protocols/@SoundCatContinuous/LOGplotPairs.m @@ -0,0 +1,71 @@ +function LOGplotPairs(x,y,marker,markersize,markeredgecolor,thislinewidth,FONTSIZE) + + +% delete(gca) +% load('NEWHOT','HOTETOBOKHORAM') + +% position = [16 124 1019 761]; +% % +% figure('Position',position); + +% if nargin <8 +% LogOrLin='linear'; +% end + +if nargin <4 + marker='.'; +end + + +% Plot the points +hold on +x=log(x); +y=log(y); +for i=1:length(x) + +% loglog(x(i),y(i),marker,'color',map(in,:),'markerfacecolor',map(in,:),'MarkerSize',markersize) +% plot3(x(i),y(i),v(i),marker,'MarkerSize',markersize,'MarkerEdgeColor',markeredgecolor,'LineWidth',thislinewidth) + plot(x(i),y(i),marker,'MarkerSize',markersize,'MarkerEdgeColor',markeredgecolor,'LineWidth',thislinewidth) + +end + +xlim([min([x ;y])-min([x ;y])/2 max([x ;y])+min([x ;y])/2]) +ylim([min([x; y])-min([x ;y])/2 max([x; y])+-min([x ;y])/2]) +hold off + +% figure('Position',[1 scrsz(4)/2 scrsz(3)/2 scrsz(4)/2]) + +Ylabel('log_e \sigma_2','FontSize',FONTSIZE,'FontName','Cambria Math'); +set(double(gca),'Fontsize',15) +Xlabel('log_e \sigma_1','FontSize',FONTSIZE,'FontName','Cambria Math') + +% grid on +setyticklabels=1 + +if setyticklabels==1 + +Ytick=get(double(gca),'YtickLabel'); +Xtick=get(double(gca),'XtickLabel'); +% +% Ytick=num2str((3:0.5:6)'); +% Xtick=num2str((3:0.5:6)'); + +% set(gca,'ytick',[],'xtick',[]); +end +% +% axis square +% HUMANORRAT=2 +% if HUMANORRAT==2 +% ylim([2.5 6]) +% xlim([2.2 6.3]) +% else +% ylim([3.5 6]) +% xlim([3.5 6.5]) +% end +if setyticklabels==1 +set(double(gca),'ytick',str2num(Ytick),'xtick',str2num(Xtick)); +set(double(gca),'yticklabel',num2str(round(round(exp(str2num(Ytick)).*100)./100)),'xticklabel',num2str(round(round(exp(str2num(Xtick)).*100)./100))); +end + +% set(gca,'yticklabel',num2str(round(exp(str2num(Ytick)).*100)./100),'xticklabel',num2str(round(exp(str2num(Xtick)).*100)./100)); +view(2) diff --git a/Protocols/@SoundCatContinuous/OverallPerformanceSection.m b/Protocols/@SoundCatContinuous/OverallPerformanceSection.m new file mode 100644 index 00000000..b377fc98 --- /dev/null +++ b/Protocols/@SoundCatContinuous/OverallPerformanceSection.m @@ -0,0 +1,127 @@ +% [x, y] = OverallPerformanceSection(obj, action, x,y) +% +% Reports overall performance. Uses training_stage from SideSection. +% +% PARAMETERS: +% ----------- +% +% action One of: +% 'init' To initialise the section and set up the GUI +% for it +% +% 'close' Delete all of this section's GUIs and data +% +% 'reinit' Delete all of this section's GUIs and data, +% and reinit, at the same position on the same +% figure as the original section GUI was placed. +% +% 'evalueta' Look at history and compute hit fraction, etc. +% +% x, y Only relevant to action = 'init'; they indicate the initial +% position to place the GUI at, in the current figure window +% +% RETURNS: +% -------- +% +% perf When action == 'init', returns x and y, pixel positions on +% the current figure, updated after placing of this section's GUI. +% When action == 'evaluate', returns a vector with elements +% [ntrials, violation_rate, left_hit_frac, right_hit_frac, hit_frac] +% +% + +% CDB, 23-March-2013 + +function [x, y] = OverallPerformanceSection(obj, action, x,y) + +GetSoloFunctionArgs(obj); + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + + DispParam(obj, 'ntrials', 0, x, y); next_row(y); + DispParam(obj, 'violation_rate', 0, x, y, 'TooltipString', ... + 'Fraction of trials with a center poke violation'); next_row(y); + DispParam(obj, 'timeout_rate', 0, x, y, 'TooltipString', ... + 'Fraction of trials with timeout'); next_row(y); + DispParam(obj, 'Left_hit_frac', 0, x, y, 'TooltipString', ... + 'Fraction of correct Left trials'); next_row(y); + DispParam(obj, 'Right_hit_frac', 0, x, y, 'TooltipString', ... + 'Fraction of correct Right trials'); next_row(y); + DispParam(obj, 'hit_frac', 0, x, y, 'TooltipString', ... + 'Fraction of correct trials'); next_row(y); + + SubheaderParam(obj, 'title', 'Overall Performance', x, y); + next_row(y, 1.5); + SoloParamHandle(obj, 'previous_parameters', 'value', []); + + % ------------------------------------------------------------------ + % evaluate + % ------------------------------------------------------------------ + + case 'evaluate' + + switch value(training_stage) + case 1, %% center led on -> poke in the center -> go cue -> reward light and sound + if n_done_trials > 1, + ntrials.value = n_done_trials; + %violation_rate.value = numel(find(isnan(hit_history)))/n_done_trials; + violation_rate.value = numel(find(violation_history))/n_done_trials; + timeout_rate.value = numel(find(timeout_history))/n_done_trials; + goods = ~isnan(hit_history)'; + lefts = previous_sides(1:n_done_trials)=='l'; + rights = previous_sides(1:n_done_trials)=='r'; + Left_hit_frac.value = mean(hit_history(goods & lefts)); + Right_hit_frac.value = mean(hit_history(goods & rights)); + hit_frac.value = mean(hit_history(goods)); + end; + end + + if nargout > 0, + x = [n_done_trials, value(violation_rate), value(timeout_rate), value(Left_hit_frac), ... + value(Right_hit_frac), value(hit_frac)]; + end; + + + % ------------------------------------------------------------------ + % close + % ------------------------------------------------------------------ + + case 'close', + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % ------------------------------------------------------------------ + % reinit + % ------------------------------------------------------------------ + + case 'reinit', + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + +end + + diff --git a/Protocols/@SoundCatContinuous/PlayStimuli.m b/Protocols/@SoundCatContinuous/PlayStimuli.m new file mode 100644 index 00000000..52ca7c8f --- /dev/null +++ b/Protocols/@SoundCatContinuous/PlayStimuli.m @@ -0,0 +1,87 @@ + +function [x, y] = PlayStimuli(obj, action, varargin) +GetSoloFunctionArgs(obj); + +switch action, + + case 'init' + + if length(varargin) < 2, + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end; + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'StimuliPlayShow', 0, x, y, 'OnString', 'StimToPlay', ... + 'OffString', 'StimToPlay', 'TooltipString', 'Show/Hide Sounds panel'); + set_callback(StimuliPlayShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + oldx=x; oldy=y; parentfig=double(gcf); + + SoloParamHandle(obj, 'myfig', 'value', figure('Position', [100 100 560 440], ... + 'closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', mfilename), 'saveable', 0); + set(double(gcf), 'Visible', 'off'); + x=10;y=10; + + MenuParam(obj, 'filter_type', {'GAUS','LPFIR', 'FIRLS','BUTTER','MOVAVRG','KAISER','EQUIRIP','HAMMING'}, ... + 'GAUS', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent filters. ''LPFIR'': lowpass FIR ''FIRLS'': Least square linear-phase FIR filter design\n', ... + '\n''BUTTER'': IIR Butterworth lowpass filter ''GAUS'': Gaussian filter (window)\n', ... + '\n''MOVAVRG'': Moving average FIR filter ''KAISER'': Kaiser-window FIR filtering\n', ... + '\n''EQUIRIP'':Eqiripple FIR filter ''HAMMING'': Hamming-window based FIR'])); + next_row(y, 1.3) + NumeditParam(obj, 'A1_sigma', 0.01, x,y,'label','A1_sigma','TooltipString','Sigma value for the stimulus'); + next_row(y); + NumeditParam(obj,'fcut',110,x,y,'label','fcut','TooltipString','Cut off frequency on the original white noise'); + next_row(y); + NumeditParam(obj,'lfreq',2000,x,y,'label','Modulator_LowFreq','TooltipString','Lower bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'hfreq',20000,x,y,'label','Modulator_HighFreq','TooltipString','Upper bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'dur',0.5,x,y,'label','dur','TooltipString','duration of stimulus in ms'); + next_row(y); + + sname='GaussNoise'; + + srate=SoundManagerSection(obj,'get_sample_rate'); + Fs=srate; + replace=1; + T=dur; + L=floor(T*Fs); % Length of signal + sigma_1=1; + + pos1 = sigma_1*randn(Fs,1); + base = randsample(pos1,L,replace); + filtbase=filt(base,fcut,Fs,value(filter_type)); + normbase=filtbase./(max(abs(filtbase))); + + mod1 = sigma_1*randn(Fs,1); + mod1 = randsample(mod1,L,replace); + hf = design(fdesign.bandpass('N,F3dB1,F3dB2',10,value(lfreq),value(hfreq),Fs)); + filtmod=filter(hf,mod1); + modulator=filtbase./(max(abs(filtmod))); + + AUD1=normbase(1:dur*srate).*modulator(1:dur*srate).*A1_sigma; + w=[AUD1'; AUD1']; + SoundManagerSection(obj, 'declare_new_sound', sname); + SoundManagerSection(obj, 'set_sound',sname,w) + + SubheaderParam(obj, [sname 'Head'], sname, x,y,'TooltipString',''); + PushbuttonParam(obj, [sname 'Play'], x,y, 'label', 'Play', 'position', [x y 30 20]); + set_callback(eval([sname 'Play']),{'SoundManagerSection', 'play_sound', sname}); + PushbuttonParam(obj, [sname 'Stop'], x,y, 'label', 'Stop', 'position', [x+30 y 30 20]); + set_callback(eval([sname 'Stop']),{'SoundManagerSection', 'stop_sound', sname}); + + x=oldx; y=oldy; + figure(parentfig); + + case 'hide', + StimuliPlayShow.value = 0; set(value(myfig), 'Visible', 'off'); + + case 'show', + StimuliPlayShow.value = 1; set(value(myfig), 'Visible', 'on'); + + case 'show_hide', + if StimuliPlayShow == 1, set(value(myfig), 'Visible', 'on'); %#ok (defined by GetSoloFunctionArgs) + else set(value(myfig), 'Visible', 'off'); + end; +end diff --git a/Protocols/@SoundCatContinuous/ProduceNoiseStimuli.m b/Protocols/@SoundCatContinuous/ProduceNoiseStimuli.m new file mode 100644 index 00000000..262d6a6d --- /dev/null +++ b/Protocols/@SoundCatContinuous/ProduceNoiseStimuli.m @@ -0,0 +1,33 @@ +clear all + +T=1000; +fcut=110; +Fs=10000; +filter_type='GAUS'; +outband=55; +Ind=[0.4]; + +minS1_d=1; +maxS1_d=30; +numClass=8; + +S1_d(1)=minS1_d; +S2_d(1)=S1_d(1)*(1-Ind)/(1+Ind); +S1_u(1)=S1_d; +S2_u(1)=S1_u(1)*(1+Ind)/(1-(Ind)); +for ii=2:numClass +S1_d(ii)=S2_u(ii-1); +S2_d(ii)=S1_d(ii)*(1-Ind)/(1+Ind); +S1_u(ii)=S1_d(ii); +S2_u(ii)=S1_u(ii)*(1+Ind)/(1-Ind); +end + +[rawA rawB filtA filtB]=noise(Sigma1(ss),Sigma2(ss),T,fcut,Fs,filter_type,outband); + + +pairs=[]; +pairs(:,1)=[S1_d S1_u]; +pairs(:,2)=[S2_d S2_u]; +thesepairs=pairs(2:end-1,:) +LOGplotPairs(thesepairs(:,1),thesepairs(:,2),'s',18,'k',1,16) + diff --git a/Protocols/@SoundCatContinuous/PunishmentSection.m b/Protocols/@SoundCatContinuous/PunishmentSection.m new file mode 100644 index 00000000..5289350e --- /dev/null +++ b/Protocols/@SoundCatContinuous/PunishmentSection.m @@ -0,0 +1,133 @@ +% Typical section code-- this file may be used as a template to be added +% on to. The code below stores the current figure and initial position when +% the action is 'init'; and, upon 'reinit', deletes all SoloParamHandles +% belonging to this section, then calls 'init' at the proper GUI position +% again. + + +% [x, y] = YOUR_SECTION_NAME(obj, action, x, y) +% +% Section that takes care of YOUR HELP DESCRIPTION +% +% PARAMETERS: +% ----------- +% +% obj Default object argument. +% +% action One of: +% 'init' To initialise the section and set up the GUI +% for it +% +% 'reinit' Delete all of this section's GUIs and data, +% and reinit, at the same position on the same +% figure as the original section GUI was placed. +% +% 'prepare_next_trial' Goes through the processing necessary +% to compute what the next trial's correct side +% should be. +% +% 'get_current_side' Returns either the string 'l' or the +% string 'r', for which side is the current trial's +% correcy side. +% +% +% x, y Relevant to action = 'init'; they indicate the initial +% position to place the GUI at, in the current figure window +% +% RETURNS: +% -------- +% +% [x, y] When action == 'init', returns x and y, pixel positions on +% the current figure, updated after placing of this section's GUI. +% +% +% x When action == 'get_current_side', returns either the string 'l' +% or the string 'r', for Left and Right, respectively. +% + + +function [x, y] = PunishmentSection(obj, action, x, y) + +GetSoloFunctionArgs(obj); + +switch action + case 'init', + % Save the figure and the position in the figure where we are + % going to start adding GUI elements: + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + + NumeditParam(obj, 'DrinkTime', 20, x, y, 'TooltipString', sprintf('\nTime over which drinking is ok')); next_row(y); + ToggleParam(obj, 'WarningSoundPanel', 1, x, y, 'OnString', 'warn show', 'OffString', 'warn hide', 'position', [x y 80 20]); + NumeditParam(obj, 'WarnDur', 4, x, y, 'labelfraction', 0.6, 'TooltipString', 'Warning sound duration in secs', 'position', [x+80 y 60 20]); + NumeditParam(obj, 'DangerDur',15, x, y, 'labelfraction', 0.6, 'TooltipString', sprintf('\nDuration of post-drink period where poking is punished'), 'position', [x+140 y 60 20]); next_row(y); + set_callback(WarningSoundPanel, {mfilename, 'WarningSoundPanel'}); + % start subpanel + oldx = x; oldy = y; oldfigure = double(gcf); + SoloParamHandle(obj, 'WarningSoundPanelFigure', 'saveable', 0, 'value', figure('Position', [120 120 430 156])); + sfig = value(WarningSoundPanelFigure); + set(sfig, 'MenuBar', 'none', 'NumberTitle', 'off', ... + 'Name', 'Warning sound', 'CloseRequestFcn', 'Classical(classical, ''closeWarningSoundPanel'')'); + SoundInterface(obj, 'add', 'WarningSound', 10, 10); + SoundInterface(obj, 'set', 'WarningSound', 'Vol', 0.0002); + SoundInterface(obj, 'set', 'WarningSound', 'Vol2', 0.004); + SoundInterface(obj, 'set', 'WarningSound', 'Dur1', 4); + SoundInterface(obj, 'set', 'WarningSound', 'Loop', 0); + SoundInterface(obj, 'set', 'WarningSound', 'Style', 'WhiteNoiseRamp'); + + SoundInterface(obj, 'add', 'DangerSound', 215, 10); + SoundInterface(obj, 'set', 'DangerSound', 'Vol', 0.004); + SoundInterface(obj, 'set', 'DangerSound', 'Dur1', 1); + SoundInterface(obj, 'set', 'DangerSound', 'Loop', 1); + SoundInterface(obj, 'set', 'DangerSound', 'Style', 'WhiteNoise'); + + x = oldx; y = oldy; figure(oldfigure); + % end subpanel + SoloFunctionAddVars('SMASection', 'ro_args', {'DrinkTime', 'WarnDur', 'DangerDur'}); + + [x, y] = PunishInterface(obj, 'add', 'PostDrinkPun', x, y); %#ok + next_row(y); + + %--------------------------------------------------------------- + % WarningSoundPanel + %--------------------------------------------------------------- + + case 'WarningSoundPanel' + if WarningSoundPanel==0, set(value(WarningSoundPanelFigure), 'Visible', 'off'); + else set(value(WarningSoundPanelFigure), 'Visible', 'on'); + end; + + %--------------------------------------------------------------- + % CLOSE + %--------------------------------------------------------------- + + case 'close', + if exist('WarningSoundPanelFigure', 'var') && ishandle(value(WarningSoundPanelFigure)), + delete(value(WarningSoundPanelFigure)); + end; + + if exist('PostDrinkPun_SoundsPanel', 'var') && ishandle(value(PostDrinkPun_SoundsPanel)), + delete(value(PostDrinkPun_SoundsPanel)); + end + + %--------------------------------------------------------------- + % REINIT + %--------------------------------------------------------------- + case 'reinit', + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename '_']); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); +end; + + diff --git a/Protocols/@SoundCatContinuous/RewardsSection.m b/Protocols/@SoundCatContinuous/RewardsSection.m new file mode 100644 index 00000000..65ead5c1 --- /dev/null +++ b/Protocols/@SoundCatContinuous/RewardsSection.m @@ -0,0 +1,237 @@ +% Typical section code-- this file may be used as a template to be added +% on to. The code below stores the current figure and initial position when +% the action is 'init'; and, upon 'reinit', deletes all SoloParamHandles +% belonging to this section, then calls 'init' at the proper GUI position +% again. +% +% +% [x, y] = YOUR_SECTION_NAME(obj, action, x, y) +% +% Section that takes care of YOUR HELP DESCRIPTION +% +% PARAMETERS: +% ----------- +% +% obj Default object argument. +% +% action One of: +% 'init' To initialise the section and set up the GUI +% for it +% +% 'reinit' Delete all of this section's GUIs and data, +% and reinit, at the same position on the same +% figure as the original section GUI was placed. +% +% 'get_stimulus' Returns either (1) a structure with fields 'type' +% and 'duration', with the contents of 'type' being +% 'lights' and the contents of 'duration' the +% maximum duration, in secs, of the stimulus; or (2) a +% structure with the fields 'type', 'duration', and +% 'id', with contents 'sounds', duration of sound in +% secs, and integer sound_id, respectively. +% +% 'get_poked_trials' Returns a double, number of trials in +% which subject poked in the appropriate poke at +% some point. +% +% +% x, y Relevant to action = 'init'; they indicate the initial +% position to place the GUI at, in the current figure window +% +% RETURNS: +% -------- +% +% [x, y] When action == 'init', returns x and y, pixel positions on +% the current figure, updated after placing of this section's GUI. +% +% +% x When action == 'get_current_side', returns either the string 'l' +% or the string 'r', for Left and Right, respectively. +% + + +function [x, y] = RewardsSection(obj, action, x, y) + +GetSoloFunctionArgs(obj); + +switch action + case 'init', + % Save the figure and the position in the figure where we are + % going to start adding GUI elements: + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)]); + + + DispParam(obj, 'n_trials', 0, x, y, ... + 'TooltipString', 'total # of elapsed trials'); next_row(y); + DispParam(obj, 'poked_trials', 0, x, y, ... + 'TooltipString', '# of trials in which subject poked in the appropriate poke at some point'); next_row(y); + DispParam(obj, 'consec_p_trials', 0, x, y, ... + 'TooltipString', '# of consecutive "poked_trials"'); next_row(y); + DispParam(obj, 'consec_up_trials', 0, x, y, ... + 'TooltipString', '# of consecutive UN"poked_trials"'); + ToggleParam(obj, 'WaterBlock', 0, x, y, 'position', [x+185 y 15 15], 'label', '', 'TooltipString', ... + sprintf('\nif BLACK, no water delivery, in "direct" mode. If BROWN, normal water delivery'), ... + 'OnString', '', 'OffString', ''); next_row(y); + DispParam(obj, 'rewarded_trials', 0, x, y, ... + 'TooltipString', '# of trials in which subject poked and got water'); next_row(y); + DispParam(obj, 'consec_r_trials', 0, x, y, ... + 'TooltipString', '# of consecutive trials, ending in last trial, in which subject poked and got water'); next_row(y); + DispParam(obj, 'rt', 0, x, y, ... + 'TooltipString', 'reaction time'); next_row(y); + NumeditParam(obj, 'rtThreshold', 4, x, y, 'TooltipString', ... + sprintf('\nrt less than this defines a "quick" trial')); next_row(y); + DispParam(obj, 'consec_q_trials', 0, x, y, ... + 'TooltipString', '# of consecutive trials with rt less than rtThreshold'); next_row(y); + DispParam(obj, 'good_trials', 0, x, y, ... + 'TooltipString', '# of trials in which subject exceeded post CS/pre CS poke ratio'); next_row(y); + DispParam(obj, 'consec_g_trials', 0, x, y, ... + 'TooltipString', '# of consecutive trials, ending in last trial, in which subject exceeded post CS/pre CS poke ratio'); next_row(y); + + % ------- + + SoloParamHandle(obj, 'r_trials', 'value', []); + SoloParamHandle(obj, 'q_trials', 'value', []); + SoloParamHandle(obj, 'g_trials', 'value', []); + + SubheaderParam(obj, 'title', 'Rewards Section', x, y); + next_row(y, 1.5); + + SoloFunctionAddVars('SMASection', ... + 'ro_args', {'WaterDelivery', 'RewardTime', 'PokeMeasureTime', 'WaterBlock'}); + feval(mfilename, obj, 'WaterDelivery'); % Set whatever is appropriate for current WaterDelivery + + + % --------------------------------------------------------------------- + % + % CASE GET_POKED_TRIALS + % + % --------------------------------------------------------------------- + + + case 'get_poked_trials', + x = value(poked_trials); %#ok + return; + + case 'add_to_pd' + %% add to pd + x.reward_time=cell2mat(get_history(RewardTime)); + + % --------------------------------------------------------------------- + % + % CASE PREPARE_NEXT_TRIAL + % + % --------------------------------------------------------------------- + + + case 'prepare_next_trial', + if isempty(parsed_events), return; end; + if ~isempty(previous_sides), %#ok + previous_sides = previous_sides(:); + wdh = get_history(WaterDelivery); + if isempty(wdh), wdh{1}='direct'; end; % fix for wierd bug + switch value(wdh{end}), + case 'direct', csstate = 'direct_cs'; + case 'on correct poke', csstate = 'cs'; + case 'on correctly timed poke', csstate = 'rewardable_cs'; + otherwise + error('huh?'); + end; + cs_onset = parsed_events.states.(csstate)(1,1); + if isequal(StimulusSection(obj, 'get_last_stimulus_loc'), 'anti-loc'), + if previous_sides(end)=='l', poke = 'R'; else poke = 'L'; end; + else + if previous_sides(end)=='l', poke = 'L'; else poke = 'R'; end; + end; + mypokes = parsed_events.pokes.(poke)(:,1); + + if ~isempty(find(mypokes > cs_onset,1)) + poked_trials.value = poked_trials+1; %#ok + consec_p_trials.value = consec_p_trials+1; %#ok + consec_up_trials.value = 0; + else + consec_p_trials.value = 0; + consec_up_trials.value = consec_up_trials+1; %#ok + end + end; + + if rows(parsed_events.states.lefthit)>0 || rows(parsed_events.states.righthit)>0, + r_trials.value = [r_trials(1:n_done_trials-1) 1]; %#ok + rewarded_trials.value = rewarded_trials+1; %#ok + consec_r_trials.value = consec_r_trials+1; %#ok + else + r_trials.value = [r_trials(1:n_done_trials-1) 0]; %#ok + consec_r_trials.value = 0; + end; + + hit_history.value = value(r_trials); + + n_trials.value = n_trials+1; %#ok + + + % Compute reaction times only for non-direct delivery modes: + if strcmp(csstate, 'direct_cs'), + consec_q_trials.value = 0; + else + if rows(parsed_events.states.lefthit>0) + rt.value = parsed_events.states.lefthit(1,1) - parsed_events.states.(csstate)(1,1); + elseif rows(parsed_events.states.righthit>0) + rt.value = parsed_events.states.righthit(1,1) - parsed_events.states.(csstate)(1,1); + else + warning('CLASSICAL:No_hit_state', 'No lefthit or righthit -- not computing rt!'); + end; + + if rt < rtThreshold, consec_q_trials.value = consec_q_trials + 1; %#ok + else consec_q_trials.value = 0; + end; + end; + + % Now compute the good poke ratio stuff + if ~isempty(previous_sides), + preCSonset_pokes = ... + length(find(parsed_events.states.(csstate)(1,1)-PokeMeasureTime < mypokes & ... + mypokes < parsed_events.states.(csstate)(1,1))); + postCSonset_pokes = ... + length(find(parsed_events.states.(csstate)(1,1) < mypokes & ... + mypokes < parsed_events.states.(csstate)(1,1)+PokeMeasureTime)); + if preCSonset_pokes > 0, PokeRatio.value = postCSonset_pokes / preCSonset_pokes; + elseif postCSonset_pokes > 0, PokeRatio.value = Inf; + else PokeRatio.value = 0; + end; + if PokeRatio > PokeRatioThreshold, + good_trials.value = good_trials+1; %#ok + consec_g_trials.value = consec_g_trials+1; %#ok + else + consec_g_trials.value = 0; + end; + end; + + + + + % --------------------------------------------------------------------- + % + % CASE REINIT + % + % --------------------------------------------------------------------- + + case 'reinit', + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); +end + + + diff --git a/Protocols/@SoundCatContinuous/SideSection.m b/Protocols/@SoundCatContinuous/SideSection.m new file mode 100644 index 00000000..fe36faec --- /dev/null +++ b/Protocols/@SoundCatContinuous/SideSection.m @@ -0,0 +1,469 @@ + + +function [x, y] = SideSection(obj, action, x,y) + +GetSoloFunctionArgs(obj); + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + y0 = y; + + [x, y] = AntibiasSectionAthena(obj, 'init', x, y); + + + ToggleParam(obj, 'antibias_LRprob', 0, x,y,... + 'OnString', 'AB_Prob ON',... + 'OffString', 'AB_Prob OFF',... + 'TooltipString', sprintf(['If on (Yellow) then it enables the AntiBias algorithm\n'... + 'based on changing the probablity of Left vs Right'])); + + next_row(y); + NumeditParam(obj, 'LeftProb', 0.5, x, y); next_row(y); + set_callback(LeftProb, {mfilename, 'new_leftprob'}); + MenuParam(obj, 'MaxSame', {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Inf}, Inf, x, y, ... + 'TooltipString', sprintf(['\nMaximum number of consecutive trials where correct\n' ... + 'response is on the same side. Overrides antibias. Thus, for\n' ... + 'example, if MaxSame=5 and there have been 5 Left trials, the\n' ... + 'next trial is guaranteed to be Right'])); next_row(y); + + DispParam(obj, 'ThisTrial', 'LEFT', x, y); next_row(y); + SoloParamHandle(obj, 'previous_sides', 'value', []); + DeclareGlobals(obj, 'ro_args', 'previous_sides'); + SubheaderParam(obj, 'title', 'Sides Section', x, y); + next_row(y, 1.5); + next_column(x); y = 5; + NumeditParam(obj, 'RewardCollection_duration', 10, x,y,'label','RewardCollection_duration','TooltipString','Wait until rat collects the reward'); + next_row(y); + NumeditParam(obj, 'CenterLed_duration', 200, x,y,'label','Central LED duration','TooltipString','Duration of Center Led'); + next_row(y); + NumeditParam(obj, 'SideLed_duration', 0.5, x,y,'label','Side LED duration','TooltipString','Duration of SideLed'); + next_row(y); + NumeditParam(obj, 'legal_cbreak', 0.1, x,y, 'position', [x, y, 175 20], 'TooltipString','Time in sec for which it is ok to be outside the center port before a violation occurs.'); + ToggleParam(obj, 'LED_during_legal_cbreak', 1, x, y, 'OnString', 'LED ON LcB', 'OffString', 'LED off LcB', ... + 'position', [x+180 y 20 20], 'TooltipString', ... + 'If 1 (black), turn center port LED back on during legal_cbreak; if 0 (brown), leave LED off'); + next_row(y); + NumeditParam(obj, 'SettlingIn_time', 0.2, x,y, 'position', [x, y, 175 20], 'TooltipString','Initial settling period during which "legal cbreak period" can be longer than the usual "legal_cbreak"'); + next_row(y); + NumeditParam(obj, 'settling_legal_cbreak', 0.1, x,y, 'position', [x, y, 175 20], 'TooltipString','Time in sec for which it is ok during the "SettlingIn_time" to be outside the center port before a violation occurs.'); + ToggleParam(obj, 'LED_during_settling_legal_cbreak', 0, x, y, 'OnString', 'LED ON SetLcB', 'OffString', 'LED OFF setLcB', ... + 'position', [x+180 y 20 20], 'TooltipString', ... + 'If 1 (black), turn center port LED back on during settling_legal_cbreak; if 0 (brown), leave LED off'); + next_row(y); + MenuParam(obj, 'side_lights' ,{'none','both','correct side','anti side'},1, x,y,'label','Side Lights','TooltipString','Controls the side LEDs during wait_for_spoke'); + next_row(y); + + + NumeditParam(obj, 'A1_time', 0.4, x,y,'label','AUD1 on Time','TooltipString','Duration of first stimulus'); + next_row(y); + set_callback(A1_time, {mfilename, 'new_CP_duration'}); + NumeditParam(obj, 'PreStim_time', 0.20, x,y,'label','Pre-Stim NIC time','TooltipString','Time in NIC before starting the stimulus'); + next_row(y); + set_callback(PreStim_time, {mfilename, 'new_CP_duration'}); + NumeditParam(obj, 'time_bet_aud1_gocue', 0, x,y,'label','A1-GoCue time','TooltipString','time between the end of the stimulus and the go cue '); + next_row(y); + set_callback(time_bet_aud1_gocue, {mfilename, 'new_CP_duration'}); + DispParam(obj, 'init_CP_duration', 0.01, x,y,'label','init_CP duration','TooltipString','Duration of Nose in Central Poke before Go cue starts (see Total_CP_duration)'); + next_row(y); + DispParam(obj, 'CP_duration', PreStim_time+A1_time+time_bet_aud1_gocue, x,y,'label','CP duration','TooltipString','Duration of Nose in Central Poke before Go cue starts (see Total_CP_duration)'); + set_callback(CP_duration, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_go_cue' ,0, x,y,'label','Go Cue Duration','TooltipString','duration of go cue (see Total_CP_duration)'); + set_callback(time_go_cue, {mfilename, 'new_time_go_cue'}); + next_row(y); + DispParam(obj, 'Total_CP_duration', CP_duration+time_go_cue, x, y, 'TooltipString', 'Total nose in center port time, in secs. Sum of CP_duration and Go Cue duration'); %#ok<*NODEF> + next_row(y); + ToggleParam(obj, 'warmup_on', 1, x,y,... + 'OnString', 'Warmup ON',... + 'OffString', 'Warmup OFF',... + 'TooltipString', sprintf(['If on (Yellow) then it applies the initial warming up phase, during which the\n',... + 'CP_duration starts small and gradually grows to last_session_max_cp_duration'])); + next_row(y); + % to modify it for widefield imagine + NumeditParam(obj,'imaging',0,x,y,'label','Scope Trigger'); + next_row(y); + NumeditParam(obj, 'reward_delay', 0.01, x,y,'label','Reward Delay','TooltipString','Delay between side poke and reward delivery'); + next_row(y); + NumeditParam(obj, 'reward_duration', 0.1, x,y,'label','Reward Duration','TooltipString','Duration of reward sound'); + next_row(y); + NumeditParam(obj, 'drink_time', 1, x,y,'label','Drink Time','TooltipString','waits to finish water delivery'); + next_row(y); + NumeditParam(obj, 'error_iti', 5, x,y,'label','Error Timeout','TooltipString','ITI on error trials'); + next_row(y); + NumeditParam(obj, 'violation_iti', 1, x,y,'label','Violation Timeout','TooltipString','Center poke violation duration'); + next_row(y); + MenuParam(obj, 'reward_type', {'Always','DelayedReward', 'NoReward'}, ... + 'Always', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nThis menu is to determine the Reward delivery on wrong-hit trials\n',... + '\nIf ''Always'': reward will be available on each trial no matter which side rat goes first\n',... + '\n If rat pokes first on the wrong side, then reward will be delivered with a delay (if DelayedReward) or not delivered at all (if NoReward)'])); + set_callback(reward_type, {mfilename, 'new_reward_type'}); + next_row(y); + NumeditParam(obj,'secondhit_delay',0,x,y,'label','SecondHit Delay','TooltipString','Reward will be delayed with this amount if reward_type=DelayedReward'); + + next_row(y); + ToggleParam(obj, 'random_A1_time', 0, x,y,... + 'OnString', 'random A1_time ON',... + 'OffString', 'random A1_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random sampling of A1_time')); + next_row(y); + ToggleParam(obj, 'random_prego_time', 0, x,y,... + 'OnString', 'random prego_time ON',... + 'OffString', 'random prego_time OFF',... + 'TooltipString', sprintf('If on (black) then it enables the random sampling of time between the end of the stimulus and the go cue')); + + next_column(x); + y=5; + NumeditParam(obj,'trials_in_stage',1,x,y,'label','Trial Counter'); + next_row(y); + NumeditParam(obj,'training_stage',1,x,y,'label','Training Stage'); + next_row(y); + ToggleParam(obj,'use_training',0,x,y,'OnString','Using Autotrain','OffString','Manual Settings'); + + next_row(y); + NumeditParam(obj, 'ntrial_correct_bias', 0, x, y, ... + 'TooltipString', 'antibias starts from trial=ntrial_correct_bias'); + next_row(y); + NumeditParam(obj, 'right_left_diff', .12, x, y, ... + 'TooltipString', 'antibias applies if difference between right and left sides is bigger than this number'); + next_row(y); + NumeditParam(obj, 'max_wtr_mult', 4, x, y, ... + 'TooltipString', 'wtr_mult will be min(max_wtr_mult,right_hit/left_hit)'); + next_row(y); + NumeditParam(obj, 'left_wtr_mult', 1, x, y, ... + 'TooltipString', 'all left reward times are multiplied by this number'); + next_row(y); + NumeditParam(obj, 'right_wtr_mult', 1, x, y, ... + 'TooltipString', 'all right reward times are multiplied by this number'); + next_row(y); + ToggleParam(obj, 'antibias_wtr_mult', 0, x,y,... + 'OnString', 'AB ON',... + 'OffString', 'AB OFF',... + 'TooltipString', sprintf(['If on (black) then it disables the wtr_mult entries\n'... + 'and uses hitfrac to adjust the water times'])); + + next_row(y); + ToggleParam(obj, 'stimuli_on', 1, x,y,... + 'OnString', 'Stimuli ON',... + 'OffString', 'Stimuli OFF',... + 'TooltipString', sprintf('If on (black) then it disable the presentation of sound stimuli during nose poke')); + set_callback(stimuli_on, {mfilename, 'new_CP_duration'}); + + next_row(y); + SoloFunctionAddVars('SoundCatSMA', 'ro_args', ... + {'CP_duration';'SideLed_duration';'CenterLed_duration';'side_lights' ; ... + 'RewardCollection_duration';'training_stage'; ... + 'legal_cbreak' ; 'LED_during_legal_cbreak' ; ... + 'SettlingIn_time';'settling_legal_cbreak' ; 'LED_during_settling_legal_cbreak' ; ... + 'time_go_cue'; ... + 'stimuli_on';'A1_time';'time_bet_aud1_gocue' ; ... + 'PreStim_time';'warmup_on' + 'drink_time';'reward_delay';'reward_duration';'left_wtr_mult';... + 'right_wtr_mult';'antibias_wtr_mult';... + 'reward_type';'secondhit_delay';'error_iti';'violation_iti';'imaging'}); + + SoloFunctionAddVars('StimulusSection', 'ro_args', ... + {'ThisTrial';'A1_time';'time_bet_aud1_gocue' ; ... + 'PreStim_time'}); + SoloFunctionAddVars('StimulatorSection', 'ro_args', ... + {'A1_time';'time_bet_aud1_gocue';'time_go_cue'; ... + 'PreStim_time';'CP_duration';'Total_CP_duration'}); + + % History of hit/miss: + SoloParamHandle(obj, 'deltaf_history', 'value', []); + + SoloFunctionAddVars('OverallPerformanceSection', 'ro_args', ... + {'training_stage'}); + + + SoloParamHandle(obj, 'previous_parameters', 'value', []); + + case 'new_leftprob', + AntibiasSectionAthena(obj, 'update_biashitfrac', value(LeftProb)); + + + case 'new_CP_duration', + if stimuli_on == 0 + PreStim_time.value=0; + A1_time.value=0; + time_bet_aud1_gocue.value=0; + disable(PreStim_time); + disable(A1_time); + disable(time_bet_aud1_gocue); + else + enable(PreStim_time); + enable(A1_time); + enable(time_bet_aud1_gocue); + end + CP_duration.value=PreStim_time + A1_time + time_bet_aud1_gocue; + Total_CP_duration.value = CP_duration + time_go_cue; %#ok<*NASGU> + + case 'new_time_go_cue', + Total_CP_duration.value = CP_duration + time_go_cue; + SoundInterface(obj, 'set', 'GoSound', 'Dur1', value(time_go_cue)); + + case 'new_reward_type' + if strcmp(reward_type,'DelayedReward') + enable(secondhit_delay) + else + secondhit_delay=0; + disable(secondhit_delay) + + end + + + case 'prepare_next_trial' + + + switch value(training_stage) + case 0, %% learning the reward sound association -left or right led on -> poke -> sound+reward + settling_time.value=0.01; + delay_time.value=0; + allow_nic_breaks.value=1; + side_lights.value=3; + trials_in_stage.value=0; + reward_delay.value=0.01; + left_prob.value=0.5; + right_prob.value=0.5; + time_go_cue.value=0.200; + reward_duration=0.200; + + + case 1, %% center led on -> poke in the center -> go cue -> reward light and sound + %what A1_time and time before go cue? + if random_A1_time + A1_times = [0.2, 0.4]; + randomix = randi(length(A1_times), 1); + A1_time.value = A1_times(randomix); + end + + if random_prego_time + prego_times = [0.2, 0.4, 0.6, 0.8, 1]; + randomixx = randi(length(prego_times), 1); + time_bet_aud1_gocue.value = prego_times(randomixx); + end + settling_time.value=0.25; + delay_time.value=0; + allow_nic_breaks.value=1; + side_lights.value=3; + training_stage.value=1; + trials_in_stage.value=0; + reward_delay.value=0.01; + left_prob.value=0.5; + right_prob.value=0.5; + if n_done_trials <1 && warmup_on ==1 + CP_duration.value=value(init_CP_duration); + else + CP_duration.value=PreStim_time + A1_time + time_bet_aud1_gocue; + end + Total_CP_duration.value = CP_duration + time_go_cue; %#ok<*NASGU> + + + case 3, % like stage 2, now passive exposure to the stimuli - reward comes anyway + case 4 %% now reward comes only if rat goes to the correct side + + end + + + %% update hit_history, previous_sides, etc + was_viol=false; + was_hit=false; + was_timeout=false; + if n_done_trials>0 + if ~isempty(parsed_events) + if isfield(parsed_events,'states') + if isfield(parsed_events.states,'timeout_state') + was_timeout=rows(parsed_events.states.timeout_state)>0; + end + if isfield(parsed_events.states,'violation_state') + was_viol=rows(parsed_events.states.violation_state)>0; + end + end + + end + + violation_history.value=[violation_history(:); was_viol]; + timeout_history.value=[timeout_history(:); was_timeout]; + + SideSection(obj,'update_side_history'); + + if ~was_viol && ~was_timeout + %was_hit=rows(parsed_events.states.hit_state)>0; + was_hit=rows(parsed_events.states.second_hit_state)==0; + hit_history.value=[hit_history(:); was_hit]; + + else + % There was a violation or timeout + hit_history.value=[hit_history(:); nan]; + end + + % Now calculate the deltaF and sides - this maybe interesting + % even in a violation or timeout case. + + fn=fieldnames(parsed_events.states); + led_states=find(strncmp('led',fn,3)); + deltaF=0; + n_l=0; + n_r=0; + for lx=1:numel(led_states) + lind=led_states(lx); + if rows(parsed_events.states.(fn{lind}))>0 + if fn{lind}(end)=='l' + deltaF=deltaF-1; + n_l=n_l+1; + elseif fn{lind}(end)=='r' + deltaF=deltaF+1; + n_r=n_r+1; + elseif fn{lind}(end)=='b' + n_l=n_l+1; + n_r=n_r+1; + + end + end + + end + + % if deltaF>0 then a right poke is a hit + % if deltaF<0 then a left poke is a hit + + deltaf_history.value=[deltaf_history(:); deltaF]; + + end + + if antibias_LRprob ==1 + if n_done_trials >ntrial_correct_bias && ~was_viol && ~was_timeout + nonan_hit_history=value(hit_history); + nonan_hit_history(isnan(nonan_hit_history))=[]; + nonan_previous_sides=value(previous_sides); + nan_history=value(hit_history); + nonan_previous_sides(isnan(nan_history))=[]; + AntibiasSectionAthena(obj, 'update', value(LeftProb), nonan_hit_history(:)',nonan_previous_sides(:)); % <~> Transposed hit history so that it is the expected column vector. (Antibias errors out otherwise.) 2007.09.05 01:39 + end + + + if ~isinf(MaxSame) && length(previous_sides) > MaxSame && ... + all(previous_sides(n_done_trials-MaxSame+1:n_done_trials) == previous_sides(n_done_trials)), %#ok + if previous_sides(end)=='l', ThisTrial.value = 'RIGHT'; + else ThisTrial.value = 'LEFT'; + end; + else + choiceprobs = AntibiasSectionAthena(obj, 'get_posterior_probs'); + if rand(1) <= choiceprobs(1), ThisTrial.value = 'LEFT'; + else ThisTrial.value = 'RIGHT'; + end; + end; + + else + if (rand(1)<=LeftProb) + ThisTrial.value='LEFT'; + + else + ThisTrial.value='RIGHT'; + end + + end + + + + +% %% Do the anti-bias with changing reward delivery +% % reset anti-bias +% left_wtr_mult.value=1; +% right_wtr_mult.value=1; +% if n_done_trials>ntrial_correct_bias && antibias_wtr_mult==1 +% hh=hit_history(n_done_trials-ntrial_correct_bias:n_done_trials); +% ps=previous_sides(n_done_trials-ntrial_correct_bias:n_done_trials); +% +% right_hit=nanmean(hh(ps=='r')); +% left_hit=nanmean(hh(ps=='l')); +% +% if abs(right_hit-left_hit) + + case 'get_left_prob' + x = value(LeftProb); + + case 'get_cp_history' + x = cell2mat(get_history(CP_duration)); + + case 'get_stimdur_history' + x = cell2mat(get_history(A1_time)); + + case 'update_side_history' + if strcmp(ThisTrial, 'LEFT') + ps=value(previous_sides); + ps(n_done_trials)='l'; + previous_sides.value=ps; + + else + ps=value(previous_sides); + ps(n_done_trials)='r'; + previous_sides.value=ps; + end; + + case 'get_current_side' + if strcmp(ThisTrial, 'LEFT') + x = 'l'; %#ok + else + x = 'r'; + end; + + + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'reinit', + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + +end + + diff --git a/Protocols/@SoundCatContinuous/SideSection_aa.m b/Protocols/@SoundCatContinuous/SideSection_aa.m new file mode 100644 index 00000000..086836f0 --- /dev/null +++ b/Protocols/@SoundCatContinuous/SideSection_aa.m @@ -0,0 +1,378 @@ + + +function [x, y] = SideSection(obj, action, x,y) + +GetSoloFunctionArgs(obj); + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + + SoloParamHandle(obj, 'my_gui_info', 'value', [x y double(gcf)], 'saveable', 0); + y0 = y; + + [x, y] = AntibiasSection(obj, 'init', x, y); + + NumeditParam(obj, 'LeftProb', 0.5, x, y); next_row(y); + set_callback(LeftProb, {mfilename, 'new_leftprob'}); + MenuParam(obj, 'MaxSame', {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Inf}, Inf, x, y, ... + 'TooltipString', sprintf(['\nMaximum number of consecutive trials where correct\n' ... + 'response is on the same side. Overrides antibias. Thus, for\n' ... + 'example, if MaxSame=5 and there have been 5 Left trials, the\n' ... + 'next trial is guaranteed to be Right'])); next_row(y); + + DispParam(obj, 'ThisTrial', 'LEFT', x, y); next_row(y); + SoloParamHandle(obj, 'previous_sides', 'value', []); + DeclareGlobals(obj, 'ro_args', 'previous_sides'); + SubheaderParam(obj, 'title', 'Sides Section', x, y); + next_row(y, 1.5); + next_column(x); y = 5; + NumeditParam(obj, 'CP_duration', 0.250, x,y,'label','CP duration','TooltipString','Duration of Nose in Central Poke before Go cue starts (see Total_CP_duration)'); + set_callback(CP_duration, {mfilename, 'new_CP_duration'}); + next_row(y); + NumeditParam(obj, 'time_go_cue' ,0.2, x,y,'label','Go Cue Duration','TooltipString','duration of go cue (see Total_CP_duration)'); + set_callback(time_go_cue, {mfilename, 'new_time_go_cue'}); + next_row(y); + DispParam(obj, 'Total_CP_duration', CP_duration+time_go_cue, x, y, 'TooltipString', 'Total nose in center port time, in secs. Sum of CP_duration and Go Cue duration'); %#ok<*NODEF> + next_row(y); + NumeditParam(obj, 'RewardCollection_duration', 10, x,y,'label','RewardCollection_duration','TooltipString','Wait until rat collects the reward'); + next_row(y); + NumeditParam(obj, 'CenterLed_duration', 20, x,y,'label','Central LED duration','TooltipString','Duration of Center Led'); + next_row(y); + NumeditParam(obj, 'SideLed_duration', 0.5, x,y,'label','Side LED duration','TooltipString','Duration of SideLed'); + next_row(y); + NumeditParam(obj, 'legal_cbreak', 0, x,y, 'position', [x, y, 175 20], 'TooltipString','Time in sec for which it is ok to be outside the center port before a violation occurs.'); + ToggleParam(obj, 'LED_during_legal_cbreak', 1, x, y, 'OnString', 'LED ON LcB', 'OffString', 'LED off LcB', ... + 'position', [x+180 y 20 20], 'TooltipString', ... + 'If 1 (black), turn center port LED back on during legal_cbreak; if 0 (brown), leave LED off'); + next_row(y); + NumeditParam(obj, 'SettlingIn_time', 0.2, x,y, 'position', [x, y, 175 20], 'TooltipString','Initial settling period during which "legal cbreak period" can be longer than the usual "legal_cbreak"'); + next_row(y); + NumeditParam(obj, 'settling_legal_cbreak', 0.05, x,y, 'position', [x, y, 175 20], 'TooltipString','Time in sec for which it is ok during the "SettlingIn_time" to be outside the center port before a violation occurs.'); + ToggleParam(obj, 'LED_during_settling_legal_cbreak', 0, x, y, 'OnString', 'LED ON SetLcB', 'OffString', 'LED OFF setLcB', ... + 'position', [x+180 y 20 20], 'TooltipString', ... + 'If 1 (black), turn center port LED back on during settling_legal_cbreak; if 0 (brown), leave LED off'); + next_row(y); + MenuParam(obj, 'side_lights' ,{'none','both','correct side','anti side'},1, x,y,'label','Side Lights','TooltipString','Controls the side LEDs during wait_for_spoke'); + next_row(y); + % NumeditParam(obj, 'wait_for_cpoke_timeout', 4, x,y,'label','Response Timeout','TooltipString','Time after NIC to wait for a side poke'); + % next_row(y); + + + NumeditParam(obj, 'A1_time', 3, x,y,'label','AUD1 on Time','TooltipString','Duration of first stimulus'); + next_row(y); + NumeditParam(obj, 'A2_time', 3, x,y,'label','AUD2 On Time','TooltipString','Duration of second stimulus'); + next_row(y); + NumeditParam(obj, 'Del_time', 2, x,y,'label','Delay Duration Time','TooltipString','Duration of delay period'); + next_row(y); + NumeditParam(obj, 'PreStim_time', 0.05, x,y,'label','Pre-Stim NIC time','TooltipString','Time in NIC before starting the stimulus'); + next_row(y); + NumeditParam(obj, 'time_bet_aud2_gocue', 0.05, x,y,'label','A2-GoCue time','TooltipString','time between the end of the second stimulus and the go cue '); + next_row(y); + NumeditParam(obj, 'reward_delay', 0.01, x,y,'label','Reward Delay','TooltipString','Delay between side poke and reward delivery'); + next_row(y); + NumeditParam(obj, 'reward_duration', 0.1, x,y,'label','Reward Duration','TooltipString','Duration of reward sound'); + next_row(y); + NumeditParam(obj, 'drink_time', 1, x,y,'label','Drink Time','TooltipString','waits to finish water delivery'); + next_row(y); + ToggleParam(obj, 'ignore_errors', 0, x,y,'OffString','Errors Punished','OnString','Errors Ignored','TooltipString','If ignore errors, then subject will eventually get the reward at the correct poke'); + next_row(y); + NumeditParam(obj, 'error_iti', 10, x,y,'label','Error Timeout','TooltipString','ITI on error trials'); + next_row(y); + + NumeditParam(obj, 'violation_iti', 1, x,y,'label','Violation Timeout','TooltipString','Center poke violation duration'); + + next_column(x); + y=5; + NumeditParam(obj,'trials_in_stage',1,x,y,'label','Trial Counter'); + next_row(y); + NumeditParam(obj,'training_stage',2,x,y,'label','Training Stage'); + next_row(y); + ToggleParam(obj,'use_training',0,x,y,'OnString','Using Autotrain','OffString','Manual Settings'); + + next_row(y); + NumeditParam(obj, 'ntrial_correct_bias', 30, x, y, ... + 'TooltipString', 'antibias starts from trial=ntrial_correct_bias'); + next_row(y); + NumeditParam(obj, 'right_left_diff', .12, x, y, ... + 'TooltipString', 'antibias applies if difference between right and left sides is bigger than this number'); + next_row(y); + NumeditParam(obj, 'max_wtr_mult', 4, x, y, ... + 'TooltipString', 'wtr_mult will be min(max_wtr_mult,right_hit/left_hit)'); + next_row(y); + NumeditParam(obj, 'left_wtr_mult', 1, x, y, ... + 'TooltipString', 'all left reward times are multiplied by this number'); + next_row(y); + NumeditParam(obj, 'right_wtr_mult', 1, x, y, ... + 'TooltipString', 'all right reward times are multiplied by this number'); + next_row(y); + ToggleParam(obj, 'antibias_wtr_mult', 0, x,y,... + 'OnString', 'AB ON',... + 'OffString', 'AB OFF',... + 'TooltipString', sprintf(['If on (black) then it disables the wtr_mult entries\n'... + 'and uses hitfrac to adjust the water times'])); + + next_row(y); + SoloFunctionAddVars('AthenaSMA', 'ro_args', ... + {'CP_duration';'SideLed_duration';'CenterLed_duration';'side_lights' ; ... + 'RewardCollection_duration';'ignore_errors';'training_stage'; ... + 'legal_cbreak' ; 'LED_during_legal_cbreak' ; ... + 'SettlingIn_time';'settling_legal_cbreak' ; 'LED_during_settling_legal_cbreak' ; ... + 'time_go_cue'; ... + 'A1_time';'A2_time';'Del_time';'time_bet_aud2_gocue' ; ... + 'PreStim_time' + 'drink_time';'reward_delay';'reward_duration';'left_wtr_mult';... + 'right_wtr_mult';'antibias_wtr_mult';... + 'error_iti';'violation_iti'}); + + SoloFunctionAddVars('StimulusSection', 'ro_args', ... + {'ThisTrial';'A1_time';'A2_time';'Del_time';'time_bet_aud2_gocue' ; ... + 'PreStim_time'}); + + % History of hit/miss: + SoloParamHandle(obj, 'deltaf_history', 'value', []); + + SoloFunctionAddVars('OverallPerformanceSection', 'ro_args', ... + {'training_stage'}); + + + SoloParamHandle(obj, 'previous_parameters', 'value', []); + + case 'new_leftprob', + %AntibiasSection(obj, 'update_biashitfrac', value(LeftProb)); + + + case 'new_CP_duration', + if training_stage ==2 + CP_duration=PreStim_time + A1_time + A2_time + Del_time + time_bet_aud2_gocue; + end + Total_CP_duration.value = CP_duration + time_go_cue; %#ok<*NASGU> + + case 'new_time_go_cue', + Total_CP_duration.value = CP_duration + time_go_cue; + SoundInterface(obj, 'set', 'GoSound', 'Dur1', value(time_go_cue)); + + case 'prepare_next_trial' + + + switch value(training_stage) + case 0, %% learning the reward sound association -left or right led on -> poke -> sound+reward + settling_time.value=0.01; + delay_time.value=0; + allow_nic_breaks.value=1; + ignore_errors.value=1; + side_lights.value=3; + trials_in_stage.value=0; + reward_delay.value=0.01; + error_iti.value=4; + left_prob.value=0.5; + right_prob.value=0.5; + time_go_cue.value=0.200; + reward_duration=0.200; + + + case 1, %% center led on -> poke in the center -> go cue -> reward light and sound + settling_time.value=0.25; + delay_time.value=0; + allow_nic_breaks.value=1; + ignore_errors.value=1; + side_lights.value=3; + training_stage.value=1; + trials_in_stage.value=0; + reward_delay.value=0.01; + error_iti.value=5; + left_prob.value=0.5; + right_prob.value=0.5; + + + case 2, % like stage 1 - now waiting time grows + CP_duration=PreStim_time + A1_time + A2_time + Del_time + time_bet_aud2_gocue; + case 3, % like stage 2, now passive exposure to the stimuli - reward comes anyway + case 4 %% now reward comes only if rat goes to the correct side + + end + + + %% update hit_history, previous_sides, etc + was_viol=false; + was_hit=false; + was_timeout=false; + if n_done_trials>0 + if ~isempty(parsed_events) + if isfield(parsed_events,'states') + if isfield(parsed_events.states,'timeout_state') + was_timeout=rows(parsed_events.states.timeout_state)>0; + end + if isfield(parsed_events.states,'violation_state') + was_viol=rows(parsed_events.states.violation_state)>0; + end + end + + end + + violation_history.value=[violation_history(:); was_viol]; + timeout_history.value=[timeout_history(:); was_timeout]; + + SideSection(obj,'update_side_history'); + + if ~was_viol && ~was_timeout + was_hit=rows(parsed_events.states.hit_state)>0; + hit_history.value=[hit_history(:); was_hit]; + + else + % There was a violation or timeout + hit_history.value=[hit_history(:); nan]; + end + + % Now calculate the deltaF and sides - this maybe interesting + % even in a violation or timeout case. + + fn=fieldnames(parsed_events.states); + led_states=find(strncmp('led',fn,3)); + deltaF=0; + n_l=0; + n_r=0; + for lx=1:numel(led_states) + lind=led_states(lx); + if rows(parsed_events.states.(fn{lind}))>0 + if fn{lind}(end)=='l' + deltaF=deltaF-1; + n_l=n_l+1; + elseif fn{lind}(end)=='r' + deltaF=deltaF+1; + n_r=n_r+1; + elseif fn{lind}(end)=='b' + n_l=n_l+1; + n_r=n_r+1; + + end + end + + end + + % if deltaF>0 then a right poke is a hit + % if deltaF<0 then a left poke is a hit + + deltaf_history.value=[deltaf_history(:); deltaF]; + + end + + % if n_done_trials >3 && ~was_viol + % AntibiasSection(obj, 'update', value(LeftProb), hit_history(1:n_done_trials)',previous_sides(1:n_done_trials)); % <~> Transposed hit history so that it is the expected column vector. (Antibias errors out otherwise.) 2007.09.05 01:39 + % end + % + % + % if ~isinf(MaxSame) && length(previous_sides) > MaxSame && ... + % all(previous_sides(n_done_trials-MaxSame+1:n_done_trials) == previous_sides(n_done_trials)), %#ok + % if previous_sides(end)=='l', ThisTrial.value = 'RIGHT'; + % else ThisTrial.value = 'LEFT'; + % end; + % else + % choiceprobs = AntibiasSection(obj, 'get_posterior_probs'); + % if rand(1) <= choiceprobs(1), ThisTrial.value = 'LEFT'; + % else ThisTrial.value = 'RIGHT'; + % end; + % end; + + + %% Do the anti-bias with changing reward delivery + % reset anti-bias + left_wtr_mult.value=1; + right_wtr_mult.value=1; + if n_done_trials>ntrial_correct_bias && antibias_wtr_mult==1 + hh=hit_history(n_done_trials-ntrial_correct_bias:n_done_trials); + ps=previous_sides(n_done_trials-ntrial_correct_bias:n_done_trials); + + right_hit=nanmean(hh(ps=='r')); + left_hit=nanmean(hh(ps=='l')); + + if abs(right_hit-left_hit) + + case 'get_left_prob' + x = value(LeftProb); + + case 'get_cp_history' + x = cell2mat(get_history(CP_duration)); + + case 'update_side_history' + if strcmp(ThisTrial, 'LEFT') + ps=value(previous_sides); + ps(n_done_trials)='l'; + previous_sides.value=ps; + + else + ps=value(previous_sides); + ps(n_done_trials)='r'; + previous_sides.value=ps; + end; + + case 'get_current_side' + if strcmp(ThisTrial, 'LEFT') + x = 'l'; %#ok + else + x = 'r'; + end; + + + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + case 'reinit', + currfig = double(gcf); + + % Get the original GUI position and figure: + x = my_gui_info(1); y = my_gui_info(2); figure(my_gui_info(3)); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + % Reinitialise at the original GUI position and figure: + [x, y] = feval(mfilename, obj, 'init', x, y); + + % Restore the current figure: + figure(currfig); + +end + + diff --git a/Protocols/@SoundCatContinuous/SoundCatContinuous.m b/Protocols/@SoundCatContinuous/SoundCatContinuous.m new file mode 100644 index 00000000..7e9c669c --- /dev/null +++ b/Protocols/@SoundCatContinuous/SoundCatContinuous.m @@ -0,0 +1,262 @@ +% AltSoundCatCatch protocol +% EM, October 2020 + +function [obj] = SoundCatContinuous(varargin) + +% Default object is of our own class (mfilename); +% we inherit only from Plugins + +obj = class(struct, mfilename, pokesplot2, saveload, sessionmodel, soundmanager, soundui, antibias, ... + water, distribui, punishui, comments, soundtable, sqlsummary,reinforcement); + +%--------------------------------------------------------------- +% BEGIN SECTION COMMON TO ALL PROTOCOLS, DO NOT MODIFY +%--------------------------------------------------------------- + +% If creating an empty object, return without further ado: +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')) + return; +end + +if isa(varargin{1}, mfilename) % If first arg is an object of this class itself, we are + % Most likely responding to a callback from + % a SoloParamHandle defined in this mfile. + if length(varargin) < 2 || ~ischar(varargin{2}) + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else + action = varargin{2}; varargin = varargin(3:end); %#ok + end +else % Ok, regular call with first param being the action string. + action = varargin{1}; varargin = varargin(2:end); %#ok +end + +GetSoloFunctionArgs(obj); + +switch action + + %% init + case 'init' + dispatcher('set_trialnum_indicator_flag'); + hackvar = 10; SoloFunctionAddVars('SessionModel', 'ro_args', 'hackvar'); %#ok + SoloParamHandle(obj, 'myfig', 'saveable', 0); myfig.value = figure; + + % Make the title of the figure be the protocol name, and if someone tries + % to close this figure, call dispatcher's close_protocol function, so it'll know + % to take it off the list of open protocols. + name = mfilename; + set(value(myfig), 'Name', name, 'Tag', name, ... + 'closerequestfcn', 'dispatcher(''close_protocol'')', 'MenuBar', 'none'); + % At this point we have one SoloParamHandle, myfig + % Let's put the figure where we want it and give it a reasonable size: + set(value(myfig), 'Position', [485 144 850 680]); + + SoloParamHandle(obj, 'nsessions_healthy_number_of_pokes', 'value', 0, 'save_with_settings', 1); + SoloParamHandle(obj, 'post_DelComp_protocol', 'value', '', 'save_with_settings', 1); + SoloParamHandle(obj, 'post_DelComp_settings_filename', 'value', '', 'save_with_settings', 1); + + + SoloParamHandle(obj, 'hit_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'hit_history'}); + SoloFunctionAddVars('SideSection', 'rw_args', 'hit_history'); + + %pair_history changed to stimulus_history (from AthenaDelayComp) + SoloParamHandle(obj, 'stimulus_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'stimulus_history'}); + SoloFunctionAddVars('StimulusSection', 'rw_args', 'stimulus_history'); + + SoloParamHandle(obj, 'violation_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'violation_history'}); + SoloFunctionAddVars('SideSection', 'rw_args', 'violation_history'); + + SoloParamHandle(obj, 'timeout_history', 'value', []); + DeclareGlobals(obj, 'ro_args', {'timeout_history'}); + SoloFunctionAddVars('SideSection', 'rw_args', 'timeout_history'); + + + SoundManagerSection(obj, 'init'); + x = 5; y = 5; % Initial position on main GUI window + [x, y] = SavingSection(obj, 'init', x, y); + + %% slow ramp up of water amount + %%the water volume is controlled by a 5-parameter logistic function: WaterAmount(t) = maxasymp + (minasymp/(1+(t/inflp)^slp).^assym) + NumeditParam(obj, 'maxasymp', 38, x,y,'label','maxasymp','TooltipString',... + 'the water volume is controlled by a 5-parameter logistic function: WaterAmount(trialnum) = maxasymp + (minasymp/(1+(trialnum/inflp)^slp).^assym)'); + next_row(y); + NumeditParam(obj, 'slp', 3, x,y,'label','slp','TooltipString','Water Modulation: Slope of the logistic function'); + next_row(y); + NumeditParam(obj, 'inflp', 350, x,y,'label','inflp','TooltipString','Water Modulation: concentration at the inflection point'); + next_row(y); + NumeditParam(obj, 'minasymp', -21, x,y,'label','inflp','TooltipString','Water Modulation: minimum asymptote'); + next_row(y); + NumeditParam(obj, 'assym', 0.7, x,y,'label','assym','TooltipString','Water Modulation: asymmetry factor'); + next_row(y); + DispParam(obj, 'trial_1', 0, x, y, 'TooltipString', 'uL on first trial'); + next_row(y); + DispParam(obj, 'trial_150', 0, x, y, 'TooltipString', 'uL on trial 150'); + next_row(y); + DispParam(obj, 'trial_300', 0, x, y, 'TooltipString', 'uL on trial 300'); + next_row(y); + set_callback({maxasymp;slp;inflp;minasymp;assym}, {mfilename, 'change_water_modulation_params'}); + feval(mfilename, obj, 'change_water_modulation_params'); + + %AthenaSMA changed to SoundCatSMA (From AthenaDelayComp) + SoloFunctionAddVars('SoundCatSMA', 'ro_args', ... + {'maxasymp';'slp';'inflp';'minasymp';'assym'}); + [x, y] = WaterValvesSection(obj, 'init', x, y); + + % For plotting with the pokesplot plugin, we need to tell it what + % colors to plot with: + my_state_colors = SoundCatSMA(obj, 'get_state_colors'); + % In pokesplot, the poke colors have a default value, so we don't need + % to specify them, but here they are so you know how to change them. + my_poke_colors = struct( ... + 'L', 0.6*[1 0.66 0], ... + 'C', [0 0 0], ... + 'R', 0.9*[1 0.66 0]); + + [x, y] = PokesPlotSection(obj, 'init', x, y, ... + struct('states', my_state_colors, 'pokes', my_poke_colors)); next_row(y); + + [x, y] = CommentsSection(obj, 'init', x, y); + SessionDefinition(obj, 'init', x, y, value(myfig)); next_row(y, 2); %#ok + SessionDefinition(obj, 'set_old_style_parsing_flag',0); + % [x, y] = PunishmentSection(obj, 'init', x, y); %#ok + + next_column(x); y=5; + [x, y] = OverallPerformanceSection(obj, 'init', x, y); + [x, y] = StimulatorSection(obj, 'init', x, y); next_row(y, 1.3); + [x, y] = SideSection(obj, 'init', x, y); %#ok + [x, y] = SoundSection(obj,'init',x,y); +% [x, y] = PlayStimuli(obj,'init',x,y); + [x, y] = StimulusSection(obj,'init',x,y); + + figpos = get(double(gcf), 'Position'); + [expmtr, rname]=SavingSection(obj, 'get_info'); + HeaderParam(obj, 'prot_title', [mfilename ': ' expmtr ', ' rname], x, y, 'position', [10 figpos(4)-25, 800 20]); + + SoundCatSMA(obj, 'init'); + feval(mfilename, obj, 'prepare_next_trial'); + + %% change_water_modulation_params + case 'change_water_modulation_params' + display_guys = [1 150 300]; + for i=1:numel(display_guys) + t = display_guys(i); + + myvar = eval(sprintf('trial_%d', t)); + myvar.value = maxasymp + (minasymp/(1+(t/inflp)^slp).^assym); + end + + %% prepare next trial + case 'prepare_next_trial' + + SideSection(obj, 'prepare_next_trial'); + % Run SessionDefinition *after* SideSection so we know whether the + % trial was a violation or not + SessionDefinition(obj, 'next_trial'); + StimulatorSection(obj, 'update_values'); + OverallPerformanceSection(obj, 'evaluate'); + StimulusSection(obj,'prepare_next_trial'); + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + + nTrials.value = n_done_trials; + + [sma, prepare_next_trial_states] = SoundCatSMA(obj, 'prepare_next_trial'); + + % Default behavior of following call is that every 20 trials, the data + % gets saved, not interactive, no commit to CVS. + SavingSection(obj, 'autosave_data'); + + CommentsSection(obj, 'clear_history'); % Make sure we're not storing unnecessary history + if n_done_trials==1 % Auto-append date for convenience. + CommentsSection(obj, 'append_date'); CommentsSection(obj, 'append_line', ''); + end + + if n_done_trials==1 + [expmtr, rname]=SavingSection(obj, 'get_info'); + prot_title.value=[mfilename ' on rig ' get_hostname ' : ' expmtr ', ' rname '. Started at ' datestr(now, 'HH:MM')]; + end + + % try send_n_done_trials(obj); end + + %% trial_completed + case 'trial_completed' + + % Do any updates in the protocol that need doing: + feval(mfilename, 'update'); + % And PokesPlot needs completing the trial: + PokesPlotSection(obj, 'trial_completed'); + %% update + case 'update' + PokesPlotSection(obj, 'update'); + + + %% close + case 'close' + PokesPlotSection(obj, 'close'); + %PunishmentSection(obj, 'close'); + SideSection(obj, 'close'); + StimulusSection(obj,'close'); + + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)) %#ok + delete(value(myfig)); + end + delete_sphandle('owner', ['^@' class(obj) '$']); + + + %% end_session + case 'end_session' + prot_title.value = [value(prot_title) ', Ended at ' datestr(now, 'HH:MM')]; + + + %% pre_saving_settings + case 'pre_saving_settings' + + StimulusSection(obj,'hide'); + SessionDefinition(obj, 'run_eod_logic_without_saving'); + perf = OverallPerformanceSection(obj, 'evaluate'); + cp_durs = SideSection(obj, 'get_cp_history'); +% [classperf tot_perf]= StimulusSection(obj, 'get_class_perform'); + + %% + % YOU NEED TO CHANGE THIS +% [stimuli] = StimulusSection(obj,'get_stimuli'); + %% + + [stim1dur] = SideSection(obj,'get_stimdur_history'); + %stim_history = StimulatorSection(obj,'get_history'); + +% CommentsSection(obj, 'append_line', ... +% sprintf(['ntrials = %d, violations = %.2f, timeouts=%.2f, hits = %.2f\n', ... +% 'pre-Go cue went from %.3f to %.3f (delta=%.3f)\n', ... +% 'Low = %.2f, High = %.2f'], ... +% perf(1), perf(2), perf(3), perf(6), cp_durs(1), cp_durs(end), cp_durs(end)-cp_durs(1), classperf(1),classperf(2))); + + pd.hits=hit_history(:); + pd.sides=previous_sides(:); + pd.viols=violation_history(:); + pd.timeouts=timeout_history(:); +% pd.performance=tot_perf(:); + pd.cp_durs=cp_durs(:); + + +% pd.stimuli=stimuli(:); + % Athena: look into pair_history perhaps stimulus_history and stimuli + % are the same + %pd.stimulus=stimulus_history(:); + + pd.stim1dur=stim1dur(:); + + %pd.stimul=stim_history(:); + + sendsummary(obj,'protocol_data','PLACEHOLDER it was just pd'); + + %% otherwise + otherwise + warning('Unknown action! "%s"\n', action); +end + +return; + diff --git a/Protocols/@SoundCatContinuous/SoundCatSMA - Copy.m b/Protocols/@SoundCatContinuous/SoundCatSMA - Copy.m new file mode 100644 index 00000000..265125ce --- /dev/null +++ b/Protocols/@SoundCatContinuous/SoundCatSMA - Copy.m @@ -0,0 +1,424 @@ + +function [varargout] = SoundCatSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + srate=SoundManagerSection(obj,'get_sample_rate'); + freq1=5; + dur1=1.5*1000; + Vol=1; + tw=Vol*(MakeBupperSwoop(srate,0, freq1 , freq1 , dur1/2 , dur1/2,0,0.1)); + SoundManagerSection(obj, 'declare_new_sound', 'LRewardSound', [tw ; zeros(1, length(tw))]) + SoundManagerSection(obj, 'declare_new_sound', 'RRewardSound', [zeros(1, length(tw));tw]) + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + case 'prepare_next_trial', + + %% Setup water + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = value(time_go_cue); %SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + RLreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RewardSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + Lreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'LRewardSound'); + Rreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RRewardSound'); + + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); + + %% Declare variables + % These will get moved to other functions as SoloParamHandles. + + WaterAmount=maxasymp + (minasymp./(1+(n_done_trials/inflp).^slp).^assym); +% WaterValvesSection(obj, 'set_water_amounts', WaterAmount, WaterAmount); +% [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + WValveTimes = GetValveTimes(WaterAmount, [2 3]); + LeftWValveTime = WValveTimes(1); + RightWValveTime = WValveTimes(2); + [LeftWMult RightWMult] = SideSection(obj, 'get_water_mult'); + LeftWValveTime=LeftWValveTime*LeftWMult; + RightWValveTime=RightWValveTime*RightWMult; + + side = SideSection(obj, 'get_current_side'); + if side == 'l' + HitEvent = 'Lin'; ErrorEvent = 'Rin'; + HitState = 'lefthit'; SideLight = left1led; + SecondHitState = 'secondlefthit'; + + else + HitEvent = 'Rin'; ErrorEvent = 'Lin'; + HitState = 'righthit'; SideLight = right1led; + SecondHitState = 'secondrighthit'; + end; + + + if strcmp(reward_type, 'Always') + LEDOn=1; + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + else + LEDOn=0; + if strcmp(reward_type, 'NoReward') + AnyReward=0; + wait_for_second_hit=error_iti; + else + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + end + end; + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); + + + % to modify it for widefield imagine + if value(imaging)==1 && ~isnan(bSettings('get', 'DIOLINES', 'scope')); + trigscope = bSettings('get', 'DIOLINES', 'scope'); + else + trigscope = nan; + end + + if value(imaging)==1 + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', ... + 0.5, 'DOut', trigscope, 'loop', 0); %for Ephys +% 99999, 'DOut', trigscope, 'loop', 0); %for Miniscope Camera + + else + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', 0); %dummy wave. + end + + % for video imagine + if ~isnan(bSettings('get', 'DIOLINES', 'video')) + trigvideo = bSettings('get', 'DIOLINES', 'video'); + else + trigvideo = nan; + end + + sma = add_scheduled_wave(sma, 'name', 'TrigVideo', 'preamble', 0, 'sustain', 99999, 'DOut', trigvideo); %dummy wave. + + + % ---BEGIN: for training stage 0 only--- + if side=='l', + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', LeftWValveTime, 'DOut', left1water); + reward_sound_id=Lreward_sound_id; + else + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', RightWValveTime, 'DOut', right1water); + reward_sound_id=Rreward_sound_id; + end; + % ---END: for training stage 0 only--- + + sma = add_scheduled_wave(sma, 'name', 'stimA1', 'preamble', PreStim_time, ... + 'sustain', A1_time, 'sound_trig', A1_sound_id); + + + + switch value(training_stage) + + case 0 %% learning the reward sound association -left or right led on -> poke -> sound+reward + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + + case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly -stimuli can be present + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led, 'SchedWaveTrig','+TrigScope+TrigVideo'}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + if stimuli_on ==0 || n_done_trials <1 + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + else + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time+0.00001, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period +stimA1'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + end + + + % nose is out and we're in "SettlingIn_time": + % if settling_legal_cbreak time elapses, go to violation state, + % if nose is put back in, go to copy of cp start + % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks + sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'settling_period_In', 'cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'settling_period_In','cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks + sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state+1', ... + 'Clo', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % nose is out and we're still in legal_cbreak: + % if legal_cbreak time elapses, go to violation_state, + % if nose is put back in, go to copy of cp start + sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', 30000, ... + 'output_actions', {'DOut', LEDOn*SideLight}, ... + 'input_to_statechange',{HitEvent, HitState, ErrorEvent, 'second_hit_state'}); + + % The two states that make a LeftHit: + %with reward sound + +% sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight', 'SoundOut', Lreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound + sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', SideLight+left1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a RightHit: + + %with reward sound +% sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight', 'SoundOut', Rreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound + sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', SideLight+right1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + + sma = add_state(sma,'name','second_hit_state','self_timer', wait_for_second_hit,... + 'output_actions',{'DOut', LEDOn*SideLight},... + 'input_to_statechange',{HitEvent, SecondHitState,'Tup','check_next_trial_ready'}); + + + % The two states that make a SecondLeftHit: + sma = add_state(sma,'name', 'secondlefthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% sma = add_state(sma, 'self_timer', reward_delay, ... +% 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Lreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+left1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a SecondRightHit: + sma = add_state(sma,'name', 'secondrighthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% sma = add_state(sma, 'self_timer', reward_delay, ... +% 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Rreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+right1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % and a common hit_state that we flick through + sma = add_state(sma, 'name', 'hit_state', 'self_timer', 0.0001, ... + 'input_to_statechange', {'Tup', 'drink_state'}); + + + end %end of swith for different training_stages + + +% sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... +% 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + if stimuli_on ==0 + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + else + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke-stimA1', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + end + + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','timeout_state','self_timer', timeout_duration,... + 'output_actions',{'SoundOut',to_sound_id},... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'output_actions',{ 'SchedWaveTrig','-TrigScope-TrigVideo'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'lefthit', 'righthit', 'hit_state','second_hit_state', 'error_state', 'violation_state','timeout_state'}; + + sma = StimulatorSection(obj,'prepare_next_trial',sma); + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors', + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'righthit', [0.3 0.9 0], ... + 'lefthit', [0 0.9 0.3], ... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + + + case 'reinit', + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/SoundCatSMA-30.m b/Protocols/@SoundCatContinuous/SoundCatSMA-30.m new file mode 100644 index 00000000..48e0983c --- /dev/null +++ b/Protocols/@SoundCatContinuous/SoundCatSMA-30.m @@ -0,0 +1,407 @@ + +function [varargout] = SoundCatSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + srate=SoundManagerSection(obj,'get_sample_rate'); + freq1=5; + dur1=1.5*1000; + Vol=0.01; + tw=Vol*(MakeBupperSwoop(srate,0, freq1 , freq1 , dur1/2 , dur1/2,0,0.1)); + SoundManagerSection(obj, 'declare_new_sound', 'LRewardSound', [tw ; zeros(1, length(tw))]) + SoundManagerSection(obj, 'declare_new_sound', 'RRewardSound', [zeros(1, length(tw));tw]) + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + case 'prepare_next_trial', + + %% Setup water + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = value(time_go_cue); %SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + RLreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RewardSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + Lreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'LRewardSound'); + Rreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RRewardSound'); + + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); + + %% Declare variables + % These will get moved to other functions as SoloParamHandles. + + WaterAmount=maxasymp + (minasymp./(1+(n_done_trials/inflp).^slp).^assym); +% WaterValvesSection(obj, 'set_water_amounts', WaterAmount, WaterAmount); +% [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + WValveTimes = GetValveTimes(WaterAmount, [2 3]); + LeftWValveTime = WValveTimes(1); + RightWValveTime = WValveTimes(2); + [LeftWMult RightWMult] = SideSection(obj, 'get_water_mult'); + LeftWValveTime=LeftWValveTime*LeftWMult; + RightWValveTime=RightWValveTime*RightWMult; + + side = SideSection(obj, 'get_current_side'); + if side == 'l' + HitEvent = 'Lin'; ErrorEvent = 'Rin'; + HitState = 'lefthit'; SideLight = left1led; + SecondHitState = 'secondlefthit'; + + else + HitEvent = 'Rin'; ErrorEvent = 'Lin'; + HitState = 'righthit'; SideLight = right1led; + SecondHitState = 'secondrighthit'; + end; + + + if strcmp(reward_type, 'Always') + LEDOn=1; + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + else + LEDOn=0; + if strcmp(reward_type, 'NoReward') + AnyReward=0; + wait_for_second_hit=error_iti; + else + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + end + end; + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); + + + % to modify it for widefield imagine + if value(imaging)==1 && ~isnan(bSettings('get', 'DIOLINES', 'scope')); + trigscope = bSettings('get', 'DIOLINES', 'scope'); + else + trigscope = nan; + end + + if value(imaging)==1 + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', ... + 0.5, 'DOut', trigscope, 'loop', 0); %for Miniscope Camera + %0.5, 'DOut', trigscope, 'loop', 0); for Ephys + else + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', 0); %dummy wave. + end + + % ---BEGIN: for training stage 0 only--- + if side=='l', + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', LeftWValveTime, 'DOut', left1water); + reward_sound_id=Lreward_sound_id; + else + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', RightWValveTime, 'DOut', right1water); + reward_sound_id=Rreward_sound_id; + end; + % ---END: for training stage 0 only--- + + sma = add_scheduled_wave(sma, 'name', 'stimA1', 'preamble', PreStim_time, ... + 'sustain', A1_time, 'sound_trig', A1_sound_id); + + + + switch value(training_stage) + + case 0 %% learning the reward sound association -left or right led on -> poke -> sound+reward + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + + case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly -stimuli can be present + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led, 'SchedWaveTrig','+TrigScope'}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + if stimuli_on ==0 || n_done_trials <1 + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + else + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time+0.00001, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period +stimA1'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + end + + + % nose is out and we're in "SettlingIn_time": + % if settling_legal_cbreak time elapses, go to violation state, + % if nose is put back in, go to copy of cp start + % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks + sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'settling_period_In', 'cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'settling_period_In','cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks + sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state+1', ... + 'Clo', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % nose is out and we're still in legal_cbreak: + % if legal_cbreak time elapses, go to violation_state, + % if nose is put back in, go to copy of cp start + sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', 30000, ... + 'output_actions', {'DOut', LEDOn*SideLight}, ... + 'input_to_statechange',{HitEvent, HitState, ErrorEvent, 'second_hit_state'}); + + % The two states that make a LeftHit: + %with reward sound + + sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight', 'SoundOut', Lreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound +% sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight'}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', SideLight+left1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a RightHit: + + %with reward sound + sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight', 'SoundOut', Rreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound +% sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight'}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', SideLight+right1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + + sma = add_state(sma,'name','second_hit_state','self_timer', wait_for_second_hit,... + 'output_actions',{'DOut', LEDOn*SideLight},... + 'input_to_statechange',{HitEvent, SecondHitState,'Tup','check_next_trial_ready'}); + + + % The two states that make a SecondLeftHit: + sma = add_state(sma,'name', 'secondlefthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Lreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+left1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a SecondRightHit: + sma = add_state(sma,'name', 'secondrighthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Rreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+right1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % and a common hit_state that we flick through + sma = add_state(sma, 'name', 'hit_state', 'self_timer', 0.0001, ... + 'input_to_statechange', {'Tup', 'drink_state'}); + + + end %end of swith for different training_stages + + +% sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... +% 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + if stimuli_on ==0 + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + else + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke-stimA1', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + end + + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','timeout_state','self_timer', timeout_duration,... + 'output_actions',{'SoundOut',to_sound_id},... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'output_actions',{ 'SchedWaveTrig','-TrigScope'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'lefthit', 'righthit', 'hit_state','second_hit_state', 'error_state', 'violation_state','timeout_state'}; + + sma = StimulatorSection(obj,'prepare_next_trial',sma); + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors', + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'righthit', [0.3 0.9 0], ... + 'lefthit', [0 0.9 0.3], ... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + + + case 'reinit', + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/SoundCatSMA-video.m b/Protocols/@SoundCatContinuous/SoundCatSMA-video.m new file mode 100644 index 00000000..3ce436c5 --- /dev/null +++ b/Protocols/@SoundCatContinuous/SoundCatSMA-video.m @@ -0,0 +1,418 @@ + +function [varargout] = SoundCatSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + srate=SoundManagerSection(obj,'get_sample_rate'); + freq1=5; + dur1=1.5*1000; + Vol=1; + tw=Vol*(MakeBupperSwoop(srate,0, freq1 , freq1 , dur1/2 , dur1/2,0,0.1)); + SoundManagerSection(obj, 'declare_new_sound', 'LRewardSound', [tw ; zeros(1, length(tw))]) + SoundManagerSection(obj, 'declare_new_sound', 'RRewardSound', [zeros(1, length(tw));tw]) + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + case 'prepare_next_trial', + + %% Setup water + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = value(time_go_cue); %SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + RLreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RewardSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + Lreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'LRewardSound'); + Rreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RRewardSound'); + + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); + + %% Declare variables + % These will get moved to other functions as SoloParamHandles. + + WaterAmount=maxasymp + (minasymp./(1+(n_done_trials/inflp).^slp).^assym); +% WaterValvesSection(obj, 'set_water_amounts', WaterAmount, WaterAmount); +% [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + WValveTimes = GetValveTimes(WaterAmount, [2 3]); + LeftWValveTime = WValveTimes(1); + RightWValveTime = WValveTimes(2); + [LeftWMult RightWMult] = SideSection(obj, 'get_water_mult'); + LeftWValveTime=LeftWValveTime*LeftWMult; + RightWValveTime=RightWValveTime*RightWMult; + + side = SideSection(obj, 'get_current_side'); + if side == 'l' + HitEvent = 'Lin'; ErrorEvent = 'Rin'; + HitState = 'lefthit'; SideLight = left1led; + SecondHitState = 'secondlefthit'; + + else + HitEvent = 'Rin'; ErrorEvent = 'Lin'; + HitState = 'righthit'; SideLight = right1led; + SecondHitState = 'secondrighthit'; + end; + + + if strcmp(reward_type, 'Always') + LEDOn=1; + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + else + LEDOn=0; + if strcmp(reward_type, 'NoReward') + AnyReward=0; + wait_for_second_hit=error_iti; + else + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + end + end; + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); + + + % to modify it for widefield imagine + if value(imaging)==1 && ~isnan(bSettings('get', 'DIOLINES', 'scope')); + trigscope = bSettings('get', 'DIOLINES', 'scope'); + else + trigscope = nan; + end + + if value(imaging)==1 + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', ... + 0.5, 'DOut', trigscope, 'loop', 0); %for Ephys +% 99999, 'DOut', trigscope, 'loop', 0); %for Miniscope Camera + + else + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', 0); %dummy wave. + end + + % for video imagine + if ~isnan(bSettings('get', 'DIOLINES', 'video')) + trigvideo = bSettings('get', 'DIOLINES', 'video'); + else + trigvideo = nan; + end + + sma = add_scheduled_wave(sma, 'name', 'TrigVideo', 'preamble', 0, 'sustain', 99999, 'DOut', trigvideo); + + + % ---BEGIN: for training stage 0 only--- + if side=='l', + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', LeftWValveTime, 'DOut', left1water); + reward_sound_id=Lreward_sound_id; + else + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', RightWValveTime, 'DOut', right1water); + reward_sound_id=Rreward_sound_id; + end; + % ---END: for training stage 0 only--- + + sma = add_scheduled_wave(sma, 'name', 'stimA1', 'preamble', PreStim_time, ... + 'sustain', A1_time, 'sound_trig', A1_sound_id); + + + + switch value(training_stage) + + case 0 %% learning the reward sound association -left or right led on -> poke -> sound+reward + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + + case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly -stimuli can be present + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led, 'SchedWaveTrig','+TrigScope+TrigVideo'}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + if stimuli_on ==0 || n_done_trials <1 + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + else + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time+0.00001, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period +stimA1'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + end + + + % nose is out and we're in "SettlingIn_time": + % if settling_legal_cbreak time elapses, go to violation state, + % if nose is put back in, go to copy of cp start + % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks + sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'settling_period_In', 'cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'settling_period_In','cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks + sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state+1', ... + 'Clo', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % nose is out and we're still in legal_cbreak: + % if legal_cbreak time elapses, go to violation_state, + % if nose is put back in, go to copy of cp start + sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', 30000, ... + 'output_actions', {'DOut', LEDOn*SideLight}, ... + 'input_to_statechange',{HitEvent, HitState, ErrorEvent, 'second_hit_state'}); + + % The two states that make a LeftHit: + %with reward sound + + sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight', 'SoundOut', Lreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound +% sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight'}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', SideLight+left1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a RightHit: + + %with reward sound + sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight', 'SoundOut', Rreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound +% sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight'}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', SideLight+right1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + + sma = add_state(sma,'name','second_hit_state','self_timer', wait_for_second_hit,... + 'output_actions',{'DOut', LEDOn*SideLight},... + 'input_to_statechange',{HitEvent, SecondHitState,'Tup','check_next_trial_ready'}); + + + % The two states that make a SecondLeftHit: + sma = add_state(sma,'name', 'secondlefthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Lreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+left1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a SecondRightHit: + sma = add_state(sma,'name', 'secondrighthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Rreward_sound_id}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+right1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % and a common hit_state that we flick through + sma = add_state(sma, 'name', 'hit_state', 'self_timer', 0.0001, ... + 'input_to_statechange', {'Tup', 'drink_state'}); + + + end %end of swith for different training_stages + + +% sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... +% 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + if stimuli_on ==0 + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + else + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke-stimA1', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + end + + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','timeout_state','self_timer', timeout_duration,... + 'output_actions',{'SoundOut',to_sound_id},... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'output_actions',{ 'SchedWaveTrig','-TrigScope-TrigVideo'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'lefthit', 'righthit', 'hit_state','second_hit_state', 'error_state', 'violation_state','timeout_state'}; + + sma = StimulatorSection(obj,'prepare_next_trial',sma); + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors', + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'righthit', [0.3 0.9 0], ... + 'lefthit', [0 0.9 0.3], ... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + + + case 'reinit', + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/SoundCatSMA.m b/Protocols/@SoundCatContinuous/SoundCatSMA.m new file mode 100644 index 00000000..265125ce --- /dev/null +++ b/Protocols/@SoundCatContinuous/SoundCatSMA.m @@ -0,0 +1,424 @@ + +function [varargout] = SoundCatSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + srate=SoundManagerSection(obj,'get_sample_rate'); + freq1=5; + dur1=1.5*1000; + Vol=1; + tw=Vol*(MakeBupperSwoop(srate,0, freq1 , freq1 , dur1/2 , dur1/2,0,0.1)); + SoundManagerSection(obj, 'declare_new_sound', 'LRewardSound', [tw ; zeros(1, length(tw))]) + SoundManagerSection(obj, 'declare_new_sound', 'RRewardSound', [zeros(1, length(tw));tw]) + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + case 'prepare_next_trial', + + %% Setup water + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = value(time_go_cue); %SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + RLreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RewardSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + Lreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'LRewardSound'); + Rreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RRewardSound'); + + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); + + %% Declare variables + % These will get moved to other functions as SoloParamHandles. + + WaterAmount=maxasymp + (minasymp./(1+(n_done_trials/inflp).^slp).^assym); +% WaterValvesSection(obj, 'set_water_amounts', WaterAmount, WaterAmount); +% [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + WValveTimes = GetValveTimes(WaterAmount, [2 3]); + LeftWValveTime = WValveTimes(1); + RightWValveTime = WValveTimes(2); + [LeftWMult RightWMult] = SideSection(obj, 'get_water_mult'); + LeftWValveTime=LeftWValveTime*LeftWMult; + RightWValveTime=RightWValveTime*RightWMult; + + side = SideSection(obj, 'get_current_side'); + if side == 'l' + HitEvent = 'Lin'; ErrorEvent = 'Rin'; + HitState = 'lefthit'; SideLight = left1led; + SecondHitState = 'secondlefthit'; + + else + HitEvent = 'Rin'; ErrorEvent = 'Lin'; + HitState = 'righthit'; SideLight = right1led; + SecondHitState = 'secondrighthit'; + end; + + + if strcmp(reward_type, 'Always') + LEDOn=1; + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + else + LEDOn=0; + if strcmp(reward_type, 'NoReward') + AnyReward=0; + wait_for_second_hit=error_iti; + else + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + end + end; + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); + + + % to modify it for widefield imagine + if value(imaging)==1 && ~isnan(bSettings('get', 'DIOLINES', 'scope')); + trigscope = bSettings('get', 'DIOLINES', 'scope'); + else + trigscope = nan; + end + + if value(imaging)==1 + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', ... + 0.5, 'DOut', trigscope, 'loop', 0); %for Ephys +% 99999, 'DOut', trigscope, 'loop', 0); %for Miniscope Camera + + else + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', 0); %dummy wave. + end + + % for video imagine + if ~isnan(bSettings('get', 'DIOLINES', 'video')) + trigvideo = bSettings('get', 'DIOLINES', 'video'); + else + trigvideo = nan; + end + + sma = add_scheduled_wave(sma, 'name', 'TrigVideo', 'preamble', 0, 'sustain', 99999, 'DOut', trigvideo); %dummy wave. + + + % ---BEGIN: for training stage 0 only--- + if side=='l', + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', LeftWValveTime, 'DOut', left1water); + reward_sound_id=Lreward_sound_id; + else + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', RightWValveTime, 'DOut', right1water); + reward_sound_id=Rreward_sound_id; + end; + % ---END: for training stage 0 only--- + + sma = add_scheduled_wave(sma, 'name', 'stimA1', 'preamble', PreStim_time, ... + 'sustain', A1_time, 'sound_trig', A1_sound_id); + + + + switch value(training_stage) + + case 0 %% learning the reward sound association -left or right led on -> poke -> sound+reward + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + + case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly -stimuli can be present + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led, 'SchedWaveTrig','+TrigScope+TrigVideo'}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + if stimuli_on ==0 || n_done_trials <1 + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + else + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', SettlingIn_time+0.00001, ... + 'output_actions', {'SchedWaveTrig', 'center_poke + settling_period +stimA1'}, ... + 'input_to_statechange', {'Tup', 'cp_legal_cbreak_period', ... + 'Cout','current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + end + + + % nose is out and we're in "SettlingIn_time": + % if settling_legal_cbreak time elapses, go to violation state, + % if nose is put back in, go to copy of cp start + % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks + sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'settling_period_In', 'cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'settling_period_In','cp_legal_cbreak_period', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks + sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state+1', ... + 'Clo', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % nose is out and we're still in legal_cbreak: + % if legal_cbreak time elapses, go to violation_state, + % if nose is put back in, go to copy of cp start + sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... + 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... + 'input_to_statechange', {'Tup', 'violation_state', ... + 'Cin', 'current_state+1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + % center poke: + % A copy of two states above, but without triggering the + % start of the center_poke scheduled wave. + sma = add_state(sma, 'self_timer', 10000, ... + 'input_to_statechange', {'Cout', 'current_state-1', ... + 'center_poke_Out', 'wait_for_collecting_reward', ... + 'Rin', 'violation_state', ... + 'Rout', 'violation_state', ... + 'Lin', 'violation_state', ... + 'Lout', 'violation_state'}); + + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', 30000, ... + 'output_actions', {'DOut', LEDOn*SideLight}, ... + 'input_to_statechange',{HitEvent, HitState, ErrorEvent, 'second_hit_state'}); + + % The two states that make a LeftHit: + %with reward sound + +% sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight', 'SoundOut', Lreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound + sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', SideLight+left1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a RightHit: + + %with reward sound +% sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight', 'SoundOut', Rreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound + sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', SideLight+right1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + + sma = add_state(sma,'name','second_hit_state','self_timer', wait_for_second_hit,... + 'output_actions',{'DOut', LEDOn*SideLight},... + 'input_to_statechange',{HitEvent, SecondHitState,'Tup','check_next_trial_ready'}); + + + % The two states that make a SecondLeftHit: + sma = add_state(sma,'name', 'secondlefthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% sma = add_state(sma, 'self_timer', reward_delay, ... +% 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Lreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+left1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a SecondRightHit: + sma = add_state(sma,'name', 'secondrighthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% sma = add_state(sma, 'self_timer', reward_delay, ... +% 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Rreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+right1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % and a common hit_state that we flick through + sma = add_state(sma, 'name', 'hit_state', 'self_timer', 0.0001, ... + 'input_to_statechange', {'Tup', 'drink_state'}); + + + end %end of swith for different training_stages + + +% sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... +% 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + if stimuli_on ==0 + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + else + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke-stimA1', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + end + + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','timeout_state','self_timer', timeout_duration,... + 'output_actions',{'SoundOut',to_sound_id},... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'output_actions',{ 'SchedWaveTrig','-TrigScope-TrigVideo'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'lefthit', 'righthit', 'hit_state','second_hit_state', 'error_state', 'violation_state','timeout_state'}; + + sma = StimulatorSection(obj,'prepare_next_trial',sma); + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors', + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'righthit', [0.3 0.9 0], ... + 'lefthit', [0 0.9 0.3], ... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + + + case 'reinit', + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/SoundCatSMART.m b/Protocols/@SoundCatContinuous/SoundCatSMART.m new file mode 100644 index 00000000..542366b4 --- /dev/null +++ b/Protocols/@SoundCatContinuous/SoundCatSMART.m @@ -0,0 +1,413 @@ + +function [varargout] = SoundCatSMA(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init' + + srate=SoundManagerSection(obj,'get_sample_rate'); + freq1=5; + dur1=1.5*1000; + Vol=1; + tw=Vol*(MakeBupperSwoop(srate,0, freq1 , freq1 , dur1/2 , dur1/2,0,0.1)); + SoundManagerSection(obj, 'declare_new_sound', 'LRewardSound', [tw ; zeros(1, length(tw))]) + SoundManagerSection(obj, 'declare_new_sound', 'RRewardSound', [zeros(1, length(tw));tw]) + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + case 'prepare_next_trial', + + %% Setup water + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + %% Setup sounds + sone_sound_id = SoundManagerSection(obj, 'get_sound_id', 'SOneSound'); + go_sound_id = SoundManagerSection(obj, 'get_sound_id', 'GoSound'); + go_cue_duration = value(time_go_cue); %SoundManagerSection(obj, 'get_sound_duration', 'GoSound'); + RLreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RewardSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + viol_snd_duration = SoundManagerSection(obj, 'get_sound_duration', 'ViolationSound'); + to_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + timeout_duration = SoundManagerSection(obj, 'get_sound_duration', 'TimeoutSound'); + Lreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'LRewardSound'); + Rreward_sound_id = SoundManagerSection(obj, 'get_sound_id', 'RRewardSound'); + + + A1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimAUD1'); + + %% Declare variables + % These will get moved to other functions as SoloParamHandles. + + WaterAmount=maxasymp + (minasymp./(1+(n_done_trials/inflp).^slp).^assym); +% WaterValvesSection(obj, 'set_water_amounts', WaterAmount, WaterAmount); +% [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + WValveTimes = GetValveTimes(WaterAmount, [2 3]); + LeftWValveTime = WValveTimes(1); + RightWValveTime = WValveTimes(2); + [LeftWMult RightWMult] = SideSection(obj, 'get_water_mult'); + LeftWValveTime=LeftWValveTime*LeftWMult; + RightWValveTime=RightWValveTime*RightWMult; + + side = SideSection(obj, 'get_current_side'); + if side == 'l' + HitEvent = 'Lin'; ErrorEvent = 'Rin'; + HitState = 'lefthit'; SideLight = left1led; + SecondHitState = 'secondlefthit'; + + else + HitEvent = 'Rin'; ErrorEvent = 'Lin'; + HitState = 'righthit'; SideLight = right1led; + SecondHitState = 'secondrighthit'; + end; + + + if strcmp(reward_type, 'Always') + LEDOn=1; + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + else + LEDOn=0; + if strcmp(reward_type, 'NoReward') + AnyReward=0; + wait_for_second_hit=error_iti; + else + AnyReward=1; + wait_for_second_hit=30000; + error_iti=0; + end + end; + + + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + sma = add_scheduled_wave(sma, 'name', 'center_poke', 'preamble', CP_duration, ... + 'sustain', go_cue_duration, 'sound_trig', go_sound_id); + + sma = add_scheduled_wave(sma, 'name', 'settling_period', 'preamble', SettlingIn_time); + + + % to modify it for widefield imagine + if value(imaging)==1 && ~isnan(bSettings('get', 'DIOLINES', 'scope')); + trigscope = bSettings('get', 'DIOLINES', 'scope'); + else + trigscope = nan; + end + + if value(imaging)==1 + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', ... + 0.5, 'DOut', trigscope, 'loop', 0); %for Ephys +% 99999, 'DOut', trigscope, 'loop', 0); %for Miniscope Camera + + else + sma = add_scheduled_wave(sma, 'name', 'TrigScope', 'preamble', 0, 'sustain', 0); %dummy wave. + end + + % for video imagine + if ~isnan(bSettings('get', 'DIOLINES', 'video')) + trigvideo = bSettings('get', 'DIOLINES', 'video'); + else + trigvideo = nan; + end + + sma = add_scheduled_wave(sma, 'name', 'TrigVideo', 'preamble', 0, 'sustain', 99999, 'DOut', trigvideo); %dummy wave. + + + % ---BEGIN: for training stage 0 only--- + if side=='l', + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', LeftWValveTime, 'DOut', left1water); + reward_sound_id=Lreward_sound_id; + else + sma = add_scheduled_wave(sma, 'name', 'reward_delivery', 'preamble', reward_delay, ... + 'sustain', RightWValveTime, 'DOut', right1water); + reward_sound_id=Rreward_sound_id; + end; + % ---END: for training stage 0 only--- + + sma = add_scheduled_wave(sma, 'name', 'stimA1', 'preamble', PreStim_time, ... + 'sustain', A1_time, 'sound_trig', A1_sound_id); + + + + switch value(training_stage) + + case 0 %% learning the reward sound association -left or right led on -> poke -> sound+reward + sma = add_state(sma, 'name', 'sideled_on', 'self_timer', SideLed_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{'Tup','wait_for_collecting_reward'}); + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', RewardCollection_duration, ... + 'output_actions', {'DOut', SideLight}, ... + 'input_to_statechange',{HitEvent,'hit_state','Tup','wait_for_collecting_reward',ErrorEvent,'second_hit_state'}); + + sma = add_state(sma,'name','second_hit_state','self_timer',RewardCollection_duration,... + 'output_actions',{'DOut', SideLight},... + 'input_to_statechange',{'Tup','second_hit_state',HitEvent,'hit_state'}); + + sma = add_state(sma,'name','hit_state','self_timer',0.01,... + 'output_actions', {'DOut', SideLight,'SchedWaveTrig','reward_delivery','SoundOut',reward_sound_id},... + 'input_to_statechange',{'Tup','drink_state'}); + + + case 1 %% center led on -> poke in the center -> go cue -> reward light and sound -- waiting time grows slowlly -stimuli can be present + + sma = add_state(sma,'name','wait_for_cpoke','self_timer',CenterLed_duration, ... + 'output_actions', {'DOut', center1led, 'SchedWaveTrig','+TrigScope+TrigVideo'}, ... + 'input_to_statechange', {'Cin','cp';'Tup','timeout_state'}); + + + if stimuli_on ==0 || n_done_trials <1 + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', 30000, ... + 'output_actions', {'SchedWaveTrig', 'center_poke'}, ... + 'input_to_statechange', {'Cout','wait_for_collecting_reward'}); + else + + % center poke starts: trigger center_poke scheduled wave, + % and when that ends go to side_led_on + sma = add_state(sma,'name','cp','self_timer', 30000, ... + 'output_actions', {'SchedWaveTrig', 'center_poke+stimA1'}, ... + 'input_to_statechange', {'Cout','wait_for_collecting_reward'}); + end + + +% % nose is out and we're in "SettlingIn_time": +% % if settling_legal_cbreak time elapses, go to violation state, +% % if nose is put back in, go to copy of cp start +% % when SettlingIn_time elapses (settling_period_In) "legal cbreaks" changes to usueal legal_cbreaks +% sma = add_state(sma, 'self_timer', settling_legal_cbreak+0.00001, ... +% 'output_actions', {'DOut', center1led*LED_during_settling_legal_cbreak}, ... +% 'input_to_statechange', {'Tup', 'violation_state', ... +% 'Cin', 'current_state+1', ... +% 'settling_period_In', 'cp_legal_cbreak_period', ... +% 'center_poke_Out', 'wait_for_collecting_reward', ... +% 'Rin', 'violation_state', ... +% 'Rout', 'violation_state', ... +% 'Lin', 'violation_state', ... +% 'Lout', 'violation_state'}); +% +% % center poke: +% % A copy of two states above, but without triggering the +% % start of the center_poke scheduled wave. +% sma = add_state(sma, 'self_timer', 10000, ... +% 'input_to_statechange', {'Cout', 'current_state-1', ... +% 'settling_period_In','cp_legal_cbreak_period', ... +% 'center_poke_Out', 'wait_for_collecting_reward', ... +% 'Rin', 'violation_state', ... +% 'Rout', 'violation_state', ... +% 'Lin', 'violation_state', ... +% 'Lout', 'violation_state'}); +% +% % SettlingIn_time elapsed and from now on cbreaks are treated given legal_cbreaks +% sma = add_state(sma,'name','cp_legal_cbreak_period', 'self_timer', 10000, ... +% 'input_to_statechange', {'Cout', 'current_state+1', ... +% 'Clo', 'current_state+1', ... +% 'center_poke_Out', 'wait_for_collecting_reward', ... +% 'Rin', 'violation_state', ... +% 'Rout', 'violation_state', ... +% 'Lin', 'violation_state', ... +% 'Lout', 'violation_state'}); +% +% % nose is out and we're still in legal_cbreak: +% % if legal_cbreak time elapses, go to violation_state, +% % if nose is put back in, go to copy of cp start +% sma = add_state(sma, 'self_timer', legal_cbreak+0.00001, ... +% 'output_actions', {'DOut', center1led*LED_during_legal_cbreak}, ... +% 'input_to_statechange', {'Tup', 'violation_state', ... +% 'Cin', 'current_state+1', ... +% 'center_poke_Out', 'wait_for_collecting_reward', ... +% 'Rin', 'violation_state', ... +% 'Rout', 'violation_state', ... +% 'Lin', 'violation_state', ... +% 'Lout', 'violation_state'}); +% +% % center poke: +% % A copy of two states above, but without triggering the +% % start of the center_poke scheduled wave. +% sma = add_state(sma, 'self_timer', 10000, ... +% 'input_to_statechange', {'Cout', 'current_state-1', ... +% 'center_poke_Out', 'wait_for_collecting_reward', ... +% 'Rin', 'violation_state', ... +% 'Rout', 'violation_state', ... +% 'Lin', 'violation_state', ... +% 'Lout', 'violation_state'}); + + + sma = add_state(sma, 'name', 'wait_for_collecting_reward', 'self_timer', 30000, ... + 'output_actions', {'DOut', LEDOn*SideLight, 'SchedWaveTrig','-stimA1'}, ... + 'input_to_statechange',{HitEvent, HitState, ErrorEvent, 'second_hit_state'}); + + % The two states that make a LeftHit: + %with reward sound + +% sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight', 'SoundOut', Lreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound + sma = add_state(sma,'name', 'lefthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', SideLight+left1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a RightHit: + + %with reward sound +% sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... +% 'output_actions', {'DOut', SideLight', 'SoundOut', Rreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + + % without reward sound + sma = add_state(sma,'name', 'righthit','self_timer', reward_delay, ... + 'output_actions', {'DOut', SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', SideLight+right1water,},... + 'input_to_statechange',{'Tup','hit_state'}); + + + sma = add_state(sma,'name','second_hit_state','self_timer', wait_for_second_hit,... + 'output_actions',{'DOut', LEDOn*SideLight},... + 'input_to_statechange',{HitEvent, SecondHitState,'Tup','check_next_trial_ready'}); + + + % The two states that make a SecondLeftHit: + sma = add_state(sma,'name', 'secondlefthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% sma = add_state(sma, 'self_timer', reward_delay, ... +% 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Lreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', LeftWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+left1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % The two states that make a SecondRightHit: + sma = add_state(sma,'name', 'secondrighthit','self_timer', secondhit_delay, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); +% sma = add_state(sma, 'self_timer', reward_delay, ... +% 'output_actions', {'DOut', AnyReward*SideLight', 'SoundOut', AnyReward*Rreward_sound_id}, ... +% 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', reward_delay, ... + 'output_actions', {'DOut', AnyReward*SideLight'}, ... + 'input_to_statechange', {'Tup', 'current_state+1'}); + sma = add_state(sma, 'self_timer', RightWValveTime, ... + 'output_actions', {'DOut', AnyReward*(SideLight+right1water)},... + 'input_to_statechange',{'Tup','hit_state'}); + + % and a common hit_state that we flick through + sma = add_state(sma, 'name', 'hit_state', 'self_timer', 0.0001, ... + 'input_to_statechange', {'Tup', 'drink_state'}); + + + end %end of swith for different training_stages + + +% sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... +% 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + sma = add_state(sma,'name','drink_state','self_timer',AnyReward*drink_time+error_iti,... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + if stimuli_on ==0 + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + else + + sma = add_state(sma,'name','violation_state','self_timer',viol_snd_duration,... + 'output_actions',{'SchedWaveTrig', '-center_poke-stimA1', ... + 'SoundOut',viol_sound_id, 'DOut', center1led},... + 'input_to_statechange', {'Tup', 'current_state+1'}); + end + + sma = add_state(sma, 'self_timer', max(0.001, violation_iti-viol_snd_duration), ... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + sma = add_state(sma,'name','timeout_state','self_timer', timeout_duration,... + 'output_actions',{'SoundOut',to_sound_id},... + 'input_to_statechange',{'Tup','preclean_up_state'}); + + + sma = add_state(sma,'name','preclean_up_state','self_timer',0.5,... + 'output_actions',{ 'SchedWaveTrig','-TrigScope-TrigVideo'},... + 'input_to_statechange',{'Tup','check_next_trial_ready'}); + + varargout{2} = {'check_next_trial_ready'}; + + varargout{1} = sma; + + % Not all 'prepare_next_trial_states' are defined in all training + % stages. So we send to dispatcher only those states that are + % defined. + state_names = get_labels(sma); state_names = state_names(:,1); + prepare_next_trial_states = {'lefthit', 'righthit', 'hit_state','second_hit_state', 'error_state', 'violation_state','timeout_state'}; + + sma = StimulatorSection(obj,'prepare_next_trial',sma); + dispatcher('send_assembler', sma, intersect(state_names, prepare_next_trial_states)); + + case 'get_state_colors', + varargout{1} = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'righthit', [0.3 0.9 0], ... + 'lefthit', [0 0.9 0.3], ... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); + % 'go_cue_on', [0.63 1 0.94]*0.6, ... + % 'prerw_postcs', [0.25 0.45 0.48], ... + % 'lefthit', [0.53 0.78 1.00], ... + % 'lefthit_pasound', [0.53 0.78 1.00]*0.7, ... + % 'righthit', [0.52 1.0 0.60], ... + % 'righthit_pasound', [0.52 1.0 0.60]*0.7, ... + % 'warning', [0.3 0 0], ... + % 'danger', [0.5 0.05 0.05], ... + % 'hit', [0 1 0] + + + + + case 'reinit', + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); + + otherwise + warning('do not know how to do %s',action); +end \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/SoundSection.m b/Protocols/@SoundCatContinuous/SoundSection.m new file mode 100644 index 00000000..4e49253d --- /dev/null +++ b/Protocols/@SoundCatContinuous/SoundSection.m @@ -0,0 +1,63 @@ + + +function [x, y] = SoundSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2, + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end; + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'SoundsShow', 0, x, y, 'OnString', 'Sounds', ... + 'OffString', 'Sounds', 'TooltipString', 'Show/Hide Sounds panel'); + set_callback(SoundsShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + oldx=x; oldy=y; parentfig=double(gcf); + + SoloParamHandle(obj, 'myfig', 'value', figure('Position', [100 100 560 440], ... + 'closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', mfilename), 'saveable', 0); + set(double(gcf), 'Visible', 'off'); + x=10;y=10; + + [x,y]=SoundInterface(obj,'add','ViolationSound',x,y,'Volume',0.005,'Freq',9000,'Duration',0.5); +% [x,y]=SoundInterface(obj,'add','ViolationSound',x,y,'Style','WhiteNoise','Volume',0.01); + [x,y]=SoundInterface(obj,'add','ErrorSound',x,y,'Style','WhiteNoise','Volume',0.08); + [x,y]=SoundInterface(obj,'add','TimeoutSound',x,y,'Style','WhiteNoise','Volume',0.08,'Duration',0.5); + next_column(x); + y=10; + [x,y]=SoundInterface(obj,'add','GoSound',x,y,'Style','Tone','Volume',0.005,'Freq',3000,'Duration',0.2); + SoundInterface(obj, 'disable', 'GoSound', 'Dur1'); + + [x,y]=SoundInterface(obj,'add','RewardSound',x,y,'Style','Bups','Volume',1,'Freq',5,'Duration',1.5); + [x,y]=SoundInterface(obj,'add','SOneSound',x,y,'Style','Tone','Volume',0.005,'Freq',3000,'Duration',0.2); + SoundInterface(obj, 'disable', 'GoSound', 'Dur1'); + SoundInterface(obj, 'disable', 'GoSound', 'Freq1'); + [x,y]=SoundInterface(obj,'add','STwoSound',x,y,'Style','WhiteNoise','Volume',0.08); + + + + x=oldx; y=oldy; + figure(parentfig); + + case 'hide', + SoundsShow.value = 0; set(value(myfig), 'Visible', 'off'); + + case 'show', + SoundsShow.value = 1; set(value(myfig), 'Visible', 'on'); + + case 'show_hide', + if SoundsShow == 1, set(value(myfig), 'Visible', 'on'); %#ok (defined by GetSoloFunctionArgs) + else set(value(myfig), 'Visible', 'off'); + end; + +end + \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/StimulatorSection.m b/Protocols/@SoundCatContinuous/StimulatorSection.m new file mode 100644 index 00000000..ff91fbe0 --- /dev/null +++ b/Protocols/@SoundCatContinuous/StimulatorSection.m @@ -0,0 +1,325 @@ +% +% PARAMETERS: +% ----------- +% +% obj Default object argument. +% +% action One of: +% +% 'prepare_next_trial' +% +% 'init' +% +% +% RETURNS: +% -------- +% +% +% +% +% +% + + + +function [varargout] = StimulatorSection(obj, action, x, y) + +GetSoloFunctionArgs(obj); + +switch action + +%% init +% ---------------------------------------------------------------- +% +% INIT +% +% ---------------------------------------------------------------- + + case 'init' + %NumeditParam(obj,'LCB_onstim', 0,x,y,'position',[x y 100 20],'labelfraction',0.6); + %NumeditParam(obj,'LCB_nostim', 0,x,y,'position',[x+100 y 100 20],'labelfraction',0.6); next_row(y); + %SoloParamHandle(obj,'LegalCBrk_temp', 'value',0); + + MenuParam(obj,'StimInterval',{'WholeTrial','S1','DelayDur','GoCue'},1,x,y,'labelfraction',0.30); next_row(y); + set_callback(StimInterval, {mfilename, 'StimInterval'}); + MenuParam(obj,'StimOnSide',{'both','left','right'},1,x,y,'labelfraction',0.3); next_row(y); + + diolines = bSettings('get','DIOLINES', 'all'); + for i = 1:size(diolines,1); dionames{i} = diolines{i,1}; dionums(i) = diolines{i,2}; end %#ok + [dionums,order] = sort(dionums); + dionames2 = cell(0); + for i = 1:length(dionums); if ~isnan(dionums(i)); dionames2{end+1} = dionames{order(i)}; end; end %#ok + dionames2 = cell(0); + dionames2{1} = 'opto'; + + MenuParam(obj,'StimLine',dionames2,1,x,y,'labelfraction',0.30); next_row(y); + + SC = state_colors(obj); + WC = wave_colors(obj); + states = fieldnames(SC); + waves = fieldnames(WC); + states(2:end+1) = states; + states{1} = 'cp'; + states(end+1:end+length(waves)) = waves; + + MenuParam(obj,'StimState',states,1,x,y,'labelfraction',0.30); next_row(y); + + NumeditParam(obj,'StimStates', 1,x,y,'position',[x y 100 20],'labelfraction',0.60); + NumeditParam(obj,'StimLines', 1,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); + + NumeditParam(obj,'StartDelay', 0,x,y,'position',[x y 100 20],'labelfraction',0.60); + NumeditParam(obj,'StimFreq', 20,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); + NumeditParam(obj,'PulseWidth',15,x,y,'position',[x y 100 20],'labelfraction',0.60); + NumeditParam(obj,'NumPulses', 1,x,y,'position',[x+100 y 100 20],'labelfraction',0.60); next_row(y); + + DispParam(obj,'SD',0 ,x,y,'position',[x y 50 20],'labelfraction',0.4); + DispParam(obj,'SF',20,x,y,'position',[x+50 y 50 20],'labelfraction',0.4); + DispParam(obj,'PW',15,x,y,'position',[x+100 y 50 20],'labelfraction',0.4); + DispParam(obj,'NP',1,x,y,'position',[x+150 y 50 20],'labelfraction',0.4); next_row(y); + + NumeditParam(obj,'StimProb', 0,x,y,'position',[x y 100 20],'labelfraction',0.65); + ToggleParam( obj,'ShuffleValues',0,x,y,'position',[x+100 y 100 20],'OnString','Shuffle','OffString','Lock'); next_row(y); + + SoloParamHandle(obj, 'stimulator_history', 'value', []); + + SubheaderParam(obj, 'title', 'Stimulator Section', x, y); next_row(y); + varargout{1} = x; + varargout{2} = y; + +%% update_values +% ----------------------------------------------------------------------- +% +% UPDATE_VALUES +% +% ----------------------------------------------------------------------- + + case 'update_values' + + StimulatorSection(obj,'StimInterval'); + sh = value(stimulator_history); %#ok + %if n_done_trials == 0 || sh(end) == 0 + % LegalCBrk_temp.value = value(LegalCBrk); %#ok + %end + + if ~dispatcher('is_running') + %dispatcher is not running, last stim_hist not used, lop it off + sh = sh(1:end-1); + end + + if value(StimProb) == 0 + %LCB_nostim.value = value(LegalCBrk); %#ok + stimulator_history.value = [sh, 0]; + elseif rand(1) <= value(StimProb) %&& ((value(StimOnFree)==1 && strcmp(value(ThisTrial_Free),'FREE')) || value(StimOnFree)==0) + stimulator_history.value = [sh, 1]; + %LegalCBrk.value = value(LCB_onstim); + else + %LegalCBrk.value = value(LCB_nostim); %#ok + stimulator_history.value = [sh, 0]; + end + + + +%% prepare_next_trial +% ----------------------------------------------------------------------- +% +% PREPARE_NEXT_TRIAL +% +% ----------------------------------------------------------------------- + + case 'prepare_next_trial' + sh = value(stimulator_history); %#ok + + sma = x; + + sd = value(StartDelay); + sf = value(StimFreq); + pw = value(PulseWidth); + np = value(NumPulses); + ss = value(StimStates); + sl = value(StimLines); + + if value(ShuffleValues) == 1 + sd = sd(ceil(rand(1) * length(sd))); + sf = sf(ceil(rand(1) * length(sf))); + pw = pw(ceil(rand(1) * length(pw))); + np = np(ceil(rand(1) * length(np))); + ss = ss(ceil(rand(1) * length(ss))); + sl = sl(ceil(rand(1) * length(sl))); + else + if length(unique([length(sd) length(sf) length(pw) length(np) length(ss) length(sl)])) > 1 + disp('Warning: param values in StimulatorSection have different lengths. Only first value will be used.'); + temp = 1; + else + temp = ceil(rand(1) * length(sd)); + end + sd = sd(temp); sf = sf(temp); pw = pw(temp); np = np(temp); ss = ss(temp); sl = sl(temp); + end + + pss = get(get_ghandle(StimState),'String'); %#ok + psl = get(get_ghandle(StimLine), 'String'); %#ok + if ss > length(pss) + disp('StimState value greater than list of possible stim states'); + else + StimState.value = ss; + disp('test ss') + value(ss) + end + + if sl > length(psl) + slc = ['0',num2str(sl),'0']; + z = find(slc == '0'); + if length(z) > 2 + sln = []; + for i = 1:length(z)-1 + sln(end+1) = str2num(slc(z(i)+1:z(i+1)-1)); %#ok + end + if any(sln > length(psl)) + disp('StimLine value greater than list of possible stim lines'); + else + slname = psl{sln(1)}; + for i=2:length(sln) + slname = [slname,'+',psl{sln(i)}]; %#ok + end + if sum(strcmp(psl,slname)) == 0 + psl{end+1} = slname; + set(get_ghandle(StimLine),'String',psl) + end + StimLine.value = find(strcmp(psl,slname)==1,1,'first'); + sl = sln; + end + else + disp('StimLine value greater than list of possible stim lines'); + end + else + StimLine.value = sl; + end + + for i = 1:length(sl) + stimline = bSettings('get','DIOLINES',psl{sl(i)}); + + sma = add_scheduled_wave(sma,... + 'name', ['stimulator_wave',num2str(i)],... + 'preamble', (1/sf)-(pw/1000),... %%%% Remember: change it such that if this is negative makes it 0 + 'sustain' , pw/1000,... + 'DOut', stimline,... + 'loop', np-1); + + if sd ~= 0 + sma = add_scheduled_wave(sma,... + 'name',['stimulator_wave_pause',num2str(i)],... + 'preamble',sd,... + 'trigger_on_up',['stimulator_wave',num2str(i)]); + else + sma = add_scheduled_wave(sma,... + 'name',['stimulator_wave_pause',num2str(i)],... + 'preamble',1,... + 'trigger_on_up',['stimulator_wave',num2str(i)]); + end + end + + for i = 1:length(sl) + if sh(end) == 1 + if strcmp(value(StimState),'none') == 0 + if sd ~= 0 + sma = add_stimulus(sma,['stimulator_wave_pause',num2str(i)],value(StimState)); + else + sma = add_stimulus(sma,['stimulator_wave',num2str(i)],value(StimState)); + end + + SD.value = sd; SF.value = sf; PW.value = pw; NP.value = np; + end + else + SD.value = 0; SF.value = 0; PW.value = 0; NP.value = 0; + end + end + + varargout{1} = sma; + + + %% Case StimInterval + case 'StimInterval' + + if strcmp(StimInterval, 'WholeTrial') + PulseWidth.value = Total_CP_duration*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time; + elseif strcmp(StimInterval, 'S1') + PulseWidth.value = A1_time*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time; + elseif strcmp(StimInterval, 'DelayDur') + PulseWidth.value = time_bet_aud1_gocue*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time + A1_time; + elseif strcmp(StimInterval, 'GoCue') + PulseWidth.value = time_go_cue*1000; + StimFreq.value = 1000/PulseWidth; + StartDelay.value = PreStim_time + A1_time + time_bet_aud1_gocue; + + end +%% set +% ----------------------------------------------------------------------- +% +% SET +% +% ----------------------------------------------------------------------- + case 'set' + varname = x; + newval = y; + + try + temp = 'SoloParamHandle'; %#ok + eval(['test = isa(',varname,',temp);']); + if test == 1 + eval([varname,'.value = newval;']); + end + catch %#ok + showerror; + warning(['Unable to assign value: ',num2str(newval),' to SoloParamHandle: ',varname]); %#ok + end + + +%% get +% ----------------------------------------------------------------------- +% +% GET +% +% ----------------------------------------------------------------------- + case 'get' + varname = x; + + try + temp = 'SoloParamHandle'; %#ok + eval(['test = isa(',varname,',temp);']); + if test == 1 + eval(['varargout{1} = value(',varname,');']); + end + catch %#ok + showerror; + warning(['Unable to get value from SoloParamHandle: ',varname]); %#ok + end + + +%% reinit +% ----------------------------------------------------------------------- +% +% REINIT +% +% ----------------------------------------------------------------------- + case 'reinit' + currfig = double(gcf); + + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + + % Reinitialise at the original GUI position and figure: + feval(mfilename, obj, 'init'); + + % Restore the current figure: + figure(currfig); +end + + diff --git a/Protocols/@SoundCatContinuous/StimulusSection.m b/Protocols/@SoundCatContinuous/StimulusSection.m new file mode 100644 index 00000000..daaed21f --- /dev/null +++ b/Protocols/@SoundCatContinuous/StimulusSection.m @@ -0,0 +1,435 @@ + + +function [x, y] = StimulusSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2, + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end; + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'StimulusShow', 0, x, y, 'OnString', 'Stimuli', ... + 'OffString', 'Stimuli', 'TooltipString', 'Show/Hide Stimulus panel'); + set_callback(StimulusShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + + SoloParamHandle(obj, 'myfig', 'value', figure('closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', mfilename), 'saveable', 0); + screen_size = get(0, 'ScreenSize'); + set(value(myfig),'Position',[1 screen_size(4)-740, 1000 1000]); % put fig at top right + set(double(gcf), 'Visible', 'off'); + x=10;y=10; + + SoloParamHandle(obj, 'ax', 'saveable', 0, ... + 'value', axes('Position', [0.01 0.5 0.45 0.45])); + ylabel('log_e A','FontSize',16,'FontName','Cambria Math'); + set(value(ax),'Fontsize',15) + xlabel('Sound Categorization','FontSize',16,'FontName','Cambria Math') + + SoloParamHandle(obj, 'axperf', 'saveable', 0, ... + 'value', axes('Position', [0.5 0.5 0.45 0.45])); + ylabel('log_e A','FontSize',16,'FontName','Cambria Math'); + set(value(axperf),'Fontsize',15) + xlabel('Sound Categorization','FontSize',16,'FontName','Cambria Math') + + SoundManagerSection(obj, 'declare_new_sound', 'StimAUD1') + SoloParamHandle(obj, 'thisstim', 'value', []); + SoloParamHandle(obj, 'thisstimlog', 'value', []); + SoloParamHandle(obj, 'h1', 'value', []); + + y=5; + + next_row(y); + next_row(y); + PushbuttonParam(obj, 'refresh_stimuli', x,y , 'TooltipString', 'Instantiates the stimuli given the new set of parameters'); + set_callback(refresh_stimuli, {mfilename, 'plot_stimuli'}); + + + next_row(y); + next_row(y); + MenuParam(obj, 'Rule', {'S2>S_boundry Left','S2>S_boundry Right'}, ... + 'S2>S_boundry Left', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nThis bottom determines the rule\n', ... + '\n''S2>S_boundry Left'' means if Aud2 > Aud_boundry then reward will be delivered from the left water spout and if Aud2 < Aud_boundry then water comes form right\n',... + '\n''S2>S_boundry Right'' means if Aud2 < Aud_boundry then reward will be delivered from the left water spout and if Aud2 > Aud_boundry then water comes from right\n'])); + next_row(y, 1) + + next_column(x); + y=5; + MenuParam(obj, 'filter_type', {'GAUS','LPFIR', 'FIRLS','BUTTER','MOVAVRG','KAISER','EQUIRIP','HAMMING'}, ... + 'GAUS', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent filters. ''LPFIR'': lowpass FIR ''FIRLS'': Least square linear-phase FIR filter design\n', ... + '\n''BUTTER'': IIR Butterworth lowpass filter ''GAUS'': Gaussian filter (window)\n', ... + '\n''MOVAVRG'': Moving average FIR filter ''KAISER'': Kaiser-window FIR filtering\n', ... + '\n''EQUIRIP'':Eqiripple FIR filter ''HAMMING'': Hamming-window based FIR'])); + next_row(y, 1) + NumeditParam(obj,'fcut',110,x,y,'label','fcut','TooltipString','Cut off frequency on the original white noise'); + next_row(y); + NumeditParam(obj,'lfreq',2000,x,y,'label','Modulator_LowFreq','TooltipString','Lower bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'hfreq',20000,x,y,'label','Modulator_HighFreq','TooltipString','Upper bound for the frequency modulator'); + next_row(y); + DispParam(obj, 'A1_sigma', 0.01, x,y,'label','A1_sigma','TooltipString','Sigma value for the first stimulus'); + next_row(y); + DispParam(obj, 'A1_freq', 0.01, x,y,'label','A1_freq','TooltipString','Sigma value for the first stimulus'); + next_row(y); + NumeditParam(obj,'minS1',0.007,x,y,'label','minS1','TooltipString','min sigma value for AUD1'); + next_row(y); + NumeditParam(obj,'maxS1',0.05,x,y,'label','maxS1','TooltipString','max sigma value for AUD1'); + next_row(y); + NumeditParam(obj,'minF1',1,x,y,'label','minF1','TooltipString','min frequency value for AUD1'); + next_row(y); + NumeditParam(obj,'maxF1',9,x,y,'label','maxF1','TooltipString','max frequency value for AUD1'); + next_row(y); + NumeditParam(obj,'boundary',0,x,y,'label','boundary','TooltipString','decision boundary for categorisation (log)'); + next_row(y); + MenuParam(obj, 'mu_location', {'center', 'side'}, ... + 'center', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nLocation of boundary'])); + next_row(y); + ToggleParam(obj, 'frequency_categorization', 0, x,y,... + 'OnString', 'frequency ON',... + 'OffString', 'frequency OFF',... + 'TooltipString', sprintf('If on (black) then it enables the presentation of pure tones')); + next_row(y); + MenuParam(obj, 'DistributionType', {'uniform','unim-unif', 'unif-unim', 'unimodal', 'bimodal', 'asym-unif', 'Unim-Unif', 'Unif-Unim'}, ... + 'uniform', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent distributions'])); + set_callback(DistributionType, {mfilename, 'DistributionType'}); + if frequency_categorization + boundary.value = (log(value(minF1)) + log(value(maxF1)))/2; + else + boundary.value = (log(value(minS1)) + log(value(maxS1)))/2; + end + if strcmp(mu_location,'center') + boundary.value = value(boundary); + elseif strcmp(mu_location,'side') + boundary.value = (log(value(minS1)) + value(boundary))/2; + end + StimulusSection(obj,'plot_stimuli'); + + set_callback(frequency_categorization, {mfilename, 'FrequencyCategorization'}); + + case 'prepare_next_trial' + + %% d or u? + + SideSection(obj,'get_current_side'); + StimulusSection(obj,'pick_current_stimulus'); + if frequency_categorization + A1_freq.value=exp(value(thisstim)); + A1_sigma.value=0; + A1 = log(value(A1_freq)); + else + A1_sigma.value=exp(value(thisstim)); + A1_freq.value=0; + A1 = log(value(A1_sigma)); + end + +% value(thisstimlog(n_done_trials+1)) - value(boundary) + if value(thisstimlog(n_done_trials+1)) > value(boundary)%value(numClass) + set(value(h1), 'YData', value(A1), 'color',[0.4 0.8 0.1],'markerfacecolor',[0.4 0.8 0.1]); + else + set(value(h1), 'YData', value(A1), 'color',[0.8 0.4 0.1],'markerfacecolor',[0.8 0.4 0.1]); + end + + % Plot current stimulus and move to saving stimulus history + + %% produce noise pattern + srate=SoundManagerSection(obj,'get_sample_rate'); + Fs=srate; + T=value(A1_time); + + if frequency_categorization + dur1 = A1_time*1000; + bal=0; + freq1=A1_freq*1000; + vol=0.001; + RVol=vol*min(1,(1+bal));LVol=vol*min(1,(1-bal)); + t=0:(1/srate):(dur1/1000); t = t(1:end-1); + tw=sin(t*2*pi*freq1); + RW=RVol*tw; + %w=[LW;RW]; + AUD1 = RW; + else + [rawA1 rawA2 normA1 normA2]=noisestim(1,1,T,value(fcut),Fs,value(filter_type)); + modulator=singlenoise(1,T,[value(lfreq) value(hfreq)],Fs,'BUTTER'); + AUD1=normA1(1:A1_time*srate).*modulator(1:A1_time*srate).*A1_sigma; + end + + if ~isempty(AUD1) + SoundManagerSection(obj, 'set_sound', 'StimAUD1', [AUD1'; AUD1']) + end + + + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + if n_done_trials >0 + + if ~violation_history(n_done_trials) && ~timeout_history(n_done_trials) + StimulusSection(obj,'update_stimulus_history'); + else + StimulusSection(obj,'update_stimulus_history_nan'); + end + end + + + %% Case make_stimuli + case 'make_stimuli' + if nargout>0 + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + + stim_range_log = stim_max_log - stim_min_log; + if strcmp(DistributionType,'uniform') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + %stim_i = exp(stim_i_log) + elseif strcmp(DistributionType,'unimodal') + mu_1 = value(boundary); + sigma = 0.4; + stim_tot = []; + for i = 1:1000 + stim_i_log = stim_max_log +1; + while (stim_i_log > stim_max_log) ||(stim_i_log < stim_min_log) + stim_i_log = normrnd(mu_1,sigma); + end + stim_tot = [stim_tot stim_i_log]; + end + +% stim_tot = [] +% for i = 1:1000 +% stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); +% stim_i_log = stim_i_log + 0.4 * cos(2 * pi * 0.5 * (stim_i_log - stim_min_log)/(stim_max_log - stim_min_log)) +% stim_i_log = stim_i_log - 0.4; +% stim_i_log = stim_min_log + (stim_i_log - stim_min_log) * stim_range_log/(stim_range_log - 0.8) +% stim_tot = [stim_tot stim_i_log]; +% end + + elseif strcmp(DistributionType,'bimodal') + mu_1 = (stim_min_log + value(boundary))/2; + mu_2 = (stim_max_log + value(boundary))/2; + sigma = 0.2; +% stim_tot = []; +% for i = 1:1000 + stim_i_log = stim_max_log + 1; + while (stim_i_log > stim_max_log) ||(stim_i_log < stim_min_log) + if rand()>0.5 + stim_i_log = normrnd(mu_1,sigma); + else + stim_i_log = normrnd(mu_2,sigma); + end + end +% stim_tot = [stim_tot stim_i_log]; +% end +% stim_tot = [stim_tot stim_i_log]; + + +% stim_tot = [] +% for i = 1:1000 +% stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); +% stim_i_log = stim_i_log + 0.07 * cos(2 * pi * 1.5 * (stim_i_log - stim_min_log)/(stim_max_log - stim_min_log)) +% stim_i_log = stim_i_log - 0.07; +% stim_i_log = stim_min_log + (stim_i_log - stim_min_log) * stim_range_log/(stim_range_log - 0.14) +% stim_tot = [stim_tot stim_i_log]; +% end + + elseif strcmp(DistributionType,'unim-unif') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + if stim_i_log < (stim_max_log + stim_min_log) / 2 + for i = 1:30 + stim_i_log = stim_i_log - 0.01 * sin(2 * pi * 0.5 * (stim_i_log - stim_min_log) / (stim_range_log/2)); + end + end + elseif strcmp(DistributionType,'unif-unim') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + if stim_i_log > (stim_max_log + stim_min_log) / 2 + for i = 1:30 + stim_i_log = stim_i_log - 0.01 * sin(2 * pi * 0.5 * (stim_i_log - stim_min_log) / (stim_range_log/2)); + end + end + elseif strcmp(DistributionType,'Unim-Unif') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + if stim_i_log < (stim_max_log + stim_min_log) / 2 + for i = 1:30 + stim_i_log = stim_i_log + 0.01 * sin(2 * pi * 0.5 * (stim_i_log - stim_min_log) / (stim_range_log/2)); + end + end + elseif strcmp(DistributionType,'Unif-Unim') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + if stim_i_log > (stim_max_log + stim_min_log) / 2 + for i = 1:30 + stim_i_log = stim_i_log + 0.01 * sin(2 * pi * 0.5 * (stim_i_log - stim_min_log) / (stim_range_log/2)); + end + end + elseif strcmp(DistributionType,'asym-unif') +% stim_tot = []; + stim_half_log = value(boundary) - stim_min_log; +% for i = 1:1000 + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + if stim_i_log < (stim_min_log + value(boundary)) / 2 + for i = 1:30 + stim_i_log = stim_i_log - 0.01 * sin(2 * pi * 0.5 * (stim_i_log - stim_min_log) / (stim_half_log/2)); + end + end +% stim_tot = [stim_tot stim_i_log]; +% end + end + x = stim_i_log; + end + + + %% Case pick_current_stimulus + case 'pick_current_stimulus' + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + if strcmp(Rule,'S2>S_boundry Left') + if strcmp(ThisTrial, 'LEFT') + stim_i_log = stim_min_log; + while stim_i_log <= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + else + stim_i_log = stim_max_log; + while stim_i_log >= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + end + elseif strcmp(Rule,'S2>S_boundry Right') + if strcmp(ThisTrial, 'LEFT') + stim_i_log = stim_max_log; + while stim_i_log >= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + else + stim_i_log = stim_min_log; + while stim_i_log <= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + end + end + thisstim.value=stim_i_log; + thisstimlog(n_done_trials+1)= stim_i_log; + %disp(exp(value(thisstimlog))) + + %% Case plot_pais + case 'plot_stimuli' + + %% plot the stimuli + if frequency_categorization + boundary.value = (log(value(minF1)) + log(value(maxF1)))/2; + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + stim_min = value(minF1); + stim_max = value(maxF1); + + else + boundary.value = (log(value(minS1)) + log(value(maxS1)))/2; + if strcmp(mu_location,'center') + boundary.value = value(boundary); + elseif strcmp(mu_location,'side') + boundary.value = (log(value(minS1)) + value(boundary))/2; + end + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + stim_min = value(minS1); + stim_max = value(maxS1); + end + cla(value(ax)) + xd=1; + axes(value(ax)); + plot(xd,stim_min_log,'s','MarkerSize',15,'MarkerEdgeColor',[0 0 0],'LineWidth',2) + hold on + plot(xd,stim_max_log,'s','MarkerSize',15,'MarkerEdgeColor',[0 0 0],'LineWidth',2) + line([0,2], [value(boundary),value(boundary)]); + axis square + set(value(ax),'ytick',([stim_min_log, stim_max_log]),'xtick',xd); + set(value(ax),'yticklabel',([stim_min, stim_max]),'xticklabel','S1'); + ylabel('\sigma_1 in log scale','FontSize',16,'FontName','Cambria Math'); + set(value(ax),'Fontsize',15) + xlabel('S1','FontSize',16,'FontName','Cambria Math') + + SideSection(obj,'get_current_side'); + StimulusSection(obj,'pick_current_stimulus'); + + A1 = value(thisstim); + + %% plot the stimulus; + if value(thisstim) > value(boundary)%value(numClass) + h1.value=plot(xd,value(A1),'s','color',[0.4 0.8 0.1],'markerfacecolor',[0.4 0.8 0.1],'MarkerSize',15,'LineWidth',3); + else + h1.value=plot(xd,value(A1),'s','color',[0.8 0.4 0.1],'markerfacecolor',[0.8 0.4 0.1],'MarkerSize',15,'LineWidth',3); + end + + + %% Case frequency ON + case 'FrequencyCategorization' + if frequency_categorization == 1 + enable(maxF1);enable(minF1); + StimulusSection(obj,'plot_stimuli'); + else + disable(maxF1);disable(minF1); + StimulusSection(obj,'plot_stimuli'); + end + + %% Case get_stimuli + case 'get_stimuli' + if nargout>0 + x=value(S1); + end + + + %% Case close + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)), %#ok + delete(value(myfig)); + end; + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + %% Case update_stimuli + case 'update_stimuli' + StimulusSection(obj,'plot_stimuli'); + + case 'update_stimulus_history' + ps=value(stimulus_history); + ps(n_done_trials)=value(thisstimlog(n_done_trials)); + stimulus_history.value=ps; + + case 'update_stimulus_history_nan' + ps=value(stimulus_history); + ps(n_done_trials)=value(thisstimlog(n_done_trials));%nan; + stimulus_history.value=ps; + + %% Case hide + case 'hide', + StimulusShow.value = 0; set(value(myfig), 'Visible', 'off'); + %% Case show + case 'show', + StimulusShow.value = 1; set(value(myfig), 'Visible', 'on'); + %% Case Show_hide + case 'show_hide', + if StimulusShow == 1, set(value(myfig), 'Visible', 'on'); %#ok (defined by GetSoloFunctionArgs) + else set(value(myfig), 'Visible', 'off'); + end; + end + \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/StimulusSection_.m b/Protocols/@SoundCatContinuous/StimulusSection_.m new file mode 100644 index 00000000..b605526f --- /dev/null +++ b/Protocols/@SoundCatContinuous/StimulusSection_.m @@ -0,0 +1,349 @@ + + +function [x, y] = StimulusSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + if length(varargin) < 2, + error('Need at least two arguments, x and y position, to initialize %s', mfilename); + end; + x = varargin{1}; y = varargin{2}; + + ToggleParam(obj, 'StimulusShow', 0, x, y, 'OnString', 'Stimuli', ... + 'OffString', 'Stimuli', 'TooltipString', 'Show/Hide Stimulus panel'); + set_callback(StimulusShow, {mfilename, 'show_hide'}); %#ok (Defined just above) + next_row(y); + + SoloParamHandle(obj, 'myfig', 'value', figure('closerequestfcn', [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none', ... + 'Name', mfilename), 'saveable', 0); + screen_size = get(0, 'ScreenSize'); + set(value(myfig),'Position',[1 screen_size(4)-740, 1000 1000]); % put fig at top right + set(double(gcf), 'Visible', 'off'); + x=10;y=10; + + SoloParamHandle(obj, 'ax', 'saveable', 0, ... + 'value', axes('Position', [0.01 0.5 0.45 0.45])); + ylabel('log_e A','FontSize',16,'FontName','Cambria Math'); + set(value(ax),'Fontsize',15) + xlabel('Sound Categorization','FontSize',16,'FontName','Cambria Math') + + SoloParamHandle(obj, 'axperf', 'saveable', 0, ... + 'value', axes('Position', [0.5 0.5 0.45 0.45])); + ylabel('log_e A','FontSize',16,'FontName','Cambria Math'); + set(value(axperf),'Fontsize',15) + xlabel('Sound Categorization','FontSize',16,'FontName','Cambria Math') + + SoundManagerSection(obj, 'declare_new_sound', 'StimAUD1') + SoloParamHandle(obj, 'thisstim', 'value', []); + SoloParamHandle(obj, 'thisstimlog', 'value', []); + SoloParamHandle(obj, 'h1', 'value', []); + + y=5; + + next_row(y); + next_row(y); + PushbuttonParam(obj, 'refresh_stimuli', x,y , 'TooltipString', 'Instantiates the stimuli given the new set of parameters'); + set_callback(refresh_stimuli, {mfilename, 'plot_stimuli'}); + + + next_row(y); + next_row(y); + MenuParam(obj, 'Rule', {'S2>S_boundry Left','S2>S_boundry Right'}, ... + 'S2>S_boundry Left', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nThis bottom determines the rule\n', ... + '\n''S2>S_boundry Left'' means if Aud2 > Aud_boundry then reward will be delivered from the left water spout and if Aud2 < Aud_boundry then water comes form right\n',... + '\n''S2>S_boundry Right'' means if Aud2 < Aud_boundry then reward will be delivered from the left water spout and if Aud2 > Aud_boundry then water comes from right\n'])); + next_row(y, 1) + + next_column(x); + y=5; + MenuParam(obj, 'filter_type', {'GAUS','LPFIR', 'FIRLS','BUTTER','MOVAVRG','KAISER','EQUIRIP','HAMMING'}, ... + 'GAUS', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent filters. ''LPFIR'': lowpass FIR ''FIRLS'': Least square linear-phase FIR filter design\n', ... + '\n''BUTTER'': IIR Butterworth lowpass filter ''GAUS'': Gaussian filter (window)\n', ... + '\n''MOVAVRG'': Moving average FIR filter ''KAISER'': Kaiser-window FIR filtering\n', ... + '\n''EQUIRIP'':Eqiripple FIR filter ''HAMMING'': Hamming-window based FIR'])); + next_row(y, 1) + NumeditParam(obj,'fcut',110,x,y,'label','fcut','TooltipString','Cut off frequency on the original white noise'); + next_row(y); + NumeditParam(obj,'lfreq',2000,x,y,'label','Modulator_LowFreq','TooltipString','Lower bound for the frequency modulator'); + next_row(y); + NumeditParam(obj,'hfreq',20000,x,y,'label','Modulator_HighFreq','TooltipString','Upper bound for the frequency modulator'); + next_row(y); + DispParam(obj, 'A1_sigma', 0.01, x,y,'label','A1_sigma','TooltipString','Sigma value for the first stimulus'); + next_row(y); + DispParam(obj, 'A1_freq', 0.01, x,y,'label','A1_freq','TooltipString','Sigma value for the first stimulus'); + next_row(y); + NumeditParam(obj,'minS1',0.007,x,y,'label','minS1','TooltipString','min sigma value for AUD1'); + next_row(y); + NumeditParam(obj,'maxS1',0.05,x,y,'label','maxS1','TooltipString','max sigma value for AUD1'); + next_row(y); + NumeditParam(obj,'minF1',1,x,y,'label','minF1','TooltipString','min frequency value for AUD1'); + next_row(y); + NumeditParam(obj,'maxF1',9,x,y,'label','maxF1','TooltipString','max frequency value for AUD1'); + next_row(y); + NumeditParam(obj,'boundary',0,x,y,'label','boundary','TooltipString','decision boundary for categorisation (log)'); + next_row(y); + NumeditParam(obj,'mu',0,x,y,'label','mu','TooltipString','mean of distribution'); + next_row(y); + ToggleParam(obj, 'frequency_categorization', 0, x,y,... + 'OnString', 'frequency ON',... + 'OffString', 'frequency OFF',... + 'TooltipString', sprintf('If on (black) then it enables the presentation of pure tones')); + next_row(y); + MenuParam(obj, 'DistributionType', {'uniform','unim-unif', 'unif-unim'}, ... + 'uniform', x, y, 'labelfraction', 0.35, 'TooltipString', sprintf(['\nDifferent distributions'])); + set_callback(DistributionType, {mfilename, 'DistributionType'}); + if frequency_categorization + boundary.value = (log(value(minF1)) + log(value(maxF1)))/2; + else + boundary.value = (log(value(minS1)) + log(value(maxS1)))/2; + end + StimulusSection(obj,'plot_stimuli'); + + set_callback(frequency_categorization, {mfilename, 'FrequencyCategorization'}); + + case 'prepare_next_trial' + + %% d or u? + + SideSection(obj,'get_current_side'); + StimulusSection(obj,'pick_current_stimulus'); + if frequency_categorization + A1_freq.value=exp(value(thisstim)); + A1_sigma.value=0; + A1 = log(value(A1_freq)); + else + A1_sigma.value=exp(value(thisstim)); + A1_freq.value=0; + A1 = log(value(A1_sigma)); + end + +% value(thisstimlog(n_done_trials+1)) - value(boundary) + if value(thisstimlog(n_done_trials+1)) > value(boundary)%value(numClass) + set(value(h1), 'YData', value(A1), 'color',[0.4 0.8 0.1],'markerfacecolor',[0.4 0.8 0.1]); + else + set(value(h1), 'YData', value(A1), 'color',[0.8 0.4 0.1],'markerfacecolor',[0.8 0.4 0.1]); + end + + % Plot current stimulus and move to saving stimulus history + + %% produce noise pattern + srate=SoundManagerSection(obj,'get_sample_rate'); + Fs=srate; + T=value(A1_time); + + if frequency_categorization + dur1 = A1_time*1000; + bal=0; + freq1=A1_freq*1000; + vol=0.002; + RVol=vol*min(1,(1+bal));LVol=vol*min(1,(1-bal)); + t=0:(1/srate):(dur1/1000); t = t(1:end-1); + tw=sin(t*2*pi*freq1); + RW=RVol*tw; + %w=[LW;RW]; + AUD1 = RW; + else + [rawA1 rawA2 normA1 normA2]=noisestim(1,1,T,value(fcut),Fs,value(filter_type)); + modulator=singlenoise(1,T,[value(lfreq) value(hfreq)],Fs,'BUTTER'); + AUD1=normA1(1:A1_time*srate).*modulator(1:A1_time*srate).*A1_sigma; + end + + if ~isempty(AUD1) + SoundManagerSection(obj, 'set_sound', 'StimAUD1', [AUD1'; AUD1']) + end + + + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + if n_done_trials >0 + + if ~violation_history(n_done_trials) && ~timeout_history(n_done_trials) + StimulusSection(obj,'update_stimulus_history'); + else + StimulusSection(obj,'update_stimulus_history_nan'); + end + end + + + %% Case make_stimuli + case 'make_stimuli' + if nargout>0 + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + + stim_range_log = stim_max_log - stim_min_log; + if strcmp(DistributionType,'uniform') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + %stim_i = exp(stim_i_log) + elseif strcmp(DistributionType,'unim-unif') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + if stim_i_log < (stim_max_log + stim_min_log) / 2 + for i = 1:30 + stim_i_log = stim_i_log - 0.01 * sin(2 * pi * 0.5 * (stim_i_log - stim_min_log) / (stim_range_log/2)); + end + end + elseif strcmp(DistributionType,'unif-unim') + stim_i_log = stim_min_log + rand() * (stim_max_log - stim_min_log); + if stim_i_log > (stim_max_log + stim_min_log) / 2 + for i = 1:30 + stim_i_log = stim_i_log - 0.01 * sin(2 * pi * 0.5 * (stim_i_log - stim_min_log) / (stim_range_log/2)); + end + end + end + x = stim_i_log; + end + + + %% Case pick_current_stimulus + case 'pick_current_stimulus' + if frequency_categorization + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + else + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + end + if strcmp(Rule,'S2>S_boundry Left') + if strcmp(ThisTrial, 'LEFT') + stim_i_log = stim_min_log; + while stim_i_log <= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + else + stim_i_log = stim_max_log; + while stim_i_log >= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + end + elseif strcmp(Rule,'S2>S_boundry Right') + if strcmp(ThisTrial, 'LEFT') + stim_i_log = stim_max_log; + while stim_i_log >= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + else + stim_i_log = stim_min_log; + while stim_i_log <= value(boundary) + stim_i_log = StimulusSection(obj,'make_stimuli'); + end + %stim_i = exp(stim_i_log); + end + end + thisstim.value=stim_i_log; + thisstimlog(n_done_trials+1)= stim_i_log; + %disp(exp(value(thisstimlog))) + + %% Case plot_pais + case 'plot_stimuli' + + %% plot the stimuli + if frequency_categorization + boundary.value = (log(value(minF1)) + log(value(maxF1)))/2; + stim_min_log = log(value(minF1)); + stim_max_log = log(value(maxF1)); + stim_min = value(minF1); + stim_max = value(maxF1); + + else + boundary.value = (log(value(minS1)) + log(value(maxS1)))/2; + stim_min_log = log(value(minS1)); + stim_max_log = log(value(maxS1)); + stim_min = value(minS1); + stim_max = value(maxS1); + end + cla(value(ax)) + xd=1; + axes(value(ax)); + plot(xd,stim_min_log,'s','MarkerSize',15,'MarkerEdgeColor',[0 0 0],'LineWidth',2) + hold on + plot(xd,stim_max_log,'s','MarkerSize',15,'MarkerEdgeColor',[0 0 0],'LineWidth',2) + line([0,2], [value(boundary),value(boundary)]); + axis square + set(value(ax),'ytick',([stim_min_log, stim_max_log]),'xtick',xd); + set(value(ax),'yticklabel',([stim_min, stim_max]),'xticklabel','S1'); + ylabel('\sigma_1 in log scale','FontSize',16,'FontName','Cambria Math'); + set(value(ax),'Fontsize',15) + xlabel('S1','FontSize',16,'FontName','Cambria Math') + + SideSection(obj,'get_current_side'); + StimulusSection(obj,'pick_current_stimulus'); + + A1 = value(thisstim); + + %% plot the stimulus; + if value(thisstim) > value(boundary)%value(numClass) + h1.value=plot(xd,value(A1),'s','color',[0.4 0.8 0.1],'markerfacecolor',[0.4 0.8 0.1],'MarkerSize',15,'LineWidth',3); + else + h1.value=plot(xd,value(A1),'s','color',[0.8 0.4 0.1],'markerfacecolor',[0.8 0.4 0.1],'MarkerSize',15,'LineWidth',3); + end + + + %% Case frequency ON + case 'FrequencyCategorization' + if frequency_categorization == 1 + enable(maxF1);enable(minF1); + StimulusSection(obj,'plot_stimuli'); + else + disable(maxF1);disable(minF1); + StimulusSection(obj,'plot_stimuli'); + end + + %% Case get_stimuli + case 'get_stimuli' + if nargout>0 + x=value(S1); + end + + + %% Case close + case 'close' + % Delete all SoloParamHandles who belong to this object and whose + % fullname starts with the name of this mfile: + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)), %#ok + delete(value(myfig)); + end; + delete_sphandle('owner', ['^@' class(obj) '$'], ... + 'fullname', ['^' mfilename]); + + %% Case update_stimuli + case 'update_stimuli' + StimulusSection(obj,'plot_stimuli'); + + case 'update_stimulus_history' + ps=value(stimulus_history); + ps(n_done_trials)=value(thisstimlog(n_done_trials)); + stimulus_history.value=ps; + + case 'update_stimulus_history_nan' + ps=value(stimulus_history); + ps(n_done_trials)=nan; + stimulus_history.value=ps; + + %% Case hide + case 'hide', + StimulusShow.value = 0; set(value(myfig), 'Visible', 'off'); + %% Case show + case 'show', + StimulusShow.value = 1; set(value(myfig), 'Visible', 'on'); + %% Case Show_hide + case 'show_hide', + if StimulusShow == 1, set(value(myfig), 'Visible', 'on'); %#ok (defined by GetSoloFunctionArgs) + else set(value(myfig), 'Visible', 'off'); + end; + end + \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/noiselib.m b/Protocols/@SoundCatContinuous/noiselib.m new file mode 100644 index 00000000..57dd5c7d --- /dev/null +++ b/Protocols/@SoundCatContinuous/noiselib.m @@ -0,0 +1,51 @@ +function [base,filtbase]=noiselib(num,T,fcut,Fs,filter_type,outband) +close all +clc + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; + +for ii=1:num +s = RandStream('mcg16807','Seed',ii) +RandStream.setDefaultStream(s) + +replace=1; +L=T*Fs/1000; % Length of signal +t=L*1000*linspace(0,1,L)/Fs; % time in miliseconds +%%%%%%%%%%% produce position values %%%%%%% +pos1 = randn(Fs,1); +pos1(pos1>outband)=[]; +pos1(pos1<-outband)=[]; + +base(:,num)=pos1; +%base = randsample(pos1,L,replace); +%%%% Filter the original position values %%%%%% +filtbase(:,num)=filt(base,fcut,Fs,filter_type); + +end + +end + +%%%%%% plot the row and filtered position values %%%%%%%%% +% subplot(2,2,1) +% plot(t,base,'r'); +% ylabel('base') +% xlabel('Time (ms)') +% subplot(2,2,2) +% plot(t,target,'g'); +% ylabel('target') +% xlabel('Time (ms)') +% subplot(2,2,3) +% plot(t,filtbase) +% ylabel('filtbase') +% xlabel('Time (ms)') +% subplot(2,2,4) +% plot(t,filttarget) +% ylabel('filttarget') +% xlabel('Time (ms)') diff --git a/Protocols/@SoundCatContinuous/private/filt.m b/Protocols/@SoundCatContinuous/private/filt.m new file mode 100644 index 00000000..e4c572ff --- /dev/null +++ b/Protocols/@SoundCatContinuous/private/filt.m @@ -0,0 +1,60 @@ +function filtsignal=filt(signal,fcut,Fs,filter_type) +a=2; % wp/ws used in butterworth method and LS linear FIR method +N=200; % filter order used in lowpass FIR method +rp=3; % passband ripple in dB used in butterworth method +rs=60; % stopband attenuation in dB used in butterworth method +beta=0.1102*(rs-8.8); %used in Kaiser window to obtain sidelobe attenuation of rs dB +if strcmp(filter_type, 'GAUS') || strcmp(filter_type, 'MOVAVRG') +window = fix(Fs/fcut); % window size used in Gaussian and moving average methods +end +wp=2*fcut/Fs; % normalized passband corner frequency wp, the cutoff frequency +ws=a*wp; % normalized stopband corner frequency + + +switch filter_type + case 'BUTTER' %Butterworth IIR filter + if length(wp)>1 + ws(1)=2*(fcut(1)/2)/Fs; + ws(2)=2*(fcut(2)+fcut(1)/2)/Fs; + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'bandpass'); + else + [n,wn]=buttord(wp,ws,rp,rs); + [b,a]=butter(n,wn,'low'); + end + filtsignal=filter(b,a,signal);%conventional filtering + case 'LPFIR' %Lowpass FIR filter + d=fdesign.lowpass('N,Fc',N,fcut,Fs); % Fc is the 6-dB down point, N is the filter order(N+1 filter coefficients) + Hd = design(d); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'FIRLS' %Least square linear-phase FIR filter design + b=firls(255,[0 2*fcut/Fs a*2*fcut/Fs 1],[1 1 0 0]); + filtsignal=filter(b,1,signal); %conventional filtering + case 'EQUIRIP' %Eqiripple FIR filter + d=fdesign.lowpass('Fp,Fst,Ap,Ast',wp,ws,rp,rs); + Hd=design(d,'equiripple'); + filtsignal=filter(Hd.Numerator,1,signal); %conventional filtering + case 'MOVAVRG' % Moving average FIR filtering, Rectangular window + h = ones(window,1)/window; + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'HAMMING' % Hamming-window based FIR filtering + b = fir1(150,wp); + filtsignal = filter(b, 1, signal); + filtsignal = filter(h, 1, signal); + case 'GAUS' % Gaussian-window FIR filtering + h = normpdf(1:window, 0, fix(window/2)); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + case 'GAUS1' % Gaussian-window FIR filtering + b = fir1(window-1,wp,gausswin(window,2)/window); + filtsignal = filter(b, 1, signal); + case 'KAISER' %Kaiser-window FIR filtering + h=kaiser(window,beta); + b = fir1(window-1,wp,h); + filtsignal = filter(b, 1, signal); + + otherwise + sprintf('filter_type is wrong!! havaset kojast!!') +end + diff --git a/Protocols/@SoundCatContinuous/private/noisestim.m b/Protocols/@SoundCatContinuous/private/noisestim.m new file mode 100644 index 00000000..7f074ab0 --- /dev/null +++ b/Protocols/@SoundCatContinuous/private/noisestim.m @@ -0,0 +1,47 @@ +function [base,target,normbase,normtarget]=noisestim(sigma_1,sigma_2,T,fcut,Fs,filter_type) + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +replace=1; +L=floor(T*Fs); % Length of signal +t=L*linspace(0,1,L)/Fs; % time in miliseconds +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1*randn(Fs,1); +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +pos2 =sigma_2*randn(Fs,1); +% pos2(pos2>outband)=[]; +% pos2(pos2<-outband)=[]; +base = randsample(pos1,L,replace); +target = randsample(pos2,L,replace); +%%%% Filter the original position values %%%%%% +filtbase=filt(base,fcut,Fs,filter_type); +filttarget=filt(target,fcut,Fs,filter_type); +normbase=filtbase./(max(abs(filtbase))); +normtarget=filttarget./(max(abs(filttarget))); +end + +%%%%%% plot the row and filtered position values %%%%%%%%% +% subplot(2,2,1) +% plot(t,base,'r'); +% ylabel('base') +% xlabel('Time (ms)') +% subplot(2,2,2) +% plot(t,target,'g'); +% ylabel('target') +% xlabel('Time (ms)') +% subplot(2,2,3) +% plot(t,filtbase) +% ylabel('filtbase') +% xlabel('Time (ms)') +% subplot(2,2,4) +% plot(t,filttarget) +% ylabel('filttarget') +% xlabel('Time (ms)') diff --git a/Protocols/@SoundCatContinuous/private/singlenoise.m b/Protocols/@SoundCatContinuous/private/singlenoise.m new file mode 100644 index 00000000..0cf201f3 --- /dev/null +++ b/Protocols/@SoundCatContinuous/private/singlenoise.m @@ -0,0 +1,32 @@ +function [normbase]=singlenoise(sigma_1,T,fcut,Fs,filter_type) +%GetSoloFunctionArgs(obj); + +%%%%%%%%%%%%%%%%% Determines of the type of filter used %%%%%%%%%%%%%%%%%%% +%'LPFIR': lowpass FIR%%%%%'FIRLS': Least square linear-phase FIR filter design +%'BUTTER': IIR Butterworth lowpass filter%%%%%%'GAUS': Gaussian filter (window) +%'MOVAVRG': Moving average FIR filter%%%%%%%%'KAISER': Kaiser-window FIR filtering +% 'EQUIRIP':Eqiripple FIR filter%%%%% 'HAMMING': Hamming-window based FIR +% T is duration of each signal in milisecond, fcut is the cut-off frequency +% Fs is the sampling frequency +% outband=40; +sigma_1=1; +%T=10000; +%fcut=[3000 4000]; +%Fs=200000; +filter_type='BUTTER'; +outband=60; +replace=1; +L=floor(T*Fs); % Length of signal +%%%%%%%%%%% produce position values %%%%%%% +pos1 = sigma_1*randn(Fs,1); +% pos1(pos1>outband)=[]; +% pos1(pos1<-outband)=[]; + +base = randsample(pos1,L,replace); +%%%% Filter the original position values %%%%%% +%filtbase=filt(base,fcut,Fs,filter_type); +hf = design(fdesign.bandpass('N,F3dB1,F3dB2',10,fcut(1),fcut(2),Fs)); +filtbase=filter(hf,base); +normbase=filtbase./(max(abs(filtbase))); +end + diff --git a/Protocols/@SoundCatContinuous/state_colors.m b/Protocols/@SoundCatContinuous/state_colors.m new file mode 100644 index 00000000..c6df1d67 --- /dev/null +++ b/Protocols/@SoundCatContinuous/state_colors.m @@ -0,0 +1,16 @@ +function SC = state_colors(obj) %#ok + +SC = struct( ... + 'wait_for_cpoke', [0.68 1 0.63], ... + 'cp', [0.63 1 0.94], ... + 'cp_legal_cbreak_period', [0.63 1 0.94]*0.8, ... + 'sideled_on', [1 0.79 0.63], ... + 'wait_for_collecting_reward', [0.53 0.78 1.00],... + 'righthit', [0.3 0.9 0], ... + 'lefthit', [0 0.9 0.3], ... + 'hit_state', [0.77 0.60 0.48], ... + 'second_hit_state', [0.25 0.45 0.48], ... + 'drink_state', [0 1 0], ... + 'error_state', [1 0.54 0.54], ... + 'violation_state', [0.31 0.48 0.30], ... + 'timeout_state', 0.8*[0.31 0.48 0.30]); \ No newline at end of file diff --git a/Protocols/@SoundCatContinuous/wave_colors.m b/Protocols/@SoundCatContinuous/wave_colors.m new file mode 100644 index 00000000..0d5a7339 --- /dev/null +++ b/Protocols/@SoundCatContinuous/wave_colors.m @@ -0,0 +1,11 @@ +function WC = wave_colors(obj) %#ok + +WC = struct(... + 'stim_wave', [1 1 1 ], ... + 'prestim_wave', [1 1 1 ], ... + 'stimulator_wave', [1 1 0 ], ... + 'stimulator_wave1', [1 1 0 ], ... + 'stimulator_wave2', [1 1 0 ], ... + 'stimulator_wave3', [1 1 0 ], ... + 'stimulator_wave4', [1 1 0 ], ... + 'stimulator_wave5', [1 1 0 ]); \ No newline at end of file diff --git a/Protocols/@TaskSwitch4/HistorySection.m b/Protocols/@TaskSwitch4/HistorySection.m new file mode 100644 index 00000000..f8cd1e25 --- /dev/null +++ b/Protocols/@TaskSwitch4/HistorySection.m @@ -0,0 +1,471 @@ +function [x, y] = HistorySection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + + %%% list all the way the rat can do well/bad : nose, perf etc. + + case 'init' + x=varargin{1}; + y=varargin{2}; + + %%% info about last trial + DispParam(obj, 'last_result','', x, y, 'labelfraction', 0.5,'position', [x y 200 20]); next_row(y,1.1); + + %%% percent correct coh/incoh + DispParam(obj, 'correct_coherent',0, x, y, 'labelfraction', 0.55,'label','%hit coh','position', [x y 100 20]); + DispParam(obj, 'correct_incoherent',0, x, y, 'labelfraction', 0.55,'label','%hit incoh','position', [x+100 y 100 20]);next_row(y); + + %%% percent correct left/right + DispParam(obj, 'left_correct',0, x, y, 'labelfraction', 0.55,'label','%hit left','position', [x y 100 20]); + DispParam(obj, 'right_correct',0, x, y, 'labelfraction', 0.55,'label','%hit right','position', [x+100 y 100 20]);next_row(y); + + %%% percent correct dir/freq + DispParam(obj, 'dir_correct',0, x, y, 'labelfraction', 0.55,'label','%hit dir','position', [x y 100 20]); + DispParam(obj, 'freq_correct',0, x, y, 'labelfraction', 0.55,'label','%hit freq','position', [x+100 y 100 20]);next_row(y,1.1); + + %%% percent correct + DispParam(obj, 'total_correct',0, x, y, 'labelfraction', 0.55,'label','%hit','position', [x y 100 20]); + %%% violations + DispParam(obj, 'percent_violations',0, x, y, 'labelfraction', 0.55,'label','%viol','position', [x+100 y 100 20]);next_row(y,1.1); + %%% total number of trials + DispParam(obj, 'nTrials',0, x, y, 'labelfraction', 0.55,'position', [x y 100 20]); + %%% number of valid trials + DispParam(obj, 'nValid',0, x, y, 'labelfraction', 0.55,'position', [x+100 y 100 20]);next_row(y); + + SubheaderParam(obj,'title',mfilename,x,y); next_row(y); + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% INTERNAL VARIBLES %%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %trial variables + %binary + SoloParamHandle(obj, 'was_hit', 'value', 0); + SoloParamHandle(obj, 'was_err', 'value', 0); + SoloParamHandle(obj, 'was_nic_err', 'value', 0); + SoloParamHandle(obj, 'was_timeout', 'value', 0); + SoloParamHandle(obj, 'was_wait', 'value', 0); + + SoloParamHandle(obj, 'was_block_switch', 'value', 0); + + SoloParamHandle(obj, 'result', 'value', 0); + + + %history variables + SoloParamHandle(obj, 'hit_history', 'value',[]); + + SoloParamHandle(obj, 'side_history', 'value',[]); + SoloParamHandle(obj, 'quadrant_history', 'value',[]); + SoloParamHandle(obj, 'task_history', 'value',[]); + SoloParamHandle(obj, 'incoh_history', 'value',[]); + SoloParamHandle(obj, 'gammadir_history', 'value',[]); + SoloParamHandle(obj, 'gammafreq_history', 'value',[]); + + SoloParamHandle(obj, 'result_history', 'value',[]); + + + + %history within this block/task + SoloParamHandle(obj, 'hit_history_task', 'value',[]); + SoloParamHandle(obj, 'incoh_history_task', 'value',[]); + SoloParamHandle(obj, 'previous_task', 'value',[]); + + %previous training stage + SoloParamHandle(obj, 'previous_stage', 'value',[]); + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% SEND OUT VARIBLES %%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +% +% %%% percent correct coh/incoh +% DispParam(obj, 'correct_coherent',0, x, y, 'labelfraction', 0.55,'label','%hit coh','position', [x y 100 20]); +% DispParam(obj, 'correct_incoherent',0, x, y, 'labelfraction', 0.55,'label','%hit incoh','position', [x+100 y 100 20]);next_row(y); +% +% %%% percent correct left/right +% DispParam(obj, 'left_correct',0, x, y, 'labelfraction', 0.55,'label','%hit left','position', [x y 100 20]); +% DispParam(obj, 'right_correct',0, x, y, 'labelfraction', 0.55,'label','%hit right','position', [x+100 y 100 20]);next_row(y); +% + + + + %to training section + SoloFunctionAddVars('TrainingSection', 'ro_args',{'was_hit';'was_block_switch';... + 'result';'nValid';'total_correct';'dir_correct';'freq_correct';'left_correct';... + 'right_correct';'correct_coherent';'correct_incoherent'}); + SoloFunctionAddVars('TrainingSection', 'rw_args',{'previous_stage'}); + + %to stimulus section + SoloFunctionAddVars('StimulusSection', 'ro_args',{'hit_history';'side_history';'quadrant_history';'task_history';... + 'incoh_history';'gammadir_history';'gammafreq_history';'result'}); + SoloFunctionAddVars('StimulusSection', 'rw_args',{'was_block_switch'}); + + + + + + + + + case 'next_trial', + + %%% if we haven't done any trial yet, no reason to save any history + if(n_done_trials==0 || isempty(parsed_events) || ~isfield(parsed_events,'states')) + + + %%% initialize stuff + previous_stage.value=value(training_stage); + + + + return; + end + + %%%% BINARY VARIABLES ABOUT TRIAL RESULT %%%% + if isfield(parsed_events.states,'hit_state') + was_hit.value=rows(parsed_events.states.hit_state)>0; + if(value(was_hit)==1) + result.value=1; + last_result.value='correct'; + end + end + if isfield(parsed_events.states,'error_state') + was_err.value=rows(parsed_events.states.error_state)>0; + if(value(was_err)==1) + result.value=2; + last_result.value='error'; + end + end + if isfield(parsed_events.states,'nic_error_state') + was_nic_err.value=rows(parsed_events.states.nic_error_state)>0; + if(value(was_nic_err)==1) + result.value=3; + last_result.value='nic_viol'; + end + end + if isfield(parsed_events.states,'timeout_state') + was_timeout.value=rows(parsed_events.states.timeout_state)>0; + if(value(was_timeout)==1) + result.value=4; + last_result.value='timeout viol'; + end + end + %%% rat got it wrong and then got it right + if isfield(parsed_events.states,'wait_state') + was_wait.value=rows(parsed_events.states.wait_state)>0; + %gets it wrong first and then gets it right + if(value(was_wait)==1 && value(was_hit)==1) + result.value=5; + last_result.value='wait->right'; + end + %gets it wrong first and then doesn't get it right (how?) + if(value(was_wait)==1 && value(was_hit)==0) + result.value=6; + last_result.value='wait->err'; + end + end + + + + + + + + %%% HISTORY VARIABLES %%% + res=value(result); + if(res==1) + hit_history.value=[value(hit_history) 1]; + nTrials.value = value(nTrials) + 1; + nValid.value = value(nValid) + 1; + elseif(res==2 || res==5 || res==6) + hit_history.value=[value(hit_history) 0]; + nTrials.value = value(nTrials) + 1; + nValid.value = value(nValid) + 1; + else + hit_history.value=[value(hit_history) NaN]; + nTrials.value = value(nTrials) + 1; + end + + + %%%% RESULT HISTORY %%%% + result_history.value=[value(result_history) res]; + + + %%%% SIDE HISTORY %%%% + if strcmp(value(ThisSide), 'LEFT'), + s = 'l'; + else + s = 'r'; + end + side_history.value=[value(side_history) s]; + + + %%%% QUADRANT HISTORY %%%% + quadrant_history.value=[value(quadrant_history) value(ThisQuadrant)]; + + + %%%% TASK HISTORY %%%% + if(strcmp(value(ThisTask),'Direction')) + t = 'd'; + else + t = 'f'; + end + task_history.value=[value(task_history) t]; + + + %%%% INCOH HISTORY %%%% + if(value(incoherent_trial)==1) + c = 1; + else + c = 0; + end + incoh_history.value=[value(incoh_history) c]; + + + %%%% GAMMA_DIR HISTORY %%%% + gammadir_history.value=[value(gammadir_history) value(ThisGamma_dir)]; + + + %%%% GAMMA_FREQ HISTORY %%%% + gammafreq_history.value=[value(gammafreq_history) value(ThisGamma_freq)]; + + + + + + + %%%%%%%% GENERAL PERFORMANCES %%%%%%%% + + + %%% save overall percent correct + vec_hit=value(hit_history); + total_correct.value = nanmean(vec_hit); + + %%% save NIC violations + vec_res=value(result_history); + num_violations=length(find(vec_res==3)); + num_total=length(vec_res); + percent_violations.value=num_violations/num_total; + + %%% save left/right percent correct + vec_side=value(side_history); + left_correct.value = nanmean(vec_hit(vec_side=='l')); + right_correct.value = nanmean(vec_hit(vec_side=='r')); + + %%% save dir/freq percent correct + vec_task=value(task_history); + dir_correct.value = nanmean(vec_hit(vec_task=='d')); + freq_correct.value = nanmean(vec_hit(vec_task=='f')); + + %%% save coh/incoh percent correct + vec_incoh=value(incoh_history); + correct_coherent.value = nanmean(vec_hit(vec_incoh==0)); + correct_incoherent.value = nanmean(vec_hit(vec_incoh==1)); + + + + + + + + + %%%%%%%% TASK PERFORMANCES %%%%%%%% + + + %new task? + if(~strcmp(value(previous_task),value(ThisTask))) + + nTrials_task.value=1; + + if(value(incoherent_trial)==1) + nTrials_incoh_task.value=1; + nTrials_coh_task.value=0; + else + nTrials_incoh_task.value=0; + nTrials_coh_task.value=1; + end + + hit_history_task.value=[]; + incoh_history_task.value=[]; + + total_correct_task.value=NaN; + total_correct_coherent_task.value=NaN; + total_correct_incoherent_task.value=NaN; + + previous_task.value=value(ThisTask); + + + else + + if(res==1) + nTrials_task.value = value(nTrials_task) + 1; + + hit_history_task.value=[value(hit_history_task) 1]; + + if(value(incoherent_trial)==1) + nTrials_incoh_task.value = value(nTrials_incoh_task) + 1; + incoh_history_task.value=[value(incoh_history_task) 1]; + else + nTrials_coh_task.value = value(nTrials_coh_task) + 1; + incoh_history_task.value=[value(incoh_history_task) 0]; + end + + + + elseif(res==2) + nTrials_task.value = value(nTrials_task) + 1; + + hit_history_task.value=[value(hit_history_task) 0]; + + if(value(incoherent_trial)==1) + nTrials_incoh_task.value = value(nTrials_incoh_task) + 1; + incoh_history_task.value=[value(incoh_history_task) 1]; + else + nTrials_coh_task.value = value(nTrials_coh_task) + 1; + incoh_history_task.value=[value(incoh_history_task) 0]; + end + + + else + hit_history_task.value=[value(hit_history_task) NaN]; + + if(value(incoherent_trial)==1) + incoh_history_task.value=[value(incoh_history_task) NaN]; + else + incoh_history_task.value=[value(incoh_history_task) NaN]; + end + + end + + + + + + %%% compute performances for current task block + + %total correct + vec_hit_task=value(hit_history_task); + vec=vec_hit_task; + vec=vec(~isnan(vec)); + + %%% kernel function: last few trials are the most important + kernel = exp(-(0:length(vec)-1)/5); + kernel = kernel(end:-1:1); + + if(~isempty(vec)) + total_correct_task.value = nansum(vec .* kernel)/sum(kernel); + else + total_correct_task.value = NaN; + end + + + + + %incoherent correct + vec_hit_task=value(hit_history_task); + vec_incoh_task=value(incoh_history_task); + vec=vec_hit_task(vec_incoh_task==1); + vec=vec(~isnan(vec)); + + %%% kernel function: last few trials are the most important + kernel = exp(-(0:length(vec)-1)/5); + kernel = kernel(end:-1:1); + + if(~isempty(vec)) + total_correct_incoherent_task.value = nansum(vec .* kernel)/sum(kernel); + else + total_correct_incoherent_task.value = NaN; + end + + + + + %coherent correct + vec_hit_task=value(hit_history_task); + vec_incoh_task=value(incoh_history_task); + vec=vec_hit_task(vec_incoh_task==0); + vec=vec(~isnan(vec)); + + %%% kernel function: last few trials are the most important + kernel = exp(-(0:length(vec)-1)/5); + kernel = kernel(end:-1:1); + + if(~isempty(vec)) + total_correct_coherent_task.value = nansum(vec .* kernel)/sum(kernel); + else + total_correct_coherent_task.value = NaN; + end + + + end + + + + + + %%%%%%%% STAGE PERFORMANCES %%%%%%%% + + %new stage? + if(n_done_trials>1 && ~strcmp(value(previous_stage),value(training_stage))) + nTrials_stage.value= 0; + nDays_stage.value= 1; + previous_stage.value=value(training_stage); + else + nTrials_stage.value = value(nTrials_stage) + 1; + end + + + + + + + + case 'end_session' + + CommentsSection(obj, 'append_line', [value(training_stage) ' ; ']); + CommentsSection(obj, 'append_line', ['days: ' num2str(value(nDays_stage)) ' ; ']); + CommentsSection(obj, 'append_line', ['valid: ' num2str(value(nValid)) ' ; ']); + CommentsSection(obj, 'append_line', ['dir: ' num2str(round(value(dir_correct)*100)/100) ' ; ']); + CommentsSection(obj, 'append_line', ['freq: ' num2str(round(value(freq_correct)*100)/100) ' ; ']); + CommentsSection(obj, 'append_line', ['coh: ' num2str(round(value(correct_coherent)*100)/100) ' ; ']); + CommentsSection(obj, 'append_line', ['incoh: ' num2str(round(value(correct_incoherent)*100)/100)]); + + + + + case 'get' + + val=varargin{1}; + + eval(['x=value(' val ');']); + + + + + case 'make_and_send_summary', + + pd.hits = value(hit_history); + pd.sides = value(side_history); + pd.tasks = value(task_history); + pd.stage = value(training_stage); + sendsummary(obj, 'sides', pd.sides, 'protocol_data', pd); + + +end + + diff --git a/Protocols/@TaskSwitch4/PPfilter.mat b/Protocols/@TaskSwitch4/PPfilter.mat new file mode 100644 index 00000000..a98f32c4 Binary files /dev/null and b/Protocols/@TaskSwitch4/PPfilter.mat differ diff --git a/Protocols/@TaskSwitch4/SMA1.m b/Protocols/@TaskSwitch4/SMA1.m new file mode 100644 index 00000000..a7798492 --- /dev/null +++ b/Protocols/@TaskSwitch4/SMA1.m @@ -0,0 +1,332 @@ +function [] = SMA1(obj, action) + +GetSoloFunctionArgs; + + +switch action + + case 'init', + + feval(mfilename, obj, 'next_trial'); + + + case 'next_trial', + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%% SETUP THE HARDWARE %%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%% minimum time + min_time= 2.5E-4; % This is less than the minumum time allowed for a state transition. + + %%% define LEDs and water lines + left1led = bSettings('get', 'DIOLINES', 'left1led'); + center1led = bSettings('get', 'DIOLINES', 'center1led'); + right1led = bSettings('get', 'DIOLINES', 'right1led'); + left1water = bSettings('get', 'DIOLINES', 'left1water'); + right1water = bSettings('get', 'DIOLINES', 'right1water'); + + + start_stop = bSettings('get', 'DIOLINES', 'start_stop'); + trialnum_indicator = bSettings('get', 'DIOLINES', 'trialnum_indicator'); + cerebro1 = bSettings('get', 'DIOLINES', 'cerebro1'); + cerebro2 = bSettings('get', 'DIOLINES', 'cerebro2'); + + %%% define state machine assembler + sma = StateMachineAssembler('full_trial_structure','use_happenings', 1); + + %%% get water valve opening times (based on calibration) + [LeftWValveTime RightWValveTime] = WaterValvesSection(obj, 'get_water_times'); + + + + + %%% set the water reward + if strcmp(value(ThisSide),'LEFT') + correct_response='Lhi'; + error_response='Rhi'; + hit_dio=left1water; + hit_led=left1led; + hit_valve_time=LeftWValveTime*value(total_water_multiplier); + elseif strcmp(value(ThisSide),'RIGHT') + correct_response='Rhi'; + error_response='Lhi'; + hit_dio=right1water; + hit_led=right1led; + hit_valve_time=RightWValveTime*value(total_water_multiplier); + else + error('!!!') + end + + + + + %%% Setup sounds + hit_sound_id = SoundManagerSection(obj, 'get_sound_id', 'HitSound'); + err_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ErrorSound'); + + viol_sound_id = SoundManagerSection(obj, 'get_sound_id', 'ViolationSound'); + timeout_sound_id = SoundManagerSection(obj, 'get_sound_id', 'TimeoutSound'); + + task1_sound_id = SoundManagerSection(obj, 'get_sound_id', 'Task1Sound'); + task2_sound_id = SoundManagerSection(obj, 'get_sound_id', 'Task2Sound'); + + + + + + %%% setup variables according to current task + if(strcmp(value(ThisTask),'Direction')) + task_sound_id=task1_sound_id; + + helper_lights=0; + error_forgiveness=0; + wait_delay=0; + + centerlight=center1led+left1led+right1led; + centerlight2=center1led; + + wait_for_cpoke2='wait_for_cpoke_dir'; + + elseif(strcmp(value(ThisTask),'Frequency')) + task_sound_id=task2_sound_id; + helper_lights=value(helper_lights_freq); + error_forgiveness=value(error_forgiveness_freq); + wait_delay=value(wait_delay_freq); + + centerlight=center1led; + centerlight2=center1led; + + wait_for_cpoke2='wait_for_cpoke_freq'; + + else + error('what task?') + end + + + + %%% setup the stimulus + stimulus_sound_id = SoundManagerSection(obj, 'get_sound_id', 'StimulusSound'); + + + %%% setup the reward + sma = add_scheduled_wave(sma, 'name', 'direct_reward', 'preamble', value(reward_delay), ... + 'sustain', hit_valve_time, 'DOut', hit_dio); + + + + if(helper_lights) + sidelights=hit_led; + else + sidelights=left1led+right1led; + end + + + + + + %%%%%%%%%%%%%%%%%%%% WAIT FOR CPOKE %%%%%%%%%%%%%%%%%%%% + + + + + sma = add_state(sma,'name','wait_for_cpoke',... + 'self_timer',min_time,... + 'input_to_statechange',{'Clo','wait_for_cpoke1'}); %wait for poke out + + +% %%%spikegadgets setup +% if(~isnan(start_stop) && ~isnan(trialnum_indicator) && n_done_trials==0) +% +% sma = add_state(sma,'name','wait_for_cpoke1',... +% 'self_timer',0.5,... +% 'output_actions',{'DOut', start_stop },... %start recording (TTL up) +% 'input_to_statechange',{'Tup','wait_for_cpoke1b'}); +% +% sma = add_state(sma,'name','wait_for_cpoke1b',... +% 'self_timer',2,... +% 'output_actions',{'DOut', -start_stop },... %start recording (TTL down) +% 'input_to_statechange',{'Tup','wait_for_cpoke1c'}); +% +% sma = add_state(sma,'name','wait_for_cpoke1c',... +% 'self_timer',0.5,... +% 'output_actions',{'DOut', trialnum_indicator },... %trial pulse up +% 'input_to_statechange',{'Tup','wait_for_cpoke1d'}); +% +% sma = add_state(sma,'name','wait_for_cpoke1d',... +% 'self_timer',min_time,... +% 'output_actions',{'DOut', -trialnum_indicator },... %trial pulse down +% 'input_to_statechange',{'Tup',wait_for_cpoke2}); +% +% else + + + sma = add_state(sma,'name','wait_for_cpoke1',... + 'self_timer',min_time,... + 'input_to_statechange',{'Tup',wait_for_cpoke2}); + + +% end + + + sma = add_state(sma,'name','wait_for_cpoke_dir',... + 'self_timer',0.5,... + 'output_actions',{'SoundOut',task_sound_id;'DOut', centerlight },... + 'input_to_statechange',{'Tup','wait_for_cpoke_bis'}); + + + sma = add_state(sma,'name','wait_for_cpoke_freq',... + 'self_timer',0.5,... + 'output_actions',{'SoundOut',task_sound_id;'DOut', centerlight },... + 'input_to_statechange',{'Tup','wait_for_cpoke_bis'}); + + + + + + sma = add_state(sma,'name','wait_for_cpoke_bis',... + 'self_timer',value(wait_for_cpoke_timeout),... + 'output_actions',{'DOut', centerlight},... + 'input_to_statechange',{'Tup','timeout_state'; ... + 'Chi','nic_prestim'}); + + + + + + %%%%%%%%%%%%%%%%%%%% NIC PRESTIM %%%%%%%%%%%%%%%%%%%% + + % sma = add_state(sma,'name','nic_prestim','self_timer',value(settling_time),... + % 'output_actions',{'DOut', centerlight2 },... + % 'input_to_statechange',{'Clo','wait_for_cpoke';... + % 'Tup','cpoke'}); + + + sma = add_state(sma,'name','nic_prestim','self_timer',value(settling_time),... + 'output_actions',{'SoundOut',-task_sound_id;'DOut', centerlight2 },... + 'input_to_statechange',{'Clo','wait_for_cpoke';... + 'Tup','cpoke'}); + + + + %%%%%%%%%%%%%%%%%%%% CPOKE with legal breaks up to 0.1 %%%%%%%%%%%%%% + + legal_cbreak=0.1; + + sma = add_scheduled_wave(sma,'name','cpoke_timer','preamble', value(nose_in_center)+legal_cbreak); + + % trigger the start of the timer: + sma = add_state(sma, 'name', 'cpoke', 'self_timer', min_time, ... + 'output_actions', {'SoundOut',stimulus_sound_id; ... + 'SchedWaveTrig', 'cpoke_timer'; 'DOut', centerlight2}, ... + 'input_to_statechange', {'Cout', 'cpoke_out'; ... + 'Tup', 'cpoke_in'}); + + + sma = add_state(sma, 'name', 'cpoke_in', 'self_timer', 1000, ... + 'output_actions', {'DOut', centerlight2}, ... + 'input_to_statechange', {'Cout', 'cpoke_out'; ... + 'cpoke_timer_In', 'wait_for_cout'}); + + sma = add_state(sma, 'name', 'cpoke_out', 'self_timer', legal_cbreak, ... + 'output_actions', {'DOut', centerlight2}, ... + 'input_to_statechange', {'Cin', 'cpoke_in'; ... + 'cpoke_timer_In', 'wait_for_cout';... + 'Tup', 'nic_error_state'}); + + + + %%%%%%%%%%%%%%%%%%%% WAIT FOR COUT %%%%%%%%%%%%%%%%%%%% + + sma = add_state(sma,'name','wait_for_cout',... + 'input_to_statechange',{'Clo','wait_for_spoke'}); + + + + %%%%%%%%%%%%%%%%%%%% WAIT FOR SPOKE %%%%%%%%%%%%%%%%%%%% + + if error_forgiveness==1 + % stimulus sound keeps going after rat steps out of cpoke + sma = add_state(sma,'name','wait_for_spoke','self_timer',value(wait_for_spoke_timeout),... + 'output_actions',{'DOut',sidelights},... + 'input_to_statechange',{correct_response,'hit_state';... + error_response,'wait_state';... + 'Tup','timeout_state'}); + else + % as soon as rat steps out of cpoke, we stop stimulus sound + sma = add_state(sma,'name','wait_for_spoke','self_timer',value(wait_for_spoke_timeout),... + 'output_actions',{'SoundOut',-stimulus_sound_id;'DOut',sidelights},... + 'input_to_statechange',{correct_response,'hit_state';... + error_response,'error_state';... + 'Tup','timeout_state'}); + end + + + + %%%%%%%%%%%%%%%%%%%% HIT STATE %%%%%%%%%%%%%%%%%%%% + + % first we turn off stimulus sound, in case it didn't happen before + % (i.e. if error_forgiveness == 1) + sma = add_state(sma,'name','hit_state','self_timer',min_time,... + 'output_actions',{'SoundOut',-stimulus_sound_id},... + 'input_to_statechange',{'Tup','hit_state2'}); + + + % we do the operations for the hit state + sma = add_state(sma,'name','hit_state2','self_timer',value(reward_delay)+hit_valve_time+0.4,... + 'output_actions',{'SoundOut',hit_sound_id;'DOut',hit_led;... + 'SchedWaveTrig','direct_reward'},... + 'input_to_statechange',{'Tup','clean_up_state'}); + + + + + %%%%%%%%%%%%%%%%%%%% ERROR STATE %%%%%%%%%%%%%%%%%%%% + + sma = add_state(sma,'name','error_state','self_timer',value(total_error_delay),... + 'output_actions',{'SoundOut',err_sound_id},... + 'input_to_statechange',{'Tup','clean_up_state'}); + + + sma = add_state(sma,'name','wait_state','self_timer',wait_delay,... + 'input_to_statechange',{'Tup','wait_for_spoke'}); + + + + %%%%%%%%%%%%%%%%%%%% NIC ERROR STATE %%%%%%%%%%%%%%%%%%%% + + sma = add_multi_sounds_state(sma,[-stimulus_sound_id viol_sound_id],... + 'self_timer',value(nic_delay),... + 'state_name','nic_error_state','return_state','clean_up_state'); + + + %%%%%%%%%%%%%%%%%%%% TIMEOUT STATE %%%%%%%%%%%%%%%%%%%% + + sma = add_state(sma,'name','timeout_state','self_timer',value(timeout_delay),... + 'output_actions',{'SoundOut',timeout_sound_id},... + 'input_to_statechange',{'Tup','clean_up_state'}); + + + %%%%%%%%%%%%%%%%%%%% CLEAN UP STATE %%%%%%%%%%%%%%%%%%%% + + sma = add_multi_sounds_state(sma,[-stimulus_sound_id -timeout_sound_id -viol_sound_id -hit_sound_id -err_sound_id -task1_sound_id -task2_sound_id],... + 'state_name','clean_up_state','return_state','check_next_trial_ready'); + + + dispatcher('send_assembler', sma, {'hit_state2', 'error_state',... + 'nic_error_state','timeout_state'}); + + + case 'get' + + %val=varargin{1}; + % + %eval(['x=value(' val ');']); + + + otherwise + warning('do not know how to do %s',action); +end + + + diff --git a/Protocols/@TaskSwitch4/StimulusSection.m b/Protocols/@TaskSwitch4/StimulusSection.m new file mode 100644 index 00000000..f5b9846d --- /dev/null +++ b/Protocols/@TaskSwitch4/StimulusSection.m @@ -0,0 +1,1377 @@ +function [x, y] = StimulusSection(obj, action, varargin) + +GetSoloFunctionArgs(obj); + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + x=varargin{1}; + y=varargin{2}; + + + + %%%% WATER INCREASE + + DispParam(obj, 'water_increase_multiplier',1, x, y, 'labelfraction', 0.65,... + 'TooltipString', 'water increase multiplier as a function of n_done_trials');next_row(y); + + NumeditParam(obj, 'WI_base', .95, x, y, 'labelfraction', 0.2,'label','B','position', [x y 66 20], ... + 'TooltipString', 'water increase base'); + + NumeditParam(obj, 'WI_ratio', .00025, x, y, 'labelfraction', 0.2,'label','R','position', [x+66 y 66 20], ... + 'TooltipString', 'water increase per trial'); + + NumeditParam(obj, 'WI_max', 1.2, x, y, 'labelfraction', 0.2,'label','M','position', [x+133 y 66 20], ... + 'TooltipString', 'water increase max');next_row(y); + + ToggleParam(obj, 'water_increase_toggle', 1, x,y,... + 'OnString', 'Water increase ON','OffString', 'Water increase OFF',... + 'TooltipString', sprintf('If on water increase is on'));next_row(y); + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%% TIMING PARAMETERS WINDOW %%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%% Separate window for stimulus parameters + ToggleParam(obj, 'TimingShow', 0, x, y, 'OnString', 'Parameters timing', ... + 'OffString', 'Parameters timing', 'TooltipString', 'Show/Hide Timing panel'); + set_callback(TimingShow, {mfilename, 'show_hide3'}); + next_row(y);oldx=x; oldy=y; parentfig=double(gcf); + SoloParamHandle(obj, 'myfig3', 'value', double(figure('Position', [300 100 280 220],'closerequestfcn',... + [mfilename '(' class(obj) ', ''hide3'');'], 'MenuBar', 'none','Name', mfilename)), 'saveable', 0); + + set(gcf, 'Visible', 'off'); + x=10;y=10; + + NumeditParam(obj, 'timeout_delay', 3, x,y,'label','Timeout delay','TooltipString','Delay after timeout at spoke, follows new trial'); + next_row(y); + + NumeditParam(obj, 'reward_delay', 0.001, x,y,'label','Reward Delay','TooltipString','Delay between side poke and reward delivery'); + next_row(y); + + NumeditParam(obj, 'wait_for_spoke_timeout', 60, x,y,'label','Timeout for spoke','TooltipString','Time after NIC to wait for a side poke'); + next_row(y); + + NumeditParam(obj, 'nic_delay', 3, x,y,'label','NIC violation delay','TooltipString','Delay after NIC violation, follows new trial'); + next_row(y); + + NumeditParam(obj, 'settling_time', 0.001, x,y,'label','Pre-stimulus delay','TooltipString','Time in NIC before starting stimulus'); + next_row(y); + + NumeditParam(obj, 'wait_for_cpoke_timeout', 120, x,y,'label','Timeout for cpoke','TooltipString','Timeout waiting for cpoke'); + next_row(y); + + SubheaderParam(obj,'title','Timing',x,y); next_row(y, 1.5); + + %%% back to the main window + x=oldx; y=oldy; + figure(parentfig); + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%% SOUND PARAMETERS WINDOW %%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%% Separate window for sound parameters + ToggleParam(obj, 'SoundsShow', 0, x, y, 'OnString', 'Parameters other sounds', ... + 'OffString', 'Parameters other sounds', 'TooltipString', 'Show/Hide Sounds panel'); + set_callback(SoundsShow, {mfilename, 'show_hide2'});next_row(y); + oldx=x; oldy=y; parentfig=double(gcf); + SoloParamHandle(obj, 'myfig2', 'value', double(figure('Position', [100 100 560 440],'closerequestfcn',... + [mfilename '(' class(obj) ', ''hide2'');'], 'MenuBar', 'none','Name', mfilename)), 'saveable', 0); + set(gcf, 'Visible', 'off'); + x=10;y=10; + + %%%% Sound parameters + [x,y]=SoundInterface(obj,'add','ViolationSound',x,y,'Style','SpectrumNoise','Volume',0.0015,'Loop',1); + SoundInterface(obj,'set','ViolationSound','Freq1',8000,'Freq2',2222,'Dur1',1,'Sigma',14,'Cntrst',111,'CRatio',1); + + [x,y]=SoundInterface(obj,'add','ErrorSound',x,y,'Style','PClick','Volume',0.0035,'Loop',1); + SoundInterface(obj,'set','ErrorSound','Freq1',333,'Freq2',333,'Dur1',0.5,'Width',5); + + [x,y]=SoundInterface(obj,'add','Task1Sound',x,y,'Style','ToneFMWiggle','Volume',0.005); + SoundInterface(obj,'set','Task1Sound','Dur1',1,'Freq1',4000,'FMAmp',600,'FMFreq',5); + + next_column(x);y=10; + + [x,y]=SoundInterface(obj,'add','TimeoutSound',x,y,'Style','SpectrumNoise','Volume',0.0025,'Loop',1); + SoundInterface(obj,'set','TimeoutSound','Freq1',1000,'Freq2',1000,'Dur1',1,'Sigma',100,'Cntrst',500,'CRatio',5); + + [x,y]=SoundInterface(obj,'add','HitSound',x,y,'Style','ToneSweep','Volume',0.0025); + SoundInterface(obj,'set','HitSound','Freq1',1000,'Freq2',5000,'Dur1',0.1,'Dur2',0.3,'Tau',0.05); + + [x,y]=SoundInterface(obj,'add','Task2Sound',x,y,'Style','ToneFMWiggle','Volume',0.005); + SoundInterface(obj,'set','Task2Sound','Dur1',1,'Freq1',9000,'FMAmp',1800,'FMFreq',15); + + %%% back to the main window + x=oldx; y=oldy; + figure(parentfig); + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%% STIM. PARAMETERS WINDOW %%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%% Separate window for stimulus parameters + ToggleParam(obj, 'StimuluShow', 0, x, y, 'OnString', 'Parameters stimulus', ... + 'OffString', 'Parameters stimulus', 'TooltipString', 'Show/Hide Stimulus panel'); + set_callback(StimuluShow, {mfilename, 'show_hide'}); + next_row(y); + oldx=x; oldy=y; parentfig=double(gcf); + SoloParamHandle(obj, 'myfig', 'value', double(figure('Position', [300 100 280 220], 'closerequestfcn',... + [mfilename '(' class(obj) ', ''hide'');'], 'MenuBar', 'none','Name', mfilename)), 'saveable', 0); + set(gcf, 'Visible', 'off'); + x=10;y=10; + + %%%% Stimulus parameters + NumeditParam(obj, 'bup_width', 5, x, y, 'position', [x y 200 20], ... + 'label', 'bup_width (ms)', 'TooltipString', 'the bup width in units of msec');next_row(y); + NumeditParam(obj, 'bup_ramp', 2, x, y, 'position', [x y 200 20], ... + 'label', 'bup_ramp (ms)', 'TooltipString', 'the duration in units of msec of the upwards and downwards volume ramps for individual bups');next_row(y); + NumeditParam(obj, 'total_rate', 40, x, y, 'position', [x y 200 20], ... + 'TooltipString', 'the sum of left and right bup rates');next_row(y); + NumeditParam(obj, 'freq_lo', 6500, x, y, 'position', [x y 200 20], ... + 'TooltipString', 'low frequency (Hz)');next_row(y); + NumeditParam(obj, 'freq_hi', 14200, x, y, 'position', [x y 200 20], ... + 'TooltipString', 'high frequency (Hz)');next_row(y); + NumeditParam(obj, 'vol_low_freq', 1, x, y, 'position', [x y 200 20], ... + 'TooltipString', 'volume multiplier for clicks at low frequency');next_row(y); + NumeditParam(obj, 'vol_hi_freq', 1, x, y, 'position', [x y 200 20], ... + 'TooltipString', 'volume multiplier for clicks at high frequency');next_row(y); + %%% overall volume + NumeditParam(obj, 'vol', 0.15, x, y, 'position', [x y 200 20],'label','Overall volume multiplier', ... + 'labelfraction', 0.7,'TooltipString', 'volume multiplier for all sounds in the protocol');next_row(y); + + %%% back to the main window + x=oldx; y=oldy; + figure(parentfig); + + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%% TASK SWITCHING PARAMETERS %%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%% minimum performances required + NumeditParam(obj, 'task_switch_min_perf', .8, x, y, 'position', [x y 200 20], ... + 'label', 'Min perf', 'TooltipString', 'Minimum performance to allow switching');next_row(y); + + %%%% minimum number of trials + NumeditParam(obj, 'task_switch_mintrials', 30, x, y, 'position', [x y 200 20], ... + 'label', 'Min trials', 'TooltipString', 'Minimum number of trials before switching');next_row(y); + + %%%% switch on/off + ToggleParam(obj, 'task_switch_auto', 0, x, y, 'position', [x y 200 20], ... + 'OffString', 'Auto task switch OFF', 'OnString', 'Auto task switch ON', ... + 'TooltipString', 'If on, switches automatically between tasks');next_row(y); + + %%%% first task: direction or random? + ToggleParam(obj, 'randomize_first_task', 0, x, y, 'position', [x y 200 20], ... + 'OffString', 'Start with direction', 'OnString', 'Randomize first task', ... + 'TooltipString', 'If on, picks randomly the first task');next_row(y,2); + + %%%% are incongruent stimuli present now? + DispParam(obj, 'exist_incoherent',0, x, y,... + 'TooltipString', '1 if incoherent trials can be generated under current parameters'); + next_row(y); + + %%%% number of incoherent trials in this block + DispParam(obj, 'nTrials_incoh_task', 0, x, y,'labelfraction', 0.55,'label','nTri incoh','position', [x y 100 20]); + %%%% performance on incoherent trials in this block + DispParam(obj, 'total_correct_incoherent_task',0, x, y, 'labelfraction', 0.55,'label','%hit incoh','position', [x+100 y 100 20]);next_row(y); + + %%%% number of coherent trials in this block + DispParam(obj, 'nTrials_coh_task',0, x, y, 'labelfraction', 0.55,'label','nTri coh','position', [x y 100 20]); + %%%% performance on coherent trials in this block + DispParam(obj, 'total_correct_coherent_task',0, x, y, 'labelfraction', 0.55,'label','%hit coh','position', [x+100 y 100 20]);next_row(y); + + %%%% total number of trials in this block + DispParam(obj, 'nTrials_task',0, x, y, 'labelfraction', 0.55,'label','nTri all','position', [x y 100 20]); + %%%% overall performance in this block + DispParam(obj, 'total_correct_task', 0, x, y,'labelfraction', 0.55,'label','%hit all','position', [x+100 y 100 20]);next_row(y); + + %%%% current task + MenuParam(obj, 'ThisTask', {'Direction'; 'Frequency'}, 1, x, y, ... + 'TooltipString', 'the task of the present trial'); next_row(y); + + SubheaderParam(obj,'title','Current Task',x,y, 'position', [x y 200 18]); next_row(y,1.5); + + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%% ANTIBIAS %%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%% Ab_TAU: number of trials back for antibias + NumeditParam(obj, 'Ab_Tau', 50, x, y,'position', [x y 100 20], ... + 'TooltipString', 'Number of trials back over which to compute antibias'); + + %%% Ab_BETA: strenth of antibias + NumeditParam(obj, 'Ab_Beta', 5, x, y,'position', [x+100 y 100 20], ... + 'TooltipString', 'antibias strength');next_row(y); + + %%% antibias type + MenuParam(obj, 'antibias_type', {'No antibias'; 'Side antibias';... + 'Quadrant antibias'}, 1, x, y, 'label', 'Type', 'TooltipString',... + 'antibias type (depends on exist_incoherent)', 'labelfraction',... + 0.3333);next_row(y,1); + + %%% side antibias on/off + ToggleParam(obj, 'antibias_toggle', 1, x,y,... + 'OnString', 'Antibias ON','OffString', 'Antibias OFF',... + 'TooltipString', sprintf('If on antibias is on'));next_row(y,1.1); + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% TRAINING PARAMETERS %%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%% mixing of frequency evidence + SliderParam(obj,'stimulus_mixing_freq',0,0,1,x, y,'label','%mixing freq', 'position', [x y 200 20]);next_row(y); + %%% mixing of direction evidence + SliderParam(obj,'stimulus_mixing_dir',0,0,1,x, y,'label','%mixing dir', 'position', [x y 200 20]);next_row(y,1.3); + + + %%% on trials w/ error forgiveness for frequency, wait delay + SliderParam(obj,'wait_delay_freq',0.2,0.2,3,x, y,'label','wait delay', 'position', [x y 200 20]);next_row(y); + + %%% helper lights on/off for frequency trials + ToggleParam(obj, 'helper_lights_freq', 1, x, y, ... + 'OffString', 'Freq. lights OFF', ... + 'OnString', 'Freq. lights ON', ... + 'TooltipString', 'If on (black), LED lights help indicate what to do; if off (brown), no helper LED lights','position', [x y 100 20]); + + %%% toggle error forgiveness on/off for frequency trials + ToggleParam(obj, 'error_forgiveness_freq', 1, x, y, ... + 'OffString', 'Forgive OFF', ... + 'OnString', 'Forgive ON', ... + 'TooltipString', 'If on (black), start new trial upon wrong side choice; if off (brown), can choose again',... + 'position', [x+100 y 100 20]);next_row(y,1.1); + + %%% required time with nose in center + SliderParam(obj,'nose_in_center',0.05,0.05,1.3,x, y,'label','min NIC', 'position', [x y 200 20]);next_row(y,1.1); + + SubheaderParam(obj,'title','Training parameters',x,y, 'position', [x y 200 18]); next_row(y); + + + + + + + + + %%%%%% COLUMN 3 %%%%%% + y=5; next_column(x); + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% DIRECTION TASK %%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%% delay for each quadrant + DispParam(obj, 'Delay3Dir',4, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Delay4Dir',4, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Delay2Dir',4, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Delay1Dir',4, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,1.1); + + %%% water for each quadrant + DispParam(obj, 'Water3Dir',1, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Water4Dir',1, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Water2Dir',1, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Water1Dir',1, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,1.1); + + %%% probability for each quadrant + DispParam(obj, 'Prob3Dir',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Prob4Dir',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Prob2Dir',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Prob1Dir',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,1.1); + + %%% percent correct for each quadrant + DispParam(obj, 'Bias3Dir',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Bias4Dir',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Bias2Dir',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Bias1Dir',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,1.1); + + SubheaderParam(obj,'title','Quadrant Antibias',x,y, 'position', [x y 407 20]); next_row(y,1); + + + + DispParam(obj, 'WaterL',1, x, y,'position', [x y 100 20]); + DispParam(obj, 'WaterR',1, x, y,'position', [x+100 y 100 20]); + + next_column(x); + + DispParam(obj, 'DelayL',4, x, y,'position', [x y 100 20]); + DispParam(obj, 'DelayR',4, x, y,'position', [x+100 y 100 20]);next_row(y,1.1); + + next_column(x,-1); + + DispParam(obj, 'BiasL',0.5, x, y,'position', [x y 100 20]); + DispParam(obj, 'BiasR',0.5, x, y,'position', [x+100 y 100 20]); + + next_column(x); + + DispParam(obj, 'ProbL',0.5, x, y,'position', [x y 100 20]); + DispParam(obj, 'ProbR',0.5, x, y,'position', [x+100 y 100 20]);next_row(y,1.1); + + next_column(x,-1); + SubheaderParam(obj,'title','Side Antibias',x,y, 'position', [x y 407 20]); next_row(y,1.1); + + + %%%%%%%%%%% all the possible durations and gammas + NumeditParam(obj, 'durations_dir', [1.3], x, y, 'position', [x y 200 20], ... + 'label','Duration values','TooltipString', 'possible stimulus durations');next_row(y); + NumeditParam(obj, 'gamma_freq_values_dir', [0], x, y, 'position', [x y 200 20], ... + 'label','Gamma_freq values','TooltipString', 'possible gamma_freq values');next_row(y); + NumeditParam(obj, 'gamma_dir_values_dir', [4], x, y, 'position', [x y 200 20], ... + 'label','Gamma_dir values','TooltipString', 'possible gamma_dir values');next_row(y); + + + + + + %%%%%% COLUMN 3 %%%%%% + y=5; next_column(x); + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% FREQUENCY TASK %%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + %%% delay for each quadrant + DispParam(obj, 'Delay3Freq',4, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Delay4Freq',4, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Delay2Freq',4, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Delay1Freq',4, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,1.1); + + %%% water for each quadrant + DispParam(obj, 'Water3Freq',1, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Water4Freq',1, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Water2Freq',1, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Water1Freq',1, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,1.1); + + %%% probability for each quadrant + DispParam(obj, 'Prob3Freq',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Prob4Freq',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Prob2Freq',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Prob1Freq',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,1.1); + + %%% percent correct for each quadrant + DispParam(obj, 'Bias3Freq',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Bias4Freq',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y); + DispParam(obj, 'Bias2Freq',0.25, x, y,'labelfraction',0.6,'position', [x y 100 20]); + DispParam(obj, 'Bias1Freq',0.25, x, y,'labelfraction',0.6,'position', [x+100 y 100 20]);next_row(y,5.4); + + + + + %%%%%%%%%%% all the possible durations and gammas + NumeditParam(obj, 'durations_freq', [1.3], x, y, 'position', [x y 200 20], ... + 'label','Duration values','TooltipString', 'possible stimulus durations');next_row(y); + NumeditParam(obj, 'gamma_freq_values_freq', [4], x, y, 'position', [x y 200 20], ... + 'label','Gamma_freq values','TooltipString', 'possible gamma_freq values');next_row(y); + NumeditParam(obj, 'gamma_dir_values_freq', [0], x, y, 'position', [x y 200 20], ... + 'label','Gamma_dir values','TooltipString', 'possible gamma_dir values');next_row(y); + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%% IMAGE PLOTS %%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%% button to update all plots + PushbuttonParam(obj,'draw', x, y, 'position', [x+170 y 30 20],'label', 'draw'); + set_callback(draw, {mfilename, 'update_plot_button'}); + next_row(y); + + + %%% direction plot + newaxes = axes; + SoloParamHandle(obj, 'myaxesdir', 'saveable', 0,'value', double(newaxes)); + set(value(myaxesdir),'Position', [.52 .58 .21 .21]); + SoloParamHandle(obj, 'matrixdir', 'saveable',0); + + + %%% frequency plot + newaxes = axes; + SoloParamHandle(obj, 'myaxesfreq', 'saveable', 0,'value', double(newaxes)); + set(value(myaxesfreq),'Position', [.74 .58 .21 .21]); + SoloParamHandle(obj, 'matrixfreq', 'saveable',0); + + + feval(mfilename, obj, 'initialize_plots'); + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%% STIMULUS VARIABLES %%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + next_row(y,9.3); + next_column(x,-1); + + SubheaderParam(obj,'title','This',x,y, 'position', [x y 50 20]); + %%% current quadrant/side + DispParam(obj, 'ThisSide','RIGHT', x, y,'label','Side','position', [x+50 y 70 20]); + DispParam(obj, 'ThisQuadrant',4, x, y,'label','Quad','position', [x+120 y 70 20]); + %%% current duration + DispParam(obj, 'ThisDuration', 1.3, x, y,'label','Dur', 'position', [x+190 y 70 20], 'labelfraction', 0.6); + %%% current gammas + DispParam(obj, 'ThisGamma_dir',1, x, y,'label','Gdir','position', [x+260 y 70 20]); + DispParam(obj, 'ThisGamma_freq',1, x, y,'label','Gfreq','position', [x+330 y 70 20]);next_row(y); + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%% LAST TRIALS PLOT %%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + newaxes = axes; + SoloParamHandle(obj, 'myaxeshistory0', 'saveable', 0,'value', double(newaxes)); + set(value(myaxeshistory0),'Position', [.87 .83 .08 .08]); + set(value(myaxeshistory0),'XLim',[0.5 6.5],'YLim',[0.5 6.5]); + set(value(myaxeshistory0),'XTick',[],'YTick',[]); + set(value(myaxeshistory0),'Box','off'); + + newaxes = axes; + SoloParamHandle(obj, 'myaxeshistory1', 'saveable', 0,'value', double(newaxes)); + set(value(myaxeshistory1),'Position', [.775 .83 .08 .08]); + set(value(myaxeshistory0),'XLim',[0.5 6.5],'YLim',[0.5 6.5]); + set(value(myaxeshistory1),'XTick',[],'YTick',[]); + set(value(myaxeshistory1),'Box','off'); + + newaxes = axes; + SoloParamHandle(obj, 'myaxeshistory2', 'saveable', 0,'value', double(newaxes)); + set(value(myaxeshistory2),'Position', [.68 .83 .08 .08]); + set(value(myaxeshistory2),'XTick',[],'YTick',[]); + set(value(myaxeshistory2),'Box','off'); + + newaxes = axes; + SoloParamHandle(obj, 'myaxeshistory3', 'saveable', 0,'value', double(newaxes)); + set(value(myaxeshistory3),'Position', [.585 .83 .08 .08]); + set(value(myaxeshistory3),'XTick',[],'YTick',[]); + set(value(myaxeshistory3),'Box','off'); + + newaxes = axes; + SoloParamHandle(obj, 'myaxeshistory4', 'saveable', 0,'value', double(newaxes)); + set(value(myaxeshistory4),'Position', [.49 .83 .08 .08]); + set(value(myaxeshistory4),'XTick',[],'YTick',[]); + set(value(myaxeshistory4),'Box','off'); + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% INTERNAL VARIBLES %%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %stimulus variables + SoloParamHandle(obj, 'ThisSideFrequency', 'value', 'RIGHT'); + SoloParamHandle(obj, 'ThisSideDirection', 'value', 'RIGHT'); + SoloParamHandle(obj, 'incoherent_trial', 'value', 1); + + %variables to send to the state matrix + SoloParamHandle(obj, 'total_error_delay', 'value', 0); + SoloParamHandle(obj, 'total_water_multiplier', 'value', 1); + + %information about current stimulus to be saved in the data file + SoloParamHandle(obj, 'ThisStimulus', 'value', []); + + + + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% SEND OUT VARIBLES %%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%% send to the state matrix section + SoloFunctionAddVars('SMA1', 'ro_args',{'ThisTask';'settling_time';... + 'reward_delay';'nose_in_center';'nic_delay';... + 'error_forgiveness_freq';'wait_delay_freq';... + 'wait_for_cpoke_timeout';'wait_for_spoke_timeout'; + 'timeout_delay';'ThisSide';'helper_lights_freq';... + 'total_error_delay';'total_water_multiplier'}); + + %%% send to the training section + SoloFunctionAddVars('TrainingSection', 'rw_args', {... + 'antibias_type';'gamma_dir_values_dir';'gamma_dir_values_freq';... + 'gamma_freq_values_dir';'gamma_freq_values_freq';... + 'durations_dir';'durations_freq';'stimulus_mixing_dir';... + 'stimulus_mixing_freq';'nose_in_center' ;'error_forgiveness_freq';... + 'wait_delay_freq';'helper_lights_freq';'ThisTask';... + 'randomize_first_task';'task_switch_auto';'task_switch_min_perf'}); + + %%% send to the history section + SoloFunctionAddVars('HistorySection', 'ro_args', {'ThisSide';... + 'ThisQuadrant';'incoherent_trial';... + 'ThisGamma_dir';'ThisGamma_freq';'ThisTask'}); + + SoloFunctionAddVars('HistorySection', 'rw_args', {'nTrials_task';... + 'nTrials_coh_task';'nTrials_incoh_task';'total_correct_task';... + 'total_correct_incoherent_task';... + 'total_correct_coherent_task'}); + + + + + + + case 'next_trial', + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%% WHAT TASK ON THIS TRIAL? %%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + was_block_switch.value=0; + + %%% choose task for the first trial in the session + if(n_done_trials<=1) + if(value(randomize_first_task)==1) + if(rand(1)>0.5) + ThisTask.value='Direction'; + else + ThisTask.value='Frequency'; + end + else + ThisTask.value='Direction'; + end + + %%% it's not the first trial: should we switch task? + elseif(value(task_switch_auto)==1) + + %%% requirement 1: at least N trials in this task + flag1=value(nTrials_task)>=value(task_switch_mintrials); + if(value(exist_incoherent)) + %%% requirement 2: performances in the last N trials coherent above min + flag2a=value(total_correct_coherent_task)>=value(task_switch_min_perf); + %%% requirement 3: performances in the last N trials incoherent above min + flag2b=value(total_correct_incoherent_task)>=value(task_switch_min_perf); + flag2=(flag2a && flag2b); + else + %%% requirement 2: performances in the last N trials above min + flag2=value(total_correct_task)>=value(task_switch_min_perf); + end + + if(flag1 && flag2) %%% switch task + was_block_switch.value=1; + if(strcmp(value(ThisTask),'Direction')) + %from direction to frequency + ThisTask.value='Frequency'; + else + %from frequency to direction + ThisTask.value='Direction'; + end + end + end + + %%% setup stimulus variables according to current task + if(strcmp(value(ThisTask),'Direction')) + durations=value(durations_dir); + gamma_dir_values=value(gamma_dir_values_dir); + gamma_freq_values=value(gamma_freq_values_dir); + crosstalk_dir=0; + crosstalk_freq=1-value(stimulus_mixing_dir); + elseif(strcmp(value(ThisTask),'Frequency')) + durations=value(durations_freq); + gamma_dir_values=value(gamma_dir_values_freq); + gamma_freq_values=value(gamma_freq_values_freq); + crosstalk_freq=0; + crosstalk_dir=1-value(stimulus_mixing_freq); + else + error('what task?') + end + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%% CHOOSE ANTIBIAS %%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + %%% is it possible to have incoherent trials? + + %stimuli aren't mixed + flag1=value(stimulus_mixing_dir)<1; + flag2=value(stimulus_mixing_freq)<1; + %one of the dimensions is not modulated + flag3=(length(gamma_dir_values)==1 && gamma_dir_values(1)==0); + flag4=(length(gamma_freq_values)==1 && gamma_freq_values(1)==0); + + if(flag1 || flag2 || flag3 || flag4) + %there is no such thing as an incoherent trial + exist_incoherent.value=0; + else + exist_incoherent.value=1; + end + + + if(value(antibias_toggle)==1) + if(value(exist_incoherent)==1) + antibias_type.value='Quadrant antibias'; + else + antibias_type.value='Side antibias'; + end + else + antibias_type.value='No antibias'; + end + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%% COMPUTE ANTIBIAS %%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + vec_hit=value(hit_history); + %%% kernel function: last few trials are the most important, + %%% Ab_Tau decides how many trials back matter + kernel = exp(-(0:length(vec_hit)-1)/value(Ab_Tau)); + kernel = kernel(end:-1:1); + + + %%% SIDE ANTIBIAS + vec_sides=value(side_history); + vec_sides = vec_sides(1:length(vec_hit)); + %%%% compute bias + if(isempty(find(vec_sides=='l',1)) || isempty(find(vec_sides=='r',1))) + fracs=[0.5 0.5]; + else + bias_left = nansum(vec_hit(vec_sides=='l') .* kernel(vec_sides=='l'))/sum(kernel(vec_sides=='l')); + bias_right = nansum(vec_hit(vec_sides=='r') .* kernel(vec_sides=='r'))/sum(kernel(vec_sides=='r')); + fracs=[bias_left bias_right]; + end; + fracs=fracs./sum(fracs); + if(~isempty(find(isnan(fracs)))) + fracs=[0.5 0.5]; + end + BiasL.value=round(fracs(1)*100)/100; + BiasR.value=round(fracs(2)*100)/100; + %%% compute resulting probabilities, water and delay + p = exp(-fracs*value(Ab_Beta)); + p=p./sum(p); + ProbL.value=p(1); + ProbR.value=p(2); + WaterL.value=p(1)*2; + WaterR.value=p(2)*2; + DelayL.value=max(p(1)*8,2.5); + DelayR.value=max(p(2)*8,2.5); + + + + + %%% QUADRANT ANTIBIAS + vec_quad=value(quadrant_history); + vec_task=value(task_history); + vec_quad = vec_quad(1:length(vec_hit)); + vec_task = vec_task(1:length(vec_hit)); + vec_quadd=vec_quad(vec_task=='d'); + vec_hitd=vec_hit(vec_task=='d'); + vec_quadf=vec_quad(vec_task=='f'); + vec_hitf=vec_hit(vec_task=='f'); + %%%% compute bias for direction task + if(isempty(find(vec_quadd==1,1)) || isempty(find(vec_quadd==2,1)) ||... + isempty(find(vec_quadd==4,1)) || isempty(find(vec_quadd==3,1))) + fracsd=[0.25 0.25 0.25 0.25]; + else + fracsd=nan(1,4); + for i=1:4 + fracsd(i)=nansum(vec_hitd(vec_quadd==i) .* kernel(vec_quadd==i))/sum(kernel(vec_quadd==i)); + end + end; + fracsd=fracsd./sum(fracsd); + if(~isempty(find(isnan(fracsd)))) + fracsd=[0.25 0.25 0.25 0.25]; + end + Bias1Dir.value=round(fracsd(1)*100)/100; + Bias2Dir.value=round(fracsd(2)*100)/100; + Bias3Dir.value=round(fracsd(3)*100)/100; + Bias4Dir.value=round(fracsd(4)*100)/100; + %%% compute resulting probabilities, water and delay for direction task + p = exp(-fracsd*1.5*value(Ab_Beta)); + p=p./sum(p); + Prob1Dir.value=p(1); + Prob2Dir.value=p(2); + Prob3Dir.value=p(3); + Prob4Dir.value=p(4); + Water1Dir.value=p(1)*4; + Water2Dir.value=p(2)*4; + Water3Dir.value=p(3)*4; + Water4Dir.value=p(4)*4; + Delay1Dir.value=max(p(1)*16,2); + Delay2Dir.value=max(p(2)*16,2); + Delay3Dir.value=max(p(3)*16,2); + Delay4Dir.value=max(p(4)*16,2); + + %%%% compute bias for frequency task + if(isempty(find(vec_quadf==1,1)) || isempty(find(vec_quadf==2,1)) ||... + isempty(find(vec_quadf==4,1)) || isempty(find(vec_quadf==3,1))) + fracsf=[0.25 0.25 0.25 0.25]; + else + fracsf=nan(1,4); + for i=1:4 + fracsf(i)=nansum(vec_hitf(vec_quadf==i) .* kernel(vec_quadf==i))/sum(kernel(vec_quadf==i)); + end + end; + fracsf=fracsf./sum(fracsf); + if(~isempty(find(isnan(fracsf)))) + fracsf=[0.25 0.25 0.25 0.25]; + end + Bias1Freq.value=round(fracsf(1)*100)/100; + Bias2Freq.value=round(fracsf(2)*100)/100; + Bias3Freq.value=round(fracsf(3)*100)/100; + Bias4Freq.value=round(fracsf(4)*100)/100; + %%% compute resulting probabilities, water and delay for frequency task + p = exp(-fracsf*value(Ab_Beta)); + p=p./sum(p); + Prob1Freq.value=p(1); + Prob2Freq.value=p(2); + Prob3Freq.value=p(3); + Prob4Freq.value=p(4); + Water1Freq.value=p(1)*4; + Water2Freq.value=p(2)*4; + Water3Freq.value=p(3)*4; + Water4Freq.value=p(4)*4; + Delay1Freq.value=max(p(1)*16,2); + Delay2Freq.value=max(p(2)*16,2); + Delay3Freq.value=max(p(3)*16,2); + Delay4Freq.value=max(p(4)*16,2); + + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%% SELECT QUADRANT/SIDE %%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + %INCOHERENT IS DEFINED -> CHOOSE QUADRANT + if(value(exist_incoherent)==1) + + %%%% DIRECTION TASK + if(strcmp(value(ThisTask),'Direction')) + %%%% CHOOSE THE QUADRANT + if(strcmp(value(antibias_type),'Quadrant antibias')) + ThisQuadrant.value=find(mnrnd(1,[value(Prob1Dir) value(Prob2Dir)... + value(Prob3Dir) value(Prob4Dir)])==1); + elseif(strcmp(value(antibias_type),'Side antibias')) + ThisQuadrant.value=find(mnrnd(1,[value(Prob1Dir) value(Prob2Dir)... + value(Prob3Dir) value(Prob4Dir)])==1); + elseif(strcmp(value(antibias_type),'No antibias')) + ThisQuadrant.value=find(mnrnd(1,[0.25 0.25 0.25 0.25])==1); + else + error('what antibias????') + end + %%%% ASSIGN THE SIDES + if(value(ThisQuadrant)==1) + ThisSide.value='RIGHT'; + ThisSideDirection.value='RIGHT'; + ThisSideFrequency.value='LEFT'; + incoherent_trial.value=1; + end + if(value(ThisQuadrant)==2) + ThisSide.value='LEFT'; + ThisSideDirection.value='LEFT'; + ThisSideFrequency.value='LEFT'; + incoherent_trial.value=0; + end + if(value(ThisQuadrant)==3) + ThisSide.value='LEFT'; + ThisSideDirection.value='LEFT'; + ThisSideFrequency.value='RIGHT'; + incoherent_trial.value=1; + end + if(value(ThisQuadrant)==4) + ThisSide.value='RIGHT'; + ThisSideDirection.value='RIGHT'; + ThisSideFrequency.value='RIGHT'; + incoherent_trial.value=0; + end + + %%%% FREQUENCY TASK + elseif(strcmp(value(ThisTask),'Frequency')) + %%%% CHOOSE THE QUADRANT + if(strcmp(value(antibias_type),'Quadrant antibias')) + ThisQuadrant.value=find(mnrnd(1,[value(Prob1Freq) value(Prob2Freq)... + value(Prob3Freq) value(Prob4Freq)])==1); + elseif(strcmp(value(antibias_type),'Side antibias')) + ThisQuadrant.value=find(mnrnd(1,[0 value(ProbL) 0 value(ProbR)])==1); + elseif(strcmp(value(antibias_type),'No antibias')) + ThisQuadrant.value=find(mnrnd(1,[0.25 0.25 0.25 0.25])==1); + else + error('what antibias????') + end + %%%% ASSIGN THE SIDES + if(value(ThisQuadrant)==1) + ThisSide.value='LEFT'; + ThisSideDirection.value='RIGHT'; + ThisSideFrequency.value='LEFT'; + incoherent_trial.value=1; + end + if(value(ThisQuadrant)==2) + ThisSide.value='LEFT'; + ThisSideDirection.value='LEFT'; + ThisSideFrequency.value='LEFT'; + incoherent_trial.value=0; + end + if(value(ThisQuadrant)==3) + ThisSide.value='RIGHT'; + ThisSideDirection.value='LEFT'; + ThisSideFrequency.value='RIGHT'; + incoherent_trial.value=1; + end + if(value(ThisQuadrant)==4) + ThisSide.value='RIGHT'; + ThisSideDirection.value='RIGHT'; + ThisSideFrequency.value='RIGHT'; + incoherent_trial.value=0; + end + else + error('what task????') + end + + %INCOHERENT NOT DEFINED -> ONLY CHOOSE THE SIDE + else + incoherent_trial.value=0; + ThisQuadrant.value=NaN; + %CHOOSE SIDE + sidevals={'LEFT','RIGHT'}; + if(strcmp(value(antibias_type),'Side antibias')) + ind=find(mnrnd(1,[value(ProbL) value(ProbR)])==1); + elseif(strcmp(value(antibias_type),'No antibias')) + ind=find(mnrnd(1,[0.5 0.5])==1); + else + error('what antibias???') + end + %%% ASSIGN THE SIDE + ThisSide.value=sidevals{ind}; + ThisSideDirection.value=sidevals{ind}; + ThisSideFrequency.value=sidevals{ind}; + end + + + + + + + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% ASSIGN WATER/DELAY %%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %water multiplier + if(value(water_increase_toggle)==1) + water_increase_multiplier.value=value(WI_base)+n_done_trials*value(WI_ratio); + water_increase_multiplier.value=min([value(WI_max),value(water_increase_multiplier)]); + else + water_increase_multiplier.value=1; + end + + + %%%% QUADRANT ANTIBIAS + if(strcmp(value(antibias_type),'Quadrant antibias')) + %%%% DIRECTION TASK + if(strcmp(value(ThisTask),'Direction')) + if(value(ThisQuadrant)==1) + total_water_multiplier.value=value(Water1Dir)*value(water_increase_multiplier); + total_error_delay.value=value(Delay1Dir); + end + if(value(ThisQuadrant)==2) + total_water_multiplier.value=value(Water2Dir)*value(water_increase_multiplier); + total_error_delay.value=value(Delay2Dir); + end + if(value(ThisQuadrant)==3) + total_water_multiplier.value=value(Water3Dir)*value(water_increase_multiplier); + total_error_delay.value=value(Delay3Dir); + end + if(value(ThisQuadrant)==4) + total_water_multiplier.value=value(Water4Dir)*value(water_increase_multiplier); + total_error_delay.value=value(Delay4Dir); + end + %%%% FREQUENCY TASK + elseif(strcmp(value(ThisTask),'Frequency')) + if(value(ThisQuadrant)==1) + total_water_multiplier.value=value(Water1Freq)*value(water_increase_multiplier); + total_error_delay.value=value(Delay1Freq); + end + if(value(ThisQuadrant)==2) + total_water_multiplier.value=value(Water2Freq)*value(water_increase_multiplier); + total_error_delay.value=value(Delay2Freq); + end + if(value(ThisQuadrant)==3) + total_water_multiplier.value=value(Water3Freq)*value(water_increase_multiplier); + total_error_delay.value=value(Delay3Freq); + end + if(value(ThisQuadrant)==4) + total_water_multiplier.value=value(Water4Freq)*value(water_increase_multiplier); + total_error_delay.value=value(Delay4Freq); + end + else + error('what task????') + end + %%%% SIDE ANTIBIAS + elseif(strcmp(value(antibias_type),'Side antibias')) + if(strcmp(value(ThisSide),'RIGHT')) + total_water_multiplier.value=value(WaterR)*value(water_increase_multiplier); + total_error_delay.value=value(DelayR); + elseif(strcmp(value(ThisSide),'LEFT')) + total_water_multiplier.value=value(WaterL)*value(water_increase_multiplier); + total_error_delay.value=value(DelayL); + else + error('what side???') + end + %%%% NO ANTIBIAS + elseif(strcmp(value(antibias_type),'No antibias')) + total_water_multiplier.value=1; + total_error_delay.value=4; + else + error('what antibias???') + end + + + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%% GENERATE STIMULUS %%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + %%%% get sample rate + srate = SoundManagerSection(obj, 'get_sample_rate'); + + + %%% select duration for the current trial + vec=durations; + ra=randperm(length(vec)); + ThisDuration.value=vec(ra(1)); + + + %%% select gamma frequency for the current trial + vec=value(gamma_freq_values); + ra=randperm(length(vec)); + if(strcmp(value(ThisSideFrequency),'RIGHT')) + ThisGamma_freq.value=vec(ra(1)); + elseif(strcmp(value(ThisSideFrequency),'LEFT')) + ThisGamma_freq.value=-vec(ra(1)); + else + error('what?') + end + + + %%% select gamma direction for the current trial + vec=value(gamma_dir_values); + ra=randperm(length(vec)); + if(strcmp(value(ThisSideDirection),'RIGHT')) + ThisGamma_dir.value=vec(ra(1)); + elseif(strcmp(value(ThisSideDirection),'LEFT')) + ThisGamma_dir.value=-vec(ra(1)); + else + error('whattt?') + end + + + freq_vec=[value(freq_lo) value(freq_hi)]; + + + [snd data] = make_pbup_mixed3(value(total_rate),... + value(ThisGamma_dir),value(ThisGamma_freq), srate, value(ThisDuration), ... + 'bup_width',value(bup_width),'crosstalk_dir', crosstalk_dir,... + 'crosstalk_freq', crosstalk_freq,'freq_vec',freq_vec,'bup_ramp',... + value(bup_ramp),'vol_low',value(vol_low_freq),'vol_hi',value(vol_hi_freq)); + + + snd=snd*value(vol); + + + if ~SoundManagerSection(obj, 'sound_exists', 'StimulusSound'), + SoundManagerSection(obj, 'declare_new_sound', 'StimulusSound'); + SoundManagerSection(obj, 'set_sound', 'StimulusSound', snd); + else + snd_prev = SoundManagerSection(obj, 'get_sound', 'StimulusSound'); + if ~isequal(snd, snd_prev), + SoundManagerSection(obj, 'set_sound', 'StimulusSound', snd); + end; + end; + + bpt.freqs=freq_vec; + bpt.crosstalk_dir=crosstalk_dir; + bpt.crosstalk_freq=crosstalk_freq; + bpt.bup_width=value(bup_width); + bpt.bup_ramp=value(bup_ramp); + bpt.vol_low=value(vol_low_freq); + bpt.vol_hi=value(vol_hi_freq); + bpt.vol=value(vol); + bpt.gamma_dir = value(ThisGamma_dir); + bpt.gamma_freq = value(ThisGamma_freq); + bpt.duration = value(ThisDuration); + bpt.left_hi = data.left_hi; + bpt.right_hi = data.right_hi; + bpt.left_lo = data.left_lo; + bpt.right_lo = data.right_lo; + ThisStimulus.value = bpt; + push_history(ThisStimulus); + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%% UPDATE PLOT BUTTON %%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + case 'update_plot_button', + + %%% re-initialize the plots regardless of n_done_trials + feval(mfilename, obj, 'initialize_plots'); + + %populate the images + feval(mfilename, obj, 'update_plot'); + + + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%% UPDATE PLOT %%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + case 'update_plot', + + if(n_done_trials==1) + %if it's the first trial, re-initialize plots + feval(mfilename, obj, 'initialize_plots'); + end + + %%% GET THE DATA + hits=value(hit_history); + side=value(side_history); + task=value(task_history); + task=task(1:length(hits)); + gdir=value(gammadir_history); + gdir=gdir(1:length(hits)); + gfreq=value(gammafreq_history); + gfreq=gfreq(1:length(hits)); + %remove violation trials + ind=find(~isnan(hits)); + hits=hits(ind); + side=side(ind); + task=task(ind); + gdir=gdir(ind); + gfreq=gfreq(ind); + %save choice (where the rat went) + choice=nan(1,length(side)); + for i=1:length(side) + if(hits(i)==1) + if(side(i)=='r') + choice(i)=1; + else + choice(i)=0; + end + else + if(side(i)=='r') + choice(i)=0; + else + choice(i)=1; + end + end + end + + + %%% POPULATE DIRECTION TASK + + %unique values of direction during direction trials + xvec=value(gamma_dir_values_dir); + xvec=unique([-xvec xvec]); + %unique values of frequency during direction trials + yvec=value(gamma_freq_values_dir); + yvec=unique([-yvec yvec]); + n1=length(xvec); + n2=length(yvec); + %subselect direction trials + curtask='d'; + ind=find(task==curtask); + gdir1=gdir(ind); + gfreq1=gfreq(ind); + choice1=choice(ind); + %populate matrix + matrice=nan(n2,n1); + for i=1:n2 + for j=1:n1 + indz=find(gfreq1==yvec(i) & gdir1==xvec(j)); + if(~isempty(indz)) + matrice(i,j)=mean(choice1(indz)); + else + matrice(i,j)=NaN; + end + end + end + %retrieve image handle + hdir=value(matrixdir); + %populate image + set(hdir,'CData',matrice); + + + %%% POPULATE FREQUENCY TASK + + %unique values of direction during frequency trials + xvec=value(gamma_dir_values_freq); + xvec=unique([-xvec xvec]); + %unique values of frequency during frequency trials + yvec=value(gamma_freq_values_freq); + yvec=unique([-yvec yvec]); + n1=length(xvec); + n2=length(yvec); + %subselect frequency trials + curtask='f'; + ind=find(task==curtask); + gdir1=gdir(ind); + gfreq1=gfreq(ind); + choice1=choice(ind); + %populate matrix + matrice=nan(n2,n1); + for i=1:n2 + for j=1:n1 + indz=find(gfreq1==yvec(i) & gdir1==xvec(j)); + if(~isempty(indz)) + matrice(i,j)=mean(choice1(indz)); + else + matrice(i,j)=NaN; + end + end + end + %retrieve image handle + hfreq=value(matrixfreq); + %populate image + set(hfreq,'CData',matrice); + + + + %%% POPULATE LAST TRIAL PLOT + + + hits=value(hit_history); + task=value(task_history); + task=task(1:length(hits)); + gdir=value(gammadir_history); + gdir=gdir(1:length(hits)); + gfreq=value(gammafreq_history); + gfreq=gfreq(1:length(hits)); + + if(~isempty(hits) && ~isnan(hits(end))) + + %%% copy over old plots + cla(value(myaxeshistory4)); + copyobj(get(value(myaxeshistory3),'Children'),value(myaxeshistory4)); + set(value(myaxeshistory4),'XLim',get(value(myaxeshistory3),'XLim')); + set(value(myaxeshistory4),'YLim',get(value(myaxeshistory3),'YLim')); + set(value(myaxeshistory4),'XTick',[],'YTick',[]); + set(value(myaxeshistory4),'Box','off'); + cla(value(myaxeshistory3)); + copyobj(get(value(myaxeshistory2),'Children'),value(myaxeshistory3)); + set(value(myaxeshistory3),'XLim',get(value(myaxeshistory2),'XLim')); + set(value(myaxeshistory3),'YLim',get(value(myaxeshistory2),'YLim')); + set(value(myaxeshistory3),'XTick',[],'YTick',[]); + set(value(myaxeshistory3),'Box','off'); + cla(value(myaxeshistory2)); + copyobj(get(value(myaxeshistory1),'Children'),value(myaxeshistory2)); + set(value(myaxeshistory2),'XLim',get(value(myaxeshistory1),'XLim')); + set(value(myaxeshistory2),'YLim',get(value(myaxeshistory1),'YLim')); + set(value(myaxeshistory2),'XTick',[],'YTick',[]); + set(value(myaxeshistory2),'Box','off'); + cla(value(myaxeshistory1)); + copyobj(get(value(myaxeshistory0),'Children'),value(myaxeshistory1)); + set(value(myaxeshistory1),'XLim',get(value(myaxeshistory0),'XLim')); + set(value(myaxeshistory1),'YLim',get(value(myaxeshistory0),'YLim')); + set(value(myaxeshistory1),'XTick',[],'YTick',[]); + set(value(myaxeshistory1),'Box','off'); + + %%% populate new plot + lasthit=hits(end); + lasttask=task(end); + lastgdir=gdir(end); + lastgfreq=gfreq(end); + cla(value(myaxeshistory0)); + hold(value(myaxeshistory0),'off'); + if(lasttask=='d') + xvec=value(gamma_dir_values_dir); + xvec=unique([-xvec xvec]); + yvec=value(gamma_freq_values_dir); + yvec=unique([-yvec yvec]); + yvec=yvec(end:-1:1); + n1=length(xvec); + n2=length(yvec); + plot(value(myaxeshistory0),mean(1:n1),mean(1:n2),'xb'); + elseif(lasttask=='f') + xvec=value(gamma_dir_values_freq); + xvec=unique([-xvec xvec]); + yvec=value(gamma_freq_values_freq); + yvec=unique([-yvec yvec]); + yvec=yvec(end:-1:1); + n1=length(xvec); + n2=length(yvec); + plot(value(myaxeshistory0),mean(1:n1),mean(1:n2),'or'); + end + hold(value(myaxeshistory0),'on'); + mat=repmat(1:n1,n2,1)'; + vec1=mat(:); + mat=repmat(1:n2,n1,1); + vec2=mat(:); + plot(value(myaxeshistory0),vec1,vec2,'.k') + xval=find(lastgdir==xvec); + yval=find(lastgfreq==yvec); + if(~isempty(xval) && ~isempty(yval)) + if(lasthit==1) + plot(value(myaxeshistory0),xval,yval,'.g','MarkerSize',20) + else + plot(value(myaxeshistory0),xval,yval,'.r','MarkerSize',20) + end + end + set(value(myaxeshistory0),'XLim',[0.5 n1+.5],'YLim',[.5 n2+.5]); + hold(value(myaxeshistory0),'off'); + set(value(myaxeshistory0),'XTick',[],'YTick',[]); + set(value(myaxeshistory0),'Box','off'); + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%% INITIALIZE PLOTS %%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + case 'initialize_plots', + + %%% generate colormap for imagesc plots + bu=nan(64,3); + bu(:,1)=[zeros(23,1); linspace(0,1,17)'; ones(15,1); linspace(1,0.5,9)']; + bu(:,2)=[zeros(7,1); linspace(0,1,17)'; ones(15,1); linspace(1,0,17)'; zeros(8,1)]; + bu(:,3)=[linspace(0.5625,1,8)'; ones(15,1); linspace(1,0,17)'; zeros(24,1)]; + bu=[1 1 1;bu]; + + %%% DIRECTION PLOT + + %unique values of direction during direction trials + xvec=value(gamma_dir_values_dir); + xvec=unique([xvec -xvec]); + %unique values of frequency during direction trials + yvec=value(gamma_freq_values_dir); + yvec=unique([yvec -yvec]); + n1=length(xvec); + n2=length(yvec); + %initialize image and save handle + axes(value(myaxesdir)); + hdir=imagesc(nan(n2,n1),[-0.0159 1]); + matrixdir.value=hdir; + colormap(bu); + colorbar; + axis image + set(value(myaxesdir),'XTick',[],'YTick',[]); + title('Direction') + + + %%% FREQUENCY PLOT + + %unique values of direction during frequency trials + xvec=value(gamma_dir_values_freq); + xvec=unique([xvec -xvec]); + %unique values of frequency during frequency trials + yvec=value(gamma_freq_values_freq); + yvec=unique([yvec -yvec]); + n1=length(xvec); + n2=length(yvec); + %initialize image and save handle + axes(value(myaxesfreq)); + hfreq=imagesc(nan(n2,n1),[-0.0159 1]); + matrixfreq.value=hfreq; + colormap(bu); + colorbar; + axis image + set(value(myaxesfreq),'XTick',[],'YTick',[]); + title('Frequency') + + + + + + case 'hide', + StimuluShow.value = 0; set(value(myfig), 'Visible', 'off'); + case 'show', + StimuluShow.value = 1; set(value(myfig), 'Visible', 'on'); + case 'show_hide', + if StimuluShow == 1, set(value(myfig), 'Visible', 'on'); + else set(value(myfig), 'Visible', 'off'); + end; + + + case 'hide2', + SoundsShow.value = 0; set(value(myfig2), 'Visible', 'off'); + case 'show2', + SoundsShow.value = 1; set(value(myfig2), 'Visible', 'on'); + case 'show_hide2', + if SoundsShow == 1, set(value(myfig2), 'Visible', 'on'); + else set(value(myfig2), 'Visible', 'off'); + end; + + + case 'hide3', + TimingShow.value = 0; set(value(myfig3), 'Visible', 'off'); + case 'show3', + TimingShow.value = 1; set(value(myfig3), 'Visible', 'on'); + case 'show_hide3', + if TimingShow == 1, set(value(myfig3), 'Visible', 'on'); + else set(value(myfig3), 'Visible', 'off'); + end; + + + case 'close', + delete(value(myfig)); + delete(value(myfig2)); + delete(value(myfig3)); + + + case 'get' + val=varargin{1}; + eval(['x=value(' val ');']); + +end + + diff --git a/Protocols/@TaskSwitch4/TaskSwitch4.m b/Protocols/@TaskSwitch4/TaskSwitch4.m new file mode 100644 index 00000000..8956695f --- /dev/null +++ b/Protocols/@TaskSwitch4/TaskSwitch4.m @@ -0,0 +1,224 @@ +% TaskSwitch4 protocol +% Marino Pagan, October 2015 + +function [obj] = TaskSwitch4(varargin) + +% Default object is of our own class (mfilename); +% we inherit only from Plugins + +obj = class(struct, mfilename, saveload, water, ... + pokesplot2, soundmanager, soundui, ... + distribui, comments, sqlsummary); + +%--------------------------------------------------------------- +% BEGIN SECTION COMMON TO ALL PROTOCOLS, DO NOT MODIFY +%--------------------------------------------------------------- + +% If creating an empty object, return without further ado: +if nargin==0 || (nargin==1 && ischar(varargin{1}) && strcmp(varargin{1}, 'empty')), + return; +end; + +if isa(varargin{1}, mfilename), % If first arg is an object of this class itself, we are + % Most likely responding to a callback from + % a SoloParamHandle defined in this mfile. + if length(varargin) < 2 || ~ischar(varargin{2}), + error(['If called with a "%s" object as first arg, a second arg, a ' ... + 'string specifying the action, is required\n']); + else action = varargin{2}; varargin = varargin(3:end); %#ok + end; +else % Ok, regular call with first param being the action string. + action = varargin{1}; varargin = varargin(2:end); %#ok +end; + +GetSoloFunctionArgs(obj); + +switch action, + + case 'init' + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%% INIT %%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + getSessID(obj); + dispatcher('set_trialnum_indicator_flag'); + % Make default figure + SoloParamHandle(obj, 'myfig', 'saveable', 0); myfig.value = double(figure); + % Make the title of the figure be the protocol name + name = mfilename;set(value(myfig), 'Name', name, 'Tag', name, ... + 'closerequestfcn', 'dispatcher(''close_protocol'')', 'MenuBar', 'none'); + % Hack Variable + hackvar = 10; SoloFunctionAddVars('SessionModel', 'ro_args', 'hackvar'); %#ok + % Put the figure where we want it and resize (x,y,width,height) + set(value(myfig), 'Position', [400 100 850 570]); + % From Plugins/@soundmanager: + SoundManagerSection(obj, 'init'); + + + % ---------------------- + % Set up the main GUI window + % ---------------------- + x = 5; y = 5; % Initial position on main GUI window + + + % From Plugins/@saveload: + [x, y] = SavingSection(obj, 'init', x, y); + + next_row(y,-0.4); + + % Comments + [x, y] = CommentsSection(obj, 'init', x, y); + + % From Plugins/@water: + [x, y] = WaterValvesSection(obj, 'init', x, y, 'streak_gui', 1); + + next_row(y,-0.4); + + % PokesPlot + SC = state_colors(obj); + [x, y] = PokesPlotSection(obj,'init',x,y,struct('states',SC)); + PokesPlotSection(obj, 'set_alignon', 'cpoke(1,1)'); + PokesPlotSection(obj, 'hide'); + next_row(y); + + + % History + [x, y] = HistorySection(obj, 'init', x, y); + + + % Training + [x, y] = TrainingSection(obj, 'init', x, y); + + + %%%%%%%%%%%%%%%%%%%%% NEXT COLUMN %%%%%%%%%%%%%%%%%%%%% + y=5; next_column(x); + + + % Stimulus + [x, y] = StimulusSection(obj, 'init', x, y); + + + + figpos = get(gcf, 'Position'); + [expmtr, rname]=SavingSection(obj, 'get_info'); + HeaderParam(obj, 'prot_title', ['TaskSwitch4: ' expmtr ', ' rname], ... + x, y, 'position', [10 figpos(4)-20, 807 20]); + + + % OK, start preparing the first trial + TaskSwitch4(obj, 'prepare_next_trial'); + + + + + case 'prepare_next_trial' + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%% PREPARE NEXT TRIAL %%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + %%% update history + HistorySection(obj, 'next_trial'); + + %%% update training stage + TrainingSection(obj, 'next_trial'); + + %%% set up stimulus for next trial + StimulusSection(obj, 'next_trial'); + + + SoundManagerSection(obj, 'send_not_yet_uploaded_sounds'); + + % invoke autosave + SavingSection(obj, 'autosave_data'); + if n_done_trials==1 + [expmtr, rname]=SavingSection(obj, 'get_info'); + prot_title.value=['TaskSwitch4 - on rig ' get_hostname ' : ' expmtr ', ' rname '. Started at ' datestr(now, 'HH:MM')]; + + + + end + + %%% prepare the actual state matrix + SMA1(obj,'next_trial'); + + + %%% this line updates the "training room" webpage on Zut + try send_n_done_trials(obj); end + + + + case 'trial_completed' + feval(mfilename, 'update'); + + StimulusSection(obj, 'update_plot'); + + % And PokesPlot needs completing the trial: + PokesPlotSection(obj, 'trial_completed'); + + if n_done_trials==1, + CommentsSection(obj, 'append_date'); + CommentsSection(obj, 'append_line', ''); + end; + CommentsSection(obj, 'clear_history'); % Make sure we're not storing unnecessary history + + + + case 'update' + PokesPlotSection(obj, 'update'); + + + + case 'close' + PokesPlotSection(obj, 'close'); + CommentsSection(obj, 'close'); + + if exist('myfig', 'var') && isa(myfig, 'SoloParamHandle') && ishandle(value(myfig)), + delete(value(myfig)); + end; + + if exist('myfig2', 'var') && isa(myfig2, 'SoloParamHandle') && ishandle(value(myfig2)), + delete(value(myfig2)); + end; + + if exist('myfig3', 'var') && isa(myfig3, 'SoloParamHandle') && ishandle(value(myfig3)), + delete(value(myfig3)); + end; + + try + delete_sphandle('owner', ['^@' class(obj) '$']); + catch + warning('Some SoloParams were not properly cleaned up'); + end + + + case 'end_session' + HistorySection(obj, 'end_session'); + TrainingSection(obj, 'end_session'); + prot_title.value = [value(prot_title) ', Ended at ' datestr(now, 'HH:MM')]; + + + case 'pre_saving_settings' + HistorySection(obj, 'make_and_send_summary'); + + + case 'get' + val=varargin{1}; + + eval(['x=value(' val ');']); + + + otherwise, + warning('Unknown action! "%s"\n', action); +end; + +return; + diff --git a/Protocols/@TaskSwitch4/TrainingSection.m b/Protocols/@TaskSwitch4/TrainingSection.m new file mode 100644 index 00000000..1b9993df --- /dev/null +++ b/Protocols/@TaskSwitch4/TrainingSection.m @@ -0,0 +1,748 @@ +function [x, y] = TrainingSection(obj, action, varargin) + +%%%% TODO: + +%%% training stages and substages : when nic grows etc. -> make explicit + +%%% add the possibility to go back in training (???) +%manual +%%% reward more the harder task / make it easier??? +%manual +%%% put some constant checks: bias -> update antibias ; motivation -> increase water ; incoherent trial performance is low -> pump up incoh delay+reward +%manual + +GetSoloFunctionArgs(obj); + + +switch action, + + % ------------------------------------------------------------------ + % INIT + % ------------------------------------------------------------------ + + case 'init' + x=varargin{1}; + y=varargin{2}; + + %%%% AUTO STAGE SWITCH PARAMETERS + + + + ToggleParam(obj, 'stage_switch_auto', 1, x, y, 'position', [x y 200 20], ... + 'OffString', 'Autotrain OFF', 'OnString', 'Autotrain ON', ... + 'TooltipString', 'If on, switches automatically between training stages'); + next_row(y,1); + + + + + NumeditParam(obj, 'nDays_stage',1, x, y, 'labelfraction', 0.55,'label','nDays','position', [x y 100 20]); + NumeditParam(obj, 'nTrials_stage',0, x, y, 'labelfraction', 0.55,'label','nTrials','position', [x+100 y 100 20]);next_row(y); + + + + %%%% MANUALLY SET THE STAGE + + DispParam(obj, 'stage_explanation', sprintf('Stage description'),... + x, y, 'label','','position', [x y 200 20], 'labelfraction', 0.01,... + 'TooltipString', 'Description of current stage');next_row(y); + + + MenuParam(obj, 'training_stage', {'Stage 1'; 'Stage 2'; 'Stage 3';... + 'Stage 4'; 'Stage 5'; 'Stage 6'; 'Stage 7'; 'Stage 8a'; 'Stage 8b';... + 'Stage 8'; 'Stage 9a'; 'Stage 9b'; 'Stage 9'; 'Stage 10a'; 'Stage 10b';... + 'Stage 10'; 'Stage grow NIC'}, 1, x, y, ... + 'label', 'Active Stage', 'TooltipString', 'the current training stage'); + + PushbuttonParam(obj,'update_active_stage', x, y, 'position', [x+170 y 30 20],'label', 'OK'); + set_callback(update_active_stage, {mfilename, 'update_stage_button'}); + next_row(y); + + SubheaderParam(obj,'title',mfilename,x,y); + next_row(y, 1.5); + + SoloFunctionAddVars('HistorySection', 'ro_args', {'training_stage';'nDays_stage'}); + SoloFunctionAddVars('HistorySection', 'rw_args', {'nTrials_stage'}); + + + + + case 'next_trial', + if(n_done_trials>1 && value(stage_switch_auto)==1) + feval(mfilename, obj, 'update_stage'); + end + + + + + case 'update_stage_button', + + %reboot stage + nTrials_stage.value= 0; + nDays_stage.value= 1; + previous_stage.value=value(training_stage); + + %set parameters + feval(mfilename, obj, 'update_stage'); + + + + + case 'update_stage', + + %new stage? + if(~strcmp(value(previous_stage),value(training_stage))) + nTrials_stage.value= 0; + nDays_stage.value= 1; + previous_stage.value=value(training_stage); + end + + + + switch value(training_stage) + + case 'Stage 1', + stage_explanation.value=sprintf('dir only: progressively grow NIC'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=0; + %%% direction task parameters + stimulus_mixing_dir.value=0; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=0; + durations_dir.value=1.3; + %%% other parameters + nose_in_center.value=0.05; + antibias_type.value='Side antibias'; + end + %%% algorithm: grow NIC + if(value(nose_in_center)>=1.3) + nose_in_center.value=1.3; + training_stage.value='Stage 2'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + elseif(value(nTrials_stage)>0 && value(nose_in_center)<1.3 && value(was_hit)==1) + nose_in_center.value=value(nose_in_center)+0.001; + end + + case 'Stage 2', + stage_explanation.value=sprintf('dir only: wait for good endpoints'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=0; + %%% direction task parameters + stimulus_mixing_dir.value=0; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=0; + durations_dir.value=1.3; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Side antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.75 ... + && value(left_correct)>0.7 && value(right_correct)>0.7 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 3'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + case 'Stage 3', + stage_explanation.value=sprintf('dir+freq: add easy frequency trials'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.8; + %%% direction task parameters + stimulus_mixing_dir.value=0; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=0; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=0; + gamma_dir_values_freq.value=0; + gamma_freq_values_freq.value=4; + durations_freq.value=10; + %%% frequency task help parameters + helper_lights_freq.value=1; + error_forgiveness_freq.value=1; + wait_delay_freq.value=0.2; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Side antibias'; + end + %%% algorithm: turn lights off, increase wait delay + if(value(nTrials_stage)>30) + %%% after 30 trials turn off the helper lights + helper_lights_freq.value=0; + %%% slowly increase the wait delay + if(value(wait_delay_freq)<3) + if(strcmp(value(ThisTask),'Frequency') && value(result)==5) + wait_delay_freq.value=value(wait_delay_freq)+0.1; + end + else + wait_delay_freq.value=3; + error_forgiveness_freq.value=0; + durations_freq.value=1.3; + training_stage.value='Stage 4'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + end + + case 'Stage 4', + stage_explanation.value=sprintf('dir+freq: wait for good endpoints'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.8; + %%% direction task parameters + stimulus_mixing_dir.value=0; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=0; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=0; + gamma_dir_values_freq.value=0; + gamma_freq_values_freq.value=4; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Side antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.7 ... + && value(freq_correct)>0.7 ... + && value(left_correct)>0.7 && value(right_correct)>0.7 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 5'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + case 'Stage 5', + stage_explanation.value=sprintf('dir+freq: progressively mix stimuli'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.8; + %%% direction task parameters + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=0; + durations_dir.value=1.3; + %%% frequency task parameters + gamma_dir_values_freq.value=0; + gamma_freq_values_freq.value=4; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Side antibias'; + end + %%% algorithm: at the end of each block increase mixing + if(value(stimulus_mixing_dir)<0.99 || value(stimulus_mixing_freq)<0.99) + if(value(was_block_switch)==1) + if(strcmp(value(ThisTask),'Direction')) + stimulus_mixing_dir.value=value(stimulus_mixing_dir)+0.1; + if(value(stimulus_mixing_dir)>1) + stimulus_mixing_dir.value=1; + end + else + stimulus_mixing_freq.value=value(stimulus_mixing_freq)+0.1; + if(value(stimulus_mixing_freq)>1) + stimulus_mixing_freq.value=1; + end + end + end + else + training_stage.value='Stage 6'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + case 'Stage 6', + stage_explanation.value=sprintf('mixed stimuli: wait for endpoints'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.8; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=0; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=0; + gamma_freq_values_freq.value=4; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Side antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.7 ... + && value(freq_correct)>0.7 ... + && value(left_correct)>0.7 && value(right_correct)>0.7 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 7'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + case 'Stage 7', + stage_explanation.value=sprintf('incongruent 1 (4; 1,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=[1 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 4]; + gamma_freq_values_freq.value=4; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.63 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 8a'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + case 'Stage 8a', + stage_explanation.value=sprintf('incongruent 2a (4; 1,1.5,3.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=[1 1.5 3.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 1.5 3.5 4]; + gamma_freq_values_freq.value=4; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.63 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 8b'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + case 'Stage 8b', + stage_explanation.value=sprintf('incongruent 2b (4; 1,2,3,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=[1 2 3 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2 3 4]; + gamma_freq_values_freq.value=4; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.63 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 8'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + + + + case 'Stage 8', + stage_explanation.value=sprintf('incongruent 2 (4; 1,2.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=4; + gamma_freq_values_dir.value=[1 2.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2.5 4]; + gamma_freq_values_freq.value=4; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.63 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 9a'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + case 'Stage 9a', + stage_explanation.value=sprintf('harder 1a (3.5,4; 1,2.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=[3.5 4]; + gamma_freq_values_dir.value=[1 2.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2.5 4]; + gamma_freq_values_freq.value=[3.5 4]; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.63 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 9b'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + case 'Stage 9b', + stage_explanation.value=sprintf('harder 1b (3,4; 1,2.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=[3 4]; + gamma_freq_values_dir.value=[1 2.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2.5 4]; + gamma_freq_values_freq.value=[3 4]; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.6 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 9'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + + + case 'Stage 9', + stage_explanation.value=sprintf('harder 1 (2.5,4; 1,2.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=[2.5 4]; + gamma_freq_values_dir.value=[1 2.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2.5 4]; + gamma_freq_values_freq.value=[2.5 4]; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.6 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 10a'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + + case 'Stage 10a', + stage_explanation.value=sprintf('harder 2a (2,2.5,4; 1,2.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=[2 2.5 4]; + gamma_freq_values_dir.value=[1 2.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2.5 4]; + gamma_freq_values_freq.value=[2 2.5 4]; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.6 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 10b'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + case 'Stage 10b', + stage_explanation.value=sprintf('harder 2b (1.5,2.5,4; 1,2.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=[1.5 2.5 4]; + gamma_freq_values_dir.value=[1 2.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2.5 4]; + gamma_freq_values_freq.value=[1.5 2.5 4]; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + %%% algorithm: wait for good endpoints + if(n_done_trials>150 && value(nValid)>150 && value(dir_correct)>0.68 ... + && value(freq_correct)>0.68 ... + && value(left_correct)>0.68 && value(right_correct)>0.68 ... + && value(correct_coherent)>0.68 && value(correct_incoherent)>0.6 ... + && value(nDays_stage)>2 && value(nTrials_stage)>150) + training_stage.value='Stage 10'; + nTrials_stage.value= 0; + nDays_stage.value= 1; + end + + + + + + case 'Stage 10', + stage_explanation.value=sprintf('harder 2 (1,2.5,4; 1,2.5,4)'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + ThisTask.value='Direction'; + randomize_first_task.value=0; + task_switch_auto.value=1; + task_switch_min_perf.value=0.7; + %%% direction task parameters + stimulus_mixing_dir.value=1; + gamma_dir_values_dir.value=[1 2.5 4]; + gamma_freq_values_dir.value=[1 2.5 4]; + durations_dir.value=1.3; + %%% frequency task parameters + stimulus_mixing_freq.value=1; + gamma_dir_values_freq.value=[1 2.5 4]; + gamma_freq_values_freq.value=[1 2.5 4]; + durations_freq.value=1.3; + %%% frequency task help parameters + helper_lights_freq.value=0; + error_forgiveness_freq.value=0; + %%% other parameters + nose_in_center.value=1.3; + antibias_type.value='Quadrant antibias'; + end + + case 'Stage grow NIC', + stage_explanation.value=sprintf('progressively grow NIC'); + %%% updated only on the first trial + if(value(nTrials_stage)==0) + %%% task parameters + nose_in_center.value=0.8; + end + %%% algorithm: grow NIC + if(value(nose_in_center)>=1.3) + nose_in_center.value=1.3; + elseif(value(nTrials_stage)>0 && value(nose_in_center)<1.3 && value(was_hit)==1) + nose_in_center.value=value(nose_in_center)+0.05; + end + + end + + + case 'end_session' + + + %%%%%%%%% HERE YOU MIGHT WANT TO IMPLEMENT CHECKS AND SWITCH STAGE + %%%%%%%%% IF NECESSARY!!! + +% +% if(value(stage_switch_auto)==1) +% +% feval(mfilename, obj, 'update_stage'); +% +% end + + + nDays_stage.value = value(nDays_stage) + 1; + + + + + case 'get' + + val=varargin{1}; + + eval(['x=value(' val ');']); + + + + + + + +end + + diff --git a/Protocols/@TaskSwitch4/get_variables_script.m b/Protocols/@TaskSwitch4/get_variables_script.m new file mode 100644 index 00000000..87a86f09 --- /dev/null +++ b/Protocols/@TaskSwitch4/get_variables_script.m @@ -0,0 +1,11 @@ +x = TaskSwitch4; + +% GetSoloFunctionArgs(x) + +hits=StimulusSection(x,'get','hit_history'); +side=StimulusSection(x,'get','side_history'); +task=StimulusSection(x,'get','task_history'); + + +% temp=StimulusSection(x,'get','hit_history') + diff --git a/Protocols/@TaskSwitch4/make_pbup_mixed3.m b/Protocols/@TaskSwitch4/make_pbup_mixed3.m new file mode 100644 index 00000000..141e3263 --- /dev/null +++ b/Protocols/@TaskSwitch4/make_pbup_mixed3.m @@ -0,0 +1,206 @@ +% [snd data] = make_pbup_mixed3(R, gamma_dir, gamma_freq, srate, T, varargin) +% +% Makes Poisson bups +% bup events from the left and right speakers are independent Poisson +% events +% +% ======= +% inputs: +% +% R total rate (in clicks/sec) of bups from both left and right +% speakers (r_L + r_R). +% +% gamma_dir the natural log ratio of right and left rates: log(r_R/r_L) +% +% gamma_freq the natural log ratio of high and low probabilities: log(p_H/p_L) +% +% srate sample rate +% +% T total time (in sec) of Poisson bup trains to be generated +% +% ========= +% varargin: +% +% bup_width +% width of a bup in msec (Default 3) +% +% bup_ramp +% the duration in msec of the upwards and downwards volume ramps +% for individual bups. The bup volume ramps up following a cos^2 +% function over this duration and it ramps down in an inverse +% fashion. +% +% crosstalk_dir +% between 0 and 1, determines volume of left clicks that are +% heard in the right channel, and vice versa. +% +% crosstalk_freq +% between 0 and 1, determines volume of hi clicks that are +% added to low clicks, and vice versa. +% +% vol_hi +% volume multiplier for clicks at high frequency +% +% vol_low +% volume multiplier for clicks at low frequency +% +% ======== +% outputs: +% +% snd a vector representing the sound generated + +% data a struct containing the actual bup times (in sec, centered in +% middle of every bup) in snd. +% data.left and data.right +% + +function [snd data] = make_pbup_mixed3(R, gamma_dir, gamma_freq, srate, T, varargin) + +pairs = {... + 'bup_width', 5; ... + 'bup_ramp', 2; ... + 'crosstalk_dir' 0; ... + 'crosstalk_freq' 0; ... + 'freq_vec', [6500 14200]; ... + 'vol_low', 1; ... + 'vol_hi', 1; ... + }; parseargs(varargin, pairs); + + +% rates of Poisson events on left and right +rrate = R/(exp(-gamma_dir)+1); +lrate = R - rrate; + + + +% rates of Poisson events on left and right +hirate = R/(exp(-gamma_freq)+1); +lorate = R - hirate; + + + +frac_hi=hirate./(hirate+lorate); +frac_lo=1-frac_hi; + + +rhirate=rrate*frac_hi; +rlorate=rrate*frac_lo; + + +lhirate=lrate*frac_hi; +llorate=lrate*frac_lo; + + + + +%t = linspace(0, T, srate*T); +lT = srate*T; %the length of what previously was the t vector + +% times of the bups are Poisson events +tp_rhi = find(rand(1,lT) < rhirate/srate); +tp_rlo = find(rand(1,lT) < rlorate/srate); + +tp_lhi = find(rand(1,lT) < lhirate/srate); +tp_llo = find(rand(1,lT) < llorate/srate); + + +data.right_hi = tp_rhi/srate; +data.right_lo = tp_rlo/srate; +data.left_hi = tp_lhi/srate; +data.left_lo = tp_llo/srate; + + + + + +buph = singlebup_old(srate, 0, 'ntones', 1, 'width', bup_width, 'basefreq', max(freq_vec), 'ramp', bup_ramp); +bupl = singlebup_old(srate, 0, 'ntones', 1, 'width', bup_width, 'basefreq', min(freq_vec), 'ramp', bup_ramp); + + +if(length(buph)/2==round(length(buph)/2)) + buph=[buph 0]; +end +if(length(bupl)/2==round(length(bupl)/2)) + bupl=[bupl 0]; +end + +buph=buph*vol_hi; +bupl=bupl*vol_low; + + + + + + +if crosstalk_freq > 0, % implement crosstalk_freq + + temp_buph = buph + crosstalk_freq*bupl; + temp_bupl = bupl + crosstalk_freq*buph; + + buph=temp_buph/(1+crosstalk_freq); + bupl=temp_bupl/(1+crosstalk_freq); + +end; + + + + + + + + +w = floor(length(buph)/2); + +snd = zeros(2, lT); + + +for i = 1:length(tp_rhi), % place hi-freq right bups + bup=buph; + if tp_rhi(i) > w && tp_rhi(i) < lT-w, + snd(2,tp_rhi(i)-w:tp_rhi(i)+w) = snd(2,tp_rhi(i)-w:tp_rhi(i)+w)+bup; + end; +end; + +for i = 1:length(tp_rlo), % place lo-freq right bups + bup=bupl; + if tp_rlo(i) > w && tp_rlo(i) < lT-w, + snd(2,tp_rlo(i)-w:tp_rlo(i)+w) = snd(2,tp_rlo(i)-w:tp_rlo(i)+w)+bup; + end; +end; + +for i = 1:length(tp_lhi), % place hi-freq left bups + bup=buph; + if tp_lhi(i) > w && tp_lhi(i) < lT-w, + snd(1,tp_lhi(i)-w:tp_lhi(i)+w) = snd(1,tp_lhi(i)-w:tp_lhi(i)+w)+bup; + end; +end; + +for i = 1:length(tp_llo), % place lo-freq left bups + bup=bupl; + if tp_llo(i) > w && tp_llo(i) < lT-w, + snd(1,tp_llo(i)-w:tp_llo(i)+w) = snd(1,tp_llo(i)-w:tp_llo(i)+w)+bup; + end; +end; + + + + +if crosstalk_dir > 0, % implement crosstalk_dir + temp_snd(1,:) = snd(1,:) + crosstalk_dir*snd(2,:); + temp_snd(2,:) = snd(2,:) + crosstalk_dir*snd(1,:); + + % normalize the sound so that the volume (summed across both + % speakers) is the same as the original snd before crosstalk + ftemp_snd = fft(temp_snd,2); + fsnd = fft(snd,2); + Ptemp_snd = ftemp_snd .* conj(ftemp_snd); + Psnd = fsnd .* conj(fsnd); + vol_scaling = sqrt(sum(Psnd(:))/sum(Ptemp_snd(:))); + + snd = real(ifft(ftemp_snd * vol_scaling)); +end; + +snd(snd>1) = 1; +snd(snd<-1) = -1; + + diff --git a/Protocols/@TaskSwitch4/parseargs.m b/Protocols/@TaskSwitch4/parseargs.m new file mode 100644 index 00000000..84bf42d9 --- /dev/null +++ b/Protocols/@TaskSwitch4/parseargs.m @@ -0,0 +1,187 @@ +%parseargs [opts] = parseargs(arguments, pairs, singles, ignore_unknowns) +% +% Variable argument parsing-- supersedes parseargs_example. This +% function is meant to be used in the context of other functions +% which have variable arguments. Typically, the function using +% variable argument parsing would be written with the following +% header: +% +% function myfunction(args, ..., varargin) +% +% and would define the variables "pairs" and "singles" (in a +% format described below), and would then include the line +% +% parseargs(varargin, pairs, singles); +% +% 'pairs' and 'singles' specify how the variable arguments should +% be parsed; their format is decribed below. It is best +% understood by looking at the example at the bottom of these help +% comments. +% +% varargin can be of two forms: +% 1) A cell array where odd entries are variable names and even entries are +% the corresponding values +% 2) A struct where the fieldnames are the variable names and the values of +% the fields are the values (for pairs) or the existence of the field +% triggers acts as a single. +% +% pairs can be of two forms: +% 1) an n x 2 cell array where the first column are the variable names and +% the 2nd column are the default values. +% 2) A struct where the fieldnames are the variable names and the values of +% the fields are the values. +% +% PARSEARGS DOES NOT RETURN ANY VALUES; INSTEAD, IT USES ASSIGNIN +% COMMANDS TO CHANGE OR SET VALUES OF VARIABLES IN THE CALLING +% FUNCTION'S SPACE. +% +% +% +% PARAMETERS: +% ----------- +% +% -arguments The varargin list, I.e. a row cell array. +% +% -pairs A cell array of all those arguments that are +% specified by argument-value pairs. First column +% of this cell array must indicate the variable +% names; the second column must indicate +% correponding default values. +% +% -singles A cell array of all those arguments that are +% specified by a single flag. The first column must +% indicate the flag; the second column must +% indicate the corresponding variable name that +% will be affected in the caller's workspace; the +% third column must indicate the value that that +% variable will take upon appearance of the flag; +% and the fourth column must indicate a default +% value for the variable. +% +% +% Example: +% -------- +% +% In "pairs", the first column defines both the variable name and the +% marker looked for in varargin, and the second column defines that +% variable's default value: +% +% pairs = {'thingy' 20 ; ... +% 'blob' 'that'}; +% +% In "singles", the first column is the flag to be looked for in varargin, +% the second column defines the variable name this flag affects, the third +% column defines the value the variable will take if the flag was found, and +% the last column defines the value the variable takes if the flag was NOT +% found in varargin. +% +% singles = {'no_plot' 'plot_fg' '0' '1'; ... +% {'plot' 'plot_fg' '1' '1'}; +% +% +% Now for the function call from the user function: +% +% parseargs({'blob', 'fuff!', 'no_plot'}, pairs, singles); +% +% This will set, in the caller space, thingy=20, blob='fuff!', and +% plot_fg=0. Since default values are in the second column of "pairs" +% and the fourth column of "singles", and in the call to +% parseargs 'thingy' was not specified, 'thingy' takes on its +% default value of 20. +% +% Note that the arguments to parseargs may be in any order-- the +% only ordering restriction is that whatever immediately follows +% pair names (e.g. 'blob') will be interpreted as the value to be +% assigned to them (e.g. 'blob' takes on the value 'fuff!'); +% +% If you never use singles, you can just call "parseargs(varargin, pairs)" +% without the singles argument. +% + + +function [varargout] = parseargs(arguments, pairs, singles,ignore_unknowns) + +if nargin < 3, singles = {}; end; +if nargin < 4, ignore_unknowns=false; end; + +% This assigns all the default values for pairs. +if isstruct(pairs) + out=pairs; + fn=fieldnames(pairs); + for fx=1:numel(fn) + assignin('caller',fn{fx}, pairs.(fn{fx})); + end + pairs=fn; +else + for i=1:size(pairs,1), + assignin('caller', pairs{i,1}, pairs{i,2}); + end; +end +% This assigns all the default values for singles. +for i=1:size(singles,1), + assignin('caller', singles{i,2}, singles{i,4}); +end; +if isempty(singles), singles = {'', '', [], []}; nosingles=true; else nosingles=false; end; +if isempty(pairs), pairs = {'', []}; nopairs=true; else nopairs=false; end; + + +% Now we assign the value to those passed by arguments. +if numel(arguments)==1 && isstruct(arguments{1}) + arguments=arguments{1}; + fn=fieldnames(arguments); + for arg=1:numel(fn) + switch fn{arg} + case pairs(:,1) + assignin('caller',fn{arg}, arguments.(fn{arg})); + out.(fn{arg})=arguments.(fn{arg}); + case singles(:,1) + u = find(strcmp(fn{arg}, singles(:,1))); + out.(fn{arg})=singles(:,1); + assignin('caller', singles{u,2}, singles{u,3}); + otherwise + if ~ignore_unknowns + fn{arg} + + mname = evalin('caller', 'mfilename'); + error([mname ' : Didn''t understand above parameter']); + end + end + end + +else + arg = 1; while arg <= length(arguments), + + switch arguments{arg}, + + case pairs(:,1), + if arg+1 <= length(arguments) + assignin('caller', arguments{arg}, arguments{arg+1}); + arg = arg+1; + end; + + case singles(:,1), + u = find(strcmp(arguments{arg}, singles(:,1))); + assignin('caller', singles{u,2}, singles{u,3}); + + otherwise + if ~ignore_unknowns + arguments{arg} + mname = evalin('caller', 'mfilename'); + error([mname ' : Didn''t understand above parameter']); + else + if nosingles + arg=arg+1; + elseif nopairs + % don't increment args. + else + error('Cannot use ignore_unknown and a mix of singles and pairs') + end + + end + end; + arg = arg+1; end; +end +if nargout>0 + varargout{1}=out; +end +return; diff --git a/Protocols/@TaskSwitch4/singlebup_old.m b/Protocols/@TaskSwitch4/singlebup_old.m new file mode 100644 index 00000000..049aeab9 --- /dev/null +++ b/Protocols/@TaskSwitch4/singlebup_old.m @@ -0,0 +1,59 @@ +% [snd] = singlebup_old(srate, att, { 'width', 5}, {'ramp', 2}, {'basefreq', 2000}, ... +% {'ntones' 5}, {'PPfilter_fname', ''}); ... + + +function [snd] = singlebup_old(srate, att, varargin) + + pairs = { ... + 'width', 5 ; ... + 'ramp', 2 ; ... + 'basefreq' 2000 ; ... + 'ntones' 5 ; ... + 'PPfilter_fname' '' ; ... + }; parseargs(varargin, pairs); + + + width = width/1000; + ramp = ramp/1000; + + if isempty(PPfilter_fname) + FilterPath=['Protocols' filesep 'PPfilter.mat']; + else + FilterPath = PPfilter_fname; + end; + PP = load(FilterPath); + PP=PP.PP; + + + t = 0:(1/srate):width; + + snd = zeros(size(t)); + for i=1:ntones, + f = basefreq*(2.^(i-1)); + attenuation = att - ppval(PP, log10(f)); + snd = snd + (10.^(-attenuation./20)) .* sin(2*pi*f*t); + end; + + if max(abs(snd)) >= 1, snd = snd/(1.01*max(abs(snd))); end; + + rampedge=MakeEdge(srate, ramp); ramplen = length(rampedge); + snd(1:ramplen) = snd(1:ramplen) .* fliplr(rampedge); + snd(end-ramplen+1:end) = snd(end-ramplen+1:end) .* rampedge; + + return; + + +% ------------------------------------------------------------- +% +% +% +% ------------------------------------------------------------- + + + +function [envelope] = MakeEdge(srate, coslen) + + t = (0:(1/srate):coslen)*pi/(2*coslen); + envelope = (cos(t)).^2; + return; + \ No newline at end of file diff --git a/Protocols/@TaskSwitch4/state_colors.m b/Protocols/@TaskSwitch4/state_colors.m new file mode 100644 index 00000000..4b49bc19 --- /dev/null +++ b/Protocols/@TaskSwitch4/state_colors.m @@ -0,0 +1,26 @@ +function SC = state_colors(obj) %#ok + + +% Colors that the various states take when plotting +SC = struct( ... + 'wait_for_cpoke', [210 105 205]/255, ... % orchid + 'wait_for_cpoke_dir', (255/210)*[210 105 205]/255, ... % orchid + 'wait_for_cpoke_freq', (105/255)*[210 105 205]/255, ... % orchid + 'cpoke', [0.52 0.76 1], ... + 'cpoke_in', [0.52 0.76 1], ... + 'cpoke_out', [0.52 0.76 1], ... + 'wait_for_cout', [0.52 0.6 0.8], ... + 'nic_error_state', [1 0.45 0.45], ... + 'wait_for_spoke', [132 111 255]/255, ... % slate blue + 'hit_state', [50 255 50]/255, ... % green + 'hit_state2', [50 255 50]/255, ... % green + 'error_state', [255 0 0]/255, ... % red + 'wait_state', [255 0 0]/255, ... % red + 'timeout_state', [255 236 139]/255, ... % light goldenrod + 'state_0', [1 1 1 ], ... + 'iti', [0.5 0.5 0.5], ... + 'check_next_trial_ready', [0.7 0.7 0.7]); + + + + diff --git a/Protocols/sendsummary_error_log.txt b/Protocols/sendsummary_error_log.txt new file mode 100644 index 00000000..ed2506da --- /dev/null +++ b/Protocols/sendsummary_error_log.txt @@ -0,0 +1,667 @@ +First time connecting with bdata server since matlab start. +Matlab<->MySQL connector v1.36 +Current database is "akrami_db" +connected +54 left_pokes = 0; +Index: 4, Size: 4 +java.lang.IndexOutOfBoundsException: Index: 4, Size: 4 + at java.util.ArrayList.rangeCheck(ArrayList.java:657) + at java.util.ArrayList.get(ArrayList.java:433) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Component.show(Component.java:1639) + at java.awt.Component.show(Component.java:1671) + at java.awt.Component.setVisible(Component.java:1623) + at javax.swing.JComponent.setVisible(JComponent.java:2644) + at javax.swing.JTabbedPane.fireStateChanged(JTabbedPane.java:394) + at javax.swing.JTabbedPane$ModelListener.stateChanged(JTabbedPane.java:270) + at javax.swing.DefaultSingleSelectionModel.fireStateChanged(DefaultSingleSelectionModel.java:132) + at javax.swing.DefaultSingleSelectionModel.setSelectedIndex(DefaultSingleSelectionModel.java:67) + at javax.swing.JTabbedPane.setSelectedIndexImpl(JTabbedPane.java:616) + at javax.swing.JTabbedPane.setSelectedIndex(JTabbedPane.java:591) + at com.mathworks.widgets.desk.DTTabbedPane.setSelectedIndex(DTTabbedPane.java:415) + at com.mathworks.widgets.desk.DTTabbedPane.toFront(DTTabbedPane.java:362) + at com.mathworks.widgets.desk.DTNestingContainer.toFront(DTNestingContainer.java:362) + at com.mathworks.widgets.desk.DTMultipleClientFrame.moveToFront(DTMultipleClientFrame.java:718) + at com.mathworks.widgets.desk.Desktop.toFront(Desktop.java:1354) + at sun.reflect.GeneratedMethodAccessor203.invoke(Unknown Source) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at com.mathworks.widgets.desk.Desktop$DeferredFacadeProxy.invoke(Desktop.java:8564) + at com.sun.proxy.$Proxy6.toFront(Unknown Source) + at com.mathworks.widgets.desk.Desktop.toFront(Desktop.java:1247) + at com.mathworks.mde.editor.DebugFileOpeningService.doOpenEditorForDebug(DebugFileOpeningService.java:224) + at com.mathworks.mde.editor.DebugFileOpeningService.access$200(DebugFileOpeningService.java:42) + at com.mathworks.mde.editor.DebugFileOpeningService$3.run(DebugFileOpeningService.java:170) + at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311) + at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758) + at java.awt.EventQueue.access$500(EventQueue.java:97) + at java.awt.EventQueue$3.run(EventQueue.java:709) + at java.awt.EventQueue$3.run(EventQueue.java:703) + at java.security.AccessController.doPrivileged(Native Method) + at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74) + at java.awt.EventQueue.dispatchEvent(EventQueue.java:728) + at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205) + at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) + at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) + at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) + at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) + at java.awt.EventDispatchThread.run(EventDispatchThread.java:82) +Exception in thread "AWT-EventQueue-0": java.lang.IndexOutOfBoundsException: Index: 4, Size: 4 + at java.util.ArrayList.rangeCheck(ArrayList.java:657) + at java.util.ArrayList.get(ArrayList.java:433) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Container.createHierarchyEvents(Container.java:1444) + at java.awt.Component.show(Component.java:1639) + at java.awt.Component.show(Component.java:1671) + at java.awt.Component.setVisible(Component.java:1623) + at javax.swing.JComponent.setVisible(JComponent.java:2644) + at javax.swing.JTabbedPane.fireStateChanged(JTabbedPane.java:394) + at javax.swing.JTabbedPane$ModelListener.stateChanged(JTabbedPane.java:270) + at javax.swing.DefaultSingleSelectionModel.fireStateChanged(DefaultSingleSelectionModel.java:132) + at javax.swing.DefaultSingleSelectionModel.setSelectedIndex(DefaultSingleSelectionModel.java:67) + at javax.swing.JTabbedPane.setSelectedIndexImpl(JTabbedPane.java:616) + at javax.swing.JTabbedPane.setSelectedIndex(JTabbedPane.java:591) + at com.mathworks.widgets.desk.DTTabbedPane.setSelectedIndex(DTTabbedPane.java:415) + at com.mathworks.widgets.desk.DTTabbedPane.toFront(DTTabbedPane.java:362) + at com.mathworks.widgets.desk.DTNestingContainer.toFront(DTNestingContainer.java:362) + at com.mathworks.widgets.desk.DTMultipleClientFrame.moveToFront(DTMultipleClientFrame.java:718) + at com.mathworks.widgets.desk.Desktop.toFront(Desktop.java:1354) + at sun.reflect.GeneratedMethodAccessor203.invoke(Unknown Source) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at com.mathworks.widgets.desk.Desktop$DeferredFacadeProxy.invoke(Desktop.java:8564) + at com.sun.proxy.$Proxy6.toFront(Unknown Source) + at com.mathworks.widgets.desk.Desktop.toFront(Desktop.java:1247) + at com.mathworks.mde.editor.DebugFileOpeningService.doOpenEditorForDebug(DebugFileOpeningService.java:224) + at com.mathworks.mde.editor.DebugFileOpeningService.access$200(DebugFileOpeningService.java:42) + at com.mathworks.mde.editor.DebugFileOpeningService$3.run(DebugFileOpeningService.java:170) + at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311) + at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758) + at java.awt.EventQueue.access$500(EventQueue.java:97) + at java.awt.EventQueue$3.run(EventQueue.java:709) + at java.awt.EventQueue$3.run(EventQueue.java:703) + at java.security.AccessController.doPrivileged(Native Method) + at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74) + at java.awt.EventQueue.dispatchEvent(EventQueue.java:728) + at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205) + at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) + at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) + at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) + at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) + at java.awt.EventDispatchThread.run(EventDispatchThread.java:82) +if system_dependent('IsDebugMode')==1, dbquit; end + +MATLAB:ErrorRecovery:ItemNoLongerOnPath +Error using load +..\PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat is not found in the current folder or on the MATLAB path, but exists in: + C:\ratter\ExperPort + C:\ratter\Protocols + C:\ratter\Bpod Protocols + C:\ratter\SoloData + +Change the MATLAB current folder or add its folder to the MATLAB path. +On line 17 of C:\ratter\ExperPort\MySQLUtility\bdata.m +On line 7 of C:\ratter\ExperPort\MySQLUtility\get_network_info.m +On line 18 of C:\ratter\ExperPort\Plugins\@sqlsummary\CentrePoketrainingsummary.m +On line 317 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 96 of C:\ratter\ExperPort\Modules\@dispatcher\ProtocolsSection.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 165 of C:\ratter\ExperPort\Modules\@dispatcher\ProtocolsSection.m +On line 210 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +Error calculating session length +Index exceeds the number of array elements. Index must not exceed 0. + 7×1 struct array with fields: + + file + name + line + +Failed to send summary to sql +Output argument "y" (and possibly others) not assigned a value in the execution with "sqlsummary.CentrePoketrainingsummary>sess_length" function. + 6×1 struct array with fields: + + file + name + line + +Error occurred during sendsummary execution: +Output argument "y" (and possibly others) not assigned a value in the execution with "sqlsummary.CentrePoketrainingsummary>sess_length" function. + + +******* + + WARNING: Unable to complete 'close' action on protocol + Last error was: "Error using fprintf +Function is not defined for 'struct' inputs." + + + +******* + +Warning: B-data NOT enabled for liquid calibration. No calibration values received. + +MATLAB:cd:NonExistentFolder +Error using cd +Unable to change current folder to 'C:\ratter\Protocols\Sounds' (Name is nonexistent or not a folder). +On line 13 of C:\ratter\ExperPort\Plugins\@soundui\private\fillSoundsMenu.m +On line 173 of C:\ratter\ExperPort\Plugins\@soundui\SoundInterface.m +On line 41 of C:\ratter\Protocols\@ArpitCentrePokeTraining\SoundSection.m +On line 179 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 152 of C:\ratter\ExperPort\Modules\@dispatcher\ProtocolsSection.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m +Error calculating session length +Index exceeds the number of array elements. Index must not exceed 0. + 8×1 struct array with fields: + + file + name + line + +Failed to send summary to sql +Output argument "y" (and possibly others) not assigned a value in the execution with "sqlsummary.CentrePoketrainingsummary>sess_length" function. + 7×1 struct array with fields: + + file + name + line + +Error occurred during sendsummary execution: +Output argument "y" (and possibly others) not assigned a value in the execution with "sqlsummary.CentrePoketrainingsummary>sess_length" function. + + +******* + + WARNING: Unable to complete 'close' action on protocol + Last error was: "Error using fprintf +Function is not defined for 'struct' inputs." + + + +******* + + +ans = + + dispatcher object: 1-by-1 + +clc +Warning: B-data NOT enabled for liquid calibration. No calibration values received. +64 sessid = getSessID(obj); +if system_dependent('IsDebugMode')==1, dbstep; end +if system_dependent('IsDebugMode')==1, dbstep; end +if system_dependent('IsDebugMode')==1, dbstep; end +if system_dependent('IsDebugMode')==1, dbstep; end + +MATLAB:cd:NonExistentFolder +Error using cd +Unable to change current folder to 'C:\ratter\Protocols\Sounds' (Name is nonexistent or not a folder). +On line 13 of C:\ratter\ExperPort\Plugins\@soundui\private\fillSoundsMenu.m +On line 173 of C:\ratter\ExperPort\Plugins\@soundui\SoundInterface.m +On line 41 of C:\ratter\Protocols\@ArpitCentrePokeTraining\SoundSection.m +On line 179 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 152 of C:\ratter\ExperPort\Modules\@dispatcher\ProtocolsSection.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m +Error calculating session length +Index exceeds the number of array elements. Index must not exceed 0. + 8×1 struct array with fields: + + file + name + line + +if system_dependent('IsDebugMode')==1, dbquit; end +64 sessid = getSessID(obj); +if system_dependent('IsDebugMode')==1, dbcont; end +Failed to send summary to sql +Dimensions of arrays being concatenated are not consistent. + 6×1 struct array with fields: + + file + name + line + +Error occurred during sendsummary execution: +Dimensions of arrays being concatenated are not consistent. + + +******* + + WARNING: Unable to complete 'close' action on protocol + Last error was: "Error using fprintf +Function is not defined for 'struct' inputs." + + + +******* + +Warning: B-data NOT enabled for liquid calibration. No calibration values received. +64 sessid = getSessID(obj); +if system_dependent('IsDebugMode')==1, dbcont; end +Failed to send summary to sql +Dimensions of arrays being concatenated are not consistent. + 7×1 struct array with fields: + + file + name + line + +Error occurred during sendsummary execution: +Dimensions of arrays being concatenated are not consistent. + + +******* + + WARNING: Unable to complete 'close' action on protocol + Last error was: "Error using fprintf +Function is not defined for 'struct' inputs." + + + +******* + + +ans = + + dispatcher object: 1-by-1 + + +ans = + + dispatcher object: 1-by-1 + + +ans = + + dispatcher object: 1-by-1 + +Warning: B-data NOT enabled for liquid calibration. No calibration values received. +76 colstr = [ +if system_dependent('IsDebugMode')==1, dbstep; end +if system_dependent('IsDebugMode')==1, dbstep; end +if system_dependent('IsDebugMode')==1, dbquit; end +clc +143 valstr = [ +if system_dependent('IsDebugMode')==1, dbcont; end +if system_dependent('IsDebugMode')==1, dbquit; end +!git status +On branch 2_step_soundcat_arpit +Your branch is up to date with 'origin/2_step_soundcat_arpit'. + +Changes not staged for commit: + (use "git add/rm ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + deleted: ../ExperPort/Plugins/@sqlsummary/CentrePoketrainingsummary.asv + +Untracked files: + (use "git add ..." to include in what will be committed) + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR01_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR01_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR02_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR02_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR03_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR03_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR04_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR04_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_experimenter_ratname_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_experimenter_ratname_250508a.mat + sendsummary_error_log.txt + ../sendsummary_error_log.txt + +no changes added to commit (use "git add" and/or "git commit -a") +flush +Bpod successfully disconnected. +!git status +On branch 2_step_soundcat_arpit +Your branch is up to date with 'origin/2_step_soundcat_arpit'. + +Changes not staged for commit: + (use "git add/rm ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + deleted: ../ExperPort/Plugins/@sqlsummary/CentrePoketrainingsummary.asv + +Untracked files: + (use "git add ..." to include in what will be committed) + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR01_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR01_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR02_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR02_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR03_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR03_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR04_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_arpit_AR04_250507a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_experimenter_ratname_250506a.mat + ../ExperPort/settings_@ArpitCentrePokeTraining_experimenter_ratname_250508a.mat + sendsummary_error_log.txt + ../sendsummary_error_log.txt + +no changes added to commit (use "git add" and/or "git commit -a") +!git pull +From https://github.com/LIMLabSWC/ratter + ec159f7..c79aaeb 2_step_soundcat_arpit -> origin/2_step_soundcat_arpit +Updating ec159f7..c79aaeb +Fast-forward + .../@sqlsummary/CentrePoketrainingsummary.asv | 315 --------------------- + .../@sqlsummary/CentrePoketrainingsummary.m | 311 +++++++++++--------- + .../ArpitCentrePokeTraining.m | 2 +- + Protocols/@ArpitCentrePokeTraining/ParamsSection.m | 2 +- + 4 files changed, 177 insertions(+), 453 deletions(-) + delete mode 100644 ExperPort/Plugins/@sqlsummary/CentrePoketrainingsummary.asv +Bpod('COM9'); newstartup; +Starting Bpod Console v1.8.0 +*********************************************************** +UPDATE NOTICE: Bpod Console v'1.8.1 is available to download! + View release notes here +To update run UpdateBpodSoftware() OR see instructionshere +*********************************************************** +Bpod State Machine r2 connected on port COM9 +runrats('init'); +test +First time connecting with bdata server since matlab start. +Matlab<->MySQL connector v1.36 +Current database is "akrami_db" +connected +[Warning: Using 'twister' to set RAND's internal state causes RAND, RANDI, and RANDN to use legacy +random number generators. This syntax is not recommended. See Replace Discouraged Syntaxes of rand +and randn to use RNG to replace the old syntax.] +[> In dispatcher (line 121) +In runrats (line 97)] +############################################################ +ALERT! No WavePlayer module detected! +Protocols will error out if analog scheduled waves are used. +############################################################ +WARNING: Failed to keep runrats on top +ERROR in bdata: mym(CON_ID,'call update_riginfo("{S}","{S}","{S}","{S}","{S}","{S}","{S}")',vs{1}, vs{2} , vs{3} , vs{4} , vs{5} , vs{6} , vs{7} ); + + +Error using mym +PROCEDURE akrami_db.update_riginfo does not exist +On line 158 of C:\ratter\ExperPort\MySQLUtility\bdata.m +On line 19 of C:\ratter\ExperPort\Modules\@runrats\private\update_riginfo.m +On line 286 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +Rig Info updated successfully. +Rig 31: IP=172.24.155.31, MAC=F875A4DEBBD8, Hostname=AKRAMI-RIG031 +Updating Current Session +ERROR in bdata: mym(CON_ID,'call update_rigtrials("{Si}","{Si}","{S}","{S}")',vs{1}, vs{2} , vs{3} , vs{4} ); + + +Error using mym +Incorrect number of arguments for PROCEDURE akrami_db.update_rigtrials; expected 0, got 4 +On line 158 of C:\ratter\ExperPort\MySQLUtility\bdata.m +On line 40 of C:\ratter\ExperPort\Plugins\@sqlsummary\send_n_done_trials.m +On line 1253 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1244 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1195 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:minrhs +Not enough input arguments. +On line 61 of C:\Program Files\MATLAB\R2024a\toolbox\matlab\codetools\export.m +On line 155 of C:\ratter\ExperPort\Plugins\@pokesplot\pokesplot_preferences_pane.m +On line 173 of C:\ratter\ExperPort\Plugins\@pokesplot\PokesPlotSection.m +On line 168 of C:\ratter\Protocols\@Rigtest_singletrial\Rigtest_singletrial.m +On line 152 of C:\ratter\ExperPort\Modules\@dispatcher\ProtocolsSection.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 184 of C:\ratter\ExperPort\Modules\@dispatcher\ProtocolsSection.m +On line 211 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1266 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1244 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1195 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m +Flagged stopping process as complete. +Warning: B-data NOT enabled for liquid calibration. No calibration values received. +[Warning: You created a new handle ("TrainingStageParamsSection_last_session_CP") since these +settings +were saved, no setting for it can be loaded.] +[> In loading_set_soloparamhandle_values (line 50) +In loading_core_load_sequence (line 35) +In load_solouiparamvalues (line 192) +In runrats (line 1386) +In runrats (line 1302) +In timer/timercb (line 71) +In matlab.graphics.interaction.uiaxes/EditInteraction/figureMotionFcn +In matlab.graphics.interaction.uiaxes.EditInteraction>@(~,e)this.figureMotionFcn(e)] +[Warning: You created a new handle ("TrainingStageParamsSection_max_CP") since these settings +were saved, no setting for it can be loaded.] +[> In loading_set_soloparamhandle_values (line 50) +In loading_core_load_sequence (line 35) +In load_solouiparamvalues (line 192) +In runrats (line 1386) +In runrats (line 1302) +In timer/timercb (line 71) +In matlab.graphics.interaction.uiaxes/EditInteraction/figureMotionFcn +In matlab.graphics.interaction.uiaxes.EditInteraction>@(~,e)this.figureMotionFcn(e)] +[Warning: You created a new handle ("TrainingStageParamsSection_CPfraction_inc") since these settings +were saved, no setting for it can be loaded.] +[> In loading_set_soloparamhandle_values (line 50) +In loading_core_load_sequence (line 35) +In load_solouiparamvalues (line 192) +In runrats (line 1386) +In runrats (line 1302) +In timer/timercb (line 71) +In matlab.graphics.interaction.uiaxes/EditInteraction/figureMotionFcn +In matlab.graphics.interaction.uiaxes.EditInteraction>@(~,e)this.figureMotionFcn(e)] +[Warning: You created a new handle ("TrainingStageParamsSection_recent_violation") since these +settings +were saved, no setting for it can be loaded.] +[> In loading_set_soloparamhandle_values (line 50) +In loading_core_load_sequence (line 35) +In load_solouiparamvalues (line 192) +In runrats (line 1386) +In runrats (line 1302) +In timer/timercb (line 71) +In matlab.graphics.interaction.uiaxes/EditInteraction/figureMotionFcn +In matlab.graphics.interaction.uiaxes.EditInteraction>@(~,e)this.figureMotionFcn(e)] +[Warning: You created a new handle ("TrainingStageParamsSection_recent_timeout") since these settings +were saved, no setting for it can be loaded.] +[> In loading_set_soloparamhandle_values (line 50) +In loading_core_load_sequence (line 35) +In load_solouiparamvalues (line 192) +In runrats (line 1386) +In runrats (line 1302) +In timer/timercb (line 71) +In matlab.graphics.interaction.uiaxes/EditInteraction/figureMotionFcn +In matlab.graphics.interaction.uiaxes.EditInteraction>@(~,e)this.figureMotionFcn(e)] +[Warning: You created a new handle ("TrainingStageParamsSection_stage_violation") since these +settings +were saved, no setting for it can be loaded.] +[> In loading_set_soloparamhandle_values (line 50) +In loading_core_load_sequence (line 35) +In load_solouiparamvalues (line 192) +In runrats (line 1386) +In runrats (line 1302) +In timer/timercb (line 71) +In matlab.graphics.interaction.uiaxes/EditInteraction/figureMotionFcn +In matlab.graphics.interaction.uiaxes.EditInteraction>@(~,e)this.figureMotionFcn(e)] + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m + +MATLAB:UndefinedFunction +Undefined function 'callback' for input arguments of type 'struct'. +On line 80 of C:\ratter\SoloData\Settings\experimenter\ratname\pipeline_ArpitCentrePokeTraining_experimenter_ratname_250508a.m +On line 2034 of C:\ratter\ExperPort\Plugins\@sessionmodel2\SessionDefinition.m +On line 264 of C:\ratter\Protocols\@ArpitCentrePokeTraining\ArpitCentrePokeTraining.m +On line 539 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 314 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 278 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 150 of C:\ratter\ExperPort\Modules\@dispatcher\RunningSection.m +On line 202 of C:\ratter\ExperPort\Modules\@dispatcher\dispatcher.m +On line 1501 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 1198 of C:\ratter\ExperPort\Modules\@runrats\runrats.m +On line 40 of C:\ratter\ExperPort\HandleParam\parse_and_call_sph_callback.m +On line 107 of C:\ratter\ExperPort\HandleParam\@SoloParamHandle\callback.m +On line 5 of C:\ratter\ExperPort\HandleParam\generic_callback.m +Flagged stopping process as complete. +reached sesm +############################################################ +ALERT! No WavePlayer module detected! +Protocols will error out if analog scheduled waves are used. +############################################################ + +ans = + + runrats object: 1-by-1 + +[Warning: WARNING in SoloUtility/add_and_commit.m: +The CVSROOT_STRING setting is empty. +Saved behavioral data and settings files will not be sent +to any data repository. + +IF YOU WISH TO EMPLOY THIS FEATURE, +please set CVSROOT_STRING in the custom settings file +Settings/Settings_Custom.conf. Instructions are available +there. If you are running the old RPbox-based system and +Settings_Custom.conf does not exist, copy +Settings_Template.conf to Settings_Custom.conf and then +try again.] +[> In add_and_commit (line 89) +In save_soloparamvalues (line 229) +In saveload/SavingSection (line 289) +In runrats (line 1558) +In timer/timercb (line 71) +In dispatcher/RunningSection (line 280) +In dispatcher/RunningSection (line 150) +In dispatcher (line 202) +In runrats (line 1501) +In runrats (line 1198) +In parse_and_call_sph_callback (line 40) +In SoloParamHandle/callback (line 107) +In generic_callback (line 5)] +ERROR in bdata: mym(CON_ID,'insert into parsed_events values ("{S}", "{M}")',vs{1}, vs{2} ); + + +Error using mym +Data too long for column 'events' at row 1 +On line 158 of C:\ratter\ExperPort\MySQLUtility\bdata.m +On line 177 of C:\ratter\ExperPort\Plugins\@sqlsummary\sendsummary.m +On line 228 of C:\ratter\Protocols\@ArpitSoundCatContinuous\ArpitSoundCatContinuous.m +On line 423 of C:\MATLABscripts_EphysExperiment\OpenEphys_Bcontrol_Neuroblueprint_GUI.m +On line 323 of C:\MATLABscripts_EphysExperiment\OpenEphys_Bcontrol_Neuroblueprint_GUI.m +On line 157 of C:\MATLABscripts_EphysExperiment\OpenEphys_Bcontrol_Neuroblueprint_GUI.m +No errors encountered during sendsummary execution. diff --git a/Protocols/settings_@ArpitCentrePokeTraining_experimenter_ratname_250506a.mat b/Protocols/settings_@ArpitCentrePokeTraining_experimenter_ratname_250506a.mat new file mode 100644 index 00000000..24c98418 Binary files /dev/null and b/Protocols/settings_@ArpitCentrePokeTraining_experimenter_ratname_250506a.mat differ diff --git a/Protocols/settings_@ArpitSoundCatContinuous_lida_LP12_250708c.mat b/Protocols/settings_@ArpitSoundCatContinuous_lida_LP12_250708c.mat new file mode 100644 index 00000000..756f390c Binary files /dev/null and b/Protocols/settings_@ArpitSoundCatContinuous_lida_LP12_250708c.mat differ diff --git a/README.md b/README.md index a825d6c0..a0345758 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,13 @@ ## BControl - Behavioral Experimentation System -BControl is a sophisticated system designed for conducting behavioral experiments with high precision and flexibility. It aims to facilitate rapid interaction with experimental subjects, provide high-time-resolution measurements, and offer ease of programming and modification. For more information, check out the [Brody lab wiki](https://brodylabwiki.princeton.edu/bcontrol/index.php?title=General_overview). +BControl is a sophisticated system designed for conducting behavioural experiments with high precision and flexibility. It aims to facilitate rapid interaction with experimental subjects, provide high-time-resolution measurements, and offer ease of programming and modification. For more information, check out the [Brody lab wiki](https://brodylabwiki.princeton.edu/bcontrol/index.php?title=General_overview). ## `ratter` Repository This repository stores the BControl code for our high-throughput behavior training facility. Certain directories and files within the `ratter` repository are intentionally excluded from version control: -- `/SoloData/`: Contains raw data and configuration files essential for running experiments. - -- `/.svn/`: Houses Subversion (SVN) related files, which are typically hidden files generated by SVN for tracking changes and managing versions. +- The `/SoloData/` directory contains raw data and configuration files essential for running experiments. It is version-controlled with SVN and stored on our internal server. -- `/ExperPort/Settings/Settings_Custom.conf`: rig specific configurations +- The `/ExperPort/Settings/Settings_Custom.conf` file contains rig-specific configurations. Instead, we provide `/ExperPort/Settings/_Settings_Custom.conf`, which is a template. After downloading, users should rename it to `Settings_Custom.conf` and add their rig-specific settings. -- `/PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat`: stores the hostnames, users and passwords - - -## Setting Up Connection with GitHub on Windows 7 Machines - -If you're encountering an issue where TLS 1.2 is not available on your Windows 7 machine, follow these steps to manually enable TLS 1.2 via the registry: - -1. Press `Win + R`, type `regedit`, and press Enter to open the Registry Editor. -2. Navigate to `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols`. -3. Create the following keys if they do not exist: `TLS 1.2\Client` and `TLS 1.2\Server`. -4. Under `TLS 1.2\Client`, create a DWORD value named `DisabledByDefault` and set its value to `0`. -5. Also under `TLS 1.2\Client`, create another DWORD value named `Enabled` and set its value to `1`. -6. Repeat the process for `TLS 1.2\Server` if needed. +- The `/PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat` file stores hostnames, users, and passwords. It is version-controlled with SVN and stored on our internal server. diff --git a/sparse_init.log b/sparse_init.log new file mode 100644 index 00000000..2e78c697 --- /dev/null +++ b/sparse_init.log @@ -0,0 +1,36 @@ +========================================= +🔧 Initializing sparse SVN checkout +📂 Target directory: /c/ratter +📡 Repository URL: http://172.24.155.220/svn/akramilab/ratter +📄 Log file: sparse_init.log +========================================= + +➡️ Checking out repository root (empty)... + U . +Checked out revision 44223. +✅ Fetching: PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat +Updating 'PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat': +A PASSWORD_CONFIG-DO_NOT_VERSIONCONTROL.mat +Updated to revision 44223. + +📁 Preparing SoloData structure... +Updating 'SoloData': +A SoloData +Updated to revision 44223. +Updating 'SoloData\Data': +A SoloData\Data +Updated to revision 44223. +Updating 'SoloData\Settings': +A SoloData\Settings +Updated to revision 44223. + +📁 Adding training_videos folder... +Updating 'training_videos': +A training_videos +Updated to revision 44223. + +✅ DONE: Sparse checkout initialized. +📌 You can now selectively fetch folders like: + svn update --set-depth=infinity SoloData/Data/arpit + svn update --set-depth=infinity training_videos/rig1 +