Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions +ndr/+format/+intan/Intan_RHD2000_blockinfo.m
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function [blockinfo, bytes_per_block, bytes_present, num_data_blocks] = Intan_RHD2000_blockinfo(filename, header)
function [blockinfo, bytes_per_block, bytes_present, num_data_blocks, file_blocks] = Intan_RHD2000_blockinfo(filename, header)
% INTAN_RHD2000_BLOCKINFO - Block information for an Intan RHD2000 file
%
% [BLOCK_INFO, BYTES_PER_BLOCK, BYTES_PRESENT, NUMDATABLOCKS] = ...
% [BLOCK_INFO, BYTES_PER_BLOCK, BYTES_PRESENT, NUMDATABLOCKS, FILE_BLOCKS] = ...
% INTAN_RHD2000_BLOCKINFO(FILENAME [, HEADER])
%
% Computes the parameters of each data block of an Intan_RHD_2000 file.
Expand All @@ -14,14 +14,19 @@
% FILENAME should be the name of an RHD2000 file (normally with extension
% '.rhd'). HEADER should be the header information structure that is returned
% by READ_INTAN_RHD2000_HEADER; if it is left blank, it will be read from the
% file.
%
% file using the default 'detect' fileMode, which automatically aggregates
% across an Intan multi-file recording when sibling files are present.
%
% BLOCK_INFO is a structure describing the parameters of each block.
% BYTES_PER_BLOCK is the number of bytes per data block
% BYTES_PRESENT is the number of non-header bytes in the file.
% NUMDATABLOCKS is the number of data blocks in the file.
% BYTES_PRESENT is the number of non-header bytes in the file (or the sum
% across all files when HEADER describes a multi-file recording).
% NUMDATABLOCKS is the number of data blocks in the file (or the sum across
% all files when HEADER describes a multi-file recording).
% FILE_BLOCKS is a vector with the per-file data block counts (length 1 in
% single-file mode, length N in multi-file mode).
%
% See also: READ_INTAN_RHD2000_HEADER, READ_INTAN_RHD2000_DATAFILE, CAT_INTAN_RHD2000_FILES
% See also: READ_INTAN_RHD2000_HEADER, READ_INTAN_RHD2000_DATAFILE, CAT_INTAN_RHD2000_FILES, GETRHD2000FILELIST

if nargin<2,
header = ndr.format.intan.read_Intan_RHD2000_header(filename);
Expand Down Expand Up @@ -97,10 +102,28 @@
end;
bytes_per_block = block_offset;

% How many data blocks are in this file?
bytes_present = header.fileinfo.filesize - header.fileinfo.headersize;
num_data_blocks_float = bytes_present / bytes_per_block;
num_data_blocks = floor(num_data_blocks_float);
if num_data_blocks~=num_data_blocks_float,
warning(['File ' filename ' may be truncated or corrupted. Proceeding with ' int2str(num_data_blocks) ' of ' num2str(num_data_blocks_float) ' data blocks.']);
% How many data blocks are in this file (or across all files in multi-file mode)?
if isfield(header.fileinfo,'multifile') && strcmp(header.fileinfo.multifile.fileMode,'multiFile'),
mf = header.fileinfo.multifile;
file_blocks = zeros(1, numel(mf.files));
bytes_present = 0;
for i = 1:numel(mf.files),
bytes_present_i = mf.file_sizes(i) - mf.headersize;
num_blocks_float_i = bytes_present_i / bytes_per_block;
num_blocks_i = floor(num_blocks_float_i);
if num_blocks_i ~= num_blocks_float_i,
warning(['File ' mf.files{i} ' may be truncated or corrupted. Proceeding with ' int2str(num_blocks_i) ' of ' num2str(num_blocks_float_i) ' data blocks.']);
end;
file_blocks(i) = num_blocks_i;
bytes_present = bytes_present + bytes_present_i;
end;
num_data_blocks = sum(file_blocks);
else,
bytes_present = header.fileinfo.filesize - header.fileinfo.headersize;
num_data_blocks_float = bytes_present / bytes_per_block;
num_data_blocks = floor(num_data_blocks_float);
if num_data_blocks~=num_data_blocks_float,
warning(['File ' filename ' may be truncated or corrupted. Proceeding with ' int2str(num_data_blocks) ' of ' num2str(num_data_blocks_float) ' data blocks.']);
end;
file_blocks = num_data_blocks;
end;
39 changes: 39 additions & 0 deletions +ndr/+format/+intan/detectRHD2000FileMode.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function fileMode = detectRHD2000FileMode(filename)
% DETECTRHD2000FILEMODE - Detect whether an RHD file is part of a multi-file recording
%
% FILEMODE = DETECTRHD2000FILEMODE(FILENAME)
%
% Returns 'multiFile' when FILENAME follows the Intan
% '<prefix>_<YYMMDD>_<HHMMSS>.rhd' naming convention and at least one
% additional sibling file in the same directory shares that prefix and
% matches the same timestamp pattern. Returns 'singleFile' otherwise.
%
% See also: GETRHD2000FILELIST, READ_INTAN_RHD2000_HEADER, READ_INTAN_RHD2000_DATAFILE

