Skip to content

Laboratory of Neuropsychology

MatOFF History Scripts

Introduction

In addition to the standard scripting capabilities (protocol files and metafiles),MatOFF allows an advanced user to introduce MATLAB scripts into the search for event code sequences. These scripts use the History command. They process information about all trials (the entire history of behavior) when deciding whether or not to accept the current trial. The scripting mechanism is ideal for analyzing the time-course of events.We have used it to study learning. History scripting can also be used to add new features toMatOFF, like the ability to detect the onset of an eye movement, or to save behavioral information to a disk file.

Anatomy of a history script

MatOFF maintains a table (history table) for each unit in a file. The table has a list of "classes" and "class values" for each trial in the unit. This allows you to categorize trials in multiple ways. For example, each trial in an experiment could be placed into two classes:

Class 1: movement direction (four possible values: 1=left, 2=right, 3=up, 4=down)
Class 2: successful trial (two possible values: 0=failure, 1=success)

These classes are assigned during a sequence search using a MATLAB script. An appropriate script might look something like this:

% Some of the event codes in this data file are
% movement direction: 71=left, 72=right, 73=up, 74=down
% performance: 95=successful trial
%
class=1; % create a new class that reflects the movement direction.
% find the event code for direction in this trial
direction = allcodes(find( (allcodes >= 71) & (allcodes <= 74) ));

% Use the event code to assign a value to class 1: 1, 2, 3, or 4 for the 4 directions
if ~isempty(direction)
assign_class_value(current_trial,class,direction-70);
end
%
class=2; % create a class that indicates success or failure
if find(allcodes==95)
assign_class_value(current_trial,class,1); % 1 is success
else
assign_class_value(current_trial,class,0); % 0 is fail
end

The classes just created have not added any information beyond that already contained in the event codes for a single trial. With this addition, we use class 2 above to create a new class. This new class is for trials that are successful and have at least two prior trials also successful.

class=3 % create class that uses information from two previous trials
if (current_trial > 2) % cannot test first two trials of the unit
if find_class_value(current_trial,2) & find_class_value(current_trial-1,2) & find_class_value(current_trial-2,2)
assign_class_value(current_trial,class,1); % classify this successful trial based on past two successes
else
assign_class_value(current_trial,class,0); % zero otherwise
end
end

The new class can be used to select trials for rasters, histograms, statistics, etc. Set the special variable accept =1 before exiting the history to accept the trial. If accept=0, the trial will be rejected just like a mis-matched sequence.

if find_class_value(current_trial,3) % accept only trials in class 3
accept=1;
else
accept=0;
end

The ability to accept or reject search sequences based on classes makes this "history scripting" a powerful tool for studying time-varying phenomena.

Variables and support functions available to history scripts

History scripts are written in MATLAB code. The history script file must have a ".m" file extension. A history scriptmust notbe a MATLAB function.If you use the "function" keyword the script will not be able to interact withMatOFF. The default path for a history script is set by the environmental variableHISTORYPATH.

The script will have access to these variables:

ACCEPT / REJECT / ABORT

accept value set by History script to accept (1), reject (0) current trial, or abort scan (-1)

CURRENT TRIAL

allcodes array of every event code in this trial
center_event center event code. Returns zero if the centering is on a history class
center_event_number value ofCentercommand (position of center code in the search sequence)
codes array of event codes in this trial that matched the sequence
current_trial trial number of sequence being matched
last_trial number of trials in the unit
mark_event mark event code
mark_event_number value ofMarkcommand (position of mark code in the search sequence)

ENVIRONMENT

data_file_name name of the currently open data file
unit_name name of the currently open unit

<<strong>PRINT MESSAGES

DEBUG_M
WARNING_M constant to select a warning message type for issue_message()
ERROR_M constant to select an error message type for issue_message()

History scripts also have access to a series ofsupport functions:

CREATE CLASSES

assign_class_value(trial,class,value) create a class, add a class value, or change a class value

DELETE CLASSES

