Skip to content
Closed
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
82 changes: 82 additions & 0 deletions HED_integration/integrateCTAGGER.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
function [dataStruct, tags] = integrateCTAGGER(dataStruct, varargin)

% Parse input arguments
p = parseArguments(dataStruct, varargin{:});

if ~isempty(p.sidecar) && exist(p.sidecar, 'file')
tags = fileread(p.sidecar); % Read directly from the file
else
% Hardcoded default JSON
tags = ['{' ...
'"onset":{"Description":"Time at which the event occurred, in seconds.","Units":"seconds"},' ...
'"duration":{"Description":"Duration of the event, in seconds.","Units":"seconds"},' ...
'"trial_type":{"Description":"Type of trial.","Levels":{' ...
'"stimulus":"Event indicating stimulus",' ...
'"response":"Event indicating response"' ...
'}}' ...
'}'
];
end

try
[tags, canceled] = useCTAGGER(tags);

if canceled
disp('Tagging process canceled.');
return;
end
catch ME
error('CTAGGER Error: %s', ME.message);
end

dataStruct.tags = tags;
disp('Tagging complete.');
end

function [tags, canceled] = useCTAGGER(tags)
% Wrapper for launching CTAGGER
canceled = false;
[newTags, canceled] = loadCTAGGER(tags);
if ~canceled
tags = newTags;
end
end

function [result, canceled] = loadCTAGGER(json)
% Launch CTAGGER
canceled = false;
notified = false;

try
loader = javaObject('TaggerLoader', json);
catch ME
error('Error initializing CTAGGER: %s', ME.message);
end

timeout = 300; % seconds
tStart = tic;
while ~notified && toc(tStart) < timeout
pause(0.5);
notified = loader.isNotified();
end

if ~notified
error('CTAGGER did not respond within the timeout period.');
end

% Check if tagging was canceled
if loader.isCanceled()
canceled = true;
result = '';
else
result = char(loader.getHEDJson());
end
end

function p = parseArguments(dataStruct, varargin)
parser = inputParser;
parser.addRequired('dataStruct', @(x) isstruct(x));
parser.addParameter('sidecar', '', @ischar);
parser.parse(dataStruct, varargin{:});
p = parser.Results;
end
23 changes: 23 additions & 0 deletions HED_integration/testCTagger.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
% Load the events.json file
eventsJsonFile = 'task-FacePerception_events.json';

if exist(eventsJsonFile, 'file')
disp('Using events.json as input...');
sidecar = eventsJsonFile;
else
sidecar = '';
end

% Load CTagger as BST plugin
bst_plugin('Install', 'ctagger');

%example data structure
dataStruct.events = struct('onset', {0.5, 1.0, 1.5}, ...
'duration', {0.5, 0.5, 0.5}, ...
'trial_type', {'stimulus', 'response', 'stimulus'});

% Integrate CTAGGER
[dataStruct, tags] = integrateCTAGGER(dataStruct, 'sidecar', sidecar);

disp('Updated JSON:');
disp(tags);
117 changes: 117 additions & 0 deletions process_grouphed.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
function varargout = process_group_by_hed(varargin)

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription()
% Description of the process
sProcess.Comment = 'Group Events by HED Tags';
sProcess.Category = 'Custom';
sProcess.SubGroup = 'User';
sProcess.Index = 1002;
sProcess.isSeparator = 1;

% Define the input and output types
sProcess.InputTypes = {'data', 'raw'};
sProcess.OutputTypes = {'data', 'raw'};
sProcess.nInputs = 1;
sProcess.nMinFiles = 1;

% Add options for user configuration
sProcess.options.showOutput.Comment = 'Display grouped events in a message box';
sProcess.options.showOutput.Type = 'checkbox';
sProcess.options.showOutput.Value = 1;
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess)
Comment = sProcess.Comment;
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInput)
% Initialize output file list
OutputFiles = {};

% Check for exactly one input file
if length(sInput) ~= 1
bst_report('Error', sProcess, sInput, 'This process requires exactly one input file.');
return;
end

% Load the input data structure
DataStruct = in_bst_data(sInput.FileName);
if ~isfield(DataStruct, 'Events') || isempty(DataStruct.Events)
bst_report('Error', sProcess, sInput, 'No events found in the data.');
return;
end

