Skip to content

Commit

Permalink
Merge pull request #24 from VisLab/main
Browse files Browse the repository at this point in the history
Added a generate sidecar method to the hedtools object
  • Loading branch information
VisLab authored Jul 24, 2024
2 parents ed4b2a7 + eb6662e commit ef0f01d
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 5 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/links.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: Lychee link checker

on:
workflow_dispatch:

permissions:
contents: read

jobs:
codespell:
name: Lychee link checker
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Link Checker
id: lychee
uses: lycheeverse/lychee-action@v1
with:
# Check all markdown, html and reStructuredText files in repo (default)
args: --base . --verbose --no-progress './**/*.md' './**/*.html' './**/*.rst'
6 changes: 6 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- This file lists all links/regex to be ignored by lychee in the link checker -->
https://www.sciencedirect.com/science/article/pii/S1053811921010387
https://www.sciencedirect.com/science/article/pii/S0010945221001106
https://www.sciencedirect.com/science/article/pii/S1388245717309069
(_anchor|-anchor)

3 changes: 3 additions & 0 deletions hedmat/hedtools/HedTools.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
end

methods (Abstract)

sidecar = generateSidecar(obj, eventsIn, valueColumns, skipColumns);

annotations = getHedAnnotations(obj, eventsIn, sidecar, ...
removeTypesOn, includeContext, replaceDefs);

Expand Down
48 changes: 47 additions & 1 deletion hedmat/hedtools/HedToolsPython.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@
obj.resetHedVersion(version)
end

function sidecar = generateSidecar(obj, eventsIn, valueColumns, ...
skipColumns)
% Return a sidecar string based on an events data.
%
% Parameters:
% eventsIn - char, string or rectified struct.
% valueColumns - cell array of char giving names of
% columns to be treated as value columns.
% skipColumns - cell array of char giving names of
% columns to be skipped.
%
% Returns:
% sidecar - char array with sidecar.
%
tabSum = HedToolsPython.getTabularSummary(valueColumns, ...
skipColumns);
events = HedToolsPython.getTabularObj(eventsIn, py.None);
tabSum.update(events.dataframe);
hedDict = tabSum.extract_sidecar_template();
sidecar = char(py.json.dumps(hedDict));
end

function annotations = getHedAnnotations(obj, events, ...
sidecar, varargin)
% Return a cell array of HED annotations of same length as events.
Expand Down Expand Up @@ -308,7 +330,7 @@
if ischar(query)
try
queryHandler = mmod.QueryHandler(query);
catch MException
catch ME
throw (MException(...
'HedToolsPythonGetHedQueryHandler:InvalidQuery', ...
'Query: %s cannot be parsed.', query));
Expand Down Expand Up @@ -378,5 +400,29 @@
end
end

function tabularSum = getTabularSummary(valueColumns, skipColumns)
% Returns a HED TabularSummary object.
%
% Parameters:
% valueColumns - cell array with value column names.
% skipColumns - cell array with skip column names
%
% Returns:
% tabularSum - TabularSummary object.
%
% Throws: HedFileError if valueColumns and skipColumns
% overlap.
%

try
valueList = py.list(valueColumns);
skipList = py.list(skipColumns);
tabularSum = py.hed.tools.analysis.tabular_summary.TabularSummary(valueList, skipList);
catch ME
throw(MException('HedToolsPythonGetTabularSummary:ColumnNameOverlap', ...
'valueColumns and skipColumns can not overlap'))
end
end

end
end
33 changes: 33 additions & 0 deletions hedmat/hedtools/HedToolsService.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,39 @@
obj.resetSessionInfo(host);
end

function sidecar = generateSidecar(obj, eventsIn, valueColumns, ...
skipColumns)
% Return a sidecar string based on an events data.
%
% Parameters:
% eventsIn - char, string or rectified struct.
% valueColumns - cell array of char giving names of
% columns to be treated as value columns.
% skipColumns - cell array of char giving names of
% columns to be skipped.
%
% Returns:
% sidecar - char array with sidecar.
%
request = obj.getRequestTemplate();
request.service = 'events_generate_sidecar';
request.events_string = HedTools.formatEvents(eventsIn);
request.columns_skip = skipColumns;
request.columns_value = valueColumns;
response = webwrite(obj.ServicesUrl, request, obj.WebOptions);
response = jsondecode(response);
error_msg = HedToolsService.getResponseError(response);
if error_msg
throw(MException(...
'HedToolsServiceGetHedAnnotations:ServiceError', error_msg));
elseif strcmpi(response.results.msg_category, 'warning')
throw(MException( ...
'HedToolsServiceGenerateSidecar:InvalidData', ...
"Input errors:\n" + response.results.data));
end
sidecar = response.results.data;
end