remove_class(trial,class) remove a trial from a class assignment
purge_class(class) remove all assignments for a class (completely remove class)

READ CLASSES

list_class(class) return a matrix of all trials assigned to a class and their class values
list_trial(trial) return a matrix of all classes and associated values for a single trial
find_class_value([trials],class) return the class value(s) for one or more trials in a given class. returns NaN for trials not in the class
zfind_class_value([trials],class) return the class value(s) for one or more trials in a given class. returns 0 for trials not in the class

GET DATA

list_analog(trial) return a list of all analog values (x and y) and their times
list_events(trial) return a list of all events and event times for a trial
list_history_data returns the history data array (numeric or character) supplied by theHistory datacommand
list_spikes(trial) returns a list of all spike times for a trial

MESSAGES

issue_message(type,message) issues a debug, warning, or error message string to the console
logfile_write(string) write a string to the log file

ENVIRONMENT

list_environment() get a copy of theenvironmental variables

In addition to the support functions, the script can include any standard MATLAB commands or functions.

Details of support functions

status = assign_class_value(trial,class,value)

Create a class, add a class value, or change a class value.

Inputs
trial Trial for history change
class Class to add or amend
class_value Value for the class
Outputs
status 1 = successful run, negative value is an error code

find_class_value([trials],class)

return the class value(s) for one or more trials in a given class. returns NaN for trials not in the class

Inputs
trials list of trials to look up
Outputs
values one-to-one match with trials list. NaNs are inserted for trials with no values

zfind_class_value([trials],class)

Same asfind_class_value()except that zeros are used for trials with no values.

status = purge_class(class)

Remove all assignments for a class (completely remove class).

Inputs
class Class to remove
Outputs
status 1 = successful run, negative value is an error code


status = remove_class(trial,class)

Remove a trial from a class assignment.

Inputs
trial Trial for history change
class Class to remove
Outputs
status 1 = successful run, negative value is an error code

xy_data=list_analog(trial)

Return a list of all analog values (x and y) and their times.

Inputs
trial Trial to look up
Outputs
xy_data row 1 x1 x2 x3 x4 ... xn x analog values
row 2 y1 y2 y3 y4 ... yn y analog values
row 3 t1 t2 t3 t4 ... tn time in milliseconds

list=list_class(class)

return a matrix of all trials assigned to a class and their class values

Inputs
class class to list
Outputs
list row 1 trial numbers
row 2 matching class values

environment = list_environment()

get a copy of the environment

Inputs
none
Outputs
environment. structure with the following classes:
datapath
defaultpath
displayunits
dumppath
editor
eogsamplerate
eventcodefile
formatpath
graphicsformat
layoutpath
makdat
makdump
metachar1
metachar2
metapath
plotpath
protocolpath
remapfile
statpath
maxinputtrials
dumplabel
maxtrialsfound
dumptext
dumpmode
historypath
analogstart
analogstop

list=list_events(trial)

return a matrix of all event codes and their time stamps for a specified trial

Inputs
trial trial to look up
Outputs
list row 1 event codes. Every code irrespective of the sequence or globalignore lists.
row 2 event times. Trial starts at 0.1, all values stored in milliseconds

row_array=list_history_data

serves as a data link between protocol scripts and history scripts.

Inputs
none
Outputs
row_array fetch the data left in memory by theHistory datacommand.

list=list_spikes(trial)

return a list of spike times for this trial. Time 0 is the start of the trial.

Inputs
trial trial to look up
Outputs
list spike times in milliseconds since the start of the trial


list=list_trial(trial)

All classes and class values associated with a trial.

Inputs
trial trial to look up
Outputs
list row 1 classes that this trial belongs to (if any)
row 2 class values associated with each class

issue_message(message_type,message_string)

Print a debug, warning, or error message.

Inputs
message_type 1=Debug, 2=Warning, 3=Error (use predefined values: DEBUG_M, WARNING_M, ERROR_M)
string The message to print.

success=logfile_write(logstring)

Write a string to the log file, if a log file is open.

