Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility update including a critical revision for latency reading #78

Merged
merged 6 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions import/bemobil_bids_motionconvert.m
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

function motionOut = bemobil_bids_motionconvert(motionIn, objects, streamsConfig)
% This function performs minimal preprocessing and channel sorting for motion data
% Follows WIP BIDS motion specification
% Follows BIDS specification for MOTION data
%--------------------------------------------------------------------------

newCell = {};
Expand Down Expand Up @@ -34,9 +34,13 @@
% 'headRigid_rotY', 'rightHand_rotY';, ...
% 'headRigid_rotZ', 'rightHand_rotZ'};
%
% if nothing is found using these methods, no stream is processed as
% If nothing is found using these methods, no stream is processed as
% quaternion
% search by channel names is prioritized
% Search by channel names is prioritized
%
% If option streamsConfig{streamIndex}.quaternions.keep_quats = 1,
% the BIDS formatted data will contain quaternion channels not Euler
% angles

% quaternion [w,x,y,z] components, in this order
% w is the non-axial component
Expand Down Expand Up @@ -65,6 +69,12 @@
if isfield(streamsConfig{iM}.quaternions, 'output_order')
eulerComponents = streamsConfig{iM}.euler_components;
end

if isfield(streamsConfig{iM}.quaternions, 'keep_quats')
keepQuats = streamsConfig{iM}.quaternions.keep_quats;
else
keepQuats = 0;
end
end

% Finding position data
Expand Down Expand Up @@ -212,18 +222,28 @@
end

if quatFound

