From c3678400001642db7978ed9a1d4f27b73803e5fe Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 1 Mar 2022 15:45:14 +0100 Subject: [PATCH 1/2] save SoftwarePresentation details if present --- runTests.m | 5 +- src/createDataDictionary.m | 12 +- src/createJson.m | 5 +- templates/.bidsignore | 19 +- tests/manualTests/test_makeRawDataset.m | 231 +++++++++++++----------- 5 files changed, 145 insertions(+), 127 deletions(-) diff --git a/runTests.m b/runTests.m index 756b997d..fdbb821c 100644 --- a/runTests.m +++ b/runTests.m @@ -24,8 +24,9 @@ function runTests() folderToCover = fullfile(pwd, 'src'); testFolder = fullfile(pwd, 'tests'); - success = moxunit_runtests( ... - testFolder, ... + addpath(fullfile(testFolder, 'utils')); + + success = moxunit_runtests(testFolder, ... '-verbose', '-recursive', '-with_coverage', ... '-cover', folderToCover, ... '-cover_xml_file', 'coverage.xml', ... diff --git a/src/createDataDictionary.m b/src/createDataDictionary.m index 7fae0e6a..7a668154 100644 --- a/src/createDataDictionary.m +++ b/src/createDataDictionary.m @@ -17,21 +17,23 @@ function createDataDictionary(cfg, logFile) fileName = strrep(logFile(1).filename, '.tsv', '.json'); fullFilename = getFullFilename(fileName, cfg); - jsonContent = setJsonContent(logFile); + jsonContent = setJsonContent(logFile, cfg); - opts.Indent = ' '; - - bids.util.jsonencode(fullFilename, jsonContent, opts); + bids.util.jsonencode(fullFilename, jsonContent); end -function jsonContent = setJsonContent(logFile) +function jsonContent = setJsonContent(logFile, cfg) % regular _events file: add default _event file fields to the json content if ~isfield(logFile, 'isStim') || isempty(logFile.isStim) || ~logFile.isStim jsonContent = logFile.columns; + if isfield(cfg, 'StimulusPresentation') + jsonContent.StimulusPresentation = cfg.StimulusPresentation; + end + % _stim file: write stim-specific fields to the json content elseif logFile.isStim diff --git a/src/createJson.m b/src/createJson.m index ddcd12ce..a86deb3a 100644 --- a/src/createJson.m +++ b/src/createJson.m @@ -13,10 +13,12 @@ function createJson(varargin) % % :param cfg: Configuration. See ``checkCFG()``. % :type cfg: structure + % % :param modality: can be any of the following ``'beh'``, ``'func'``, ``'eeg'``, % ``'ieeg'``, ``'meg'``) to specify which JSON to save. If it is not % provided it will read from ``cfg.fileName.modality``. % :type modality: string + % % :param extraInfo: contains information in the JSON file. Beware % that the BIDS validator is pretty strict on what information can % go in a JSON so this can be useful to store additional information @@ -55,8 +57,7 @@ function createJson(varargin) end %% save - opts.Indent = ' '; - bids.util.jsonencode(fullFilename, jsonContent, opts); + bids.util.jsonencode(fullFilename, jsonContent); end diff --git a/templates/.bidsignore b/templates/.bidsignore index 7e9b244c..f7923a3d 100644 --- a/templates/.bidsignore +++ b/templates/.bidsignore @@ -1,11 +1,12 @@ -*_beh.* -*_eeg.* -*_ieeg.* -*_meg.* +*_eeg.json +*_eeg.edf -# *_beh.tsv -# *_eeg.edf -# *_ieeg.edf -# *_meg.fif +*_ieeg.json +*_ieeg.edf -# *_bold.nii.gz +*_meg.json +*_meg.fif + +*_beh.tsv + +# *_bold.nii.gz \ No newline at end of file diff --git a/tests/manualTests/test_makeRawDataset.m b/tests/manualTests/test_makeRawDataset.m index 1b799350..06811287 100644 --- a/tests/manualTests/test_makeRawDataset.m +++ b/tests/manualTests/test_makeRawDataset.m @@ -2,14 +2,12 @@ function test_makeRawDataset() - outputDir = fullfile(fileparts(mfilename('fullpath')), 'output'); - - if isdir(outputDir) - rmdir(outputDir, 's'); + if isdir(outputDir()) + rmdir(outputDir(), 's'); end %% set up - cfg.dir.output = outputDir; + cfg = baseCfg(); cfg.bids.datasetDescription.Name = 'dummy'; cfg.bids.datasetDescription.BIDSVersion = '1.0.0'; @@ -20,93 +18,24 @@ function test_makeRawDataset() %% MRI task data cfg.mri.repetitionTime = 1.56; - cfg.subject.subjectNb = 1; - cfg.subject.runNb = 1; - cfg.task.name = 'testtask'; - cfg.task.instructions = 'do this'; - - cfg.verbosity = 0; cfg = createFilename(cfg); - logFile.extraColumns.Speed.length = 1; - logFile.extraColumns.LHL24.length = 3; - logFile.extraColumns.is_Fixation.length = 1; - - logFile = saveEventsFile('init', cfg, logFile); - extraInfo = struct('extraInfo', struct('nestedExtraInfo', 'something extra')); createJson(cfg, extraInfo); - createDatasetDescription(cfg); - - % create the events file and header - logFile = saveEventsFile('open', cfg, logFile); - - % ROW 2: normal events : all info is there - logFile(1, 1).onset = 2; - logFile(end, 1).trial_type = 'MotionUp'; - logFile(end, 1).duration = 3; - logFile(end, 1).Speed = 2; - logFile(end, 1).is_Fixation = true; - logFile(end, 1).LHL24 = 1:3; - - % ROW 3: missing info (speed, LHL24) - logFile(1, 1).onset = 3; - logFile(end, 1).trial_type = 'static'; - logFile(end, 1).duration = 4; - logFile(end, 1).is_Fixation = false; - - % ROW 4: missing info (duration is missing and speed is empty) - logFile(2, 1).onset = 4; - logFile(end, 1).trial_type = 'BLUES'; - logFile(end, 1).Speed = []; - logFile(end, 1).is_Fixation = true; - logFile(end, 1).LHL24 = rand(1, 3); - - % ROW 5: missing info (array is not the right size) - logFile(5, 1).onset = 5; - logFile(end, 1).trial_type = 'jazz'; - logFile(end, 1).duration = 3; - logFile(end, 1).LHL24 = rand(1, 2); + createTsvWithContent(cfg); - saveEventsFile('save', cfg, logFile); - - % close the file - saveEventsFile('close', cfg, logFile); - - % add dummy stim data - stimLogFile.extraColumns.Speed.length = 1; - stimLogFile.extraColumns.LHL24.length = 1; - stimLogFile.extraColumns.is_Fixation.length = 1; - - stimLogFile.SamplingFrequency = cfg.mri.repetitionTime; - stimLogFile.StartTime = 0; - - stimLogFile = saveEventsFile('init_stim', cfg, stimLogFile); - stimLogFile = saveEventsFile('open', cfg, stimLogFile); - for i = 1:100 - stimLogFile(i, 1).onset = cfg.mri.repetitionTime * i; - stimLogFile(i, 1).trial_type = 'test'; - stimLogFile(i, 1).duration = 1; - stimLogFile(i, 1).Speed = rand(1); - stimLogFile(i, 1).is_Fixation = rand > 0.5; - stimLogFile(i, 1).LHL24 = randn(); - end - saveEventsFile('save', cfg, stimLogFile); - saveEventsFile('close', cfg, stimLogFile); + createDatasetDescription(cfg); %% MRI bold rest data and fancy suffixes clear cfg; - cfg.dir.output = outputDir; + cfg = baseCfg(); cfg.testingDevice = 'mri'; - cfg.subject.subjectNb = 1; - cfg.subject.runNb = 1; - % deal with MRI suffixes cfg.suffix.reconstruction = 'fast recon'; cfg.suffix.contrastEnhancement = 'test'; @@ -126,15 +55,11 @@ function test_makeRawDataset() %% EEG data and fancy suffixes clear cfg; - cfg.dir.output = outputDir; + cfg = baseCfg(); cfg.testingDevice = 'eeg'; - cfg.subject.subjectNb = 1; - cfg.subject.runNb = 1; - cfg.task.name = 'target practice'; - cfg.task.instructions = 'do this'; cfg.bids.eeg.EEGReference = 'Cz'; cfg.bids.eeg.SamplingFrequency = 2400; @@ -146,18 +71,16 @@ function test_makeRawDataset() extraInfo = struct('extraInfo', struct('nestedExtraInfo', 'something extra')); createJson(cfg, extraInfo); + createTsvWithContent(cfg); + %% iEEG data and fancy suffixes clear cfg; - cfg.dir.output = outputDir; + cfg = baseCfg(); cfg.testingDevice = 'ieeg'; - cfg.subject.subjectNb = 1; - cfg.subject.runNb = 1; - cfg.task.name = 'implanted target practice'; - cfg.task.instructions = 'do this'; cfg.bids.ieeg.iEEGReference = 'Cz'; cfg.bids.ieeg.SamplingFrequency = 2400; @@ -169,18 +92,16 @@ function test_makeRawDataset() extraInfo = struct('extraInfo', struct('nestedExtraInfo', 'something extra')); createJson(cfg, extraInfo); + createTsvWithContent(cfg); + %% MEG data and fancy suffixes clear cfg; - cfg.dir.output = outputDir; + cfg = baseCfg(); cfg.testingDevice = 'meg'; - cfg.subject.subjectNb = 1; - cfg.subject.runNb = 1; - cfg.task.name = 'magnetic target practice'; - cfg.task.instructions = 'do this'; cfg.bids.meg.SamplingFrequency = 2400; cfg.bids.meg.PowerLineFrequency = 60; @@ -194,24 +115,24 @@ function test_makeRawDataset() extraInfo = struct('extraInfo', struct('nestedExtraInfo', 'something extra')); createJson(cfg, extraInfo); + createTsvWithContent(cfg); + %% beh data and fancy suffixes clear cfg; - cfg.dir.output = outputDir; + cfg = baseCfg(); cfg.testingDevice = 'pc'; - cfg.subject.subjectNb = 1; - cfg.subject.runNb = 1; - cfg.task.name = 'easy target practice'; - cfg.task.instructions = 'do this'; cfg = createFilename(cfg); extraInfo = struct('extraInfo', struct('nestedExtraInfo', 'something extra')); createJson(cfg, extraInfo); + createTsvWithContent(cfg); + %% add dummy data templateFolder = fullfile(fileparts(mfilename('fullpath')), '..', '..', 'templates'); @@ -222,8 +143,7 @@ function test_makeRawDataset() boldFilename = [basename '_task-testtask_run-001_date-' cfg.fileName.date '_bold.nii.gz']; - copyfile( ... - fullfile(templateFolder, 'dummyData.nii.gz'), ... + copyfile(fullfile(templateFolder, 'dummyData.nii.gz'), ... fullfile(funcDir, boldFilename)); boldFilename = [basename '_task-rest', ... @@ -247,27 +167,120 @@ function test_makeRawDataset() behFilename = [basename '_task-easyTargetPractice_run-001_date-', ... cfg.fileName.date '_beh.tsv']; - copyfile( ... - fullfile(templateFolder, 'dummyData.nii.gz'), ... + copyfile(fullfile(templateFolder, 'dummyData.nii.gz'), ... fullfile(eegDir, eegFilename)); - copyfile( ... - fullfile(templateFolder, 'dummyData.nii.gz'), ... + copyfile(fullfile(templateFolder, 'dummyData.nii.gz'), ... fullfile(megDir, megFilename)); - copyfile( ... - fullfile(templateFolder, 'dummyData.nii.gz'), ... + copyfile(fullfile(templateFolder, 'dummyData.nii.gz'), ... fullfile(ieegDir, ieegFilename)); - copyfile( ... - fullfile(templateFolder, 'dummyData.nii.gz'), ... + copyfile(fullfile(templateFolder, 'dummyData.nii.gz'), ... fullfile(behDir, behFilename)); %% actually do the conversion of the source data thus created clear; - outputDir = fullfile(fileparts(mfilename('fullpath')), 'output'); - cfg.dir.output = outputDir; + cfg.dir.output = outputDir(); convertSourceToRaw(cfg); end + +function createTsvWithContent(cfg) + + logFile.extraColumns.Speed.length = 1; + logFile.extraColumns.LHL24.length = 3; + logFile.extraColumns.is_Fixation.length = 1; + + logFile = saveEventsFile('init', cfg, logFile); + + % create the events file and header + logFile = saveEventsFile('open', cfg, logFile); + + % ROW 2: normal events : all info is there + logFile(1, 1).onset = 2; + logFile(end, 1).trial_type = 'MotionUp'; + logFile(end, 1).duration = 3; + logFile(end, 1).Speed = 2; + logFile(end, 1).is_Fixation = true; + logFile(end, 1).LHL24 = 1:3; + + % ROW 3: missing info (speed, LHL24) + logFile(1, 1).onset = 3; + logFile(end, 1).trial_type = 'static'; + logFile(end, 1).duration = 4; + logFile(end, 1).is_Fixation = false; + + % ROW 4: missing info (duration is missing and speed is empty) + logFile(2, 1).onset = 4; + logFile(end, 1).trial_type = 'BLUES'; + logFile(end, 1).Speed = []; + logFile(end, 1).is_Fixation = true; + logFile(end, 1).LHL24 = rand(1, 3); + + % ROW 5: missing info (array is not the right size) + logFile(5, 1).onset = 5; + logFile(end, 1).trial_type = 'jazz'; + logFile(end, 1).duration = 3; + logFile(end, 1).LHL24 = rand(1, 2); + + saveEventsFile('save', cfg, logFile); + + % close the file + saveEventsFile('close', cfg, logFile); + + createJson(cfg); + + % add dummy stim data + SamplingFrequency = 100; + + stimLogFile.extraColumns.Speed.length = 1; + stimLogFile.extraColumns.LHL24.length = 1; + stimLogFile.extraColumns.is_Fixation.length = 1; + + stimLogFile.SamplingFrequency = SamplingFrequency; + stimLogFile.StartTime = 0; + + stimLogFile = saveEventsFile('init_stim', cfg, stimLogFile); + stimLogFile = saveEventsFile('open', cfg, stimLogFile); + for i = 1:100 + stimLogFile(i, 1).onset = SamplingFrequency * i; + stimLogFile(i, 1).trial_type = 'test'; + stimLogFile(i, 1).duration = 1; + stimLogFile(i, 1).Speed = rand(1); + stimLogFile(i, 1).is_Fixation = rand > 0.5; + stimLogFile(i, 1).LHL24 = randn(); + end + saveEventsFile('save', cfg, stimLogFile); + saveEventsFile('close', cfg, stimLogFile); + + createJson(cfg); + +end + +function value = outputDir() + value = fullfile(fileparts(mfilename('fullpath')), 'output'); +end + +function value = baseCfg() + value.dir.output = outputDir; + value.StimulusPresentation = stimulusPresentation(); + value.subject.subjectNb = 1; + value.subject.runNb = 1; + value.task.instructions = 'do this'; + value.verbosity = 0; +end + +function value = stimulusPresentation() + + value.OperatingSystem = computer(); + value.SoftwareRRID = 'SCR_002881'; + value.Code = 'https://github.com/cpp-lln-lab/TODO.git'; + value.SoftwareVersion = sprintf('%i.%i.%i', 3, 0, 18); + + runsOn = 'Matlab - '; + runsOn = [runsOn version()]; + value.SoftwareName = ['Psychtoolbox on ' runsOn]; + +end From 069bd9293900e87e71aca06b222b3c664ad0542b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 14:47:03 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- templates/.bidsignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/.bidsignore b/templates/.bidsignore index f7923a3d..85dba60b 100644 --- a/templates/.bidsignore +++ b/templates/.bidsignore @@ -9,4 +9,4 @@ *_beh.tsv -# *_bold.nii.gz \ No newline at end of file +# *_bold.nii.gz