Inputs
logstring The string to send to the log file. No new-line character is necessary.
Outputs
success Returns 0 if no file is open, returns 1 otherwise.

logfile_write

Using history scripts

These are the available history commands:

history on set history data array to [1] and process the user's history script; use the result to accept or reject trials from analysis
history off set history data array to [0] and skip all history processing
history clear remove all history data in memory
history rewrite <all> save history data to disk
history file <filename> identify the file containing the user's history script
history spot show display history spot times
history spot noshow do not display history spot times
history spot firstclass first class to use for history spots
history spot lastclass last class to use for history spots
history data <data> save data for use by history scripts
Related Commands
sort history <class> sort trials based on history class values
validatespikes on/off use history class values to make a time window for rasters and histograms
validatespikes start/end <class> set classes used to mark the start and end of valid spikes for each trial
validatespikes color <color> color used to mark spikes NOT INCULDED in the histogram
dump history show every trial number and class value for all classes
center center value can be a history class. must be class number greater than 99

History scripts are written in the MATLAB language and saved as a MATLAB file (".m" extension). They must not be a MATLAB function. The script file name is given toMatOFFusing theHistorycommand. A secondHistorycommand is used to enable the script.

history file myscript.m
history on

When the history is on, each match of a trial sequence calls the history script. The history script can assign a 0 or 1 to the variable "accept." If accept has the value 1, the matched trial is accepted. That is, the trial is treated as it normally would be if no history is enabled. If accept has the value 0, the trial is rejected as if one of the event codes of the trial did not match the search sequence. If the script makes no assignment,MatOFFassigns the value 1 and issues a warning.

Data (history data array) can be sentfrom a protocol file to a history script using theHistory datacommand. The history data array can be a simple numeric array with values separated by spaces:

history data 13 19 21 145 666 18

The history data array can also be sent as one long text string. The user may have to parse the text string.

history data 'unitA rewarded left 101'

If theDump spikescommand is not being used, a string can also be passed using theSetenv dumptextcommand. You can use thelist_environment() function to get a copy of the dumptext string:

env=list_environment();
dumptext=env.dumptext;

Either way a string is passed, this string can be parsed with MATLAB code like this:

[unit_name,remainder]=strtok(list_history_data);
[reward_string,remainder]=strtok(remainder);
[direction,remainder]=strtok(remainder);
stimulus_number=str2num(remainder);

Note thatHistory onsets the history data array to [1] andHistory offsets the history data array to [0].

Just like it is common to run different sequences on the same unit, it is common to run different history scripts on a unit. The user can begin with a very inclusive sequence (with just a few event codes common to all trials) along with a script that builds up a classification system. After that, more restrictive sequences can be used along with scripts that take advantage of the previously-created classes. The classifications (history) exists only in memory unless it is saved to disk using theHistory rewritecommand. The history is added to the other files associated with the currently open file. A history requires two files (extensions .history and .hindex). The next time the unit is opened with theUnitcommand its history will be loaded into memory. The history can be deleted from memory using theHistory clearcommand.Dump historyis used to view the current history in memory.

If multiple units have been recorded simultaneously during the experiment, these units will have a common behavioral history. When saving the history for the current unit,MatOFF> can search for other units that come from the same set of trials.History rewrite allprovides this service.

History classes can be used to place markers on plots using theHistory spotcommand The markers are the same as spot events (seeSpotcommand), however, the marker placements are determined by setting absolute times rather than event codes. The absolute times are stored as history class values. For example, to place a spot exactly 2.5 seconds into trial 3, save this time value into a class:

assign_class_value(3,500,2500); % only integers accepted, so time is stored in milliseconds

Class 500 is used for this class value, so the following commands are needed to tellMatOFFto use this value:

history spot show
history spot firstclass 500
history spot lastclass 501

Since the lastclass is 501, class 501 can also be used to store spot times. Thus, practically any number of spots can be placed in a single trial.

History classes can be used to sort data using theHistory sortcommand. The history script creates a class with class values for each trial.MatOFFwill then use these class values to sort trials for raster display.