% convert from quaternions to euler angles
orientationInQuaternion = dataPre(quaternionIndices,:)';
orientationInEuler = util_quat2eul(orientationInQuaternion); % the BeMoBIL util script
orientationInQuaternion = dataPre(quaternionIndices,:);
orientationInEuler = util_quat2eul(orientationInQuaternion'); % the BeMoBIL util script
orientationInEuler = orientationInEuler';
occindices = find(orientationInQuaternion(1,:) == missingval);
orientationInEuler(:,occindices) = nan;
orientationInQuaternion(:,occindices) = nan;

% unwrap euler angles
orientationInEuler = unwrap(orientationInEuler, [], 2);

% assing Euler or quat angles to orientation channel group
if keepQuats
orientation = orientationInQuaternion;
else
orientation = orientationInEuler;
end
else
orientationInEuler = [];
quaternionIndices = [];
orientationInEuler = [];
orientation = orientationInEuler;
quaternionIndices = [];
end

if cartFound
Expand All @@ -241,15 +261,22 @@
otherData = dataPre(otherChans,:);

% concatenate the converted data
objectData = [orientationInEuler; position; otherData];
objectData = [orientation; position; otherData];
dataPost = [dataPost; objectData];

% enter channel information
for ei = 1:size(orientationInEuler,1)
motionStream.label{end + 1} = [objects{ni} '_eul_' eulerComponents{ei}];
motionStream.hdr.label{end + 1} = [objects{ni} '_eul_' eulerComponents{ei}];
motionStream.hdr.chantype{end + 1} = 'ORNT';
motionStream.hdr.chanunit{end + 1} = 'rad';
for ei = 1:size(orientation,1)
if keepQuats
motionStream.label{end + 1} = [objects{ni} '_quat_' quaternionComponents{ei}];
motionStream.hdr.label{end + 1} = [objects{ni} '_quat_' quaternionComponents{ei}];
motionStream.hdr.chantype{end + 1} = 'ORNT';
motionStream.hdr.chanunit{end + 1} = 'n/a';
else
motionStream.label{end + 1} = [objects{ni} '_eul_' eulerComponents{ei}];
motionStream.hdr.label{end + 1} = [objects{ni} '_eul_' eulerComponents{ei}];
motionStream.hdr.chantype{end + 1} = 'ORNT';
motionStream.hdr.chanunit{end + 1} = 'rad';
end
end

for ci = 1:size(position,1)
Expand Down
41 changes: 34 additions & 7 deletions import/bemobil_xdf2bids.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ function bemobil_xdf2bids(config, varargin)
%--------------------------------------------------------------------------
% config.phys.streams{1}.stream_name = 'force1'; % optional
%
%--------------------------------------------------------------------------
%
%
%
%
% Optional Inputs :
% Provide optional inputs as key value pairs.
% Usage:
Expand All @@ -91,6 +94,7 @@ function bemobil_xdf2bids(config, varargin)
% Sein Jeung ([email protected]) & Soeren Grothkopp ([email protected])
%--------------------------------------------------------------------------


% add load_xdf to path
ft_defaults
[filepath,~,~] = fileparts(which('ft_defaults'));
Expand Down Expand Up @@ -162,18 +166,23 @@ function bemobil_xdf2bids(config, varargin)
end

% default channel types and units
% following CMIXF-12, BIDS v1.8.0 appendix on Units
motion_type.POS.unit = 'm';
motion_type.ORNT.unit = 'rad';
motion_type.VEL.unit = 'm/s';
motion_type.ANGVEL.unit = 'r/s';
motion_type.ACC.unit = 'm/s^2';
motion_type.ANGACC.unit = 'r/s^2';
motion_type.GYRO.unit = 'rad/s';
motion_type.ACCEL.unit = 'm/s^2';
motion_type.ANGACC.unit = 'rad/s^2';
motion_type.MAGN.unit = 'fm';
motion_type.JNTANG.unit = 'r';
motion_type.JNTANG.unit = 'rad';
motion_type.LATENCY.unit = 'seconds';

end

% BIDS-motion Keywords list (according to BEP 029, Jan 2023)
componentKeywords = {'x' ,'y', 'z', 'quat_x', 'quat_y', 'quat_z', 'quat_w', 'n/a'};
channelTypeKeywords = {'ACCEL', 'ANGACC', 'GYRO', 'JNTANG', 'LATENCY', 'MAGN', 'MISC', 'ORNT', 'POS', 'TIME', 'VEL'};

% physio-related fields
%--------------------------------------------------------------------------
if importPhys
Expand Down Expand Up @@ -925,6 +934,10 @@ function bemobil_xdf2bids(config, varargin)
end
end

if isempty(streamInds)
continue;
end

% select stream configuration
streamsConfig = config.motion.streams(streamConfigInds);

Expand Down Expand Up @@ -952,6 +965,8 @@ function bemobil_xdf2bids(config, varargin)
disp(['Using custom unit ' config.motion.(motionChanType).unit ' for type ' motionChanType])
elseif isfield(motion_type, motionChanType)
motion.hdr.chanunit{ci} = motion_type.(motionChanType).unit;
else
motion.hdr.chanunit{ci} = 'n/a';
end

splitlabel = regexp(motion.hdr.label{ci}, '_', 'split');
Expand All @@ -962,7 +977,7 @@ function bemobil_xdf2bids(config, varargin)

% assign object names and anatomical positions
for iN = 1:numel(rb_names)
if contains(lower(motion.hdr.label{ci}),lower(rb_names{iN}))
if contains(lower(motion.hdr.label{ci}),lower(rb_names{iN})) && ~contains(lower(motion.hdr.label{ci}), 'latency')
motioncfg.channels.tracked_point{end+1} = rb_names{iN};
motioncfg.channels.placement{end+1} = rb_anat{iN};
end
Expand All @@ -974,8 +989,20 @@ function bemobil_xdf2bids(config, varargin)
motioncfg.channels.placement{end+1} = 'n/a';
end

motioncfg.channels.component{end+1} = splitlabel{end};
if strcmp(splitlabel{end-1}, 'quat')
motioncfg.channels.component{end+1} = ['quat_' splitlabel{end}];
else
motioncfg.channels.component{end+1} = splitlabel{end};
end

% make sure that the type and component keywords conform to BIDS
if ~any(strcmp(motioncfg.channels.type{end},channelTypeKeywords))
motioncfg.channels.type{end} = 'MISC';
end

if ~any(strcmp(motioncfg.channels.component{end},componentKeywords))
motioncfg.channels.component{end} = 'n/a';
end
end

% tracking system-specific information
Expand Down
6 changes: 3 additions & 3 deletions pop/pop_importbids_mobi.m
Original file line number Diff line number Diff line change
Expand Up @@ -646,8 +646,8 @@ case lower(specified_ref)
bids.otherdata = [];

% extract data type
splitName = regexp(otherFileRaw,'_','split');
datatype = splitName{end}(1:end-4);
splitName = regexp(otherFileRaw,'_','split');
datatype = splitName{end}(1:end-4);

joinedName = join(splitName, '_');
otherFileJSON = [joinedName{1}(1:end-3) 'json'];
Expand Down Expand Up @@ -760,7 +760,7 @@ case lower(specified_ref)
useLatency = ~isempty(latencyInd);
if useLatency
latencyHeader = channelData{latencyInd,strcmp(channelData(1,:),'name')};
latencyRowInData = find(strcmp(headers, latencyHeader));
latencyRowInData = latencyInd-1;
end
elseif strcmp(datatype,'physio')
% check if the tracking system comes with latency
Expand Down