[dirname, fname, ext] = fileparts(filename);
if isempty(dirname),
dirname = pwd;
end;

tok = regexp(fname, '^(.*)_(\d{6})_(\d{6})$', 'tokens', 'once');
if isempty(tok),
fileMode = 'singleFile';
return;
end;

prefix = tok{1};
prefix_pattern = ['^' regexptranslate('escape', prefix) '_(\d{6})_(\d{6})$'];
d = dir(fullfile(dirname, [prefix '_*_*' ext]));

count = 0;
for i = 1:numel(d),
[~, name_i, ~] = fileparts(d(i).name);
if ~isempty(regexp(name_i, prefix_pattern, 'once')),
count = count + 1;
if count > 1,
fileMode = 'multiFile';
return;
end;
end;
end;
fileMode = 'singleFile';
71 changes: 71 additions & 0 deletions +ndr/+format/+intan/getRHD2000FileList.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
function files = getRHD2000FileList(filename, fileMode)
% GETRHD2000FILELIST - Get the list of RHD2000 files comprising a recording
%
% FILES = GETRHD2000FILELIST(FILENAME, FILEMODE)
%
% Given a single .rhd FILENAME, return a cell array of full-path file names
% that together make up the recording.
%
% FILEMODE may be:
% 'detect' (default) - Auto-detect: resolves to 'multiFile' if more than
% one file matching the prefix+timestamp pattern is found in the same
% directory, otherwise 'singleFile'. See DETECTRHD2000FILEMODE.
% 'singleFile' - The recording is a single .rhd file. Returns a cell
% array containing just FILENAME.
% 'multiFile' - The recording is spread across many .rhd files saved by
% the Intan acquisition software with the same base prefix and a
% <YYMMDD>_<HHMMSS> timestamp before the extension. Returns the sorted
% (chronological) list of all files in the same directory matching
% '<prefix>_<YYMMDD>_<HHMMSS>.rhd', where <prefix> is parsed from
% FILENAME.
%
% See also: READ_INTAN_RHD2000_HEADER, READ_INTAN_RHD2000_DATAFILE, DETECTRHD2000FILEMODE

if nargin < 2 || isempty(fileMode),
fileMode = 'detect';
end;

if strcmp(fileMode, 'detect'),
fileMode = ndr.format.intan.detectRHD2000FileMode(filename);
end;

