├── README.md ├── Axion ├── AxionBioSystems_Matlab_guide.pdf ├── freadstring.m ├── parseGuid.m ├── BlockVectorDataType.m ├── StimulationEventData.m ├── Annotation.m ├── EntryRecordID.m ├── EventTag.m ├── Spike_v1.m ├── TagType.m ├── DateTime.m ├── TagEntry.m ├── load_AxIS_file.m ├── Entry.m ├── BlockVectorHeaderExtension.m ├── StimulationWaveform.m ├── StimulationChannels.m ├── CRC32.m ├── Waveform.m ├── Note.m ├── Tag.m ├── StimulationEvent.m ├── EntryRecord.m ├── WellInformation.m ├── PlateTypes.m ├── BlockVectorHeader.m ├── LegacySupport.m ├── ChannelMapping.m ├── calc_spike_rate.m ├── ChannelArray.m ├── LoadArgs.m └── BlockVectorLegacyLoader.m ├── util_funcs ├── filt_LFP.m ├── filt_spike.m ├── pwelch_allchan.m ├── sliding_win.m ├── filt_powerline.m ├── nice_figure.m ├── neighbor_phase.m ├── butterpass.m ├── CVn.m ├── autocorr.m ├── binarize_spikes.m ├── plot_filled.m ├── spike_detect_abs.m ├── bin_spikes.m ├── mPSD.m ├── collect_spikes.m ├── redblue.m ├── spike_raster.m ├── burst_detect.m ├── inst_cfpw.m ├── stft.m ├── plot_tight.m ├── spike_detect.m ├── kernel_findpeak.m ├── shadedErrorBar.m ├── firpass.m ├── eegfilt.m └── pop_eegfiltnew.m ├── scripts ├── ctrFC_pipeline.m ├── ctrNS_pipeline.m ├── nEEGfeats_mouse.m ├── ctr2D_pipeline.m ├── corticoid_eegfeatures.m ├── ctc_findpeaks.m ├── corticoid_script.m ├── corticoid_NS_2D.m~ ├── corticoid_NS_2D.m ├── WT_CDKL5.m └── corticoid_analysis.m └── functions ├── p1_convertpreprocess.m ├── compute_nEEGfeats.m ├── MEA_summary.m ├── compute_peakfeats.m ├── MEA_process.m └── MEA_convert.m /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Axion/AxionBioSystems_Matlab_guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voytekresearch/OscillatoryOrganoids/HEAD/Axion/AxionBioSystems_Matlab_guide.pdf -------------------------------------------------------------------------------- /util_funcs/filt_LFP.m: -------------------------------------------------------------------------------- 1 | function LFP = filt_LFP(data,fs) 2 | 3 | LFP_cut = 300; 4 | 5 | [b,a] = butter(10, 2*LFP_cut/fs, 'low'); 6 | LFP = filtfilt(b,a,data); 7 | 8 | end -------------------------------------------------------------------------------- /util_funcs/filt_spike.m: -------------------------------------------------------------------------------- 1 | function Sp = filt_spike(data,fs) 2 | 3 | spike_freq = [300, 2800]; 4 | 5 | [b,a] = butter(10, 2*spike_freq/fs, 'bandpass'); 6 | Sp = filtfilt(b,a,data); 7 | 8 | end -------------------------------------------------------------------------------- /util_funcs/pwelch_allchan.m: -------------------------------------------------------------------------------- 1 | function P = pwelch_allchan(data, fs, overlap) 2 | Nchan = size(data,2); 3 | P = zeros(fs/2+1, Nchan); 4 | for j=1:Nchan 5 | P(:,j) = pwelch(data(:,j), fs, overlap, fs); 6 | end 7 | end -------------------------------------------------------------------------------- /util_funcs/sliding_win.m: -------------------------------------------------------------------------------- 1 | function stacked = sliding_win(data, winLen, stepLen) 2 | 3 | %rotate 4 | if size(data,1)==1 5 | data = data'; 6 | end 7 | 8 | max_iter = ceil((size(data,1)-winLen)/stepLen); 9 | stacked = zeros(winLen, max_iter); 10 | for i=1:max_iter 11 | stacked(:,i) = data((1:winLen)+(i-1)*stepLen,:); 12 | end -------------------------------------------------------------------------------- /util_funcs/filt_powerline.m: -------------------------------------------------------------------------------- 1 | function data = filt_powerline(data, fs) 2 | 3 | %line_freq = [60, 180, 300, 454, 540, 1260, 1363]; 4 | line_freq = [58 62; 178 182; 298 302; 538 542; 1256 1262; 1358 1366; 1616 1622; 2263 2277]; 5 | for i=1:size(line_freq,1) 6 | stopband = [line_freq(i,1),line_freq(i,2)]; 7 | [b, a]=butter(3,2*stopband/fs, 'stop'); 8 | data = filtfilt(b,a,data); 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /util_funcs/nice_figure.m: -------------------------------------------------------------------------------- 1 | function nice_figure(f_handle, filename, dim) 2 | %nice_figure(f_handle, filename, dim) 3 | % plots a nice figure at filename (full file name) with the specified 4 | % dimensions (dim) in inches (I think) 5 | 6 | set(f_handle,'Units','Inches'); 7 | pos = get(f_handle,'Position'); 8 | set(f_handle,'PaperPosition',[0.05 0.05 dim],'PaperUnits','Inches','PaperSize',dim) 9 | %f_handle.PaperPosition = [0 0 dim]; 10 | print('-dpdf','-r300', filename) -------------------------------------------------------------------------------- /util_funcs/neighbor_phase.m: -------------------------------------------------------------------------------- 1 | function NFC = neighbor_phase(data, fs, winLen, stepLen) 2 | %NFC = neighbor_phase(data, fs, winLen, stepLen) 3 | 4 | %% 5 | lag = 1; 6 | [F Ft Fa] = stft([],data, fs, winLen, stepLen, 400); %prime window size 7 | ph = F./abs(F); %normalize fourier amplitude 8 | df = fs/winLen; 9 | %dph = F(1:end-lag,:,:)./F(1+lag:end,:,:); 10 | %keyboard 11 | for i=1:(200/df)+1 12 | dph(i,:) = mean((ph(i+1,:,:)./ph(i,:,:)),3); 13 | end 14 | NFC = abs(dph(1:end-1,:))+abs(dph(2:end,:)); -------------------------------------------------------------------------------- /util_funcs/butterpass.m: -------------------------------------------------------------------------------- 1 | function dataF = butterpass(data,fs, passband, order) 2 | %dataF = butterpass(data,fs, passband, order) 3 | %bandpass filter data matrix using butterworth filter 4 | %outputs: 5 | % dataF: output data matrix 6 | %inputs: 7 | % data: data matrix, [sample x channel] 8 | % fs: sampling rate 9 | % passband: [lowcut highcut] 10 | % (optional)order: filter order, default = 2 11 | 12 | if nargin==3 13 | order=2; 14 | end 15 | 16 | [b,a] = butter(order, 2*passband/fs, 'bandpass'); 17 | dataF = filtfilt(b,a,data); 18 | 19 | end -------------------------------------------------------------------------------- /Axion/freadstring.m: -------------------------------------------------------------------------------- 1 | function FileString = freadstring( aFileID ) 2 | %FREADSTRING reads a unicode string starting at the current location 3 | %of the file handle held by aFileID. Note that this function assumes that the 4 | %next 4 bytes will be an int32 that gives the length of a utf-8 string in bytes, 5 | %immediately after it. 6 | 7 | fBytes = fread(aFileID, 1, 'int32=>int32'); 8 | fBytes = fread(aFileID, double(fBytes), 'uint8=>uint8'); 9 | fBytes = fBytes'; 10 | FileString = native2unicode(fBytes, 'UTF-8'); 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /util_funcs/CVn.m: -------------------------------------------------------------------------------- 1 | function [cv_all isi_avg]= CVn(isi, n) 2 | %function [cv_all isi_avg]= CVn(isi, n) 3 | % isi: interspike interval of the spiketrain (i.e. diff(spike_time)) 4 | % n: nth order CV, calculates spread over n adjacent spikes 5 | % cv_all: output vector of cv2 between pairs of isi (see HOLT CV2 paper) 6 | if length(isi)thresh 14 | [p, spikes{chan,1}] = findpeaks(abs(data(:,chan)), 'minpeakdistance', mpd, 'minpeakheight', thresh); 15 | spk_cnt(chan) = length(p); 16 | else 17 | disp(sprintf('No spikes on channel: %i',chan)); 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /util_funcs/bin_spikes.m: -------------------------------------------------------------------------------- 1 | function bin_sp = bin_spikes(spike_times, t_orig, binned_rate) 2 | %function bin_sp = bin_spikes(spike_times, t_orig, binned_rate) 3 | % spike_times: vector of spike time stamps in index number 4 | % t_orig = [spk_rate t_end]: original sampled rate, and end time in s 5 | % binned_rate: bin rate, usually 1000 6 | % 7 | % don't call this for cell data, call binarize_spikes 8 | 9 | spk_rate = t_orig(1); 10 | t_end = t_orig(2); 11 | bin_sp = zeros(ceil(t_end*binned_rate),1); 12 | spk_inds = round(spike_times/spk_rate*binned_rate); 13 | if length(unique(spk_inds)) == length(spk_inds) 14 | %if no repeating indices, just index it 15 | bin_sp(spk_inds(spk_inds>0)) = 1; 16 | else 17 | for idx=spk_inds 18 | bin_sp(idx)= bin_sp(idx)+1; 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /util_funcs/mPSD.m: -------------------------------------------------------------------------------- 1 | function [PSD, Fa] = mPSD(data, Fs, winLen, stepLen, end_freq) 2 | %function mPSD(data, Fs, winLen, stepLen, end_freq) 3 | % computes median of stft, colloquially referred to as median welch. 4 | % Basically it does the exact same thing as welch's method, but takes 5 | % median instead of the mean, because median is robust to huge outliers and 6 | % spectral data is non-uniformly distributed 7 | % 8 | % data: time series 9 | % Fs: sampling frequency of the data 10 | % winLen: window length in number of samples (use same number as Fs) 11 | % stepLen: step length in number of samples (use about 1/50 as Fs (or 10 samples) 12 | % end_freq (optional): cut of frequency of stft, default is fs/2 13 | % PSD: returns median PSD 14 | % Fa: returns frequency axis 15 | 16 | if size(data,1)1 15 | %non-symmetric window 16 | window = winlen(1):winlen(2); 17 | else 18 | %symmetric window 19 | window = -winlen:winlen; 20 | end 21 | %grab spikes 22 | spikes = zeros(length(loc), length(window)); 23 | for i=1:length(loc) 24 | try 25 | spikes(i,:) = data(loc(i)+window); 26 | catch 27 | disp(sprintf('Spike window #%i outside of data range', i)) 28 | end 29 | end 30 | %remove empty rows 31 | spikes(all(spikes==0,2),:)=[]; 32 | spikes=spikes'; -------------------------------------------------------------------------------- /util_funcs/redblue.m: -------------------------------------------------------------------------------- 1 | function C = redblue(m) 2 | %REDBLUE Shades of red and blue color map 3 | % REDBLUE(M), is an M-by-3 matrix that defines a colormap. 4 | % The colors begin with bright blue, range through shades of 5 | % blue to white, and then through shades of red to bright red. 6 | % REDBLUE, by itself, is the same length as the current figure's 7 | % colormap. If no figure exists, MATLAB creates one. 8 | % 9 | % For example, to reset the colormap of the current figure: 10 | % 11 | % colormap(redblue) 12 | % 13 | % See also HSV, GRAY, HOT, BONE, COPPER, PINK, FLAG, 14 | % COLORMAP, RGBPLOT. 15 | 16 | % Adam Auton, 9th October 2009 17 | 18 | if nargin < 1, m = size(get(gcf,'colormap'),1); end 19 | 20 | colors = get(gca,'colororder'); 21 | BB = colors(1,:); 22 | RR = colors(2,:); 23 | m1 = m/2; 24 | C = ones(m,3); 25 | for i=1:m/2 26 | C(i,:) = ([1,1,1]-RR)*(i-m/2)/(m/2)+[1,1,1]; 27 | C(m-i+1,:) = ([1,1,1]-BB)*(i-m/2)/(m/2)+[1,1,1]; 28 | end 29 | -------------------------------------------------------------------------------- /Axion/BlockVectorDataType.m: -------------------------------------------------------------------------------- 1 | classdef BlockVectorDataType < uint16 2 | %BLOCKVECTORDATATYPE Enumeration of known types of block vector data. 3 | % 4 | % Raw_v1: Continuous data from an Axion Muse or Maestro device. 5 | % 6 | % Spike_v1: Binary Spike Data recorded by a Spike detector in Axis. 7 | 8 | 9 | enumeration 10 | Raw_v1(0) 11 | Spike_v1(1) 12 | end 13 | 14 | methods(Static) 15 | function [value , success] = TryParse(aInput) 16 | try 17 | value = BlockVectorDataType(aInput); 18 | success = true; 19 | catch e 20 | 21 | warning(... 22 | 'BlockVectorDataType:TryParse', ... 23 | ['Unsupported BlockVectorDataType', e]); 24 | 25 | value = aInput; 26 | success = false; 27 | end 28 | end 29 | end 30 | 31 | end 32 | 33 | -------------------------------------------------------------------------------- /scripts/ctrFC_pipeline.m: -------------------------------------------------------------------------------- 1 | do_convert = 0; 2 | wells2process = [7 8]; 3 | batch_folder = '~/Documents/data/Muotri/Fetal/'; 4 | cd(batch_folder) 5 | if do_convert 6 | p1_convertpreprocess(batch_folder, wells2process) 7 | end 8 | %% collect 9 | D = dir(batch_folder); 10 | D = D(3:end); % ignore ./ and ../ 11 | D = D([D.isdir]); 12 | psd = {}; 13 | nws_smo = {}; 14 | recday = zeros(1,length(D)); 15 | for f = 1:length(D) 16 | % get date string 17 | date_str = D(f).name(end-10:end-5); 18 | recday(f)=datenum(date_str,'mmddyy'); 19 | cur_data = load([D(f).name, '/summary.mat']); 20 | psd{f} = cur_data.PSDw(wells2process); 21 | nws_smo{f} = cur_data.nws_smo(wells2process,:)'; 22 | end 23 | dayVec = (recday-min(recday))/7+10; 24 | 25 | %% 26 | % computing features 27 | fs = 1000; 28 | fp_params.MPH = 1; 29 | fp_params.MPH_ratio = 0.8; 30 | fp_params.MPD = 200; 31 | fp_params.toss_thresh_ratio = 0.001; 32 | psd_freq = 0:0.5:500; 33 | [event_feats, pk_times] = compute_peakfeats(nws_smo, fs, fp_params); 34 | ctrlFC_EMAfeatures = compute_nEEGfeats(psd_freq, psd, event_feats); 35 | save([batch_folder, 'ctrlFC_EMAfeatures.mat'], 'dayVec', 'ctrlFC_EMAfeatures') -------------------------------------------------------------------------------- /scripts/ctrNS_pipeline.m: -------------------------------------------------------------------------------- 1 | do_convert = 1; 2 | wells2process = 1:12; 3 | batch_folder = '~/Documents/data/Muotri/Neurosphere/'; 4 | cd(batch_folder) 5 | if do_convert 6 | p1_convertpreprocess(batch_folder, wells2process) 7 | end 8 | %% collect 9 | D = dir(batch_folder); 10 | D = D(3:end); % ignore ./ and ../ 11 | D = D([D.isdir]); 12 | psd = {}; 13 | nws_smo = {}; 14 | recday = zeros(1,length(D)); 15 | for f = 1:length(D) 16 | % get date string 17 | date_str = D(f).name(end-10:end-5); 18 | recday(f)=datenum(date_str,'mmddyy'); 19 | cur_data = load([D(f).name, '/summary.mat']); 20 | psd{f} = cur_data.PSDw(wells2process); 21 | nws_smo{f} = cur_data.nws_smo(wells2process,:)'; 22 | end 23 | dayVec = (recday-min(recday))/7+10; 24 | 25 | %% 26 | % computing features 27 | fs = 1000; 28 | fp_params.MPH = 1; 29 | fp_params.MPH_ratio = 0.8; 30 | fp_params.MPD = 200; 31 | fp_params.toss_thresh_ratio = 0.001; 32 | psd_freq = 0:0.5:500; 33 | [event_feats, pk_times] = compute_peakfeats(nws_smo, fs, fp_params); 34 | ctrlFC_EMAfeatures = compute_nEEGfeats(psd_freq, psd, event_feats); 35 | save([batch_folder, 'ctrlFC_EMAfeatures.mat'], 'dayVec', 'ctrlFC_EMAfeatures') -------------------------------------------------------------------------------- /scripts/nEEGfeats_mouse.m: -------------------------------------------------------------------------------- 1 | %% script for computing mouse primary culture (mpc) nEEG features 2 | warning('off') 3 | datapath = '/Users/rdgao/Documents/data/Muotri/Spencer/'; 4 | fs = 1000; 5 | fp_params.MPH = 1; 6 | fp_params.MPH_ratio = 0.8; 7 | fp_params.MPD = 200; 8 | fp_params.toss_thresh_ratio = 0.001; 9 | psd_freq = 0:0.5:500; 10 | 11 | files = {'39127_summary.mat', '41111_summary.mat'}; 12 | wt_inds = {[1 2 4 5 6 9], [1 2 5 6 7 8 9 10]}; 13 | 14 | %% 15 | for f = 1:length(files) 16 | disp('Loading...') 17 | load([datapath,files{f}]) 18 | disp([datapath,files{f}]) 19 | 20 | DIV = [agg_summary.date_num] - agg_summary(1).date_num+3; 21 | dayVec = DIV/7; % this is fucking named dayVec even though it's weeks. Why do I do this to myself. 22 | for i=1:length(agg_summary) 23 | nws_smo{i} = agg_summary(i).nws_smo(wt_inds{f},:)'; 24 | psd{i} = agg_summary(i).PSDw(wt_inds{f}); 25 | end 26 | 27 | % computing features 28 | [event_feats, pk_times] = compute_peakfeats(nws_smo, fs, fp_params); 29 | mpc_EMAfeatures = compute_nEEGfeats(psd_freq, psd, event_feats); 30 | 31 | save([datapath, 'mpc', int2str(f), '_EMAfeatures.mat'], 'dayVec', 'mpc_EMAfeatures') 32 | clear nws_smo psd 33 | end 34 | 35 | 36 | -------------------------------------------------------------------------------- /scripts/ctr2D_pipeline.m: -------------------------------------------------------------------------------- 1 | do_convert = 0; 2 | 3 | % Please use wells A1, A3, B4 and C3 ONLY. 4 | % The earliest time point corresponds to 8 weeks in culture. The latest correspond to 20 weeks in culture. 5 | wells2process = [1 3 8 11]; 6 | batch_folder = '~/Documents/data/Muotri/2D/'; 7 | cd(batch_folder) 8 | if do_convert 9 | p1_convertpreprocess(batch_folder, wells2process) 10 | end 11 | %% collect 12 | D = dir(batch_folder); 13 | D = D(3:end); % ignore ./ and ../ 14 | D = D([D.isdir]); 15 | psd = {}; 16 | nws_smo = {}; 17 | recday = zeros(1,length(D)); 18 | for f = 1:length(D) 19 | % get date string 20 | date_str = D(f).name(end-10:end-5); 21 | recday(f)=datenum(date_str,'mmddyy'); 22 | cur_data = load([D(f).name, '/summary.mat']); 23 | psd{f} = cur_data.PSDw(wells2process); 24 | nws_smo{f} = cur_data.nws_smo(wells2process,:)'; 25 | end 26 | dayVec = (recday-recday(1))/7+8; 27 | 28 | %% 29 | % computing features 30 | fs = 1000; 31 | fp_params.MPH = 1; 32 | fp_params.MPH_ratio = 0.8; 33 | fp_params.MPD = 200; 34 | fp_params.toss_thresh_ratio = 0.001; 35 | psd_freq = 0:0.5:500; 36 | [event_feats, pk_times] = compute_peakfeats(nws_smo, fs, fp_params); 37 | ctrl2D_EMAfeatures = compute_nEEGfeats(psd_freq, psd, event_feats); 38 | save([batch_folder, 'ctrl2D_EMAfeatures.mat'], 'dayVec', 'ctrl2D_EMAfeatures') 39 | -------------------------------------------------------------------------------- /Axion/StimulationEventData.m: -------------------------------------------------------------------------------- 1 | classdef StimulationEventData 2 | %STIMULATIONTAGBLOCK Structure that contains the data describing a 3 | %marked, stimulation portion of a file 4 | 5 | properties(SetAccess = private) 6 | %ID: Number that StimulationEvent tags attach to 7 | ID; 8 | %StimDuration: Length of time (in seconds) that ths stimulation 9 | %portion of this block lasted 10 | StimDuration; 11 | %ArtifactEliminationDuration: Length of time (in seconds) that this 12 | %Artifact Elimination portion of this block lasted 13 | ArtifactEliminationDuration; 14 | %ChannelArrayIdList Channel array IDs that were used in this block 15 | ChannelArrayIdList; 16 | %Textual description of this Tag block 17 | Description; 18 | end 19 | 20 | methods 21 | function this = StimulationEventData(... 22 | aId, aStimDuration, aArtElimDuration,... 23 | aChannelArrayIdList, aDescription) 24 | this.ID = aId; 25 | this.StimDuration = aStimDuration; 26 | this.ArtifactEliminationDuration = aArtElimDuration; 27 | this.ChannelArrayIdList = aChannelArrayIdList; 28 | this.Description = aDescription; 29 | end 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /Axion/Annotation.m: -------------------------------------------------------------------------------- 1 | classdef Annotation < EventTag 2 | %ANNOTATION tag that correspond to events listed in AxIS's play bar 3 | properties(GetAccess = public, SetAccess = private) 4 | NoteText; 5 | end 6 | 7 | methods 8 | function this = Annotation(aFileID, aRawTag) 9 | this@EventTag(aFileID, aRawTag) 10 | 11 | %Assume EventTag constructor leaves us at the right place 12 | fWellColumn = fread(aFileID, 1, 'uint8=>uint8'); 13 | fWellRow = fread(aFileID, 1, 'uint8=>uint8'); 14 | fElectrodeColumn = fread(aFileID, 1, 'uint8=>uint8'); 15 | fElectrodeRow = fread(aFileID, 1, 'uint8=>uint8'); 16 | 17 | %Annotations are always broadcast 18 | if fWellColumn ~= 0 || fWellRow ~= 0 || ... 19 | fElectrodeColumn ~= 0 || fElectrodeRow ~= 0 20 | warning('File may be corrupt'); 21 | end 22 | 23 | this.NoteText = freadstring(aFileID); 24 | 25 | fStart = aRawTag.Start + TagEntry.BaseSize; 26 | if ftell(aFileID) > (fStart + aRawTag.EntryRecord.Length) 27 | warning('File may be corrupt'); 28 | end 29 | 30 | end 31 | end 32 | 33 | end 34 | 35 | -------------------------------------------------------------------------------- /util_funcs/spike_raster.m: -------------------------------------------------------------------------------- 1 | function spike_raster(t,spikes,well, th_mode) 2 | % function spike_raster(t,spikes,well, th_mode) 3 | 4 | %figure 5 | if nargin==2 6 | %plot binarized data 7 | colors = get(gca,'colororder'); 8 | %hold on 9 | for i=1:size(spikes,1) 10 | if any(find(spikes(i,:))) 11 | %line([t(find(spikes(i,:)));t(find(spikes(i,:)))], [i-0.2;i+0.2]+20, 'color', colors(2,:), 'linewidth',2); 12 | %line([t(find(spikes(i,:)));t(find(spikes(i,:)))], [i-0.2;i+0.2], 'color', 'k', 'linewidth',1) 13 | plot([t(find(spikes(i,:)));t(find(spikes(i,:)))], [i-0.2;i+0.2], 'k.') 14 | end 15 | end 16 | hold off 17 | else 18 | %plot time data in cell form 19 | if nargin<4 20 | th_mode = 1; 21 | end 22 | hold on 23 | for i=1:size(spikes,2) 24 | if ~isempty(spikes{well,i}) 25 | %line([t(spikes{well,i,th_mode}) t(spikes{well,i,th_mode})]', [i-0.2 i+0.2], 'color', 'k') 26 | %line([t(spikes{well,i}) t(spikes{well,i})]', [i-0.2 i+0.2], 'color', 'k') 27 | %plot(t(spikes{well,i,th_mode}), i, 'k.') 28 | scatter(t(spikes{well,i,th_mode}), i*ones(1,length(spikes{well,i,th_mode})), 5, 'k.') 29 | end 30 | end 31 | hold off 32 | ylim([0 size(spikes,2)+1]) 33 | end 34 | xlabel('Time') 35 | ylabel('Channel') -------------------------------------------------------------------------------- /util_funcs/burst_detect.m: -------------------------------------------------------------------------------- 1 | function [burst_labels, b_inds, b_times] = burst_detect(spk_times, min_num, burst_int) 2 | %[burst_labels, b_inds, b_times] = find_bursts(spk_times, min_num, burst_int) 3 | % spike_times: timestamp of spike train 4 | % min_num: minimum number of spikes required to be labeled burst (>=2) 5 | % burst_int: time difference between consecutive spikes to be considered 6 | 7 | burst_labels = zeros(size(spk_times)); 8 | 9 | bursting = 0; 10 | cur_burst = 1; 11 | for i=1:length(spk_times)-min_num+1 12 | 13 | %scroll through windows 14 | cur_inds = i+(1:min_num)-1; 15 | cur_win = spk_times(cur_inds); 16 | 17 | if all(diff(cur_win)0, 'r') 43 | % hold off -------------------------------------------------------------------------------- /functions/p1_convertpreprocess.m: -------------------------------------------------------------------------------- 1 | function p1_convertpreprocess(batch_folder, wells2process) 2 | % Conversion and Preprocessing 3 | % ** requires the Axion functions also included ** 4 | % 5 | % Each Axion .raw file in this folder is decoded and saved into a folder, 6 | % where each well is saved as its own .mat file. 7 | % 8 | % For preprocessing, MEA_process does the following: 9 | % - downsamples raw data to 1000Hz LFP 10 | % - detect MUA/spikes based on adapted standard deviation estimation 11 | % from Quiroga et al., Neural Comp (2004) 12 | % - save LFP, spike timing, and spike waveforms to LFP_sp.mat 13 | % 14 | % For summary, MEA_summary computes and saves (& optionally prints out): 15 | % - median and welch's PSD 16 | % - network spike vector 17 | % - network spiking autocorrelation 18 | % for all the wells, 19 | 20 | % define raw data folder to run conversion 21 | cd(batch_folder) 22 | raw_data_folder = batch_folder; 23 | raw_files = dir('*.raw'); 24 | 25 | for f=1:length(raw_files) 26 | output_folder = [raw_data_folder raw_files(f).name(1:end-4)]; 27 | processed_file = [output_folder '/LFP_Sp.mat']; 28 | disp(raw_files(f).name) 29 | % do the conversion 30 | MEA_convert(raw_files(f).name, output_folder, wells2process); 31 | % do preprocessing 32 | MEA_process(output_folder, wells2process); 33 | % compute and saveout summary info 34 | MEA_summary(processed_file, [output_folder '/'], wells2process, 1) 35 | end -------------------------------------------------------------------------------- /util_funcs/inst_cfpw.m: -------------------------------------------------------------------------------- 1 | function [cf, bw, pw] = inst_cfpw(data, fs, osc_band, winLen, stepLen) 2 | %[cf bw pw] = inst_cfpw(data, fs, osc_band, winLen, stepLen) 3 | % inputs: 4 | % data - data, will automatically rotate 2D matrix to be compatible 5 | % fs - sampling frequency 6 | % osc_band - [lowcut highcut] of band pass filter 7 | % winLen - window length (samples) to do the median averaging, 0 for raw 8 | % stepLen - step length to move the window for median averaging 9 | % outputs: 10 | % cf - center frequency; instantaneous center freq if no winLen specified 11 | % bw - bandwidth, [] if no winLen specified 12 | % pw - instantaneous power 13 | 14 | % rotate data to column vector 15 | if size(data,2)>size(data,1) 16 | data=data'; 17 | end 18 | %size(data) 19 | disp('Filtering...') 20 | osc = eegfilt(data',fs, osc_band(1), osc_band(2))'; 21 | IF = (diff(unwrap(angle(hilbert(osc))))*fs/2/pi); 22 | PW = log10(abs((hilbert(osc))).^2); 23 | 24 | % not doing median stacking 25 | if winLen ==0 26 | cf = IF; 27 | bw = []; 28 | pw = PW(1:end-1); 29 | return 30 | end 31 | 32 | num_chan = size(data,2); 33 | cf = zeros(ceil((length(IF)-winLen)/stepLen), num_chan); 34 | bw = cf; 35 | pw = cf; 36 | disp('Stacking...') 37 | for i=1:num_chan 38 | cf_stack = sliding_win(IF(:,i),winLen,stepLen); 39 | cf(:,i) = median(cf_stack); 40 | bw(:,i) = iqr(cf_stack); 41 | pw(:,i) = median(sliding_win(PW(2:end,i),winLen,stepLen)); 42 | end 43 | end 44 | 45 | -------------------------------------------------------------------------------- /functions/compute_nEEGfeats.m: -------------------------------------------------------------------------------- 1 | function EMAfeatures = compute_nEEGfeats(psd_freq, psd_cell, event_feats) 2 | 3 | num_feat = 23; 4 | num_wells = size(event_feats.ED_05,2); 5 | num_recs = length(psd_cell); 6 | 7 | % stack all features in 3D array 8 | EMAfeatures = zeros(num_recs, num_wells, num_feat); 9 | 10 | % 7: events per hour 11 | EMAfeatures(:,:,7) = event_feats.EPH; 12 | 13 | % 8-11: RMS, 50%, 5%, 95%, event (SAT) duration 14 | EMAfeatures(:,:,8) = event_feats.ED_rms; 15 | EMAfeatures(:,:,9) = event_feats.ED_50; 16 | EMAfeatures(:,:,10) = event_feats.ED_05; 17 | EMAfeatures(:,:,11) = event_feats.ED_95; 18 | 19 | % 12-15: RMS, 50%, 5%, and 95% inter-event duration 20 | EMAfeatures(:,:,12) = event_feats.IEI_rms; 21 | EMAfeatures(:,:,13) = event_feats.IEI_50; 22 | EMAfeatures(:,:,14) = event_feats.IEI_05; 23 | EMAfeatures(:,:,15) = event_feats.IEI_95; 24 | 25 | % loopdiloop for spectral features 26 | % delta (0-3), theta(3-8), alpha(8-15), beta(15-30) 27 | band_bounds = [0, 3, 8, 15, 30]; 28 | for r_idx = 1:num_recs 29 | for well = 1:num_wells 30 | % 20-23: relative spectral power 31 | % sum to 1 from 0-30Hz 32 | psd = psd_cell{r_idx}{well}(find(psd_freq<=band_bounds(end)),:); 33 | psd_norm = psd./repmat(sum(psd,1),length(find(band_bounds(end))),1); 34 | for bb = 1:length(band_bounds)-1 35 | bb_inds = find(psd_freq>=band_bounds(bb) & psd_freqdouble'); 28 | this.EventTimeSample = fread(aFileID, 1, 'int64=>int64'); 29 | this.EventDurationSamples = fread(aFileID, 1, 'int64=>int64'); 30 | 31 | this.EventTime = double(this.EventTimeSample) / this.SamplingFrequency; 32 | else 33 | error('Encountered an error while loading EventTag %s', aRawTag.TagGuid); 34 | end 35 | end 36 | 37 | end 38 | 39 | end 40 | 41 | -------------------------------------------------------------------------------- /Axion/Spike_v1.m: -------------------------------------------------------------------------------- 1 | classdef Spike_v1 < Waveform 2 | %SPIKE_V1: An extension of Waveform that represents a spike recorded by 3 | % spike detector in Axis. 4 | % 5 | % TriggerSampleOffset: Offset (in samples) from the start of the 6 | % waveform where the spike detector was 7 | % triggered 8 | % 9 | % StandardDeviation: RMS voltage value of the signal noise at the 10 | % time the spike was caputred 11 | % 12 | % ThresholdMultiplier: Multiplier(if applicable) of the RMS Noise that was 13 | % used as the trigger voltage for this spike 14 | % 15 | 16 | 17 | properties(GetAccess = public, Constant = true) 18 | LOADED_HEADER_SIZE = 30; 19 | end 20 | 21 | properties (GetAccess = public, SetAccess = private) 22 | TriggerSampleOffset 23 | StandardDeviation 24 | ThresholdMultiplier 25 | end 26 | 27 | methods 28 | 29 | function this = Spike_v1( ... 30 | aChannel, ... 31 | aStart, ... 32 | aData, ... 33 | aSource, ... 34 | aTriggerSampleOffset, ... 35 | aStandardDeviation, ... 36 | aThresholdMultiplier) 37 | if(nargin == 0) 38 | return; 39 | end 40 | 41 | this.Channel = aChannel; 42 | this.Start = aStart; 43 | this.Data = aData; 44 | this.Source = aSource; 45 | this.TriggerSampleOffset = aTriggerSampleOffset; 46 | this.StandardDeviation = aStandardDeviation; 47 | this.ThresholdMultiplier = aThresholdMultiplier; 48 | end 49 | end 50 | 51 | end 52 | 53 | -------------------------------------------------------------------------------- /Axion/TagType.m: -------------------------------------------------------------------------------- 1 | classdef TagType < uint16 2 | %TAGTYPE Enumeration of the types of tags that are known. 3 | 4 | enumeration 5 | %Deleted: Tag revision where this TagGUID has been deleted 6 | %remarks: This is a special case, Tags may alternate between this 7 | % type and their own as the are deleted / undeleted 8 | Deleted(uint16(0)), 9 | 10 | % WellTreatment: Describes the treatment state of a well in a file 11 | WellTreatment(uint16(1)), 12 | 13 | %UserAnnotation: Time based note added to the file by the user 14 | UserAnnotation(uint16(2)), 15 | 16 | %SystemAnnotation: Time based note added to the file by Axis 17 | SystemAnnotation(uint16(3)), 18 | 19 | %DataLossEvent: Tag that records any loss of data in the system that affects this 20 | % recorded file. 21 | %Remarks: Coming soon, Currently Unused! 22 | DataLossEvent(uint16(4)), 23 | 24 | %StimulationEvent: Tag that describes a stimulation that was applied to the plate 25 | % during recording 26 | StimulationEvent(uint16(5)), 27 | 28 | %StimulationChannelGroup: Tag that lists the channels that were loaded for stimulation for a StimulationEvent 29 | % Many StimulationEvent tags may reference the same StimulationChannelGroup 30 | StimulationChannelGroup (uint16(6)), 31 | 32 | %StimulationWaveform: Tag that lists the stimulation that was applied for stimulation for a StimulationEvent 33 | % Many StimulationEvent tags may reference the same StimulationWaveform 34 | StimulationWaveform (uint16(7)), 35 | 36 | %CalibrationTag: Tag that is used for axis's internal calibration 37 | % of noise mesurements (Use is currently not 38 | % supported in matlab) 39 | CalibrationTag(uint16(8)), 40 | end 41 | 42 | end 43 | 44 | -------------------------------------------------------------------------------- /Axion/DateTime.m: -------------------------------------------------------------------------------- 1 | classdef DateTime 2 | 3 | properties (GetAccess = public, Constant = true) 4 | Size = 14; 5 | end 6 | %DATETIME class representation of a date and time. 7 | properties (GetAccess = public, SetAccess = private) 8 | Year 9 | Month 10 | Day 11 | Hour 12 | Minute 13 | Second 14 | Millisecond 15 | end 16 | 17 | methods(Access = public) 18 | 19 | function this = DateTime(aFileID) 20 | %DateTime: Loads a DateTime from a file, assuming the read stream is 21 | %pointed at the correct location. 22 | this.Year = fread(aFileID, 1, 'uint16=>double'); 23 | this.Month = fread(aFileID, 1, 'uint16=>double'); 24 | this.Day = fread(aFileID, 1, 'uint16=>double'); 25 | this.Hour = fread(aFileID, 1, 'uint16=>double'); 26 | this.Minute = fread(aFileID, 1, 'uint16=>double'); 27 | this.Second = fread(aFileID, 1, 'uint16=>double'); 28 | this.Millisecond = fread(aFileID, 1, 'uint16=>double'); 29 | end 30 | 31 | function datevector = ToDateTimeVect(this) 32 | %returns a six element date vector containing the represented time 33 | % and date in decimal form. 34 | % See also: clock, datevec, datenum, now 35 | fSeconds = this.Second + (this.Millisecond * 1e-3); 36 | datevector = double([this.Year this.Month this.Day this.Hour this.Minute fSeconds]); 37 | end 38 | 39 | function datenumber = ToDateTimeNumber(this) 40 | % returns the represented date and time as a serial date 41 | % number. 42 | % See also: clock, datevec, datenum, now 43 | datenumber = datenum(this.ToDateTimeVect()); 44 | end 45 | end 46 | 47 | end 48 | 49 | -------------------------------------------------------------------------------- /Axion/TagEntry.m: -------------------------------------------------------------------------------- 1 | classdef TagEntry < Entry 2 | %TAGENTRY Section of an AxisFile that contains TagRevison 3 | 4 | properties(GetAccess = public, Constant = true) 5 | BaseSize = int64(2 + DateTime.Size + 16 + 4); 6 | end 7 | 8 | properties(GetAccess = public, SetAccess = private) 9 | % CreationDate: The date/time that this tag revision was created 10 | CreationDate; 11 | % TagGuid: GUID unique to this tag and its revisions 12 | TagGuid; 13 | % RevisionNumber: The number of times this tag has been revised up to this revision 14 | RevisionNumber; 15 | % Type: The type of event that created this tag (UserAnnotation, DataLossEvent, etc) 16 | Type; 17 | end 18 | 19 | methods 20 | function this = TagEntry(aEntryRecord, aFileID) 21 | this = this@Entry(aEntryRecord, int64(ftell(aFileID))); 22 | 23 | fTypeShort = fread(aFileID, 1, 'uint16=>uint16'); 24 | try 25 | this.Type = TagType(fTypeShort); 26 | catch e 27 | if(strcmp('MATLAB:class:InvalidEnum', e.identifier)) 28 | warning('TagEntry:UnknonwTagType','Unknown tag type %i will be ignored', fTypeShort); 29 | else 30 | e.throw; 31 | end 32 | this.Type = TagType(TagType.Deleted); 33 | end 34 | this.CreationDate = DateTime(aFileID); 35 | guidBytes = fread(aFileID, 16, 'uint8=>uint8'); 36 | this.TagGuid = parseGuid(guidBytes); 37 | this.RevisionNumber = fread(aFileID, 1, 'uint32=>uint32'); 38 | 39 | %Seek to the end, we only parse the heads of the TagEntries as 40 | %the file loads 41 | fseek(aFileID, int64(this.EntryRecord.Length) - TagEntry.BaseSize, 'cof'); 42 | 43 | end 44 | end 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /Axion/load_AxIS_file.m: -------------------------------------------------------------------------------- 1 | %load_AxIS_file [[DEPRECATED]] reads raw recording files created by AxIS 2 | % 3 | % Legal forms: 4 | % data = load_AxIS_file(filename); 5 | % data = load_AxIS_file(filename, well); 6 | % data = load_AxIS_file(filename, electrode); 7 | % data = load_AxIS_file(filename, well, electrode); 8 | % data = load_AxIS_file(filename, timespan); 9 | % data = load_AxIS_file(filename, well, timespan); 10 | % data = load_AxIS_file(filename, electrode, timespan); 11 | % data = load_AxIS_file(filename, well, electrode, timespan); 12 | % 13 | % Required arguments: 14 | % filename Pathname of the file to load 15 | % 16 | % Optional arguments: 17 | % well String listing which wells (in a multiwell file) to load. 18 | % Format is a comma-delimited string with whitespace ignored, e.g. 19 | % 'A1, B2,C3' limits the data loaded to wells A1, B2, and C3. 20 | % Also acceptable: 'all' to load all wells. 21 | % If this parameter is omitted, all wells are loaded. 22 | % For a single-well file, this parameter is ignored. 23 | % 24 | % electrode Which electrodes to load. Format is either a comma-delimited string 25 | % with whitespace ignored (e.g. '11, 22,33') or a single channel number; 26 | % that is, a number, not part of a string. 27 | % Also acceptable: 'all' to load all channels and 'none', '-1', or -1 28 | % to load no data (returns only header information). 29 | % If this parameter is omitted, all channels are loaded. 30 | % 31 | % timespan Span of time, in seconds, over which to load data. Format is a two-element 32 | % array, [t0 t1], where t0 is the start time and t1 is the end time and both 33 | % are in seconds after the first sample in the file. Samples returned are ones 34 | % that were taken at time >= t0 and <= t1. The beginning of the file 35 | % is at 0 seconds. 36 | % If this parameter is omitted, the data is not filtered based on time. 37 | % 38 | 39 | function aData = load_AxIS_file(aFileName, varargin) 40 | aData = AxisFile(aFileName).DataSets(1).load_as_legacy_struct(varargin); 41 | end 42 | 43 | -------------------------------------------------------------------------------- /scripts/ctc_findpeaks.m: -------------------------------------------------------------------------------- 1 | %% find peaks and get peak features 2 | for r_idx = 1:length(nws_smo) 3 | disp(dates(r_idx).name) 4 | for well = wells 5 | pktimes{r_idx}{well} = kernel_findpeak(nws_smo{r_idx}(:,well), MPH, MPH_ratio, MPD, toss_thresh_ratio); 6 | end 7 | end 8 | 9 | %% 10 | recDays_IEI_flat = cell(1,length(recs)); 11 | recDays_ED_flat = cell(1,length(recs)); 12 | IEI_flat = cell(1,length(recs)); 13 | ED_flat = cell(1,length(recs)); 14 | for r_idx = 1:length(recs) 15 | rec = recs(r_idx); 16 | for well=wells 17 | n_events = size(pktimes{rec}{well},1); 18 | % ------- EVENTS PER HOUR ------ % 19 | EPH(r_idx, well-4) = n_events/T{rec}*3600; 20 | 21 | 22 | if n_events>0 23 | % ----------- IEI FEATURES ----------------- 24 | % time vector for regression and stuff 25 | recDays_IEI_flat{r_idx} = [recDays_IEI_flat{r_idx} recDays(r_idx).*ones(1,n_events-1)]; 26 | 27 | % inter event interval 28 | iei = diff(pktimes{rec}{well}(:,2))/fs; 29 | IEI_flat{r_idx} = [IEI_flat{r_idx} iei']; 30 | IEI_mean(r_idx,well-4) = mean(iei); 31 | IEI_std(r_idx,well-4) = std(iei); 32 | 33 | % quantile features (for baby EEG) 34 | IEI_rms(r_idx,well-4) = mean(iei.^2).^0.5; 35 | IEI_50(r_idx, well-4) = quantile(iei, 0.5); 36 | IEI_05(r_idx, well-4) = quantile(iei, 0.05); 37 | IEI_95(r_idx, well-4) = quantile(iei, 0.95); 38 | 39 | % ----------- EVENT DURATION FEATURES ----------------- 40 | recDays_ED_flat{r_idx} = [recDays_ED_flat{r_idx} recDays(r_idx).*ones(1,n_events)]; 41 | 42 | % event duration 43 | ev_dur = (pktimes{rec}{well}(:,3)-pktimes{rec}{well}(:,1))/fs; 44 | ED_flat{r_idx} = [ED_flat{r_idx} ev_dur']; 45 | ED_mean(r_idx,well-4) = mean(ev_dur); 46 | ED_std(r_idx,well-4) = std(ev_dur); 47 | 48 | ED_rms(r_idx,well-4) = mean(ev_dur.^2).^0.5; 49 | ED_50(r_idx, well-4) = quantile(ev_dur, 0.5); 50 | ED_05(r_idx, well-4) = quantile(ev_dur, 0.05); 51 | ED_95(r_idx, well-4) = quantile(ev_dur, 0.95); 52 | end 53 | end 54 | end -------------------------------------------------------------------------------- /Axion/Entry.m: -------------------------------------------------------------------------------- 1 | classdef(Abstract = true) Entry < handle 2 | 3 | %ENTRY base class for all Axion file entries 4 | % 5 | % EntryRecord: Record that indicated this recod in the file. 6 | % 7 | % Start: Location (# of bytes from start of file) of this 8 | % entryss 9 | 10 | properties (GetAccess = private, SetAccess = private) 11 | indiciesForChannels 12 | indiciesForElectrodes 13 | end 14 | 15 | properties (GetAccess = public, SetAccess = private) 16 | EntryRecord 17 | Start 18 | end 19 | 20 | methods (Access = protected) 21 | function this = Entry(varargin) 22 | % Entry: Construct a new instance of Entry. 23 | %s 24 | % Valid aruments: 25 | % 26 | % Entry() Consructs an entry that is not tied to a 27 | % location in a file. 28 | % 29 | % Entry(aEntryRecord, aStart) Constructs a new Entry where: 30 | % 31 | % aEntryRecord: An EntryRecord that specifies the type and 32 | % the length of the entry in the file 33 | % 34 | % aStart: An int64 specifiying the number of bytes 35 | % from the beginning of the file where the 36 | % entry starts in the file. 37 | % 38 | 39 | fNumArgs = length(varargin); 40 | 41 | if fNumArgs == 0 42 | % Handle the no-argument case 43 | return 44 | elseif fNumArgs == 2 45 | aEntryRecord = varargin{1}; 46 | aStart = varargin{2}; 47 | else 48 | error('Entry: Argument Error') 49 | end 50 | 51 | if(~isa(aEntryRecord, 'EntryRecord')) 52 | error('Entry: Unexpected Type') 53 | end 54 | 55 | if(~isa(aStart, 'int64')) 56 | error('Entry: Unexpected Type') 57 | end 58 | this.EntryRecord = aEntryRecord; 59 | this.Start = aStart; 60 | end 61 | end 62 | 63 | end 64 | 65 | -------------------------------------------------------------------------------- /functions/MEA_summary.m: -------------------------------------------------------------------------------- 1 | function MEA_summary(agg_file, output_folder, wells, print_report) 2 | % MEA_summary(agg_file, output_folder, wells, print_report) 3 | % load a LFP_Sp.mat file and produce a print out of network spike vector 4 | % and PSDs, and save out summary file to disk 5 | 6 | disp('Loading file.') 7 | load(agg_file) 8 | fs = mean(1./diff(t_s)); 9 | 10 | % binarize spikes and get network spike 11 | disp('Computing.') 12 | bsp = squeeze(binarize_spikes(ceil(t_ds(end)), fs, spikes, 1000)); 13 | nws = squeeze(sum(bsp,2)); 14 | nws = nws(:,1:length(t_ds)); 15 | nws_smo = zeros(size(nws)); 16 | 17 | for well=wells 18 | PSDm{well} = mPSD(LFP{well}, fs_ds, fs_ds*2, fs_ds/2, 500); 19 | PSDw{well} = pwelch(LFP{well},fs_ds*2,fs_ds,fs_ds*2,fs_ds); 20 | nws_smo(well,:) = conv(nws(well,:),gausswin(50),'same'); 21 | end 22 | ac = autocorr(nws', 5000); 23 | % normalize AC 24 | ac = ac./ac(ceil(length(ac)/2), :); 25 | % set lag0 to 0 for better plotting 26 | ac(ceil(length(ac)/2), :) = 0; 27 | % transpose for saving 28 | ac = ac'; 29 | t_ac = (-5000:5000)/fs_ds; 30 | 31 | % plotting 32 | f_axis = 0:0.5:fs_ds/2; 33 | disp('Plotting.') 34 | figure(1) 35 | figure(2) 36 | for well=wells 37 | figure(1) 38 | subplot(3,4,well) 39 | loglog(f_axis, PSDm{well}) 40 | xlim([1,fs_ds/2]) 41 | xticks([1,100]) 42 | xticklabels({'1', '100'}) 43 | axis tight 44 | figure(2) 45 | subplot(3,4,well) 46 | loglog(f_axis, PSDw{well}) 47 | xlim([1,fs_ds/2]) 48 | xticks([1,100]) 49 | xticklabels({'1', '100'}) 50 | axis tight 51 | end 52 | figure(1) 53 | title('Median PSD') 54 | figure(2) 55 | title('Welch PSD') 56 | 57 | plot_tight(nws_smo',[3,4],[],[]) 58 | plot_tight(ac', [3,4],[],[]) 59 | 60 | 61 | % saving plots 62 | disp('Saving out.') 63 | if ~exist(output_folder,'file') 64 | mkdir(output_folder); 65 | end 66 | if print_report 67 | figure(1) 68 | nice_figure(gcf, [output_folder 'medianpsd'], [12 9]) 69 | figure(2) 70 | nice_figure(gcf, [output_folder 'welchpsd'], [12 9]) 71 | figure(3) 72 | nice_figure(gcf, [output_folder 'networkspikes'], [24 9]) 73 | figure(4) 74 | nice_figure(gcf, [output_folder 'nws_ac'], [12 9]) 75 | end 76 | close all 77 | save([output_folder, 'summary.mat'], 'PSDm', 'PSDw', 'nws', 'nws_smo', 'ac', 't_ac', 't_ds', 'fs_ds', 'f_axis', 'wells') 78 | 79 | -------------------------------------------------------------------------------- /Axion/BlockVectorHeaderExtension.m: -------------------------------------------------------------------------------- 1 | classdef BlockVectorHeaderExtension < Entry 2 | %BlockVectorHeaderExtension: Contains additional metadata about a loaded 3 | % Block vector set. 4 | % 5 | % ExtensionVersionMajor: Latest and greatest is currently 1 6 | % 7 | % ExtensionVersionMinor: Latest and greatest is currently 0 8 | % 9 | % DataType Type of the data in this entry 10 | % (BlockVectorDataType.m) 11 | % 12 | % Added: DateTime that this DataSet was last 13 | % Added to the file (DateTime.m) 14 | % 15 | % Modified: DateTime that this DataSet was last 16 | % modified (DateTime.m) 17 | % 18 | % Name: ` Name for this dataset (May be blank) 19 | % 20 | % Description: Textual metadata from axis with processing 21 | % metadata for this dataset 22 | 23 | properties(Constant, GetAccess = private) 24 | MaxNameChar = 50; 25 | end 26 | 27 | properties(GetAccess = public, SetAccess = private) 28 | ExtensionVersionMajor 29 | ExtensionVersionMinor 30 | DataType 31 | Added 32 | Modified 33 | Name 34 | Description 35 | end 36 | 37 | methods(Access = public) 38 | function this = BlockVectorHeaderExtension(aEntryRecord, aFileID) 39 | this = this@Entry(aEntryRecord, int64(ftell(aFileID))); 40 | 41 | this.ExtensionVersionMajor = fread(aFileID, 1, 'uint16=>uint16'); 42 | this.ExtensionVersionMinor = fread(aFileID, 1, 'uint16=>uint16'); 43 | this.DataType = BlockVectorDataType.TryParse(fread(aFileID, 1, 'uint16=>uint16')); 44 | this.Added = DateTime(aFileID); 45 | this.Modified = DateTime(aFileID); 46 | this.Name = deblank(fread(aFileID, ... 47 | (BlockVectorHeaderExtension.MaxNameChar), '*char').'); 48 | this.Description = deblank(fread(aFileID, ... 49 | (this.Start + this.EntryRecord.Length) - ftell(aFileID) , '*char').'); 50 | 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /scripts/corticoid_script.m: -------------------------------------------------------------------------------- 1 | %% Negraes, Gao, Trujillo et al. 2018 2 | %% master script for processing organoid spikes and LFPs 3 | 4 | %% loading data 5 | save_path = '/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/' 6 | %load('/Users/rdgao/Documents/data/Muotri/InfantEEGFeatures/preterm_features.mat') 7 | load('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/aggregate.mat', 'nws_smo', 'T') 8 | load('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/names.mat') 9 | 10 | %% 11 | % grab date information and set parameters 12 | wells = 5:12; 13 | %exclude recordings not done after exchanging media, or pharmacology 14 | exclusion = [5 9 12 15 25 26 34 35 36 40]; 15 | recs = setdiff(1:length(dates),exclusion); 16 | dayVec = zeros(1,length(recs)); 17 | for day = 1:length(recs) 18 | %parse numerical date 19 | date = dates(recs(day)).name(5:end); 20 | disp(date) 21 | dayVec(day) = datenum(str2num(date(5:6)), str2num(date(1:2)), str2num(date(3:4))); 22 | end 23 | recDays = dayVec-dayVec(1)+1; 24 | dayVec = recDays/7+7; %use weeks instead 25 | 26 | %% spike pre-processing 27 | % from raw spiketime data: 28 | % - binarize spikes and get network spiking 29 | 30 | 31 | %% peak finding 32 | % from network spikes (nws_smo), find peaks 33 | fs = 1000; 34 | % MPH = 1; 35 | % MPH_ratio = 0.8; 36 | % MPD = 200; 37 | % toss_thresh_ratio = 0.001; 38 | % call script 39 | %disp('Peak finding & Event Features...') 40 | %ctc_findpeaks % sub-script 41 | 42 | warning('off') 43 | fp_params.MPH = 1; 44 | fp_params.MPH_ratio = 0.8; 45 | fp_params.MPD = 200; 46 | fp_params.toss_thresh_ratio = 0.001; 47 | 48 | for i=1:length(nws_smo) 49 | nws_smo_valid{i} = nws_smo{i}(:,5:12); 50 | end 51 | nws_smo_valid = nws_smo_valid(recs); 52 | [event_feats, pk_times] = compute_peakfeats(nws_smo_valid, fs, fp_params); 53 | %save([save_path, 'event_timing.mat'], 'pktimes', 'IEI*', 'ED*', 'EPH') 54 | 55 | %% grab features for baby eeg classifier analysis 56 | %ctc_eegfeatures % sub-script 57 | load('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/aggregate.mat', 'PSDw'); 58 | %event_feats = load('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/event_timing.mat'); 59 | for i=1:length(PSDw) 60 | psd{i} = PSDw{i}(5:12); 61 | end 62 | psd = psd(recs); 63 | psd_freq = 0:0.5:500; 64 | ctc_EMAfeatures = compute_nEEGfeats(psd_freq, psd, event_feats) 65 | %save([save_path, 'ctc_EMAfeatures.mat'], 'dayVec', 'ctc_EMAfeatures') 66 | 67 | %% grab extra organoid features for self-prediction -------------------------------------------------------------------------------- /scripts/corticoid_NS_2D.m~: -------------------------------------------------------------------------------- 1 | %% neurosphere data 2 | cd('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/neurosphere/') 3 | fig_folder = '~/Dropbox/Research/Reports/2018 - CorticoidOscillation/CorticoidFigs/'; 4 | load LFP_Sp.mat 5 | colors = get(gca,'colororder'); 6 | %% 7 | fs = 12500; 8 | bsp = binarize_spikes(ceil(t_ds), fs, spikes,fs_ds); 9 | nws = (squeeze(sum(bsp,2))'); 10 | nws_smo = tsmovavg(nws','s',50)'; 11 | plot_tight(nws_smo(:,1:end), [3 4], [], [0, 10]) 12 | %% SI figure - example of neurosphere data 13 | well=5; 14 | chan = 6; 15 | XL = [0 60]; 16 | figure 17 | subplot(2,1,1) 18 | spike_raster(t_s,spikes,well) 19 | xlabel('') 20 | set(gca, 'xtick', []) 21 | set(gca, 'ytick',[0,60]) 22 | xlim(XL) 23 | title('Spike Raster') 24 | 25 | % plot network spike 26 | subplot(4,1,3) 27 | plot(t_ds, nws_smo(1:length(t_ds),well), 'k', 'linewidth', 1) 28 | set(gca, 'xtick', []) 29 | ylabel('Spikes') 30 | xlim(XL) 31 | ylim([0 10]) 32 | box off 33 | title('Population Spiking') 34 | 35 | % plot example LFP 36 | subplot(4,1,4) 37 | plot(t_ds, LFP{well}(:,chan), 'linewidth', 1) 38 | xlim(XL) 39 | %set(gca, 'ytick', 0) 40 | %set(gca, 'yticklabel', '0') 41 | xlabel('Time (s)') 42 | ylabel('Voltage') 43 | box off 44 | title('Local Field Potential (LFP)') 45 | %nice_figure(gcf, [fig_folder 'SI_NS_raster'],[6 6]) 46 | %% SI figure - example of neurosphere kernel 47 | MPH = 1; 48 | MPH_ratio = 0.8; 49 | MPD = 200; 50 | toss_thresh_ratio = 0.001; 51 | %[ker_ind] = kernel_findpeak(nws_smo(:,well), [-500, 1000], 0.75, fs_ds); 52 | ker_ind = kernel_findpeak(nws_smo(:,well), MPH, MPH_ratio, MPD, toss_thresh_ratio); 53 | %make smoothed LFP 54 | smo_mask = gausswin(100)./sum(gausswin(100)); 55 | LFP_smo = conv(LFP{well}(:,chan), smo_mask,'same'); 56 | %LFP_smo = LFP{well}(:,chan); 57 | % get LFP kernel 58 | ker = collect_spikes(nws_smo(:,well),[],ker_ind(:,2),[-500 1000]); 59 | lfp_ker = collect_spikes(LFP_smo,[],ker_ind(:,2),[-500 1000]); 60 | 61 | figure 62 | subplot(2,1,1) 63 | plot((-500:1000)/1000, ker, 'color', [0 0 0 0.2], 'linewidth', 1) 64 | ylim([]) 65 | ylabel('Spikes') 66 | subplot(2,1,2) 67 | plot((-500:1000)/1000, lfp_ker, 'color', [colors(1,:) 0.2], 'linewidth', 1) 68 | ylim([-2 2]*1e-5) 69 | xlabel('Time (s)') 70 | ylabel('Voltage (V)') 71 | nice_figure(gcf, [fig_folder 'SI_NS_kernel_scaled'],[3 6]) 72 | 73 | %% 2D 74 | cd('/Users/rgao/Documents/data/Muotri/Pri_Corticoids/2D') 75 | load LFP_Sp.mat 76 | %% 77 | fs = 12500; 78 | bsp = binarize_spikes(ceil(t_ds(end)), fs,spikes,fs_ds); 79 | nws = (squeeze(sum(bsp,2))'); 80 | nws_smo = tsmovavg(nws','s',50)'; 81 | plot_tight(nws_smo(:,1:end), [3 4], [], [0, 10]) 82 | %% 83 | figure 84 | plot(t_ds, LFP{7}(:,6)*1e5) 85 | hold on 86 | plot((0:length(nws_smo)-1)/fs_ds,nws_smo(:,7), 'k') 87 | hold off 88 | spike_raster(t_s,spikes,7) -------------------------------------------------------------------------------- /scripts/corticoid_NS_2D.m: -------------------------------------------------------------------------------- 1 | %% neurosphere data 2 | cd('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/neurosphere/') 3 | fig_folder = '~/Dropbox/Research/Reports/2018 - CorticoidOscillation/CorticoidFigs/'; 4 | load LFP_Sp.mat 5 | colors = get(gca,'colororder'); 6 | %% 7 | fs = 12500; 8 | bsp = binarize_spikes(ceil(t_ds), fs, spikes,fs_ds); 9 | nws = (squeeze(sum(bsp,2))'); 10 | nws_smo = tsmovavg(nws','s',50)'; 11 | plot_tight(nws_smo(:,1:end), [3 4], [], [0, 10]) 12 | %% SI figure - example of neurosphere data 13 | well=5; 14 | chan = 6; 15 | XL = [0 60]; 16 | figure 17 | subplot(2,1,1) 18 | spike_raster(t_s,spikes,well) 19 | xlabel('') 20 | set(gca, 'xtick', []) 21 | set(gca, 'ytick',[0,60]) 22 | xlim(XL) 23 | title('Spike Raster') 24 | 25 | % plot network spike 26 | subplot(4,1,3) 27 | plot(t_ds, nws_smo(1:length(t_ds),well), 'k', 'linewidth', 1) 28 | set(gca, 'xtick', []) 29 | ylabel('Spikes') 30 | xlim(XL) 31 | ylim([0 10]) 32 | box off 33 | title('Population Spiking') 34 | 35 | % plot example LFP 36 | subplot(4,1,4) 37 | plot(t_ds, LFP{well}(:,chan), 'linewidth', 1) 38 | xlim(XL) 39 | %set(gca, 'ytick', 0) 40 | %set(gca, 'yticklabel', '0') 41 | xlabel('Time (s)') 42 | ylabel('Voltage') 43 | box off 44 | title('Local Field Potential (LFP)') 45 | %nice_figure(gcf, [fig_folder 'SI_NS_raster'],[6 6]) 46 | %% SI figure - example of neurosphere kernel 47 | MPH = 1; 48 | MPH_ratio = 0.8; 49 | MPD = 200; 50 | toss_thresh_ratio = 0.001; 51 | %[ker_ind] = kernel_findpeak(nws_smo(:,well), [-500, 1000], 0.75, fs_ds); 52 | ker_ind = kernel_findpeak(nws_smo(:,well), MPH, MPH_ratio, MPD, toss_thresh_ratio); 53 | %make smoothed LFP 54 | smo_mask = gausswin(100)./sum(gausswin(100)); 55 | LFP_smo = conv(LFP{well}(:,chan), smo_mask,'same'); 56 | %LFP_smo = LFP{well}(:,chan); 57 | % get LFP kernel 58 | ker = collect_spikes(nws_smo(:,well),[],ker_ind(:,2),[-500 1000]); 59 | lfp_ker = collect_spikes(LFP_smo,[],ker_ind(:,2),[-500 1000]); 60 | 61 | figure 62 | subplot(2,1,1) 63 | plot((-500:1000)/1000, ker, 'color', [0 0 0 0.2], 'linewidth', 1) 64 | ylim([0 20]) 65 | ylabel('Spikes') 66 | subplot(2,1,2) 67 | plot((-500:1000)/1000, lfp_ker, 'color', [colors(1,:) 0.2], 'linewidth', 1) 68 | ylim([-2 2]*1e-5) 69 | xlabel('Time (s)') 70 | ylabel('Voltage (V)') 71 | nice_figure(gcf, [fig_folder 'SI_NS_kernel_scaled'],[3 6]) 72 | 73 | %% 2D 74 | cd('/Users/rgao/Documents/data/Muotri/Pri_Corticoids/2D') 75 | load LFP_Sp.mat 76 | %% 77 | fs = 12500; 78 | bsp = binarize_spikes(ceil(t_ds(end)), fs,spikes,fs_ds); 79 | nws = (squeeze(sum(bsp,2))'); 80 | nws_smo = tsmovavg(nws','s',50)'; 81 | plot_tight(nws_smo(:,1:end), [3 4], [], [0, 10]) 82 | %% 83 | figure 84 | plot(t_ds, LFP{7}(:,6)*1e5) 85 | hold on 86 | plot((0:length(nws_smo)-1)/fs_ds,nws_smo(:,7), 'k') 87 | hold off 88 | spike_raster(t_s,spikes,7) -------------------------------------------------------------------------------- /Axion/StimulationWaveform.m: -------------------------------------------------------------------------------- 1 | classdef StimulationWaveform < Tag 2 | %STIMULATIONWAVEFORM Storage element for StimulationEventData, before it 3 | %is linked to StimulationEvents 4 | 5 | properties(GetAccess = private, Constant = true) 6 | CurrentVersion = 0; 7 | end 8 | 9 | properties(SetAccess = private) 10 | TagBlocks = StimulationEventData.empty(0); 11 | MicroOps; 12 | end 13 | 14 | methods 15 | function this = StimulationWaveform(aFileID, aRawTag) 16 | this@Tag(aRawTag.TagGuid); 17 | 18 | %Move to the correct location in the file 19 | fStart = aRawTag.Start + TagEntry.BaseSize; 20 | fSeekResult = fseek(aFileID, fStart, 'bof'); 21 | 22 | if(fSeekResult == 0) 23 | fVersion = fread(aFileID, 1, 'uint16=>uint16'); 24 | switch fVersion 25 | case StimulationWaveform.CurrentVersion 26 | fNumBlocks = fread(aFileID, 1, 'uint16=>uint16'); %Reserved short 27 | for fBlock = 1:fNumBlocks 28 | 29 | fId = fread(aFileID, 1, 'uint16=>uint16'); 30 | fread(aFileID, 1, 'uint16=>uint16'); %Type: Unused for now 31 | fStimDuration = fread(aFileID, 1, 'double=>double'); 32 | fArtElimDuration = fread(aFileID, 1, 'double=>double'); 33 | fChannelArrayIdList = fread(aFileID, 2, 'uint16=>uint16'); 34 | if(fChannelArrayIdList(2) == 0) 35 | fChannelArrayIdList = fChannelArrayIdList(1); 36 | end 37 | fDescription = freadstring(aFileID); 38 | this.TagBlocks(fBlock) = StimulationEventData(... 39 | fId, fStimDuration, fArtElimDuration,... 40 | fChannelArrayIdList, fDescription); 41 | end 42 | this.MicroOps = freadstring(aFileID); 43 | otherwise 44 | this.TagBlocks = cell(0); 45 | this.MicroOps = ''; 46 | warning('StimulationWaveform version not supported'); 47 | end 48 | else 49 | error('Encountered an error while loading StimulationWaveform %s', aRawTag.TagGuid); 50 | end 51 | 52 | if ftell(aFileID) > (fStart + aRawTag.EntryRecord.Length) 53 | warning('File may be corrupt'); 54 | end 55 | 56 | end 57 | end 58 | 59 | end 60 | 61 | -------------------------------------------------------------------------------- /Axion/StimulationChannels.m: -------------------------------------------------------------------------------- 1 | classdef StimulationChannels < Tag 2 | %STIMULATIONCHANNELS File data that enumerates channels used in a 3 | %stimulation 4 | 5 | properties(GetAccess = private, Constant = true) 6 | CurrentVersion = 0; 7 | MinArraySize = int64(20); 8 | end 9 | 10 | properties(SetAccess = private) 11 | ChannelGroups 12 | end 13 | 14 | methods 15 | function this = StimulationChannels(aFileID, aRawTag) 16 | this@Tag(aRawTag.TagGuid); 17 | 18 | %Move to the correct location in the file 19 | fStart = int64(aRawTag.Start + TagEntry.BaseSize); 20 | aSeekReult = fseek(aFileID, fStart, 'bof'); 21 | 22 | fTagStart = int64(aRawTag.Start); 23 | fTagEnd = int64(fTagStart + aRawTag.EntryRecord.Length); 24 | 25 | if aSeekReult == 0 26 | fVersion = fread(aFileID, 1, 'uint16=>uint16'); 27 | switch fVersion 28 | case StimulationChannels.CurrentVersion 29 | 30 | fread(aFileID, 1, 'uint16=>uint16'); %Reserved short 31 | this.ChannelGroups = struct([]); 32 | fArray = 1; 33 | fPos = int64(ftell(aFileID)); 34 | while (fTagEnd - fPos) > StimulationChannels.MinArraySize 35 | fId = fread(aFileID, 1, 'uint32=>uint32'); 36 | this.ChannelGroups(fArray).ID = fId; 37 | 38 | fPlateType = fread(aFileID, 1, 'uint32=>uint32'); 39 | this.ChannelGroups(fArray).PlateType = fPlateType; 40 | 41 | fNumChannels = fread(aFileID, 1, 'uint32=>uint32'); 42 | fChannels = arrayfun(@(a)(ChannelMapping(aFileID)),... 43 | 1:fNumChannels,'UniformOutput',false); 44 | this.ChannelGroups(fArray).Mappings = [fChannels{:}]; 45 | 46 | fArray = fArray +1; 47 | fPos = int64(ftell(aFileID)); 48 | end 49 | 50 | otherwise 51 | this.ChannelGroups = cell(0); 52 | warning('Stimulation channels version not suported'); 53 | end 54 | else 55 | error('Encountered an error while loading StimulationChannels %s', aRawTag.TagGuid); 56 | end 57 | 58 | if ftell(aFileID) > (fTagStart + aRawTag.EntryRecord.Length) 59 | warning('File may be corrupt'); 60 | end 61 | 62 | end 63 | end 64 | 65 | end 66 | 67 | -------------------------------------------------------------------------------- /functions/compute_peakfeats.m: -------------------------------------------------------------------------------- 1 | function [event_feats, pktimes] = compute_peakfeats(nws_smo, fs, fp_params) 2 | num_recs = length(nws_smo); 3 | num_wells = size(nws_smo{1},2); 4 | 5 | disp('Peak finding...') 6 | % find peaks and get peak features 7 | pktimes = cell(1,num_recs); 8 | for r_idx = 1:num_recs 9 | % disp(dates(r_idx).name) 10 | for well = 1:num_wells 11 | pktimes{r_idx}{well} = kernel_findpeak(nws_smo{r_idx}(:,well), fp_params.MPH, fp_params.MPH_ratio, fp_params.MPD, fp_params.toss_thresh_ratio); 12 | end 13 | end 14 | 15 | disp('Computing features...') 16 | % compute features 17 | EPH = zeros(num_recs, num_wells); 18 | %recDays_IEI_flat = cell(1,num_recs); 19 | %recDays_ED_flat = cell(1,num_recs); 20 | IEI_flat = cell(1,num_recs); 21 | ED_flat = cell(1,num_recs); 22 | 23 | for r_idx = 1:num_recs 24 | for well=1:num_wells 25 | n_events = size(pktimes{r_idx}{well},1); 26 | % ------- EVENTS PER HOUR ------ % 27 | EPH(r_idx, well) = (n_events/(size(nws_smo{1},1)/fs))*3600; 28 | if n_events>0 29 | % ----------- IEI FEATURES ----------------- 30 | % time vector for regression and stuff 31 | % recDays_IEI_flat{r_idx} = [recDays_IEI_flat{r_idx} recDays(r_idx).*ones(1,n_events-1)]; 32 | 33 | % inter event interval 34 | iei = diff(pktimes{r_idx}{well}(:,2))/fs; 35 | IEI_flat{r_idx} = [IEI_flat{r_idx} iei']; 36 | IEI_mean(r_idx,well) = mean(iei); 37 | IEI_std(r_idx,well) = std(iei); 38 | 39 | % quantile features (for baby EEG) 40 | IEI_rms(r_idx,well) = mean(iei.^2).^0.5; 41 | IEI_50(r_idx, well) = quantile(iei, 0.5); 42 | IEI_05(r_idx, well) = quantile(iei, 0.05); 43 | IEI_95(r_idx, well) = quantile(iei, 0.95); 44 | 45 | % ----------- EVENT DURATION FEATURES ----------------- 46 | % recDays_ED_flat{r_idx} = [recDays_ED_flat{r_idx} recDays(r_idx).*ones(1,n_events)]; 47 | 48 | % event duration 49 | ev_dur = (pktimes{r_idx}{well}(:,3)-pktimes{r_idx}{well}(:,1))/fs; 50 | ED_flat{r_idx} = [ED_flat{r_idx} ev_dur']; 51 | ED_mean(r_idx,well) = mean(ev_dur); 52 | ED_std(r_idx,well) = std(ev_dur); 53 | 54 | ED_rms(r_idx,well) = mean(ev_dur.^2).^0.5; 55 | ED_50(r_idx, well) = quantile(ev_dur, 0.5); 56 | ED_05(r_idx, well) = quantile(ev_dur, 0.05); 57 | ED_95(r_idx, well) = quantile(ev_dur, 0.95); 58 | end 59 | end 60 | end 61 | 62 | % look i'm not proud of this either but it will have to do for now 63 | event_feats = struct(); 64 | event_feats.EPH = EPH; 65 | event_feats.ED_rms = ED_rms; 66 | event_feats.ED_50 = ED_50; 67 | event_feats.ED_05 = ED_05; 68 | event_feats.ED_95 = ED_95; 69 | event_feats.IEI_rms = IEI_rms; 70 | event_feats.IEI_50 = IEI_50; 71 | event_feats.IEI_05 = IEI_05; 72 | event_feats.IEI_95 = IEI_95; 73 | -------------------------------------------------------------------------------- /util_funcs/stft.m: -------------------------------------------------------------------------------- 1 | function [output, output_time, output_f] = stft(timestamp, data, Fs, winLen, stepLen, end_freq) 2 | %function [output, output_time, output_f] = stft(timestamp, data, Fs, winLen, stepLen, end_freq) 3 | % performs short-time windowed fourier transform on data, with a hamming 4 | % window applied. Basically does the same thing as spectrogram.m 5 | % 6 | % timestamp: leave blank [] 7 | % data: time series 8 | % Fs: sampling frequency of the data 9 | % winLen: window length in number of samples (use same number as Fs) 10 | % stepLen: step length in number of samples (use about 1/50 as Fs (or 10 samples) 11 | % end_freq (optional): cut of frequency of stft, default is fs/2 12 | 13 | if isempty(timestamp) 14 | timestamp = linspace(1/Fs,size(data,1)/Fs, size(data,1)); 15 | end 16 | %winLen=Fs; 17 | max_iter = ceil((size(data,1)-winLen)/stepLen); 18 | %preallocate output 19 | output_time = zeros(max_iter,1); 20 | output_f = linspace(0,Fs-Fs/winLen, winLen); 21 | if isempty(end_freq) 22 | end_ind = length(output_f); 23 | else 24 | end_ind = find(output_f>end_freq,1)-1; %find index 25 | end 26 | output_f=output_f(1:end_ind); 27 | 28 | output = zeros(end_ind, size(data,2), max_iter); 29 | H = hamming(winLen); 30 | HAM = repmat(H./sqrt(sum(H.^2)),1,size(data,2)); 31 | 32 | %%%stepping through 33 | for i=1:max_iter 34 | %dc offset 35 | cur_window = data((1:winLen)+(i-1)*stepLen,:)/sqrt(winLen); 36 | cur_window = (cur_window - repmat(mean(cur_window,1),winLen,1)).*HAM; 37 | output_time(i)=timestamp(winLen+(i-1)*stepLen); 38 | F = fft(cur_window); 39 | %use multitaper 40 | %F = pmtm(cur_window,4,winLen,Fs); 41 | output(:,:,i) = (F(1:end_ind,:)); 42 | %output(:,:,i) = abs(F(1:end_ind,:)); 43 | 44 | %output(:,i)=step_features(cur_window, FREQS); 45 | end 46 | end 47 | 48 | 49 | function C = step_features(data_window, FREQS) 50 | eeg = data_window; 51 | F = fft(eeg); 52 | %find frequency with highest power 53 | %[temp maxF] = max(log(abs(F(FREQS(2):55,:)))); %cut at 55Hz to avoid powerline 54 | %F3=(maxF+FREQS(2)-1)'; 55 | 56 | F = F(1:FREQS(3),:); %lowpass 57 | F(60:62,:)=[]; %remove powerline 58 | F=F(FREQS(1):end,:); 59 | spect=log(abs(F)); 60 | F1 = reshape(spect,[],1); 61 | 62 | D=size(F,2); %dimension (num of channels) 63 | F=F./abs(F); %normalize magnitude 64 | 65 | %pairwise phase difference between every channel 66 | C=zeros(size(F,1), sum(1:(D-1))); 67 | ind=1; 68 | for j=1:D 69 | for k=j+1:D 70 | C(:,ind)=F(:,j)./F(:,k); 71 | ind=ind+1; 72 | end 73 | end 74 | F2 = -squeeze(sum(abs(angle(C)),2))/size(C,2); 75 | C = [F1;F2;F3]; 76 | end 77 | 78 | function C = phase_cor(data_window, FREQS) 79 | eeg = data_window; 80 | F = fft(eeg); 81 | F=F(1:101,:); %up to 100hz 82 | 83 | D=size(F,2); %dimension (num of channels) 84 | F=F./abs(F); %normalize magnitude 85 | 86 | %pairwise phase difference between every channel 87 | C=zeros(size(F,1), sum(1:(D-1))); 88 | ind=1; 89 | for j=1:D 90 | for k=j+1:D 91 | C(:,ind)=F(:,j)./F(:,k); 92 | ind=ind+1; 93 | end 94 | end 95 | F2 = -squeeze(sum(abs(angle(C)),2))/size(C,2); 96 | C = F2; 97 | end -------------------------------------------------------------------------------- /util_funcs/plot_tight.m: -------------------------------------------------------------------------------- 1 | function plot_tight(data_array, dim, x_ax, y_ax) 2 | %plot_tight(data_array, dim, x_ax, y_ax) 3 | figure 4 | nrow = dim(1); 5 | ncol = dim(2); 6 | 7 | h = 1/nrow - 0.01; 8 | w = 1/ncol - 0.01; 9 | if isempty(x_ax) 10 | x_ax = 1:size(data_array,1); 11 | end 12 | 13 | 14 | for row = 1:nrow 15 | for col = 1:ncol 16 | %2D dataset 17 | if length(size(data_array))==2 18 | if any(data_array(:, col+(row-1)*ncol)) 19 | subplot('Position', [(col-1)/ncol+0.005, 1-(row)/nrow+0.005 w h]); 20 | plot(x_ax,data_array(:, col+(row-1)*ncol), 'k'); 21 | %loglog(x_ax,data_array(:, col+(row-1)*ncol), 'k'); 22 | xlim([x_ax(1),x_ax(end)]) 23 | if ~isempty(y_ax) 24 | ylim([y_ax(1),y_ax(end)]) 25 | end 26 | end 27 | %Hleg=legend(num2str(col+(row-1)*ncol)); 28 | %set(Hleg,'fontsize',10) 29 | end 30 | 31 | %3D dataset 32 | if length(size(data_array))==3 33 | if any(any(data_array(:,:, col+(row-1)*ncol))) 34 | subplot('Position', [(col-1)/ncol+0.005, 1-(row)/nrow+0.005 w h]); 35 | imagesc(x_ax, y_ax, data_array(:,:,col+(row-1)*ncol)'); 36 | end 37 | end 38 | set(gca,'XTick',[]);set(gca,'YTick',[]); set(gca, 'ydir', 'normal'); 39 | end 40 | end 41 | % 42 | % 43 | % d1=dim(1); 44 | % d2=dim(2); 45 | % for i=1:d1 46 | % for j=1:d2 47 | % 48 | % 49 | % %2D dataset 50 | % if length(size(data_array))==2 51 | % if any(data_array(:,j+(i-1)*d2)) 52 | % subplot('Position', [mod(i-1,d1)/d1+0.005, 1-(mod(j-1,d2)/d2)-1/d2+0.005, 1/d1-0.008, 1/d2-0.008 ]); 53 | % if isempty(x_ax) 54 | % plot(data_array(:,j+(i-1)*d2)); 55 | % else 56 | % plot(x_ax, data_array(:,j+(i-1)*d2)); 57 | % %bar(x_ax, data_array(:,j+(i-1)*d2)); 58 | % end 59 | % %xlim([x_ax(1),x_ax(end)]) 60 | % %ylim([min(data_array(:,j+(i-1)*d2)), max(data_array(:,i+(j-1)*d2))]); 61 | % %ylim([0 1]) 62 | % end 63 | % end 64 | % 65 | % %3D dataset 66 | % if length(size(data_array))==3 67 | % if any(data_array(:,:,i+(j-1)*d2)) 68 | % subplot('Position', [mod(i-1,d1)/d1+0.005, 1-(mod(j-1,d2)/d2)-1/d2+0.005, 1/d1-0.008, 1/d2-0.008 ]); 69 | % 70 | % imagesc(x_ax, y_ax, data_array(:,:,i+(j-1)*d2)); %transpose because MI somehow got stored backwards, should fix 71 | % 72 | % %for SFC (wrapped phase) 73 | % %imagesc(x_ax, y_ax, [data_array(:,:,i+(j-1)*d2) data_array(:,:,i+(j-1)*d2)]); %transpose because MI somehow got stored backwards, should fix 74 | % 75 | % caxis([0 0.3]) 76 | % end 77 | % end 78 | % set(gca,'XTick',[]);set(gca,'YTick',[]); set(gca, 'ydir', 'normal'); 79 | % end 80 | % end 81 | % end -------------------------------------------------------------------------------- /Axion/CRC32.m: -------------------------------------------------------------------------------- 1 | classdef CRC32 < handle 2 | %CRC32 Class for calculating CRC32s 3 | % For Quick Reference, see http://en.wikipedia.org/wiki/Cyclic_redundancy_check 4 | % CRC32 implementation adapted from: http://damieng.com/blog/2006/08/08/calculating_crc32_in_c_and_net 5 | 6 | 7 | properties (SetAccess = private, GetAccess = private) 8 | table 9 | end 10 | 11 | properties (Constant = true, GetAccess = public) 12 | DefaultPolynomial = hex2dec('edb88320'); 13 | DefaultSeed = hex2dec('ffffffff'); 14 | end 15 | 16 | properties (SetAccess = private, GetAccess = public) 17 | Polynomial 18 | Seed 19 | Hash 20 | end 21 | 22 | methods (Access = public) 23 | 24 | function this = CRC32(aPolynomial, aSeed) 25 | if nargin < 2 26 | aSeed = CRC32.DefaultSeed; 27 | end 28 | if nargin < 1 29 | aPolynomial = CRC32.DefaultPolynomial; 30 | end 31 | this.Polynomial = cast(aPolynomial, 'uint32'); 32 | this.Seed = cast(aSeed, 'uint32'); 33 | this.table = CRC32.InitializeTable(this.Polynomial); 34 | this.Initialize(); 35 | end 36 | 37 | function Initialize(this) 38 | this.Hash = cast(this.Seed, 'uint32'); 39 | end 40 | 41 | function crc = Compute(this, bytes, start, size) 42 | if nargin < 3 43 | start = 1; 44 | end 45 | if nargin < 4 46 | size = length(bytes) - start + 1; 47 | end 48 | crc = bitxor(... %This is Just a bitwise not 49 | CRC32.DefaultSeed,... 50 | this.CalculateHash(this.table, this.Seed, bytes, start, size)); 51 | end 52 | 53 | end 54 | 55 | methods (Access = private, Static = true) 56 | 57 | function createTable = InitializeTable(aPolynomial) 58 | 59 | polynomial = cast(aPolynomial, 'uint32'); 60 | createTable = cast(zeros(256, 0), 'uint32'); 61 | 62 | for i = 0 : 255 63 | entry = cast(i, 'uint32'); 64 | for j = 0 : 7 65 | if bitand(entry, uint32(1)) == uint32(1) 66 | entry = bitxor( (bitshift(entry, -1)) , polynomial); 67 | else 68 | entry = (bitshift(entry, -1)); 69 | end 70 | 71 | end 72 | createTable(i + 1) = entry; 73 | end 74 | end 75 | 76 | function crc = CalculateHash(table, seed, buffer, start, size) 77 | crc = seed; 78 | crcspace = (1:size) + (start - 1); 79 | for i = crcspace 80 | lookup = cast( bitand(... 81 | bitxor(buffer(i) , crc), ... 82 | 255), 'uint8'); 83 | crc = bitxor(... 84 | bitshift(crc , -8) , ... 85 | table(uint16(lookup) + 1)); 86 | end 87 | end 88 | end 89 | end 90 | 91 | -------------------------------------------------------------------------------- /functions/MEA_process.m: -------------------------------------------------------------------------------- 1 | function MEA_process(data_folder, wells2process) 2 | % MEA_process(data_folder, wells2process) 3 | % data_folder: file path of folder where raw .mat well data is stored 4 | % wells2process: wells to process further 5 | % MEA_process takes groups of raw time series and does the following: 6 | % - downsample to get LFP at 1000Hz 7 | % - detect and grab spikes 8 | % really this could be applied to high sampling rate data in general, but 9 | % for now we keep it to MEA data 10 | 11 | %analysis parameters------------------------------------------------------- 12 | % currently only works for 12 well plates 13 | num_well = 12; 14 | num_chan = 64; 15 | fs = 12500; 16 | fs_ds = 1000; 17 | sp_freq = [300,3000]; 18 | auto_spike = 1; 19 | std_thr = 5.5; %5.5 std for spike detection 20 | spike_len = 12; 21 | wells = wells2process; 22 | 23 | %LFP, spike detection & MUA----------------------------------------------------- 24 | spikes = cell(num_well,num_chan); 25 | spike_cnt = zeros(num_well,num_chan); 26 | spike_shape = cell(num_well,num_chan); 27 | spike_avg = zeros(num_well,num_chan,spike_len*2+1); 28 | LFP = cell(num_well,1); 29 | 30 | %filtering 31 | for well=wells 32 | f=[data_folder, '/well_' num2str(well) '.mat']; 33 | disp(['Loading... ',f]) 34 | if exist(f, 'file') 35 | load(f) 36 | 37 | % downsampling LFP to 1000Hz 38 | disp('Downsampling... ') 39 | LFP{well} = resample(MEA,2,25); 40 | 41 | % spike detection 42 | % first apply median well filter 43 | % find all 0 channels, exclude those in median calc 44 | mask = 1-all(MEA==0); 45 | well_med = median(MEA(:,find(mask)),2); 46 | % re-reference only the channels that have data 47 | MEA = MEA - well_med*mask; 48 | 49 | disp('Filtering... ') 50 | % filtering and do spike detection 51 | filtered = butterpass(MEA,fs,sp_freq,3); 52 | try 53 | [spikes(well,:), spike_cnt(well,:)] = spike_detect_abs(filtered,fs,std_thr); 54 | catch 55 | keyboard 56 | end 57 | 58 | % grab spike waveforms and compute average spike shape 59 | for chan = 1:num_chan 60 | spike_shape{well,chan} = collect_spikes(filtered,[],spikes{well,chan},spike_len); 61 | %.*repmat(-sign(spike_shape{well,chan}(spike_len+1,:)),spike_len*2+1,1) 62 | 63 | if ~isempty(spike_shape{well,chan}) 64 | % get average spike waveform 65 | % spike_avg(well,chan,:) = mean(spike_shape{well,chan},2); 66 | 67 | % little matrix trick to get the average: taking the inner 68 | % product between spike shape and its polarity flips all 69 | % the spikes to the same direction 70 | spike_avg(well,chan,:) = -sign(spike_shape{well,chan}(spike_len+1,:))*spike_shape{well,chan}'; 71 | end 72 | end 73 | 74 | else 75 | disp('Non-existent well, skip.') 76 | end 77 | end 78 | 79 | t_s = t; 80 | t_ds = (t(1):(1/fs_ds):t(end))'; 81 | final_output = [data_folder, '/LFP_Sp.mat']; 82 | save(final_output, 'LFP', 't_ds', 'fs_ds', 'spikes', 'spike_shape', 'spike_avg', 'spike_cnt', 't_s', '-v7.3') 83 | -------------------------------------------------------------------------------- /Axion/Waveform.m: -------------------------------------------------------------------------------- 1 | classdef Waveform 2 | %WAVEFORM Container for single dimensional recorded sample data 3 | % 4 | % Channel: Source Location of the waveform 5 | % 6 | % Start: Time(In Seconds) of Recording Start 7 | % 8 | % Data: Sample data 9 | % 10 | % Source: BlockVectorDataSet that contains this Waveform 11 | 12 | properties(GetAccess = public, SetAccess = protected) 13 | Channel; 14 | Start; 15 | Data; 16 | Source; 17 | end 18 | 19 | methods 20 | 21 | function this = Waveform(aChannel, aStart, aData, aSource) 22 | 23 | if(nargin == 0) 24 | return; 25 | end 26 | 27 | if(~isa(aChannel,'ChannelMapping')) 28 | error(['Waveform: Unexpected Argument for aChannel: ' aChannel]); 29 | end 30 | 31 | if(~isa(aSource,'BlockVectorSet')) 32 | error(['Waveform: Unexpected Argument for aSource: ' aSource]); 33 | end 34 | 35 | this.Channel = aChannel; 36 | this.Start = aStart; 37 | this.Data = aData; 38 | this.Source = aSource; 39 | end 40 | 41 | function [timeData, voltageData] = GetTimeVoltageVector(this) 42 | %GetTimeVoltageVector: Returns a vector for time and voltage 43 | % for this waveform in a single call 44 | timeData = this.GetTimeVector(); 45 | voltageData = this.GetVoltageVector(); 46 | end 47 | 48 | function voltageData = GetVoltageVector(this) 49 | % GetTimeVector: returns a voltage vector for this waveform based 50 | % on the uncasted sample data (Stored as int16) and the source 51 | % header's specified voltage scale 52 | % 53 | % If this Method is called on an array of waveforms, the 54 | % lengths of the waveforms MUST agree 55 | fData = double([this(:).Data]); 56 | fSource = [this(:).Source]; 57 | fHeader = [fSource(:).Header]; 58 | fVoltageScale = [fHeader(:).VoltageScale]; 59 | voltageData = fData * diag(fVoltageScale); 60 | end 61 | 62 | function timeData = GetTimeVector(this) 63 | % GetTimeVector: returns a time vector for this waveform based 64 | % on the Start time, Length of the data, and the Sampling 65 | % Frequency of the source header 66 | % 67 | % If this Method is called on an array of waveforms, the 68 | % lengths of the waveforms MUST agree 69 | fSource = [this(:).Source]; 70 | fHeader = [fSource(:).Header]; 71 | fSamplingPeriod = 1./[fHeader(:).SamplingFrequency]; 72 | 73 | timeData = repmat((0 : (length(this(1).Data) - 1))', 1,length(this)); 74 | timeData = timeData * diag(fSamplingPeriod); 75 | 76 | fStart = ones(size(timeData)); 77 | fStart = fStart * diag([this(:).Start]); 78 | 79 | timeData = timeData + fStart; 80 | end 81 | 82 | end 83 | 84 | end 85 | 86 | -------------------------------------------------------------------------------- /Axion/Note.m: -------------------------------------------------------------------------------- 1 | classdef Note < Entry 2 | %Note Container class for Axis File notes 3 | % 4 | % Investigator: Text data taken from 'Investigator' field of the Axis 5 | % GUI 6 | % 7 | % ExperimentID: Text data taken from 'Experiment ID' field of the Axis 8 | % GUI 9 | % 10 | % Description: Text data taken from 'Description' field of the Axis 11 | % GUI 12 | % 13 | % Revision: Number of revisions this note has experienced 14 | % 15 | % RevisionDate: Date this note was last revised (See DateTime.m) 16 | 17 | properties (Constant = true, GetAccess = public) 18 | SIZE = 618; 19 | end 20 | 21 | properties (Constant = true, GetAccess = private) 22 | %Constants for offsets and sizes in binary notes entries. 23 | ExperimentIDOffset = 50; 24 | DescriptionOffset = 100; 25 | RevisionOffset = 600; 26 | InvestigatorLength = 50; 27 | ExperimentIDLength = 50; 28 | DescriptionLength = 500; 29 | end 30 | 31 | properties (GetAccess = public, SetAccess = private) 32 | Investigator 33 | ExperimentID 34 | Description 35 | Revision 36 | RevisionDate 37 | end 38 | 39 | methods 40 | function this = Note(aEntryRecord, aFileID) 41 | this = this@Entry(aEntryRecord, int64(ftell(aFileID))); 42 | 43 | if(nargin == 0) 44 | return 45 | end 46 | 47 | this.Investigator = deblank(fread(aFileID, Note.InvestigatorLength, '*char').'); 48 | % strip '\r' characters so that lines aren't double-spaced 49 | this.Investigator(this.Investigator==13)=[]; 50 | 51 | fseek(aFileID, this.Start + Note.ExperimentIDOffset, 'bof'); 52 | this.ExperimentID = deblank(fread(aFileID, Note.ExperimentIDLength, '*char').'); 53 | % strip '\r' characters so that lines aren't double-spaced 54 | this.ExperimentID(this.ExperimentID==13)=[]; 55 | 56 | fseek(aFileID, this.Start + Note.DescriptionOffset, 'bof'); 57 | this.Description = deblank(fread(aFileID, Note.DescriptionLength, '*char').'); 58 | % strip '\r' characters so that lines aren't double-spaced 59 | this.Description(this.Description==13)=[]; 60 | 61 | fseek(aFileID, this.Start + Note.RevisionOffset, 'bof'); 62 | this.Revision = fread(aFileID, 1, 'uint32=>uint32'); 63 | this.RevisionDate = DateTime(aFileID); 64 | 65 | if(ftell(aFileID) ~= (this.Start + this.EntryRecord.Length)) 66 | error('Unexpected BlockVectorHeader length') 67 | end 68 | 69 | end 70 | end 71 | 72 | methods(Static = true) 73 | function array = ParseArray(aEntryRecord, aFileID) 74 | fCount = aEntryRecord.Length / Note.SIZE; 75 | array = Note.empty(0,fCount); 76 | for i = 1 : fCount 77 | fEntryRecord = EntryRecord(EntryRecordID.NotesArray, Note.SIZE); 78 | array(i) = Note(fEntryRecord, aFileID); 79 | end 80 | end 81 | end 82 | end -------------------------------------------------------------------------------- /Axion/Tag.m: -------------------------------------------------------------------------------- 1 | classdef Tag < handle 2 | %TAG Base class of user genreated metadata for a file 3 | % Currently, AxionFileTags include Annotation (from the play bar), 4 | % StimulationEvent (when stimulations occur), and WellInformation 5 | % (from the Plate Map Editor). 6 | 7 | properties(GetAccess = private, SetAccess = private) 8 | EntryNodes; 9 | end 10 | 11 | properties(GetAccess = public, SetAccess = private) 12 | % TagGuid: Unique GUID for this tag and its revision history. 13 | % When a new tag is added to a TagCollection, if its GUID matches 14 | % that of an existing tag, that tag is updated with the new tag as a revision. 15 | TagGuid; 16 | 17 | % HeadRevisionNumber: Each Revision of a tag by axis has a new, 18 | % iterated revison number. This is the most recent tag's revision 19 | % number 20 | HeadRevisionNumber; 21 | 22 | %Type: The TypeID attributed to this tag 23 | Type; 24 | end 25 | 26 | methods 27 | function this = Tag(aGuid) 28 | this.TagGuid = aGuid; 29 | this.HeadRevisionNumber = -1; 30 | this.EntryNodes = TagEntry.empty(0,1); 31 | end 32 | 33 | function new = Promote(this, aFileId) 34 | %%% Promote: 35 | % Converts a base tag to the type that is dictated by its Type 36 | % property. Note that the returned object is a new instance 37 | [~,idx] = sort(arrayfun(@(a)(a.RevisionNumber), this.EntryNodes)); 38 | fEntryNodes = this.EntryNodes(idx); 39 | fHead = fEntryNodes(end); 40 | switch fHead.Type 41 | case TagType.UserAnnotation 42 | new = Annotation(aFileId, fHead); 43 | case TagType.SystemAnnotation 44 | new = Annotation(aFileId, fHead); 45 | case TagType.WellTreatment 46 | new = WellInformation(aFileId, fHead); 47 | case TagType.StimulationEvent 48 | new = StimulationEvent(aFileId, fHead); 49 | case TagType.StimulationChannelGroup 50 | new = StimulationChannels(aFileId, fHead); 51 | case TagType.StimulationWaveform 52 | new = StimulationWaveform(aFileId, fHead); 53 | case TagType.CalibrationTag 54 | %For Calibration Tags, No Additonal Parsing supported 55 | new = Tag(this.TagGuid); 56 | otherwise 57 | new = Tag(this.TagGuid); 58 | if(this.Type ~= TagType.Deleted) 59 | warning('Unknown Tag Type found. Is this loader out of date?'); 60 | end 61 | end 62 | new.EntryNodes = fEntryNodes; 63 | new.HeadRevisionNumber = fHead.RevisionNumber; 64 | new.Type = fHead.Type; 65 | end 66 | 67 | function AddNode(this, aNode) 68 | %%% AddNode 69 | % Adds a TagEntry to the revision history of this Tag series 70 | this.EntryNodes = [this.EntryNodes; aNode]; 71 | %sort by revison number 72 | [this.HeadRevisionNumber, idx] = max(arrayfun(@(a)(a.RevisionNumber), this.EntryNodes)); 73 | this.Type = this.EntryNodes(idx).Type; 74 | end 75 | end 76 | 77 | end 78 | 79 | -------------------------------------------------------------------------------- /util_funcs/spike_detect.m: -------------------------------------------------------------------------------- 1 | function [spikes spike_shapes] = spike_detect(data,fs,auto_detect) 2 | %function [spikes spike_shapes] = spike_detect(data,auto_detect) 3 | % data: time X chan (filtered) 4 | % auto_detect: whether to use auto threshold (5.5std) or manual 5 | mpd = 0.002*fs; %min peak distance 2ms 6 | winlen = 0.004*fs; %spike shape, 8ms spike-centered window 7 | 8 | [len, numchan] = size(data); 9 | spikes = cell(numchan,3); 10 | spike_shapes = cell(numchan,3); 11 | for chan = 1:numchan 12 | thr1 = median(abs(data(:,chan))/0.6745)*5; %robust estimation by Q.Quiroga 13 | thr2 = median(abs(data(:,chan))/0.6745)*5.5; 14 | 15 | spikes{chan,3}=zeros(1,2); 16 | spike_shapes{chan,3} = zeros(1,2); 17 | 18 | auto_thr = thr2; %5.5 std 19 | if auto_detect 20 | %auto-detect at 5.5 std 21 | if max(data(:,chan))>auto_thr 22 | %skip if channel does not go above auto_thr 23 | [p spikes{chan,1}] = findpeaks(data(:,chan),'minpeakheight',auto_thr, 'minpeakdistance',mpd); %peak spikes 24 | spike_shapes{chan,1} = collect_spikes(data(:,chan),[],spikes{chan,1},winlen); %peak spike shape 25 | end 26 | if min(data(:,chan))<-auto_thr 27 | [p spikes{chan,2}] = findpeaks(-data(:,chan),'minpeakheight',auto_thr, 'minpeakdistance',mpd); %trough spikes 28 | spike_shapes{chan,2} = collect_spikes(data(:,chan),[],spikes{chan,2},winlen); %trough spike shape 29 | end 30 | spikes{chan,3} = [length(spikes{chan,1}) length(spikes{chan,2})]; %num of spikes 31 | spike_shapes{chan,3} = spikes{chan,3}; %num spikes 32 | else 33 | %manual-detect, thresholding with pre-cutoff at 5.5 std 34 | if max(data(:,chan))>thr2 | min(data(:,chan))<-thr2 35 | %only display if signal crosses threshold 36 | figure 37 | plot(data(:,chan)); 38 | line([ones(1,4); len*ones(1,4)], [thr1 -thr1 thr2 -thr2; thr1 -thr1 thr2 -thr2]); 39 | title(chan) 40 | set(gcf, 'Position', get(0,'Screensize')); % Maximize figure 41 | [x, y] = ginput(2); 42 | if isempty(y) 43 | %no selected spikes 44 | spikes{chan,1}=[]; 45 | spikes{chan,2}=[]; 46 | spikes{chan,3}=zeros(1,2); 47 | else 48 | %first thr is peak, second is trough 49 | if y(2)>y(1); y = flipud(y); end; 50 | [p spikes{chan,1}] = findpeaks(data(:,chan),'minpeakheight',y(1), 'minpeakdistance',mpd); 51 | [p spikes{chan,2}] = findpeaks(-data(:,chan),'minpeakheight',-y(2), 'minpeakdistance',mpd); 52 | spikes{chan,3} = [length(spikes{chan,1}) length(spikes{chan,2})]; 53 | 54 | spike_shapes{chan,1} = collect_spikes(data(:,chan),[],spikes{chan,1},winlen); 55 | spike_shapes{chan,2} = collect_spikes(data(:,chan),[],spikes{chan,2},winlen); 56 | for i=1:2 57 | if spikes{chan,3}(i)>0 58 | subplot(1,2,i) 59 | plot(mean(spike_shapes{chan,i},2), 'linewidth',2) 60 | hold on 61 | plot(mean(spike_shapes{chan,i},2)+std(spike_shapes{chan,i},0,2), 'color', 'r') 62 | hold off 63 | title(spikes{chan,3}(i)) 64 | end 65 | end 66 | pause 67 | end 68 | spike_shapes{chan,3} = spikes{chan,3}; 69 | close all 70 | else 71 | disp(sprintf('Skipped Channel: %i',chan)); 72 | end 73 | end 74 | end -------------------------------------------------------------------------------- /Axion/StimulationEvent.m: -------------------------------------------------------------------------------- 1 | classdef StimulationEvent < EventTag 2 | %STIMULATIONEVENT Event data that corresponds to a tagged stimulation 3 | %that occurred in the file 4 | 5 | properties(GetAccess = private, Constant = true) 6 | CurrentVersion = 0; 7 | end 8 | 9 | properties(GetAccess = private, SetAccess = private) 10 | SequenceNumber; 11 | WaveformTag; 12 | ChannelsTag; 13 | end 14 | 15 | properties(SetAccess = private) 16 | PlateType; 17 | Electrodes; 18 | EventData; 19 | end 20 | 21 | methods 22 | function this = StimulationEvent(aFileID, aRawTag) 23 | this@EventTag(aFileID, aRawTag) 24 | %Assume EventTag leaves us at the correct location in the file 25 | 26 | fVersion = fread(aFileID, 1, 'uint16=>uint16'); 27 | switch fVersion 28 | case StimulationEvent.CurrentVersion 29 | fread(aFileID, 1, 'uint16=>uint16');%Reserved 30 | 31 | this.WaveformTag = parseGuid(fread(aFileID, 16, 'uint8=>uint8')); 32 | this.ChannelsTag = parseGuid(fread(aFileID, 16, 'uint8=>uint8')); 33 | 34 | this.EventData = fread(aFileID, 1, 'uint16=>uint16'); 35 | this.SequenceNumber = fread(aFileID, 1, 'uint16=>uint16'); 36 | otherwise 37 | this.WaveformTag = ''; 38 | this.ChannelsTag = ''; 39 | this.EventData = uint16(hex2dec('FFFF')); 40 | this.SequenceNumber = uint16(hex2dec('FFFF')); 41 | warning('Stimulation Event version not supported'); 42 | end 43 | 44 | fStart = aRawTag.Start + TagEntry.BaseSize; 45 | if ftell(aFileID) > (fStart + aRawTag.EntryRecord.Length) 46 | warning('File may be corrupt'); 47 | end 48 | end 49 | 50 | function Link(this, aTagMap) 51 | if ~isa(aTagMap,'containers.Map') 52 | error('Link should be called with a map'); 53 | end 54 | if(aTagMap.isKey(this.WaveformTag)) 55 | this.WaveformTag = aTagMap(this.WaveformTag); 56 | else 57 | warning('Missing Stimulation Waveform Tag: %s', this.WaveformTag); 58 | end 59 | if(aTagMap.isKey(this.ChannelsTag)) 60 | this.ChannelsTag = aTagMap(this.ChannelsTag); 61 | else 62 | warning('Missing Stimulation Channels Tag: %s', this.WaveformTag); 63 | end 64 | 65 | if isa(this.WaveformTag,'StimulationWaveform') && isa(this.ChannelsTag,'StimulationChannels') 66 | fEventDatas = this.WaveformTag.TagBlocks; 67 | fChannels = this.ChannelsTag.ChannelGroups; 68 | this.EventData = fEventDatas(find(arrayfun(@(a)(a.ID) == this.EventData, fEventDatas),1)); 69 | 70 | this.Electrodes = arrayfun(... 71 | @(aChanId)(fChannels(find(arrayfun(@(a)(a.ID) == aChanId, fChannels),1))),... 72 | this.EventData.ChannelArrayIdList); 73 | 74 | this.PlateType = unique(arrayfun(@(a)(a.PlateType), this.Electrodes)); 75 | this.Electrodes = arrayfun(@(a)(a.Mappings), this.Electrodes, 'UniformOutput', false); 76 | 77 | if length(this.Electrodes) == 1 78 | this.Electrodes = this.Electrodes{1}; 79 | end 80 | end 81 | end 82 | end 83 | 84 | end 85 | 86 | -------------------------------------------------------------------------------- /Axion/EntryRecord.m: -------------------------------------------------------------------------------- 1 | classdef EntryRecord 2 | %ENTRYRECORD Structure representing an area of data in an Axis file 3 | % 4 | % Type: Type of data in the associated entry. See: EntryRecordID.m 5 | % 6 | % Length: Length (in bytes) of the associated data entry. 7 | % 8 | 9 | properties(Constant, GetAccess = private) 10 | LENGTH_MASK_HIGH = uint64(hex2dec( 'ffffff')); 11 | LENGTH_MASK_LOW = uint64(hex2dec('ffffffff')); 12 | 13 | end 14 | 15 | properties(GetAccess = public, SetAccess = private) 16 | Type 17 | Length 18 | end 19 | 20 | methods 21 | function this = EntryRecord(aType, aLength) 22 | if nargin > 0 23 | this.Type = EntryRecordID(aType); 24 | if(isinf(aLength) == 1) 25 | this.Length = inf; 26 | else 27 | this.Length = int64(aLength); 28 | end 29 | end 30 | end 31 | end 32 | 33 | methods(Static = true) 34 | 35 | function this = FromUint64(aValues) 36 | % FromUint64: Deserailizes an entry record from its native 64 37 | % bit format in AxIS files. 38 | % 39 | % ----------------64 Bits-------------- 40 | % | ID (1 Byte) | Length (7 Bytes) | 41 | % ------------------------------------- 42 | % 43 | % Note that only the last entry in a file amy have length == 44 | % (0x00ff ffff ffff ffff) which denotes an entry that reads to 45 | % the end of the file. These entrist have a length feild == inf 46 | % when deserialized 47 | % 48 | this = EntryRecord.empty(0, length(aValues)); 49 | 50 | if(~isa(aValues, 'uint64')) 51 | error('EntryRecord.FromUint64: aValues must be of type uint64)'); 52 | end 53 | 54 | for i= 1:length(aValues) 55 | long = aValues(i); 56 | % Read the upper word (with ID feild) 57 | [fID, fHaveParser] = EntryRecordID.TryParse(bitshift(long, int8(8-64))); 58 | % Shift right 4 bytes and mask with LENGTH_MASK_HIGH 59 | fLength = uint64(bitand(bitshift(long, int8(32-64)), EntryRecord.LENGTH_MASK_HIGH)); 60 | % Start the check to see if this may be a 'Read to the end' 61 | % style EntryRecord 62 | fIsinf = fLength == EntryRecord.LENGTH_MASK_HIGH; 63 | % Shift left 4 bytes to be andded with lower word 64 | fLength = uint64(bitshift(fLength, 32)); 65 | 66 | % Read the lower word 67 | fLowWord = uint64(bitand(long, EntryRecord.LENGTH_MASK_LOW)); 68 | % Finish the check to see if this may be a 'Read to the end' 69 | % style EntryRecord 70 | fIsinf = fIsinf && (fLowWord == EntryRecord.LENGTH_MASK_LOW); 71 | 72 | %Recombine the upper and lower length portions. 73 | fLength = int64(bitor(... 74 | fLength,... 75 | fLowWord)); 76 | 77 | % If we don't know this Entry record type, read it as 78 | % skipped 79 | if(~fHaveParser) 80 | fID = EntryRecordID.Skip; 81 | end 82 | 83 | if(fIsinf) 84 | this(i) = EntryRecord(fID, inf); 85 | else 86 | this(i) = EntryRecord(fID, fLength); 87 | end 88 | end 89 | end 90 | end 91 | end 92 | 93 | -------------------------------------------------------------------------------- /Axion/WellInformation.m: -------------------------------------------------------------------------------- 1 | classdef WellInformation < Tag 2 | %WELLINFORMATION Class that describes the platemap data for a single well 3 | 4 | properties(GetAccess = public, SetAccess = private) 5 | 6 | % location bytes that are currently irrelevant 7 | WellColumn; 8 | WellRow; 9 | 10 | % IsOn: Active state of the well 11 | IsOn; 12 | % IsControl: True if this well was intended to be a control 13 | IsControl; 14 | 15 | %0 - 255 RGB values for the displayed color 16 | Red; 17 | Green; 18 | Blue; 19 | 20 | % Unbounded treatment identification string for a well's treatment 21 | TreatmentWhat; 22 | 23 | % Unbounded string for additional comments from the user about the well treatment 24 | AdditionalInformation; 25 | 26 | % Number of the "how much", normalized to the TreatmentHowMuchBaseUnit 27 | TreatmentHowMuchBaseValue; 28 | 29 | % Numeric representation of the exponent applied by an SI unit prefix 30 | % e.g. in "30 uM", TreatmentHowMuchUnitExponent = 6 because 1e6 is used to 31 | % normalizes the stored 30e-6 in TreatmentHowMuchBaseValue to 30. 32 | TreatmentHowMuchUnitExponent; 33 | 34 | 35 | % String contains the user entered base unit e.g. the 'M' in "nM" 36 | % or the "Hz" in "kHz" 37 | TreatmentHowMuchBaseUnit; 38 | 39 | end 40 | 41 | methods 42 | function this = WellInformation(aFileID, aRawTag) 43 | this@Tag(aRawTag.TagGuid); 44 | 45 | %Move to the correct location in the file 46 | fStart = aRawTag.Start + TagEntry.BaseSize; 47 | fSeekResult = fseek(aFileID, fStart, 'bof'); 48 | 49 | if(fSeekResult == 0) 50 | this.WellColumn = fread(aFileID, 1, 'uint8=>uint8'); 51 | this.WellRow = fread(aFileID, 1, 'uint8=>uint8'); 52 | fElectrodeColumn = fread(aFileID, 1, 'uint8=>uint8'); 53 | fElectrodeRow = fread(aFileID, 1, 'uint8=>uint8'); 54 | 55 | %Electrode position should alweays be broadcast to well 56 | %here 57 | if fElectrodeColumn ~= 0 || fElectrodeRow ~= 0 58 | warning('File may be corrupt'); 59 | end 60 | 61 | fWellType = fread(aFileID, 1, 'uint8=>uint8'); 62 | if bitand(fWellType, 1) ~= 0; 63 | this.IsOn = true; 64 | else 65 | this.IsOn = false; 66 | end 67 | 68 | if bitand(fWellType, 2) ~= 0; 69 | this.IsControl = true; 70 | else 71 | this.IsControl = false; 72 | end 73 | 74 | this.Red = fread(aFileID, 1, 'uint8=>uint8'); 75 | this.Green = fread(aFileID, 1, 'uint8=>uint8'); 76 | this.Blue = fread(aFileID, 1, 'uint8=>uint8'); 77 | 78 | %User Treatment Data 79 | this.TreatmentWhat = freadstring(aFileID); 80 | this.AdditionalInformation = freadstring(aFileID); 81 | this.TreatmentHowMuchBaseValue = fread(aFileID, 1, 'double=>double'); 82 | this.TreatmentHowMuchUnitExponent = fread(aFileID, 1, 'int8=>int8'); 83 | this.TreatmentHowMuchBaseUnit = freadstring(aFileID); 84 | 85 | if ftell(aFileID) > (fStart + aRawTag.EntryRecord.Length) 86 | warning('File may be corrupt'); 87 | end 88 | else 89 | error('Encountered an error while loading StimulationChannels %s', aRawTag.TagGuid); 90 | end 91 | end 92 | end 93 | end 94 | 95 | -------------------------------------------------------------------------------- /Axion/PlateTypes.m: -------------------------------------------------------------------------------- 1 | classdef PlateTypes 2 | %PlateTypes finds dimentions for known plate types 3 | % This class has to be modified every time a new plate type is added 4 | % to AxIS 5 | 6 | properties(Constant, Access=public) 7 | % All the channels of a Muse, in artichoke order 8 | LinearSingleWell64 = uint32(hex2dec('0400000')); 9 | 10 | % Muse single-well plate 11 | P200D30S = uint32(hex2dec('0400001')); 12 | 13 | % All the channels of a Maestro, in artichoke order 14 | LinearTwelveWell = uint32(hex2dec('3000000')); 15 | 16 | % Standard Maestro 12 well plate 17 | TwelveWell = uint32(hex2dec('3000001')); 18 | 19 | % Opaque Maestro 48 well plate 20 | FortyEightWell = uint32(hex2dec('3000002')); 21 | 22 | % Standard Maestro 96 well plate 23 | NinetySixWell = uint32(hex2dec('3000003')); 24 | 25 | % Transparent Maestro 48 well plate 26 | FortyEightWellTransparent = uint32(hex2dec('3000004')); 27 | 28 | % Standard Maestro 384 well plate 29 | ThreeEightyFourWell = uint32(hex2dec('3000005')); 30 | end 31 | 32 | properties (Constant, Access=private) 33 | 34 | MUSE_MASK = uint32(hex2dec('0400000')); 35 | MAESTRO_MASK = uint32(hex2dec('3000000')); 36 | 37 | MuseElectrodeMap = [1, 1, 8, 8; ... % LinearSingleWell64 38 | 1, 1, 8, 8]; % P200D30S 39 | 40 | MaestroElectrodeMap = [ 3, 4, 8, 8; ... %LinearTwelveWell 41 | 3, 4, 8, 8; ... %TwelveWell 42 | 6, 8, 4, 4; ... %FortyEightWell 43 | 8, 12, 3, 3; ... %NinetySixWell 44 | 6, 8, 4, 4; ... %FortyEightWellTransparent 45 | 16, 24, 2, 1];...%ThreeEightyFourWell% 46 | end 47 | 48 | methods(Static) 49 | function fPlateDimentions = GetWellDimensions(aPlateType) 50 | % GetWellDimensions returns a 2-element array of plate 51 | % dimensions. 52 | % 53 | % First element is the number of well rows, second element 54 | % is the number of well columns. 55 | % 56 | 57 | offset = bitand(aPlateType, 15); 58 | 59 | if (bitand(aPlateType, PlateTypes.MUSE_MASK) == PlateTypes.MUSE_MASK) 60 | fPlateDimentions = PlateTypes.MuseElectrodeMap(offset + 1, (1:2)); 61 | elseif (bitand(aPlateType, PlateTypes.MAESTRO_MASK) == PlateTypes.MAESTRO_MASK) 62 | fPlateDimentions = PlateTypes.MaestroElectrodeMap(offset + 1, (1:2)); 63 | else 64 | warning('File has and unknown plate type. These Matlab Scripts may be out of date.'); 65 | fPlateDimentions = []; 66 | end 67 | end 68 | 69 | function fElectrodeDimentions = GetElectrodeDimensions(aPlateType) 70 | % GetElectrodeDimensions returns a 4-element array of plate 71 | % dimensions (wells and electrodes within wells). 72 | % 73 | % Format is [well rows, well columns, electrode rows, electrode 74 | % columns]. 75 | % 76 | % NOTE: wells of a 96-well plates have 3 electrode rows an 3 77 | % electrode columns. However, the second row contains only 2 78 | % valid electrodes. 79 | % 80 | 81 | offset = bitand(aPlateType, 15); 82 | 83 | if (bitand(aPlateType, PlateTypes.MUSE_MASK) == PlateTypes.MUSE_MASK) 84 | fElectrodeDimentions = PlateTypes.MuseElectrodeMap(offset + 1, :); 85 | elseif (bitand(aPlateType, PlateTypes.MAESTRO_MASK) == PlateTypes.MAESTRO_MASK) 86 | fElectrodeDimentions = PlateTypes.MaestroElectrodeMap(offset + 1, :); 87 | else 88 | warning('File has and unknown plate type. These Matlab Scripts may be out of date.'); 89 | fElectrodeDimentions = []; 90 | end 91 | end 92 | end 93 | 94 | end 95 | 96 | -------------------------------------------------------------------------------- /Axion/BlockVectorHeader.m: -------------------------------------------------------------------------------- 1 | classdef BlockVectorHeader < Entry 2 | %BlockVectorHeader Primary data regarding a BlockVectorSet of Data 3 | % This class contains the basic information about an entry of 4 | % Block Vector Data that is necessary to load it as a series of 5 | % vectors. 6 | % 7 | % SamplingFrequency: Recording sampling rate (in Hz) of the data 8 | % 9 | % VoltageScale: Voltage (in Volts) of the conversion factor 10 | % from the stored int16's in the Data vectors 11 | % to a real voltage value. i.e. Signal = 12 | % double(VoltageScale) * Waveform.Data 13 | % 14 | % FileStartTime: DateTime (See DateTime.m) when this 15 | % recording started 16 | % 17 | % ExperimentStartTime: DateTime (See DateTime.m) when the 18 | % device that acquired this data started 19 | % aquiring 20 | % 21 | % FirstBlock: Pointer (# of bytes frome the beginning of 22 | % the file) to the start of the associated 23 | % BlockVectorData entry 24 | % 25 | % NumChannelsPerBlock: Number of Channels of data stored in every 26 | % block of the data. 27 | % 28 | % NumSamplesPerBlock: Number of samples in every channel-wise 29 | % vector of the block. 30 | % 31 | % BlockHeaderSize: Number of bytes used for header of each 32 | % block. 33 | % 34 | 35 | 36 | 37 | properties (Constant = true, GetAccess = public) 38 | SIZE = 64; 39 | end 40 | 41 | properties(GetAccess = public, SetAccess = private) 42 | SamplingFrequency 43 | VoltageScale 44 | FileStartTime 45 | ExperimentStartTime 46 | FirstBlock 47 | NumChannelsPerBlock 48 | NumSamplesPerBlock 49 | BlockHeaderSize 50 | end 51 | 52 | methods 53 | function this = BlockVectorHeader(aEntryRecord, aFileID) 54 | this = this@Entry(aEntryRecord, int64(ftell(aFileID))); 55 | 56 | this.SamplingFrequency = fread(aFileID, 1, 'double=>double'); 57 | this.VoltageScale = fread(aFileID, 1, 'double=>double'); 58 | this.FileStartTime = DateTime(aFileID); 59 | this.ExperimentStartTime = DateTime(aFileID); 60 | this.FirstBlock = fread(aFileID, 1, 'int64=>int64'); 61 | this.NumChannelsPerBlock = fread(aFileID, 1, 'uint32=>uint32'); 62 | this.NumSamplesPerBlock = fread(aFileID, 1, 'uint32=>uint32'); 63 | this.BlockHeaderSize = fread(aFileID, 1, 'uint32=>uint32'); 64 | 65 | if(this.EntryRecord.Length ~= -1 && ... 66 | ftell(aFileID) ~= (this.Start + this.EntryRecord.Length)) 67 | error('Unexpected BlockVectorHeader length') 68 | end 69 | 70 | end 71 | end 72 | 73 | methods (Static = true) 74 | function this = Generate(... 75 | aFileID, ... 76 | aSamplingFrequency, ... 77 | aVoltageScale, ... 78 | aFileStartTime, ... 79 | aExperimentStartTime, ... 80 | aFirstBlock, ... 81 | aNumChannelsPerBlock, ... 82 | aNumSamplesPerBlock, ... 83 | aBlockHeaderSize) 84 | 85 | this = BlockVectorHeader(EntryRecord(EntryRecordID.BlockVectorHeader, -1), aFileID); 86 | this.SamplingFrequency = aSamplingFrequency; 87 | this.VoltageScale = aVoltageScale; 88 | this.FileStartTime = aFileStartTime; 89 | this.ExperimentStartTime = aExperimentStartTime; 90 | this.FirstBlock = aFirstBlock; 91 | this.NumChannelsPerBlock = aNumChannelsPerBlock; 92 | this.NumSamplesPerBlock = aNumSamplesPerBlock; 93 | this.BlockHeaderSize = aBlockHeaderSize; 94 | 95 | end 96 | end 97 | 98 | end -------------------------------------------------------------------------------- /functions/MEA_convert.m: -------------------------------------------------------------------------------- 1 | function MEA_convert(raw_file, output_folder, wells2process) 2 | % MEA_convert(raw_file, output_folder) 3 | % raw_file: file path of .raw file to be unpacked 4 | % output_folder: file path of output folder to be created 5 | % wells2process: subset of wells to unpack 6 | warning('off') 7 | % set up intake of raw data 8 | if ~exist(output_folder) 9 | % if the folder doesn't exist, create and do conversion 10 | mkdir(output_folder); 11 | end 12 | % load the axion file into matlab 13 | disp('Loading .raw file...') 14 | file = AxisFile(raw_file); 15 | 16 | % compute plate dimension from file header 17 | num_wells = length(unique([file.DataSets.ChannelArray.Channels.WellRow])) * ... 18 | length(unique([file.DataSets.ChannelArray.Channels.WellColumn])); 19 | 20 | if num_wells~=12 && num_wells~=48 21 | % if you get something weird, default to 12 wells 22 | disp('Weird number of channel/well combination') 23 | num_wells=12; 24 | end 25 | %parameters & loading data into MATLAB 26 | if num_wells == 12 27 | %dimensions of the plate (# wells per row,col) 28 | plate_dim=[3,4]; 29 | %number of channels per well 30 | num_chan=64; 31 | row = {'A','B','C'}; 32 | col = {'1' '2' '3' '4'}; 33 | elseif num_wells==48 34 | %dimensions of the plate (# wells per row,col) 35 | plate_dim=[6,8]; %[6,8] or [3,4] 36 | %number of channels per well 37 | num_chan=16; %16 or 64 38 | row = {'A','B','C' 'D','E','F'}; 39 | col = {'1' '2' '3' '4' '5' '6' '7' '8'}; 40 | end 41 | 42 | %gather wells & save into matfiles 43 | for k=1:plate_dim(1) 44 | for l=1:plate_dim(2) 45 | 46 | % extract wells 47 | disp(sprintf('Well: (%i,%i)', k,l)); 48 | outname = sprintf([output_folder '/well_%i.mat'], (k-1)*plate_dim(2)+l); 49 | 50 | if any(wells2process==((k-1)*plate_dim(2)+l)) 51 | 52 | % if we want to convert this well 53 | if ~exist(outname, 'file') 54 | 55 | %dataset too large, load well by well 56 | %load well 57 | data = file.DataSets.LoadData([row{k} col{l}]); 58 | if all(all(squeeze([cellfun(@isempty,data(k,l,:,:))]))) 59 | %there is nothing in the well, skip to next iteration 60 | %of fore loop 61 | disp(sprintf('Whole well (%i, %i) empty, skipped.',k,l)); 62 | continue 63 | 64 | elseif any(any(squeeze([cellfun(@isempty,data(k,l,:,:))]))) 65 | %some channels are empty, have to go through each channel 66 | %fill empty channels with 0s 67 | disp('Empty channels encountered. Sorting...') 68 | 69 | %find first non-empty channel 70 | [r,c] = find(1-squeeze(cellfun(@isempty,data(k,l,:,:))),1); 71 | 72 | % get time vector and data length from here 73 | t = data{k,l,r,c}.GetTimeVector; 74 | data_len = length(t); 75 | 76 | % pre initialize MEA array 77 | MEA = zeros(data_len, num_chan); 78 | chan_dims = size(squeeze(data(k,l,:,:))); 79 | for rr = 1:chan_dims(1) 80 | for cc = 1:chan_dims(2) 81 | if ~isempty(data{k,l,rr,cc}) 82 | %get data of non-empty channel 83 | MEA(:,(cc-1)*chan_dims(1)+rr) = data{k,l,rr,cc}.GetVoltageVector; 84 | else 85 | disp(sprintf('(%i, %i) empty.',rr,cc)); 86 | end 87 | end 88 | end 89 | else 90 | %all channels have data, just convert and save 91 | temp = [data{k,l,:,:}]; 92 | MEA = temp.GetVoltageVector; 93 | t = temp(1).GetTimeVector; 94 | end 95 | save(sprintf([output_folder '/well_%i.mat'], (k-1)*plate_dim(2)+l), 't', 'MEA', '-v7.3') 96 | else 97 | disp('Well conversion exists.') 98 | end 99 | else 100 | disp('Well skipped by user.') 101 | end 102 | end 103 | end -------------------------------------------------------------------------------- /scripts/WT_CDKL5.m: -------------------------------------------------------------------------------- 1 | %% CDKL5 vs. WT organoids (see daily17_05_11.m for original code) 2 | set(0,'defaultfigurecolor',[1 1 1]) 3 | load('/Users/rdgao/Documents/data/Muotri/CDKL5_WT/aggregate.mat', 'nws_smo', 'ker_win') 4 | load('/Users/rdgao/Documents/data/Muotri/CDKL5_WT/CDKL5_dates.mat') 5 | %% peak finding 6 | % from network spikes (nws_smo), find peaks 7 | fs = 1000; 8 | 9 | disp('Peak finding & Event Features...') 10 | warning('off') 11 | fp_params.MPH = 1; 12 | fp_params.MPH_ratio = 0.8; 13 | fp_params.MPD = 200; 14 | fp_params.toss_thresh_ratio = 0.001; 15 | %[event_feats, pk_times] = compute_peakfeats(nws_smo, fs, fp_params); 16 | 17 | %% some housekeeping stuff 18 | dayVec=1:24; 19 | for i=1:24 20 | dates{i}=num2str(i); 21 | end 22 | nws_smo{24}(:,12)=0; 23 | 24 | %% recompute kernel locations with new code 25 | kernels = cell(24,1); 26 | for r_idx = 1:length(nws_smo) 27 | for well = 1:12 28 | %pktimes = kernel_findpeak(nws_smo, fp_params.MPH, fp_params.MPH_ratio, fp_params.MPD, fp_params.toss_thresh_ratio); 29 | pktimes{r_idx}{well} = kernel_findpeak(nws_smo{r_idx}(:,well), fp_params.MPH, fp_params.MPH_ratio, fp_params.MPD, fp_params.toss_thresh_ratio); 30 | if ~isempty(pktimes{r_idx}{well}) 31 | kernels{r_idx}{well} = collect_spikes(nws_smo{r_idx}(:,well),[],pktimes{r_idx}{well}(:,2),ker_win); 32 | else 33 | kernels{r_idx}{well} = []; 34 | end 35 | end 36 | end 37 | 38 | %% kernel aggregate stats 39 | nsubs = zeros(length(kernels),12); 40 | peakAmp = zeros(length(kernels),12); 41 | FWHM = zeros(length(kernels),12); 42 | total_spikes = zeros(length(kernels),12); 43 | for day = 1:length(kernels) 44 | disp(dates{day}) 45 | for well = 1:12 46 | [nsubs_, peakAmp_,FWHM_, subpks_, subpksT_] = kernel_pkfinding(kernels{day}{well}); 47 | nsubs(day, well) = mean(nsubs_); 48 | peakAmp(day, well) = mean(peakAmp_); 49 | FWHM(day, well) = mean(FWHM_); 50 | total_spikes(day, well) = mean(sum(kernels{day}{well},1)); 51 | end 52 | end 53 | %% load WT data 54 | WT_data = load('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/aggregate.mat', 'kernels'); 55 | load('/Users/rdgao/Documents/data/Muotri/Pri_Corticoids/names.mat'); 56 | % grab date information and set parameters 57 | wells = 5:12; 58 | %exclude recordings not done after exchanging media, or pharmacology 59 | exclusion = [5 9 12 15 25 26 34 35 36 40]; 60 | recs = setdiff(1:length(dates),exclusion); 61 | dayVec = zeros(1,length(recs)); 62 | for day = 1:length(recs) 63 | %parse numerical date 64 | date = dates(recs(day)).name(5:end); 65 | disp(date) 66 | dayVec(day) = datenum(str2num(date(5:6)), str2num(date(1:2)), str2num(date(3:4))); 67 | end 68 | recDays = dayVec-dayVec(1); 69 | dayVec = recDays/7+7; %use weeks instead 70 | 71 | kernels_WT = WT_data.kernels; 72 | nsubs_WT = zeros(length(kernels_WT),8); 73 | peakAmp_WT = zeros(length(kernels_WT),8); 74 | FWHM_WT = zeros(length(kernels_WT),8); 75 | total_spikes_WT = zeros(length(kernels_WT),8); 76 | for day = 1:length(kernels_WT) 77 | disp(day) 78 | for well = 5:12 79 | [nsubs_, peakAmp_,FWHM_, subpks_, subpksT_] = kernel_pkfinding(kernels_WT{day}{well}); 80 | nsubs_WT(day, well-4) = mean(nsubs_); 81 | peakAmp_WT(day, well-4) = mean(peakAmp_); 82 | FWHM_WT(day, well-4) = mean(FWHM_); 83 | total_spikes_WT(day, well-4) = mean(sum(kernels_WT{day}{well},1)); 84 | end 85 | end 86 | 87 | 88 | %% 89 | conds = {[1 2 5 6 9 10], [3 4 7 8 11 12]}; 90 | plot_feat = {nsubs, nsubs_WT}; 91 | figure 92 | plot_filled(CDKL5_dayVec, mean(plot_feat{1}(:,conds{1}),2), std(plot_feat{1}(:,conds{1}),1,2), 'r') 93 | hold on 94 | plot_filled(CDKL5_dayVec, mean(plot_feat{1}(:,conds{2}),2), std(plot_feat{1}(:,conds{2}),1,2), 'k') 95 | plot_filled(dayVec, mean(plot_feat{2}(recs,:),2), std(plot_feat{2}(recs,:),1,2), 'b') 96 | title('Oscillation Subpeaks') 97 | ylabel('# Peaks') 98 | xlabel('Recording') 99 | hold off 100 | 101 | figure 102 | hold on 103 | 104 | plot(CDKL5_dayVec, plot_feat{1}(:,conds{1}), 'or-') 105 | plot(CDKL5_dayVec, plot_feat{1}(:,conds{2}), 'ok-') 106 | plot(dayVec, plot_feat{2}(recs,:), 'ob-') 107 | hold off 108 | %% 109 | 110 | %% mean % of subpeaks 111 | conds = {[1 2 5 6 9 10], [3 4 7 8 11 12]}; 112 | figure 113 | hold on 114 | %plot(dayVec,nsubs(:,conds{1}), '.-', 'color', [0 0 0 0.4]) 115 | plot(dayVec,mean(nsubs(:,conds{1}),2), 'o-', 'color', [0 0 0 0.4]) 116 | %plot_filled(dayVec, mean(nsubs(:,conds{1}),2), std(nsubs(:,conds{1}),2), 'k') 117 | 118 | %plot(dayVec,nsubs(:,conds{2}), '.-', 'color', [1 0 0 0.4]) 119 | plot(dayVec,mean(nsubs(:,conds{2}),2), 'o-', 'color', [1 0 0 0.4]) 120 | hold off 121 | xlabel('DIV') 122 | ylabel('# of subpeaks') 123 | -------------------------------------------------------------------------------- /util_funcs/kernel_findpeak.m: -------------------------------------------------------------------------------- 1 | function pktimes = kernel_findpeak(x, MPH, MPH_ratio, MPD, toss_thresh_ratio) 2 | %% iterative peak sifting 3 | % 1. find all peaks (above some optional minimum value and dist) 4 | % 2. sort peak indices by peak amplitude 5 | % while (a peak was eliminated on last iter) 6 | % for all peak indices 7 | % find all indices flanking the first peak that are above certain threshold 8 | % find all detected peak indices that fall within those flanking indices 9 | % if set is not empty 10 | % throw out peak indices, peak eliminated = true 11 | 12 | % whether to plot the spikes 13 | do_viz = 0; 14 | 15 | % first, find all peaks satisfying criterion of minpeakheight and 16 | % minpeakdistance 17 | [PK,IND] =findpeaks(x, ... 18 | 'minpeakheight', max(max(x)*MPH_ratio, MPH), ... 19 | 'minpeakdistance',MPD); 20 | 21 | % sort peaks descending 22 | [PK_sorted, sorted_ind] = sort(PK, 'descend'); 23 | IND_sorted = IND(sorted_ind); 24 | 25 | % set sifting threshold 26 | thresh_val = max(PK_sorted)*toss_thresh_ratio; 27 | 28 | % sifting 29 | peaks_eliminated = 1; 30 | did_elim = 0; 31 | while peaks_eliminated 32 | peaks_eliminated = 0; % reset flag 33 | cur_ind = 1; 34 | while cur_ind<=length(PK_sorted) 35 | % loop through all peak inds, use while loop because number of 36 | % peaks dynamically decreases 37 | 38 | % find the last index before the peak that met threshold 39 | i1 = find(x(1:IND_sorted(cur_ind))i1 & IND_sorteduint32'); 33 | fChannelArraySize = 8 * (fNumChannels + 1); 34 | 35 | fseek(aFileID, 0, 'eof'); 36 | fEOF = ftell(aFileID); 37 | 38 | fPostNotesGap = LegacySupport.INITIAL_HEADER_LENGTH - (aEntriesStart + Note.SIZE); 39 | fPostChannelsGap = fDataStart - (LegacySupport.INITIAL_HEADER_LENGTH + BlockVectorHeader.SIZE +fChannelArraySize); 40 | 41 | fEntryRecords = EntryRecord(EntryRecordID.NotesArray, Note.SIZE); 42 | 43 | fEntryRecords = [fEntryRecords EntryRecord(EntryRecordID.Skip, fPostNotesGap)]; 44 | 45 | fEntryRecords = [fEntryRecords EntryRecord(EntryRecordID.BlockVectorHeader, BlockVectorHeader.SIZE)]; 46 | 47 | fEntryRecords = [fEntryRecords EntryRecord(EntryRecordID.ChannelArray, fChannelArraySize)]; 48 | 49 | fEntryRecords = [fEntryRecords EntryRecord(EntryRecordID.Skip, fPostChannelsGap)]; 50 | 51 | fEntryRecords = [fEntryRecords EntryRecord(EntryRecordID.BlockVectorData, fEOF - fDataStart)]; 52 | 53 | fEntryRecords = [fEntryRecords EntryRecord(EntryRecordID.Terminate, 0)]; 54 | 55 | end 56 | 57 | function [fType, fData, fChannelMapping, fHeader] = GenerateRolstonEntries(aFileID, aExtension) 58 | fChannelMapping = ChannelArray.version_0_1_channel_array(); 59 | 60 | fseek(aFileID, 0, 'eof'); 61 | fEOF = ftell(aFileID); 62 | fseek(aFileID, 0, 'bof'); 63 | 64 | if strcmp(aExtension, '.raw') 65 | % Read header 66 | 67 | fType = BlockVectorDataType.Raw_v1; 68 | fNumChannels = fread(aFileID, 1, 'uint16'); 69 | fSamplingFrequency = fread(aFileID, 1, 'uint32'); 70 | fread(aFileID, 1, 'uint16'); %Gain 71 | 72 | coefficients = fread(aFileID, LegacySupport.NUM_COEFFICIENTS, 'double'); 73 | fVoltageScale = coefficients(2) / 1200; 74 | 75 | fExperimentStartTime = DateTime(aFileID); 76 | fNumSamples = 1; 77 | 78 | elseif strcmp(aExtension, '.spk') 79 | error('LegacySupport:Unsupported', ... 80 | 'File appears to be a deprecated AxIS v0.0 spike file, this type of file is not supported'); 81 | else 82 | error('LegacySupport:extension', ... 83 | 'File appears to be a deprecated AxIS v0.0 file, but filename does not end with .raw or .spk'); 84 | end 85 | 86 | fDataStart = ftell(aFileID); 87 | 88 | fHeader = BlockVectorHeader.Generate(... 89 | aFileID, ... 90 | fSamplingFrequency, ... 91 | fVoltageScale, ... 92 | fExperimentStartTime, ... 93 | fExperimentStartTime, ... 94 | fDataStart, ... 95 | fNumChannels, ... 96 | fNumSamples, ... 97 | 0); 98 | 99 | fData = EntryRecord(EntryRecordID.BlockVectorData, fEOF - fDataStart); 100 | fseek(aFileID, double(fDataStart), 'bof'); 101 | fData = BlockVectorData(fData, aFileID); 102 | 103 | end 104 | end 105 | end 106 | 107 | -------------------------------------------------------------------------------- /util_funcs/shadedErrorBar.m: -------------------------------------------------------------------------------- 1 | function varargout=shadedErrorBar(x,y,errBar,lineProps,transparent) 2 | % function H=shadedErrorBar(x,y,errBar,lineProps,transparent) 3 | % 4 | % Purpose 5 | % Makes a 2-d line plot with a pretty shaded error bar made 6 | % using patch. Error bar color is chosen automatically. 7 | % 8 | % Inputs 9 | % x - vector of x values [optional, can be left empty] 10 | % y - vector of y values or a matrix of n observations by m cases 11 | % where m has length(x); 12 | % errBar - if a vector we draw symmetric errorbars. If it has a size 13 | % of [2,length(x)] then we draw asymmetric error bars with 14 | % row 1 being the upper bar and row 2 being the lower bar 15 | % (with respect to y). ** alternatively ** errBar can be a 16 | % cellArray of two function handles. The first defines which 17 | % statistic the line should be and the second defines the 18 | % error bar. 19 | % lineProps - [optional,'-k' by default] defines the properties of 20 | % the data line. e.g.: 21 | % 'or-', or {'-or','markerfacecolor',[1,0.2,0.2]} 22 | % transparent - [optional, 0 by default] if ==1 the shaded error 23 | % bar is made transparent, which forces the renderer 24 | % to be openGl. However, if this is saved as .eps the 25 | % resulting file will contain a raster not a vector 26 | % image. 27 | % 28 | % Outputs 29 | % H - a structure of handles to the generated plot objects. 30 | % 31 | % 32 | % Examples 33 | % y=randn(30,80); x=1:size(y,2); 34 | % shadedErrorBar(x,mean(y,1),std(y),'g'); 35 | % shadedErrorBar(x,y,{@median,@std},{'r-o','markerfacecolor','r'}); 36 | % shadedErrorBar([],y,{@median,@std},{'r-o','markerfacecolor','r'}); 37 | % 38 | % Overlay two transparent lines 39 | % y=randn(30,80)*10; x=(1:size(y,2))-40; 40 | % shadedErrorBar(x,y,{@mean,@std},'-r',1); 41 | % hold on 42 | % y=ones(30,1)*x; y=y+0.06*y.^2+randn(size(y))*10; 43 | % shadedErrorBar(x,y,{@mean,@std},'-b',1); 44 | % hold off 45 | % 46 | % 47 | % Rob Campbell - November 2009 48 | 49 | 50 | 51 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 52 | % Error checking 53 | error(nargchk(3,5,nargin)) 54 | 55 | 56 | %Process y using function handles if needed to make the error bar 57 | %dynamically 58 | if iscell(errBar) 59 | fun1=errBar{1}; 60 | fun2=errBar{2}; 61 | errBar=fun2(y); 62 | y=fun1(y); 63 | else 64 | y=y(:)'; 65 | end 66 | 67 | if isempty(x) 68 | x=1:length(y); 69 | else 70 | x=x(:)'; 71 | end 72 | 73 | 74 | %Make upper and lower error bars if only one was specified 75 | if length(errBar)==length(errBar(:)) 76 | errBar=repmat(errBar(:)',2,1); 77 | else 78 | s=size(errBar); 79 | f=find(s==2); 80 | if isempty(f), error('errBar has the wrong size'), end 81 | if f==2, errBar=errBar'; end 82 | end 83 | 84 | if length(x) ~= length(errBar) 85 | error('length(x) must equal length(errBar)') 86 | end 87 | 88 | %Set default options 89 | defaultProps={'-k'}; 90 | if nargin<4, lineProps=defaultProps; end 91 | if isempty(lineProps), lineProps=defaultProps; end 92 | if ~iscell(lineProps), lineProps={lineProps}; end 93 | 94 | if nargin<5, transparent=0; end 95 | 96 | 97 | 98 | 99 | 100 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 101 | % Plot to get the parameters of the line 102 | H.mainLine=plot(x,y,lineProps{:}); 103 | 104 | 105 | % Work out the color of the shaded region and associated lines 106 | % Using alpha requires the render to be openGL and so you can't 107 | % save a vector image. On the other hand, you need alpha if you're 108 | % overlaying lines. There we have the option of choosing alpha or a 109 | % de-saturated solid colour for the patch surface . 110 | 111 | col=get(H.mainLine,'color'); 112 | edgeColor=col+(1-col)*0.55; 113 | patchSaturation=0.15; %How de-saturated or transparent to make patch 114 | if transparent 115 | faceAlpha=patchSaturation; 116 | patchColor=col; 117 | set(gcf,'renderer','openGL') 118 | else 119 | faceAlpha=1; 120 | patchColor=col+(1-col)*(1-patchSaturation); 121 | set(gcf,'renderer','painters') 122 | end 123 | 124 | 125 | %Calculate the error bars 126 | uE=y+errBar(1,:); 127 | lE=y-errBar(2,:); 128 | 129 | 130 | %Add the patch error bar 131 | holdStatus=ishold; 132 | if ~holdStatus, hold on, end 133 | 134 | 135 | %Make the patch 136 | yP=[lE,fliplr(uE)]; 137 | xP=[x,fliplr(x)]; 138 | 139 | %remove nans otherwise patch won't work 140 | xP(isnan(yP))=[]; 141 | yP(isnan(yP))=[]; 142 | 143 | 144 | H.patch=patch(xP,yP,1,'facecolor',patchColor,... 145 | 'edgecolor','none',... 146 | 'facealpha',faceAlpha); 147 | 148 | 149 | %Make pretty edges around the patch. 150 | H.edge(1)=plot(x,lE,'-','color',edgeColor); 151 | H.edge(2)=plot(x,uE,'-','color',edgeColor); 152 | 153 | %Now replace the line (this avoids having to bugger about with z coordinates) 154 | delete(H.mainLine) 155 | H.mainLine=plot(x,y,lineProps{:}); 156 | 157 | 158 | if ~holdStatus, hold off, end 159 | 160 | 161 | if nargout==1 162 | varargout{1}=H; 163 | end 164 | -------------------------------------------------------------------------------- /Axion/ChannelMapping.m: -------------------------------------------------------------------------------- 1 | classdef ChannelMapping 2 | %CHANNELMAPPING represents the mapping of an electrode position to an 3 | %Amplifier (Artichoke) channel by way of the plate it was recorded 4 | %with. 5 | % 6 | % WellRow: Numeric representation of the well row (e.g. A -> 1) 7 | % 8 | % WellColumn: Well column number of this electrode 9 | % 10 | % ElectrodeColumn: Column in the well of the electrode 11 | % 12 | % ElectrodeRow: Row in the well of the electrode 13 | % 14 | % ChannelAchk: Specific amplifer chip (Artichoke) to which the 15 | % electrode was connected 16 | % 17 | % ChannelIndex: Specific channel number of connected amplifier 18 | % to which the electrode was connected 19 | % 20 | % AuxData: Additional data for programmatic usage 21 | 22 | properties (GetAccess = private, Constant = true) 23 | nullByte = typecast(int8(-1), 'uint8'); 24 | nullWord = typecast(int16(-1), 'uint16'); 25 | end 26 | 27 | properties (GetAccess = public, SetAccess = private) 28 | WellRow 29 | WellColumn 30 | ElectrodeColumn 31 | ElectrodeRow 32 | ChannelAchk 33 | ChannelIndex 34 | AuxData 35 | end 36 | 37 | methods(Access = public) 38 | 39 | function this = ChannelMapping( varargin ) 40 | 41 | fNArgIn = length(varargin); 42 | 43 | if(fNArgIn == 0) 44 | % Create a nonsense (Null) Channel Mapping 45 | this.WellRow = ChannelMapping.nullByte; 46 | this.WellColumn = ChannelMapping.nullByte; 47 | this.ElectrodeColumn = ChannelMapping.nullByte; 48 | this.ElectrodeRow = ChannelMapping.nullByte; 49 | this.ChannelAchk = ChannelMapping.nullByte; 50 | this.ChannelIndex = ChannelMapping.nullByte; 51 | this.AuxData = ChannelMapping.nullWord; 52 | 53 | elseif(fNArgIn == 1) 54 | % Assume Argument is a file ID from fOpen and that is 55 | % seeked to the correct spot, read in arguments from this 56 | % file 57 | 58 | aFileID = varargin{1}; 59 | 60 | this.WellColumn = fread(aFileID, 1, 'uint8=>uint8'); 61 | this.WellRow = fread(aFileID, 1, 'uint8=>uint8'); 62 | this.ElectrodeColumn = fread(aFileID, 1, 'uint8=>uint8'); 63 | this.ElectrodeRow = fread(aFileID, 1, 'uint8=>uint8'); 64 | this.ChannelAchk = fread(aFileID, 1, 'uint8=>uint8'); 65 | this.ChannelIndex = fread(aFileID, 1, 'uint8=>uint8'); 66 | this.AuxData = fread(aFileID, 1, 'uint16=>uint16'); 67 | 68 | elseif (fNArgIn == 6) 69 | % Construct a new Channel Mapping from Scratch 70 | % Argument order is(WellRow, WellColumn, ElectrodeColumn, 71 | % ElectrodeRow, ChannelAchk, ChannelIndex) 72 | 73 | this.WellRow = uint8(varargin{1}); 74 | this.WellColumn = uint8(varargin{2}); 75 | this.ElectrodeColumn = uint8(varargin{3}); 76 | this.ElectrodeRow = uint8(varargin{4}); 77 | this.ChannelAchk = uint8(varargin{5}); 78 | this.ChannelIndex = uint8(varargin{6}); 79 | this.AuxData = ChannelMapping.nullWord; 80 | 81 | elseif (fNArgIn == 7) 82 | % Construct a new Channel Mapping from Scratch 83 | % Argument order is(WellRow, WellColumn, ElectrodeColumn, 84 | % ElectrodeRow, ChannelAchk, ChannelIndex, AuxData) 85 | 86 | this.WellRow = uint8(varargin{1}); 87 | this.WellColumn = uint8(varargin{2}); 88 | this.ElectrodeColumn = uint8(varargin{3}); 89 | this.ElectrodeRow = uint8(varargin{4}); 90 | this.ChannelAchk = uint8(varargin{5}); 91 | this.ChannelIndex = uint8(varargin{6}); 92 | this.AuxData = uint16(varargin{7}); 93 | else 94 | error('Argument Error') 95 | end 96 | end 97 | 98 | function retval = eq(this, aObj) 99 | if(~isa(aObj, 'ChannelMapping') ... 100 | || ~isa(this, 'ChannelMapping') ) 101 | retval = 0; 102 | return; 103 | end 104 | retval = ... 105 | this.WellRow == aObj.WellRow ... 106 | && this.WellColumn == aObj.WellColumn ... 107 | && this.ElectrodeColumn == aObj.ElectrodeColumn ... 108 | && this.ElectrodeRow == aObj.ElectrodeRow ... 109 | && this.ChannelAchk == aObj.ChannelAchk ... 110 | && this.ChannelIndex == aObj.ChannelIndex; 111 | 112 | end 113 | end 114 | end 115 | 116 | -------------------------------------------------------------------------------- /Axion/calc_spike_rate.m: -------------------------------------------------------------------------------- 1 | % 2 | % calc_spike_rate(aInputFile, aOutputFile, aIntervalSeconds, aStartSeconds, aEndSeconds) 3 | % 4 | % Required Parameters 5 | % aInputFile path and name of the input spike file 6 | % aOutputFile path and name of the spike rate file to write (in CSV format) 7 | % 8 | % Optional Parameters 9 | % aStartSeconds time you want to start from in the file, in seconds (default 0) 10 | % aEndSeconds time you want to stop, in seconds (default Inf, end of file) 11 | % aIntervalSeconds interval that will be used between aStartSeconds and aEndSeconds (default 60) 12 | % 13 | % Note: Parameters must be specified in order. If optional parameters are omitted, 14 | % the default is used, as follows: 15 | % 16 | % calc_spike_rate(aInputFile, aOutputFile) 17 | % => uses defaults for aIntervalSeconds, aStartSeconds, aEndSeconds 18 | % 19 | % calc_spike_rate(aInputFile, aOutputFile, aIntervalSeconds) 20 | % => uses defaults for StartSeconds, aEndSeconds 21 | % 22 | % calc_spike_rate(aInputFile, aOutputFile, aIntervalSeconds, aStartSeconds) 23 | % => uses defaults for aEndSeconds 24 | % 25 | % 26 | % The script will divide the time between aStartSeconds and aEndSeconds into N intervals, 27 | % where N = aEndSeconds - aStartSeconds) / aIntervalSeconds, rounded up, and output 28 | % spike counts for the intervals: 29 | % 30 | % [aStartSeconds, aStartSeconds + aIntervalSeconds) 31 | % [aStartSeconds + aIntervalSeconds, aStartSeconds + 2*aIntervalSeconds) 32 | % [aStartSeconds + 2*aIntervalSeconds, aStartSeconds + 3*aIntervalSeconds) 33 | % .... 34 | % [aStartSeconds + (N-1)*aIntervalSeconds, aStartSeconds + N*aIntervalSeconds) 35 | % 36 | 37 | function calc_spike_rate(aInputFile, aOutputFile, aIntervalSeconds, aStartSeconds, aEndSeconds) 38 | 39 | ELECTRODE_ROWS = 8; 40 | ELECTRODE_COLS = 8; 41 | NUM_ELECTRODES = ELECTRODE_ROWS * ELECTRODE_COLS; 42 | 43 | % Handle default arguments: 44 | % aIntervalSeconds = 60 45 | % aStartSeconds = 0 46 | % aEndSeconds = Inf 47 | if nargin < 3 48 | aIntervalSeconds = 60; 49 | end 50 | 51 | if nargin < 4 52 | aStartSeconds = 0; 53 | end 54 | 55 | if nargin < 5 56 | aEndSeconds = Inf; 57 | end 58 | 59 | if nargin > 5 60 | error('calc_spike_rate:tooManyParameters', ['calc_spike_rate: Too many input parameters']); 61 | end 62 | 63 | if ischar(aInputFile) 64 | disp(['Loading spikes from file ' aInputFile]); 65 | 66 | fSpikeData = load_AxIS_file(aInputFile, [aStartSeconds aEndSeconds]); 67 | else 68 | error('calc_spike_rate:invalidFileName', ['calc_spike_rate: Invalid file name: ' aInputFile]); 69 | end 70 | 71 | if ~strcmp(fSpikeData.fileType, 'spike') 72 | if strcmp(fSpikeData.fileType, 'spike-beta') 73 | % This is a beta-format spike file. Not currently supported by calc_spike_rate. 74 | error('calc_spike_rate:betaFormat', ['Beta-format spike file not supported: ' aInputFile]); 75 | else 76 | % File was not a spike file 77 | error('calc_spike_rate:invalidSpikeFile', ['Invalid spike file format in file ' aInputFile]); 78 | end 79 | end 80 | 81 | % If aEndSeconds is infinite, we want to read until the end of the file, but we can't leave 82 | % it as Inf, because then we'll try to create a table with an infinite number of rows. Instead, 83 | % base it on the last spike in the file. 84 | if aEndSeconds == Inf 85 | aEndSeconds = (fSpikeData.spikes(end).startingSample + fSpikeData.spikes(end).triggerSampleOffset) / ... 86 | fSpikeData.samplingFrequency; 87 | end 88 | 89 | fNumIntervals = ceil((aEndSeconds - aStartSeconds) / aIntervalSeconds); 90 | 91 | fSpikeCounts = zeros(fNumIntervals, ELECTRODE_ROWS, ELECTRODE_COLS); 92 | 93 | for i=1:length(fSpikeData.spikes) 94 | % Iterate over each loaded spike and determine whether or where it falls in the spike 95 | % rate table. 96 | fSpikeTime = (fSpikeData.spikes(i).startingSample + fSpikeData.spikes(i).triggerSampleOffset) / ... 97 | fSpikeData.samplingFrequency; 98 | fSpikeInterval = floor((fSpikeTime - aStartSeconds) / aIntervalSeconds); 99 | 100 | if fSpikeInterval < 0 101 | % This spike was too early to be counted 102 | continue; 103 | elseif fSpikeInterval >= fNumIntervals 104 | % This spike was too late to be counted. Since the file is ordered by spike time, 105 | % we can stop reading. 106 | break; 107 | else 108 | % we have the channel number ranging from 11 to 88 (this is more a label than a number) 109 | % Decode it: 110 | fChannelY = floor(fSpikeData.spikes(i).channel / 10); 111 | fChannelX = mod(fSpikeData.spikes(i).channel, 10); 112 | %fChannelIndex = fChannelX + (fChannelY - 1) * ELECTRODE_COLS; 113 | 114 | fSpikeCounts(fSpikeInterval + 1, fChannelY, fChannelX) = ... 115 | fSpikeCounts(fSpikeInterval + 1, fChannelY, fChannelX) + 1; 116 | end 117 | end 118 | 119 | % Make sure file doesn't exist so we don't overwrite it 120 | if exist(aOutputFile, 'file') 121 | error('calc_spike_rate:outputFileExists', ['Output file already exists: ' aOutputFile]); 122 | end 123 | 124 | % Write spike rates in CSV format 125 | fid = fopen(aOutputFile, 'w'); 126 | if fid == -1 127 | % error opening output file 128 | error('calc_spike_rate:errorOpeningOutputFile', ['Couldn''t write to output file ' aOutputFile]); 129 | end 130 | 131 | fprintf(fid, 'Interval Start (S),Interval End (S)'); 132 | for i=1:ELECTRODE_ROWS 133 | for j=1:ELECTRODE_COLS 134 | fprintf(fid, ',Channel %d%d', i, j); 135 | end 136 | end 137 | 138 | fprintf(fid, '\n'); 139 | 140 | for fInterval=1:fNumIntervals 141 | fprintf(fid, '%f,%f', aStartSeconds + (fInterval - 1) * aIntervalSeconds, ... 142 | aStartSeconds + fInterval * aIntervalSeconds); 143 | 144 | for i=1:ELECTRODE_ROWS 145 | for j=1:ELECTRODE_COLS 146 | fprintf(fid, ',%d', fSpikeCounts(fInterval, i, j)); 147 | end 148 | end 149 | 150 | fprintf(fid, '\n'); 151 | end 152 | 153 | fclose(fid); 154 | 155 | disp(['Finished writing ' aOutputFile]); 156 | 157 | -------------------------------------------------------------------------------- /Axion/ChannelArray.m: -------------------------------------------------------------------------------- 1 | classdef ChannelArray < Entry 2 | %CHANNELARRAY Class that represents a list of loaded Channels in a BlockVectorDataSet 3 | % 4 | % PlateType: Numeric ID of the loaded plate that thes channels are 5 | % associated with. 6 | % 7 | % Channels: Vector of Channelmapping objects in the order that they 8 | % are included in continuous file. 9 | 10 | properties (GetAccess = private, SetAccess = private) 11 | electrodeHashMap 12 | channelLut 13 | end 14 | 15 | properties(GetAccess = public, SetAccess = private) 16 | PlateType 17 | Channels 18 | end 19 | 20 | 21 | methods(Static, Access = private) 22 | function varagout = HandleVarargin(varargin) 23 | if(nargin == 0) 24 | varagout = {}; 25 | elseif(nargin == 2) 26 | varagout{1} = varargin{1}; 27 | varagout{2} = int64(ftell( varargin{2})); 28 | else 29 | error('Argument Error') 30 | end 31 | end 32 | end 33 | 34 | methods (Access = public) 35 | function this = ChannelArray(varargin) 36 | entryConstructorArgs = ChannelArray.HandleVarargin(varargin{:}); ... 37 | this = this@Entry(entryConstructorArgs{:}); 38 | 39 | if(nargin == 0) 40 | this.PlateType = []; 41 | this.Channels = []; 42 | this.electrodeHashMap = []; 43 | this.channelLut = []; 44 | elseif(nargin == 2) 45 | aFileID = varargin{2}; 46 | this.PlateType = fread(aFileID, 1, 'uint32=>uint32'); 47 | fNnumChannels = fread(aFileID, 1, 'uint32=>uint32'); 48 | 49 | this.Channels = ChannelMapping.empty(0, fNnumChannels); 50 | 51 | fIndices = int32(1:fNnumChannels); 52 | for i = fIndices 53 | this.Channels(i) = ChannelMapping(aFileID); 54 | end 55 | 56 | 57 | this.RebuildHashMaps(); 58 | 59 | 60 | if(ftell(aFileID) ~= (this.Start + this.EntryRecord.Length)) 61 | error('Unexpected Channel array length') 62 | end 63 | else 64 | error('Argument Error') 65 | end 66 | 67 | end 68 | 69 | function index = LookupElectrode(this, ... 70 | aWellColumn, aWellRow,... 71 | aElectrodeColumn, aElectrodeRow) 72 | %LookupElectrode: Quickly finds the index Channels of a given 73 | % electrode position 74 | 75 | index = this.electrodeHashMap(ChannelMapping.HashElectrode(... 76 | aWellColumn, aWellRow, aElectrodeColumn, aElectrodeRow)); 77 | 78 | end 79 | 80 | function index = LookupChannel(this, ... 81 | aChannelAchk, aChannelIndex) 82 | %LookupChannel: Quickly finds the index Channels of a given 83 | % Amplifier (Artichoke) channel 84 | hash = bitshift(uint32(aChannelAchk), 8); 85 | hash = bitor(uint32(aChannelIndex), hash); 86 | 87 | hash = hash + 1; % 1-based indexing for MATLAB 88 | 89 | index = this.channelLut(hash); 90 | 91 | end 92 | 93 | end 94 | 95 | methods(Access = private) 96 | 97 | function RebuildHashMaps(this) 98 | 99 | this.electrodeHashMap = containers.Map('KeyType', 'int32', 'ValueType', 'int32'); 100 | this.channelLut = zeros(length(this.Channels),1); 101 | 102 | fIndices = 1 : length(this.Channels); 103 | 104 | for fIndex = fIndices 105 | 106 | fElectrodeHash = ChannelArray.HashElectrode( ... 107 | this.Channels(fIndex).WellColumn,... 108 | this.Channels(fIndex).WellRow,... 109 | this.Channels(fIndex).ElectrodeColumn,... 110 | this.Channels(fIndex).ElectrodeRow); 111 | 112 | fChannelHash = ChannelArray.HashChannel( ... 113 | this.Channels(fIndex).ChannelAchk,... 114 | this.Channels(fIndex).ChannelIndex); 115 | 116 | if (this.electrodeHashMap.isKey(fElectrodeHash)) 117 | error('Key already added') 118 | end 119 | 120 | this.electrodeHashMap(fElectrodeHash) = fIndex; 121 | this.channelLut(fChannelHash+1) = fIndex; 122 | 123 | end 124 | end 125 | end 126 | 127 | methods(Access = private, Static = true) 128 | 129 | 130 | function hash = HashElectrode( ... 131 | aWellColumn, aWellRow,... 132 | aElectrodeColumn, aElectrodeRow) 133 | 134 | hash = bitshift(uint32(aWellColumn), 24); 135 | hash = bitor(bitshift(uint32(aWellRow), 16), hash); 136 | hash = bitor(bitshift(uint32(aElectrodeColumn), 8), hash); 137 | hash = bitor(uint32(aElectrodeRow), hash); 138 | 139 | hash = uint32(hash); 140 | 141 | end 142 | 143 | function hash = HashChannel( ... 144 | aChannelAchk, aChannelIndex) 145 | 146 | hash = bitshift(uint32(aChannelAchk), 8); 147 | hash = bitor(uint32(aChannelIndex), hash); 148 | 149 | hash = uint32(hash); 150 | 151 | end 152 | 153 | end 154 | 155 | methods (Static) 156 | function fChannelArray = version_0_1_channel_array() 157 | 158 | fChannelArray = ChannelArray(); 159 | % Hardware-to-grid channel mapping. This is a constant only for Muse Beta (AxIS v0.1) 160 | % and file format version 0.1. For later versions, mapping is loaded from the file itself. 161 | fChannelMapping = LegacySupport.P200D30S_CHANNEL_MAPPING; 162 | fChannelArray.PlateType = LegacySupport.P200D30S_PLATE_TYPE; 163 | 164 | fChannelArray.Channels = ChannelMapping.empty(0,(length(fChannelMapping))); 165 | 166 | for fiCol = 1:size(fChannelMapping, 1) 167 | for fiRow = 1:size(fChannelMapping, 2) 168 | fCurrentChannelIndex = fChannelMapping(fiRow, fiCol); 169 | 170 | fNewMapping = ChannelMapping(... 171 | 1, 1,... 172 | fiCol, fiRow,... 173 | 0, fCurrentChannelIndex); 174 | 175 | fChannelArray.Channels(fCurrentChannelIndex + 1) = fNewMapping; 176 | end 177 | end 178 | 179 | fChannelArray.RebuildHashMaps(); 180 | 181 | end 182 | end 183 | 184 | end 185 | 186 | -------------------------------------------------------------------------------- /util_funcs/firpass.m: -------------------------------------------------------------------------------- 1 | % eegfilt() - (high|low|band)-pass filter data using two-way least-squares 2 | % FIR filtering. Optionally uses the window method instead of 3 | % least-squares. Multiple data channels and epochs supported. 4 | % Requires the MATLAB Signal Processing Toolbox. 5 | % Usage: 6 | % >> [smoothdata] = eegfilt(data,srate,locutoff,hicutoff); 7 | % >> [smoothdata,filtwts] = eegfilt(data,srate,locutoff,hicutoff, ... 8 | % epochframes,filtorder,revfilt,firtype,causal); 9 | % Inputs: 10 | % data = (channels,frames*epochs) data to filter 11 | % srate = data sampling rate (Hz) 12 | % locutoff = low-edge frequency in pass band (Hz) {0 -> lowpass} 13 | % hicutoff = high-edge frequency in pass band (Hz) {0 -> highpass} 14 | % epochframes = frames per epoch (filter each epoch separately {def/0: data is 1 epoch} 15 | % filtorder = length of the filter in points {default 3*fix(srate/locutoff)} 16 | % revfilt = [0|1] reverse filter (i.e. bandpass filter to notch filter). {default 0} 17 | % firtype = 'firls'|'fir1' {'firls'} 18 | % causal = [0|1] use causal filter if set to 1 (default 0) 19 | % 20 | % Outputs: 21 | % smoothdata = smoothed data 22 | % filtwts = filter coefficients [smoothdata <- filtfilt(filtwts,1,data)] 23 | % 24 | % See also: firls(), filtfilt() 25 | 26 | % Author: Scott Makeig, Arnaud Delorme, Clemens Brunner SCCN/INC/UCSD, La Jolla, 1997 27 | 28 | % Copyright (C) 4-22-97 from bandpass.m Scott Makeig, SCCN/INC/UCSD, scott@sccn.ucsd.edu 29 | % 30 | % This program is free software; you can redistribute it and/or modify 31 | % it under the terms of the GNU General Public License as published by 32 | % the Free Software Foundation; either version 2 of the License, or 33 | % (at your option) any later version. 34 | % 35 | % This program is distributed in the hope that it will be useful, 36 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 37 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 | % GNU General Public License for more details. 39 | % 40 | % You should have received a copy of the GNU General Public License 41 | % along with this program; if not, write to the Free Software 42 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 43 | 44 | % 05-08-97 fixed frequency bound computation -sm 45 | % 10-22-97 added MINFREQ tests -sm 46 | % 12-05-00 added error() calls -sm 47 | % 01-25-02 reformated help & license, added links -ad 48 | % 03-20-12 added firtype option -cb 49 | 50 | function [smoothdata,filtwts] = firpass(data,srate,locutoff,hicutoff,epochframes,filtorder,revfilt,firtype,causal) 51 | 52 | if nargin<4 53 | fprintf(''); 54 | help eegfilt 55 | return 56 | end 57 | 58 | %if ~exist('firls') 59 | % error('*** eegfilt() requires the signal processing toolbox. ***'); 60 | %end 61 | 62 | [chans frames] = size(data); 63 | if chans > 1 & frames == 1, 64 | help eegfilt 65 | error('input data should be a row vector.'); 66 | end 67 | nyq = srate*0.5; % Nyquist frequency 68 | %MINFREQ = 0.1/nyq; 69 | MINFREQ = 0; 70 | 71 | minfac = 3; % this many (lo)cutoff-freq cycles in filter 72 | min_filtorder = 15; % minimum filter length 73 | trans = 0.15; % fractional width of transition zones 74 | 75 | if locutoff>0 & hicutoff > 0 & locutoff > hicutoff, 76 | error('locutoff > hicutoff ???\n'); 77 | end 78 | if locutoff < 0 | hicutoff < 0, 79 | error('locutoff | hicutoff < 0 ???\n'); 80 | end 81 | 82 | if locutoff>nyq, 83 | error('Low cutoff frequency cannot be > srate/2'); 84 | end 85 | 86 | if hicutoff>nyq 87 | error('High cutoff frequency cannot be > srate/2'); 88 | end 89 | 90 | if nargin<6 91 | filtorder = 0; 92 | end 93 | if nargin<7 94 | revfilt = 0; 95 | end 96 | if nargin<8 97 | firtype = 'firls'; 98 | end 99 | if nargin<9 100 | causal = 0; 101 | end 102 | 103 | if strcmp(firtype, 'firls') 104 | warning('Using firls to estimate filter coefficients. We recommend that you use fir1 instead, which yields larger attenuation. In future, fir1 will be used by default!'); 105 | end 106 | 107 | if isempty(filtorder) | filtorder==0, 108 | if locutoff>0, 109 | filtorder = minfac*fix(srate/locutoff); 110 | elseif hicutoff>0, 111 | filtorder = minfac*fix(srate/hicutoff); 112 | end 113 | 114 | if filtorder < min_filtorder 115 | filtorder = min_filtorder; 116 | end 117 | end 118 | 119 | if nargin<5 120 | epochframes = 0; 121 | end 122 | if epochframes ==0, 123 | epochframes = frames; % default 124 | end 125 | epochs = fix(frames/epochframes); 126 | if epochs*epochframes ~= frames, 127 | error('epochframes does not divide frames.\n'); 128 | end 129 | 130 | if filtorder*3 > epochframes, % Matlab filtfilt() restriction 131 | fprintf('eegfilt(): filter order is %d. ',filtorder); 132 | error('epochframes must be at least 3 times the filtorder.'); 133 | end 134 | if (1+trans)*hicutoff/nyq > 1 135 | error('high cutoff frequency too close to Nyquist frequency'); 136 | end; 137 | 138 | if locutoff > 0 & hicutoff > 0, % bandpass filter 139 | if revfilt 140 | fprintf('eegfilt() - performing %d-point notch filtering.\n',filtorder); 141 | else 142 | fprintf('eegfilt() - performing %d-point bandpass filtering.\n',filtorder); 143 | end; 144 | fprintf(' If a message, ''Matrix is close to singular or badly scaled,'' appears,\n'); 145 | fprintf(' then Matlab has failed to design a good filter. As a workaround, \n'); 146 | fprintf(' for band-pass filtering, first highpass the data, then lowpass it.\n'); 147 | if strcmp(firtype, 'firls') 148 | f=[MINFREQ (1-trans)*locutoff/nyq locutoff/nyq hicutoff/nyq (1+trans)*hicutoff/nyq 1]; 149 | fprintf('eegfilt() - low transition band width is %1.1g Hz; high trans. band width, %1.1g Hz.\n',(f(3)-f(2))*srate/2, (f(5)-f(4))*srate/2); 150 | m=[0 0 1 1 0 0]; 151 | elseif strcmp(firtype, 'fir1') 152 | filtwts = fir1(filtorder, [locutoff, hicutoff]./(srate/2)); 153 | end 154 | elseif locutoff > 0 % highpass filter 155 | if locutoff/nyq < MINFREQ 156 | error(sprintf('eegfilt() - highpass cutoff freq must be > %g Hz\n\n',MINFREQ*nyq)); 157 | end 158 | fprintf('eegfilt() - performing %d-point highpass filtering.\n',filtorder); 159 | if strcmp(firtype, 'firls') 160 | f=[MINFREQ locutoff*(1-trans)/nyq locutoff/nyq 1]; 161 | fprintf('eegfilt() - highpass transition band width is %1.1g Hz.\n',(f(3)-f(2))*srate/2); 162 | m=[ 0 0 1 1]; 163 | elseif strcmp(firtype, 'fir1') 164 | filtwts = fir1(filtorder, locutoff./(srate/2), 'high'); 165 | end 166 | elseif hicutoff > 0 % lowpass filter 167 | if hicutoff/nyq < MINFREQ 168 | error(sprintf('eegfilt() - lowpass cutoff freq must be > %g Hz',MINFREQ*nyq)); 169 | end 170 | fprintf('eegfilt() - performing %d-point lowpass filtering.\n',filtorder); 171 | if strcmp(firtype, 'firls') 172 | f=[MINFREQ hicutoff/nyq hicutoff*(1+trans)/nyq 1]; 173 | fprintf('eegfilt() - lowpass transition band width is %1.1g Hz.\n',(f(3)-f(2))*srate/2); 174 | m=[ 1 1 0 0]; 175 | elseif strcmp(firtype, 'fir1') 176 | filtwts = fir1(filtorder, hicutoff./(srate/2)); 177 | end 178 | else 179 | error('You must provide a non-0 low or high cut-off frequency'); 180 | end 181 | if revfilt 182 | if strcmp(firtype, 'fir1') 183 | error('Cannot reverse filter using ''fir1'' option'); 184 | else 185 | m = ~m; 186 | end; 187 | end; 188 | 189 | if strcmp(firtype, 'firls') 190 | filtwts = firls(filtorder,f,m); % get FIR filter coefficients 191 | end 192 | 193 | smoothdata = zeros(chans,frames); 194 | for e = 1:epochs % filter each epoch, channel 195 | for c=1:chans 196 | try 197 | if causal 198 | smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filter( filtwts,1,data(c,(e-1)*epochframes+1:e*epochframes)); 199 | else smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filtfilt(filtwts,1,data(c,(e-1)*epochframes+1:e*epochframes)); 200 | end; 201 | catch, 202 | if causal 203 | smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filter( filtwts,1,double(data(c,(e-1)*epochframes+1:e*epochframes))); 204 | else smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filtfilt(filtwts,1,double(data(c,(e-1)*epochframes+1:e*epochframes))); 205 | end; 206 | end; 207 | if epochs == 1 208 | if rem(c,20) ~= 0, fprintf('.'); 209 | else fprintf('%d',c); 210 | end 211 | end 212 | end 213 | end 214 | fprintf('\n'); 215 | -------------------------------------------------------------------------------- /util_funcs/eegfilt.m: -------------------------------------------------------------------------------- 1 | % eegfilt() - (high|low|band)-pass filter data using two-way least-squares 2 | % FIR filtering. Optionally uses the window method instead of 3 | % least-squares. Multiple data channels and epochs supported. 4 | % Requires the MATLAB Signal Processing Toolbox. 5 | % Usage: 6 | % >> [smoothdata] = eegfilt(data,srate,locutoff,hicutoff); 7 | % >> [smoothdata,filtwts] = eegfilt(data,srate,locutoff,hicutoff, ... 8 | % epochframes,filtorder,revfilt,firtype,causal); 9 | % Inputs: 10 | % data = (channels,frames*epochs) data to filter 11 | % srate = data sampling rate (Hz) 12 | % locutoff = low-edge frequency in pass band (Hz) {0 -> lowpass} 13 | % hicutoff = high-edge frequency in pass band (Hz) {0 -> highpass} 14 | % epochframes = frames per epoch (filter each epoch separately {def/0: data is 1 epoch} 15 | % filtorder = length of the filter in points {default 3*fix(srate/locutoff)} 16 | % revfilt = [0|1] reverse filter (i.e. bandpass filter to notch filter). {default 0} 17 | % firtype = 'firls'|'fir1' {'firls'} 18 | % causal = [0|1] use causal filter if set to 1 (default 0) 19 | % 20 | % Outputs: 21 | % smoothdata = smoothed data 22 | % filtwts = filter coefficients [smoothdata <- filtfilt(filtwts,1,data)] 23 | % 24 | % See also: firls(), filtfilt() 25 | 26 | % Author: Scott Makeig, Arnaud Delorme, Clemens Brunner SCCN/INC/UCSD, La Jolla, 1997 27 | 28 | % Copyright (C) 4-22-97 from bandpass.m Scott Makeig, SCCN/INC/UCSD, scott@sccn.ucsd.edu 29 | % 30 | % This program is free software; you can redistribute it and/or modify 31 | % it under the terms of the GNU General Public License as published by 32 | % the Free Software Foundation; either version 2 of the License, or 33 | % (at your option) any later version. 34 | % 35 | % This program is distributed in the hope that it will be useful, 36 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 37 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 | % GNU General Public License for more details. 39 | % 40 | % You should have received a copy of the GNU General Public License 41 | % along with this program; if not, write to the Free Software 42 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 43 | 44 | % 05-08-97 fixed frequency bound computation -sm 45 | % 10-22-97 added MINFREQ tests -sm 46 | % 12-05-00 added error() calls -sm 47 | % 01-25-02 reformated help & license, added links -ad 48 | % 03-20-12 added firtype option -cb 49 | 50 | function [smoothdata,filtwts] = eegfilt(data,srate,locutoff,hicutoff,epochframes,filtorder,revfilt,firtype,causal) 51 | 52 | if nargin<4 53 | fprintf(''); 54 | help eegfilt 55 | return 56 | end 57 | 58 | %if ~exist('firls') 59 | % error('*** eegfilt() requires the signal processing toolbox. ***'); 60 | %end 61 | 62 | [chans frames] = size(data); 63 | if chans > 1 & frames == 1, 64 | help eegfilt 65 | error('input data should be a row vector.'); 66 | end 67 | nyq = srate*0.5; % Nyquist frequency 68 | %MINFREQ = 0.1/nyq; 69 | MINFREQ = 0; 70 | 71 | minfac = 3; % this many (lo)cutoff-freq cycles in filter 72 | min_filtorder = 15; % minimum filter length 73 | trans = 0.15; % fractional width of transition zones 74 | 75 | if locutoff>0 & hicutoff > 0 & locutoff > hicutoff, 76 | error('locutoff > hicutoff ???\n'); 77 | end 78 | if locutoff < 0 | hicutoff < 0, 79 | error('locutoff | hicutoff < 0 ???\n'); 80 | end 81 | 82 | if locutoff>nyq, 83 | error('Low cutoff frequency cannot be > srate/2'); 84 | end 85 | 86 | if hicutoff>nyq 87 | error('High cutoff frequency cannot be > srate/2'); 88 | end 89 | 90 | if nargin<6 91 | filtorder = 0; 92 | end 93 | if nargin<7 94 | revfilt = 0; 95 | end 96 | if nargin<8 97 | firtype = 'firls'; 98 | end 99 | if nargin<9 100 | causal = 0; 101 | end 102 | 103 | if strcmp(firtype, 'firls') 104 | %warning('Using firls to estimate filter coefficients. We recommend that you use fir1 instead, which yields larger attenuation. In future, fir1 will be used by default!'); 105 | end 106 | 107 | if isempty(filtorder) | filtorder==0, 108 | if locutoff>0, 109 | filtorder = minfac*fix(srate/locutoff); 110 | elseif hicutoff>0, 111 | filtorder = minfac*fix(srate/hicutoff); 112 | end 113 | 114 | if filtorder < min_filtorder 115 | filtorder = min_filtorder; 116 | end 117 | end 118 | 119 | if nargin<5 120 | epochframes = 0; 121 | end 122 | if epochframes ==0, 123 | epochframes = frames; % default 124 | end 125 | epochs = fix(frames/epochframes); 126 | if epochs*epochframes ~= frames, 127 | error('epochframes does not divide frames.\n'); 128 | end 129 | 130 | if filtorder*3 > epochframes, % Matlab filtfilt() restriction 131 | fprintf('eegfilt(): filter order is %d. ',filtorder); 132 | error('epochframes must be at least 3 times the filtorder.'); 133 | end 134 | if (1+trans)*hicutoff/nyq > 1 135 | error('high cutoff frequency too close to Nyquist frequency'); 136 | end; 137 | 138 | if locutoff > 0 & hicutoff > 0, % bandpass filter 139 | if revfilt 140 | %fprintf('eegfilt() - performing %d-point notch filtering.\n',filtorder); 141 | else 142 | % fprintf('eegfilt() - performing %d-point bandpass filtering.\n',filtorder); 143 | end; 144 | % fprintf(' If a message, ''Matrix is close to singular or badly scaled,'' appears,\n'); 145 | % fprintf(' then Matlab has failed to design a good filter. As a workaround, \n'); 146 | % fprintf(' for band-pass filtering, first highpass the data, then lowpass it.\n'); 147 | if strcmp(firtype, 'firls') 148 | f=[MINFREQ (1-trans)*locutoff/nyq locutoff/nyq hicutoff/nyq (1+trans)*hicutoff/nyq 1]; 149 | % fprintf('eegfilt() - low transition band width is %1.1g Hz; high trans. band width, %1.1g Hz.\n',(f(3)-f(2))*srate/2, (f(5)-f(4))*srate/2); 150 | m=[0 0 1 1 0 0]; 151 | elseif strcmp(firtype, 'fir1') 152 | filtwts = fir1(filtorder, [locutoff, hicutoff]./(srate/2)); 153 | end 154 | elseif locutoff > 0 % highpass filter 155 | if locutoff/nyq < MINFREQ 156 | error(sprintf('eegfilt() - highpass cutoff freq must be > %g Hz\n\n',MINFREQ*nyq)); 157 | end 158 | fprintf('eegfilt() - performing %d-point highpass filtering.\n',filtorder); 159 | if strcmp(firtype, 'firls') 160 | f=[MINFREQ locutoff*(1-trans)/nyq locutoff/nyq 1]; 161 | fprintf('eegfilt() - highpass transition band width is %1.1g Hz.\n',(f(3)-f(2))*srate/2); 162 | m=[ 0 0 1 1]; 163 | elseif strcmp(firtype, 'fir1') 164 | filtwts = fir1(filtorder, locutoff./(srate/2), 'high'); 165 | end 166 | elseif hicutoff > 0 % lowpass filter 167 | if hicutoff/nyq < MINFREQ 168 | error(sprintf('eegfilt() - lowpass cutoff freq must be > %g Hz',MINFREQ*nyq)); 169 | end 170 | fprintf('eegfilt() - performing %d-point lowpass filtering.\n',filtorder); 171 | if strcmp(firtype, 'firls') 172 | f=[MINFREQ hicutoff/nyq hicutoff*(1+trans)/nyq 1]; 173 | fprintf('eegfilt() - lowpass transition band width is %1.1g Hz.\n',(f(3)-f(2))*srate/2); 174 | m=[ 1 1 0 0]; 175 | elseif strcmp(firtype, 'fir1') 176 | filtwts = fir1(filtorder, hicutoff./(srate/2)); 177 | end 178 | else 179 | error('You must provide a non-0 low or high cut-off frequency'); 180 | end 181 | if revfilt 182 | if strcmp(firtype, 'fir1') 183 | error('Cannot reverse filter using ''fir1'' option'); 184 | else 185 | m = ~m; 186 | end; 187 | end; 188 | 189 | if strcmp(firtype, 'firls') 190 | filtwts = firls(filtorder,f,m); % get FIR filter coefficients 191 | end 192 | 193 | smoothdata = zeros(chans,frames); 194 | for e = 1:epochs % filter each epoch, channel 195 | for c=1:chans 196 | try 197 | if causal 198 | smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filter( filtwts,1,data(c,(e-1)*epochframes+1:e*epochframes)); 199 | else smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filtfilt(filtwts,1,data(c,(e-1)*epochframes+1:e*epochframes)); 200 | end; 201 | catch, 202 | if causal 203 | smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filter( filtwts,1,double(data(c,(e-1)*epochframes+1:e*epochframes))); 204 | else smoothdata(c,(e-1)*epochframes+1:e*epochframes) = filtfilt(filtwts,1,double(data(c,(e-1)*epochframes+1:e*epochframes))); 205 | end; 206 | end; 207 | if epochs == 1 208 | % if rem(c,20) ~= 0, fprintf('.'); 209 | % else fprintf('%d',c); 210 | % end 211 | end 212 | end 213 | end 214 | %fprintf('\n'); 215 | -------------------------------------------------------------------------------- /util_funcs/pop_eegfiltnew.m: -------------------------------------------------------------------------------- 1 | % pop_eegfiltnew() - Filter data using Hamming windowed sinc FIR filter 2 | % 3 | % Usage: 4 | % >> [EEG, com, b] = pop_eegfiltnew(EEG); % pop-up window mode 5 | % >> [EEG, com, b] = pop_eegfiltnew(EEG, locutoff, hicutoff, filtorder, 6 | % revfilt, usefft, plotfreqz, minphase); 7 | % 8 | % Inputs: 9 | % EEG - EEGLAB EEG structure 10 | % locutoff - lower edge of the frequency pass band (Hz) 11 | % {[]/0 -> lowpass} 12 | % hicutoff - higher edge of the frequency pass band (Hz) 13 | % {[]/0 -> highpass} 14 | % 15 | % Optional inputs: 16 | % filtorder - filter order (filter length - 1). Mandatory even 17 | % revfilt - [0|1] invert filter (from bandpass to notch filter) 18 | % {default 0 (bandpass)} 19 | % usefft - ignored (backward compatibility only) 20 | % plotfreqz - [0|1] plot filter's frequency and phase response 21 | % {default 0} 22 | % minphase - scalar boolean minimum-phase converted causal filter 23 | % {default false} 24 | % 25 | % Outputs: 26 | % EEG - filtered EEGLAB EEG structure 27 | % com - history string 28 | % b - filter coefficients 29 | % 30 | % Note: 31 | % pop_eegfiltnew is intended as a replacement for the deprecated 32 | % pop_eegfilt function. Required filter order/transition band width is 33 | % estimated with the following heuristic in default mode: transition band 34 | % width is 25% of the lower passband edge, but not lower than 2 Hz, where 35 | % possible (for bandpass, highpass, and bandstop) and distance from 36 | % passband edge to critical frequency (DC, Nyquist) otherwise. Window 37 | % type is hardcoded to Hamming. Migration to windowed sinc FIR filters 38 | % (pop_firws) is recommended. pop_firws allows user defined window type 39 | % and estimation of filter order by user defined transition band width. 40 | % 41 | % Author: Andreas Widmann, University of Leipzig, 2012 42 | % 43 | % See also: 44 | % firfilt, firws, windows 45 | 46 | %123456789012345678901234567890123456789012345678901234567890123456789012 47 | 48 | % Copyright (C) 2008 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 49 | % 50 | % This program is free software; you can redistribute it and/or modify 51 | % it under the terms of the GNU General Public License as published by 52 | % the Free Software Foundation; either version 2 of the License, or 53 | % (at your option) any later version. 54 | % 55 | % This program is distributed in the hope that it will be useful, 56 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 57 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 58 | % GNU General Public License for more details. 59 | % 60 | % You should have received a copy of the GNU General Public License 61 | % along with this program; if not, write to the Free Software 62 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 63 | 64 | function [EEG, com, b] = pop_eegfiltnew(EEG, locutoff, hicutoff, filtorder, revfilt, usefft, plotfreqz, minphase) 65 | 66 | com = ''; 67 | 68 | if nargin < 1 69 | help pop_eegfiltnew; 70 | return 71 | end 72 | if isempty(EEG.data) 73 | error('Cannot filter empty dataset.'); 74 | end 75 | 76 | % GUI 77 | if nargin < 2 78 | 79 | geometry = {[3, 1], [3, 1], [3, 1], 1, 1, 1, 1}; 80 | geomvert = [1 1 1 2 1 1 1]; 81 | 82 | uilist = {{'style', 'text', 'string', 'Lower edge of the frequency pass band (Hz)'} ... 83 | {'style', 'edit', 'string', ''} ... 84 | {'style', 'text', 'string', 'Higher edge of the frequency pass band (Hz)'} ... 85 | {'style', 'edit', 'string', ''} ... 86 | {'style', 'text', 'string', 'FIR Filter order (Mandatory even. Default is automatic*)'} ... 87 | {'style', 'edit', 'string', ''} ... 88 | {'style', 'text', 'string', {'*See help text for a description of the default filter order heuristic.', 'Manual definition is recommended.'}} ... 89 | {'style', 'checkbox', 'string', 'Notch filter the data instead of pass band', 'value', 0} ... 90 | {'Style', 'checkbox', 'String', 'Use minimum-phase converted causal filter (non-linear!; beta)', 'Value', 0} ... 91 | {'style', 'checkbox', 'string', 'Plot frequency response', 'value', 1}}; 92 | 93 | result = inputgui('geometry', geometry, 'geomvert', geomvert, 'uilist', uilist, 'title', 'Filter the data -- pop_eegfiltnew()', 'helpcom', 'pophelp(''pop_eegfiltnew'')'); 94 | 95 | if isempty(result), return; end 96 | 97 | locutoff = str2num(result{1}); 98 | hicutoff = str2num(result{2}); 99 | filtorder = str2num(result{3}); 100 | revfilt = result{4}; 101 | minphase = result{5}; 102 | plotfreqz = result{6}; 103 | usefft = []; 104 | 105 | else 106 | 107 | if nargin < 3 108 | hicutoff = []; 109 | end 110 | if nargin < 4 111 | filtorder = []; 112 | end 113 | if nargin < 5 || isempty(revfilt) 114 | revfilt = 0; 115 | end 116 | if nargin < 6 117 | usefft = []; 118 | elseif usefft == 1 119 | error('FFT filtering not supported. Argument is provided for backward compatibility in command line mode only.') 120 | end 121 | if nargin < 7 || isempty(plotfreqz) 122 | plotfreqz = 0; 123 | end 124 | if nargin < 8 || isempty(minphase) 125 | minphase = 0; 126 | end 127 | 128 | end 129 | 130 | % Constants 131 | TRANSWIDTHRATIO = 0.25; 132 | fNyquist = EEG.srate / 2; 133 | 134 | % Check arguments 135 | if locutoff == 0, locutoff = []; end 136 | if hicutoff == 0, hicutoff = []; end 137 | if isempty(hicutoff) % Convert highpass to inverted lowpass 138 | hicutoff = locutoff; 139 | locutoff = []; 140 | revfilt = ~revfilt; 141 | end 142 | edgeArray = sort([locutoff hicutoff]); 143 | 144 | if isempty(edgeArray) 145 | error('Not enough input arguments.'); 146 | end 147 | if any(edgeArray < 0 | edgeArray >= fNyquist) 148 | error('Cutoff frequency out of range'); 149 | end 150 | 151 | if ~isempty(filtorder) && (filtorder < 2 || mod(filtorder, 2) ~= 0) 152 | error('Filter order must be a real, even, positive integer.') 153 | end 154 | 155 | % Max stop-band width 156 | maxTBWArray = edgeArray; % Band-/highpass 157 | if revfilt == 0 % Band-/lowpass 158 | maxTBWArray(end) = fNyquist - edgeArray(end); 159 | elseif length(edgeArray) == 2 % Bandstop 160 | maxTBWArray = diff(edgeArray) / 2; 161 | end 162 | maxDf = min(maxTBWArray); 163 | 164 | % Transition band width and filter order 165 | if isempty(filtorder) 166 | 167 | % Default filter order heuristic 168 | if revfilt == 1 % Highpass and bandstop 169 | df = min([max([maxDf * TRANSWIDTHRATIO 2]) maxDf]); 170 | else % Lowpass and bandpass 171 | df = min([max([edgeArray(1) * TRANSWIDTHRATIO 2]) maxDf]); 172 | end 173 | 174 | filtorder = 3.3 / (df / EEG.srate); % Hamming window 175 | filtorder = ceil(filtorder / 2) * 2; % Filter order must be even. 176 | 177 | else 178 | 179 | df = 3.3 / filtorder * EEG.srate; % Hamming window 180 | filtorderMin = ceil(3.3 ./ ((maxDf * 2) / EEG.srate) / 2) * 2; 181 | filtorderOpt = ceil(3.3 ./ (maxDf / EEG.srate) / 2) * 2; 182 | if filtorder < filtorderMin 183 | error('Filter order too low. Minimum required filter order is %d. For better results a minimum filter order of %d is recommended.', filtorderMin, filtorderOpt) 184 | elseif filtorder < filtorderOpt 185 | warning('firfilt:filterOrderLow', 'Transition band is wider than maximum stop-band width. For better results a minimum filter order of %d is recommended. Reported might deviate from effective -6dB cutoff frequency.', filtorderOpt) 186 | end 187 | 188 | end 189 | 190 | filterTypeArray = {'lowpass', 'bandpass'; 'highpass', 'bandstop (notch)'}; 191 | fprintf('pop_eegfiltnew() - performing %d point %s filtering.\n', filtorder + 1, filterTypeArray{revfilt + 1, length(edgeArray)}) 192 | fprintf('pop_eegfiltnew() - transition band width: %.4g Hz\n', df) 193 | fprintf('pop_eegfiltnew() - passband edge(s): %s Hz\n', mat2str(edgeArray)) 194 | 195 | % Passband edge to cutoff (transition band center; -6 dB) 196 | dfArray = {df, [-df, df]; -df, [df, -df]}; 197 | cutoffArray = edgeArray + dfArray{revfilt + 1, length(edgeArray)} / 2; 198 | fprintf('pop_eegfiltnew() - cutoff frequency(ies) (-6 dB): %s Hz\n', mat2str(cutoffArray)) 199 | 200 | % Window 201 | winArray = windows('hamming', filtorder + 1); 202 | 203 | % Filter coefficients 204 | if revfilt == 1 205 | filterTypeArray = {'high', 'stop'}; 206 | b = firws(filtorder, cutoffArray / fNyquist, filterTypeArray{length(cutoffArray)}, winArray); 207 | else 208 | b = firws(filtorder, cutoffArray / fNyquist, winArray); 209 | end 210 | 211 | if minphase 212 | disp('pop_eegfiltnew() - converting filter to minimum-phase (non-linear!)'); 213 | b = minphaserceps(b); 214 | end 215 | 216 | % Plot frequency response 217 | if plotfreqz 218 | freqz(b, 1, 8192, EEG.srate); 219 | end 220 | 221 | % Filter 222 | if minphase 223 | disp('pop_eegfiltnew() - filtering the data (causal)'); 224 | EEG = firfiltsplit(EEG, b, 1); 225 | else 226 | disp('pop_eegfiltnew() - filtering the data (zero-phase)'); 227 | EEG = firfilt(EEG, b); 228 | end 229 | 230 | 231 | % History string 232 | com = sprintf('%s = pop_eegfiltnew(%s, %s, %s, %s, %s, %s, %s);', inputname(1), inputname(1), mat2str(locutoff), mat2str(hicutoff), mat2str(filtorder), mat2str(revfilt), mat2str(usefft), mat2str(plotfreqz)); 233 | 234 | end 235 | -------------------------------------------------------------------------------- /scripts/corticoid_analysis.m: -------------------------------------------------------------------------------- 1 | % network spike aggregate analysis over all recordings 2 | %cd /Users/rgao/Documents/Data/Muotri/Pri_Corticoids/ORGANOIDS 3 | %cd('/Volumes/My Passport for Mac/Dish/CTC') 4 | ker_win = [-500,2500]; 5 | smo_len = 100; 6 | LFP_smo = {}; 7 | %gaussian smoothing window to avoid harmonics in PSD 8 | smo_mask = gausswin(smo_len)./sum(gausswin(smo_len)); 9 | fs = 12500; 10 | fs_ds = 1000; 11 | min_event_spike = 1; % peak is at least 1spk tall to be considered event 12 | %recs = [1:25 27:33 36]; 13 | wells = 1:12; 14 | 15 | %% collect aggregate data 16 | % features: 17 | % - event driven population spiking, LFP kernel 18 | % - aggregate PSDs 19 | 20 | %F = dir('CTC_*'); 21 | %reorder folders because 2017 recordings are in front 22 | %F = [F(15:end);F(1:14)]; 23 | 24 | for f=1:length(F) 25 | cd(F(f).name) 26 | disp(F(f).name) 27 | load LFP_Sp.mat LFP spikes spike_cnt t_s t_ds 28 | 29 | %recording time 30 | T{f} = t_ds(end); 31 | 32 | %binarize spikes & compute network spiking 33 | disp('Bin Spikes..') 34 | bsp = binarize_spikes(ceil(t_ds(end)), fs,spikes,fs_ds); 35 | nws = squeeze(sum(bsp,2))'; nws = nws(1:length(t_ds),:); 36 | 37 | disp('Kernel Calculations..') 38 | for well=wells 39 | if isempty(LFP{well}) 40 | %skip well if there's no LFP 41 | disp(sprintf('Well %i skipped, no data.', well)) 42 | continue 43 | end 44 | %smooth network spike vector 45 | nws_smo{f}(:,well) = conv(nws(:,well), smo_mask,'same'); 46 | 47 | %find network bursts based on peaks in smoothed well spikes 48 | [PK,IND] =findpeaks(nws_smo{f}(:,well), 'minpeakheight', max(max(nws_smo{f}(:,well))*0.75,min_event_spike), 'minpeakdistance',fs_ds); 49 | 50 | % well 10 in some recordings have missing channels, normalize to 64 51 | adj = sum(1-all(LFP{well}==0))/64; 52 | if adj~=1 53 | disp(sprintf('Well %i kernel adjusted.',well)) 54 | end 55 | kernels{f}{well} = collect_spikes(nws_smo{f}(:,well),[],IND,ker_win)/adj; 56 | 57 | %record spike stamps 58 | kerTimes{f}{well} = IND./fs_ds; 59 | 60 | %collect LFP kernel at those time points 61 | for chan = 1:64 62 | if all(LFP{well}(:,chan)==0) 63 | %LFP being all zero screws up the event detection, so just 64 | %manually fill in all 0s for the event 65 | %this is a patchy solution but will do for now 66 | fill_shape = size(kernels{f}{well}); 67 | LFP_ker_smo{f}{well}(:,:,chan) = zeros(fill_shape(1:2)); 68 | LFP_ker{f}{well}(:,:,chan) = zeros(fill_shape(1:2)); 69 | else 70 | %make smoothed LFP 71 | LFP_smo = conv(LFP{well}(:,chan), smo_mask,'same'); 72 | % get LFP kernel 73 | LFP_ker_smo{f}{well}(:,:,chan) = collect_spikes(LFP_smo,[],IND,ker_win); 74 | LFP_ker{f}{well}(:,:,chan) = collect_spikes(LFP{well}(:,chan),[],IND,ker_win); 75 | end 76 | end 77 | 78 | %calculate PSD 79 | PSDw{f}{well} = pwelch(LFP{well},fs_ds*2,fs_ds*1.5,fs_ds*2,fs_ds); 80 | %PSDm{f}{well} = mPSD(LFP{well}, fs_ds, fs_ds*2, fs_ds/2, fs_ds/2); 81 | end 82 | cd .. 83 | end 84 | %save aggregate.mat ker_win T nws_smo kernels kerTimes LFP_ker LFP_ker_smo PSDw PSDm -v7.3 85 | save aggregate.mat ker_win T nws_smo kernels kerTimes LFP_ker LFP_ker_smo PSDw -v7.3 86 | 87 | %% secondary processing on event kernel 88 | cd /Users/rgao/Documents/Data/Muotri/Pri_Corticoids 89 | load aggregate.mat 90 | %% event stats & LFP self similarity 91 | wells = 5:12; 92 | for well = wells 93 | for rec = 1:length(kernels) 94 | n_events = size(kernels{rec}{well},2); 95 | 96 | % mean and std of inter event latency 97 | ker_latM(rec,well-4) = mean(diff(kerTimes{rec}{well})); 98 | ker_latS(rec,well-4) = std(diff(kerTimes{rec}{well})); 99 | 100 | % event count 101 | ker_cnt(rec, well-4) = n_events; 102 | 103 | % mean self correlation 104 | if n_events<2 105 | %only 1 event, don't compute 106 | self_cor(rec,well-4) = NaN; 107 | self_cor_LFP(rec,well-4,:) = NaN; 108 | else 109 | %more than 1 event, compute pair-wise correlation and average 110 | self_cor(rec,well-4) = sum(sum(triu(corr(kernels{rec}{well}),1)))/nchoosek(n_events,2); 111 | 112 | % LFP kernel correlation 113 | for chan = 1:64 114 | self_cor_LFP(rec,well-4, chan) = sum(sum(triu(corr(LFP_ker_smo{rec}{well}(:,:,chan)),1)))/nchoosek(n_events,2); 115 | end 116 | end 117 | 118 | % total spiking under the curve 119 | ker_totalM(rec, well-4) = mean(sum(kernels{rec}{well})); 120 | 121 | % initial peak amplitude 122 | peakAmp = max(kernels{rec}{well}); 123 | ker_peakM(rec, well-4) = mean(peakAmp); 124 | ker_peakS(rec, well-4) = std(peakAmp); 125 | end 126 | end 127 | 128 | %% event subpeaks analyses & LFP spatial corr 129 | wells = 5:12; 130 | subpks = {}; 131 | subpksT = {}; 132 | FWHM = {}; 133 | nsubpks = zeros(length(kernels),8); 134 | LFP_spatialcorr = zeros(length(kernels),8); 135 | sp2take=5; 136 | p2P = zeros(length(kernels),8,sp2take); 137 | p2T = zeros(length(kernels),8,sp2take); 138 | for well = wells 139 | for rec = 1:length(kernels) 140 | 141 | n_events = size(kernels{rec}{well},2); 142 | % subpeak processing 143 | peakAmp = max(kernels{rec}{well}); 144 | 145 | %peak finding 146 | subpks{rec,well-4} = nan(10,n_events); 147 | subpksT{rec,well-4} = nan(10,n_events); 148 | nsubs = zeros(1,n_events); 149 | FWHM{rec,well-4} = zeros(1,n_events); 150 | 151 | temp_corr = zeros(64,64,n_events); 152 | for event = 1:n_events 153 | %sub-peak finding 154 | %normalize by first peak 155 | [pks, pkts] = findpeaks(kernels{rec}{well}(:,event)/peakAmp(event),... 156 | 'minpeakheight', 1/4, ... 157 | 'minpeakwidth', 50, ... 158 | 'npeaks',10, ... 159 | 'minpeakdistance',200, ... 160 | 'minpeakprominence',1/peakAmp(event)); 161 | 162 | subpks{rec,well-4}(1:length(pks), event) = pks; %subpeak height 163 | subpksT{rec,well-4}(1:length(pks), event) = pkts; %subpeak time 164 | nsubs(event) = length(pks); %number of subpeaks 165 | 166 | %FWHM of initial peak 167 | if ~isempty(pkts) 168 | onset = find(kernels{rec}{well}(1:500,event)/peakAmp(event)<0.5, 1, 'last'); 169 | offset = pkts(1)+find(kernels{rec}{well}(pkts(1)+1:end,event)/peakAmp(event)<0.5, 1, 'first'); 170 | FWHM{rec,well-4}(event) = offset-onset; 171 | end 172 | 173 | % LFP spatial corr 174 | temp_corr(:,:,event) = abs(corr(squeeze(LFP_ker_smo{rec}{well}(:,event,:)))); 175 | end 176 | 177 | nsubpks(rec,well-4) = mean(nsubs); 178 | for pk = 1:sp2take 179 | p2P(rec, well-4, pk) = nanmedian(subpks{rec,well-4}(pk,:)); 180 | p2T(rec, well-4, pk) = nanmedian(subpksT{rec,well-4}(pk,:)); 181 | end 182 | 183 | %spatial correlation 184 | LFP_spatialcorr(rec, well-4) = (sum(sum(mean(temp_corr,3)))-64)/(64^2-64); 185 | end 186 | end 187 | 188 | %% PSD analysis of LFP kernel 189 | dF = 0.5; 190 | osc_freq = 2.5:dF:4.5; 191 | osc_idx = osc_freq/dF+1; 192 | norm_freq = (0.5:dF:10); 193 | %norm_freq = [2, 5]; 194 | norm_idx= norm_freq/dF+1; 195 | fit_freq = 0.5:dF:20; 196 | slope_range = {1:dF:10, 10:dF:30, 30:dF:50}; 197 | warning('off') 198 | osc_power = zeros(length(kernels),length(wells),64); 199 | osc_power_foof = zeros(length(kernels),length(wells),64); 200 | slopes = zeros(length(kernels),length(wells),64,length(slope_range)); 201 | 202 | for well = wells 203 | disp(well) 204 | for rec = 1:length(kernels) 205 | 206 | osc_power(rec, well-4,:) = mean(log10(PSDw{rec}{well}(osc_idx,:)))-mean(log10(PSDw{rec}{well}(norm_idx,:))); 207 | 208 | sfit = robfitPSD(PSDw{rec}{well},fit_freq,dF); 209 | osc_power_foof(rec, well-4,:) = mean(log10(PSDw{rec}{well}(osc_idx,:))) - (mean(log10(osc_freq)'*sfit(:,2)')+sfit(:,1)'); 210 | 211 | % for slp = 1:length(slope_range) 212 | % sfit = robfitPSD(PSDw{rec}{well},slope_range{slp},dF); 213 | % slopes(rec,well-4,:,slp) = sfit(:,2); 214 | % end 215 | end 216 | end 217 | 218 | %% per well regression 219 | % fit order 2 polynomial on 6 features, or a subset of the 6 220 | load names.mat 221 | %exclude recordings not done after exchanging media, or pharmacology 222 | %exclusion = [2 5 9 12 15 25 26 34 35 39]; 223 | exclusion = [5 9 12 15 25 26 34 35 36 40]; 224 | recs = setdiff(1:length(dates),exclusion); 225 | dayVec = zeros(1,length(recs)); 226 | for day = 1:length(recs) 227 | %parse numerical date 228 | date = dates(recs(day)).name(5:end); 229 | dayVec(day) = datenum(str2num(date(5:6)), str2num(date(1:2)), str2num(date(3:4))); 230 | disp(date) 231 | end 232 | recDays = dayVec-dayVec(1)+1; 233 | %% 234 | features = (cat(3, ker_latM, ker_latS./ker_latM, nsubpks, ker_peakM, self_cor, mean(osc_power_foof,3))); 235 | features = features(:,:,[1 4 6]); 236 | features = cat(3,features,features.^2); 237 | SSq = zeros(8, size(features,3)+1); 238 | PV = zeros(8, size(features,3)+1); 239 | FITNESS = zeros(8,2); 240 | for well = 1:8 241 | trainX = squeeze(features(recs,well,:)); 242 | mdl = fitlm(trainX, recDays); 243 | FITNESS(well,:) = [mdl.Rsquared.Adjusted, sqrt(mdl.MSE)]; 244 | A = anova(mdl); 245 | PV(well,:) = A.pValue; 246 | SSq(well,:) = A.MeanSq; 247 | end 248 | disp('---Within-Well Regression---') 249 | disp(sprintf('Mean R^2: %f; Mean RMSE: %f',mean(FITNESS))); 250 | %% cross-well classifier analysis 251 | %features = (cat(3, ker_latM, ker_latS./ker_latM, nsubpks, ker_peakM, self_cor, mean(osc_power_foof,3))); 252 | %features = cat(3,features(:,:,[1 4 6])); 253 | %features = cat(3,features,features.^2); 254 | N_train = 7; %6:2 split 255 | N_test = 8-N_train; 256 | Ycat = repmat(recDays,1,N_train); 257 | groups = nchoosek(1:8,N_train); 258 | train_fitness = zeros(size(groups,1),2); 259 | test_fitness = zeros(size(groups,1),2); 260 | SSq = zeros(size(groups,1),size(features,3)+1); 261 | for comb = 1:size(groups,1) 262 | trainX = reshape(features(recs,groups(comb,:),:),length(recs)*N_train,[]); 263 | testX = reshape(features(recs,setdiff(1:8,groups(comb,:)),:),length(recs)*N_test,[]); 264 | mdl = fitlm(trainX, Ycat); 265 | Ypred = predict(mdl,testX); 266 | good_idx = find(1-isnan(Ypred)); 267 | train_fitness(comb,:) = [mdl.Rsquared.Adjusted, sqrt(mdl.MSE)]; 268 | test_fitness(comb,:) = [corr(Ycat(good_idx)', Ypred(good_idx)).^2, sqrt(mean((Ycat(good_idx)'-Ypred(good_idx)).^2))]; 269 | AN = anova(mdl); 270 | SSq(comb,:) = AN.MeanSq; 271 | end 272 | disp('---Cross-Well Regression---') 273 | disp(sprintf('Mean R^2: %f; Mean RMSE: %f',mean(test_fitness))); 274 | 275 | -------------------------------------------------------------------------------- /Axion/LoadArgs.m: -------------------------------------------------------------------------------- 1 | classdef LoadArgs < handle 2 | %LOADARGS parser for file loading arguments 3 | 4 | properties (GetAccess = private, Constant = true) 5 | wellVal = 1; 6 | electrodeVal = 2; 7 | timespanVal = 3; 8 | dimensionsVal = 4; 9 | end 10 | 11 | properties (GetAccess = public, Constant = true) 12 | ByPlateDimensions = 1; 13 | ByWellDimensions = 3; 14 | ByElectrodeDimensions = 5; 15 | end 16 | 17 | properties (GetAccess = public, SetAccess = private) 18 | Well 19 | Electrode 20 | Timespan 21 | Dimensions 22 | end 23 | 24 | methods (Static) 25 | 26 | function this = LoadArgs(argin) 27 | % Internal support function for AxisFile Construction 28 | % loadAxISFileParseArgs Parses arguments for file readers 29 | % 30 | % Legal forms: 31 | % LoadArgs(); 32 | % LoadArgs(well); 33 | % LoadArgs(electrode); 34 | % LoadArgs(well, electrode); 35 | % LoadArgs(timespan); 36 | % LoadArgs(well, timespan); 37 | % LoadArgs(electrode, timespan); 38 | % LoadArgs(well, electrode, timespan); 39 | % LoadArgs(dimensions); 40 | % LoadArgs(well, dimensions); 41 | % LoadArgs(electrode, dimensions); 42 | % LoadArgs(well, electrode, dimensions); 43 | % LoadArgs(timespan, dimensions); 44 | % LoadArgs(well, timespan, dimensions); 45 | % LoadArgs(electrode, timespan, dimensions); 46 | % LoadArgs(well, electrode, timespan, dimensions); 47 | % 48 | % Required arguments: 49 | % filename Pathname of the file to load 50 | % 51 | % Optional arguments: 52 | % well String listing which wells (in a multiwell file) to load. 53 | % Format is a comma-delimited string with whitespace ignored, e.g. 54 | % 'A1, B2,C3' limits the data loaded to wells A1, B2, and C3. 55 | % Also acceptable: 'all' to load all wells. 56 | % If this parameter is omitted, all wells are loaded. 57 | % For a single-well file, this parameter is ignored. 58 | % 59 | % electrode Which electrodes to load. Format is either a comma-delimited string 60 | % with whitespace ignored (e.g. '11, 22,33') or a single channel number; 61 | % that is, a number, not part of a string. 62 | % Also acceptable: 'all' to load all channels and 'none', '-1', or -1 63 | % to load no data (returns only header information). 64 | % If this parameter is omitted, all channels are loaded. 65 | % 66 | % timespan Span of time, in seconds, over which to load data. Format is a two-element 67 | % array, [t0 t1], where t0 is the start time and t1 is the end time and both 68 | % are in seconds after the first sample in the file. Samples returned are ones 69 | % that were taken at time >= t0 and <= t1. The beginning of the file 70 | % is at 0 seconds. 71 | % If this parameter is omitted, the data is not filtered based on time. 72 | % 73 | % dimensions Preferred number of dimensions to report the waveforms in. 74 | % Value must be a whole number scalar, and only certain values are allowed: 75 | % 76 | % dimensions = 1 -> ByPlate: returns a vector of Waveform objects, 1 Waveform 77 | % s per signal in the plate 78 | % dimensions = 3 -> ByWell: Cell Array of vectors of waveform 1 Waveform per signal 79 | % in the electrode with size (well Rows) x (well Columns) 80 | % dimensions = 5 -> ByElectrode: Cell Array of vectors of waveform 1 Waveform per . 81 | % signal in the electrode with size (well Rows) x (well Columns) x 82 | % (electrode Columns) x (electrode Rows) 83 | this.Well = []; 84 | this.Electrode = []; 85 | this.Timespan = []; 86 | this.Dimensions = []; 87 | 88 | fLastArg = []; 89 | 90 | fNumArgs = length(argin); 91 | 92 | if fNumArgs > 4 93 | error('LoadArgs:excessArgs', 'Too many arguments specified'); 94 | end 95 | 96 | for i = 1:fNumArgs 97 | fCurrentArg = argin{i}; 98 | 99 | if isempty(fCurrentArg) %ignore empty args 100 | continue 101 | end 102 | 103 | if isempty(fLastArg) 104 | fParseAsWell = LoadArgs.canonical_well_electrode_argument(fCurrentArg, LoadArgs.wellVal); 105 | if ~isempty(fParseAsWell) 106 | % Argument is a well 107 | this.Well = fParseAsWell; 108 | fLastArg = LoadArgs.wellVal; 109 | continue; 110 | end 111 | end 112 | 113 | if isempty(fLastArg) || fLastArg == LoadArgs.wellVal 114 | fParseAsElectrode = LoadArgs.canonical_well_electrode_argument(fCurrentArg, LoadArgs.electrodeVal); 115 | if ~isempty(fParseAsElectrode) 116 | % Argument is an electrode 117 | this.Electrode = fParseAsElectrode; 118 | fLastArg = LoadArgs.electrodeVal; 119 | continue; 120 | end 121 | end 122 | 123 | if isempty(fLastArg) || fLastArg == LoadArgs.wellVal || fLastArg == LoadArgs.electrodeVal 124 | fParseAsTimespan = LoadArgs.canonical_timespan_argument(fCurrentArg); 125 | if ~isempty(fParseAsTimespan) 126 | % Argument is a timespanVal 127 | if isnumeric(fParseAsTimespan) && fParseAsTimespan(2) < fParseAsTimespan(1) 128 | error('load_AxIS_file_parse_args:invalidTimespan', 'Invalid timespan argument: t1 < t0'); 129 | end 130 | 131 | this.Timespan = fParseAsTimespan; 132 | fLastArg = LoadArgs.timespanVal; 133 | continue; 134 | end 135 | end 136 | 137 | if isempty(fLastArg) || i == length(argin) 138 | if isscalar(fCurrentArg) 139 | switch fCurrentArg 140 | case { LoadArgs.ByPlateDimensions, ... 141 | LoadArgs.ByWellDimensions, ... 142 | LoadArgs.ByElectrodeDimensions} 143 | this.Dimensions = fCurrentArg; 144 | otherwise 145 | this.Dimensions = []; 146 | end 147 | fLastArg = LoadArgs.dimensionsVal; 148 | continue; 149 | end 150 | end 151 | 152 | % If we get here, the argument couldn't be parsed 153 | error('load_AxIS_file_parse_args:invalidArg', ['Invalid argument #' num2str(i+1) ' to load_AxIS_file']); 154 | end 155 | 156 | if isempty(this.Well) 157 | % Default: all wells 158 | this.Well = 'all'; 159 | end 160 | 161 | if isempty(this.Electrode) 162 | % Default: all electrodes 163 | this.Electrode = 'all'; 164 | end 165 | 166 | if isempty(this.Timespan) 167 | % Default: all time 168 | this.Timespan = 'all'; 169 | end 170 | 171 | end 172 | 173 | end 174 | 175 | methods(Static, Access = private) 176 | 177 | function aParseOutput = canonical_well_electrode_argument(aArgument, type) 178 | 179 | DELIMITER = ','; 180 | aParseOutput = []; 181 | 182 | % Error-check argument type 183 | if isa(aArgument, 'LoadArgs') && ~(LoadArgs.wellVal || LoadArgs.electrodeVal) 184 | error('canonical_well_electrode_argument:invalidArgType', 'Internal error: Invalid argument type for parsing'); 185 | end 186 | 187 | % Special cases 188 | if strcmpi(aArgument, 'all') 189 | aParseOutput = 'all'; 190 | elseif type == LoadArgs.electrodeVal && isscalar(aArgument) && aArgument == -1 191 | aParseOutput = 'none'; 192 | elseif type == LoadArgs.electrodeVal && isscalar(aArgument) && isnumeric(aArgument) && aArgument > 10 193 | aParseOutput = [floor(aArgument/10) mod(aArgument, 10) ]; 194 | elseif ischar(aArgument) 195 | 196 | if type == LoadArgs.electrodeVal && ( strcmp(strtrim(aArgument), '-1') || strcmpi(aArgument, 'none') ) 197 | aParseOutput = 'none'; 198 | return; 199 | end 200 | 201 | 202 | % Convert well names to upper case 203 | fCanonicalArg = upper(aArgument); 204 | 205 | % Strip whitespace 206 | fCanonicalArg(isspace(fCanonicalArg)) = []; 207 | 208 | % Is it valid? 209 | while ~isempty(fCanonicalArg) 210 | if length(fCanonicalArg) >= 2 && ... 211 | ( (type == LoadArgs.wellVal) && isletter(fCanonicalArg(1)) && LoadArgs.isdigit_ax(fCanonicalArg(2))) || ... 212 | ( (type == LoadArgs.electrodeVal) && LoadArgs.isdigit_ax(fCanonicalArg(1)) && LoadArgs.isdigit_ax(fCanonicalArg(2))) 213 | 214 | % Valid format 215 | if type == LoadArgs.wellVal 216 | % Format is Letter then Number, where letter is the row and number is the column 217 | % Build array of column, row 218 | [nextWell, fCanonicalArg] = strtok(fCanonicalArg, ','); 219 | aParseOutput = [ aParseOutput ; ... 220 | str2num(nextWell(2:end)) (char(nextWell(1)) - char('A') + 1)]; 221 | else 222 | % Format is Number then Number, where the first is the column and the second is the row 223 | % Build array of column, row 224 | [nextWell, fCanonicalArg] = strtok(fCanonicalArg, ','); 225 | aParseOutput = [ aParseOutput ; ... 226 | str2num(nextWell(1)) str2num(nextWell(2:end))]; 227 | end 228 | 229 | % Look for the next delimiter 230 | if length(fCanonicalArg) >= 1 231 | if fCanonicalArg(1) == DELIMITER 232 | fCanonicalArg = fCanonicalArg(2:end); 233 | else 234 | % Invalid next character - not a delimiter 235 | aParseOutput = []; 236 | break; 237 | end 238 | end 239 | else 240 | % Invalid Well ID 241 | aParseOutput = []; 242 | break; 243 | end 244 | end 245 | end 246 | end 247 | 248 | function aParseOutput = canonical_timespan_argument(aArgument) 249 | 250 | if isvector(aArgument) && length(aArgument) == 2 && isnumeric(aArgument) 251 | aParseOutput = aArgument(:); 252 | else 253 | aParseOutput = []; 254 | end 255 | 256 | end 257 | 258 | function t = isdigit_ax(c) 259 | %ISDIGIT True for decimal digits. 260 | % 261 | % For a string C, ISDIGIT(C) is 1 for decimal digits and 0 otherwise. 262 | % 263 | narginchk(1, 1); 264 | 265 | t = ischar(c) & ( '0' <= c ) & ( c <= '9' ); 266 | end 267 | 268 | end 269 | end 270 | 271 | -------------------------------------------------------------------------------- /Axion/BlockVectorLegacyLoader.m: -------------------------------------------------------------------------------- 1 | classdef BlockVectorLegacyLoader < BlockVectorData 2 | %BLOCKVECTORLEGACYLOADER Static class with resources to load file data 3 | % to legacy style structures. 4 | 5 | properties(GetAccess = private, Constant = true) 6 | % Fixed by file format - there's only 1 byte for each 7 | MAX_CHANNEL_ARTICHOKE = 256; 8 | MAX_CHANNEL_INDEX = 256; 9 | end 10 | 11 | methods (Static = true) 12 | function aData = Legacy_Load_Raw_v1(aSourceData, aHeader, aTimeRange) 13 | 14 | % Check to make sure the file format makes sense for a raw file 15 | if aHeader.header.numSamplesPerBlock ~= 1 16 | error('load_AxIS_raw:argNumSamplesPerBlock', ... 17 | 'Invalid header for RAW file: incorrect samples per block'); 18 | end 19 | 20 | if aHeader.header.blockHeaderSize ~= 0 21 | error('load_AxIS_raw:argBlockHeaderSize', ... 22 | 'Invalid header for RAW file: incorrect block header size'); 23 | end 24 | 25 | fseek(aSourceData.FileID, int64(aSourceData.Start), 'bof'); 26 | 27 | % Copy input structure to output; then add information to output 28 | aData = aHeader; 29 | aData.numChannels = aData.header.numChannelsPerBlock; 30 | if aData.numChannels ~= aData.channelArray.numChannels 31 | error('load_AxIS_raw:mismatchNumChannels', ... 32 | 'Invalid RAW file: mismatched number of channels'); 33 | end 34 | 35 | if isempty(aHeader.loadedChannels) 36 | % No channels requested - don't read any data 37 | return; 38 | end 39 | 40 | % Read data 41 | fSampleFreq = int64(aData.samplingFrequency); 42 | fChannelCount = int64(aData.numChannels); 43 | fBytesPerSecond = fSampleFreq * fChannelCount * int64(2); 44 | fMaxTime = int64(aSourceData.EntryRecord.Length / fBytesPerSecond); 45 | if ~strcmp(aTimeRange, 'all') 46 | fStart = aTimeRange(1); 47 | fEnd =aTimeRange(2); 48 | 49 | if(fStart >= fEnd) 50 | warning('Invalid timespan argument: end time < start time. No valid waveform can be returned.'); 51 | return; 52 | end 53 | if(fStart > fMaxTime) 54 | warning( 'DataSet only contains %d Seconds of data (%d Seconds was the requested start time). No waveform will be returned.', fMaxTime, fStart); 55 | return; 56 | end 57 | if(fEnd > fMaxTime) 58 | fEnd = fMaxTime; 59 | warning('DataSet only contains %d Seconds of data. Returned waveforms will be shorter than requested (%d Seconds).', fMaxTime, fEnd - fStart); 60 | end 61 | 62 | fSkipInitialSamples = fStart * fSampleFreq; 63 | fSkipInitialBytes = fSkipInitialSamples * fChannelCount * int64(2); 64 | 65 | fNumSamples = int64((fEnd - fStart) * fSampleFreq); 66 | 67 | fseek(aSourceData.FileID, fSkipInitialBytes, 'cof'); 68 | else 69 | fNumSamples = int64(fMaxTime * fSampleFreq); 70 | end 71 | 72 | if length(aHeader.loadedChannels) == 1 73 | % We're only reading one channel. For efficiency, take advantage of fread's 74 | % 'skip' argument. 75 | 76 | % First, read and throw away other channels at the beginning of the data 77 | fread(aSourceData.FileID, aHeader.loadedChannels - 1, 'int16=>int16'); 78 | 79 | % Read the data for the given channel, skipping from one to the next 80 | aData.channelData = fread(aSourceData.FileID, fNumSamples, 'int16=>int16', 2*(aData.numChannels - 1)); 81 | else 82 | % We're reading multiple channels. There's no easy way to translate this into a 83 | % single fread, so we read all of the channels in the sample range in question, 84 | % then remap, throwing away what we didn't want. 85 | 86 | % First, read everything within the time range 87 | fNumSamples = fNumSamples * int64(aData.numChannels); 88 | fTempChannelData = fread(aSourceData.FileID, fNumSamples, 'int16=>int16'); 89 | 90 | % This test (which can fail only when we read the the end of the file) 91 | % makes sure that we didn't get a number of samples that's not divisible 92 | % by the number of channels. 93 | fRemainderCount = mod(length(fTempChannelData), aData.numChannels); 94 | if fRemainderCount ~= 0 95 | warning('load_AXiS_raw:remainderCheck', ... 96 | 'File %s has wrong number of samples for %u channels', ... 97 | aSourceData.FileID, aData.numChannels); 98 | end 99 | 100 | % Convert the 1D array to a 2D array, with channel as the second dimension 101 | % (starts as first dimension and then is transposed) 102 | fTempChannelData = reshape(fTempChannelData(:), aData.numChannels, []); 103 | 104 | % Remap 105 | aData.channelData = fTempChannelData(aData.loadedChannels, :); 106 | aData.channelData = aData.channelData'; 107 | end 108 | end 109 | 110 | function aData = Legacy_Load_spike_v1(aSourceData, aHeader, aTimeRange) 111 | % Check to make sure the file format makes sense for a spike file 112 | if aHeader.header.numChannelsPerBlock ~= 1 113 | error('Load_spike_v1:argNumChannelsPerBlock', ... 114 | 'Invalid header for SPIKE file: incorrect channels per block'); 115 | end 116 | 117 | if aHeader.header.blockHeaderSize < Spike_v1.LOADED_HEADER_SIZE 118 | error('Load_spike_v1:argBlockHeaderSize', ... 119 | 'Invalid header for SPIKE file: block header size too small'); 120 | end 121 | 122 | if aHeader.header.numSamplesPerBlock < 1 123 | error('load_AxIS_spike:argNumSamplesPerBlock', ... 124 | 'Invalid header for SPIKE file: number of samples per block < 1'); 125 | end 126 | 127 | % Copy input structure to output; then add information to output 128 | aData = aHeader; 129 | 130 | if isempty(aHeader.loadedChannels) 131 | % No channels requested - don't read any data 132 | return; 133 | end 134 | 135 | if ischar(aTimeRange) && strcmp(aTimeRange, 'all') 136 | fFirstSample = 0; 137 | fLastSample = Inf; 138 | else 139 | fFirstSample = aTimeRange(1) * aData.samplingFrequency; 140 | fLastSample = aTimeRange(2) * aData.samplingFrequency; 141 | end 142 | 143 | fSparseChannelIndex = sparse(... 144 | double([aData.channelArray.channel.channelAchk] + 1) , ... 145 | double([aData.channelArray.channel.channelIndex] + 1), ... 146 | double(1:aData.channelArray.numChannels), ... 147 | BlockVectorLegacyLoader.MAX_CHANNEL_ARTICHOKE,... 148 | BlockVectorLegacyLoader.MAX_CHANNEL_INDEX); 149 | 150 | spikeIndex = 1; 151 | 152 | % Manage the spike array to avoid "growing" it 153 | fSpikeArrayCapacity = 0; 154 | 155 | fseek(aSourceData.FileID, int64(aSourceData.Start), 'bof'); 156 | fStop = aSourceData.Start + aSourceData.EntryRecord.Length; 157 | 158 | while ftell(aSourceData.FileID) < fStop 159 | % Load the block header for this spike 160 | fCurrentSpike = []; 161 | fCurrentSpike.startingSample = fread(aSourceData.FileID, 1, 'int64'); %Changed uint64 to int64 162 | 163 | if feof(aSourceData.FileID) 164 | % we were at the end of the file 165 | break; 166 | end 167 | 168 | fHardwareChannelIndex = fread(aSourceData.FileID, 1, 'uint8'); 169 | fHardwareChannelAchk = fread(aSourceData.FileID, 1, 'uint8'); 170 | fCurrentSpike.triggerSampleOffset = fread(aSourceData.FileID, 1, 'uint32'); 171 | fCurrentSpike.stDev = fread(aSourceData.FileID, 1, 'double'); 172 | fCurrentSpike.threshold = fread(aSourceData.FileID, 1, 'double'); 173 | 174 | % Get well and electrode row and column 175 | fCurrentSpike.channelArrayIndex = full(fSparseChannelIndex(fHardwareChannelAchk + 1, fHardwareChannelIndex + 1)); 176 | if fCurrentSpike.channelArrayIndex == 0 177 | error('load_AxIS_spike:invalidSpikeChannel', ... 178 | ['Spike has invalid Artichoke/Channel number ' num2str(fHardwareChannelAchk) ... 179 | ' / ' num2str(fHardwareChannelIndex)]); 180 | end 181 | 182 | fCurrentSpike.channelInfo = aData.channelArray.channel(fCurrentSpike.channelArrayIndex); 183 | 184 | % Historical channel number -- XY, where X = electrodeColumn and Y = electrodeRow 185 | % This field is deprecated, since it doesn't contain multiwell information. 186 | fCurrentSpike.channel = 10*fCurrentSpike.channelInfo.electrodeColumn + ... 187 | fCurrentSpike.channelInfo.electrodeRow; 188 | 189 | % Seek forward to the end of the header. 190 | % It might seem cleaner to do this by saving the current position 191 | % before we read the header and seeking forward from there. 192 | % However, that method will not handle files larger than 4GB if fseek 193 | % and ftell are not 64-bit clean. 194 | fseek(aSourceData.FileID, aData.header.blockHeaderSize - Spike_v1.LOADED_HEADER_SIZE, 'cof'); 195 | 196 | if ftell(aSourceData.FileID) >= fStop 197 | % file ended before the start of the data -- looks like a truncated file 198 | warning('load_AxIS_spike:headerTruncated', ... 199 | 'Spike header truncated at end of file'); 200 | break; 201 | end 202 | 203 | % Read the data for this spike 204 | fCurrentSpike.waveform = fread(aSourceData.FileID, aData.header.numSamplesPerBlock, 'int16=>int16'); 205 | 206 | if length(fCurrentSpike.waveform) < aData.header.numSamplesPerBlock 207 | % file ended in the middle of the data 208 | warning('load_AxIS_spike:dataTruncated', ... 209 | 'Spike data truncated at end of file'); 210 | break; 211 | end 212 | 213 | % Does this spike match the channel filter? If not, skip it. 214 | if ~ismember(fCurrentSpike.channelArrayIndex, aData.loadedChannels) 215 | continue; 216 | end 217 | 218 | % Does this spike match the time filter? 219 | if fCurrentSpike.startingSample < fFirstSample 220 | % Too early 221 | continue; 222 | elseif fCurrentSpike.startingSample >= fLastSample 223 | % Too late. Since spikes are in chronological order, we 224 | % can stop reading now. 225 | break; 226 | end 227 | 228 | 229 | % Check for capacity in the spike array 230 | if spikeIndex > fSpikeArrayCapacity 231 | if fSpikeArrayCapacity == 0 232 | % Let it grow on its own 233 | fSpikeArrayCapacity = 1; 234 | else 235 | % Double the capacity 236 | fSpikeArrayCapacity = fSpikeArrayCapacity * 2; 237 | aData.spikes(fSpikeArrayCapacity).startingSample = -1; 238 | end 239 | end 240 | 241 | % save the spike 242 | aData.spikes(spikeIndex) = fCurrentSpike; 243 | spikeIndex = spikeIndex + 1; 244 | end 245 | 246 | % trim down the spike array 247 | if spikeIndex > 1 248 | aData.spikes = aData.spikes(1:spikeIndex-1); 249 | else 250 | aData.spikes = []; 251 | end 252 | end 253 | end 254 | 255 | end 256 | 257 | --------------------------------------------------------------------------------