function annotations = getHedAnnotations(obj, eventsIn, ...
sidecar, varargin)
% Return a cell array of HED annotations of same length as events.
Expand Down
38 changes: 38 additions & 0 deletions tests/test_hed_tools/TestHedToolsPython.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ function setUp(testCase)

methods (Test)

function testGenerateSidecar(testCase)
% Valid char events should not have errors or warnings
eventsChar = fileread(testCase.goodEventsPath);
testCase.verifyTrue(ischar(eventsChar))

% no types, no context, no replace
sidecar = testCase.hed.generateSidecar(eventsChar, ...
{'trial', 'rep_lag', 'stim_file'}, ...
{'onset', 'duration', 'sample'});
testCase.verifyTrue(ischar(sidecar));
sideStruct = jsondecode(sidecar);
testCase.verifyFalse(isfield(sideStruct, 'onset'));
testCase.verifyTrue(isstruct(sideStruct.event_type.HED));
testCase.verifyTrue(ischar(sideStruct.trial.HED));
end

function testGetHedAnnotations(testCase)
% Valid char events should not have errors or warnings
sidecarChar = fileread(testCase.goodSidecarPath);
Expand Down Expand Up @@ -557,6 +573,28 @@ function testGetTabularInputNoSidecar(testCase)
testCase.hmod.TabularInput));
end

function testGetTabularSummary(testCase)
% Test tabular summary with or without column specifications
valueColumns = {'trial', 'rep_lag', 'stim_file'};
skipColumns = {'onset', 'duration', 'sample'};
tabularSum = HedToolsPython.getTabularSummary(...
valueColumns, skipColumns);
testCase.assertTrue(py.isinstance(tabularSum, ...
testCase.hmod.tools.TabularSummary));
tabularSum1 = HedToolsPython.getTabularSummary({}, {});
testCase.assertTrue(py.isinstance(tabularSum1, ...
testCase.hmod.tools.TabularSummary));
end

function testGetTabularSummaryInvalid(testCase)
% Test tabular input with no sidecar
valueColumns = {'onset', 'rep_lag', 'stim_file'};
skipColumns = {'onset', 'duration', 'sample'};
testCase.verifyError(@() HedToolsPython.getTabularSummary(...
valueColumns, skipColumns), ...
'HedToolsPythonGetTabularSummary:ColumnNameOverlap');
end

function testGetHedFromAnnotations(testCase)
% Normal array
an1 = {'Sensory-event', 'Red'};
Expand Down
24 changes: 20 additions & 4 deletions tests/test_hed_tools/TestHedToolsService.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
methods (TestClassSetup)
function setUp(testCase)
testCase.hed = ...
HedToolsService('8.2.0', 'https://hedtools.org/hed');
HedToolsService('8.3.0', 'https://hedtools.org/hed');
% testCase.hed = ...
% HedToolsService('8.2.0', 'https://hedtools.org/hed_dev');
% HedToolsService('8.3.0', 'https://hedtools.org/hed_dev');
% testCase.hed = ...
% HedToolsService('8.2.0', 'http://127.0.0.1:5000');
% HedToolsService('8.3.0', 'http://127.0.0.1:5000');
[curDir, ~, ~] = fileparts(mfilename("fullpath"));
dataPath = fullfile(curDir, filesep, '..', filesep, '..', ...
filesep, 'data', filesep);
Expand Down Expand Up @@ -52,6 +52,22 @@ function testCreateConnection(testCase)
testCase.verifyTrue(isa(hed1, 'HedToolsService'));
end

function testGenerateSidecar(testCase)
% Valid char events should not have errors or warnings
eventsChar = fileread(testCase.goodEventsPath);
testCase.verifyTrue(ischar(eventsChar))

% no types, no context, no replace
sidecar = testCase.hed.generateSidecar(eventsChar, ...
{'trial', 'rep_lag', 'stim_file'}, ...
{'onset', 'duration', 'sample'});
testCase.verifyTrue(ischar(sidecar));
sideStruct = jsondecode(sidecar);
testCase.verifyFalse(isfield(sideStruct, 'onset'));
testCase.verifyTrue(isstruct(sideStruct.event_type.HED));
testCase.verifyTrue(ischar(sideStruct.trial.HED));
end

function testGetHedAnnotations(testCase)
% Valid char events should not have errors or warnings
sidecarChar = fileread(testCase.goodSidecarPath);
Expand Down Expand Up @@ -110,7 +126,7 @@ function testSearchHed(testCase)
factors1 = testCase.hed.searchHed(annotations, queries1);
testCase.verifyEqual(length(factors1), 3);
testCase.verifyEqual(factors1(2), 1);

% Test 2 queries on 3 strings.
queries2 = {'Sensory-event', 'Red'};
factors2 = testCase.hed.searchHed(annotations, queries2);
Expand Down

0 comments on commit ef0f01d

Please sign in to comment.