├── +adi ├── +binary │ ├── channel_writer.m │ └── file_writer.m ├── +examples │ ├── conversion_speed_testing.m │ ├── e001_writeFileFromScratch.m │ ├── e002_addCommentsToAnExistingFile.m │ └── e003_copyFile.m ├── +file_comparison │ └── @comments_comparison │ │ └── comments_comparison.m ├── +postp │ └── mergeComments.m ├── +sdk │ └── ReadMe.md ├── +sl │ ├── +array │ │ └── uniqueWithGroupIndices.m │ ├── +cellstr │ │ ├── contains.m │ │ └── join.m │ ├── +datetime │ │ ├── getTimeZone.m │ │ ├── matlabToUnix.m │ │ └── unixToMatlab.m │ ├── +dir │ │ ├── changeFileExtension.m │ │ ├── createFolderIfNoExist.m │ │ ├── filepartsx.m │ │ └── fullfile.m │ ├── +in │ │ ├── processVarargin.m │ │ └── process_varargin_result.m │ ├── +io │ │ └── mergePDFs.m │ ├── +plot │ │ └── +export │ │ │ └── saveAsPDF.m │ ├── +stack │ │ ├── calling_function_info.m │ │ ├── getMyBasePath.m │ │ └── getPackageRoot.m │ └── +str │ │ └── contains.m ├── +tests │ └── t001_speedWritingAndWriting.m ├── +w │ └── @sample_count_manager │ │ └── sample_count_manager.m ├── @channel │ ├── channel.m │ └── exportToHDF5File.m ├── @comment │ └── comment.m ├── @file_viewer │ ├── file_viewer.m │ └── private │ │ └── main.fig ├── @plotter │ └── plotter.m ├── channel_writer.m ├── comment_handle.m ├── convert.m ├── createFile.m ├── data_writer_handle.m ├── documentation │ ├── FunctionMapping.csv │ └── com_interface_notes.m ├── editFile.m ├── extractRecordToNewFile.m ├── file.m ├── file_comparison.m ├── file_handle.m ├── file_read_options.m ├── file_writer.m ├── getFileComments.m ├── h5_conversion_options.m ├── h5_file_h.m ├── h5_file_sdk.m ├── handle_logger.m ├── handle_manager.m ├── mat_comment_handle.m ├── mat_conversion_options.m ├── mat_file_h.m ├── mat_file_sdk.m ├── plot.m ├── printFile.m ├── private │ ├── ADIDatCAPI_mex.h │ ├── ADIDatIOWin64.dll │ ├── ADIDatIOWin64.lib │ ├── adi_example.cpp │ ├── c.m │ ├── c0.m │ ├── clong.m │ ├── sdk_mex.cpp │ └── sdk_mex.mexw64 ├── readFile.m ├── record.m ├── sdk.m └── test_SDK.m ├── .gitignore ├── LICENSE ├── ReadMe.md ├── adi.m ├── documentation ├── ADIDatToolkit.pdf ├── ReadMe.md ├── changes.md ├── organization.md ├── updating_mex_code.txt └── writing.md └── files ├── LabChartBinaryFormat.pdf ├── blank_labchart_8_file.adicht └── crash_testing.m /+adi/+binary/channel_writer.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) channel_writer 2 | % 3 | % Class: 4 | % adi.channel_writer 5 | % 6 | % This class facilities writing a channel to the binary file format. 7 | % More details can be found in adi.binary_file_writer 8 | % 9 | % See Also: 10 | % adi.binary_file_writer 11 | 12 | 13 | %{ 14 | Each channel header has the following format: 15 | type name description 16 | char Title[32] channel title 17 | char Units[32] units name 18 | double scale see text 19 | double offset see text 20 | double RangeHigh see text 21 | double RangeLow see text 22 | %} 23 | 24 | properties 25 | name 26 | units 27 | data 28 | fs 29 | %These are only needed for 16 bit data, although they must be in 30 | %the binary. 31 | scale = 1 32 | offset = 0 33 | end 34 | 35 | methods 36 | function obj = channel_writer(channel_name,units,fs,data) 37 | %TODO: Check name size 38 | obj.name = channel_name; 39 | obj.units = units; 40 | obj.data = data; 41 | obj.fs = fs; 42 | end 43 | function writeHeader(obj,fid) 44 | %pass 45 | 46 | temp_title = zeros(1,32,'uint8'); 47 | temp_units = zeros(1,32,'uint8'); 48 | temp_title(1:length(obj.name)) = uint8(obj.name); 49 | temp_units(1:length(obj.units)) = uint8(obj.units); 50 | 51 | fwrite(fid,temp_title,'*char'); 52 | fwrite(fid,temp_units,'*char'); 53 | fwrite(fid,obj.scale,'double'); 54 | fwrite(fid,obj.offset,'double'); 55 | %Range high and low ... - not currently used 56 | fwrite(fid,Inf,'double'); 57 | fwrite(fid,-Inf,'double'); 58 | end 59 | end 60 | 61 | end 62 | 63 | -------------------------------------------------------------------------------- /+adi/+binary/file_writer.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/+adi/+binary/file_writer.m -------------------------------------------------------------------------------- /+adi/+examples/conversion_speed_testing.m: -------------------------------------------------------------------------------- 1 | function conversion_speed_testing 2 | % 3 | % 4 | % conversion_speed_testing 5 | % 6 | % 7 | % TODO: This should be moved to the testing package. 8 | 9 | root_path = 'C:\Data\GSK\ChrisRaw'; 10 | root_path = 'C:\D\GSK_Data'; 11 | 12 | base_file = '140207 control cmg.'; 13 | file_path = fullfile(root_path,[base_file 'adicht']); 14 | mat_fpath = fullfile(root_path,[base_file 'mat']); 15 | h5_fpath = fullfile(root_path,[base_file 'h5']); 16 | 17 | %Converting the file to mat format 18 | %--------------------------------- 19 | if false 20 | tic; 21 | adinstruments.convert(file_path,'format','mat'); 22 | toc; 23 | end 24 | 25 | %Summary: 26 | %File Size : 105 MB 27 | %Total Time: 28 seconds 28 | %163 reads of 21 channels (105 MB total) - 7.4 s 29 | %Remaining time is mostly writing 30 | 31 | %Converting the file to h5 format 32 | %-------------------------------- 33 | if false 34 | tic; 35 | adinstruments.convert(file_path,'format','h5'); 36 | toc; 37 | end 38 | 39 | if false 40 | tic; 41 | h = load(mat_fpath,'data__chan_3_rec_4'); 42 | toc; 43 | end 44 | %# Values: 91,434,500 - 730 MB in memory - on disk maybe 50 MB 45 | %Channel read time: 2.637 seconds 46 | 47 | 48 | 49 | if false 50 | tic 51 | wtf = adinstruments.readFile(h5_fpath); 52 | toc 53 | 54 | c = wtf.channel_specs(3); 55 | 56 | tic 57 | data = c.getAllData(2); 58 | toc 59 | 60 | tic 61 | h = load(mat_fpath,'data__chan_3_rec_2'); 62 | toc 63 | end 64 | 65 | file_obj = h5m.file.open(h5_fpath); 66 | 67 | keyboard 68 | 69 | group_obj = h5m.group.open(file_obj,'/data__chan_3_rec_2'); 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /+adi/+examples/e001_writeFileFromScratch.m: -------------------------------------------------------------------------------- 1 | function e001_writeFileFromScratch 2 | % 3 | % adi.examples.e001_writeFileFromScratch 4 | % 5 | % In this file I wrote many more eus samples than presssure but the 6 | % Labchart loader didn't show all of the eus data 7 | % 8 | % I'm not sure why this is occuring but the big lesson seems to be that 9 | % the program doesn't completely freak out if this happens 10 | % 11 | % TODO: See if we can do this from a template file 12 | 13 | N_RECORDS = 4; 14 | 15 | %file_path = 'C:\Users\RNEL\Desktop\merge_adi_test\wtf.adicht'; 16 | file_path = 'C:\Users\Jim\Desktop\merge_adi_test\wtf.adicht'; 17 | 18 | if exist(file_path,'file') 19 | delete(file_path) 20 | end 21 | 22 | fw = adi.createFile(file_path); 23 | %fw : adi.file_writer 24 | 25 | %fw.addComment(1,300,'Does this actually work?') 26 | %fw.addComment(7,300,'Does this actually work?') 27 | 28 | pres_w = fw.addChannel(1,'Pressure',1000,'cmH2O'); 29 | eus_w = fw.addChannel(2,'eus',200,'uV'); 30 | 31 | for iRecord = 1:2 %N_RECORDS 32 | fw.startRecord; 33 | 34 | %fprintf(2,'Pressure \n'); 35 | pres_w.addSamples((1:1000)/1000) 36 | pres_w.addSamples((1:1000)/1000) 37 | pres_w.addSamples((1:1000)/1000) 38 | pres_w.addSamples((1:1000)/1000) 39 | pres_w.addSamples((1:1000)/1000) 40 | %t=0:0.001:2; 41 | 42 | %fprintf(2,'Onto EUS \n'); 43 | t = 0.001:0.001:1; 44 | 45 | y=chirp(t,0,1,150); 46 | eus_w.addSamples(y); 47 | 48 | %fw.addComment(1,2,'Cheeseburgers are great'); 49 | 50 | c_number = fw.addComment(iRecord,3,'Testing3','channel',1); 51 | fw.addComment(iRecord,4,'Testing4'); 52 | fw.addComment(iRecord,5,'Testing5'); 53 | 54 | fw.stopRecord; 55 | end 56 | 57 | 58 | 59 | fw.save; 60 | clear fw -------------------------------------------------------------------------------- /+adi/+examples/e002_addCommentsToAnExistingFile.m: -------------------------------------------------------------------------------- 1 | function e002_addCommentsToAnExistingFile() 2 | % 3 | % Why doesn't this work??? 4 | % 5 | % Is there some problem with modifying old adicht files that 6 | % weren't created with the sdk? 7 | 8 | file_path = 'C:\temp\example_labchart_file.adicht'; 9 | 10 | fw = adi.editFile(file_path); 11 | 12 | %fw.addComment(1,300,'Does this actually work?') 13 | fw.addComment(7,300,'Does this actually work?') 14 | 15 | 16 | %Fails on this line 17 | fw.save -------------------------------------------------------------------------------- /+adi/+examples/e003_copyFile.m: -------------------------------------------------------------------------------- 1 | function copyFile() 2 | 3 | original_file_path = 'C:\Users\RNEL\Desktop\merge_adi_test\large_file_to_copy.adicht'; 4 | 5 | 6 | end -------------------------------------------------------------------------------- /+adi/+file_comparison/@comments_comparison/comments_comparison.m: -------------------------------------------------------------------------------- 1 | classdef comments_comparison < handle 2 | % 3 | % Class: 4 | % adi.file_comparison.comments_comparison 5 | 6 | properties 7 | n_comments1 8 | n_comments2 9 | %Comments in 1 only 10 | %Comments in 2 only 11 | 12 | 13 | %---------------------------------------------------- 14 | %ID: 15 | % 16 | % in1, in2, string_match?, str1, str2, time_match, t1, t2 17 | 18 | end 19 | 20 | methods 21 | function obj = comments_comparison(all_c1,all_c2) 22 | 23 | 24 | s_all = cell(1,length(all_c1)+length(all_c2)); 25 | ids2 = [all_c2.id]; 26 | for i = 1:length(all_c1) 27 | c1 = all_c1(i); 28 | cur_id = c1.id; 29 | 30 | s = struct('in1',true,'in2',true,'string_match',false,... 31 | 'str1',c1.str,'str2','',... 32 | 'time_match',false,'r1',c1.record,'t1',c1.time,'r2',NaN,'t2',NaN); 33 | 34 | 35 | I = find(cur_id == ids2,1); 36 | if ~isempty(I) 37 | else 38 | end 39 | 40 | end 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /+adi/+postp/mergeComments.m: -------------------------------------------------------------------------------- 1 | function mergeComments(final_file_path,source_file_paths) 2 | %x Takes comments from files and merges them in to an older file 3 | % 4 | % adi.postp.mergeComments(*final_file_path,*source_file_paths) 5 | % 6 | % Optional Inputs: 7 | % ---------------- 8 | % final_file_path : string (default, selection) 9 | % Path to the file to which comments are added 10 | % source_file_paths : string or cellstr 11 | % 12 | % 13 | % Examples: 14 | % --------- 15 | % 1) 16 | % final_path = 'D:\adi_comment_merging\140903_C_01_pelvic _and hypogastric_PGE2.adicht'; 17 | % file_with_comments = 'D:\adi_comment_merging\140903_C_01_pelvic _and hypogastric_PGE2_CMG NVC Analysis.adicht'; 18 | % adi.postp.mergeComments(final_path,file_with_comments) 19 | % 20 | % To Handle 21 | % --------- 22 | % 1) 23 | % 24 | % Improvements: 25 | % ------------- 26 | % 1) We need to verify that the final_file_path is indeed an original 27 | % that contains the various sources. 28 | 29 | 30 | 31 | 32 | % in.final_file_path = ''; 33 | % in.source_file_paths = {}; 34 | % in = sl.in.processVarargin(in,varargin); 35 | 36 | if nargin < 1 37 | final_file_path = ''; 38 | elseif nargin < 2 39 | source_file_paths = ''; 40 | end 41 | 42 | if isempty(final_file_path) 43 | final_file_path = adi.uiGetChartFile('prompt','select file to add comments to'); 44 | elseif ~exist(final_file_path,'file') 45 | %TODO: Build in call to the file missing error, requires moving from the 46 | %sl to this package 47 | error('Final file path does not point to a file that exists') 48 | end 49 | 50 | if ischar(source_file_paths) && ~isempty(source_file_paths) 51 | source_file_paths = {source_file_paths}; 52 | end 53 | 54 | %commented_files 55 | if isempty(source_file_paths) 56 | source_file_paths = adi.uiGetChartFile('multi_select',true,'prompt',... 57 | 'Select files that contain new comments to move'); 58 | else 59 | for iFile = 1:length(source_file_paths) 60 | if ~exist(source_file_paths{iFile},'file') 61 | error('Specified file does not exist') 62 | end 63 | end 64 | end 65 | 66 | if ischar(source_file_paths) 67 | source_file_paths = {source_file_paths}; 68 | end 69 | 70 | final_h = adi.editFile(final_file_path); 71 | 72 | for iFile = 1:length(source_file_paths) 73 | cur_file_h = adi.readFile(source_file_paths{iFile}); 74 | 75 | cur_comments = cur_file_h.getAllComments(); 76 | 77 | h__getCommentInstructions(final_h.comments,cur_comments) 78 | 79 | keyboard 80 | end 81 | %final_file = adi.readFile(final_file_path); 82 | 83 | keyboard 84 | 85 | end 86 | 87 | function h__getCommentInstructions(old_comments,new_comments) 88 | %Comment Merging Rules 89 | %{ 90 | 91 | The ID should not change for a comment. Adding new comments 92 | will create a new ID. This can cause a problem however if we take multiple 93 | new files since they are not in sync. 94 | 95 | i.e. 96 | - original_file 97 | - subset1 98 | - subset2 99 | The files subset1 & subset2 don't know about each other, so presumably 100 | they have ids which overlap. 101 | 102 | How do we want to handle 103 | 104 | String content change 105 | --------------------- 106 | 107 | Channel change 108 | -------------- 109 | 110 | Time difference 111 | --------------- 112 | - use the new one 113 | - 114 | 115 | Approach 116 | -------- 117 | Find the best match for any comment in the source in the original. This may 118 | or may not exist. If it exists, the do nothing for now. Eventually we need 119 | to figure out how to handle any discrepancies. 120 | If it doesn't exist, add it. 121 | 122 | %} 123 | %TODO: Implement this 124 | %I'd like a set of instructions 125 | %- create 126 | %- delete 127 | %- edit 128 | %as well as a comment by comment analysis 129 | %- keeping 130 | %- deleting 131 | %- merging with new 132 | %- same as old 133 | %- creating 134 | %- merging with old 135 | end -------------------------------------------------------------------------------- /+adi/+sdk/ReadMe.md: -------------------------------------------------------------------------------- 1 | I'd eventually like to move all the SDKs into this folder. 2 | 3 | The main sdk would be the LabChart SDK -------------------------------------------------------------------------------- /+adi/+sl/+array/uniqueWithGroupIndices.m: -------------------------------------------------------------------------------- 1 | function [u,uI] = uniqueWithGroupIndices(A) 2 | %unique2 Returns groupings for each unique element 3 | % 4 | % [u,uI] = sl.array.uniqueWithGroupIndices(A) 5 | % 6 | % This function is a quicker way of getting the indices which 7 | % match a particular unique value. 8 | % 9 | % INPUTS 10 | % ============================================================ 11 | % A : must be sortable via sort() 12 | % 13 | % OUTPUTS 14 | % ============================================================ 15 | % u : unique values 16 | % uI : (cell array), each entry holds the indices of A which match u 17 | % 18 | % EXAMPLE 19 | % ============================================================ 20 | % [u,uI] = uniqueWithGroupIndices([3 5 3 5 5]) 21 | % u => [3 5] 22 | % uI{1} => [1 3]; 23 | % uI{2} => [2 4 5]; 24 | % 25 | % NOTE: u(#) has the same value as all A(uI{#}) 26 | 27 | 28 | 29 | % SPEED NOTE 30 | % ========================================== 31 | % r = randi(100,1,100000); 32 | % 33 | % METHOD 1 - takes time T 34 | % [u,uI] = unique2(r); 35 | % 36 | % METHOD 2 - takes time 3T 37 | % [u2,~,J] = unique_2011b(r); 38 | % uI2 = cell(1,length(u2)); 39 | % for iChan = 1:length(u2) 40 | % uI2{iChan} = strfind(J,u2(iChan)); 41 | % end 42 | % 43 | % METHOD 3 - takes time 5T 44 | % [u3,~,J] = unique_2011b(r); 45 | % uI3 = cell(1,length(u3)); 46 | % for iChan = 1:length(u3) 47 | % uI3{iChan} = find(J == u3(iChan)); 48 | % end 49 | 50 | %Add error checking 51 | %1) input must be number 52 | %2) NaN handling 53 | 54 | if isempty(A) 55 | u = []; 56 | uI = {}; 57 | return 58 | elseif length(A) == 1 59 | u = A; 60 | uI = {1}; 61 | return 62 | end 63 | 64 | %JAH TODO: Document code 65 | [Y,I2] = sort(A(:)); 66 | 67 | if isnumeric(Y) 68 | if isnan(Y) 69 | error('NaN handling not yet supported') 70 | end 71 | Itemp = find(diff(Y) ~= 0); 72 | else 73 | %could add case sensitivity 74 | Itemp = find(~strcmp(Y(1:end-1),Y(2:end))); 75 | end 76 | 77 | Istart = [1; Itemp+1]; 78 | Iend = [Itemp; length(A)]; 79 | 80 | u = Y(Istart); 81 | 82 | %Handling row vectors, note that matrices will come out 83 | %as column vectors, just like for unique() 84 | rows = size(A,1); 85 | cols = size(A,2); 86 | if (rows == 1) && (cols > 1) 87 | u = u'; 88 | I2 = I2'; 89 | end 90 | 91 | %Population of uI 92 | %----------------------------- 93 | uI = cell(1,length(u)); 94 | for iUnique = 1:length(u) 95 | uI{iUnique} = I2(Istart(iUnique):Iend(iUnique)); 96 | end 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /+adi/+sl/+cellstr/contains.m: -------------------------------------------------------------------------------- 1 | function mask = contains(data,strings_or_patterns,varargin) 2 | %x Returns mask on whether each string contains the pattern 3 | % 4 | % mask = sl.cellstr.contains(data,strings_or_patterns,varargin) 5 | % 6 | % Note the current behavior is to 'or' the responses such that a match to 7 | % any pattern will return true. 8 | % 9 | % Determine for each 'data' whether the 'strings_or_patterns' is 10 | % contained in it. 11 | % 12 | % Inputs: 13 | % ------- 14 | % data : cellstr 15 | % strings_or_patterns : char or cellstr 16 | % 17 | % Optional Inputs: 18 | % ---------------- 19 | % case_sensitive : logical (default false) 20 | % regexp : logical (default false) 21 | % 22 | % The default behavior is to OR the matches, i.e. true if any of 23 | % the strings match. For AND, all the strings must match. 24 | % use_or: logical (default []) 25 | % - true - OR 26 | % - false - AND 27 | % use_and: logical (default []) 28 | % - true - AND 29 | % - false - OR 30 | % 31 | % Improvements: 32 | % ------------- 33 | % 1) Allow specifying a regxp flag for every pattern (i.e. 'regexp' would 34 | % be an array) 35 | % 36 | % 37 | % Examples: 38 | % --------- 39 | % 1) 40 | % data = {'this is a test','example I am','cheeseburger'} 41 | % strings = {'test' 'cheese'}; 42 | % mask = sl.cellstr.contains(data,strings) 43 | % mask => [1 0 1] 44 | % 45 | % 2) 46 | % data = {'qp1','this is a qp','qp today'} 47 | % patterns = {'^qp\s*\d+'} 48 | % mask = sl.cellstr.contains(data,patterns,'regexp','true') 49 | % mask => [1 0 0] 50 | % 51 | % 3) 52 | % data = {'this is a test','test'} 53 | % strings = {'this','test'} 54 | % %Both true, because we match one of the patterns 55 | % mask = sl.cellstr.contains(data,strings_or_patterns,'use_or',true) 56 | % %Only the first one is true, because it contains both patterns 57 | % mask = sl.cellstr.contains(data,strings,'use_and',true) 58 | % 59 | % See Also: 60 | % --------- 61 | % sl.str.contains 62 | 63 | %The idea with flag is to return indices of matches for many to many 64 | %searches. Essentially, this would be like ismember() but with support 65 | %for partial matches. I think instead of implementing this I just did the 66 | %index search on my output ... 67 | % 68 | % data = {'test' 'cheese' 'nope'} 69 | % strings = {'asdf' 'school' 'bib 'testing' 'cheddar cheese'} 70 | % 71 | % Instead pf 72 | % 73 | % in.return_match_index = false; NYI 74 | in.use_or = []; 75 | in.use_and = []; 76 | in.case_sensitive = false; 77 | in.regexp = false; 78 | in = adi.sl.in.processVarargin(in,varargin); 79 | 80 | if isempty(in.use_or) && isempty(in.use_and) 81 | or_results = true; 82 | elseif ~isempty(in.use_or) 83 | or_results = in.use_or; 84 | else 85 | or_results = ~in.use_and; 86 | end 87 | 88 | if in.case_sensitive 89 | fh = @regexp; 90 | else 91 | fh = @regexpi; 92 | end 93 | 94 | if ~iscell(data) 95 | error('Input data must be a cellstr') 96 | end 97 | 98 | if ischar(strings_or_patterns) 99 | strings_or_patterns = {strings_or_patterns}; 100 | end 101 | 102 | n_patterns = length(strings_or_patterns); 103 | if n_patterns == 0 104 | mask = false(1,length(data)); 105 | return 106 | elseif or_results 107 | mask = false(1,length(data)); 108 | else 109 | mask = true(1,length(data)); 110 | end 111 | 112 | % if in.return_match_index 113 | % output_I = zeros(1,length(data)); 114 | % end 115 | 116 | for iPattern = 1:length(strings_or_patterns) 117 | cur_str_or_pattern = strings_or_patterns{iPattern}; 118 | 119 | if ~in.regexp 120 | cur_str_or_pattern = regexptranslate('escape',cur_str_or_pattern); 121 | end 122 | 123 | temp_mask = mask; 124 | 125 | %In this case we test only the ones that we need to ... 126 | %negating cur_mask provides an 'or' function rather than not negating 127 | %which would do 'and' 128 | if or_results 129 | process_mask = ~mask; 130 | else 131 | process_mask = mask; 132 | end 133 | 134 | data_to_test = data(process_mask); 135 | match_I = fh(data_to_test,cur_str_or_pattern,'once'); 136 | mask(process_mask) = ~cellfun('isempty',match_I); 137 | end 138 | 139 | % if output_I 140 | % 141 | % end 142 | 143 | end -------------------------------------------------------------------------------- /+adi/+sl/+cellstr/join.m: -------------------------------------------------------------------------------- 1 | function str = join(cellstr_input,varargin) 2 | %toString 3 | % 4 | % str = sl.cellstr.join(cellstr_input,varargin) 5 | % 6 | % INPUTS 7 | % ======================================================================= 8 | % cellstr : A cell array of strings to combine. 9 | % 10 | % OPTIONAL INPUTS 11 | % ======================================================================= 12 | % d : (default ','), delimiter string to use in combining strings 13 | % 14 | % To treat a delimiter as a literal escape the backlash with a 15 | % backlash. For example, this '\\t' will join strings with '\t' 16 | % instead of a tab. Percents should be escaped with a percent. 17 | % 18 | % The final delimiter => sprintf(delim) 19 | % 20 | % remove_empty : (default false), if true empty values are removed 21 | % 22 | % EXAMPLES 23 | % ======================================================================= 24 | % 25 | % TODO: Finish Documentation 26 | 27 | in.d = ','; 28 | in.remove_empty = false; 29 | in = adi.sl.in.processVarargin(in,varargin); 30 | 31 | if isempty(cellstr_input) 32 | str = ''; 33 | elseif ~iscell(cellstr_input) 34 | error('Input to %s must be a cell array',mfilename) 35 | else 36 | P = cellstr_input(:)'; 37 | if in.remove_empty 38 | P(cellfun('isempty',P)) = []; 39 | if isempty(P) 40 | str = ''; 41 | return 42 | end 43 | end 44 | P(2,:) = {sprintf(in.d)} ; %Added on printing to handle things like \t and \n 45 | 46 | P{2,end} = [] ; 47 | str = sprintf('%s',P{:}); 48 | end -------------------------------------------------------------------------------- /+adi/+sl/+datetime/getTimeZone.m: -------------------------------------------------------------------------------- 1 | function utc_offset = getTimeZone 2 | %getTimeZone Returns the time zone of the operating system. 3 | % 4 | % utc_offset = sl.datetime.getTimeZone 5 | % 6 | % Uses a Java call. This function is necessary for converting between 7 | % Matlab time which is local based, and many other time codes which are 8 | % all UTC based. 9 | % 10 | % OUTPUTS 11 | % ======================================================================= 12 | % utc_offset : # of hours different from UTC the computer is. For 13 | % example, a computer using Eastern Standard Time will return -5 14 | 15 | 16 | %Java call 17 | tz = java.util.TimeZone.getDefault; %in ms 18 | 19 | %convert to hours 20 | %1000 ms/s * 60 s/min * 60 min/hour 21 | utc_offset = tz.getRawOffset/3600000; -------------------------------------------------------------------------------- /+adi/+sl/+datetime/matlabToUnix.m: -------------------------------------------------------------------------------- 1 | function unix_time = matlabToUnix(matlab_time,utc_offset) 2 | %x Convert Matlab time to Unix Time 3 | % 4 | % unix_time = sl.datetime.matlabToUnix(matlab_time,*utc_offset) 5 | % 6 | % TODO: Finish documentation 7 | % 8 | % See Also: 9 | % sl.datetime.getTimeZone 10 | % sl.datetime.unixToMatlab 11 | 12 | utc_offset = 0; 13 | 14 | % if ~exist('utc_offset','var') || isempty(utc_offset) 15 | % utc_offset = sl.datetime.getTimeZone; 16 | % end 17 | 18 | SECONDS_IN_DAY = 86400; 19 | UNIX_EPOCH = 719529; 20 | 21 | unix_time = (matlab_time - utc_offset/24 - UNIX_EPOCH).*SECONDS_IN_DAY; 22 | 23 | end 24 | 25 | %Testing: 26 | %datestr(sl.datetime.unixToMatlab(sl.datetime.matlabToUnix(now))) -------------------------------------------------------------------------------- /+adi/+sl/+datetime/unixToMatlab.m: -------------------------------------------------------------------------------- 1 | function matlab_time = unixToMatlab(unix_time,utc_offset) 2 | %unixTimeToMatlabTime Converts unix time to Matlab time 3 | % 4 | % matlab_time = sl.datetime.unixToMatlab(unix_time,*utc_offset) 5 | % 6 | % INPUTS 7 | % ======================================================================= 8 | % unixTime - (element or vector), unix time, # of non-leap seconds since 9 | % January 1, 1970 10 | % 11 | % OPTIONAL INPUTS 12 | % ======================================================================= 13 | % utc_offset - (default, use local), -5 corresponds to EST 14 | % 15 | % OUTPUTS 16 | % ======================================================================= 17 | % matlab_time - double, fraction represents seconds, the integer part of 18 | % the #, floor(matlabTime) represents the # of days since A.D. 19 | % 20 | % See Also: 21 | % sl.datetime.getTimeZone 22 | 23 | 24 | if ~exist('utc_offset','var') || isempty(utc_offset) 25 | utc_offset = adi.sl.datetime.getTimeZone; 26 | end 27 | 28 | SECONDS_IN_DAY = 86400; 29 | UNIX_EPOCH = 719529; 30 | matlab_time = unix_time./SECONDS_IN_DAY + UNIX_EPOCH + utc_offset/24; -------------------------------------------------------------------------------- /+adi/+sl/+dir/changeFileExtension.m: -------------------------------------------------------------------------------- 1 | function new_file_path = changeFileExtension(file_path,new_extension) 2 | % 3 | % new_file_path = sl.dir.changeFileExtension(file_path,new_extension) 4 | % 5 | % This is a simple helper function that makes it obvious what is being 6 | % done (i.e. changing the file extension). 7 | % 8 | % Inputs: 9 | % ------- 10 | % file_path: str 11 | % Path to the file. It may be absolute or relative. 12 | % new_extension: str 13 | % The extension that the file_path should have when returned from 14 | % this function. This may or may not contain a leading period. 15 | % 16 | % Example: 17 | % -------- 18 | % file_path = 'C:\a\b.mat' 19 | % new_file_path = sl.dir.changeFileExtension(file_path,'txt') 20 | % new_file_path => 'C:\a\b.txt' 21 | 22 | 23 | %Ensure dot is present as first character 24 | if new_extension(1) ~= '.' 25 | new_extension = ['.' new_extension]; 26 | end 27 | 28 | [a,b] = fileparts(file_path); 29 | 30 | new_file_path = fullfile(a,[b new_extension]); 31 | 32 | 33 | end -------------------------------------------------------------------------------- /+adi/+sl/+dir/createFolderIfNoExist.m: -------------------------------------------------------------------------------- 1 | function output_path = createFolderIfNoExist(varargin) 2 | %createIfNecessary: Creates a folder if it doesn't exist 3 | % 4 | % This function creates a folder if it does not exist. In addition it can 5 | % be used to: 6 | % - construct the path of the folder 7 | % - construct the path to a file, ensuring that the folder path to 8 | % the file exists 9 | % 10 | % If multiple folders are missing in the path they will all be created. 11 | % 12 | % Function Forms: 13 | % --------------- 14 | % #1 - just create the folder if necessary 15 | % folder_path = sl.dir.createFolderIfNoExist(folderPath) - creates the folderPath 16 | % if it doesn't exist 17 | % 18 | % #2 - build path with multiple directories 19 | % folder_path = createFolderIfNoExist(folderPath,subdir1,subdir2,...,subdirN) - create 20 | % a directory tree starting at folderPath 21 | % 22 | % #3 - build file name, last input is file name 23 | % file_path = createFolderIfNoExist(true,folderPath,subdir1,subdir2,file_name) 24 | % 25 | % 26 | 27 | 28 | if islogical(varargin{1}) 29 | if varargin{1} 30 | file_name = varargin{end}; 31 | varargin([1 end]) = []; 32 | else 33 | varargin(1) = []; 34 | end 35 | else 36 | file_name = ''; 37 | end 38 | 39 | output_path = fullfile(varargin{:}); 40 | if ~exist(output_path,'dir') 41 | mkdir(output_path) 42 | end 43 | 44 | if ~isempty(file_name) 45 | output_path = fullfile(output_path,file_name); 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /+adi/+sl/+dir/filepartsx.m: -------------------------------------------------------------------------------- 1 | function path_out = filepartsx(file_or_folder_path,N) 2 | %filepartsx Applies fileparts() function numerous times 3 | % 4 | % path_out = sl.dir.filepartsx(file_or_folder_path,N) 5 | % 6 | % Small function to help clean up stripping of the path. 7 | % 8 | % INPUTS: 9 | % ------- 10 | % file_or_folder_path : path to file or folder 11 | % N : # of times to apply fileparts() function 12 | % 13 | % Example: 14 | % -------- 15 | % file_path = 'C:\my_dir1\my_dir2\my_file.txt'; 16 | % path_out = sl.dir.filepartsx(file_path,2); 17 | % 18 | % path_out => 'C:\my_dir1' 19 | 20 | path_out = file_or_folder_path; 21 | for iN = 1:N 22 | path_out = fileparts(path_out); 23 | end 24 | 25 | end -------------------------------------------------------------------------------- /+adi/+sl/+dir/fullfile.m: -------------------------------------------------------------------------------- 1 | function paths_out = fullfile(root,paths_in) 2 | %fullfile Appends paths to a root path 3 | % 4 | % paths_out = sl.dir.fullfile(root,paths_in) 5 | % 6 | % Performs the equivalent of fullfile() on each root/pathsIn pair. It 7 | % tries to accomplish this in the quickest way possible. 8 | % 9 | % Inputs: 10 | % ------- 11 | % root : (string), root path for appending to 12 | % paths_in : (cellstr), paths to append to the root 13 | % 14 | % Outputs: 15 | % -------- 16 | % paths_out : (cellstr) resultant concatenation of paths 17 | % 18 | % Examples: 19 | % --------- 20 | % 1) 21 | % root = 'C:\' 22 | % paths_in = {'test' 'cheese'}; 23 | % paths_out = sl.dir.fullfile(root,paths_in) 24 | % paths_out => 25 | % {'C:\test' 'C:\cheese'} 26 | % 27 | % IMPROVEMENTS 28 | % ------------ 29 | % 1) I don't like the name of this file. I might rename it ... 30 | % => fullfile would be fine 31 | % 32 | % See Also: 33 | % fullfile 34 | 35 | if isempty(paths_in) 36 | paths_out = {}; 37 | return 38 | elseif ischar(paths_in) 39 | error('paths_in must be a cell array of strings') 40 | end 41 | 42 | fs = filesep; 43 | if root(end) ~= fs 44 | root = [root fs]; 45 | end 46 | 47 | n_paths = length(paths_in); 48 | paths_out = cell(1,n_paths); 49 | for iPath = 1:n_paths 50 | paths_out{iPath} = [root paths_in{iPath}]; 51 | end 52 | 53 | %CODE ALTERNATIVES 54 | %-------------------------------------------------------------------------- 55 | 56 | %The above approach takes only about 60% of the time this one takes ... 57 | % paths_out = cellfun(@makePath_fsPresent,paths_in,'un',0); 58 | % 59 | % function myPath = makePath_fsPresent(inputPath) 60 | % myPath = [root inputPath]; 61 | % end 62 | 63 | end -------------------------------------------------------------------------------- /+adi/+sl/+in/processVarargin.m: -------------------------------------------------------------------------------- 1 | function [in,extras] = processVarargin(in,v,varargin) 2 | %processVarargin Processes varargin and overrides defaults 3 | % 4 | % Function to override default options. 5 | % 6 | % [in,extras] = sl.in.processVarargin(in,v,varargin) 7 | % 8 | % INPUTS 9 | % ======================================================================= 10 | % in : structure containing default values that may be overridden 11 | % by user inputs 12 | % v : varargin input from calling function, prop/value pairs or 13 | % structure with fields 14 | % 15 | % varargin : see optional inputs, prop/value or structure with fields 16 | % 17 | % OPTIONAL INPUTS (specify via prop/value pairs or struct) 18 | % ======================================================================= 19 | % Rules for these are: 20 | % - case insensitive 21 | % - non-matches not allowed ... 22 | % 23 | % case_sensitive : (default false) 24 | % allow_non_matches : (default false) 25 | % 26 | % 27 | % % allow_duplicates : (default false) NOT YET IMPLEMENTED 28 | % partial_match : (default false) NOT YET IMPLEMENTED 29 | % 30 | % OUTPUTS 31 | % ======================================================================= 32 | % extras : Class: sl.in.process_varargin_result 33 | % 34 | % TODO: Provide link 35 | % EXAMPLES 36 | % ======================================================================= 37 | % 1) 38 | % function test(varargin) 39 | % in.a = 1 40 | % in.b = 2 41 | % in = processVarargin(in,varargin,'allow_duplicates',true) 42 | % 43 | % Similar functions: 44 | % http://www.mathworks.com/matlabcentral/fileexchange/22671 45 | % http://www.mathworks.com/matlabcentral/fileexchange/10670 46 | % 47 | % IMPROVEMENTS 48 | % ======================================================================= 49 | % 1) For non-matched inputs, provide link to offending caller 50 | % 51 | % 52 | % See Also: 53 | % sl.in.tests.processVarargin 54 | 55 | 56 | 57 | %Check to exit code quickly when it is not used ... 58 | if isempty(v) && nargout == 1 59 | %Possible improvement 60 | %- provide code that allows this to return quicker if nargout == 2 61 | return 62 | end 63 | 64 | c.case_sensitive = false; 65 | % % % c.allow_duplicates = false; 66 | % % % c.partial_match = false; 67 | c.allow_non_matches = false; 68 | c.allow_spaces = true; 69 | 70 | 71 | %Update instructions on how to parse the optional inputs 72 | %-------------------------------------------------------------------------- 73 | %This type of code would allow a bit more flexibility on how to process 74 | %the processing options if we ever decided they needed to be different 75 | % 76 | % 77 | % c2 = c; 78 | % c2.case_sensitive = false; 79 | % 80 | %NOTE: If we don't pass in any instructions on how to parse the data 81 | %differently we can skip this step ... 82 | if ~isempty(varargin) 83 | %Updates c based on varargin from user 84 | %c = processVararginHelper(c,varargin,c2,1); 85 | c = processVararginHelper(c,varargin,c,true); 86 | end 87 | 88 | %Update optional inputs of calling function with this function's options now set 89 | [in,extras] = processVararginHelper(in,v,c,false); 90 | 91 | end 92 | 93 | 94 | 95 | function [in,extras] = processVararginHelper(in,v,c,is_parsing_options) 96 | %processVararginHelper 97 | % 98 | % [in,extras] = processVararginHelper(in,v,c,is_parsing_options) 99 | % 100 | % This function does the actual work. It is a separate function because 101 | % we use this function to handle the options on how this function should 102 | % work for the user's inputs. We use the same approach for the processing 103 | % options as we do the user's inputs. 104 | % 105 | % INPUTS 106 | % ======================================================================= 107 | % in - (structure input) 108 | % v - varargin input, might be structure or prop/value pairs 109 | % c - options for processing 110 | % is_parsing_options - specifies we are parsing the parsing options 111 | 112 | extras = adi.sl.in.process_varargin_result(in,v); 113 | 114 | %Checking the optional inputs, either a structure or a prop/value cell 115 | %array is allowed, or various forms of empty ... 116 | if isempty(v) 117 | %do nothing 118 | parse_input = false; 119 | elseif isstruct(v) 120 | %This case should generally not happen 121 | %It will if varargin is not used in the calling function 122 | parse_input = true; 123 | elseif isstruct(v{1}) && length(v) == 1 124 | %Single structure was passed in as sole argument for varargin 125 | v = v{1}; 126 | parse_input = true; 127 | elseif iscell(v) && length(v) == 1 && isempty(v{1}) 128 | %User passed in empty cell option to varargin instead of just ommitting input 129 | parse_input = false; 130 | else 131 | parse_input = true; 132 | is_str_mask = cellfun('isclass',v,'char'); 133 | 134 | %Improvement: 135 | %------------------------------------------------- 136 | %Analyze calling information ... 137 | %Provide stack trace for editing ... 138 | % 139 | % Functions needed: 140 | % 1) prototype of caller 141 | % 2) calling format of parent 142 | % 3) links to offending lines ... 143 | % 144 | % NOTE: is_parsing_options would allow us to have different 145 | % error messages ... 146 | if ~all(is_str_mask(1:2:end)) 147 | error('Unexpected format for varargin, not all properties are strings') 148 | end 149 | if mod(length(v),2) ~= 0 150 | error('Property/value pairs are not balanced, length of input: %d',length(v)) 151 | end 152 | 153 | if c.allow_spaces 154 | %strrep would be faster if we could guarantee 155 | %only single spaces :/ 156 | v(1:2:end) = regexprep(v(1:2:end),'\s+','_'); 157 | end 158 | 159 | v = v(:)'; %Ensure row vector 160 | v = cell2struct(v(2:2:end),v(1:2:end),2); 161 | end 162 | 163 | if ~parse_input 164 | return 165 | end 166 | 167 | extras.struct_mod_input = v; 168 | 169 | %At this point we should have a structure ... 170 | fn__new_values = fieldnames(v); 171 | fn__input_struct = fieldnames(in); 172 | extras.fn__new_values = fn__new_values; 173 | extras.fn__input_struct = fn__input_struct; 174 | 175 | 176 | %Matching location 177 | %-------------------------------------------------------------------------- 178 | if c.case_sensitive 179 | [is_present,loc] = ismember(fn__new_values,fn__input_struct); 180 | else 181 | [is_present,loc] = ismember(upper(fn__new_values),upper(fn__input_struct)); 182 | %NOTE: I don't currently do a check here for uniqueness of matches ... 183 | %Could have many fields which case-insensitive are the same ... 184 | end 185 | extras.is_present = is_present; 186 | extras.loc = loc; 187 | 188 | if ~all(is_present) 189 | if c.allow_non_matches 190 | %Lazy evaluation in result class 191 | else 192 | %NOTE: This would be improved by adding on the restrictions we used in mapping 193 | badVariables = fn__new_values(~is_present); 194 | error(['Bad variable names given in input structure: ' ... 195 | '\n--------------------------------- \n %s' ... 196 | ' \n--------------------------------------'],... 197 | adi.sl.cellstr.join(badVariables,'d',',')) 198 | end 199 | end 200 | 201 | %Actual assignment 202 | %--------------------------------------------------------------- 203 | for i = 1:length(fn__new_values) 204 | if is_present(i) 205 | %NOTE: By using fn_i we ensure case matching 206 | in.(fn__input_struct{loc(i)}) = v.(fn__new_values{i}); 207 | end 208 | end 209 | 210 | end 211 | -------------------------------------------------------------------------------- /+adi/+sl/+in/process_varargin_result.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) process_varargin_result < handle 2 | % 3 | % Class: 4 | % sl.in.process_varargin_result 5 | % 6 | % This is a result class from running sl.in.processVarargin 7 | % 8 | % See Also: 9 | % sl.in.processVarargin 10 | 11 | %Inputs ============================================================== 12 | properties 13 | d1 = '---- Inputs -----' 14 | input_structure 15 | raw_mod_input %The raw modification instructions ... 16 | end 17 | 18 | %The following may not be populated if the input is empty ... 19 | %Ideally they would have defaults that don't cause an error on display 20 | %... 21 | properties 22 | % FORM: 23 | % - [] 24 | % - struct 25 | % - cell array with 26 | struct_mod_input = struct([]) %modification instructions as 27 | %a structure 28 | end 29 | 30 | properties (Hidden) 31 | fn__new_values = {} 32 | fn__input_struct = {} 33 | 34 | %Result of ismember(new_values,original) 35 | is_present 36 | loc 37 | end 38 | 39 | %Outputs ============================================================== 40 | properties (Hidden) 41 | %NOTE: Ideally we could use a lazy evaluator which 42 | %removed the get method after the first evaluation 43 | %so that the default retrieval would be used ... :/ 44 | non_match_names_initialized = false 45 | unmatched_args_as_cell_initialized = false 46 | umatched_args_as_struct_initialized = false 47 | is_modified_initialized = false 48 | end 49 | 50 | properties 51 | d2 = '---- Outputs ----' 52 | non_match_names %(cellstr) 53 | umatched_args_as_cell %(?rename) 54 | umatched_args_as_struct %(?rename) 55 | is_modified 56 | end 57 | 58 | properties (Dependent) 59 | non_matches %alias of non_match_names - poor name choice :/ 60 | %Might remove, not sure if anything was using this ... 61 | end 62 | 63 | %Lazy get methods ===================================================== 64 | methods 65 | function value = get.non_matches(obj) 66 | value = obj.non_match_names; 67 | end 68 | function value = get.non_match_names(obj) 69 | if ~obj.non_match_names_initialized 70 | obj.non_match_names = obj.fn__new_values(~obj.is_present); 71 | obj.non_match_names_initialized = true; 72 | end 73 | value = obj.non_match_names; 74 | end 75 | function value = get.umatched_args_as_cell(obj) 76 | if ~obj.unmatched_args_as_cell_initialized 77 | s = obj.umatched_args_as_struct; 78 | 79 | not_matched_names = fieldnames(s); 80 | n_bad = length(not_matched_names); 81 | 82 | c = cell(1,n_bad*2); 83 | c(2:2:end) = struct2cell(s); 84 | c(1:2:end) = not_matched_names; 85 | obj.umatched_args_as_cell = c; 86 | 87 | obj.unmatched_args_as_cell_initialized = true; 88 | end 89 | value = obj.umatched_args_as_cell; 90 | end 91 | function value = get.umatched_args_as_struct(obj) 92 | if ~obj.umatched_args_as_struct_initialized 93 | obj.umatched_args_as_struct = rmfield(obj.struct_mod_input,obj.non_match_names); 94 | obj.umatched_args_as_struct_initialized = true; 95 | end 96 | value = obj.umatched_args_as_struct; 97 | end 98 | function value = get.is_modified(obj) 99 | if ~obj.is_modified_initialized 100 | fn_local = obj.fn__input_struct; 101 | n_fields = length(fn_local); 102 | 103 | changed_mask = false(1,n_fields); 104 | changed_mask(obj.loc(obj.is_present)) = true; 105 | 106 | obj.is_modified = cell2struct(num2cell(changed_mask),fn_local); 107 | obj.is_modified_initialized = true; 108 | end 109 | value = obj.is_modified; 110 | end 111 | end 112 | 113 | methods 114 | function obj = process_varargin_result(input_structure,raw_mod_input) 115 | obj.input_structure = input_structure; 116 | obj.raw_mod_input = raw_mod_input; 117 | end 118 | end 119 | 120 | end 121 | 122 | -------------------------------------------------------------------------------- /+adi/+sl/+io/mergePDFs.m: -------------------------------------------------------------------------------- 1 | function mergePDFs(input_file_paths,output_file_path,varargin) 2 | % 3 | % sl.io.mergePDFs(input_file_paths,output_file_path) 4 | % 5 | % Based on: 6 | % https://www.mathworks.com/matlabcentral/fileexchange/89127-merge-pdf-documents 7 | 8 | 9 | in.delete_inputs = false; 10 | in = adi.sl.in.processVarargin(in,varargin); 11 | 12 | %https://pdfbox.apache.org/docs/2.0.5/javadocs/org/apache/pdfbox/io/MemoryUsageSetting.html#setupMainMemoryOnly() 13 | memory_settings = org.apache.pdfbox.io.MemoryUsageSetting.setupMainMemoryOnly(); 14 | 15 | %https://pdfbox.apache.org/docs/2.0.1/javadocs/org/apache/pdfbox/multipdf/PDFMergerUtility.html 16 | merger = org.apache.pdfbox.multipdf.PDFMergerUtility; 17 | 18 | for i = 1:length(input_file_paths) 19 | if ~exist(input_file_paths{i},'file') 20 | error('File does not exist: %s',input_file_paths{i}) 21 | end 22 | merger.addSource(input_file_paths{i}); 23 | end 24 | 25 | merger.setDestinationFileName(output_file_path) 26 | 27 | merger.mergeDocuments(memory_settings) 28 | 29 | if in.delete_inputs 30 | for i = 1:length(input_file_paths) 31 | delete(input_file_paths{i}); 32 | end 33 | end 34 | 35 | end -------------------------------------------------------------------------------- /+adi/+sl/+plot/+export/saveAsPDF.m: -------------------------------------------------------------------------------- 1 | function saveAsPDF(h_fig,varargin) 2 | %X Save figure as PDF with prompt for filename 3 | % 4 | % adi.sl.plot.export.saveAsPDF(*h_fig,varargin) 5 | % 6 | % Optional Inputs 7 | % --------------- 8 | % file_path 9 | % Specify full path to file 10 | % 11 | % Examples 12 | % -------- 13 | % sl.plot.export.saveAsPDF(gcf,'file_path',file_path) 14 | % 15 | % Improvements 16 | % ------------ 17 | % 1)DONE - update documentation Allow file specification as an input 18 | % 2) Scale to print to a page size 19 | 20 | in.file_path = ''; 21 | in = adi.sl.in.processVarargin(in,varargin); 22 | 23 | if nargin == 0 || isempty(h_fig) 24 | h_fig = gcf; 25 | end 26 | 27 | if isempty(in.file_path) 28 | [file_name,path_name] = uiputfile(... 29 | {'*.pdf','PDF file (*.pdf)'; ... 30 | '*.*', 'All Files (*.*)'}, ... 31 | 'Save as', 'Untitled.pdf'); 32 | 33 | if isequal(file_name,0) || isequal(path_name,0) 34 | %disp('User pressed cancel') 35 | return 36 | end 37 | file_path = fullfile(path_name, file_name); 38 | else 39 | file_path = in.file_path; 40 | end 41 | 42 | 43 | %Ideally we wouldn't change anything ... 44 | s1 = get(h_fig); 45 | %The idea is to reset to s1 after changing 46 | 47 | h_fig.Units = 'inches'; 48 | h_fig.PaperSize = h_fig.Position(3:4); 49 | h_fig.PaperUnits = 'inches'; 50 | h_fig.PaperPosition = h_fig.Position; 51 | h_fig.PaperPositionMode = 'auto'; 52 | 53 | 54 | 55 | print(h_fig,'-dpdf','-painters',file_path); 56 | 57 | end -------------------------------------------------------------------------------- /+adi/+sl/+stack/calling_function_info.m: -------------------------------------------------------------------------------- 1 | classdef calling_function_info < handle 2 | % 3 | % Class: 4 | % sl.stack.calling_function_info 5 | % 6 | % See Also: 7 | % sl.warning.deprecated 8 | % 9 | % IMPROVEMENTS: 10 | % =================================================================== 11 | % 1) We might eventually add on a workspace grab of the level 12 | % requested 13 | % 2) Multiple levels??? 14 | % 3) Add on an option for a fully resolved name (make a separate 15 | % property) -> i.e. for when things are in packages 16 | 17 | properties 18 | line_number = NaN 19 | file_path = '' 20 | name %file_name or 'CommandWindow' when called from command window 21 | %Notes about name: 22 | % 23 | % 1) The file name lacks an extension 24 | % 2) For classes, a function will contain the class name (at 25 | % least for static methods ...) 26 | % 3) The name lacks the package 27 | is_cmd_window = false 28 | end 29 | 30 | methods 31 | function obj = calling_function_info(level) 32 | %calling_function_info Returns the caller of the calling function. 33 | % 34 | % obj = sl.stack.calling_function_info(*level) 35 | % 36 | % 37 | % INPUTS 38 | % ============================================================ 39 | % level : (scalar, default: 2) which caller to retrieve, 40 | % where 1 denotes caller of this function, 2 caller of 41 | % the function calling this function, etc 42 | % 43 | % Known Users: 44 | % sl.warning.deprecated 45 | % 46 | % tags: utility, display 47 | 48 | if nargin < 1 49 | level = 2; 50 | end 51 | 52 | s = dbstack('-completenames'); 53 | assert(level > 0,'The input ''level'' must be > 0, %d observed',level); 54 | 55 | %NOTE: Stack has most recent on top 56 | %this - index 1 57 | %caller - index 2 58 | %etc 59 | 60 | if length(s) == 1 61 | obj.name = 'CommandWindow'; 62 | obj.is_cmd_window = true; 63 | else 64 | idx = level + 1; 65 | obj.name = s(idx).name; 66 | obj.file_path = s(idx).file; 67 | obj.line_number = s(idx).line; 68 | end 69 | end 70 | end 71 | end 72 | 73 | -------------------------------------------------------------------------------- /+adi/+sl/+stack/getMyBasePath.m: -------------------------------------------------------------------------------- 1 | function base_path = getMyBasePath(file_name,varargin) 2 | %getMyPath Returns base path of calling function 3 | % 4 | % base_path = sl.stack.getMyBasePath(*file_name,varargin) 5 | % 6 | % Outputs: 7 | % -------- 8 | % base_path : path to cotaining folder of function that is calling 9 | % this function. 10 | % 11 | % Inputs: 12 | % ------- 13 | % file_name : (default '') 14 | % If empty, examines calling function, otherwise it runs which() on 15 | % the name to resolve the path. When called from a script or command 16 | % line returns the current directory. 17 | % 18 | % Optional Inputs: 19 | % ---------------- 20 | % n_dirs_up : (default 0) 21 | % If not 0, the returned value is stripped of the path by the 22 | % specified number of directories. For example a value that would 23 | % normally be: 24 | % /Users/Jim/my/returned/path/testing 25 | % with a 'n_dirs_up' value of 2 would return: 26 | % /Users/Jim/my/returned/ 27 | % n_callers_up: (default 0) 28 | % Normally this returns the path of the caller. If for some reason 29 | % you wanted to get the path of the function calling the function 30 | % that calls this function, set 'n_callers_up' to 1. Higher values can 31 | % also be used to get 'higher order' callers. 32 | % 33 | % Examples: 34 | % --------- 35 | % 1) Typical usage case: 36 | % 37 | % base_path = getMyBasePath(); 38 | % 39 | % 2) Useful for executing in a script where you want the script path 40 | % 41 | % base_path = getMyBasePath('myScriptsName') 42 | % 43 | % 3) TODO: Provide example with n_dirs_up being used 44 | % 45 | % Improvements: 46 | % ------------- 47 | % 1) Provide specific examples ... 48 | % 49 | % 50 | % See Also: 51 | % sl.dir.filepartsx 52 | 53 | in.n_dirs_up = 0; 54 | in.n_callers_up = 0; 55 | in = adi.sl.in.processVarargin(in,varargin); 56 | 57 | 58 | %NOTE: the function mfilename() can't be used with evalin 59 | % (as of 2009b) 60 | 61 | %We use the stack to get the path 62 | if nargin == 0 || isempty(file_name) 63 | stack = dbstack('-completenames'); 64 | if length(stack) == 1 65 | base_path = cd; 66 | else 67 | %NOTE: 68 | % - 1 refers to this function 69 | % - 2 refers to the calling function 70 | base_path = fileparts(stack(2 + in.n_callers_up).file); 71 | end 72 | else 73 | filePath = which(file_name); 74 | base_path = fileparts(filePath); 75 | end 76 | 77 | if in.n_dirs_up ~= 0 78 | base_path = adi.sl.dir.filepartsx(base_path,in.n_dirs_up); 79 | end 80 | 81 | end -------------------------------------------------------------------------------- /+adi/+sl/+stack/getPackageRoot.m: -------------------------------------------------------------------------------- 1 | function package_root = getPackageRoot() 2 | % 3 | % package_root = sl.stack.getPackageRoot() 4 | % 5 | % Returns the path of the folder that contains the base package. 6 | % 7 | % Examples: 8 | % Called from: 'C:\repos\matlab_git\my_repo\+package\my_function.m 9 | % Returns: 'C:\repos\matlab_git\my_repo\' 10 | % 11 | 12 | temp_path = adi.sl.stack.getMyBasePath('','n_callers_up',1); 13 | 14 | I = strfind(temp_path,'+'); 15 | if isempty(I) 16 | package_root = temp_path; 17 | else 18 | last_char_I = I(1)-2; 19 | package_root = temp_path(1:last_char_I); 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /+adi/+sl/+str/contains.m: -------------------------------------------------------------------------------- 1 | function flag = contains(input_string,string_to_match,varargin) 2 | % 3 | % sl.str.contains(s1,str_to_match,varargin) 4 | % 5 | % Checks if one string can be found in another string. 6 | % 7 | % Inputs: 8 | % ------- 9 | % input_string: char 10 | % string_to_match: char 11 | % 12 | % Optional Inputs: 13 | % ---------------- 14 | % location: (default 'anywhere' 15 | % - 'start' 16 | % - 'end' 17 | % - 'anywhere' 18 | % 19 | % Examples: 20 | % ---------- 21 | % >> sl.str.contains('testing','ing','location','end') 22 | % ans = 1 23 | % 24 | % >> sl.str.contains('testing','est','location','anywhere') 25 | 26 | in.case_sensitive = true; 27 | in.location = 'anywhere'; 28 | in = adi.sl.in.processVarargin(in,varargin); 29 | 30 | switch in.location 31 | case 'start' 32 | if in.case_sensitive 33 | flag = strncmp(input_string,string_to_match,length(string_to_match)); 34 | else 35 | flag = strncmpi(input_string,string_to_match,length(string_to_match)); 36 | end 37 | case 'end' 38 | length_str = length(string_to_match); 39 | if length(input_string) < length_str 40 | flag = false; 41 | else 42 | if in.case_sensitive 43 | flag = strcmp(input_string((end-length_str+1):end),string_to_match); 44 | else 45 | flag = strcmpi(input_string((end-length_str+1):end),string_to_match); 46 | end 47 | end 48 | case 'anywhere' 49 | if in.case_sensitive 50 | flag = any(strfind(input_string,string_to_match)); 51 | else 52 | %I had considered using regexpi but then I need to worry about 53 | %translating the string to match 54 | %flag = any(regexpi(input_string,regexptranslate(string_to_match),'match','once') 55 | flag = any(strfind(lower(input_string),lower(string_to_match))); 56 | end 57 | otherwise 58 | error('Unrecognized option for string match: %s',in.location); 59 | end 60 | 61 | end 62 | 63 | %{ 64 | 65 | %Testing end match 66 | sl.str.contains('testing','ing','location','end') 67 | 68 | %String too long 69 | sl.str.contains('testing','asdfasdfing','location','end') 70 | 71 | %Testing start 72 | sl.str.contains('testing','test','location','start') 73 | 74 | %Middle match 75 | sl.str.contains('testing','est','location','anywhere') 76 | 77 | 78 | %} -------------------------------------------------------------------------------- /+adi/+tests/t001_speedWritingAndWriting.m: -------------------------------------------------------------------------------- 1 | function t001_speedWritingAndWriting 2 | % 3 | % adi.tests.t001_speedWritingAndWriting 4 | % 5 | % STATUS: 6 | 7 | FILE_PATH = 'C:\Data\GSK\ChrisRaw\140121 pelvic nerve recordings.adicht'; 8 | SAVE_DIR = 'D:\Data\HDF5_test'; 9 | 10 | %Things to vary: 11 | %- chunk size 12 | %- compression level 13 | %- shuffle or no shuffle 14 | 15 | quick_test = false; 16 | if quick_test 17 | pct_chunk_sizes = 0.5; %0.05:0.05:1; 18 | compression_levels = 1; %0:9; 19 | shuffle_options = false; %[true false]; 20 | n_repeats = 1; 21 | else 22 | pct_chunk_sizes = 0.2:0.2:1; 23 | compression_levels = [0 1:2:9]; 24 | shuffle_options = [true, false]; 25 | n_repeats = 3; 26 | end 27 | 28 | n_chunk_sizes = length(pct_chunk_sizes); 29 | n_compression = length(compression_levels); 30 | n_shuffle = length(shuffle_options); 31 | 32 | 33 | 34 | write_times = zeros(n_chunk_sizes,n_compression,n_shuffle,n_repeats); 35 | file_sizes = write_times; 36 | 37 | [~,file_name] = fileparts(FILE_PATH); 38 | results_path = fullfile(SAVE_DIR,[file_name '_results.mat']); 39 | 40 | for iRepeat = 1:n_repeats 41 | for iChunkPct = 1:n_chunk_sizes 42 | for iCompression = 1:n_compression 43 | for iShuffle = 1:n_shuffle 44 | fprintf('Converting %s, %d, %d, %d, %d\n',datestr(now), iRepeat,iCompression,iChunkPct,iShuffle) 45 | options = adi.h5_conversion_options; 46 | options.chunk_length_pct = pct_chunk_sizes(iChunkPct); 47 | options.use_shuffle = shuffle_options(iShuffle); 48 | options.deflate_value = compression_levels(iCompression); 49 | t = tic; 50 | 51 | 52 | % suffix = sprintf('c%d_len%d_s%d',... 53 | % options.deflate_value,... 54 | % options.chunk_length,... 55 | % options.use_shuffle); 56 | 57 | suffix = sprintf('_c%d_p%d_s%d',iCompression,iChunkPct,iShuffle); 58 | 59 | save_path = fullfile(SAVE_DIR,[file_name suffix '.h5']); 60 | 61 | adi.convert(FILE_PATH,... 62 | 'save_path',save_path,... 63 | 'conversion_options',options); 64 | toc(t) %For display 65 | elapsed_time = toc(t); 66 | write_times(iChunkPct,iCompression,iShuffle,iRepeat) = elapsed_time; 67 | 68 | temp = dir(save_path); 69 | file_sizes(iChunkPct,iCompression,iShuffle,iRepeat) = temp.bytes; 70 | end 71 | end 72 | end 73 | save(results_path,'file_sizes','write_times','pct_chunk_sizes',... 74 | 'compression_levels','shuffle_options'); 75 | end 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /+adi/+w/@sample_count_manager/sample_count_manager.m: -------------------------------------------------------------------------------- 1 | classdef sample_count_manager < handle 2 | % 3 | % Class: 4 | % adi.w.sample_count_manager 5 | % 6 | % We may not need this ... 7 | 8 | properties 9 | end 10 | 11 | methods 12 | end 13 | 14 | end 15 | 16 | -------------------------------------------------------------------------------- /+adi/@channel/exportToHDF5File.m: -------------------------------------------------------------------------------- 1 | function exportToHDF5File(objs,fobj,save_path,conversion_options) 2 | % 3 | % adi.channel.exportToHDF5File 4 | % 5 | % See Also: 6 | % --------- 7 | % adi.file.exportToHDF5File 8 | 9 | 10 | DEFLATE_VALUE = conversion_options.deflate_value; 11 | MAX_SAMPLES_PER_READ = conversion_options.max_samples_per_read; 12 | CHUNK_LENGTH = conversion_options.chunk_length; 13 | SHUFFLE_FLAG = conversion_options.use_shuffle; 14 | 15 | DATA_TYPE = 'single'; 16 | 17 | group_name = '/channel_meta'; 18 | h5m.group.create(fobj,'channel_version'); 19 | h5writeatt(save_path,'/channel_version','version',1); 20 | 21 | h5m.group.create(fobj,group_name); 22 | %TODO: Rewrite with h5m library 23 | 24 | h5writeatt(save_path,group_name,'name',int16(char({objs.name}))); 25 | h5writeatt(save_path,group_name,'id',[objs.id]); 26 | 27 | temp = vertcat(objs.units); 28 | temp = int16(char(temp(:))); 29 | h5writeatt(save_path,group_name,'units',temp); 30 | h5writeatt(save_path,group_name,'dt',vertcat(objs.dt)); 31 | h5writeatt(save_path,group_name,'n_samples',vertcat(objs.n_samples)); 32 | 33 | h5m.group.create(fobj,'data_version'); 34 | h5writeatt(save_path,'/data_version','version',1); 35 | 36 | %Now onto saving the data 37 | %---------------------------------------------- 38 | n_objs = length(objs); 39 | n_records = objs(1).n_records; 40 | for iChan = 1:n_objs 41 | cur_chan = objs(iChan); 42 | for iRecord = 1:n_records 43 | cur_n_samples = cur_chan.n_samples(iRecord); 44 | chan_name = sprintf('/data__chan_%d_rec_%d',iChan,iRecord); 45 | 46 | h5create(save_path,chan_name,[cur_n_samples 1],... 47 | 'ChunkSize',[min(CHUNK_LENGTH,cur_n_samples) 1],... 48 | 'Datatype',DATA_TYPE,... 49 | 'Deflate',DEFLATE_VALUE,... 50 | 'Shuffle',SHUFFLE_FLAG); 51 | 52 | if cur_n_samples < MAX_SAMPLES_PER_READ 53 | %This is a write sequence 54 | 55 | h5write(save_path, chan_name, ... 56 | cur_chan.getData(iRecord,'leave_raw',true,'return_object',false)); 57 | else 58 | 59 | start_I = 1:MAX_SAMPLES_PER_READ:cur_n_samples; 60 | end_I = MAX_SAMPLES_PER_READ:MAX_SAMPLES_PER_READ:cur_n_samples; 61 | 62 | if length(end_I) < length(start_I) 63 | end_I(end+1) = cur_n_samples; %#ok 64 | end 65 | 66 | for iChunk = 1:length(start_I) 67 | cur_start = start_I(iChunk); 68 | cur_end = end_I(iChunk); 69 | n_samples_get = cur_end-cur_start + 1; 70 | 71 | data = cur_chan.getData(iRecord,'data_range',... 72 | [cur_start,cur_start+n_samples_get-1],'leave_raw',true,... 73 | 'return_object',false); 74 | 75 | 76 | 77 | h5write(save_path,chan_name,data,[cur_start 1],[length(data) 1],[1 1]) 78 | end 79 | end 80 | end 81 | end 82 | 83 | 84 | end -------------------------------------------------------------------------------- /+adi/@comment/comment.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) comment < handle 2 | % 3 | % Class: 4 | % adi.comment 5 | % 6 | % Holds a comment. 7 | % 8 | % NOTE: Comments can be moved after they are placed. I think 9 | % this means that the comments might not be ordered by ID. 10 | % 11 | % Also comments can be deleted, which might cause gaps in the ID 12 | % numbering as well. 13 | % 14 | % See Also 15 | % -------- 16 | % adi.file 17 | 18 | properties 19 | str %The string content of the comment 20 | id %The number associated with the comment, starts at ???? 21 | tick_position %????????? 22 | %TODO: Add time since start of record and experiment ... 23 | channel %-1 indicates all channels 24 | record %(#, not a pointer) 25 | tick_dt 26 | trigger_minus_rec_start %(in seconds) 27 | end 28 | 29 | properties (Dependent) 30 | time %Time in seconds since the start of the record. 31 | end 32 | 33 | properties (Dependent) 34 | absolute_time 35 | end 36 | properties (Hidden) 37 | %Populated by the record ... 38 | record_start_datetime 39 | end 40 | 41 | methods 42 | function value = get.absolute_time(obj) 43 | seconds = obj.tick_position*obj.tick_dt + obj.trigger_minus_rec_start; 44 | n_seconds_day = 86400; 45 | value = obj.record_start_datetime + seconds/n_seconds_day; 46 | end 47 | end 48 | 49 | properties 50 | d1 = '------ Options -----' 51 | time_relative_to_file = true 52 | %These are unfortunately not absolute times 53 | end 54 | 55 | properties (Hidden) 56 | new_time 57 | %Added to allow the user to set the new time of the comment 58 | end 59 | 60 | methods 61 | function value = get.time(obj) 62 | if ~isempty(obj.new_time) 63 | value = obj.new_time; 64 | elseif obj.time_relative_to_file 65 | value = obj.tick_position*obj.tick_dt; 66 | else 67 | value = obj.tick_position*obj.tick_dt + obj.trigger_minus_rec_start; 68 | end 69 | end 70 | function set.time(obj,value) 71 | obj.new_time = value; 72 | obj.tick_position = NaN; 73 | end 74 | end 75 | 76 | methods 77 | function obj = comment(... 78 | comment_string,... 79 | tick_pos,... 80 | channel,... 81 | comment_num,... 82 | record_id,... 83 | tick_dt,... 84 | trigger_minus_rec_start) 85 | 86 | obj.str = comment_string; 87 | obj.id = comment_num; 88 | obj.tick_position = tick_pos; 89 | obj.channel = channel; 90 | obj.record = record_id; 91 | obj.tick_dt = tick_dt; 92 | obj.trigger_minus_rec_start = trigger_minus_rec_start; 93 | end 94 | function t = toTable(objs) 95 | s = struct(); 96 | s.str = {objs.str}'; 97 | s.id = [objs.id]'; 98 | s.tick_position = [objs.tick_position]'; 99 | s.channel = [objs.channel]'; 100 | s.record = [objs.record]'; 101 | s.tick_dt = [objs.tick_dt]'; 102 | s.trigger_minus_rec_start = [objs.trigger_minus_rec_start]'; 103 | s.time = [objs.time]'; 104 | s.absolute_time = [objs.absolute_time]'; 105 | t = struct2table(s); 106 | end 107 | function mask = contains(objs,strings_or_patterns,varargin) 108 | % 109 | % 110 | % contains(data,strings_or_patterns,varargin) 111 | % 112 | % Inputs 113 | % ------ 114 | % data : cellstr 115 | % strings_or_patterns : char or cellstr 116 | % 117 | % Optional Inputs 118 | % --------------- 119 | % case_sensitive : logical (default false) 120 | % regexp : logical (default false) 121 | % 122 | % The default behavior is to OR the matches, i.e. true if any of 123 | % the strings match. For AND, all the strings must match. 124 | % use_or: logical (default []) 125 | % - true - OR 126 | % - false - AND 127 | % use_and: logical (default []) 128 | % - true - AND 129 | % - false - OR 130 | 131 | in.use_or = []; 132 | in.use_and = []; 133 | in.case_sensitive = false; 134 | in.regexp = false; 135 | in = adi.sl.in.processVarargin(in,varargin); 136 | 137 | strings = {objs.str}; 138 | 139 | mask = adi.sl.cellstr.contains(strings,strings_or_patterns,in); 140 | 141 | end 142 | % function objs = sortByID(objs) 143 | % 144 | % end 145 | % function changeTimeBasis(objs) 146 | % % NOT YET IMPLEMENTED 147 | % error('Not yet implemented') 148 | % end 149 | function pretty_print(objs) 150 | % 151 | % 152 | % Example Output: 153 | % 154 | % Format 155 | % ID : time : str 156 | % Record #04 157 | % 002: 315.85: start pump 158 | % 003: 318.85: 5 ml/hr 159 | % 004: 1624.55: stop pump 160 | % 008: 1952.00: qp 1 161 | % 005: 2088.35: start pump 162 | % 006: 2782.95: stop pump 163 | % 007: 3011.50: qp 2 164 | 165 | all_records = [objs.record]; 166 | [u,uI] = adi.sl.array.uniqueWithGroupIndices(all_records); 167 | 168 | n_records = length(u); 169 | 170 | fprintf('Format\n'); 171 | fprintf('ID : time : str\n'); 172 | 173 | for iRecord = 1:n_records 174 | cur_record_indices = uI{iRecord}; 175 | cur_record = u(iRecord); 176 | n_indices = length(cur_record_indices); 177 | 178 | fprintf('Record #%02d\n',cur_record); 179 | for iComment = 1:n_indices 180 | cur_obj = objs(cur_record_indices(iComment)); 181 | fprintf('%03d: %0.2f: %s\n',cur_obj.id,cur_obj.time,cur_obj.str); 182 | end 183 | end 184 | end 185 | function objs_out = filterByIDs(objs_in,ids_to_get,varargin) 186 | % 187 | % 188 | % objs_out = filterByIDs(objs_in,ids_to_get,varargin) 189 | 190 | in.order = 'input_id'; 191 | %Other orders: NYI 192 | % - object order 1st object, 2nd 3rd, etc (based on order of 193 | % objs_in => sort loc before returning ... 194 | in = adi.sl.in.processVarargin(in,varargin); 195 | 196 | all_ids = [objs_in.id]; 197 | 198 | [mask,loc] = ismember(ids_to_get,all_ids); 199 | 200 | if ~all(mask) 201 | error('One of the requested ids is missing') 202 | end 203 | 204 | objs_out = objs_in(loc); 205 | 206 | 207 | end 208 | function objs_out = filterByRecord(objs_in,record_id) 209 | keep_mask = [objs.record] == record_id; 210 | objs_out = objs_in(keep_mask); 211 | end 212 | function objs_out = filterByTime(objs_in,time_range) 213 | times = [objs_in.time]; 214 | keep_mask = times >= time_range(1) & times <= time_range(2); 215 | objs_out = objs_in(keep_mask); 216 | end 217 | function objs_out = filterByChannel(objs_in,channel_id) 218 | channels = [kept_objs.channel]; 219 | keep_mask = channels == -1 | channels == channel_id; 220 | objs_out = objs_in(keep_mask); 221 | end 222 | end 223 | methods 224 | function exportToHDF5File(objs,fobj,save_path,conversion_options) 225 | group_name = '/comments'; 226 | h5m.group.create(fobj,'comment_version'); 227 | h5writeatt(save_path,'/comment_version','version',1); 228 | 229 | h5m.group.create(fobj,group_name); 230 | 231 | %TODO: Rewrite with h5m library 232 | %TODO: This needs to be fixed 233 | h5writeatt(save_path,group_name,'str',int16(char({objs.str}))); 234 | 235 | h5writeatt(save_path,group_name,'id',[objs.id]); 236 | h5writeatt(save_path,group_name,'tick_position',[objs.tick_position]); 237 | h5writeatt(save_path,group_name,'channel',[objs.channel]); 238 | h5writeatt(save_path,group_name,'record',[objs.record]); 239 | h5writeatt(save_path,group_name,'tick_dt',[objs.tick_dt]); 240 | end 241 | function m = exportToMatFile(objs,m,conversion_options) 242 | 243 | m.comment_version = 1; 244 | m.comments = struct(... 245 | 'str', {objs.str},... 246 | 'id', {objs.id},... 247 | 'tick_position', {objs.tick_position},... 248 | 'channel', {objs.channel},... 249 | 'record', {objs.record},... 250 | 'tick_dt', {objs.tick_dt}); 251 | 252 | end 253 | end 254 | 255 | end 256 | 257 | -------------------------------------------------------------------------------- /+adi/@file_viewer/file_viewer.m: -------------------------------------------------------------------------------- 1 | classdef file_viewer 2 | % 3 | % Class: 4 | % adi.file_viewer 5 | % 6 | % This class was a work in progress. I'm not currently working 7 | % on finishing it ... 8 | % 9 | 10 | properties 11 | end 12 | 13 | methods 14 | function obj = file_viewer(file_path) 15 | % 16 | % obj = adi.file_viewer(file_path) 17 | % 18 | % See Also: 19 | % --------- 20 | % adi.view 21 | 22 | %TODO: 23 | %--------------------------- 24 | %- Display comments in panel 25 | % - click on comment to go to comment 26 | %X DONE Add title to figue window 27 | %- manual ylims - create nice interface for this 28 | %- support panning 29 | %- build in processing support - filtering 30 | %- add support for adding a channel 31 | %- reorder channels 32 | %- display comments on figures 33 | %- allow saving viewing settings into a file for 34 | % later retrieval 35 | 36 | f = adi.readFile(file_path); 37 | h_figure = figure; 38 | 39 | for iChannel = 1:f.n_channels 40 | cur_channel_name = f.channel_names{iChannel}; 41 | chan_obj = f.getChannelByName(cur_channel_name,'partial_match',false); 42 | 43 | %TODO: Allow array retrieval of data for multiple records 44 | all_chan_data = cell(1,f.n_records); 45 | %all_chan_data = cell(1,1); 46 | for iRecord = 1:f.n_records 47 | if ~isnan(chan_obj.dt(iRecord)) 48 | all_chan_data{iRecord} = chan_obj.getData(iRecord); 49 | else 50 | error('This causes problems with offsets, need to fix this') 51 | all_chan_data{iRecord} = []; 52 | end 53 | end 54 | 55 | subplot(f.n_channels,1,iChannel); 56 | plot([all_chan_data{:}]) 57 | 58 | end 59 | 60 | set(h_figure,'name',file_path); 61 | sl.plot.postp.linkFigureAxes(h_figure,'x'); 62 | 63 | h_axes = sl.hg.figure.getAxes(h_figure); 64 | 65 | set(h_axes,'YLimMode','manual'); 66 | 67 | scroll = sl.plot.big_data.scrollbar(gca); 68 | 69 | sl.plot.postp.autoscale(h_axes) 70 | 71 | 72 | keyboard 73 | 74 | end 75 | end 76 | 77 | end 78 | 79 | -------------------------------------------------------------------------------- /+adi/@file_viewer/private/main.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/+adi/@file_viewer/private/main.fig -------------------------------------------------------------------------------- /+adi/channel_writer.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) channel_writer < handle 2 | % 3 | % Class: 4 | % adi.channel_writer 5 | % 6 | % This is meant to be an interface 7 | % 8 | % adi.setChannelName(file_h,channel,channel_name) 9 | % adi.sdk.setChannelInfo 10 | % adi.sdk.addChannelSamples 11 | % 12 | % See Also 13 | % -------- 14 | % adi.file_writer 15 | 16 | 17 | properties 18 | id 19 | end 20 | %*** These are currently not write safe - they don't get updated 21 | properties 22 | name 23 | fs 24 | units 25 | enabled = true 26 | samples_per_record 27 | end 28 | 29 | properties (Hidden) 30 | parent %adi.file_writer 31 | end 32 | 33 | properties (Dependent) 34 | duration_per_record 35 | last_record_duration 36 | current_record 37 | end 38 | 39 | methods 40 | function value = get.duration_per_record(obj) 41 | value = obj.samples_per_record./obj.fs; 42 | end 43 | function value = get.current_record(obj) 44 | value = obj.parent.current_record; 45 | end 46 | function value = get.last_record_duration(obj) 47 | all_durations = obj.duration_per_record; 48 | if isempty(all_durations) 49 | value = NaN; 50 | else 51 | value = all_durations(end); 52 | end 53 | end 54 | end 55 | 56 | methods 57 | function obj = channel_writer(file_writer_obj,id,name,fs,units) 58 | % 59 | % 60 | % adi.channel_writer(file_writer_obj,id,name,fs,units) 61 | 62 | obj.parent = file_writer_obj; 63 | obj.id = id; 64 | obj.name = name; 65 | obj.fs = fs; 66 | obj.units = units; 67 | obj.updateName(); 68 | obj.updateInfo(); 69 | end 70 | function initializeRecord(objs,record_number) 71 | % 72 | % initializeRecord(objs,record_number) 73 | % 74 | for iObj = 1:length(objs) 75 | obj = objs(iObj); 76 | if length(obj.samples_per_record) < record_number 77 | temp = obj.samples_per_record; 78 | obj.samples_per_record = zeros(1,record_number); 79 | obj.samples_per_record(1:length(temp)) = temp; 80 | end 81 | end 82 | end 83 | function updateName(obj) 84 | file_h = obj.parent.file_h; 85 | adi.sdk.setChannelName(file_h,obj.id,obj.name); 86 | end 87 | function updateInfo(obj) 88 | % 89 | % 90 | % TODO: Expose limits 91 | % TODO: Allow updating units and everything else, update 92 | % properties and then make this call. 93 | % - not sure how to handle changes in fs with record 94 | % management - supporting this all is low priority 95 | % 96 | %Why is this handle different than the file handle? 97 | writer_h = obj.parent.data_writer_h; 98 | adi.sdk.setChannelInfo(writer_h,obj.id,1/obj.fs,obj.units,'enabled_for_record',obj.enabled); 99 | end 100 | function addSamples(obj,data) 101 | % 102 | % addSamples(obj,data) 103 | 104 | cur_record_local = obj.current_record; 105 | n_samples = length(data); 106 | obj.samples_per_record(cur_record_local) = obj.samples_per_record(cur_record_local) + n_samples; 107 | 108 | writer_h = obj.parent.data_writer_h; 109 | adi.sdk.addChannelSamples(writer_h,obj.id,data) 110 | end 111 | end 112 | 113 | end 114 | 115 | -------------------------------------------------------------------------------- /+adi/comment_handle.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) comment_handle < handle 2 | % 3 | % Class: 4 | % adi.comment_handle 5 | % 6 | % This contains a handle that is needed by the SDK to get comments 7 | % for a particular record. Methods in this class can be used to 8 | % extract actual comments from a record. 9 | % 10 | % See Also: 11 | % adi.comment 12 | 13 | 14 | properties 15 | pointer_value %Pointer to the a commenct accessor object in the mex 16 | %code. This gets cast to ADI_CommentsHandle in the mex code. This 17 | %shouldn't be changed ... 18 | 19 | is_valid = false %A handle may not be valid if there are no comments 20 | %for a given record 21 | record 22 | tick_dt 23 | file_name 24 | trigger_minus_rec_start 25 | end 26 | 27 | methods 28 | function obj = comment_handle(file_name,pointer_value,is_valid,record_id,tick_dt,trigger_minus_rec_start) 29 | % 30 | % obj = adi.comment_handle(pointer_value,is_valid) 31 | 32 | obj.file_name = file_name; 33 | obj.pointer_value = pointer_value; 34 | obj.is_valid = is_valid; 35 | obj.record = record_id; 36 | obj.tick_dt = tick_dt; 37 | obj.trigger_minus_rec_start = trigger_minus_rec_start; 38 | end 39 | function delete(obj) 40 | if ~obj.is_valid 41 | return 42 | end 43 | %fprintf(2,'ADI SDK: Deleting comment: %s\n',obj.file_name); 44 | adi.sdk.closeCommentAccessor(obj.pointer_value); 45 | end 46 | function has_another_comment = advanceCommentPointer(obj) 47 | has_another_comment = adi.sdk.advanceComments(obj); 48 | end 49 | function cur_comment = getCurrentComment(obj) 50 | cur_comment = adi.sdk.getCommentInfo(obj); 51 | end 52 | function close(obj) 53 | %fprintf(2,'ADI SDK: Closing comment: %s\n',obj.file_name); 54 | adi.sdk.closeCommentAccessor(obj.pointer_value); 55 | obj.is_valid = false; 56 | end 57 | end 58 | 59 | 60 | end 61 | 62 | -------------------------------------------------------------------------------- /+adi/convert.m: -------------------------------------------------------------------------------- 1 | function save_path = convert(file_path_or_paths,varargin) 2 | % 3 | % adi.convert(*file_path,varargin) 4 | % 5 | % This function will save a .adicht file's contents into a .mat file 6 | % (v7.3) or a hdf5 file 7 | % 8 | % The format is a bit awkward since it was originally written using a 9 | % 32bit SDK which meant that partial reads and writes needed to be 10 | % supported. 11 | % 12 | % Once converted, the converted file can be read by this program as well. 13 | % 14 | % Optional Inputs: 15 | % ---------------- 16 | % file_path_or_paths : str or cellstr (default: prompts user) 17 | % Path to the file or files. If omitted a prompt will ask the user 18 | % to select the file to convert. 19 | % format : {'h5','mat'} (default 'mat') 20 | % The format to convert the file to. H5 (HDF5) requires additional 21 | % code, located at: 22 | % https://github.com/JimHokanson/hdf5_matlab 23 | % conversion_options: {adi.h5_conversion_options OR adi.mat_conversion_options} 24 | % These classes provide access to the conversion options. The main 25 | % point of these options (at least initially) was to have control 26 | % over options that impact how fast the data is written (and then 27 | % subsequently written) 28 | % save_path : 29 | % Final file save path. This is not recommended for multiple files to 30 | % convert 31 | % save_root : 32 | % Folder to save converted files in. See also "root_path" 33 | % root_path : 34 | % If this is specified in addition to save_root then files are saved 35 | % in deeper directories after swapping this value for save_root. In 36 | % other words a file at data/type_1/ coulde be moved to 37 | % new_data/type_1 either via save_root = 'new_data/type_1' or more 38 | % generically for other files in 'data' via: 39 | % save_root = 'new_data' 40 | % root_path = 'data' 41 | % 42 | % Examples: 43 | % --------- 44 | % 45 | % 46 | % 47 | % See Also: 48 | % adi.h5_conversion_options 49 | % adi.h5_mat_conversion_options 50 | 51 | persistent base_path 52 | 53 | in.conversion_options = []; %{adi.mat_conversion_options, adi.h5_conversion_options} 54 | in.format = 'mat'; %or 'h5' 55 | in.save_path = ''; 56 | in.save_root = ''; 57 | in.root_path = ''; %To match for save root 58 | in.no_exist_only = false; 59 | in.verbose = false; 60 | in = adi.sl.in.processVarargin(in,varargin); 61 | 62 | if in.format(1) == '.' 63 | in.format(1) = []; 64 | end 65 | 66 | if nargin == 0 || isempty(file_path_or_paths) 67 | 68 | file_path_or_paths = adi.uiGetChartFile('prompt','Pick a file to convert','start_path',base_path,'multi_select',true); 69 | 70 | if isnumeric(file_path_or_paths) 71 | return 72 | end 73 | 74 | else 75 | if ischar(file_path_or_paths) && exist(file_path_or_paths,'dir') == 7 76 | %Get list of adicht files ... 77 | folder_path = file_path_or_paths; 78 | files = dir(fullfile(folder_path,'*.adicht')); 79 | file_path_or_paths = adi.sl.dir.fullfile(folder_path,{files.name}); 80 | end 81 | end 82 | 83 | if ischar(file_path_or_paths) 84 | file_path_or_paths = {file_path_or_paths}; 85 | end 86 | %This may need to change with files from different directories ... 87 | base_path = fileparts(file_path_or_paths{1}); 88 | 89 | for iFile = 1:length(file_path_or_paths) 90 | 91 | cur_file_path = file_path_or_paths{iFile}; 92 | 93 | %This may be empty, which yields control of the save path 94 | %to the convertor 95 | save_path = h__getSavePath(in,cur_file_path); 96 | 97 | if in.verbose 98 | %TODO: Print out what is being converted 99 | [~,file_name] = fileparts(cur_file_path); 100 | fprintf('Converting: %s\n',file_name); 101 | end 102 | 103 | file_obj = adi.readFile(cur_file_path); 104 | 105 | switch in.format 106 | case 'h5' 107 | %adi.file.exportToMatFile 108 | save_path = file_obj.exportToHDF5File(save_path,in.conversion_options); 109 | case 'mat' 110 | %adi.file.exportToMatFile 111 | if ~isempty(in.conversion_options) 112 | in.conversion_options.verify() 113 | end 114 | save_path = file_obj.exportToMatFile(save_path,in.conversion_options); 115 | otherwise 116 | error('Unrecognized format option: %s',in.format); 117 | end 118 | 119 | end 120 | end 121 | 122 | function save_path = h__getSavePath(in,cur_file_path) 123 | % 124 | %Save to the save_root. Preserve structure if root path is specified. 125 | 126 | [file_root_path,file_name] = fileparts(cur_file_path); 127 | 128 | if isempty(in.save_root) 129 | %If empty the converter will save it next to the original file 130 | save_path = in.save_path; 131 | else 132 | if isempty(in.root_path) 133 | save_path = fullfile(in.save_root,file_name); 134 | else 135 | if strncmp(in.root_path,file_root_path,length(in.root_path)) 136 | start_I = length(in.root_path)+1; 137 | extra_path = file_root_path(start_I:end); 138 | save_path = fullfile(in.save_root,extra_path,file_name); 139 | else 140 | error('root path needs to be in the file path being converted') 141 | end 142 | end 143 | end 144 | end -------------------------------------------------------------------------------- /+adi/createFile.m: -------------------------------------------------------------------------------- 1 | function file_writer = createFile(file_path,varargin) 2 | % 3 | % file_writer = adi.createFile(file_path) 4 | % 5 | % Inputs 6 | % ------ 7 | % file_path : string 8 | % Path to the file to create. Currently all folders in the path 9 | % must exist (these are not created for the user). The file extension 10 | % should be .adidat which is the same as .adicht but without any 11 | % settings in it. Note this is different than .adibin which is a 12 | % simpler file format and doesn't support comments or compression. 13 | % 14 | % 15 | % See Also 16 | % -------- 17 | % adi.file_writer 18 | % adi.binary.file_writer 19 | % 20 | % Improvements 21 | % ------------ 22 | % 1) Create method for opening file to append 23 | 24 | % in.copy_blank_when_new = false; 25 | % in = adi.sl.in.processVarargin(in,varargin); 26 | 27 | is_new = ~exist(file_path,'file'); 28 | 29 | %Possibilities 30 | %1) create new file de novo 31 | %2) create new file using a copy of a blank file as a starting point 32 | %3) edit a file that already exists 33 | 34 | file_h = adi.sdk.createFile(file_path); 35 | 36 | % % % if is_new 37 | % % % if in.copy_blank_when_new 38 | % % % adi.createBlankFileAtPath(file_path) 39 | % % % file_h = adi.sdk.openFile(file_path,'read_and_write',true); 40 | % % % else 41 | % % % file_h = adi.sdk.createFile(file_path); 42 | % % % end 43 | % % % else 44 | % % % file_h = adi.sdk.openFile(file_path,'read_and_write',true); 45 | % % % end 46 | % % % %file_h : adi.file_handle 47 | 48 | data_writer_h = adi.sdk.createDataWriter(file_h); 49 | %data_writer_h : adi.data_writer_handle 50 | 51 | file_writer = adi.file_writer(file_path, file_h, data_writer_h, is_new); 52 | 53 | end -------------------------------------------------------------------------------- /+adi/data_writer_handle.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) data_writer_handle < handle 2 | % 3 | % Class: 4 | % adi.data_writer_handle 5 | % 6 | % See Also: 7 | % --------- 8 | % adi.createFile 9 | % adi.file_handle 10 | 11 | properties 12 | pointer_value 13 | closed = false 14 | end 15 | 16 | methods 17 | function obj = data_writer_handle(pointer_value) 18 | obj.pointer_value = pointer_value; 19 | end 20 | function close(obj) 21 | if ~obj.closed 22 | adi.sdk.closeWriter(obj.pointer_value) 23 | obj.closed = true; 24 | end 25 | end 26 | function delete(obj) 27 | obj.close(); 28 | end 29 | end 30 | 31 | end 32 | 33 | -------------------------------------------------------------------------------- /+adi/documentation/FunctionMapping.csv: -------------------------------------------------------------------------------- 1 | Header_Order,DLL Function Name,mex option,Matlab Name,Notes 2 | 1,ADI_OpenFile,0,openFile, 3 | 2,ADI_CreateFile,NYI,, 4 | 3,ADI_GetErrorMessage,14,, 5 | 4,ADI_TickToSamplePos,NYI,, 6 | 5,ADI_SamplePosToTick,NYI,, 7 | 6,ADI_GetNumberOfRecords,1,, 8 | 7,ADI_GetNumberOfChannels,2,, 9 | 8,ADI_GetNumTicksInRecord,3,, 10 | 9,ADI_GetRecordTickPeriod,4,, 11 | 10,ADI_GetNumSamplesInRecord,5,, 12 | 11,ADI_GetRecordSamplePeriod,15,getSamplePeriod, 13 | 12,ADI_GetRecordTime,16,, 14 | 13,ADI_CreateCommentsAccessor,6,, 15 | 14,ADI_CloseCommentsAccessor,7,, 16 | 15,ADI_GetCommentInfo,8,, 17 | 16,ADI_NextComment,9,, 18 | 17,ADI_GetSamples,10,, 19 | 18,ADI_GetUnitsName,11,, 20 | 19,ADI_GetChannelName,12,, 21 | 20,ADI_SetChannelName,NYI,, 22 | 21,ADI_CreateWriter,NYI,, 23 | 22,ADI_SetChannelInfo,NYI,, 24 | 23,ADI_StartRecord,NYI,, 25 | 24,ADI_AddChannelSamples,NYI,, 26 | 25,ADI_FinishRecord,NYI,, 27 | 26,ADI_CommitFile,NYI,, 28 | 27,ADI_CloseWriter,NYI,, 29 | 28,ADI_AddComment,NYI,, 30 | 29,ADI_DeleteComment,NYI,, 31 | 30,ADI_CloseFile,13,closeFile, 32 | -------------------------------------------------------------------------------- /+adi/documentation/com_interface_notes.m: -------------------------------------------------------------------------------- 1 | %This file was me messing around with the COM interface via .NET 2 | % 3 | %Due to the interface code I would need to write most of what I need in C# 4 | %and then call my code. 5 | % 6 | %The COM interface exposes the raw data where as the C SDK only exposes 7 | %data as floating point data (single). 8 | % 9 | %For now I'm going to hold off on doing anything with this approach. 10 | 11 | 12 | %wtf = NET.addAssembly('C:\Users\Jim\Documents\ADInstruments\SimpleDataFileSDKCOM\DotNETInterop\bin\Debug\ADIDatIOWInLib.dll'); 13 | 14 | 15 | base_path = 'C:\repos\matlab_git\ad_sdk\+adinstruments\private\interfaceConverterCode'; 16 | adi_asm = NET.addAssembly(fullfile(base_path,'ADIDatIOWinLib.dll')); 17 | 18 | obj = ADIDatIOWinLib.ADIDataObject(); 19 | t = adi_asm.AssemblyHandle.GetType('ADIDatIOWinLib.IADIDataReader'); 20 | 21 | adi_data = t.InvokeMember('GetADIData',System.Reflection.BindingFlags.InvokeMethod,[],obj,[]); 22 | 23 | %Inputs: 24 | %1) function name 25 | %2) System.Reflection.BindingFlags invokeAttr 26 | %3) System.Reflection.Binder binder 27 | %4) System.Object target 28 | % - the created class 29 | %5) Args 30 | % - 31 | 32 | conv_asm = NET.addAssembly(fullfile(base_path,'interfaceConv.dll')); 33 | %clark = ADIDatIOWinLib.ADIDataObject(); 34 | 35 | %wtf.OpenFileForRead('C:\Data\GSK\ChrisRaw\140113 pelvic and hypogastric nerve recordings.adicht') 36 | wtf = interfaceConv.converter.getReader(obj,'C:\Data\GSK\ChrisRaw\140113 pelvic and hypogastric nerve recordings.adicht'); 37 | 38 | file_path = 'C:\Data\GSK\ChrisRaw\140113 pelvic and hypogastric nerve recordings.mat'; 39 | tic; 40 | huh = load(file_path,'data__chan_4_rec_4'); 41 | toc; 42 | 43 | tic; 44 | fid = fopen(file_path,'r'); 45 | arg = fread(fid,[1 Inf],'*uint8'); 46 | fclose(fid); 47 | toc; 48 | 49 | %wtf = c.getReader(clark); 50 | 51 | 52 | 53 | 54 | %% Load Assembly 55 | A = NET.addAssembly([pwd '\ClassLibrary1.dll']); 56 | % Get Type information for the IClass interface 57 | t = A.AssemblyHandle.GetType('ClassLibrary1.IClass'); 58 | %% Create an object instance 59 | obj = ClassLibrary1.Class1; 60 | %% Call the method (through reflection) 61 | % First define an Object[] for the inputs 62 | inputs = NET.createArray('System.Object',1); 63 | % Define the actual inputs 64 | inputs(1) = System.String('World'); 65 | % Call the method 66 | t.InvokeMember('myFunction',System.Reflection.BindingFlags.InvokeMethod,[],obj,inputs) 67 | 68 | 69 | %{ 70 | Structures: 71 | 'ADIDatIOWinLib.ADIChannelId' 72 | 'ADIDatIOWinLib.ADIPosition' 73 | 'ADIDatIOWinLib.ADIRational64' 74 | 'ADIDatIOWinLib.ADIScaling' 75 | 'ADIDatIOWinLib.ADITimeDate' 76 | 'ADIDatIOWinLib.BaseUnitsInfo' 77 | 'ADIDatIOWinLib.ChannelYDataRange' 78 | 'ADIDatIOWinLib.ChartCommentPos' 79 | 'ADIDatIOWinLib.RecordTimeInfo' 80 | 'ADIDatIOWinLib.TTickToSample' 81 | 'ADIDatIOWinLib.UserUnitsInfo' 82 | Enums: 83 | 'ADIDatIOWinLib.ADICommentFlags' 84 | 'ADIDatIOWinLib.ADIDataFlags' 85 | 'ADIDatIOWinLib.ADIDataType' 86 | 'ADIDatIOWinLib.ADIDataValueId' 87 | 'ADIDatIOWinLib.ADIEnumDataType' 88 | 'ADIDatIOWinLib.ADIFileOpenFlags' 89 | 'ADIDatIOWinLib.ADIRecordFlags' 90 | 'ADIDatIOWinLib.ADIReservedFlags' 91 | 'ADIDatIOWinLib.EnumCommentFlags' 92 | 'ADIDatIOWinLib.EventFindTypes' 93 | 'ADIDatIOWinLib.TimeDisplayMode' 94 | 'ADIDatIOWinLib.UnitPrefix' 95 | Interfaces: 96 | 'ADIDatIOWinLib.IADIComment' 97 | 'ADIDatIOWinLib.IADIData' 98 | 'ADIDatIOWinLib.IADIDataReader' 99 | 'ADIDatIOWinLib.IADIDataSink' 100 | 'ADIDatIOWinLib.IADIDataWriter' 101 | 'ADIDatIOWinLib.IAutoADIString' 102 | 'ADIDatIOWinLib.IEnumADIComment' 103 | 'ADIDatIOWinLib.IEnumExBase' 104 | 'ADIDatIOWinLib.IEnumFloatEx' 105 | 'ADIDatIOWinLib.IEnumShortEx' 106 | 107 | %} 108 | 109 | reader = cast(clark,'ADIDatIOWinLib.IADIDataReader') 110 | 111 | 112 | wtf = ADIDatIOWinLib.IADIDataReader(clark) 113 | 114 | %'System.Type requestedType') 115 | %{ 116 | 117 | ADIDatIOWinLib.ADIDataObject dataObject = new ADIDatIOWinLib.ADIDataObject(); 118 | ADIDatIOWinLib.IADIDataReader reader = (ADIDatIOWinLib.IADIDataReader)dataObject; 119 | reader.OpenFileForRead(filePath); 120 | mADIData = reader.GetADIData(); 121 | 122 | %} -------------------------------------------------------------------------------- /+adi/editFile.m: -------------------------------------------------------------------------------- 1 | function file_writer = editFile(file_path,varargin) 2 | %x Open a file for editing 3 | % 4 | % file_writer = adi.editFile(file_path) 5 | % 6 | % 7 | 8 | if ~exist(file_path,'file') 9 | error('File to edit doesn''t exist:\n%s',file_path) 10 | end 11 | 12 | file_h = adi.sdk.openFile(file_path,'read_and_write',true); 13 | 14 | data_writer_h = adi.sdk.createDataWriter(file_h); 15 | 16 | file_writer = adi.file_writer(file_path, file_h, data_writer_h, false); 17 | 18 | end -------------------------------------------------------------------------------- /+adi/extractRecordToNewFile.m: -------------------------------------------------------------------------------- 1 | function extractRecordToNewFile(source_file_path,record_id,varargin) 2 | % 3 | % adi.extractRecordToNewFile(source_file_path,record_id,varargin) 4 | % 5 | % 6 | % This is a bit buggy but I think it works ... 7 | % 8 | % Optional Inputs: 9 | % ---------------- 10 | % new_file_path: 11 | % channels : cell array 12 | % Names of channels whose data you would like to replace 13 | % data : cell array 14 | % Each element holds the new data to write to that channel 15 | % 16 | 17 | %{ 18 | %Example code: 19 | %-------------------------------------------------------------------------- 20 | source_file_path = 'F:\GSK\Rat_Expts\2014\edited\140919_J_01_Wistar_PudStim.adicht' 21 | record_id = 7; 22 | 23 | file_h = adi.readFile(source_file_path); 24 | 25 | eus_chan = file_h.getChannelByName('eus'); 26 | 27 | raw_eus_data = eus_chan.getData(record_id,'return_object',false); 28 | 29 | raw_eus_data = 5*raw_eus_data; 30 | 31 | adi.extractRecordToNewFile(file_h,record_id,'channels',{eus_chan.name},'data',{raw_eus_data}) 32 | %-------------------------------------------------------------------------- 33 | %} 34 | 35 | %TODO: Add on ability to not include channels 36 | %TODO: Allow passing in a file reference for the source file path 37 | %TODO: Allow passing in a time limit 38 | %TODO: Allow channel specs objects for channels 39 | %TODO: Allow non-cell array inputs for channels and data 40 | 41 | in.new_file_path = ''; 42 | in.channels = {}; 43 | in.data = {}; 44 | in = adi.sl.in.processVarargin(in,varargin); 45 | 46 | if isobject(source_file_path) 47 | file_h = source_file_path; 48 | source_file_path = file_h.file_path; 49 | else 50 | file_h = adi.readFile(source_file_path); 51 | end 52 | 53 | if isempty(in.new_file_path) 54 | [root_path,file_name] = fileparts(source_file_path); 55 | new_file_name = sprintf('%s_record_%d.adicht',file_name,record_id); 56 | in.new_file_path = fullfile(root_path,new_file_name); 57 | end 58 | 59 | fw = adi.createFile(in.new_file_path,false); 60 | 61 | 62 | chan_writer_handles = cell(1,file_h.n_channels); 63 | 64 | for iChan = 1:file_h.n_channels 65 | cur_chan_info = file_h.channel_specs(iChan); 66 | temp = fw.addChannel(iChan,cur_chan_info.name,... 67 | cur_chan_info.fs(record_id),cur_chan_info.units{record_id}); 68 | chan_writer_handles{iChan} = temp; 69 | end 70 | 71 | fw.startRecord(); 72 | 73 | for iChan = 1:file_h.n_channels 74 | cur_chan_writer = chan_writer_handles{iChan}; 75 | cur_chan_reader = file_h.channel_specs(iChan); 76 | I = find(strcmp(in.channels,cur_chan_reader.name)); 77 | if isempty(I) 78 | temp_data = cur_chan_reader.getData(record_id,'return_object',false,'leave_raw',true); 79 | else 80 | temp_data = single(in.data{I}); 81 | end 82 | cur_chan_writer.addSamples(temp_data); 83 | end 84 | 85 | fw.stopRecord(); 86 | 87 | fw.save(); 88 | fw.close(); 89 | 90 | end -------------------------------------------------------------------------------- /+adi/file.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) file < handle 2 | % 3 | % Class: 4 | % adi.file 5 | % 6 | % ***** This object should be instantiated via adi.readFile ***** 7 | % 8 | % This is the root class for a Labchart file. It holds meta 9 | % data and classes with more meta data. The channel classes can 10 | % be used to actually get data. 11 | % 12 | % See Also: 13 | % adi.readFile 14 | 15 | properties 16 | file_path %Full path to the file from which this class was populated. 17 | end 18 | 19 | properties (Hidden) 20 | file_h 21 | end 22 | 23 | properties 24 | n_records 25 | n_channels %Number of channels in the file (across all records). 26 | %This may be reduced if some channels have no data and the 27 | %input options to the constructor specify to remove empty channels. 28 | records %adi.record 29 | channel_specs %adi.channel These classes hold information 30 | %about each of the channels used. 31 | end 32 | 33 | properties (Dependent) 34 | channel_names 35 | end 36 | 37 | methods 38 | function value = get.channel_names(obj) 39 | temp = obj.channel_specs; 40 | if isempty(temp) 41 | value = {}; 42 | else 43 | value = {temp.name}; 44 | end 45 | end 46 | end 47 | 48 | %Constructor 49 | %-------------------------------- 50 | methods 51 | function obj = file(file_path,file_h,sdk,in) 52 | % 53 | % This should be created by adi.readFile 54 | % 55 | % Inputs: 56 | % ------- 57 | % file_path : str 58 | % The path of the file (for reference). 59 | % file_h : adi.file_handle 60 | % A reference to the actual file. 61 | % sdk: {adi.sdk, adi.h5_file_sdk, mat_file_sdk} 62 | % Calls are made to the SDK to interact with the file. 63 | % in : adi.file_read_options 64 | % Options for reading the file. 65 | % 66 | % See Also: 67 | % adi.readFile 68 | 69 | obj.file_path = file_path; 70 | obj.file_h = file_h; 71 | 72 | %Could try switching these to determine what is causing 73 | %the crash. Is it records or the first call to the sdk after 74 | %opening? Currently those 2 are the same. Flipping these 2 75 | %lines could disambiguate the situation. 76 | obj.n_records = sdk.getNumberOfRecords(file_h); 77 | temp_n_channels = sdk.getNumberOfChannels(file_h); 78 | 79 | 80 | 81 | %Get record objects 82 | %------------------------------------------- 83 | temp = cell(1,obj.n_records); 84 | 85 | for iRec = 1:obj.n_records 86 | temp{iRec} = adi.record(file_h,sdk,iRec); 87 | end 88 | 89 | obj.records = [temp{:}]; 90 | 91 | %Get channel objects 92 | %------------------------------------------- 93 | temp = cell(1,temp_n_channels); 94 | 95 | for iChan = 1:temp_n_channels 96 | temp{iChan} = adi.channel(file_h,sdk,iChan,obj.records,file_path); 97 | end 98 | 99 | obj.channel_specs = [temp{:}]; 100 | 101 | if in.remove_empty_channels 102 | obj.channel_specs = obj.channel_specs.removeEmptyObjects(); 103 | end 104 | 105 | if ~isempty(in.channels_remove) 106 | mask = ismember(in.channels_remove,obj.channel_names); 107 | if ~all(mask) 108 | warning('JAH:adi:file:missing_remove_channel',... 109 | 'At least one of the channels specified to be removed were not in the file') 110 | %Some of the channels that were requested to be removed 111 | %are not in the file 112 | %TODO: Print warning message 113 | end 114 | 115 | mask = ismember(obj.channel_names,in.channels_remove); 116 | obj.channel_specs(mask) = []; 117 | end 118 | 119 | if ~isempty(in.channel_name_mapping) 120 | old_names = in.channel_name_mapping(1:2:end); 121 | new_names = in.channel_name_mapping(2:2:end); 122 | [mask,loc] = ismember(old_names,obj.channel_names); 123 | %TODO: We can have an option to hide this 124 | if ~all(mask) 125 | warning('JAH:adi:file:missing_remove_channel',... 126 | 'At least one of the channels specified to be renamed were not in the file') 127 | %Some of the channels that were requested to be removed 128 | %are not in the file 129 | %TODO: Print warning message 130 | end 131 | for i = 1:length(mask) 132 | if mask(i) 133 | chan_obj = obj.channel_specs(loc(i)); 134 | chan_obj.name = new_names{i}; 135 | end 136 | end 137 | 138 | end 139 | 140 | obj.n_channels = length(obj.channel_specs); 141 | end 142 | end 143 | methods 144 | function output = isChannelInRecord(obj, channel_num, record) 145 | % 146 | % output = isChannelInRecord(obj, channel_num, record) 147 | % 148 | % Inputs 149 | % ------ 150 | % 1 based 151 | 152 | channel = obj.channel_specs(channel_num); 153 | output = channel.isValidForRecord(record); 154 | end 155 | function output = channelsInRecord(obj, record_number) 156 | % get a list of the channels in the given record_number with 157 | % samples recorded/valid 158 | 159 | mask = false(1,obj.n_channels); 160 | for i = 1:obj.n_channels 161 | mask(i) = obj.isChannelInRecord(i,record_number); 162 | end 163 | 164 | output = obj.channel_names(mask); 165 | end 166 | function all_comments = getAllComments(obj,varargin) 167 | % 168 | % record : adi.record 169 | % 170 | % Optional Inputs 171 | % --------------- 172 | % output : 173 | % - 'obj' default 174 | % Returns array of comment objects 175 | % - 'table' 176 | % Returns information as a table 177 | 178 | in.output = 'obj'; 179 | in.use_datetime = false; 180 | in = adi.sl.in.processVarargin(in,varargin); 181 | 182 | all_records = obj.records; 183 | all_comments = [all_records.comments]; 184 | switch in.output 185 | case 'obj' 186 | %do nothing 187 | % 188 | %TODO: respect in.use_datetime 189 | if in.use_datetime 190 | error('Unsupported task') 191 | end 192 | case 'table' 193 | c = all_comments; 194 | t = table; 195 | t.text = string({c.str})'; 196 | t.id = [c.id]'; 197 | t.channel = [c.channel]'; 198 | t.record = [c.record]'; 199 | t.time = [c.time]'; 200 | t.absolute_time = [c.absolute_time]'; 201 | if in.use_datetime 202 | t.absolute_time = datetime(t.absolute_time,'ConvertFrom','datenum'); 203 | end 204 | all_comments = t; 205 | end 206 | end 207 | function summarizeRecords(obj) 208 | %x Not Yet Implemented 209 | % For each record: 210 | % # of comments 211 | % which channels contain data 212 | % duration of the record 213 | error('Not yet implemented') 214 | keyboard 215 | end 216 | function chan = getChannelByName(obj,channel_name,varargin) 217 | %x Returns channel object for a given channel name 218 | % 219 | % chan = ad_sdk.adi.getChannelByName(obj,channel_name,varargin) 220 | % 221 | % See Also: 222 | % adi.channel.getChannelByName() 223 | 224 | in.case_sensitive = false; 225 | in.partial_match = true; 226 | in.multiple_channel_rule = 'error'; 227 | in = adi.sl.in.processVarargin(in,varargin); 228 | 229 | temp = obj.channel_specs; 230 | if isempty(temp) 231 | error('Requested channel: %s, not found',channel_name) 232 | end 233 | 234 | chan = temp.getChannelByName(channel_name,in); 235 | end 236 | function varargout = getChannelData(obj,... 237 | channel_number_1b_or_name, ... 238 | block_number_1b,varargin) 239 | % 240 | % 241 | % This is really a shortcut call for the following: 242 | % chan = obj.getChannelByName(name,varargin) 243 | % data = chan.getData(block_number,varargin) 244 | % 245 | % Optional Inputs 246 | % --------------- 247 | % See adi.channel.getData 248 | % 249 | % See Also 250 | % -------- 251 | % adi.channel.getData 252 | 253 | if isnumeric(channel_number_1b_or_name) 254 | spec = obj.channel_specs(channel_number_1b_or_name); 255 | else 256 | spec = obj.getChannelByName(channel_number_1b_or_name); 257 | end 258 | 259 | if nargout == 2 260 | [data,time] = spec.getData(block_number_1b,varargin{:}); 261 | varargout{1} = data; 262 | varargout{2} = time; 263 | else 264 | data = spec.getData(block_number_1b,varargin{:}); 265 | varargout{1} = data; 266 | end 267 | end 268 | end 269 | 270 | %TODO: These should be in their own class 271 | %adi.io.mat.file & 272 | %adi.io.h5.file 273 | % 274 | %Currently the SDK takes care of the loading, which bridges the gap 275 | %of knowledge of the file contents ... i.e. this file knows write 276 | %contents but the SDK needs to know how to read 277 | %File Methods 278 | methods 279 | function save_path = exportToHDF5File(obj,save_path,conversion_options) 280 | %x Exports contents to a HDF5 file. 281 | % 282 | % This is similiar to the v7.3 mat files but by calling the 283 | % HDF5 library functions directly we can control how the data 284 | % are saved. 285 | % 286 | % See Also: 287 | % adi.record.exportToHDF5File 288 | % adi.channel.exportToHDF5File 289 | 290 | if nargin < 3 || isempty(conversion_options) 291 | conversion_options = adi.h5_conversion_options; 292 | end 293 | 294 | if ~exist('save_path','var') || isempty(save_path) 295 | save_path = adi.sl.dir.changeFileExtension(obj.file_path,'h5'); 296 | else 297 | save_path = adi.sl.dir.changeFileExtension(save_path,'h5'); 298 | end 299 | 300 | if strcmp(save_path,obj.file_path) 301 | error('Conversion path and file path are the same') 302 | end 303 | 304 | if exist(save_path,'file') 305 | delete(save_path); 306 | end 307 | 308 | adi.sl.dir.createFolderIfNoExist(fileparts(save_path)); 309 | 310 | %TODO: I'd eventually like to use the h5m library I'm writing. 311 | %This would change the calls to h5writteatt 312 | 313 | fobj = h5m.file.create(save_path); 314 | h5m.group.create(fobj,'file'); 315 | 316 | %TODO: Replace with h5m library when ready 317 | h5writeatt(save_path,'/','version',1); 318 | h5writeatt(save_path,'/file','n_records',obj.n_records) 319 | h5writeatt(save_path,'/file','n_channels',obj.n_channels) 320 | 321 | obj.records.exportToHDF5File(fobj,save_path,conversion_options); 322 | obj.channel_specs.exportToHDF5File(fobj,save_path,conversion_options); 323 | 324 | end 325 | function save_path = exportToMatFile(obj,save_path,conversion_options) 326 | % 327 | % Converts the file to a mat file. 328 | % 329 | % See Also 330 | % -------- 331 | % adi.convert 332 | % adi.mat_conversion_options 333 | 334 | if nargin < 3 || isempty(conversion_options) 335 | conversion_options = adi.mat_conversion_options; 336 | end 337 | 338 | if ~exist('save_path','var') || isempty(save_path) 339 | save_path = adi.sl.dir.changeFileExtension(obj.file_path,'mat'); 340 | else 341 | save_path = adi.sl.dir.changeFileExtension(save_path,'mat'); 342 | end 343 | 344 | if strcmp(save_path,obj.file_path) 345 | error('Conversion path and file path are the same') 346 | end 347 | 348 | adi.sl.dir.createFolderIfNoExist(fileparts(save_path)); 349 | 350 | if exist(save_path,'file') 351 | delete(save_path); 352 | end 353 | 354 | %http://www.mathworks.com/help/matlab/ref/matfile.html 355 | %m = matfile(save_path); 356 | 357 | m = struct; 358 | m.file_version = 1; 359 | m.file_meta = struct('n_records',obj.n_records,'n_channels',obj.n_channels); 360 | 361 | m = obj.records.exportToMatFile(m,conversion_options); 362 | m = obj.channel_specs.exportToMatFile(m,conversion_options); 363 | 364 | save(save_path,'-struct','m',conversion_options.version); 365 | end 366 | end 367 | 368 | end 369 | 370 | -------------------------------------------------------------------------------- /+adi/file_comparison.m: -------------------------------------------------------------------------------- 1 | classdef file_comparison 2 | % 3 | % Class: 4 | % adi.file_comparison 5 | 6 | 7 | %{ 8 | 9 | fc = adi.file_comparison(p1,p2); 10 | 11 | %} 12 | 13 | properties 14 | file_path1 15 | file_path2 16 | f1 17 | f2 18 | n_records1 19 | n_records2 20 | end 21 | 22 | methods 23 | function obj = file_comparison(path1,path2) 24 | % 25 | % obj = adi.file_comparison(path1,path2) 26 | 27 | obj.file_path1 = path1; 28 | obj.file_path2 = path2; 29 | 30 | f1 = adi.readFile(path1); 31 | f2 = adi.readFile(path2); 32 | 33 | obj.n_records1 = f1.n_records; 34 | obj.n_records2 = f2.n_records; 35 | 36 | c1 = f1.getAllComments(); 37 | c2 = f2.getAllComments(); 38 | adi.file_comparison.comments_comparison(c1,c2); 39 | 40 | keyboard 41 | 42 | end 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /+adi/file_handle.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) file_handle < handle 2 | % 3 | % Class: 4 | % adi.file_handle 5 | % 6 | % This class is simply meant to hold the reference to an open file. 7 | % When no one holds it the file will be closed. 8 | 9 | properties 10 | pointer_value %Pointer to the file object in the mex code. This gets 11 | %cast to ADI_FileHandle in the mex code. 12 | file_path 13 | end 14 | 15 | methods 16 | function obj = file_handle(pointer_value,file_path) 17 | % 18 | % Inputs: 19 | % ------- 20 | % pointer_value: 21 | % file_path: 22 | 23 | obj.pointer_value = pointer_value; 24 | obj.file_path = file_path; 25 | end 26 | function delete(obj) 27 | % 28 | % 29 | % fprintf(2,'ADI SDK - Deleting file ref: %s\n',obj.file_name); 30 | 31 | adi.handle_logger.logOperation(obj.file_path,'closeFile',obj.pointer_value) 32 | adi.sdk.closeFile(obj.pointer_value); 33 | end 34 | end 35 | 36 | end 37 | 38 | -------------------------------------------------------------------------------- /+adi/file_read_options.m: -------------------------------------------------------------------------------- 1 | classdef file_read_options < handle 2 | % 3 | % Class: 4 | % adi.file_read_options 5 | % 6 | % Contains reading options. Currently this is for all file formats. 7 | 8 | properties %All file formats: 9 | %adi.file 10 | 11 | %If true, channels without data for all records are removed. 12 | remove_empty_channels = true; 13 | 14 | % This should be a cell array of channels which you wish to not 15 | % include when reading the file. 16 | channels_remove = {} %TODO: On setting ensure it is a cell array 17 | 18 | % This should be pairs of , 19 | channel_name_mapping = {}; 20 | 21 | %conversion_max_ 22 | end 23 | 24 | properties %Mat file format only 25 | load_all_mat_on_start = true; %Not yet linked to the method 26 | %See: adi.mat_file_h 27 | end 28 | 29 | methods 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /+adi/file_writer.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) file_writer < handle 2 | % 3 | % Class: 4 | % adi.file_writer 5 | % 6 | % 7 | % 8 | % See Also 9 | % -------- 10 | % adi.createFile 11 | % adi.channel_writer 12 | % 13 | % 14 | % SDK Calls: 15 | % ---------- 16 | % adi.sdk.commitFile 17 | % adi.sdk.closeWriter 18 | % adi.sdk.addComment 19 | % adi.sdk.deleteComment 20 | % adi.createFile 21 | % 22 | % ADI write operations 23 | % -------------------- 24 | % ADI_SetChannelName 25 | % ADI_CreateWriter 26 | % ADI_SetChannelInfo 27 | % ADI_StartRecord - Starts the writer recording a new record, setting the trigger time. 28 | % ADI_AddChannelSamples - Writes new data samples into the specified channel record. 29 | % ADI_FinishRecord - Ends the current record being written by the writer. 30 | % ADI_CommitFile - Ensures all changes to the file made via the writer session are written to the file. 31 | % ADI_CloseWriter - Terminates the writer session and releases resources used by the session. 32 | % ADI_AddComment - 33 | % 34 | % 35 | % Example 36 | % ----------------------------- 37 | % %***** EDIT, PATH MUST EXIST ******* 38 | % file_path = 'D:\repos\test.adidat'; 39 | % fw = adi.createFile(file_path); %fw : file_writer 40 | % 41 | % fs1 = 100; 42 | % chan = 1; 43 | % pres_chan = fw.addChannel(chan,'pressure',fs1,'cmH20'); 44 | % 45 | % fs2 = 1000; 46 | % chan = 2; 47 | % emg_chan = fw.addChannel(chan,'emg',fs2,'mV'); 48 | % 49 | % start_date_time = datenum(2023,7,19,18,0,0); 50 | % fw.startRecord('trigger_time',start_date_time); 51 | % 52 | % y1 = [1:1/fs1:10 10:-1/fs1:1 1:1/fs1:10 10:-1/fs1:1]; 53 | % pres_chan.addSamples(y1); 54 | % 55 | % %record gets truncated to shortest channel 56 | % t = (1:length(y1)*(fs2/fs1)).*1/fs2; 57 | % y2 = sin(2*pi*1/10*t); 58 | % emg_chan.addSamples(y2); 59 | % 60 | % fw.stopRecord(); 61 | % 62 | % comment_time = 2; 63 | % comment_string = 'Best EMG signal ever!'; 64 | % record = -1; %-1 is current record (or last record if stopped) 65 | % comment_channel = 2; 66 | % fw.addComment(record,comment_time,comment_string,'channel',comment_channel); 67 | % 68 | % comment_time = 9; 69 | % comment_string = 'Best time ever'; 70 | % record = -1; %-1 is current record (or last record if stopped) 71 | % comment_channel = -1; 72 | % fw.addComment(record,comment_time,comment_string,'channel',comment_channel); 73 | % fw.save(); 74 | % fw.close(); 75 | % 76 | % clear fw emg_chan pres_chan %if you want to open it in ADInstruments 77 | % %or this 78 | % %clearvars 79 | % 80 | % %Or this should work 81 | % %fw.saveAndClose(); 82 | 83 | 84 | 85 | 86 | %{ 87 | Comment editing setup 88 | --------------------- 89 | We'll have an array of comments but we'll need to access the delete and 90 | add comment methods 91 | 92 | %} 93 | 94 | %{ 95 | TODO: I need to determine how this interface should be organized. 96 | I'd like to generate a set of commands that the user would execute then 97 | implement those commands. 98 | 99 | 1) create file 100 | 2) initialize channels 101 | 3) start record 102 | 4) add comments 103 | 5) stop record 104 | 6) repeat steps 2 - 5 105 | 7) commit and close file - ??? does it help to commit more frequently? 106 | 107 | %} 108 | properties 109 | file_path %Where we are writing to 110 | current_record = NaN %NaN when not recording 111 | last_record = 0 %will always point to last record completed 112 | %0 indicates that no records have been recorded 113 | record_durations %???? We can get this for 114 | record_dts %We'll log this for every record 115 | record_trigger_times %Unix time values (Units: s) 116 | channels = {} %{adi.channel_writer} 117 | 118 | comments 119 | end 120 | 121 | properties (Dependent) 122 | current_channel_names 123 | in_record_mode 124 | end 125 | 126 | methods 127 | function value = get.current_channel_names(obj) 128 | temp = obj.channels; 129 | n_chans = length(temp); 130 | value = cell(1,n_chans); 131 | for iChan = 1:n_chans 132 | cur_chan = temp{iChan}; 133 | if ~isempty(cur_chan) 134 | value{iChan} = cur_chan.name; 135 | end 136 | end 137 | end 138 | function value = get.in_record_mode(obj) 139 | value = ~isnan(obj.current_record); 140 | end 141 | end 142 | 143 | properties (Hidden) 144 | is_new %Whether or not the file existed when created 145 | file_h %adi.file_handle 146 | data_writer_h %adi.data_writer_handle 147 | end 148 | 149 | methods 150 | function obj = file_writer(file_path,file_h,data_writer_h,is_new) 151 | % 152 | % Call these to initialize this object: 153 | % - adi.createFile() OR 154 | % - adi.editFile 155 | % 156 | % **** Don't call this directly *** 157 | % 158 | % obj = adi.file_writer(file_path,file_h) 159 | % 160 | % Inputs: 161 | % ------- 162 | % file_path : 163 | % file_h : adi.file_handle 164 | % data_writer_h : adi.data_writer_handle 165 | % is_new : logical 166 | % 167 | % 168 | % See Also: 169 | % --------- 170 | % adi.createFile 171 | % adi.editFile 172 | 173 | obj.file_path = file_path; 174 | obj.file_h = file_h; 175 | obj.data_writer_h = data_writer_h; 176 | 177 | %TODO: If the file is not new, read in the channel and record 178 | %specs from the file ... 179 | obj.is_new = is_new; 180 | if ~is_new 181 | temp_file = adi.file(file_path,file_h,adi.sdk,adi.file_read_options); 182 | obj.last_record = temp_file.n_records; 183 | %TODO: Do we want to instantiate writers for all the 184 | %channels ???? - Yes 185 | % 186 | 187 | recorded_chan_specs = temp_file.channel_specs; 188 | if ~isempty(recorded_chan_specs) 189 | max_id = max([recorded_chan_specs.id]); 190 | 191 | chan_ca = cell(1,max_id); 192 | for iChan = 1:length(recorded_chan_specs) 193 | 194 | cur_obj = recorded_chan_specs(iChan); 195 | cur_id = cur_obj.id; 196 | %* fs and units are on a record by record basis so we 197 | %use the last ones 198 | % 199 | %TODO: If a channel was not used, we shouldn't add it 200 | chan_ca{cur_id} = obj.addChannel(cur_id,cur_obj.name,cur_obj.fs(end),cur_obj.units{end}); 201 | end 202 | obj.channels = chan_ca; 203 | end 204 | 205 | records = temp_file.records; 206 | if ~isempty(records) 207 | 208 | obj.comments = [records.comments]; obj.record_durations = [records.duration]; 209 | obj.record_dts = [records.tick_dt]; 210 | % 211 | %Need to add the record times 212 | error('Not yet implemented') 213 | end 214 | end 215 | end 216 | function comment_number = addComment(obj,record,comment_time,comment_string,varargin) 217 | %x Adds a comment to the specified record 218 | % 219 | % comment_number = addComment(obj,record,comment_time,comment_string,varargin) 220 | % 221 | % Inputs 222 | % ------ 223 | % record : 224 | % -1 means current record 225 | % comment_time : 226 | % Time is in seconds ... 227 | % TODO: Clarify how this relates to the trigger 228 | % and data start 229 | % comment_string : string 230 | % The actual text of the comment to add 231 | % 232 | % Optional Inputs 233 | % --------------- 234 | % channel : 235 | % -1 (or 0) indicates to add to all channels 236 | % 1 - add to channel 1 237 | % 2 - add to channel 2 238 | % 239 | % Outputs 240 | % ------- 241 | % comment_number : numeric 242 | % The resulting id of the comment. 243 | % 244 | % Examples 245 | % -------- 246 | % comment_number = fw.addComment(-1,comment_time,comment_string,'channel',2) 247 | % 248 | % Improvements 249 | % ------------ 250 | % TODO: We could build in support to adding to channels 251 | % by name : 'channel','emg' 252 | % TODO: Build check on the channel if it is out of range 253 | % 254 | 255 | h__errorIfClosed(obj) 256 | 257 | in.channel = -1; 258 | in = adi.sl.in.processVarargin(in,varargin); 259 | 260 | if obj.last_record == 0 && isnan(obj.current_record) 261 | error('record must exist before adding comments') 262 | end 263 | 264 | %TODO: Check that record makes sense 265 | %If -1, check that we are in a record 266 | %otherwise, make sure record is between 1 and n records 267 | if record == -1 268 | record = obj.current_record; 269 | if isnan(record) 270 | record = obj.last_record; 271 | end 272 | end 273 | tick_position = round(comment_time/obj.record_dts(record)); 274 | 275 | comment_number = adi.sdk.addComment(obj.file_h,in.channel,record,tick_position,comment_string); 276 | end 277 | function deleteComment(obj,comment_number) 278 | h__errorIfClosed(obj) 279 | adi.sdk.deleteComment(obj.file_h,comment_number) 280 | end 281 | function new_chan = addChannel(obj,id,name,fs,units) 282 | %x Add a channel that can be written to 283 | % 284 | % Inputs: 285 | % ------- 286 | % id : 287 | % Channel number, starting at 1. These do not need to be 288 | % continuous. 289 | % name : string 290 | % Name of the channel 291 | % fs : numeric 292 | % Sampling rate 293 | % units : string 294 | % Units of the measurement 295 | % 296 | % Outputs: 297 | % -------- 298 | % new_chan : 299 | % 300 | new_chan = adi.channel_writer(obj,id,name,fs,units); 301 | 302 | %TODO: Do I want to make an explicit delete call to an object 303 | %if it is there 304 | %temp = obj.channels{id}; 305 | %if ~isempty(temp) 306 | % temp.delete() 307 | %end 308 | obj.channels{id} = new_chan; 309 | end 310 | function startRecord(obj,varargin) 311 | % 312 | % Optional Inputs: 313 | % ---------------- 314 | % trigger_time : datenum or datetime 315 | % *** Note, this only respects down to a resolution of 316 | % seconds 317 | % - by default, the first record starts "now" and 318 | % subsequent records start after the first plus some 319 | % buffer (see 'record_spacing' below) 320 | % fractional_seconds : default 0 321 | % provides fractional second resolution for 'trigger_time' 322 | % trigger_minus_rec_start : default 0 323 | % By default the record starts at the same time as the 324 | % trigger. Haven't tested extensively but from ADI it 325 | % says "Specifies the difference between the time of 326 | % trigger tick and the first tick in the record. This +ve 327 | % for pre-trigger delay and -ve for post-trigger delay." 328 | % record_spacing : default 30 329 | % When trigger time is not specified, this is the amount 330 | % of time, in seconds, between one record ending and the 331 | % next beginning 332 | % 333 | % Improvements 334 | % ------------ 335 | % 336 | % 337 | % Example 338 | % ------- 339 | % (Y,MO,D, H, MI,S) 340 | % start_time = datenum(2001,12,19,18,0,0); 341 | % %or start_time = datetime('now') 342 | % fw.startRecord('trigger_time',start_time); 343 | 344 | h__errorIfClosed(obj) 345 | 346 | %in.record_to_copy = []; 347 | %What are the units on trigger time??? 348 | %This should probably be afer the last record, rather than now 349 | in.trigger_time = []; 350 | in.fractional_seconds = 0; 351 | in.record_spacing = 30; 352 | in.trigger_minus_rec_start = 0; 353 | in = adi.sl.in.processVarargin(in,varargin); 354 | 355 | if obj.in_record_mode 356 | error('Unable to start a record when in record mode already') 357 | end 358 | 359 | obj.current_record = obj.last_record + 1; 360 | 361 | % % %I don't know that this is needed ... 362 | % % if ~isempty(in.record_to_copy) 363 | % % %TODO: Populate in based on these properties ... 364 | % % end 365 | % % in = rmfield(in,'record_to_copy'); 366 | 367 | if isempty(in.trigger_time) 368 | %If record 2, add duration of record 1 to this value 369 | if obj.current_record > 1 370 | %trigger_times are in Unix time (seconds) 371 | %Do we need to add a slight offset ???? 372 | in.trigger_time = obj.record_trigger_times(end) + obj.record_durations(end)+in.record_spacing; 373 | %Add duration of last record to last record start time 374 | else 375 | in.trigger_time = adi.sl.datetime.matlabToUnix(now,0); 376 | end 377 | else 378 | %Convert to Unix time, and handle datetime input (if given) 379 | if isa(in.trigger_time,'datetime') 380 | in.trigger_time = datenum(in.trigger_time); 381 | end 382 | 383 | in.trigger_time = adi.sl.datetime.matlabToUnix(in.trigger_time,0); 384 | end 385 | 386 | all_chans = [obj.channels{:}]; 387 | 388 | all_chans.initializeRecord(obj.current_record); 389 | 390 | in = rmfield(in,'record_spacing'); 391 | adi.sdk.startRecord(obj.data_writer_h,in); 392 | 393 | %TODO: Check if the channel is enabled or not for determining 394 | %which has the highest fs 395 | highest_fs = max([all_chans.fs]); 396 | obj.record_dts = [obj.record_dts 1/highest_fs]; 397 | obj.record_trigger_times = [obj.record_trigger_times in.trigger_time]; 398 | end 399 | function stopRecord(obj) 400 | h__errorIfClosed(obj) 401 | obj.last_record = obj.current_record; 402 | obj.current_record = NaN; 403 | adi.sdk.finishRecord(obj.data_writer_h) 404 | 405 | channel_objects = [obj.channels{:}]; 406 | channel_durations = [channel_objects.last_record_duration]; 407 | obj.record_durations = [obj.record_durations max(channel_durations)]; 408 | end 409 | function saveAndClose(obj) 410 | obj.save(); 411 | obj.close(); 412 | end 413 | function save(obj) 414 | h__errorIfClosed(obj) 415 | adi.sdk.commitFile(obj.data_writer_h) 416 | end 417 | function close(obj) 418 | obj.data_writer_h.close(); 419 | end 420 | function delete(obj) 421 | obj.close() 422 | delete(obj.file_h) 423 | %Why is this not working ???? 424 | %Did I not implement it correctly? 425 | % 426 | %I think I might want this as a delete method in 427 | %adi.data_writer_handle 428 | %adi.sdk.closeWriter(obj.data_writer_h.pointer_value) 429 | 430 | %MOVED TO DELETE 431 | end 432 | %addChannel 433 | end 434 | 435 | methods (Hidden) 436 | function updateSampleCount(obj,channel_obj,n_samples) 437 | %We may not need this ... 438 | end 439 | end 440 | 441 | end 442 | 443 | 444 | function h__errorIfClosed(obj) 445 | if obj.data_writer_h.closed 446 | error('Unable to execute function after closing') 447 | end 448 | end 449 | -------------------------------------------------------------------------------- /+adi/getFileComments.m: -------------------------------------------------------------------------------- 1 | function t = getFileComments(file_path) 2 | %X Returns all comments from the file 3 | % 4 | % t = adi.getFileComments(*file_path) 5 | % 6 | % Inputs 7 | % ------ 8 | % file_path : optional 9 | % If not specified the user is prompted 10 | % 11 | % Outputs 12 | % ------- 13 | % t : table 14 | % A table with all of the comments in it 15 | % 16 | % Examples 17 | % -------- 18 | % t = adi.getFileComments 19 | 20 | persistent root_folder; 21 | 22 | % Check if filePath is not provided or empty. 23 | if nargin < 1 || isempty(file_path) 24 | if isempty(root_folder) || ~exist(root_folder,'dir') 25 | % If the rootFolder is not set, ask the user for the file path. 26 | [file_name, file_folder] = uigetfile('*.adicht*', 'Select a file to read'); 27 | if file_name == 0 28 | t = []; 29 | return 30 | end 31 | file_path = fullfile(file_folder, file_name); 32 | root_folder = file_folder; 33 | else 34 | % If the rootFolder is set, start from the previous root folder. 35 | [file_name, file_folder] = uigetfile('*.adicht*', 'Select a file to read',root_folder); 36 | if file_name == 0 37 | t = []; 38 | return 39 | end 40 | file_path = fullfile(file_folder, file_name); 41 | root_folder = file_folder; 42 | end 43 | else 44 | % If filePath is provided, update the root folder to the file's directory. 45 | [file_folder, ~, ~] = fileparts(file_path); 46 | root_folder = file_folder; 47 | end 48 | 49 | f = adi.readFile(file_path); 50 | 51 | t = f.getAllComments('output','table'); 52 | 53 | end 54 | -------------------------------------------------------------------------------- /+adi/h5_conversion_options.m: -------------------------------------------------------------------------------- 1 | classdef h5_conversion_options < handle 2 | % 3 | % Class: 4 | % adi.h5_conversion_options 5 | % 6 | % See Also: 7 | % adi.channel.exportToHDF5File 8 | 9 | properties 10 | max_samples_per_read = 1e8 11 | deflate_value = 3 %(0 - 9 for gzip 12 | %0 - no compression 13 | %9 - most compression 14 | % 15 | % NOTE: At some point with gzip the data fail to compress any 16 | % more. 17 | use_shuffle = false 18 | chunk_length = 1e8 %Ideally this would be linked to 19 | %'max_samples_per_read' but for now this is ok 20 | end 21 | 22 | properties (Dependent) 23 | chunk_length_pct 24 | end 25 | 26 | methods 27 | function set.chunk_length_pct(obj,value) 28 | obj.chunk_length = round(obj.max_samples_per_read*value); 29 | end 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /+adi/h5_file_h.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) h5_file_h < handle 2 | % 3 | % Class: 4 | % adi.h5_file_h 5 | 6 | properties 7 | file_path 8 | m %Structure containing the meta data 9 | end 10 | 11 | methods 12 | function obj = h5_file_h(file_path) 13 | % 14 | % obj = adi.h5_file_h(file_path) 15 | % 16 | % See Also: 17 | % adi.readFile 18 | 19 | %TODO: Since we are not using this right away, we might 20 | %want to try and get the file path if it were evaluated 21 | %currently (in case it is relative) 22 | obj.file_path = file_path; 23 | %obj.m = h5m.file.open(file_path); 24 | 25 | 26 | %The general approach used below is to read all meta 27 | %information into memory now. We attempt to mimic the format 28 | %used when saving to a 'mat' format so that we can use the same 29 | %SDK interface. 30 | % 31 | %In general I really dislike this approach and will eventually 32 | %revert to functions that read and write structs. Also, the 33 | %encapsulation is currently shot and needs to be fixed (writing 34 | %and reading are separated) 35 | %============================================================== 36 | 37 | %File 38 | %------------------------------------- 39 | rf = @(attr)h5readatt(file_path,'/file',attr); 40 | file_struct = struct(... 41 | 'n_records', rf('n_records'),... 42 | 'n_channels', rf('n_channels')); 43 | 44 | %Records 45 | %-------------------------------------- 46 | rr = @(attr)num2cell(h5readatt(file_path,'/record_meta',attr)); 47 | 48 | record_struct = struct(... 49 | 'n_ticks', rr('n_ticks'),... 50 | 'tick_dt', rr('tick_dt'),... 51 | 'record_start', rr('record_start'),... 52 | 'data_start', rr('data_start'),... 53 | 'trigger_minus_rec_start_samples', rr('trigger_minus_rec_start_samples'))'; 54 | 55 | %Comments 56 | rco = @(attr)num2cell(h5readatt(file_path,'/comments',attr)); 57 | 58 | rs = @(attr,group_name)cellstr(char(h5readatt(file_path,group_name,attr))); 59 | 60 | % temp = h5readatt(file_path,'/comments','str'); 61 | % comment_strs = cellstr(char(temp)); 62 | 63 | comment_struct = struct(... 64 | 'str', rs('str','/comments'),... 65 | 'id', rco('id'),... 66 | 'tick_position',rco('tick_position'),... 67 | 'channel', rco('channel'),... 68 | 'record', rco('record'),... 69 | 'tick_dt', rco('tick_dt'))'; 70 | 71 | %Channels 72 | %----------------------------- 73 | rch1 = @(attr)h5readatt(file_path,'/channel_meta',attr); 74 | rch2 = @(attr)num2cell(rch1(attr)); 75 | 76 | id = rch2('id'); 77 | n_rows = length(id); 78 | 79 | dt = num2cell(rch1('dt'),2); 80 | n_samples = num2cell(rch1('n_samples'),2); 81 | 82 | units_temp = cellstr(char(rch1('units'))); 83 | 84 | n_records = length(record_struct); 85 | units_temp2 = reshape(units_temp,[n_rows n_records]); 86 | units = num2cell(units_temp2,2); 87 | 88 | %dt - separate by rows 89 | %n_samples - " " 90 | %units - yikes ... - by rows, but also char 91 | channel_struct = struct(... 92 | 'units', units,... 93 | 'name', rs('name','/channel_meta'),... 94 | 'id', id,... 95 | 'dt', dt,... 96 | 'n_samples',n_samples)'; 97 | 98 | obj.m = struct(... 99 | 'file_meta', file_struct,... 100 | 'record_meta', record_struct,... 101 | 'channel_meta', channel_struct,... 102 | 'comments', comment_struct); 103 | 104 | end 105 | end 106 | 107 | end 108 | 109 | -------------------------------------------------------------------------------- /+adi/h5_file_sdk.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) h5_file_sdk < adi.mat_file_sdk 2 | % 3 | % Class: 4 | % adi.h5_file_sdk 5 | % 6 | % This should expose the SDK in a similar manner as the regular SDK 7 | % but it should do it from a h5 file. 8 | % 9 | % JAH 2014-6-15: This code layout will likely change although the 10 | % interface will most likely not. I just don't like the non-obvious 11 | % code layout. 12 | % 13 | % See Also: 14 | % adi.mat_file_sdk 15 | % adi.sdk 16 | 17 | %NOTE: In Matlab the functions can be easily visualized by folding all 18 | %the code and then expanding this methods block 19 | methods (Static) 20 | %File specific functions 21 | %------------------------------------------------------------------ 22 | function file_h = openFile(file_path) 23 | % 24 | % file = adi.h5_file_sdk.openFile(file_path) 25 | % 26 | % NOTE: Only reading is supported. 27 | % 28 | % Inputs: 29 | % ------- 30 | % file_path : char 31 | % Full path to the file. 32 | % 33 | % Outputs: 34 | % -------- 35 | % file : adi.h5_file_h 36 | 37 | file_h = adi.h5_file_h(file_path); 38 | 39 | end 40 | function output_data = getChannelData(file_h,record,channel,start_sample,n_samples_get,get_samples,varargin) 41 | % 42 | % 43 | % output_data = getChannelData(file_h,record,channel,start_sample,n_samples_get,get_samples,varargin) 44 | % 45 | % Inputs: 46 | % -------- 47 | % file_h : adi.h5_file_h 48 | 49 | in.leave_raw = false; 50 | in = adi.sl.in.processVarargin(in,varargin); 51 | 52 | if get_samples == false 53 | %JAH: NYI 54 | error('Sorry, I can''t let you do that ...') 55 | end 56 | 57 | chan_name = sprintf('/data__chan_%d_rec_%d',channel,record); 58 | 59 | output_data = h5read(file_h.file_path,chan_name,[start_sample 1],[n_samples_get 1]); 60 | 61 | if ~in.leave_raw 62 | output_data = double(output_data); 63 | end 64 | end 65 | end 66 | 67 | %Wrapper methods 68 | methods (Static) 69 | function comments = getAllCommentsForRecord(file_h,record_id,tick_dt,trigger_minus_rec_start) 70 | % 71 | 72 | comments = adi.sdk.getAllCommentsForRecord(file_h,record_id,tick_dt,trigger_minus_rec_start,adi.mat_file_sdk); 73 | end 74 | end 75 | 76 | end 77 | 78 | -------------------------------------------------------------------------------- /+adi/handle_logger.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) handle_logger < handle 2 | % 3 | % Class: 4 | % adi.handle_logger 5 | % 6 | % This is hopefully a temporary class that logs open and close 7 | % operations. 8 | 9 | properties 10 | fid 11 | end 12 | 13 | methods 14 | %TODO: Make private 15 | function obj = handle_logger() 16 | %Call this via: 17 | % 18 | % % % % % repo_root = adi.sl.stack.getPackageRoot; 19 | % % % % % file_path = fullfile(repo_root,'temp_logger.txt'); 20 | % % % % % obj.fid = fopen(file_path,'a'); 21 | end 22 | function delete(obj) 23 | % % % % fclose(obj.fid); 24 | end 25 | end 26 | 27 | methods (Static) 28 | function logOperation(file_path,handle_type,handle_value) 29 | % 30 | % adi.handle_logger.logOperation(file_path,handle_type,handle_value) 31 | % 32 | % Inputs: 33 | % ------- 34 | % file_path : 35 | % Labchart file that is being referenced (not the save 36 | % path) 37 | % handle_type : string 38 | % Who is calling the logger? 39 | % handle_value : integer 40 | 41 | % % % % persistent obj 42 | % % % % 43 | % % % % if isempty(obj) 44 | % % % % obj = adi.handle_logger; 45 | % % % % end 46 | % % % % 47 | % % % % %write_str = sprintf('%s %s:\t%s:%ld\n',datestr(now),file_path,handle_type,handle_value); 48 | % % % % fwrite(obj.fid,write_str); 49 | end 50 | end 51 | 52 | end 53 | 54 | -------------------------------------------------------------------------------- /+adi/handle_manager.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) handle_manager 2 | % 3 | % Class: 4 | % adi.handle_manager 5 | % 6 | % This class keeps track of open files and attempts to prevent 7 | % closing a file that for some reason is not actually open. 8 | % 9 | % This class is called directly by functions in adi.sdk 10 | % 11 | % It is only used by the ADInstruments SDK since I've had some issues 12 | % with getting 13 | % 14 | % 15 | % See Also: 16 | % adi.sdk 17 | % adi.sdk.openFile 18 | 19 | properties 20 | 21 | %NOTE: This file is locked so to change and rerun this class you 22 | %need: 23 | % 24 | % adi.handle_manager.unlock 25 | % clear classes 26 | 27 | DEBUG = false 28 | %Set this to be true to enable printing of statements in the class 29 | %methods 30 | 31 | USE_FIX = true 32 | %true - single file only ever gets one pointer 33 | %false - single file gets multiple pointers 34 | % 35 | % By default this should be true unless we're inspecting how 36 | % the SDK works when this is not true. 37 | end 38 | 39 | properties 40 | map %containers.Map 41 | % 42 | % key - pointer value of open file 43 | % value - file path 44 | path_key_map 45 | % 46 | % key - file path 47 | % value - count of how many are open 48 | end 49 | 50 | methods 51 | function obj = handle_manager() 52 | obj.map = containers.Map('KeyType','int64','ValueType','char'); 53 | obj.path_key_map = containers.Map('KeyType','char','ValueType','any'); 54 | end 55 | end 56 | 57 | methods (Static) 58 | function pointer_value = checkFilePointer(file_path) 59 | % pointer_value = adi.handle_manager.checkFilePointer(file_path) 60 | % 61 | % This gets called on opening a file using the ADI sdk. If 62 | % a file is already open, we return its pointer, rather 63 | % than creating a new pointer for the file. 64 | % 65 | % If a file is not already open, then we return 0, indicating 66 | % that a new pointer should be requested from the SDK. 67 | % 68 | % Inputs: 69 | % ------- 70 | % file_path : path to the file to open 71 | % 72 | % Outputs: 73 | % -------- 74 | % pointer_value : numeric value acting as c pointer 75 | % If the file is not already open, a value of 0 is 76 | % returned 77 | % 78 | % See Also: 79 | % adi.sdk.openFile 80 | 81 | obj = adi.handle_manager.getReference(); 82 | if obj.USE_FIX 83 | if obj.path_key_map.isKey(file_path) 84 | if obj.DEBUG 85 | fprintf(2,'Pointer found for:\n%s\n',file_path); 86 | end 87 | temp = obj.path_key_map(file_path); 88 | pointer_value = temp(1); 89 | temp(2) = temp(2) + 1; 90 | obj.path_key_map(file_path) = temp; 91 | else 92 | %fprintf(2,'Pointer not found for:\n%s\n',file_path); 93 | pointer_value = 0; 94 | end 95 | 96 | else 97 | pointer_value = 0; 98 | end 99 | end 100 | function openFile(file_path,pointer_value) 101 | % 102 | % adi.handle_manager.openFile(file_path,pointer_value) 103 | % 104 | % See Also: 105 | % adi.sdk.openFile 106 | 107 | obj = adi.handle_manager.getReference(); 108 | %disp(file_path) 109 | %disp(pointer_value) 110 | if obj.map.isKey(pointer_value) 111 | error('Pointer value is redundant, this is not expected') 112 | end 113 | obj.map(pointer_value) = file_path; 114 | 115 | if obj.USE_FIX 116 | % [pointer_value count] 117 | obj.path_key_map(file_path) = [pointer_value 1]; 118 | end 119 | end 120 | function closeFile(pointer_value) 121 | % 122 | % adi.handle_manager.closeFile(pointer_value) 123 | % 124 | % See Also: 125 | % adi.sdk.closeFile 126 | % 127 | % TODO: The encapsulation is a bit lacking here. 128 | 129 | obj = adi.handle_manager.getReference(); 130 | if obj.map.isKey(pointer_value) 131 | file_path = obj.map(pointer_value); 132 | 133 | if ~obj.USE_FIX 134 | %Then just close the file 135 | %TODO: We should return the count to the SDK instead 136 | %here we would return 0 (or 1, depending on the design) 137 | obj.map.remove(pointer_value); 138 | result_code = sdk_mex(13,pointer_value); 139 | adi.sdk.handleErrorCode(result_code); 140 | else 141 | 142 | temp = obj.path_key_map(file_path); 143 | if temp(2) == 1 144 | %fprintf(2,'Closing reference for:\n%s\n',file_path); 145 | obj.map.remove(pointer_value); 146 | obj.path_key_map.remove(file_path); 147 | result_code = sdk_mex(13,pointer_value); 148 | adi.sdk.handleErrorCode(result_code); 149 | else 150 | %fprintf(2,'Decrementing reference count for:\n%s\n',file_path); 151 | temp(2) = temp(2) - 1; 152 | obj.path_key_map(file_path) = temp; 153 | end 154 | 155 | end 156 | else 157 | %TODO: Move formatted warning into adi.sl 158 | warning('Trying to close a file whose pointer is not logged') 159 | end 160 | end 161 | function output_obj = getReference() 162 | % 163 | % adi.handle_manager.getReference 164 | % 165 | persistent obj 166 | if isempty(obj) 167 | %NOTE: We lock things to try and prevent problems with 168 | %the mex file. I'm not sure if this is really necessary, 169 | %but it makes me sleep better at night :) 170 | mlock(); 171 | obj = adi.handle_manager; 172 | end 173 | output_obj = obj; 174 | end 175 | function unlock() 176 | %adi.handle_manager.unlock 177 | munlock(); 178 | end 179 | end 180 | 181 | end 182 | 183 | -------------------------------------------------------------------------------- /+adi/mat_comment_handle.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) mat_comment_handle < handle 2 | % 3 | % Class: 4 | % adi.mat_comment_handle 5 | % 6 | % NOTE: This class is a quick hack to get the mat file sdk working 7 | % 8 | % This contains a handle that is needed by the SDK to get comments 9 | % for a particular record. Methods in this class can be used to 10 | % extract actual comments from a record. 11 | % 12 | % See Also: 13 | % adi.comment 14 | 15 | 16 | properties 17 | is_valid = false %A handle may not be valid if there are no comments 18 | %for a given record 19 | record 20 | tick_dt 21 | 22 | current_index = 1 23 | trigger_minus_record_start_s 24 | end 25 | 26 | properties (Hidden) 27 | comment_data 28 | n_comments 29 | end 30 | 31 | methods 32 | function obj = mat_comment_handle(comment_data,is_valid,record_id,tick_dt,trigger_minus_record_start_s) 33 | % 34 | % adi.mat_comment_handle(comment_data,is_valid,record_id,tick_dt) 35 | 36 | %NOTE: Only comments for the record are passed in ... 37 | 38 | obj.comment_data = comment_data; 39 | obj.is_valid = is_valid; 40 | obj.record = record_id; 41 | obj.tick_dt = tick_dt; 42 | obj.n_comments = length(comment_data); 43 | obj.trigger_minus_record_start_s = trigger_minus_record_start_s; 44 | end 45 | function delete(~) 46 | end 47 | function has_another_comment = advanceCommentPointer(obj) 48 | obj.current_index = obj.current_index + 1; 49 | has_another_comment = obj.current_index <= obj.n_comments; 50 | end 51 | function cur_comment = getCurrentComment(obj) 52 | if obj.n_comments == 0 53 | cur_comment = []; 54 | else 55 | cur_comment = adi.mat_file_sdk.getCommentInfo(obj,obj.comment_data(obj.current_index)); 56 | end 57 | end 58 | function close(~) 59 | end 60 | end 61 | 62 | 63 | end 64 | 65 | -------------------------------------------------------------------------------- /+adi/mat_conversion_options.m: -------------------------------------------------------------------------------- 1 | classdef mat_conversion_options 2 | % 3 | % Class: 4 | % adi.mat_conversion_options 5 | 6 | properties 7 | partial_read = false %Not Yet Implemented 8 | %The goal was to partial read/writes of channels to disk 9 | %rather than loading everything at once and dumping in one write. 10 | version = '-v7.3'; 11 | %{ 12 | Option | Versions | Supported Features 13 | ---------+--------------+---------------------------------------------- 14 | '-v7.3' | 7.3 or later | Version 7.0 features plus support for 15 | | | data items greater than or equal to 2GB on 16 | | | 64-bit systems. This version also supports 17 | | | saving variables without compression using 18 | | | the '-nocompression' flag. 19 | ---------+--------------+---------------------------------------------- 20 | '-v7' | 7.0 or later | Version 6 features plus data compression and 21 | | | Unicode character encoding 22 | ---------+--------------+---------------------------------------------- 23 | '-v6' | 5 or later | Version 4 features plus N-dimensional arrays, 24 | | | cell and structure arrays, and variable names 25 | | | greater than 19 characters 26 | %} 27 | end 28 | 29 | methods 30 | function verify(obj) 31 | obj.verifyVersion() 32 | end 33 | function verifyVersion(obj) 34 | switch lower(obj.version) 35 | case '-v7.3' 36 | case '-v7' 37 | case '-v6' 38 | case '-v4' 39 | error('This version is not supported') 40 | otherwise 41 | error('Version "%s" not recognized',obj.version) 42 | end 43 | end 44 | end 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /+adi/mat_file_h.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) mat_file_h < handle 2 | % 3 | % Class: 4 | % adi.mat_file_h 5 | 6 | properties 7 | m %Handle to the file. 8 | end 9 | 10 | methods 11 | function obj = mat_file_h(file_path,varargin) 12 | % 13 | % obj = adi.mat_file_h(file_path,varargin) 14 | % 15 | % Optional Inputs: 16 | % ---------------- 17 | % load_all : logical (default false) 18 | % If true the entire file is loaded into memory at once, 19 | % otherwise the 'matfile' commend and the subsequent 20 | % 'matlab.io.MatFile' class are used. Loading everything 21 | % might makes things slightly faster but it also might be 22 | % a lot slower if only analyzing a portion of the file. 23 | % It might also cause an "out of memory" error. 24 | % 25 | % See Also: 26 | % adi.readFile 27 | 28 | %TODO: Link this to input options in read file 29 | 30 | in.load_all = false; 31 | in = adi.sl.in.processVarargin(in,varargin); 32 | if in.load_all 33 | obj.m = load(file_path); 34 | else 35 | obj.m = matfile(file_path,'Writable',false); 36 | end 37 | end 38 | function delete(obj) 39 | %TODO: Delete 40 | end 41 | end 42 | 43 | end 44 | 45 | -------------------------------------------------------------------------------- /+adi/mat_file_sdk.m: -------------------------------------------------------------------------------- 1 | classdef (Hidden) mat_file_sdk < handle 2 | % 3 | % Class: 4 | % adi.mat_file_sdk 5 | % 6 | % This should expose the SDK in a similar manner as the regular SDK 7 | % but it should do it from a mat file. 8 | % 9 | % The h5_file_sdk borrows implementations from this class and then 10 | % changes some of the methods. I think I would rather they both 11 | % inherit from a file sdk. 12 | % 13 | % JAH 2014-6-15: This code layout will likely change although the 14 | % interface will most likely not. I just don't like the non-obvious 15 | % code layout. 16 | 17 | %NOTE: In Matlab the functions can be easily visualized by folding all 18 | %the code and then expanding this methods block 19 | methods (Static) 20 | %File specific functions 21 | %------------------------------------------------------------------ 22 | function file_h = openFile(file_path) 23 | % 24 | % file = adi.mat_file_sdk.openFile(file_path) 25 | % 26 | % NOTE: Only reading is supported. 27 | % 28 | % Inputs: 29 | % ------- 30 | % file_path : char 31 | % Full path to the file. 32 | % 33 | % Outputs: 34 | % -------- 35 | % file : adi.mat_file_h 36 | 37 | file_h = adi.mat_file_h(file_path); 38 | 39 | end 40 | function n_records = getNumberOfRecords(file_h) 41 | %getNumberOfRecords Get the number of records for a file. 42 | % 43 | % n_records = adi.mat_file_sdk.getNumberOfRecords(file_h) 44 | % 45 | % See definition of the "records" in the definition section. 46 | 47 | file_meta = file_h.m.file_meta; 48 | n_records = file_meta.n_records; 49 | end 50 | function n_channels = getNumberOfChannels(file_h) 51 | %getNumberOfChannels Get # of channels for a file. 52 | % 53 | % n_channels = adi.mat_file_sdk.getNumberOfChannels(file_h) 54 | 55 | 56 | file_meta = file_h.m.file_meta; 57 | n_channels = file_meta.n_channels; 58 | end 59 | %Record specific functions 60 | %------------------------------------------------------------------ 61 | function n_ticks_in_record = getNTicksInRecord(file_h,record) 62 | % 63 | 64 | record_meta = file_h.m.record_meta(1,record); 65 | n_ticks_in_record = record_meta.n_ticks; 66 | 67 | end 68 | function dt_tick = getTickPeriod(file_h,record,~) 69 | % 70 | 71 | record_meta = file_h.m.record_meta(1,record); 72 | dt_tick = record_meta.tick_dt; 73 | end 74 | function [record_start,data_start,trigger_minus_rec_start_samples] = getRecordStartTime(file_h,record,tick_dt) 75 | % 76 | record_meta = file_h.m.record_meta(1,record); 77 | record_start = record_meta.record_start; 78 | data_start = record_meta.data_start; 79 | trigger_minus_rec_start_samples = record_meta.trigger_minus_rec_start_samples; 80 | end 81 | %Comment specific functions 82 | %------------------------------------------------------------------ 83 | function comments_h = getCommentAccessor(file_h,record,tick_dt,trigger_minus_record_start_s) 84 | % 85 | 86 | comments = file_h.m.comments; 87 | comments_for_record = comments([comments.record] == record); 88 | comments_h = adi.mat_comment_handle(comments_for_record,... 89 | ~isempty(comments_for_record),record,tick_dt,trigger_minus_record_start_s); 90 | end 91 | % function closeCommentAccessor(pointer_value) 92 | % end 93 | % function has_another_comment = advanceComments(comments_h) 94 | % end 95 | function comment_info = getCommentInfo(comments_h,data) 96 | % 97 | % 98 | % comment_info = adi.sdk.getCommentInfo(comments_h) 99 | 100 | comment_info = adi.comment(data.str,... 101 | data.tick_position,... 102 | data.channel,... 103 | data.id,... 104 | comments_h.record,... 105 | comments_h.tick_dt,... 106 | comments_h.trigger_minus_record_start_s); 107 | end 108 | %Channel specific functions 109 | %------------------------------------------------------------------ 110 | function n_samples = getNSamplesInRecord(file_h,record,channel) 111 | % 112 | cm = file_h.m.channel_meta; 113 | n_samples = cm(channel).n_samples(record); 114 | 115 | end 116 | function output_data = getChannelData(file_h,record,channel,start_sample,n_samples_get,get_samples,varargin) 117 | % 118 | %(file_h,record,channel,start_sample,n_samples_get,get_samples,varargin) 119 | 120 | in.leave_raw = false; 121 | in = adi.sl.in.processVarargin(in,varargin); 122 | 123 | if get_samples == false 124 | %JAH: NYI 125 | error('Sorry, I can''t let you do that ...') 126 | end 127 | 128 | chan_name = sprintf('data__chan_%d_rec_%d',channel,record); 129 | output_data = file_h.m.(chan_name)(start_sample:(start_sample+n_samples_get-1),1); 130 | 131 | if in.leave_raw 132 | %output_data = output_data; 133 | else 134 | output_data = double(output_data); %Matlab can get finicky working with singles 135 | end 136 | 137 | end 138 | function units = getUnits(file_h,record,channel) 139 | %getUnits 140 | cm = file_h.m.channel_meta(1,channel); 141 | units = cm.units{record}; 142 | end 143 | function channel_name = getChannelName(file_h,channel) 144 | % 145 | cm = file_h.m.channel_meta(1,channel); 146 | channel_name = cm.name; 147 | end 148 | function dt_channel = getSamplePeriod(file_h,record,channel) 149 | % 150 | cm = file_h.m.channel_meta(1,channel); 151 | dt_channel = cm.dt(record); 152 | end 153 | end 154 | 155 | %Wrapper methods 156 | methods (Static) 157 | function comments = getAllCommentsForRecord(file_h,record_id,tick_dt,trigger_minus_rec_start_samples) 158 | % 159 | %TODO: This isn't right 160 | 161 | comments = adi.sdk.getAllCommentsForRecord(file_h,record_id,tick_dt,trigger_minus_rec_start_samples,adi.mat_file_sdk); 162 | end 163 | end 164 | 165 | end 166 | 167 | -------------------------------------------------------------------------------- /+adi/plot.m: -------------------------------------------------------------------------------- 1 | function plot(file_path) 2 | %x Launches the plotter GUI 3 | % 4 | % The plotting GUI allows you to show an individual record OR to print 5 | % the entire study to PDF 6 | % 7 | % Calling Forms 8 | % ------------- 9 | % adi.plot() 10 | % 11 | % adi.plot(file_path) 12 | % 13 | % Inputs 14 | % ------ 15 | % file_path : string 16 | % Of type .adicht 17 | % 18 | % See Also 19 | % -------- 20 | % adi.plotter 21 | % 22 | % Improvements 23 | % ------------ 24 | % 1) Add a scrollbar for easier reviewing 25 | % 2) Expose options for printing to file (and any other options) 26 | % 3) Show if "fast plotting" is detected (i.e., is the plotBig library 27 | % present) 28 | 29 | persistent root_folder; 30 | 31 | % Check if filePath is not provided or empty. 32 | if nargin < 1 || isempty(file_path) 33 | if isempty(root_folder) || ~exist(root_folder,'dir') 34 | % If the rootFolder is not set, ask the user for the file path. 35 | [file_name, file_folder] = uigetfile('*.adicht*', 'Select a file to read'); 36 | if file_name == 0 37 | t = []; 38 | return 39 | end 40 | file_path = fullfile(file_folder, file_name); 41 | root_folder = file_folder; 42 | else 43 | % If the rootFolder is set, start from the previous root folder. 44 | [file_name, file_folder] = uigetfile('*.adicht*', 'Select a file to read',root_folder); 45 | if file_name == 0 46 | t = []; 47 | return 48 | end 49 | file_path = fullfile(file_folder, file_name); 50 | root_folder = file_folder; 51 | end 52 | else 53 | % If filePath is provided, update the root folder to the file's directory. 54 | [file_folder, ~, ~] = fileparts(file_path); 55 | root_folder = file_folder; 56 | end 57 | 58 | 59 | adi.plotter(file_path) 60 | 61 | end -------------------------------------------------------------------------------- /+adi/printFile.m: -------------------------------------------------------------------------------- 1 | function printFile(file_path, varargin) 2 | % 3 | % adi.printFile(file_path,varargin) 4 | 5 | 6 | %{ 7 | adi.printFile(file_path) 8 | %} 9 | 10 | 11 | in.width_per_slide = 20*60; 12 | in.overlap_width = 2*60; 13 | in.print_size = [8.5 11]; 14 | in = adi.sl.in.processVarargin(in,varargin); 15 | 16 | 17 | f = adi.readFile(file_path); 18 | 19 | %# of active channels 20 | %duration of all records 21 | 22 | %Options 23 | %--------------------- 24 | %- Ordering 25 | %- % to show 26 | %- width per slide (seconds) 27 | %- print size 28 | %- overlap 29 | %- which channels to plot 30 | %- need plot height info 31 | 32 | 33 | %Need to go from absolute time to 34 | record_starts = NaT(1,f.n_records); 35 | record_ends = NaT(1,f.n_records); 36 | for i = 1:f.n_records 37 | temp = f.records(i).data_start; 38 | record_starts(i) = datetime(temp,'ConvertFrom','datenum'); 39 | record_ends(i) = record_starts(i) + seconds(f.records(i).duration); 40 | end 41 | 42 | total_duration_s = record_ends(end)-record_starts(1); 43 | 44 | step_size = seconds(in.width_per_slide - in.overlap_width); 45 | starts = record_starts(1):step_size:record_ends(end); 46 | stops = starts + seconds(in.width_per_slide); 47 | 48 | n_records = f.n_records; 49 | n_pages = length(starts); 50 | 51 | n_channels = f.n_channels; 52 | 53 | %Scaling 54 | %--------------------------------------------- 55 | % 56 | % 57 | % This is only needed if no scaling is presented 58 | 59 | 60 | % chan_data = cell(1,n_records); 61 | chan_min = NaN(n_channels,1); 62 | chan_max = NaN(n_channels,1); 63 | for i = 1:n_channels 64 | for j = 1:n_records 65 | temp = f.getChannelData(i,j,'return_object',false); 66 | if ~isempty(temp) 67 | chan_min(i) = min(chan_min(i),min(temp)); 68 | chan_max(i) = max(chan_max(i),max(temp)); 69 | end 70 | end 71 | end 72 | 73 | colors = colororder(); 74 | if n_channels > size(colors,1) 75 | colors = repmat(colors,n_channels/ceil(size(colors,1)),1); 76 | end 77 | 78 | 79 | for i = 1:n_pages 80 | records_include = find(record_starts >= starts(i) & record_starts <= stops(i)); 81 | %TODO: We could optional collapse here if certain channels are missing 82 | %for all records 83 | h_axes = cell(1,n_channels); 84 | for c = 1:n_channels 85 | h_axes{c} = subplot(n_channels,1,c); 86 | hold on 87 | for j = 1:length(records_include) 88 | cur_record = records_include(j); 89 | r_t1 = record_starts(cur_record); 90 | r_t2 = record_ends(cur_record); 91 | overlap_start = max(starts(i), r_t1); 92 | overlap_stop = min(stops(i), r_t2); 93 | 94 | %TODO: Support faster plotting 95 | 96 | 97 | 98 | 99 | [data,time] = f.getChannelData(c,cur_record,... 100 | 'time_range',[overlap_start,overlap_stop],... 101 | 'datetime_out',true,'return_object',false); 102 | 103 | if ~isempty(which('plotBig')) 104 | %What was the MATLAB bug????? 105 | 106 | else 107 | plot(time,data,'Color',colors(c,:)); 108 | end 109 | 110 | %We could make this optional ... 111 | if r_t1 >= overlap_start && ... 112 | r_t1 <= overlap_stop 113 | y = [chan_min(c) chan_max(c)]; 114 | plot([r_t1 r_t1],y,'k') 115 | end 116 | 117 | 118 | %TODO: Add all comments 119 | 120 | % channel_number_1b_or_name, ... 121 | % block_number_1b,varargin) 122 | end 123 | set(gca,'YLim',[chan_min(c) chan_max(c)],'XLim',[starts(i),stops(i)]) 124 | end 125 | keyboard 126 | 127 | %Adjust everything and print 128 | % - page size 129 | % - tighten spacing 130 | % - where to save 131 | % - by default save to 132 | % - add title 133 | 134 | end 135 | 136 | 137 | 138 | 139 | end 140 | 141 | 142 | function launchGUI() 143 | 144 | 145 | end -------------------------------------------------------------------------------- /+adi/private/ADIDatIOWin64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/+adi/private/ADIDatIOWin64.dll -------------------------------------------------------------------------------- /+adi/private/ADIDatIOWin64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/+adi/private/ADIDatIOWin64.lib -------------------------------------------------------------------------------- /+adi/private/adi_example.cpp: -------------------------------------------------------------------------------- 1 | //This example runs the example provided by ADInstruments 2 | // 3 | // 4 | // mex('adi_example.cpp','-v','ADIDatIOWin64.lib') 5 | 6 | #include "mex.h" 7 | 8 | #include "stdafx.h" 9 | 10 | #define _USE_MATH_DEFINES //for M_PI 11 | #include 12 | 13 | 14 | #include 15 | 16 | //#include "Utility/LoadADIDatDll.h" 17 | #include "ADIDatCAPI_mex.h" //Non COM DLL entry points 18 | 19 | 20 | bool IsError(ADIResultCode code) 21 | { 22 | if(code != kResultSuccess) 23 | { 24 | const int kMaxErrorMsgLen = 1024; 25 | wchar_t errorMsg[kMaxErrorMsgLen]; 26 | ADI_GetErrorMessage(code,errorMsg,kMaxErrorMsgLen,0); 27 | wprintf_s(L"\n%s\n", errorMsg); 28 | return true; 29 | } 30 | return false; 31 | } 32 | 33 | 34 | 35 | //--------------------------------------------- 36 | // main function 37 | //--------------------------------------------------------- 38 | int _tmain() 39 | { 40 | 41 | #ifdef ADI_USELOADIDATDLL 42 | HMODULE dllInstance = LoadADIDatDll(); //Explicity link to the dll 43 | 44 | if(!dllInstance) 45 | { 46 | //cout<<"Usage: TestReader "< kMaxSourceBufferSize) 117 | numNewSamples = kMaxSourceBufferSize; 118 | 119 | int i = 0; 120 | long endSample = samplesAddedToChannel+numNewSamples; //std::min(ticksAddedToRecord+chanBuf.size(),nTicksInRecord); 121 | for(long sample(samplesAddedToChannel);sample`_ 4 | to MATLAB. 5 | 6 | It allows you to read LabChart files into MATLAB and to create your own LabChart files. 7 | 8 | **Only works with Windows. This is an ADInstruments issue. I can't fix it** 9 | 10 | My less-polished Python version: 11 | https://github.com/JimHokanson/adinstruments_sdk_python 12 | 13 | If you want to control/interact with LabChart: 14 | https://github.com/JimHokanson/labchart_server_matlab 15 | 16 | # Motivation 17 | 18 | LabChart provides functionality for exporting data to a '.mat' file, although I have yet to get it to work. I also prefer not having files that are simply copies of an original file but in a different format. This code allows reading data directly from LabChart data files. 19 | 20 | # Top Level Functions 21 | - adi.readFile : Reads a file 22 | - adi.convert : Converts the a given file to another format (e.g. from .adicht to .mat) 23 | 24 | # File Reading Notes 25 | 26 | Like most of my more recent readers, the raw channel data are not returned unless specifically requested. Instead a lightweight (low memory) handle is returned with relevant meta data and the raw channel data can be requested via subsequent method calls. 27 | 28 | ``` 29 | f = adi.readFile; 30 | pres_chan = f.getChannelByName('pressure'); 31 | raw_pres_data = pres_chan.getData(1); %Get data from the first record 32 | ``` 33 | 34 | At some point a function could be written that reads everything and reads it into a structure but this is really really low priority for me. 35 | 36 | To read these files on a Mac first requires converting the 'adicht' format into a 'mat' format. Unfortunately, the way in which these files needs to be created leaves the mat file looking a bit ugly. Once however it is in the 'mat' format, it can be read via the code in the same way that the original 'adicht' file could. 37 | 38 | ``` 39 | f = adi.readFile('/Users/Jim/Work/example_file.mat'); 40 | ``` 41 | 42 | If you want to extract all comments into table, do this: 43 | 44 | ``` 45 | t = adi.getFileComments 46 | %or 47 | t = adi.getFileComments('/Users/Jim/Work/example_file.mat') 48 | ``` 49 | 50 | # File Writing Notes 51 | 52 | See [here for writing LabChart files](documentation/writing.md) 53 | 54 | # Requirements & Installation 55 | 56 | Requires 64bit MATLAB on Windows. 32bit Matlab could be supported but some of the mex code would need to be rewritten. 57 | 58 | To install the package ('+adi') needs to be on the Matlab path (add parent folder of +adi folder). **Sub-folders of the package should not be added to the path.** 59 | 60 | 61 | # Tech Notes # 62 | 63 | General parts include: 64 | 65 | - sdk class/package (see adi.sdk) - this is the Matlab code that directly interfaces with the mex code 66 | - sdk_mex (see +adi/private directory) - this is the actual mex code which calls the ADInstruments dll 67 | -------------------------------------------------------------------------------- /adi.m: -------------------------------------------------------------------------------- 1 | classdef adi 2 | % 3 | % Class: 4 | % adi 5 | % 6 | % This class is meant to hold simple helpers for the package. 7 | 8 | methods (Static) 9 | function view() 10 | % 11 | % adi.view(*file_path) 12 | % 13 | % This is 14 | %TODO: Run the viewer 15 | file_path = adi.uiGetChartFile(); 16 | adi.file_viewer(file_path); 17 | end 18 | end 19 | 20 | methods (Hidden,Static) 21 | function createBlankFileAtPath(file_path) 22 | % 23 | % adi.createBlankFileAtPath(file_path) 24 | % 25 | % This function was written to try and test out the 26 | % importance of creating a new file, from scratch, versus 27 | % just copying a new file that I had previously created using 28 | % LabChart 8. 29 | % 30 | % I was having trouble with the writing aspect of the SDK. It 31 | % turns out there were bugs in the code that had yet to be 32 | % finished. 33 | % 34 | % I'm not sure if this function is necessary anymore, but 35 | % I'll leave it here for now. 36 | 37 | repo_path = adi.sl.stack.getPackageRoot(); 38 | blank_file_path = fullfile(repo_path,'files','blank_labchart_8_file.adicht'); 39 | copyfile(blank_file_path,file_path) 40 | end 41 | end 42 | methods (Static) 43 | function [file_path, file_root] = uiGetChartFile(varargin) 44 | %x Show user selection window to select Labchart files 45 | % 46 | % [file_path, file_root] = adi.uiGetChartFile(varargin) 47 | % 48 | % This function can be used to select a particular LabChart 49 | % file. 50 | % 51 | % Optional Inputs: 52 | % ---------------- 53 | % multi_select : logical (default: false) 54 | % If true multiple files can be selected. 55 | % prompt : string (default: 'Pick a file to read' 56 | % start_path : string (default: '') 57 | % If not empty the selection will start in the specified 58 | % folder. If empty AND the function was previously used 59 | % the previously selected path will be used. 60 | % 61 | % Output: 62 | % ------- 63 | % file_path : str, 0, or cellstr 64 | % If the user cancels the output is 0. Otherwise this is 65 | % the full path to the file. 66 | % file_root : str or 0 67 | % Path to the folder that contains the file. 68 | 69 | persistent last_file_root 70 | 71 | in.multi_select = false; 72 | in.prompt = 'Pick a file to read'; 73 | in.start_path = ''; 74 | in = adi.sl.in.processVarargin(in,varargin); 75 | 76 | filter_specifications = ... 77 | {'*.adicht','Labchart Files (*.adicht)'; ... 78 | '*.h5','HDF5 Files (*.h5)';... 79 | '*.mat','Matlab Files (*.mat)'}; 80 | 81 | if in.multi_select 82 | multi_select_value = 'on'; 83 | else 84 | multi_select_value = 'off'; 85 | end 86 | 87 | if isempty(in.start_path) && ~isempty(last_file_root) 88 | in.start_path = last_file_root; 89 | end 90 | 91 | if isempty(in.start_path) 92 | [file_name_or_names,file_root] = uigetfile(filter_specifications,in.prompt,'MultiSelect',multi_select_value); 93 | else 94 | [file_name_or_names,file_root] = uigetfile(filter_specifications,in.prompt,in.start_path,'MultiSelect',multi_select_value); 95 | end 96 | 97 | if isnumeric(file_name_or_names) 98 | file_path = 0; 99 | file_root = 0; 100 | else 101 | last_file_root = file_root; 102 | if ischar(file_name_or_names) 103 | file_path = fullfile(file_root,file_name_or_names); 104 | if in.multi_select 105 | file_path = {file_path}; 106 | end 107 | else 108 | n_files = length(file_name_or_names); 109 | file_path = cell(1,n_files); 110 | for iFile = 1:n_files 111 | file_path{iFile} = fullfile(file_root,file_name_or_names{iFile}); 112 | end 113 | end 114 | end 115 | end 116 | end 117 | 118 | end 119 | 120 | -------------------------------------------------------------------------------- /documentation/ADIDatToolkit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/documentation/ADIDatToolkit.pdf -------------------------------------------------------------------------------- /documentation/ReadMe.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/documentation/ReadMe.md -------------------------------------------------------------------------------- /documentation/changes.md: -------------------------------------------------------------------------------- 1 | # Changes # 2 | 3 | ## 2021-07-23 ## 4 | 5 | - Fixed pragma warnings (issue #14) 6 | - Updated documentation 7 | - Added support for versioning the mat file 8 | 9 | ``` 10 | %Save as -v6 rather than the default, -v7.3 11 | options = adi.mat_conversion_options; 12 | options.version = '-v6'; 13 | adi.convert(file_path,'conversion_options',options); 14 | ``` -------------------------------------------------------------------------------- /documentation/organization.md: -------------------------------------------------------------------------------- 1 | ---------------- 2 | The Labchart SDK 3 | ---------------- 4 | This file is meant to briefly discuss how this repo came about and how the code is organized. 5 | 6 | This code is based on the Labchart SDK provided by ADInstruments. An installer for 7 | the SDK can be found in the Labchart folder following installation of Labchart. 8 | The SDK provides a Windows dll and information on what functions are available 9 | in the dll. 10 | 11 | A mex file has been written that calls these dll functions from Matlab. 12 | A separate set of Matlab functions exist that expose this mex file to Matlab. 13 | These functions are currently located in adi.sdk (although I plan on moving this). 14 | 15 | On top of these functions I've written classes that make it slightly easier 16 | to work with the SDK. 17 | 18 | ------------------- 19 | Code in the Library 20 | ------------------- 21 | 22 | The adi package contains all relevant code. 23 | 24 | Within the adi package are meant to be relevant entry points. 25 | Ideally I want to move most of the non-entry functions outside of the base package but this is a very low priority 26 | 27 | Entry functions include: (may be outdated) 28 | - adi.readFile 29 | - adi.createFile 30 | - adi.editFile 31 | - adi.convert -------------------------------------------------------------------------------- /documentation/updating_mex_code.txt: -------------------------------------------------------------------------------- 1 | Upgrading the mex code 2 | 3 | 1) Run the ADISimpleDataFileSDK64.msi located at: 4 | C:\Program Files (x86)\ADInstruments\LabChart8\Extras 5 | 6 | 2) Copy: 7 | - ADIDatIOWin64.dll 8 | - ADIDatIOWin64.lib 9 | 10 | Over from: (path will be different by user) 11 | C:\Users\Jim\Documents\ADInstruments\SimpleDataFileSDK 12 | 13 | 3) Check and see if the header file has changed and move it over if it has. 14 | This however will most likely require some code changes ... 15 | 16 | 4) Run adi.sdk.makeMex -------------------------------------------------------------------------------- /documentation/writing.md: -------------------------------------------------------------------------------- 1 | # Writing 2 | 3 | I have not tested this extensively. Suggestions welcome. 4 | 5 | LabChart supports at least 3 different file types: 6 | 7 | 1. **.adibin** - a very simple binary format with only 1 record and no events 8 | 2. **.adidat** - a more complex format that supports multiple records and events 9 | 3. **.adicht** - the standard LabChart file format that includes not only data (like adidat) but also settings. Writing directly to this format is not supported. 10 | 11 | I haven't tested this but my impression is that you can switch formats if need be using LabChart. 12 | 13 | **If you want to write to a .adicht file, write to .adidat and convert using LabChart** 14 | 15 | # Limitations # 16 | 17 | - It should be possible to move comments, but I haven't figured out how. I can't remember if it is a bug with the API or with my code 18 | - Sampling must be at a fixed rate 19 | 20 | # ADIBIN writing # 21 | 22 | ``` 23 | file_path = 'C:\repos\test_bin.adibin'; 24 | obj = adi.binary_file_writer(file_path) 25 | %FORMAT: channel name, units, sampling_rate, data 26 | obj.addNewChannel('Pressure','cmH2O',1000,1:2000); 27 | obj.addNewChannel('EUS EMG Corrected','mV',20000,1:40000); 28 | obj.addNewChannel('Stim','V',20000,1:40000); 29 | obj.write(); 30 | ``` 31 | 32 | # ADIDAT writing # 33 | 34 | Here's an example. 35 | 36 | Note, I recommend that you don't add all of your samples at once. If you want to write a lot of data call addSamples in a loop. My impression is that there is a compression call when adding samples and if you add too many the compression fails (see issue #???) 37 | 38 | ``` 39 | file_path = 'D:\repos\test.adidat'; 40 | fw = adi.createFile(file_path); %fw : file_writer 41 | 42 | fs1 = 100; 43 | chan = 1; 44 | pres_chan = fw.addChannel(chan,'pressure',fs1,'cmH20'); 45 | 46 | fs2 = 1000; 47 | chan = 2; 48 | emg_chan = fw.addChannel(chan,'emg',fs2,'mV'); 49 | 50 | start_date_time = datenum(2023,7,19,18,0,0); 51 | fw.startRecord('trigger_time',start_date_time); 52 | 53 | y1 = [1:1/fs1:10 10:-1/fs1:1 1:1/fs1:10 10:-1/fs1:1]; 54 | pres_chan.addSamples(y1); 55 | 56 | %Note record gets truncated to shortest channel 57 | t = (1:length(y1)*(fs2/fs1)).*1/fs2; 58 | y2 = sin(2*pi*1/10*t); 59 | emg_chan.addSamples(y2); 60 | 61 | fw.stopRecord(); 62 | 63 | %Repeat startRecord and stopRecord if you want to add more 64 | 65 | comment_time = 2; 66 | comment_string = 'Best EMG signal ever!'; 67 | record = -1; %-1 is current record (or last record if stopped) 68 | comment_channel = 2; 69 | fw.addComment(record,comment_time,comment_string,'channel',comment_channel); 70 | 71 | comment_time = 9; 72 | comment_string = 'Best time ever'; 73 | record = -1; %-1 is current record (or last record if stopped) 74 | comment_channel = -1; 75 | fw.addComment(record,comment_time,comment_string,'channel',comment_channel); 76 | fw.save(); 77 | fw.close(); 78 | 79 | %Or this should work 80 | %fw.saveAndClose(); 81 | ``` 82 | 83 | Note, the writing process locks the file. If you want to view it in LabChart you should call: 84 | 85 | ``` 86 | clear fw emg_chan pres_chan %modify based on your variables 87 | %OR 88 | clearvars 89 | ``` 90 | -------------------------------------------------------------------------------- /files/LabChartBinaryFormat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/files/LabChartBinaryFormat.pdf -------------------------------------------------------------------------------- /files/blank_labchart_8_file.adicht: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimHokanson/adinstruments_sdk_matlab/2666f38e242ad3d57f012576c1a26328f54ea43b/files/blank_labchart_8_file.adicht -------------------------------------------------------------------------------- /files/crash_testing.m: -------------------------------------------------------------------------------- 1 | %Running this code enough will cause the program to crash 2 | % 3 | % I think this suggests that if the file is somehow open in on place that 4 | % it shouldn't be opened again in another. 5 | % 6 | % Suggested fix: When trying to open a file, see if it is in memory. 7 | % Problem: I'm not sure how to hold onto the file and yet not allow the 8 | % object to be destroyed. 9 | 10 | %EXPT_ID = '141010_J'; %crashes on Palidin 11 | %EXPT_ID = '140414_C'; %might crash, needs more testing 12 | %EXPT_ID = ''140724_C'; %can crash, takes some work 13 | 14 | for i = 1:100 15 | EXPT_ID = '141010_J'; 16 | c = dba.GSK.cmg_expt(EXPT_ID); 17 | pres_data = c.getData('pres'); 18 | c2 = dba.GSK.cmg_expt(EXPT_ID); 19 | pres_data2 = c2.getData('pres'); 20 | 21 | clear c 22 | clear c2 23 | clear pres_data 24 | clear pres_data2 25 | 26 | c = dba.GSK.cmg_expt(EXPT_ID); 27 | pres_data = c.getData('pres'); 28 | end --------------------------------------------------------------------------------