% Extract events
events = DataStruct.Events;

% Group events by HED tags
try
groupedEvents = groupEventsByHEDTags(events);
catch ME
bst_report('Error', sProcess, sInput, ['Error grouping events: ' ME.message]);
return;
end

% Optionally display grouped events
if sProcess.options.showOutput.Value
DisplayGroupedEvents(groupedEvents);
end

% No output modification needed; return input file
OutputFiles{1} = sInput.FileName;
end

function groupedEvents = groupEventsByHEDTags(events)
% Groups events based on their HED tags
%
% Input:
% events - Array of event structures with HED tags
% Output:
% groupedEvents - Struct where each field is a unique HED tag, and its
% value is an array of event indices

tagMap = containers.Map('KeyType', 'char', 'ValueType', 'any');

for i = 1:length(events)
% Extract the HED tags for this event
if ~isfield(events(i), 'hedTags') || isempty(events(i).hedTags)
continue;
end

% Assume hedTags is a comma-separated string; split into individual tags
hedTags = strsplit(events(i).hedTags, ',');
hedTags = strtrim(hedTags);

% Group by each tag
for j = 1:length(hedTags)
tag = hedTags{j};
if ~isKey(tagMap, tag)
tagMap(tag) = []; % Initialize empty group
end
tagMap(tag) = [tagMap(tag), i]; % Add event index to group
end
end

% Convert map to struct for easier access
groupedEvents = struct();
tagKeys = keys(tagMap);
for k = 1:length(tagKeys)
groupedEvents.(matlab.lang.makeValidName(tagKeys{k})) = tagMap(tagKeys{k});
end
end

function DisplayGroupedEvents(groupedEvents)
% Display grouped events in a message box
msg = 'Grouped Events by HED Tags:\n';
tagNames = fieldnames(groupedEvents);
for i = 1:length(tagNames)
msg = sprintf('%s\n%s: [%s]', msg, tagNames{i}, num2str(groupedEvents.(tagNames{i})));
end
msgbox(msg, 'Grouped Events', 'help');
end
29 changes: 29 additions & 0 deletions toolbox/core/bst_plugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,23 @@
PlugDesc(end).TestFile = 'process_mia_export_db.m';
PlugDesc(end).ExtraMenus = {'Start MIA', 'mia', 'loaded'};

% === EVENTS: CTAGGER ===
PlugDesc(end+1) = GetStruct('ctagger');
PlugDesc(end).Version = 'github-main';
PlugDesc(end).Category = 'Events';
PlugDesc(end).AutoUpdate = 0;
PlugDesc(end).CompiledStatus = 0;
PlugDesc(end).URLzip = 'https://github.com/hed-standard/CTagger/archive/main.zip';
PlugDesc(end).URLinfo = 'https://www.hed-resources.org/en/latest/CTaggerGuiTaggingTool.html';
PlugDesc(end).ReadmeFile = 'README.md';
PlugDesc(end).MinMatlabVer = 803; % 2014a
PlugDesc(end).LoadFolders = {'*'};
PlugDesc(end).LoadedFcn = @Configure;
PlugDesc(end).TestFile = 'CTagger.jar';
PlugDesc(end).DeleteFiles = {'docs', 'gradle', 'src', '.gradle', '.idea' ...
'build.gradle', 'gradle.properties', 'gradlew', 'gradlew.bat', ...
'readthedocs.yml', 'settings.gradle', '.codeclimate.yml', '.gitignore'};

% === FIELDTRIP ===
PlugDesc(end+1) = GetStruct('fieldtrip');
PlugDesc(end).Version = 'latest';
Expand Down Expand Up @@ -862,6 +879,18 @@ function Configure(PlugDesc)
generateCore();
% Restore current directory
cd(curDir);

case 'ctagger'
% Add .jar file to static classpath
if ~exist('TaggerLoader', 'class')
jarList = dir(bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, 'CTagger.jar'));
jarPath = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, jarList(1).name);
disp(['BST> Adding to Java classpath: ' jarPath]);
warning off
javaaddpathstatic(jarPath);
javaaddpath(jarPath);
warning on
end
end
end

Expand Down