diff --git a/phoebe.fig b/phoebe.fig index 20faf10..f7213d5 100644 Binary files a/phoebe.fig and b/phoebe.fig differ diff --git a/phoebe.m b/phoebe.m index 0bd49bf..a6084f4 100644 --- a/phoebe.m +++ b/phoebe.m @@ -256,6 +256,25 @@ function togglebutton_scan_Callback(hObject, ~, handles, FileName, PathName) end [SD,hb_streamed] = parse_fnirs_channels_oxysoft(inlet); lambda = [760,840]; + + case 4 % LUMO + pred = ' starts-with(name,''LUMO'') and type=''NIRS''' ; + result = lsl_resolve_bypred(lib, pred); + + if ~isempty(result) + inlet = lsl_inlet(result{1}); + [~,~] = inlet.pull_chunk(); + else + uiwait(warndlg(sprintf('LSL stream not found.\n\nPlease check if LSL Stream is enabled.'),'PHOEBE')) + set(handles.togglebutton_scan,'String','START MONITORING'); + set(handles.togglebutton_scan,'Value',0); + set(handles.radiobutton_singleview,'Enable','on'); + set(handles.radiobutton_doubleview,'Enable','on'); + guidata(hObject,handles) + return + end + [SD,hb_streamed] = parse_fnirs_channels_lumo(inlet); + lambda = [735,850]; end % extinction coefficients @@ -354,6 +373,22 @@ function togglebutton_scan_Callback(hObject, ~, handles, FileName, PathName) nirs_data1 = lsl_buffer(:,SD(:,3)); nirs_data2 = lsl_buffer(:,SD(:,4)); + % Confirm that LUMO stream has valid data + if (get(handles.popupmenu_device,'value') == 4) + + % Check for potential saturation in LUMO data + if (sum(any(nirs_data1 < 1e-10)) > 0) + warning('Saturated signals'); + continue + end + + % Check for any incomplete frames + if (sum(any(isnan(nirs_data1))) > 0) + warning('Some frames are incomplete'); + continue + end + end + if hb_streamed [nirs_data1,nirs_data2] = hb2raw(nirs_data1,nirs_data2,ext); end diff --git a/subfunctions/parse_fnirs_channels_lumo.m b/subfunctions/parse_fnirs_channels_lumo.m new file mode 100644 index 0000000..b976b0f --- /dev/null +++ b/subfunctions/parse_fnirs_channels_lumo.m @@ -0,0 +1,117 @@ +function [measurement_matrix,type] = parse_fnirs_channels_lumo(inlet) +% PARSE_FNIRS_CHANNELS_LUMO Parse fNIRS channels from a LUMO LSL stream +% +% [measurement_matrix,type] = PARSE_FNIRS_CHANNELS_LUMO(inlet) +% +% PARSE_FNIRS_CHANNELS_LUMO is used to parse fNIRS channel enumeration from +% a LUMO LSL stream. +% +% The code checks if the LUMO LSL stream contains valid channel enumeration +% structure, and will return an error if invalid data is found. +% +% Parameters +% inlet: an open LUMO LSL inlet +% +% Returns +% meausurement_matrix: [#channels x 4] contains the S-D pairings (cols 1-2) +% and the LSL vector indices (cols 3-4) for either wavelength 1-2 +% +% type: 0 for raw data, 1 for hemoglobin +% +% ND, Gowerlabs, October 2024 + +% LUMO transmits raw data +type = 0; + +% Get information about the stream +stream_info = inlet.info(); + +% Get individual stream attributes +stream_mac= stream_info.type(); %Type NIRS +stream_n_channels = stream_info.channel_count(); %Get total channel count + +% Validate stream attributes +if ~strcmp(stream_mac, 'NIRS') + error('Chosen LSL stream is not a fNIRS LUMO stream.'); +end + +%% Scan LSL metadata to parse information about fNIRS channels only +ch = stream_info.desc().child('channels').child('channel'); + +% Fill measurement list channel-by-channel +% Columns: [s d wl lsl_vector_index) +nirs_channel = 1; +meas_list = zeros(stream_n_channels,4); %Prepare for efficiency + +for k = 1:(stream_n_channels) + if strcmp(ch.child_value('type'),'Intensity') + + % extract source information + source = ch.child_value('source'); + rg1= regexp(source,'N(?\d+)/(?\w)','names'); + src_node = str2double(rg1.node); + + if (src_node < 1 || src_node > 54) + error('Chosen LSL stream has inconsistent source enumeration.'); + end + + switch(rg1.idx) + case 'A' + source_idx = 1; + case 'B' + source_idx = 2; + case 'C' + source_idx = 3; + otherwise + error('Chosen LSL stream has inconsistent source enumeration.'); + end + + % extract detector information + detector = ch.child_value('detector'); + rg2= regexp(detector,'N(?\d+)/(?\d)','names'); + det_node = str2double(rg2.node); + det_idx = str2double(rg2.idx); + + if (det_node < 1 || det_node > 54) || (det_idx < 1 || det_idx > 4) + error('Chosen LSL stream has inconsistent detector enumeration.'); + end + + % extract wavelength information + wavelen = ch.child_value('wavelen'); + if strcmp(wavelen,'735.000000') + wl = 0; + elseif strcmp(wavelen,'850.000000') + wl = 1; + else + error('Chosen LSL stream has inconsistent wavelength information.'); + end + + meas_list(nirs_channel,1) = (src_node - 1)*3 + source_idx; + meas_list(nirs_channel,2) = (det_node - 1)*4 + det_idx; + meas_list(nirs_channel,3) = wl; + meas_list(nirs_channel,4) = k; % This is the index(position) of channel in the LSL vector being streamed + nirs_channel = nirs_channel +1; % Increase nirs channel counter + end + ch = ch.next_sibling(); % Move to next LSL channel +end + +%% Convert to measurement_matrix: [s d wl1_lsl_index wl2_lsl_index] + +% Extract list of all wavelengths streamed by this device +wl_list = unique(meas_list(:,3)); +%If wl_list is only two wavelengths, we are good. +if size(wl_list, 1) ~= 2 + error('Chosen LSL stream has inconsistent number of wavelengths.'); +end + +measurement_matrix = zeros(size(meas_list,1)/length(wl_list),4); %Prepare for efficiency +channel_list = unique(meas_list(:,1:2),'rows'); % Pull unique list of s-d pairings + +% For all occurrences of each s-d pairings, copy LSL index into column 3 and 4 of new matrix +for i = 1: size(channel_list,1) + [r1,~] = find(ismember(meas_list(:,1:3),[channel_list(i,:) wl_list(1)],'rows')); % Find row where s-d pairing at wl1 is + [r2,~] = find(ismember(meas_list(:,1:3),[channel_list(i,:) wl_list(2)],'rows')); % Find row where s-d pairing at wl2 is + measurement_matrix(i,1:2) = channel_list(i,:); % Place unique s-d pairings + measurement_matrix(i,3) = meas_list(r1,4); % Place lsl_index of wl1 + measurement_matrix(i,4) = meas_list(r2,4); % Place lsl_index of wl2 +end \ No newline at end of file