History classes can be used to truncate XY analog traces. We have written history scripts to locate the start and end of a saccade, then store the start and stop times as class values. TheXY time classescommand tellsMatOFFto apply these class values when drawing the XY traces.

Debugging history scripts

A fewMatOFFcommands and features will be helpful for rapidly debugging history scripts. Here are some helpful tips.

1. If you modify a script with an editor while runningMatOFF,MatOFFand MATLAB may not notice the change. To make MATLAB aware of the change, issue theMatlabcommand followed by theCmdcommand. This brief shift out of and back intoMatOFFwill alert MATLAB to the change.MatOFFwill not apply this change until some event indicates the unit must be re-scanned. The somewhat obscureScancommand will force a new scan of the trial and update the history.

2.MatOFFhas an integrated system for reporting errors, warnings, and debugging information. The support functionissue_message()provides access to this messaging system. Use it to issue messages and use theErrorLevelandLogcommands to control which messages are issued and where they go. The MATOFF commandsprintf()might be helpful for providing a string toissue_message().

3. UseDump historyto see classes and class values after running a script.

4. Use the MATLAB editor to set breakpoints and single step a script.

5. You can open your own file from the script and leave debugging information there. It is probably best to open the file in append mode at the beginning of the script and close it at the end of the script.

6. Remember to useHistory clearto remove the results of a previous test. To reload a previously saved history, use the unit command. TheUnitcommand will re-read the history file even if the unit name has is not changed.

More examples

These examples can be found in the history_scripts subdirectory.

% history_all_events.m Create a text file called event_dump.txt
% Save the event code and event time for every trial
event_fid=fopen('event_dump.txt','wt'); % create a text file
for trial=1:last_trial
% go through every trial
events=list_events(trial);
% fetch a list of all events and event times for this trial
codes=events(1,:);
times=events(2,:);
fprintf(event_fid,'TRIAL %d\n',trial);
% add a header to the text file
for c=1:length(codes)
fprintf(event_fid,'%7d ',codes(c)); % write event codes in one long line
end
fprintf(event_fid,'\n'); % end the line
for c=1:length(times)
fprintf(event_fid,'%7.3f ',times(c)/1000); % write event times converted to seconds
end
fprintf(event_fid,'\n'); % end the line of event times
end
fclose(event_fid); % close text file that we created
accept=-1; % terminate scan

% history_sort_time.m
%
% Set up a class for History sort. Use the time difference between two events
% as the basis of the sort. Very similar to Sort time. After this file
% runs once (using SCAN), turn off history scripting with HISTORY OFF.
%
first_event=[86, 87, 88]; % whichever event matches first
last_event=[99]; % use first match here also
sort_class=499; % Class that will get sorted values

for trial=1:last_trial
events=list_events(trial);
codes=events(1,:);
times=events(2,:);
matchf=[]; % match to first events
matchl=[]; % match to last events
for f=1:length(first_event)
matchf=[ matchf find(codes==first_event(f)) ];
end
for l=1:length(last_event)
matchl=[ matchl find(codes==last_event(l)) ];
end
% measure time between first matches
if any(matchf) & any(matchl)
assign_class_value(trial,sort_class,abs(times(matchl(1))-times(matchf(1))));
end
end % for trial

accept=-1;

% history_spot_events.m
%
% The history spot command will place markers on each raster.
% The trial and time for each spot is stored as a class value.
% You will need to reserve N classes to store up to N spots
% within one trial. Reserve these classes using:
% history spot first <class number>
% history spot last <class number + (N-1)>
%
spot_list=[86,87,100,101]; % spot these codes with triangles
spot_first_class=500; % use Classes 500 to 509
spot_last_class= 509; % = up to 10 spots / trial

events=list_events(current_trial);
codes=events(1,:);
times=events(2,:);

class_number=spot_first_class;
for spot=1:length(spot_list)
index=find(codes==spot_list(spot));
for i=1:length(index)
if class_number <= spot_last_class
assign_class_value(current_trial,class_number,times(index(i)));
class_number=class_number+1;
end
end
end