switch fileMode,
case 'singleFile',
files = {filename};
case 'multiFile',
[dirname, fname, ext] = fileparts(filename);
if isempty(dirname),
dirname = pwd;
end;
% parse <prefix>_YYMMDD_HHMMSS
tok = regexp(fname, '^(.*)_(\d{6})_(\d{6})$', 'tokens', 'once');
if isempty(tok),
error(['Filename ' filename ' does not match the Intan multi-file pattern <prefix>_YYMMDD_HHMMSS' ext '.']);
end;
prefix = tok{1};
d = dir(fullfile(dirname, [prefix '_*_*' ext]));
keep = false(1, numel(d));
timestamps = cell(1, numel(d));
prefix_pattern = ['^' regexptranslate('escape', prefix) '_(\d{6})_(\d{6})$'];
for i = 1:numel(d),
[~, name_i, ~] = fileparts(d(i).name);
t_i = regexp(name_i, prefix_pattern, 'tokens', 'once');
if ~isempty(t_i),
keep(i) = true;
timestamps{i} = [t_i{1} t_i{2}];
end;
end;
d = d(keep);
timestamps = timestamps(keep);
if isempty(d),
error(['No RHD multi-file set found for prefix ' prefix ' in ' dirname '.']);
end;
[~, order] = sort(timestamps);
d = d(order);
files = cell(1, numel(d));
for i = 1:numel(d),
files{i} = fullfile(dirname, d(i).name);
end;
otherwise,
error(['Unknown fileMode: ' fileMode '. Use ''singleFile'' or ''multiFile''.']);
end;
57 changes: 55 additions & 2 deletions +ndr/+format/+intan/read_Intan_RHD2000_datafile.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@
% [DATA,TOTAL_SAMPLES,TOTAL_TIME,BLOCKINFO] = READ_INTAN_RHD2000_DATAFILE(FILENAME,
% HEADER, CHANNEL_TYPE, CHANNEL_NUMBERS, T0, T1);
%
% Optional name/value pairs:
% 'fileMode' - 'detect' (default), 'singleFile', or
% 'multiFile'. In 'detect' mode the reader
% checks for Intan-style sibling files in
% the same directory and automatically
% resolves to 'multiFile' or 'singleFile'.
% In 'multiFile' mode, FILENAME is treated
% as one member of a contiguous set of
% Intan-saved files sharing a common
% prefix; the reader transparently spans
% the entire set, so T0 and T1 refer to
% the time axis of the concatenated
% recording.
% 'force_single_channel_read' - Force the per-channel read path even
% when multiple consecutive channels are
% requested.
%
% Inputs:
% Reads data from the Intan Technologies .rhd 2000 file FILENAME.
% The file HEADER information can be provided in HEADER. If HEADER
Expand Down Expand Up @@ -52,13 +69,18 @@
%

force_single_channel_read = 0;
fileMode = 'detect';
assign(varargin{:});

if strcmp(fileMode, 'detect'),
fileMode = ndr.format.intan.detectRHD2000FileMode(filename);
end;

if isempty(header),
header = ndr.format.intan.read_Intan_RHD2000_header(filename);
header = ndr.format.intan.read_Intan_RHD2000_header(filename, 'fileMode', fileMode);
end;

[blockinfo, bytes_per_block, bytes_present, num_data_blocks] = ndr.format.intan.Intan_RHD2000_blockinfo(filename, header);
[blockinfo, bytes_per_block, bytes_present, num_data_blocks, file_blocks] = ndr.format.intan.Intan_RHD2000_blockinfo(filename, header);

total_samples = header.fileinfo.num_samples_per_data_block * num_data_blocks;
total_time = total_samples / header.frequency_parameters.amplifier_sample_rate; % in seconds
Expand Down Expand Up @@ -89,6 +111,37 @@
ch(channel_type) = 1;
c = find(ch);

% Multi-file mode: dispatch the request to the constituent files and
% concatenate the results, treating the recording as one continuous file.
if isfield(header.fileinfo,'multifile') && strcmp(header.fileinfo.multifile.fileMode,'multiFile'),
files = header.fileinfo.multifile.files;
spb = blockinfo(c).samples_per_block;
sr = blockinfo(c).sample_rate;
file_samples = file_blocks * spb;
cum_end = cumsum(file_samples);
cum_start = [0, cum_end(1:end-1)] + 1;

g_s0 = 1 + round(t0 * sr);
g_s1 = 1 + round(t1 * sr);

data = [];
for i = 1:numel(files),
f_lo = cum_start(i);
f_hi = cum_end(i);
if f_hi < g_s0 || f_lo > g_s1,
continue;
end;
local_lo = max(f_lo, g_s0) - f_lo + 1;
local_hi = min(f_hi, g_s1) - f_lo + 1;
local_t0 = (local_lo - 1) / sr;
local_t1 = (local_hi - 1) / sr;
d_i = ndr.format.intan.read_Intan_RHD2000_datafile(files{i}, [], channel_type, channel_numbers, local_t0, local_t1, ...
'fileMode', 'singleFile', 'force_single_channel_read', force_single_channel_read);
data = [data; d_i];
end;
return;
end;

% now compute starting and ending samples to read
s0 = 1+round(t0 * blockinfo(c).sample_rate);
s1 = 1+round(t1 * blockinfo(c).sample_rate);
Expand Down
65 changes: 58 additions & 7 deletions +ndr/+format/+intan/read_Intan_RHD2000_header.m
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
function [header] = read_Intan_RHD2000_header(filename);
function [header] = read_Intan_RHD2000_header(filename, varargin);
% READ_INTAN_RHD2000_HEADER - Read header information from an Intan data file
%
% HEADER = READ_INTAN_RHD2000_HEADER(FILENAME)
% HEADER = READ_INTAN_RHD2000_HEADER(FILENAME, 'fileMode', FILEMODE)
%
% Returns a structure HEADER with all of the information fields that
% are stored in the Intan RHD2000 file FILENAME.
%
% Optional name/value pairs:
% 'fileMode' - 'detect' (default) checks whether FILENAME has Intan
% sibling files in the same directory matching
% '<prefix>_<YYMMDD>_<HHMMSS>.rhd' and resolves to either
% 'multiFile' or 'singleFile' (see DETECTRHD2000FILEMODE).
% Pass 'singleFile' to force reading the header from a
% single .rhd file, or 'multiFile' to force treating
% FILENAME as one member of a set that together represents
% a continuous recording. The header is parsed from the
% first (chronologically earliest) file and the resulting
% HEADER exposes the recording as if it were one large
% file.
%
% HEADER contains several substructures:
% --------------------------------------------------------------------
% fileinfo | Information about the file and its version
% frequency_parameters | Information about sampling frequency
% frequency_parameters | Information about sampling frequency
% spike_triggers | Information about spike triggers for each amplifier channel
% amplifier_channels | Information about amplifier channels
% aux_input_channels | Information about auxillary input channels
Expand All @@ -19,18 +33,36 @@
% board_dig_out_channels | Digital output channels
% num_temp_sensor_channels| Number of temperature sensor channels
%
% See also: READ_INTAN_RDH2000_DATAFILE
% In multi-file mode, HEADER.fileinfo.multifile contains the list of files,
% their sizes, and the (assumed identical) header size. In single-file mode
% it contains the single file equivalents.
%
% See also: READ_INTAN_RHD2000_DATAFILE, GETRHD2000FILELIST
%

fid = fopen(filename,'r');
fileMode = 'detect';
assign(varargin{:});

if strcmp(fileMode, 'detect'),
fileMode = ndr.format.intan.detectRHD2000FileMode(filename);
end;

files = ndr.format.intan.getRHD2000FileList(filename, fileMode);

% Parse the header from the first file. Acquisition parameters are
% identical across files in a multi-file recording, so a single parse is
% sufficient to characterize the layout.
primary_filename = files{1};

fid = fopen(primary_filename,'r');
if fileid_value(fid)<0,
error(['Could not open filename ' filename_value(filename) ' for reading (check path, spelling, permissions).']);
error(['Could not open filename ' filename_value(primary_filename) ' for reading (check path, spelling, permissions).']);
end;

[dirname,fname,ext]=fileparts(filename);
[dirname,fname,ext]=fileparts(primary_filename);
s = dir(fullfile(dirname,[fname ext]));
if isempty(s),
error(['Could not find a file ' filename_value(filename) '; check spelling, permissions, extension']);
error(['Could not find a file ' filename_value(primary_filename) '; check spelling, permissions, extension']);
end;
filesize = s.bytes;
% Check 'magic number' at beginning of file to make sure this is an Intan
Expand Down Expand Up @@ -235,3 +267,22 @@
header.fileinfo.headersize = ftell(fid); % get the location of the next byte to be read

fclose(fid);

% Attach multi-file information so downstream block/data readers can treat
% the recording as one continuous file. Per-file headersize is assumed
% identical across files because the acquisition parameters match; only
% the file sizes need to be measured for each constituent file.
file_sizes = zeros(1, numel(files));
for i = 1:numel(files),
s_i = dir(files{i});
if isempty(s_i),
error(['Could not stat file ' files{i} '.']);
end;
file_sizes(i) = s_i.bytes;
end;

header.fileinfo.multifile = struct(...
'fileMode', fileMode, ...
'files', {files}, ...
'file_sizes', file_sizes, ...
'headersize', header.fileinfo.headersize);
Loading