% Accept every trial
accept=1;

% history_class_dump.m
% Selectively dump some of the history classes for all trials
% Create a text file (default name = history_dump.txt)
%
% To use:
%
% history data 2 13 21 31 <--- history classes of interest
% history file history_class_dump.m
% scan
%
% This will generate a file (history_dump.txt):
%
% Trial Classes: 2 13 21 31
% 1 1 0 1 X X indicates "no value assigned"
% 2 7244 2 2 3
% 3 7642 2 2 2
%
% Placeholder for missing values.
placeholder=' X';
% File name to create
filename='history_dump.txt';

class_list=list_history_data; % get list of classes to dump
fid=fopen(filename,'wt');
trials=1:last_trial;
table=trials; % first row
for i=1:length(class_list);
table=[table ; find_class_value(trials,class_list(i))]; % add next class values as a new row
end
% squeeze out columns without any class values
cc=~isnan(table(2:end,:)); % NaN ==> 0
sc=sum(cc,1); % sum columns
keep=find(sc > 0); % keep only trials that have non NaN data
smaller_table=table(:,keep)'; % and transpose

fprintf('Creating %s\n',filename);
fprintf(fid,'Trial Classes: ');
for i=1:length(class_list)
fprintf(fid,'%d ',class_list(i));
end
fprintf(fid,'\n');
for i=1:length(smaller_table(:,1)) % each row (trial)
for j=1:length(smaller_table(1,:)) % each column (class)
if isnan(smaller_table(i,j))
fprintf(fid,' %s ',placeholder);
else
fprintf(fid,' %4d ',smaller_table(i,j));
end
end
fprintf(fid,'\n');
end
fclose(fid);
accept=-1; % end scan

% history_validate.m
%
% Mark the range of valid spikes in each raster and histogram
% Using two event codes (start and stop). Then, set up a centering
% class exactly between the two.
%
valid_start_class=100; % start class for valid spikes
valid_end_class=101; % end class for valid spikes
centering_class=102; % center class, located half way point between start and end.
validate_start_event=60; % start time will be at this event on each trial
validate_end_event=29; % end time will be at this event on each trial
fprintf('Running: history_validate history script\n');
for T=1:last_trial
fprintf('Trial %d\n',T);
events=list_events(T); % get all events for the trial
codes=events(1,:); % array of event codes
times=events(2,:); % matching array of event times (miliseconds)
% find markers for valid spikes
index_of_start_event=find(codes==validate_start_event);
if isempty(index_of_start_event) % can't use this trial
fprintf(' starting event code not found. this trial will have no valid spikes\n');
break;
end
if length(index_of_start_event) > 1
fprintf(' start event code %d occurs %d times in trial\n', ...
start_code,length(index_of_start_event));
index_of_start_event=index_of_start_event(1); % take only the first occurance of event
end
start_time=times(index_of_start_event);
assign_class_value(T,valid_start_class,start_time);

index_of_end_event=find(codes==validate_end_event);
if isempty(index_of_end_event)
% no ending event code. Use the timestamp of the last spike
spike_times=list_spikes(T);
last_spike_time=spike_times(end);
assign_class_value(T,valid_end_class,last_spike_time);
else
if length(index_of_end_event) > 1
fprintf(' end event code %d occurs %d times in trial\n', ...
end_code,length(index_of_end_event));
index_of_end_event=index_of_end_event(1); % use just the first one
end
end_time=times(index_of_end_event);
if end_time < start_time
fprintf(' end event time occurs before start time. No spikes will be validated.\n');
% We could do something different here. Right now, just put this into the history class.
end
assign_class_value(T,valid_end_class,end_time);
end % if isempty(index_of_end_event)
half_way_time= (start_time+end_time) / 2.0;
assign_class_value(T,centering_class,half_way_time);
end % for T=1:last_trial

accept=-1; % done after first match
fprintf(' history_validate history script is finished after %d trials\n',last_trial);
fprintf(' next steps: HISTORY OFF and then SCAN');