From 88f12a14adb3adc8e11bd592df3ba8f96628810a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 7 Apr 2020 15:16:06 -0400 Subject: [PATCH 001/180] [ENHANC] intializing split utility --- phys2bids/cli/split.py | 60 +++++++++++++++++++++++++++++++++++++++++ phys2bids/split2phys.py | 15 +++++++++++ 2 files changed, 75 insertions(+) create mode 100644 phys2bids/cli/split.py create mode 100644 phys2bids/split2phys.py diff --git a/phys2bids/cli/split.py b/phys2bids/cli/split.py new file mode 100644 index 000000000..c4e3cad20 --- /dev/null +++ b/phys2bids/cli/split.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +Parser for split2phys +""" + +import argparse + +from phys2bids import __version__ + + +def _get_parser(): + """ + Parses command line inputs for this function + + Returns + ------- + parser.parse_args() : argparse dict + + Notes + ----- + # Argument parser follow template provided by RalphyZ. + # https://stackoverflow.com/a/43456577 + """ + parser = argparse.ArgumentParser() + optional = parser._action_groups.pop() + required = parser.add_argument_group('Required Argument:') + required.add_argument('-in', '--input-file', + dest='filename', + type=str, + help='The name of the file containing physiological data, with or ' + 'without extension.', + required=True) + + required.add_argument('-tr_ls', '--tr_list', + dest='tr_list', + type=list, + help='A list containing the TR(s) of the sequences used in the different ' + 'runs contained in the file', + required=True) + + required.add_argument('-ntp_ls', '--numtps_list', + dest='ntp_list', + type=list, + help='A list containing the number of trigger time points in each run', + required=True) + + optional.add_argument('-thr', '--threshold', + dest='thr', + type=float, + help='Threshold to use for trigger detection. ' + 'If "ntp" and "TR" are specified, phys2bids automatically computes ' + 'a threshold to detect the triggers. Use this parameter to set it ' + 'manually', + default=None) + optional.add_argument('-v', '--version', action='version', + version=('%(prog)s ' + __version__)) + + parser._action_groups.append(optional) + + return parser diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py new file mode 100644 index 000000000..678072348 --- /dev/null +++ b/phys2bids/split2phys.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A parallel CLI utility to segment the physiological input file +into multiple runs with padding + +""" + +import os +import logging + +from pathlib import Path + +from phys2bids.cli.split import _get_parser +from phys2bids.physio_obj import BlueprintInput From ee685c21ae7b8b18f0de356cef796ade69c3fc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 7 Apr 2020 17:19:15 -0400 Subject: [PATCH 002/180] [ENHANC] adding elements of the plan --- phys2bids/cli/split.py | 24 +++++++++++++ phys2bids/split2phys.py | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/phys2bids/cli/split.py b/phys2bids/cli/split.py index c4e3cad20..4ddff77e8 100644 --- a/phys2bids/cli/split.py +++ b/phys2bids/cli/split.py @@ -31,6 +31,30 @@ def _get_parser(): 'without extension.', required=True) + optional.add_argument('-info', '--info', + dest='info', + action='store_true', + help='Only output info about the file, don\'t process. ' + 'Default is to process.', + default=False) + + optional.add_argument('-indir', '--input-dir', + dest='indir', + type=str, + help='Folder containing input. ' + 'Default is current folder.', + default='.') + + optional.add_argument('-outdir', '--output-dir', + dest='outdir', + type=str, + help='Folder where output should be placed. ' + 'Default is current folder. ' + 'If \"-heur\" is used, it\'ll become ' + 'the site folder. Requires \"-sub\". ' + 'Optional to specify \"-ses\".', + default='.') + required.add_argument('-tr_ls', '--tr_list', dest='tr_list', type=list, diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 678072348..7a73cbbe4 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -8,8 +8,83 @@ import os import logging +import datetime +#from copy import deepcopy +#from numpy import savetxt from pathlib import Path from phys2bids.cli.split import _get_parser from phys2bids.physio_obj import BlueprintInput + +LGR = logging.getLogger(__name__) + +def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=None): + """ + Parallel workflow of phys2bids + Runs the split parser, does some check on inputs and calls + phys2bids to handle each dictionaries that have been created + based on npt_list and tr_list + """ + outdir = utils.check_input_dir(outdir) + utils.path_exists_or_make_it(outdir) + + # Create logfile name + basename = 'split2phys_' + extension = 'tsv' + isotime = datetime.datetime.now().strftime('%Y-%m-%dT%H%M%S') + logname = os.path.join(outdir, (basename + isotime + '.' + extension)) + + # Set logging format + log_formatter = logging.Formatter( + '%(asctime)s\t%(name)-12s\t%(levelname)-8s\t%(message)s', + datefmt='%Y-%m-%dT%H:%M:%S') + + # Set up logging file and open it for writing + log_handler = logging.FileHandler(logname) + log_handler.setFormatter(log_formatter) + sh = logging.StreamHandler() + + logging.basicConfig(level=logging.INFO, + handlers=[log_handler, sh]) + + version_number = _version.get_versions()['version'] + LGR.info(f'Currently running phys2bids version {version_number}') + LGR.info(f'Input file is {filename}') + + # Check options to make them internally coherent pt. II + # #!# This can probably be done while parsing? + indir = utils.check_input_dir(indir) + filename, ftype = utils.check_input_type(filename, + indir) + + infile = os.path.join(indir, filename) + utils.check_file_exists(infile) + + # Read file! + if ftype == 'acq': + from phys2bids.interfaces.acq import populate_phys_input + elif ftype == 'txt': + from phys2bids.interfaces.txt import populate_phys_input + else: + # #!# We should add a logger here. + raise NotImplementedError('Currently unsupported file type.') + + # Check equivalence of list_ntp and list_tr + if list_tr.size[0] = 1: + list_tr = list_tr * np.ones(list_ntp.size) + + # TODO : sum(ntp_list) is equivalent to num_timepoints_found + BlueprintInput.check_trigger_amount() + + # TODO : initialize dictionaries for which to call phys2bids() + + + +def _main(argv=None): + options = _get_parser().parse_args(argv) + split2phys(**vars(options)) + + +if __name__ == '__main__': + _main() From 3215022d63d788e27774ff978a68fec9c5f54306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 7 Apr 2020 21:48:36 -0400 Subject: [PATCH 003/180] ephemeral issue_36.md added in root for planif. Linter Revisions --- issue_36.md | 42 +++++++++++++++++++++++++++++++++++++++++ phys2bids/cli/split.py | 2 +- phys2bids/split2phys.py | 33 +++++++++++++++++++++----------- 3 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 issue_36.md diff --git a/issue_36.md b/issue_36.md new file mode 100644 index 000000000..f7071a7a0 --- /dev/null +++ b/issue_36.md @@ -0,0 +1,42 @@ +# Segment recordings by runs + +#36 + +## 1. Parser +Create argument parser for new command ; based on `run.py` + + in : `cli/split.py` + arg : `-ntp` and `-tr` + +### 1.1. Copy elements from main parser +`run.py` to `split.py` +Integrate arguments : -in, -info, -indir, -outdir, -thr, -v +***Do we need anything else ?*** + +### 1.2. Adapt +We have to adapt types for : `-ntp` (int --> list) and `-tr` (float --> list) +NOTES: if the TR parameter of a sequence is the same in different runs, just pad it with the same value + +``` +if list_tr.size[0] = 1: + list_tr = list_tr * np.ones(list_ntp.size) +``` + +## 2. split2phys function +Create new file for the splitting utility and integrate `physio_obj` functions + + in : `/phys2bids/split2phys.py` + +### 2.1. Verify num_timepoints_found and sum(ntp_list) are equivalent +Pad tr list so that it's equivalent to the number of runs contained in ntp_list + +**To consider eventually**: maybe, the user doesn't know how many runs are contained in the file but knows (1) the usual length of a single run, (2) the sessions has only 1 sequence type, i.e. the same TR. So both `list_ntp.size[0]`and `list_tr.size[0]` could be `1` eventhough the file has multiple runs. + + from : `/phys_obj.py`, `BlueprintInput()` + fn : `num_timepoints_found` and `check_trigger_amount` + +``` +BlueprintInput.check_trigger_amount(sum(list_ntp), tr=1) +``` +### 2.2. Find start-end indexes for each run in list +Initialize dictionaries from which to define start and end indexes of timeseries. Call phys2bids diff --git a/phys2bids/cli/split.py b/phys2bids/cli/split.py index 4ddff77e8..8030c5a78 100644 --- a/phys2bids/cli/split.py +++ b/phys2bids/cli/split.py @@ -68,7 +68,7 @@ def _get_parser(): help='A list containing the number of trigger time points in each run', required=True) - optional.add_argument('-thr', '--threshold', + required.add_argument('-thr', '--threshold', dest='thr', type=float, help='Threshold to use for trigger detection. ' diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 7a73cbbe4..2a8407bfb 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -A parallel CLI utility to segment the physiological input file -into multiple runs with padding + +A parallel CLI utility to segment the physiological input files. + +Cuts the physiological recording files into multiple runs +with padding at start and end """ @@ -10,21 +13,29 @@ import logging import datetime -#from copy import deepcopy -#from numpy import savetxt -from pathlib import Path - +# from copy import deepcopy +from numpy import ones +# from pathlib import Path +from phys2bids import utils from phys2bids.cli.split import _get_parser from phys2bids.physio_obj import BlueprintInput LGR = logging.getLogger(__name__) + def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=None): """ - Parallel workflow of phys2bids + + Parallel workflow of phys2bids. + Runs the split parser, does some check on inputs and calls phys2bids to handle each dictionaries that have been created based on npt_list and tr_list + + Arguments : + + Returns : + ... """ outdir = utils.check_input_dir(outdir) utils.path_exists_or_make_it(outdir) @@ -46,7 +57,7 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N sh = logging.StreamHandler() logging.basicConfig(level=logging.INFO, - handlers=[log_handler, sh]) + handlers=[log_handler, sh]) version_number = _version.get_versions()['version'] LGR.info(f'Currently running phys2bids version {version_number}') @@ -71,8 +82,9 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N raise NotImplementedError('Currently unsupported file type.') # Check equivalence of list_ntp and list_tr - if list_tr.size[0] = 1: - list_tr = list_tr * np.ones(list_ntp.size) + + if list_tr.size[0] == 1: + list_tr = list_tr * ones(list_ntp.size) # TODO : sum(ntp_list) is equivalent to num_timepoints_found BlueprintInput.check_trigger_amount() @@ -80,7 +92,6 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N # TODO : initialize dictionaries for which to call phys2bids() - def _main(argv=None): options = _get_parser().parse_args(argv) split2phys(**vars(options)) From e494d3e85e6374a4b3094e04f8d5ddf6d2bff20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 8 Apr 2020 15:06:41 -0400 Subject: [PATCH 004/180] updating step two, specified ways2integrate PR and possible names --- issue_36.md | 27 ++++++++++++++++++++++++++- phys2bids/cli/split.py | 10 +++++++--- phys2bids/split2phys.py | 40 ++++++++++++++++++++++++++++++++-------- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/issue_36.md b/issue_36.md index f7071a7a0..cac480a31 100644 --- a/issue_36.md +++ b/issue_36.md @@ -1,6 +1,13 @@ # Segment recordings by runs #36 +Ideas for name : split4phys, obj2split, phys4bids, split4bids, rec2run, phys2runs, ses2run, 4run2split + +**Ways to integrate PR** +1. Another Repo : create a physiopy repo that is dedicated to splitting physiological recordings concurrent to neuroimaging ; least interesting option, aim is too specific. +2. Integrate phys2bids in [name of fn]: workflow on its own that calls phys2bids at the end of script for each segments +3. Independant utility : function gets called by phys2bids if list are detected as phys2bids arguments, no parser. +4. Integrate [name of fn] in phys2bids: keep the parser, have parallel workflows for different outcomes - less easy to integrate but more convenient for users ## 1. Parser Create argument parser for new command ; based on `run.py` @@ -39,4 +46,22 @@ Pad tr list so that it's equivalent to the number of runs contained in ntp_list BlueprintInput.check_trigger_amount(sum(list_ntp), tr=1) ``` ### 2.2. Find start-end indexes for each run in list -Initialize dictionaries from which to define start and end indexes of timeseries. Call phys2bids +Initialize dictionaries from which to define start and end indexes of timeseries. + +``` +init dictionary for BlueprintInputs (dict) + + for i, elem in list_ntp: + BlueprintInput.check_trigger_amount(ntp=elem, tr=list_tr[i]) + start_index = 0 + end_index = {index of spike 0} + {index of elem*list_tr[i]} + dict[‘0’] = BlueprintInput.timeseries[0:end_index+padding, :] + if i == len(list_ntp): + end_index+padding <= number of indexes + if it’s not: padding= number of indexes-end_index + BlueprintInput = BlueprintInput.timeseries[end_index+padding; , :] + +make dict exportable +``` +## 3. Call phys2bids +Call phys2bids for each of them diff --git a/phys2bids/cli/split.py b/phys2bids/cli/split.py index 8030c5a78..8e407d8d6 100644 --- a/phys2bids/cli/split.py +++ b/phys2bids/cli/split.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """ -Parser for split2phys +Parser for split2phys. + +A parallel CLI script to segment multiple runs in physiological recording """ import argparse @@ -10,7 +12,8 @@ def _get_parser(): """ - Parses command line inputs for this function + + Parse command line inputs for this function. Returns ------- @@ -58,7 +61,8 @@ def _get_parser(): required.add_argument('-tr_ls', '--tr_list', dest='tr_list', type=list, - help='A list containing the TR(s) of the sequences used in the different ' + help='A list containing the TR(s) of the sequences' + ' used in the different ' 'runs contained in the file', required=True) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 2a8407bfb..73049f894 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -32,9 +32,11 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N phys2bids to handle each dictionaries that have been created based on npt_list and tr_list - Arguments : + Arguments + --------- - Returns : + Returns + -------- ... """ outdir = utils.check_input_dir(outdir) @@ -81,18 +83,40 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N # #!# We should add a logger here. raise NotImplementedError('Currently unsupported file type.') - # Check equivalence of list_ntp and list_tr + # Check equivalence of list_ntp and list_tr - NOT SURE TO GET THIS RIGHT + if len(tr_list) == 1: + tr_list = tr_list * ones(len(ntp_list)) - if list_tr.size[0] == 1: - list_tr = list_tr * ones(list_ntp.size) + # Sum of values in ntp_list should be equivalent to num_timepoints_found + BlueprintInput.check_trigger_amount(thr=thr, num_timepoints_expected=sum(ntp_list), tr=tr_list) - # TODO : sum(ntp_list) is equivalent to num_timepoints_found - BlueprintInput.check_trigger_amount() + # TO DO : an error should be raised if aforementioned values are non-equivalent - # TODO : initialize dictionaries for which to call phys2bids() + # Initialize dictionaries for which to define BlueprintInput + run_Blueprint = {} + + for run_idx, run_tps in list_ntp: + BlueprintInput.check_trigger_amount(ntp=run_tps, tr=list_tr[run_idx]) + start_index = 0 + # why are we playing with the object in time - wouldn't it be better to play in samples? + end_index = {index of spike 0} + {run_tps*list_tr[run_idx]} + run_Blueprint[] = BlueprintInput.timeseries[0:end_index+padding, :] + + # if last value in the list "number of timepoints in run" + if i == len(list_ntp): + padding = + end_index+padding <= number of indexes + + # not sure how to define padding + else: + padding = number of indexes-end_index + BlueprintInput = BlueprintInput.timeseries[end_index+padding; , :] + + # make dict exportable def _main(argv=None): + options = _get_parser().parse_args(argv) split2phys(**vars(options)) From cd0c0a9333d4aafdb263636fcc2dc201e66297ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 8 Apr 2020 15:53:10 -0400 Subject: [PATCH 005/180] fixed typos and syntax error --- issue_36.md | 2 +- phys2bids/split2phys.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/issue_36.md b/issue_36.md index cac480a31..d9ab817a6 100644 --- a/issue_36.md +++ b/issue_36.md @@ -1,7 +1,7 @@ # Segment recordings by runs #36 -Ideas for name : split4phys, obj2split, phys4bids, split4bids, rec2run, phys2runs, ses2run, 4run2split +Ideas for name : split4phys, split4runs, obj2split, split4bids, rec2run, phys2runs, ses2run, 4run2split **Ways to integrate PR** 1. Another Repo : create a physiopy repo that is dedicated to splitting physiological recordings concurrent to neuroimaging ; least interesting option, aim is too specific. diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 73049f894..d9398fee3 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -95,22 +95,23 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N # Initialize dictionaries for which to define BlueprintInput run_Blueprint = {} + # for run_idx, run_tps in list_ntp: BlueprintInput.check_trigger_amount(ntp=run_tps, tr=list_tr[run_idx]) start_index = 0 # why are we playing with the object in time - wouldn't it be better to play in samples? end_index = {index of spike 0} + {run_tps*list_tr[run_idx]} - run_Blueprint[] = BlueprintInput.timeseries[0:end_index+padding, :] + run_Blueprint[run_idx] = BlueprintInput.timeseries[:end_index+padding, :] + padding = # if last value in the list "number of timepoints in run" - if i == len(list_ntp): - padding = - end_index+padding <= number of indexes + if run_idx == list_ntp.size[0]: + end_index + padding <= number of indexes # not sure how to define padding else: - padding = number of indexes-end_index - BlueprintInput = BlueprintInput.timeseries[end_index+padding; , :] + padding = number of indexes - end_index + BlueprintInput = BlueprintInput.timeseries[end_index + padding; , :] # make dict exportable From 7759012019bc3bc887962e47492da65dd9d7dfe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 8 Apr 2020 16:22:59 -0400 Subject: [PATCH 006/180] fixed typos and updated 2nd part of plan --- phys2bids/phys2bids.py | 2 +- phys2bids/split2phys.py | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index a127bc424..c5c8ee610 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -44,7 +44,7 @@ def print_summary(filename, ntp_expected, ntp_found, samp_freq, time_offset, outfile): """ - Prints a summary onscreen and in file with informations on the files. + Print a summary onscreen and in file with informations on the files. Parameters ---------- diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index d9398fee3..4f69dea09 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -97,23 +97,28 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N # for run_idx, run_tps in list_ntp: + # ascertain run length BlueprintInput.check_trigger_amount(ntp=run_tps, tr=list_tr[run_idx]) - start_index = 0 - # why are we playing with the object in time - wouldn't it be better to play in samples? - end_index = {index of spike 0} + {run_tps*list_tr[run_idx]} - run_Blueprint[run_idx] = BlueprintInput.timeseries[:end_index+padding, :] - padding = + # define idx of first trigger and padding length + start_index = BlueprintInput.timeseries[0] + padding = arb_val + + # I'M A BIT CONFUSED here. not sure if i get this right + end_index = start_index + (run_tps * list_tr[run_idx]) + run_Blueprint[run_idx] = BlueprintInput.timeseries[start_index - padding: + end_index + padding, :] # if last value in the list "number of timepoints in run" if run_idx == list_ntp.size[0]: - end_index + padding <= number of indexes + end_index + padding # <= number of indexes hmmm... don't remember our plan - # not sure how to define padding + # not sure how and where to define padding else: padding = number of indexes - end_index BlueprintInput = BlueprintInput.timeseries[end_index + padding; , :] # make dict exportable + # delete at index def _main(argv=None): From 1168258e4edc5a628e9cf9a4b947fe506f79b2ee Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 9 Apr 2020 02:45:19 +0200 Subject: [PATCH 007/180] Add trig_idx as BlueprintInput attribute --- phys2bids/physio_obj.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index fbc79a1a8..c37e8eb4e 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -108,6 +108,8 @@ class BlueprintInput(): *if* check_trigger_amount() is run thr: float Threshold used by check_trigger_amount() to detect trigger points. + trig_idx: int + Index of first trigger, computed *if* check_trigger_amount() is run Methods ------- @@ -264,6 +266,9 @@ def check_trigger_amount(self, chtrig=1, thr=None, num_timepoints_expected=0, tr self.timeseries: The property `timeseries` is shifted with the 0 being the time of first trigger. + self.trig_idx: + Property of the `BlueprintInput` class. + Contains index of first trigger """ LGR.info('Counting trigger points') # Use the trigger channel to find the TRs, @@ -283,6 +288,7 @@ def check_trigger_amount(self, chtrig=1, thr=None, num_timepoints_expected=0, tr LGR.info(f'The number of timepoints found with the manual threshold of {thr} ' f'is {num_timepoints_found}') time_offset = self.timeseries[0][timepoints.argmax()] + self. if num_timepoints_expected: LGR.info('Checking number of timepoints') From e1b7e3782a3ba76623a9cd06657932fe83d77177 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 9 Apr 2020 02:45:40 +0200 Subject: [PATCH 008/180] General reordering, comments and corrections --- phys2bids/split2phys.py | 107 +++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 34 deletions(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 4f69dea09..51cb1b537 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -9,13 +9,14 @@ """ -import os -import logging import datetime - +import logging +import os # from copy import deepcopy -from numpy import ones # from pathlib import Path + +from numpy import ones + from phys2bids import utils from phys2bids.cli.split import _get_parser from phys2bids.physio_obj import BlueprintInput @@ -23,7 +24,8 @@ LGR = logging.getLogger(__name__) -def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=None): +def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, + ntp_list=[0, ], tr_list=[1, ], thr=None, padding=0): """ Parallel workflow of phys2bids. @@ -62,7 +64,7 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N handlers=[log_handler, sh]) version_number = _version.get_versions()['version'] - LGR.info(f'Currently running phys2bids version {version_number}') + LGR.info(f'Currently running split2phys version {version_number}') LGR.info(f'Input file is {filename}') # Check options to make them internally coherent pt. II @@ -74,7 +76,18 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N infile = os.path.join(indir, filename) utils.check_file_exists(infile) - # Read file! + # Check that ntp_list is longer than 1 element + # If/when we set other parameters, we're going to change here + if len(ntp_list) == 1: + raise Exception('Only one run was specified. Don\'t run this workflow, ' + 'or check input') + + # Check equivalent length of list_ntp and list_tr + if len(tr_list) != 1 and len(ntp_list) != len(tr_list): + # Check out this page for all the builtin errors: + # https://docs.python.org/3/library/exceptions.html#bltin-exceptions + + # Import right interface to read the file if ftype == 'acq': from phys2bids.interfaces.acq import populate_phys_input elif ftype == 'txt': @@ -83,42 +96,68 @@ def split2phys(filename, indir='.', outdir='.', ntp_list=[0], tr_list=[1], thr=N # #!# We should add a logger here. raise NotImplementedError('Currently unsupported file type.') - # Check equivalence of list_ntp and list_tr - NOT SURE TO GET THIS RIGHT + # Actually read file! + LGR.info(f'Reading the file {infile}') + phys_in = populate_phys_input(infile, chtrig) # phys_in is a BlueprintInput object + LGR.info('Reading infos') + phys_in.print_info(filename) + + if chplot != '' or info: + viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, + phys_in.freq, infile, chplot) + # If only info were asked, end here. + if info: + return + if len(tr_list) == 1: tr_list = tr_list * ones(len(ntp_list)) # Sum of values in ntp_list should be equivalent to num_timepoints_found - BlueprintInput.check_trigger_amount(thr=thr, num_timepoints_expected=sum(ntp_list), tr=tr_list) - - # TO DO : an error should be raised if aforementioned values are non-equivalent - - # Initialize dictionaries for which to define BlueprintInput - run_Blueprint = {} - - # - for run_idx, run_tps in list_ntp: + phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, + num_timepoints_expected=sum(ntp_list), + tr=1) + + # Check that sum(ntp_list) is equivalent to num_timepoints_found, else bye! + # num_timepoints_found becomes an attribute of the object when you call check_trigger_amount + if phys_in.num_timepoints_found != sum(ntp_list): + # Again, raise your exception + + # Initialize dictionaries to save phys_in endpoints + run_endpoints = {} + # initialise start index as 0 + start_index = 0 + + for run_idx, run_tps in enumerate(list_ntp): # ascertain run length - BlueprintInput.check_trigger_amount(ntp=run_tps, tr=list_tr[run_idx]) - # define idx of first trigger and padding length - start_index = BlueprintInput.timeseries[0] - padding = arb_val + phys_in.check_trigger_amount(ntp=run_tps, tr=list_tr[run_idx]) # I'M A BIT CONFUSED here. not sure if i get this right - end_index = start_index + (run_tps * list_tr[run_idx]) - run_Blueprint[run_idx] = BlueprintInput.timeseries[start_index - padding: - end_index + padding, :] - - # if last value in the list "number of timepoints in run" - if run_idx == list_ntp.size[0]: - end_index + padding # <= number of indexes hmmm... don't remember our plan - - # not sure how and where to define padding - else: - padding = number of indexes - end_index - BlueprintInput = BlueprintInput.timeseries[end_index + padding; , :] + # Almost. It's really not easy! LET'S START NOT SUPPORTING MULTIFREQ + + # end_index is run_tps * list_tr[run_idx] expressed in the channel frequency + # ASSUMING THE FREQUENCY IS EXPRESSED IN Hz AND NOT (SUB)MULTIPLES OF Hz + # plus the start_index, plus the index of the first trigger + # We're going to add it as an attribute in physio_obj + # Check it. It might be wrong. + end_index = run_tps * list_tr[run_idx] * phys_in.freq[chtrig] + \ + start_index + phys_in.trig_idx + + # # if last value in the list "number of timepoints in run" + # if run_idx == list_ntp.size[0]: + # end_index + padding # <= number of indexes hmmm... don't remember our plan + # if the padding is too much for the remaining timeseries length + # then the padding become less + if phys_in.timeseries[chtrig].shape[0] < (end_index + padding): + padding = phys_in.timeseries[chtrig].shape[0] - end_index + + # Save end_index in dictionary -> start_index is run_idx-1 + # While saving, add the padding + run_endpoints[run_idx] = (end_index + padding) + # set start_index for next run as end_index of this one + start_index = end_index # make dict exportable - # delete at index + # delete at index ← not necessary anymore. def _main(argv=None): From 5610ec9a6df16d1fe0e2a9e2daca369481da8ffe Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 9 Apr 2020 13:06:04 +0200 Subject: [PATCH 009/180] Finish adding trig_idx attribute --- phys2bids/physio_obj.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index c37e8eb4e..45d2985e1 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -288,7 +288,6 @@ def check_trigger_amount(self, chtrig=1, thr=None, num_timepoints_expected=0, tr LGR.info(f'The number of timepoints found with the manual threshold of {thr} ' f'is {num_timepoints_found}') time_offset = self.timeseries[0][timepoints.argmax()] - self. if num_timepoints_expected: LGR.info('Checking number of timepoints') @@ -323,6 +322,7 @@ def check_trigger_amount(self, chtrig=1, thr=None, num_timepoints_expected=0, tr self.thr = thr self.timeseries[0] -= time_offset self.num_timepoints_found = num_timepoints_found + self.trig_idx = timepoints.argmax() def print_info(self, filename): """ From 232dea9d053d1482d6e0f7e576b9d19d114cac80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 9 Apr 2020 11:50:55 -0400 Subject: [PATCH 010/180] implementing comments from stefano - corrections --- phys2bids/split2phys.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 51cb1b537..c946d89fb 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -116,48 +116,42 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, num_timepoints_expected=sum(ntp_list), tr=1) - + # Check that sum(ntp_list) is equivalent to num_timepoints_found, else bye! # num_timepoints_found becomes an attribute of the object when you call check_trigger_amount if phys_in.num_timepoints_found != sum(ntp_list): - # Again, raise your exception + raise ValueError() # not sure if it's the good one # Initialize dictionaries to save phys_in endpoints run_endpoints = {} + # initialise start index as 0 start_index = 0 - + for run_idx, run_tps in enumerate(list_ntp): - # ascertain run length + # ascertain run length and initialise Blueprint object phys_in.check_trigger_amount(ntp=run_tps, tr=list_tr[run_idx]) - # I'M A BIT CONFUSED here. not sure if i get this right - # Almost. It's really not easy! LET'S START NOT SUPPORTING MULTIFREQ + # define padding - 20s * freq of trigger - padding is in nb of samples + padding = 20 * phys_in.freq[chtrig] - # end_index is run_tps * list_tr[run_idx] expressed in the channel frequency - # ASSUMING THE FREQUENCY IS EXPRESSED IN Hz AND NOT (SUB)MULTIPLES OF Hz - # plus the start_index, plus the index of the first trigger - # We're going to add it as an attribute in physio_obj - # Check it. It might be wrong. + # LET'S START NOT SUPPORTING MULTIFREQ - start_index is first elem of tuple end_index = run_tps * list_tr[run_idx] * phys_in.freq[chtrig] + \ - start_index + phys_in.trig_idx + start_index + phys_in.trig_idx - # # if last value in the list "number of timepoints in run" - # if run_idx == list_ntp.size[0]: - # end_index + padding # <= number of indexes hmmm... don't remember our plan # if the padding is too much for the remaining timeseries length - # then the padding become less + # then the padding stops at the end of recording if phys_in.timeseries[chtrig].shape[0] < (end_index + padding): padding = phys_in.timeseries[chtrig].shape[0] - end_index # Save end_index in dictionary -> start_index is run_idx-1 # While saving, add the padding run_endpoints[run_idx] = (end_index + padding) + # set start_index for next run as end_index of this one start_index = end_index # make dict exportable - # delete at index ← not necessary anymore. def _main(argv=None): From ba8b2334abb4c41301f31a16c0229e6bbc651716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 9 Apr 2020 12:14:36 -0400 Subject: [PATCH 011/180] trimming and detailed comments --- phys2bids/split2phys.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index c946d89fb..82fdc8043 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -30,9 +30,8 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, Parallel workflow of phys2bids. - Runs the split parser, does some check on inputs and calls - phys2bids to handle each dictionaries that have been created - based on npt_list and tr_list + Runs the split parser, does some check on inputs and exports + end indexes of each run based on npt_list and tr_list Arguments --------- @@ -152,6 +151,10 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, start_index = end_index # make dict exportable + # or call it from phys2bids + # or call phys2bids from here + # or integrate this bit of code in phys2bids and adapt main parser by accepting + # lists and adding -run argument def _main(argv=None): From de4e8f6678e2326c48df3e9ea3adae74b991457d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 9 Apr 2020 12:19:10 -0400 Subject: [PATCH 012/180] trimming and detailed comments - additions --- phys2bids/split2phys.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 82fdc8043..1f58f0d2b 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -120,6 +120,7 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # num_timepoints_found becomes an attribute of the object when you call check_trigger_amount if phys_in.num_timepoints_found != sum(ntp_list): raise ValueError() # not sure if it's the good one + # TODO : automatize tps correction # Initialize dictionaries to save phys_in endpoints run_endpoints = {} @@ -134,7 +135,7 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # define padding - 20s * freq of trigger - padding is in nb of samples padding = 20 * phys_in.freq[chtrig] - # LET'S START NOT SUPPORTING MULTIFREQ - start_index is first elem of tuple + # LET'S START NOT SUPPORTING MULTIFREQ - end_index is start+first_trigger+nb_samples in run end_index = run_tps * list_tr[run_idx] * phys_in.freq[chtrig] + \ start_index + phys_in.trig_idx From 80c43f22c9e8ad441f9a8119a3350c17f37029de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 9 Apr 2020 12:36:57 -0400 Subject: [PATCH 013/180] lintering --- phys2bids/split2phys.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 1f58f0d2b..6fcc71564 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -17,15 +17,15 @@ from numpy import ones -from phys2bids import utils +from phys2bids import utils, viz, _version from phys2bids.cli.split import _get_parser -from phys2bids.physio_obj import BlueprintInput +# from phys2bids.physio_obj import LGR = logging.getLogger(__name__) def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, - ntp_list=[0, ], tr_list=[1, ], thr=None, padding=0): + ntp_list=[0, ], tr_list=[1, ], chplot='', thr=None, padding=0): """ Parallel workflow of phys2bids. @@ -83,6 +83,7 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # Check equivalent length of list_ntp and list_tr if len(tr_list) != 1 and len(ntp_list) != len(tr_list): + raise Exception('') # Check out this page for all the builtin errors: # https://docs.python.org/3/library/exceptions.html#bltin-exceptions @@ -128,15 +129,15 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # initialise start index as 0 start_index = 0 - for run_idx, run_tps in enumerate(list_ntp): + for run_idx, run_tps in enumerate(ntp_list): # ascertain run length and initialise Blueprint object - phys_in.check_trigger_amount(ntp=run_tps, tr=list_tr[run_idx]) + phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) # define padding - 20s * freq of trigger - padding is in nb of samples padding = 20 * phys_in.freq[chtrig] # LET'S START NOT SUPPORTING MULTIFREQ - end_index is start+first_trigger+nb_samples in run - end_index = run_tps * list_tr[run_idx] * phys_in.freq[chtrig] + \ + end_index = run_tps * tr_list[run_idx] * phys_in.freq[chtrig] + \ start_index + phys_in.trig_idx # if the padding is too much for the remaining timeseries length From 3165b4d0645a7347c9884200416cabe19881a171 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 10 Apr 2020 18:51:36 +0200 Subject: [PATCH 014/180] Add start index to dictionary making it a dictionary of tuples --- phys2bids/split2phys.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 6fcc71564..8ea5e7d70 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -120,7 +120,7 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # Check that sum(ntp_list) is equivalent to num_timepoints_found, else bye! # num_timepoints_found becomes an attribute of the object when you call check_trigger_amount if phys_in.num_timepoints_found != sum(ntp_list): - raise ValueError() # not sure if it's the good one + raise ValueError() # not sure if it's the good one ← you can use a general "Exception" # TODO : automatize tps correction # Initialize dictionaries to save phys_in endpoints @@ -145,9 +145,9 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, if phys_in.timeseries[chtrig].shape[0] < (end_index + padding): padding = phys_in.timeseries[chtrig].shape[0] - end_index - # Save end_index in dictionary -> start_index is run_idx-1 + # Save start: and end_index in dictionary # While saving, add the padding - run_endpoints[run_idx] = (end_index + padding) + run_endpoints[run_idx] = (start_index, (end_index + padding)) # set start_index for next run as end_index of this one start_index = end_index From b13daeab4ec7fe6dd25a685aa6789b138d42e8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 10 Apr 2020 13:11:57 -0400 Subject: [PATCH 015/180] typos and error raising --- phys2bids/phys2bids.py | 2 +- phys2bids/split2phys.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index c5c8ee610..3cf5842a7 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -179,7 +179,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): """ Main workflow of phys2bids. - Runs the parser, does some checks on input, then imports + Run the parser, does some checks on input, then imports the right interface file to read the input. If only info is required, it returns a summary onscreen. Otherwise, it operates on the input to return a .tsv.gz file, possibily diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 6fcc71564..3c98c814f 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -136,7 +136,7 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # define padding - 20s * freq of trigger - padding is in nb of samples padding = 20 * phys_in.freq[chtrig] - # LET'S START NOT SUPPORTING MULTIFREQ - end_index is start+first_trigger+nb_samples in run + # LET'S START NOT SUPPORTING MULTIFREQ - end_index is nb of samples in run+start+first_trig end_index = run_tps * tr_list[run_idx] * phys_in.freq[chtrig] + \ start_index + phys_in.trig_idx From 66b15c2f718d1bbbf5f4d0b68ac0f89cd322a26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 10 Apr 2020 13:46:47 -0400 Subject: [PATCH 016/180] define exceptions --- phys2bids/split2phys.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 3c98c814f..fb78fdbdb 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -15,8 +15,6 @@ # from copy import deepcopy # from pathlib import Path -from numpy import ones - from phys2bids import utils, viz, _version from phys2bids.cli.split import _get_parser # from phys2bids.physio_obj import @@ -81,11 +79,21 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, raise Exception('Only one run was specified. Don\'t run this workflow, ' 'or check input') - # Check equivalent length of list_ntp and list_tr - if len(tr_list) != 1 and len(ntp_list) != len(tr_list): - raise Exception('') - # Check out this page for all the builtin errors: - # https://docs.python.org/3/library/exceptions.html#bltin-exceptions + # Check equivalency of length for list_ntp and list_tr + if len(tr_list) != 1 and len(ntp_list) < len(tr_list): + raise Exception('Multiple sequence types have been listed in tr,' + 'but the number of run is less than types of sequence') + # 2 sequence types, 3 runs ; which one is it?????? + if len(tr_list) != 1 and len(tr_list) < len(ntp_list): + raise Exception('Multiple sequence types have been listed in tr,' + 'but the number of run doesn\'t match') + + # Check out this page for all the builtin errors: + # https://docs.python.org/3/library/exceptions.html#bltin-exceptions + + # if multiple runs of same sequence in recording - pad the list with same value + if len(tr_list) == 1: + tr_list = tr_list * len(ntp_list) # Import right interface to read the file if ftype == 'acq': @@ -109,13 +117,10 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, if info: return - if len(tr_list) == 1: - tr_list = tr_list * ones(len(ntp_list)) - # Sum of values in ntp_list should be equivalent to num_timepoints_found phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, num_timepoints_expected=sum(ntp_list), - tr=1) + tr=1) # TODO : define a non-hard-coded value # Check that sum(ntp_list) is equivalent to num_timepoints_found, else bye! # num_timepoints_found becomes an attribute of the object when you call check_trigger_amount From 6d449f5e115c3caacc0e3f1debf426264c0eb177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 10 Apr 2020 20:21:09 -0400 Subject: [PATCH 017/180] opting for indexes by updating object in loop --- phys2bids/split2phys.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index fb78fdbdb..a7b60cd8e 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -135,15 +135,20 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, start_index = 0 for run_idx, run_tps in enumerate(ntp_list): - # ascertain run length and initialise Blueprint object - phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) - + # ascertain run length and and (re)initialise Blueprint object + if run_idx == 0: + phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) + else: + phys_in = phys_in.delete_at_index([start_index:end_index]) + phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) # define padding - 20s * freq of trigger - padding is in nb of samples padding = 20 * phys_in.freq[chtrig] # LET'S START NOT SUPPORTING MULTIFREQ - end_index is nb of samples in run+start+first_trig - end_index = run_tps * tr_list[run_idx] * phys_in.freq[chtrig] + \ - start_index + phys_in.trig_idx + end_index = (run_tps * tr_list[run_idx]) * phys_in.freq[chtrig] + \ + start_index + phys_in.trig_idx # problem : it only goes for the first run + # either we update phys_in wth delete_at_index and use this attribute and drop start_index + # or we don't use this attribute and figure the index another way # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording @@ -151,11 +156,11 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, padding = phys_in.timeseries[chtrig].shape[0] - end_index # Save end_index in dictionary -> start_index is run_idx-1 - # While saving, add the padding + # While saving, add the padding ; or not if we can feed it to phys2bids run_endpoints[run_idx] = (end_index + padding) # set start_index for next run as end_index of this one - start_index = end_index + # start_index = end_index # make dict exportable # or call it from phys2bids From 22677cbf11995f70b2e2a7a52970cbdad28c24a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 17 Apr 2020 13:42:44 -0400 Subject: [PATCH 018/180] redefining plan and minor fixes in split --- issue_36.md | 77 +++++++++++------------------------------ phys2bids/split2phys.py | 11 +++--- 2 files changed, 25 insertions(+), 63 deletions(-) diff --git a/issue_36.md b/issue_36.md index d9ab817a6..6922325be 100644 --- a/issue_36.md +++ b/issue_36.md @@ -3,65 +3,30 @@ #36 Ideas for name : split4phys, split4runs, obj2split, split4bids, rec2run, phys2runs, ses2run, 4run2split -**Ways to integrate PR** -1. Another Repo : create a physiopy repo that is dedicated to splitting physiological recordings concurrent to neuroimaging ; least interesting option, aim is too specific. -2. Integrate phys2bids in [name of fn]: workflow on its own that calls phys2bids at the end of script for each segments -3. Independant utility : function gets called by phys2bids if list are detected as phys2bids arguments, no parser. -4. Integrate [name of fn] in phys2bids: keep the parser, have parallel workflows for different outcomes - less easy to integrate but more convenient for users +# The option we settled for : +Integrate the splitting function with phys2bids. -## 1. Parser -Create argument parser for new command ; based on `run.py` - - in : `cli/split.py` - arg : `-ntp` and `-tr` - -### 1.1. Copy elements from main parser -`run.py` to `split.py` -Integrate arguments : -in, -info, -indir, -outdir, -thr, -v -***Do we need anything else ?*** - -### 1.2. Adapt -We have to adapt types for : `-ntp` (int --> list) and `-tr` (float --> list) -NOTES: if the TR parameter of a sequence is the same in different runs, just pad it with the same value - -``` -if list_tr.size[0] = 1: - list_tr = list_tr * np.ones(list_ntp.size) -``` - -## 2. split2phys function -Create new file for the splitting utility and integrate `physio_obj` functions - - in : `/phys2bids/split2phys.py` - -### 2.1. Verify num_timepoints_found and sum(ntp_list) are equivalent -Pad tr list so that it's equivalent to the number of runs contained in ntp_list - -**To consider eventually**: maybe, the user doesn't know how many runs are contained in the file but knows (1) the usual length of a single run, (2) the sessions has only 1 sequence type, i.e. the same TR. So both `list_ntp.size[0]`and `list_tr.size[0]` could be `1` eventhough the file has multiple runs. - - from : `/phys_obj.py`, `BlueprintInput()` - fn : `num_timepoints_found` and `check_trigger_amount` +# What we need to do +## 1.Adapt phys2bids parser arguments, in `run.py` + -[] ntp type, from int to list + -[] tr type, from float to list + -[] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. ``` -BlueprintInput.check_trigger_amount(sum(list_ntp), tr=1) -``` -### 2.2. Find start-end indexes for each run in list -Initialize dictionaries from which to define start and end indexes of timeseries. +if multiple item (in ntp or in tr): + -run is True. +If run is True: + redirect to part of code that splits the file ``` -init dictionary for BlueprintInputs (dict) - - for i, elem in list_ntp: - BlueprintInput.check_trigger_amount(ntp=elem, tr=list_tr[i]) - start_index = 0 - end_index = {index of spike 0} + {index of elem*list_tr[i]} - dict[‘0’] = BlueprintInput.timeseries[0:end_index+padding, :] - if i == len(list_ntp): - end_index+padding <= number of indexes - if it’s not: padding= number of indexes-end_index - BlueprintInput = BlueprintInput.timeseries[end_index+padding; , :] -make dict exportable -``` -## 3. Call phys2bids -Call phys2bids for each of them +## 2.Ascertain lists length before running main workflow, after loading file (in `phys2bids.py, line 241`) + -[] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` + -[] add padding to non-equivalent list (in `split2phys.py line 92`) +## 3.Check that sum(ntp_list) gives the right amount of trigger tps + -[] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` + -[] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) +## 4.***Code the thing*** (that I really need help with) + -[] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 + -[] Insert If... else statement to redirect. + -[] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index a7b60cd8e..a0966a7fd 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -A parallel CLI utility to segment the physiological input files. +A parallel to phys2bids. Cuts the physiological recording files into multiple runs with padding at start and end @@ -22,7 +22,7 @@ LGR = logging.getLogger(__name__) -def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, +def ses2run(filename, info=False, indir='.', outdir='.', chtrig=1, ntp_list=[0, ], tr_list=[1, ], chplot='', thr=None, padding=0): """ @@ -31,12 +31,9 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, Runs the split parser, does some check on inputs and exports end indexes of each run based on npt_list and tr_list - Arguments - --------- + It could be a function in phys + uses if it detects lists in tr and ntp arguments - Returns - -------- - ... """ outdir = utils.check_input_dir(outdir) utils.path_exists_or_make_it(outdir) From 3e6880c84289d970ca39f0f5cbff3bada2589888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 17 Apr 2020 13:45:22 -0400 Subject: [PATCH 019/180] fixing rendering in md --- issue_36.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/issue_36.md b/issue_36.md index 6922325be..6c801ba8a 100644 --- a/issue_36.md +++ b/issue_36.md @@ -8,9 +8,9 @@ Integrate the splitting function with phys2bids. # What we need to do ## 1.Adapt phys2bids parser arguments, in `run.py` - -[] ntp type, from int to list - -[] tr type, from float to list - -[] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. +-[] ntp type, from int to list +-[] tr type, from float to list +-[] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. ``` if multiple item (in ntp or in tr): @@ -21,12 +21,12 @@ If run is True: ``` ## 2.Ascertain lists length before running main workflow, after loading file (in `phys2bids.py, line 241`) - -[] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` - -[] add padding to non-equivalent list (in `split2phys.py line 92`) +-[] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` +-[] add padding to non-equivalent list (in `split2phys.py line 92`) ## 3.Check that sum(ntp_list) gives the right amount of trigger tps - -[] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` - -[] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) +-[] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` +-[] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) ## 4.***Code the thing*** (that I really need help with) - -[] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 - -[] Insert If... else statement to redirect. - -[] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. +-[] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 +-[] Insert If... else statement to redirect. +-[] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. From ce0d1edb1b7a3cc1d5125c1ff62a3d59f6a0c58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 17 Apr 2020 13:46:08 -0400 Subject: [PATCH 020/180] fixing rendering in md --- issue_36.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/issue_36.md b/issue_36.md index 6c801ba8a..c1d49ba76 100644 --- a/issue_36.md +++ b/issue_36.md @@ -21,12 +21,12 @@ If run is True: ``` ## 2.Ascertain lists length before running main workflow, after loading file (in `phys2bids.py, line 241`) --[] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` --[] add padding to non-equivalent list (in `split2phys.py line 92`) +-[ ] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` +-[ ] add padding to non-equivalent list (in `split2phys.py line 92`) ## 3.Check that sum(ntp_list) gives the right amount of trigger tps --[] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` --[] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) +-[ ] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` +-[ ] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) ## 4.***Code the thing*** (that I really need help with) --[] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 --[] Insert If... else statement to redirect. --[] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. +-[ ] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 +-[ ] Insert If... else statement to redirect. +-[ ] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. From 9f00c4b03d8464f3ce2fc38be41a4b4f301697cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 17 Apr 2020 13:47:54 -0400 Subject: [PATCH 021/180] fixing rendering in md --- issue_36.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/issue_36.md b/issue_36.md index c1d49ba76..9ba167b01 100644 --- a/issue_36.md +++ b/issue_36.md @@ -8,9 +8,9 @@ Integrate the splitting function with phys2bids. # What we need to do ## 1.Adapt phys2bids parser arguments, in `run.py` --[] ntp type, from int to list --[] tr type, from float to list --[] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. +- [ ] ntp type, from int to list +- [ ] tr type, from float to list +- [ ] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. ``` if multiple item (in ntp or in tr): @@ -21,12 +21,12 @@ If run is True: ``` ## 2.Ascertain lists length before running main workflow, after loading file (in `phys2bids.py, line 241`) --[ ] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` --[ ] add padding to non-equivalent list (in `split2phys.py line 92`) +- [ ] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` +- [ ] add padding to non-equivalent list (in `split2phys.py line 92`) ## 3.Check that sum(ntp_list) gives the right amount of trigger tps --[ ] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` --[ ] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) +- [ ] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` +- [ ] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) ## 4.***Code the thing*** (that I really need help with) --[ ] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 --[ ] Insert If... else statement to redirect. --[ ] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. +- [ ] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 +- [ ] Insert If... else statement to redirect. +- [ ] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. From 817f100c0eb8b22af50b3e0d1dc8bbff04ef8e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sat, 18 Apr 2020 10:55:35 -0400 Subject: [PATCH 022/180] reverting to previous split2phys --- issue_36.md | 20 ++++++++++---------- phys2bids/split2phys.py | 30 ++++++++++++++---------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/issue_36.md b/issue_36.md index 9ba167b01..36c152f0c 100644 --- a/issue_36.md +++ b/issue_36.md @@ -8,9 +8,9 @@ Integrate the splitting function with phys2bids. # What we need to do ## 1.Adapt phys2bids parser arguments, in `run.py` -- [ ] ntp type, from int to list -- [ ] tr type, from float to list -- [ ] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. +- [ ] ntp type, from int to list +- [ ] tr type, from float to list +- [ ] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. ``` if multiple item (in ntp or in tr): @@ -21,12 +21,12 @@ If run is True: ``` ## 2.Ascertain lists length before running main workflow, after loading file (in `phys2bids.py, line 241`) -- [ ] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` -- [ ] add padding to non-equivalent list (in `split2phys.py line 92`) +- [ ] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` +- [ ] add padding to non-equivalent list (in `split2phys.py line 92`) ## 3.Check that sum(ntp_list) gives the right amount of trigger tps -- [ ] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` -- [ ] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) +- [ ] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` +- [ ] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) ## 4.***Code the thing*** (that I really need help with) -- [ ] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 -- [ ] Insert If... else statement to redirect. -- [ ] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. +- [ ] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 +- [ ] Insert If... else statements to redirect. +- [ ] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index a0966a7fd..fb78fdbdb 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -A parallel to phys2bids. +A parallel CLI utility to segment the physiological input files. Cuts the physiological recording files into multiple runs with padding at start and end @@ -22,7 +22,7 @@ LGR = logging.getLogger(__name__) -def ses2run(filename, info=False, indir='.', outdir='.', chtrig=1, +def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, ntp_list=[0, ], tr_list=[1, ], chplot='', thr=None, padding=0): """ @@ -31,9 +31,12 @@ def ses2run(filename, info=False, indir='.', outdir='.', chtrig=1, Runs the split parser, does some check on inputs and exports end indexes of each run based on npt_list and tr_list - It could be a function in phys - uses if it detects lists in tr and ntp arguments + Arguments + --------- + Returns + -------- + ... """ outdir = utils.check_input_dir(outdir) utils.path_exists_or_make_it(outdir) @@ -132,20 +135,15 @@ def ses2run(filename, info=False, indir='.', outdir='.', chtrig=1, start_index = 0 for run_idx, run_tps in enumerate(ntp_list): - # ascertain run length and and (re)initialise Blueprint object - if run_idx == 0: - phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) - else: - phys_in = phys_in.delete_at_index([start_index:end_index]) - phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) + # ascertain run length and initialise Blueprint object + phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) + # define padding - 20s * freq of trigger - padding is in nb of samples padding = 20 * phys_in.freq[chtrig] # LET'S START NOT SUPPORTING MULTIFREQ - end_index is nb of samples in run+start+first_trig - end_index = (run_tps * tr_list[run_idx]) * phys_in.freq[chtrig] + \ - start_index + phys_in.trig_idx # problem : it only goes for the first run - # either we update phys_in wth delete_at_index and use this attribute and drop start_index - # or we don't use this attribute and figure the index another way + end_index = run_tps * tr_list[run_idx] * phys_in.freq[chtrig] + \ + start_index + phys_in.trig_idx # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording @@ -153,11 +151,11 @@ def ses2run(filename, info=False, indir='.', outdir='.', chtrig=1, padding = phys_in.timeseries[chtrig].shape[0] - end_index # Save end_index in dictionary -> start_index is run_idx-1 - # While saving, add the padding ; or not if we can feed it to phys2bids + # While saving, add the padding run_endpoints[run_idx] = (end_index + padding) # set start_index for next run as end_index of this one - # start_index = end_index + start_index = end_index # make dict exportable # or call it from phys2bids From d30f41922556c48b7fbbb915d7512c62110801c1 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Sat, 18 Apr 2020 19:08:21 +0200 Subject: [PATCH 023/180] Add comments and structure of if statements to call split function --- phys2bids/cli/run.py | 4 +- phys2bids/phys2bids.py | 13 +++- phys2bids/split2phys.py | 160 ++++++++++++++++++++-------------------- 3 files changed, 95 insertions(+), 82 deletions(-) diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index 8b9118ccd..a6d1d10a3 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -99,13 +99,13 @@ def _get_parser(): 'Default is 0. Note: the estimation of when the ' 'neuroimaging acquisition started cannot take place ' 'with this default.', - default=0) + default=[0, ]) optional.add_argument('-tr', '--tr', dest='tr', type=float, help='TR of sequence in seconds. ' 'Default is 0 second.', - default=0) + default=[0, ]) optional.add_argument('-thr', '--threshold', dest='thr', type=float, diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 3cf5842a7..082c23434 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -33,7 +33,7 @@ from copy import deepcopy from pathlib import Path -from numpy import savetxt +from numpy import savetxt, ones from phys2bids import utils, viz, _version from phys2bids.cli.run import _get_parser @@ -262,6 +262,17 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Create trigger plot. If possible, to have multiple outputs in the same # place, adds sub and ses label. + if len(ntp) > 1: + if len(tr) == 1: + # pad tr to have same length as ntp + # François check this statement. Maybe you need numpy.ones() (added in the import already) + tr = tr * len(ntp) + elif len(ntp) != len(tr): + raise exception + + # call split2phys + # get dictionary in the form {run: (startpoint, endpoint), [...]} + if tr != 0 and num_timepoints_expected != 0: # Run analysis on trigger channel to get first timepoint and the time offset. # #!# Get option of no trigger! (which is wrong practice or Respiract) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index 82de99674..e45593edd 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -38,84 +38,84 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, -------- ... """ - outdir = utils.check_input_dir(outdir) - utils.path_exists_or_make_it(outdir) - - # Create logfile name - basename = 'split2phys_' - extension = 'tsv' - isotime = datetime.datetime.now().strftime('%Y-%m-%dT%H%M%S') - logname = os.path.join(outdir, (basename + isotime + '.' + extension)) - - # Set logging format - log_formatter = logging.Formatter( - '%(asctime)s\t%(name)-12s\t%(levelname)-8s\t%(message)s', - datefmt='%Y-%m-%dT%H:%M:%S') - - # Set up logging file and open it for writing - log_handler = logging.FileHandler(logname) - log_handler.setFormatter(log_formatter) - sh = logging.StreamHandler() - - logging.basicConfig(level=logging.INFO, - handlers=[log_handler, sh]) - - version_number = _version.get_versions()['version'] - LGR.info(f'Currently running split2phys version {version_number}') - LGR.info(f'Input file is {filename}') - - # Check options to make them internally coherent pt. II - # #!# This can probably be done while parsing? - indir = utils.check_input_dir(indir) - filename, ftype = utils.check_input_type(filename, - indir) - - infile = os.path.join(indir, filename) - utils.check_file_exists(infile) - - # Check that ntp_list is longer than 1 element - # If/when we set other parameters, we're going to change here - if len(ntp_list) == 1: - raise Exception('Only one run was specified. Don\'t run this workflow, ' - 'or check input') - - # Check equivalency of length for list_ntp and list_tr - if len(tr_list) != 1 and len(ntp_list) < len(tr_list): - raise Exception('Multiple sequence types have been listed in tr,' - 'but the number of run is less than types of sequence') - # 2 sequence types, 3 runs ; which one is it?????? - if len(tr_list) != 1 and len(tr_list) < len(ntp_list): - raise Exception('Multiple sequence types have been listed in tr,' - 'but the number of run doesn\'t match') - - # Check out this page for all the builtin errors: - # https://docs.python.org/3/library/exceptions.html#bltin-exceptions - - # if multiple runs of same sequence in recording - pad the list with same value - if len(tr_list) == 1: - tr_list = tr_list * len(ntp_list) - - # Import right interface to read the file - if ftype == 'acq': - from phys2bids.interfaces.acq import populate_phys_input - elif ftype == 'txt': - from phys2bids.interfaces.txt import populate_phys_input - else: - # #!# We should add a logger here. - raise NotImplementedError('Currently unsupported file type.') - - # Actually read file! - LGR.info(f'Reading the file {infile}') - phys_in = populate_phys_input(infile, chtrig) # phys_in is a BlueprintInput object - LGR.info('Reading infos') - phys_in.print_info(filename) - - if chplot != '' or info: - viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, - phys_in.freq, infile, chplot) - # If only info were asked, end here. - if info: - return + # outdir = utils.check_input_dir(outdir) + # utils.path_exists_or_make_it(outdir) + + # # Create logfile name + # basename = 'split2phys_' + # extension = 'tsv' + # isotime = datetime.datetime.now().strftime('%Y-%m-%dT%H%M%S') + # logname = os.path.join(outdir, (basename + isotime + '.' + extension)) + + # # Set logging format + # log_formatter = logging.Formatter( + # '%(asctime)s\t%(name)-12s\t%(levelname)-8s\t%(message)s', + # datefmt='%Y-%m-%dT%H:%M:%S') + + # # Set up logging file and open it for writing + # log_handler = logging.FileHandler(logname) + # log_handler.setFormatter(log_formatter) + # sh = logging.StreamHandler() + + # logging.basicConfig(level=logging.INFO, + # handlers=[log_handler, sh]) + + # version_number = _version.get_versions()['version'] + # LGR.info(f'Currently running split2phys version {version_number}') + # LGR.info(f'Input file is {filename}') + + # # Check options to make them internally coherent pt. II + # # #!# This can probably be done while parsing? + # indir = utils.check_input_dir(indir) + # filename, ftype = utils.check_input_type(filename, + # indir) + + # infile = os.path.join(indir, filename) + # utils.check_file_exists(infile) + + # # Check that ntp_list is longer than 1 element + # # If/when we set other parameters, we're going to change here + # if len(ntp_list) == 1: + # raise Exception('Only one run was specified. Don\'t run this workflow, ' + # 'or check input') + + # # Check equivalency of length for list_ntp and list_tr + # if len(tr_list) != 1 and len(ntp_list) < len(tr_list): + # raise Exception('Multiple sequence types have been listed in tr,' + # 'but the number of run is less than types of sequence') + # # 2 sequence types, 3 runs ; which one is it?????? + # if len(tr_list) != 1 and len(tr_list) < len(ntp_list): + # raise Exception('Multiple sequence types have been listed in tr,' + # 'but the number of run doesn\'t match') + + # # Check out this page for all the builtin errors: + # # https://docs.python.org/3/library/exceptions.html#bltin-exceptions + + # # if multiple runs of same sequence in recording - pad the list with same value + # if len(tr_list) == 1: + # tr_list = tr_list * len(ntp_list) + + # # Import right interface to read the file + # if ftype == 'acq': + # from phys2bids.interfaces.acq import populate_phys_input + # elif ftype == 'txt': + # from phys2bids.interfaces.txt import populate_phys_input + # else: + # # #!# We should add a logger here. + # raise NotImplementedError('Currently unsupported file type.') + + # # Actually read file! + # LGR.info(f'Reading the file {infile}') + # phys_in = populate_phys_input(infile, chtrig) # phys_in is a BlueprintInput object + # LGR.info('Reading infos') + # phys_in.print_info(filename) + + # if chplot != '' or info: + # viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, + # phys_in.freq, infile, chplot) + # # If only info were asked, end here. + # if info: + # return # Sum of values in ntp_list should be equivalent to num_timepoints_found phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, @@ -125,7 +125,7 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # Check that sum(ntp_list) is equivalent to num_timepoints_found, else bye! # num_timepoints_found becomes an attribute of the object when you call check_trigger_amount if phys_in.num_timepoints_found != sum(ntp_list): - raise ValueError() # not sure if it's the good one ← you can use a general "Exception" + raise Exception() # not sure if it's the good one ← you can use a general "Exception" # TODO : automatize tps correction # Initialize dictionaries to save phys_in endpoints @@ -157,6 +157,8 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # set start_index for next run as end_index of this one start_index = end_index + # phys_in.start_at_time(start_index) + # make dict exportable # or call it from phys2bids # or call phys2bids from here From 84fca59e4f722390ccd9a79a5ab2b70945525fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sat, 18 Apr 2020 14:28:17 -0400 Subject: [PATCH 024/180] erase duplicate in split, regorg phys2bids, changes types in CLI --- issue_36.md | 35 +++++------- phys2bids/cli/run.py | 20 ++++--- phys2bids/phys2bids.py | 41 +++++++++----- phys2bids/split2phys.py | 120 ++-------------------------------------- 4 files changed, 59 insertions(+), 157 deletions(-) diff --git a/issue_36.md b/issue_36.md index 36c152f0c..3890bc358 100644 --- a/issue_36.md +++ b/issue_36.md @@ -8,25 +8,18 @@ Integrate the splitting function with phys2bids. # What we need to do ## 1.Adapt phys2bids parser arguments, in `run.py` -- [ ] ntp type, from int to list -- [ ] tr type, from float to list +- [x] tr type, from float to list +- [x] ntp type, from int to list +- [ ] add a check for those lists in a different issue - [ ] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. - -``` -if multiple item (in ntp or in tr): - -run is True. - -If run is True: - redirect to part of code that splits the file -``` - -## 2.Ascertain lists length before running main workflow, after loading file (in `phys2bids.py, line 241`) -- [ ] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) - in `split2phys.py line 75 to 90` -- [ ] add padding to non-equivalent list (in `split2phys.py line 92`) -## 3.Check that sum(ntp_list) gives the right amount of trigger tps -- [ ] adapt check_trigger_amount section in `phys2bids.py, line 265`, with `split2phys.py, line 118` -- [ ] find the right value to give to `tr` parameter when running `phys_in.check_trigger_amount()` (no hard-coded value) -## 4.***Code the thing*** (that I really need help with) -- [ ] Figure out where to insert the loop (developped in `split2phys.py line 130`). Probably somewhere around line 294 -- [ ] Insert If... else statements to redirect. -- [ ] Adapt `phys_out` to deal with multi-run, and eventually, multi-freq files. +## 2.In phys2bids, ascertain lists length +- [x] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) +- [x] add padding of ones to tr when non-equivalent list +- [x] Insert If... else statements to redirect. +- [x] check that sum(ntp_list) gives the right amount of trigger tps +- [x] Figure out where to call split2phys +## 3.In split2phys, +- [x] adapt check_trigger_amount section +- [ ] Create dictionary with start, end index +- [ ] Adapt `phys_out` to deal with multi-run, +- [ ] and eventually, multi-freq files (in a different issue). diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index a6d1d10a3..670050a1a 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -78,6 +78,11 @@ def _get_parser(): help='Specify alongside \"-heur\". Code of ' 'session to process.', default=None) + # optional.add_argument('-run', '--multi-run', + # dest= run, + # help='' + # + # optional.add_argument('-chtrig', '--channel-trigger', dest='chtrig', type=int, @@ -94,17 +99,18 @@ def _get_parser(): default=None) optional.add_argument('-ntp', '--numtps', dest='num_timepoints_expected', - type=int, - help='Number of expected timepoints (TRs). ' - 'Default is 0. Note: the estimation of when the ' - 'neuroimaging acquisition started cannot take place ' - 'with this default.', + type=list, + help='Number of expected trigger timepoints (TRs). ' + 'Default is 0. Note: the estimation of beggining of ' + 'neuroimaging acquisition cannot take place with this default.' + 'Give a list of each expected ntp for multi-run recordings.', default=[0, ]) optional.add_argument('-tr', '--tr', dest='tr', - type=float, + type=list, help='TR of sequence in seconds. ' - 'Default is 0 second.', + 'Default is 0 second.' + 'You can list each TR used throughout the session', default=[0, ]) optional.add_argument('-thr', '--threshold', dest='thr', diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 082c23434..c43c07b2c 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -256,23 +256,36 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if chplot != '' or info: viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, phys_in.freq, infile, chplot) - # If only info were asked, end here. - if info: - return - # Create trigger plot. If possible, to have multiple outputs in the same - # place, adds sub and ses label. - if len(ntp) > 1: - if len(tr) == 1: - # pad tr to have same length as ntp - # François check this statement. Maybe you need numpy.ones() (added in the import already) - tr = tr * len(ntp) - elif len(ntp) != len(tr): - raise exception + # Multi-run section + # Check list length, more than 1 means multi-run + if len(num_timepoints_expected) > 1: - # call split2phys - # get dictionary in the form {run: (startpoint, endpoint), [...]} + # if multiple runs of same sequence in recording - pad the list with arbitrary value + # NOTE : we could also duplicate the item by multiplying with len(ntp) + if len(tr) == 1: + tr = ones() * len(num_timepoints_expected) + # Check equivalency of length + elif len(num_timepoints_expected) != len(tr): + raise Exception('Number of sequence types listed with TR doesn\'t ' + 'match expected number of runs in the session') + # Sum of values in ntp_list should be equivalent to num_timepoints_found + phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, + num_timepoints_expected=sum(num_timepoints_expected), + tr=1) # TODO : define a non-hard-coded value + + # Check that sum(ntp_list) is equivalent to num_timepoints_found, else call split2phys + if phys_in.num_timepoints_found != sum(num_timepoints_expected): + raise Exception() # not sure if it's the good one ← you can use a general "Exception" + # TODO : automatize tps correction + + # CALL SPLIT2PHYS, give it BlueprintInput object and lists + # it will give a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} + # ideally, we'd want to have a figure for each run + # Create trigger plot. If possible, to have multiple outputs in the same + # place, adds sub and ses label. + # NOTE : and len(tr)<2 ? if tr != 0 and num_timepoints_expected != 0: # Run analysis on trigger channel to get first timepoint and the time offset. # #!# Get option of no trigger! (which is wrong practice or Respiract) diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index e45593edd..fa254eda5 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -9,23 +9,11 @@ """ -import datetime -import logging -import os -# from copy import deepcopy -# from pathlib import Path +from phys2bids.physio_obj.BlueprintInput import check_trigger_amount -from phys2bids import utils, viz, _version -from phys2bids.cli.split import _get_parser -# from phys2bids.physio_obj import -LGR = logging.getLogger(__name__) - - -def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, - ntp_list=[0, ], tr_list=[1, ], chplot='', thr=None, padding=0): +def split2phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ]): """ - Parallel workflow of phys2bids. Runs the split parser, does some check on inputs and exports @@ -38,98 +26,9 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, -------- ... """ - # outdir = utils.check_input_dir(outdir) - # utils.path_exists_or_make_it(outdir) - - # # Create logfile name - # basename = 'split2phys_' - # extension = 'tsv' - # isotime = datetime.datetime.now().strftime('%Y-%m-%dT%H%M%S') - # logname = os.path.join(outdir, (basename + isotime + '.' + extension)) - - # # Set logging format - # log_formatter = logging.Formatter( - # '%(asctime)s\t%(name)-12s\t%(levelname)-8s\t%(message)s', - # datefmt='%Y-%m-%dT%H:%M:%S') - - # # Set up logging file and open it for writing - # log_handler = logging.FileHandler(logname) - # log_handler.setFormatter(log_formatter) - # sh = logging.StreamHandler() - - # logging.basicConfig(level=logging.INFO, - # handlers=[log_handler, sh]) - - # version_number = _version.get_versions()['version'] - # LGR.info(f'Currently running split2phys version {version_number}') - # LGR.info(f'Input file is {filename}') - - # # Check options to make them internally coherent pt. II - # # #!# This can probably be done while parsing? - # indir = utils.check_input_dir(indir) - # filename, ftype = utils.check_input_type(filename, - # indir) - - # infile = os.path.join(indir, filename) - # utils.check_file_exists(infile) - - # # Check that ntp_list is longer than 1 element - # # If/when we set other parameters, we're going to change here - # if len(ntp_list) == 1: - # raise Exception('Only one run was specified. Don\'t run this workflow, ' - # 'or check input') - - # # Check equivalency of length for list_ntp and list_tr - # if len(tr_list) != 1 and len(ntp_list) < len(tr_list): - # raise Exception('Multiple sequence types have been listed in tr,' - # 'but the number of run is less than types of sequence') - # # 2 sequence types, 3 runs ; which one is it?????? - # if len(tr_list) != 1 and len(tr_list) < len(ntp_list): - # raise Exception('Multiple sequence types have been listed in tr,' - # 'but the number of run doesn\'t match') - - # # Check out this page for all the builtin errors: - # # https://docs.python.org/3/library/exceptions.html#bltin-exceptions - - # # if multiple runs of same sequence in recording - pad the list with same value - # if len(tr_list) == 1: - # tr_list = tr_list * len(ntp_list) - - # # Import right interface to read the file - # if ftype == 'acq': - # from phys2bids.interfaces.acq import populate_phys_input - # elif ftype == 'txt': - # from phys2bids.interfaces.txt import populate_phys_input - # else: - # # #!# We should add a logger here. - # raise NotImplementedError('Currently unsupported file type.') - - # # Actually read file! - # LGR.info(f'Reading the file {infile}') - # phys_in = populate_phys_input(infile, chtrig) # phys_in is a BlueprintInput object - # LGR.info('Reading infos') - # phys_in.print_info(filename) - - # if chplot != '' or info: - # viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, - # phys_in.freq, infile, chplot) - # # If only info were asked, end here. - # if info: - # return - - # Sum of values in ntp_list should be equivalent to num_timepoints_found - phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, - num_timepoints_expected=sum(ntp_list), - tr=1) # TODO : define a non-hard-coded value - - # Check that sum(ntp_list) is equivalent to num_timepoints_found, else bye! - # num_timepoints_found becomes an attribute of the object when you call check_trigger_amount - if phys_in.num_timepoints_found != sum(ntp_list): - raise Exception() # not sure if it's the good one ← you can use a general "Exception" - # TODO : automatize tps correction # Initialize dictionaries to save phys_in endpoints - run_endpoints = {} + run_timestamps = {} # initialise start index as 0 start_index = 0 @@ -152,25 +51,16 @@ def split2phys(filename, info=False, indir='.', outdir='.', chtrig=1, # Save start: and end_index in dictionary # While saving, add the padding - run_endpoints[run_idx] = (start_index, (end_index + padding)) + run_timestamps[run_idx] = (start_index, (end_index + padding)) # set start_index for next run as end_index of this one start_index = end_index # phys_in.start_at_time(start_index) + # NOTE : if we aim for updating indexes, how do we keep original timestamps ? # make dict exportable # or call it from phys2bids # or call phys2bids from here # or integrate this bit of code in phys2bids and adapt main parser by accepting # lists and adding -run argument - - -def _main(argv=None): - - options = _get_parser().parse_args(argv) - split2phys(**vars(options)) - - -if __name__ == '__main__': - _main() From e49f554d54d29d9591b870dbf6a249aa7a882d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 28 Apr 2020 10:39:22 -0400 Subject: [PATCH 025/180] updating --- phys2bids/phys2bids.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 2a1ed3c95..00f4e15ce 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -193,12 +193,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): """ Main workflow of phys2bids. -<<<<<<< HEAD Run the parser, does some checks on input, then imports -======= - - Runs the parser, does some checks on input, then imports ->>>>>>> 8df362490de26c613555ead3c3f034fd920c3b05 the right interface file to read the input. If only info is required, it returns a summary onscreen. Otherwise, it operates on the input to return a .tsv.gz file, possibily From 4356fab759a9218c666c9f3981ce719e413c206b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 28 Apr 2020 11:14:18 -0400 Subject: [PATCH 026/180] adding smoia changes --- phys2bids/physio_obj.py | 119 +++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 38 deletions(-) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 45d2985e1..b0ff784db 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -15,7 +15,8 @@ def is_valid(var, var_type, list_type=None): """ - Checks that the var is of a certain type. + Check that the var is of a certain type. + If type is list and list_type is specified, checks that the list contains list_type. @@ -50,7 +51,8 @@ def is_valid(var, var_type, list_type=None): def has_size(var, data_size, token): """ - Checks that the var has the same dimension of the data + Check that the var has the same dimension of the data. + If it's not the case, fill in the var or removes exceding var entry. Parameters @@ -82,6 +84,7 @@ def has_size(var, data_size, token): class BlueprintInput(): """ Main input object for phys2bids. + Contains the blueprint to be populated. !!! Pay attention: there's rules on how to populate this object. See below ("Attention") !!! @@ -90,7 +93,7 @@ class BlueprintInput(): ---------- timeseries : (ch, [tps]) list List of numpy 1d arrays - one for channel, plus one for time. - Time channel has to be the first, trigger the second. + Time channel has to be the first. Contains all the timeseries recorded. Supports different frequencies! freq : (ch) list of floats @@ -102,14 +105,14 @@ class BlueprintInput(): in the output files. units : (ch) list of strings List of the units of the channels. + trigger_idx : int + The trigger index. Optional. Default is 0. num_timepoints_found: int or None Amount of timepoints found in the automatic count. This is initialised as "None" and then computed internally, *if* check_trigger_amount() is run thr: float Threshold used by check_trigger_amount() to detect trigger points. - trig_idx: int - Index of first trigger, computed *if* check_trigger_amount() is run Methods ------- @@ -130,32 +133,35 @@ class BlueprintInput(): Notes ----- The timeseries (and as a consequence, all the other properties) - should start with an entry for time and an entry for trigger. - Both should have the same length - hence same sampling. Meaning: + should start with an entry for time. + It should have the same length of the trigger - hence same sampling. + Meaning: - timeseries[0] → ndarray representing time - - timeseries[1] → ndarray representing trigger - - timeseries[0].shape == timeseries[1].shape + - timeseries[chtrig] → ndarray representing trigger + - timeseries[0].shape == timeseries[chtrig].shape As a consequence: - - freq[0] == freq[1] + - freq[0] == freq[chtrig] - ch_name[0] = 'time' - - ch_name[1] = 'trigger' - units[0] = 's' - - Actual number of channels (ANC) +1 <= ch_amount <= ANC +2 + - Actual number of channels +1 <= ch_amount """ - def __init__(self, timeseries, freq, ch_name, units): + + def __init__(self, timeseries, freq, ch_name, units, trigger_idx): + """Initialise BlueprintInput (see class docstring).""" self.timeseries = is_valid(timeseries, list, list_type=np.ndarray) self.freq = has_size(is_valid(freq, list, list_type=(int, float)), self.ch_amount, 0.0) self.ch_name = has_size(ch_name, self.ch_amount, 'unknown') self.units = has_size(units, self.ch_amount, '[]') + self.trigger_idx = is_valid(trigger_idx, int) self.num_timepoints_found = None @property def ch_amount(self): """ - Property. Returns number of channels (ch). + Property. Return number of channels (ch). Returns ------- @@ -164,17 +170,55 @@ def ch_amount(self): """ return len(self.timeseries) - def rename_channels(self, new_names, ch_trigger=None): + def __getitem__(self, idx): + """ + Return a copy of the object with a sliced version of self.timeseries. + + The slicing is based on the trigger. If necessary, computes a sort of + interpolation to get the right index in multifreq. + + Parameters + ---------- + idx: int, tuple of int or slicer + indexes to use to slice the timeseries. + + Returns + ------- + BlueprintInput object + a copy of the object with the part of timeseries expressed by idx. + """ + sliced_timeseries = [] + + if isinstance(idx, int): + idx = slice(idx, idx + 1) + + if not self.trigger_idx: + self.trigger_idx = 0 + + for n, channel in enumerate(self.timeseries): + idx_dict = {'start': idx.start, 'stop': idx.stop, 'step': idx.step} + for i in ['start', 'stop', 'step']: + if idx_dict[i]: + idx_dict[i] = int(np.floor(self.freq[n] + / self.freq[self.trigger_idx] + * idx_dict[i])) + + new_idx = slice(idx_dict['start'], idx_dict['stop'], idx_dict['step']) + sliced_timeseries[n] = channel[new_idx] + + return BlueprintInput(sliced_timeseries, self.freq, self.ch_name, + self.units, self.trigger_idx) + + def rename_channels(self, new_names): """ - Renames the channels. If 'time' or 'trigger' were specified, - it makes sure that they're the first and second entry. + Rename the channels. + + If 'time' was specified, it makes sure that it's the first entry. Parameters ---------- new_names: list of str New names for channels. - ch_trigger: - Number of the channel containing the trigger. Notes ----- @@ -192,8 +236,7 @@ def rename_channels(self, new_names, ch_trigger=None): def return_index(self, idx): """ - Returns the proper list entry of all the - properties of the object, given an index. + Return the list entries of all the object properties, given an index. Parameters ---------- @@ -211,8 +254,7 @@ def return_index(self, idx): def delete_at_index(self, idx): """ - Returns all the proper list entry of the - properties of the object, given an index. + Delete the list entries of the object properties, given their index. Parameters ---------- @@ -230,19 +272,22 @@ def delete_at_index(self, idx): Removes element at index idx self.units: Removes element at index idx - self.ch_amount: - In all the property that are lists, the element correspondent to - `idx` gets deleted + self.trigger_idx: + If the deleted index was the one of the trigger, set the trigger idx to 0. """ del self.timeseries[idx] del self.freq[idx] del self.ch_name[idx] del self.units[idx] - def check_trigger_amount(self, chtrig=1, thr=None, num_timepoints_expected=0, tr=0): + if self.trigger_idx == idx: + LGR.warning('Removing trigger channel - are you sure you are doing' + 'the right thing?') + self.trigger_idx = 0 + + def check_trigger_amount(self, thr=None, num_timepoints_expected=0, tr=0): """ - Counts trigger points and corrects time offset in - the list representing time. + Count trigger points and correct time offset in channel "time". Parameters ---------- @@ -266,14 +311,12 @@ def check_trigger_amount(self, chtrig=1, thr=None, num_timepoints_expected=0, tr self.timeseries: The property `timeseries` is shifted with the 0 being the time of first trigger. - self.trig_idx: - Property of the `BlueprintInput` class. - Contains index of first trigger """ LGR.info('Counting trigger points') # Use the trigger channel to find the TRs, # comparing it to a given threshold. - trigger = self.timeseries[chtrig] + trigger = self.timeseries[self.trigger_idx] + LGR.info(f'The trigger is in channel {self.trigger_idx}') flag = 0 if thr is None: thr = np.mean(trigger) + 2 * np.std(trigger) @@ -322,7 +365,6 @@ def check_trigger_amount(self, chtrig=1, thr=None, num_timepoints_expected=0, tr self.thr = thr self.timeseries[0] -= time_offset self.num_timepoints_found = num_timepoints_found - self.trig_idx = timepoints.argmax() def print_info(self, filename): """ @@ -353,6 +395,7 @@ def print_info(self, filename): class BlueprintOutput(): """ Main output object for phys2bids. + Contains the blueprint to be exported. Attributes @@ -386,7 +429,9 @@ class BlueprintOutput(): init_from_blueprint: method to populate from input blueprint instead of init """ + def __init__(self, timeseries, freq, ch_name, units, start_time): + """Initialise BlueprintOutput (see class docstring).""" self.timeseries = is_valid(timeseries, np.ndarray) self.freq = is_valid(freq, (int, float)) self.ch_name = has_size(ch_name, self.ch_amount, 'unknown') @@ -407,8 +452,7 @@ def ch_amount(self): def return_index(self, idx): """ - Returns all the proper list entry of the - properties of the object, given an index. + Return the list entries of all the object properties, given an index. Parameters ---------- @@ -426,8 +470,7 @@ def return_index(self, idx): def delete_at_index(self, idx): """ - Returns all the proper list entry of the - properties of the object, given an index. + Delete the list entries of the object properties, given their index. Parameters ---------- From f8d986323022411259d7a46d1357606ee24f2d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 28 Apr 2020 12:20:14 -0400 Subject: [PATCH 027/180] adjusting split and main workflow to smoia changes --- phys2bids/phys2bids.py | 5 +++-- phys2bids/split2phys.py | 39 ++++++++++++++++----------------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 00f4e15ce..52eb28eb2 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -34,7 +34,7 @@ from numpy import savetxt, ones -from phys2bids import utils, viz, _version +from phys2bids import utils, viz, _version, split2phys from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput @@ -294,7 +294,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # TODO : automatize tps correction # CALL SPLIT2PHYS, give it BlueprintInput object and lists - # it will give a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} + run_idx = split2phys(phys_in, num_timepoints_expected, tr) + # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # ideally, we'd want to have a figure for each run # Create trigger plot. If possible, to have multiple outputs in the same diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index fa254eda5..b3a71b4c3 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -16,8 +16,7 @@ def split2phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ]): """ Parallel workflow of phys2bids. - Runs the split parser, does some check on inputs and exports - end indexes of each run based on npt_list and tr_list + Arguments --------- @@ -30,37 +29,31 @@ def split2phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ]): # Initialize dictionaries to save phys_in endpoints run_timestamps = {} - # initialise start index as 0 - start_index = 0 - for run_idx, run_tps in enumerate(ntp_list): - # ascertain run length and initialise Blueprint object + + # initialise Blueprint object with run info phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) # define padding - 20s * freq of trigger - padding is in nb of samples - padding = 20 * phys_in.freq[chtrig] + padding = 20 * phys_in.freq[0] - # LET'S START NOT SUPPORTING MULTIFREQ - end_index is nb of samples in run+start+first_trig - end_index = run_tps * tr_list[run_idx] * phys_in.freq[chtrig] + \ - start_index + phys_in.trig_idx + # initialise start of run as index of first trigger (after padd of last run if not first) + run_start = phys_in.trigger_idx + + # LET'S START NOT SUPPORTING MULTIFREQ - end_index is nb of samples in run+first_trig + run_end = run_tps * tr_list[run_idx] * phys_in.freq[0] + phys_in.trigger_idx # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording - if phys_in.timeseries[chtrig].shape[0] < (end_index + padding): - padding = phys_in.timeseries[chtrig].shape[0] - end_index + if phys_in.timeseries[0].shape[0] < (run_end + padding): + padding = phys_in.timeseries[0].shape[0] - run_end # Save start: and end_index in dictionary # While saving, add the padding - run_timestamps[run_idx] = (start_index, (end_index + padding)) - - # set start_index for next run as end_index of this one - start_index = end_index + run_timestamps[run_idx] = ((run_start - padding), (run_end + padding)) - # phys_in.start_at_time(start_index) - # NOTE : if we aim for updating indexes, how do we keep original timestamps ? + # update obj - SHOULD IT BE THE NEW START IDX OR A TUPLE (new_start,same_end ) + phys_in.__getitem__(run_end) + # NOTE : how do we keep original timestamps ? - # make dict exportable - # or call it from phys2bids - # or call phys2bids from here - # or integrate this bit of code in phys2bids and adapt main parser by accepting - # lists and adding -run argument + return(run_timestamps) From d6e3e1e496270038a2c016918798e48c7fdc7678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 30 Apr 2020 16:10:36 -0400 Subject: [PATCH 028/180] reformatting run definition with indexes --- issue_36.md | 25 --------------- phys2bids/phys2bids.py | 11 +++++-- phys2bids/split2phys.py | 71 ++++++++++++++++++++++++----------------- 3 files changed, 49 insertions(+), 58 deletions(-) delete mode 100644 issue_36.md diff --git a/issue_36.md b/issue_36.md deleted file mode 100644 index 3890bc358..000000000 --- a/issue_36.md +++ /dev/null @@ -1,25 +0,0 @@ -# Segment recordings by runs - -#36 -Ideas for name : split4phys, split4runs, obj2split, split4bids, rec2run, phys2runs, ses2run, 4run2split - -# The option we settled for : -Integrate the splitting function with phys2bids. - -# What we need to do -## 1.Adapt phys2bids parser arguments, in `run.py` -- [x] tr type, from float to list -- [x] ntp type, from int to list -- [ ] add a check for those lists in a different issue -- [ ] (uncertain) either add argument to specify the file is multi-run, or detect if list contains multiple item. -## 2.In phys2bids, ascertain lists length -- [x] Raise appropriate errors (e.g. ntp list has 1 item but `-run` is True, ntp has more than 1 item and tr has more) -- [x] add padding of ones to tr when non-equivalent list -- [x] Insert If... else statements to redirect. -- [x] check that sum(ntp_list) gives the right amount of trigger tps -- [x] Figure out where to call split2phys -## 3.In split2phys, -- [x] adapt check_trigger_amount section -- [ ] Create dictionary with start, end index -- [ ] Adapt `phys_out` to deal with multi-run, -- [ ] and eventually, multi-freq files (in a different issue). diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 52eb28eb2..7990dc74b 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -290,14 +290,16 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Check that sum(ntp_list) is equivalent to num_timepoints_found, else call split2phys if phys_in.num_timepoints_found != sum(num_timepoints_expected): - raise Exception() # not sure if it's the good one ← you can use a general "Exception" + raise Exception('The number of triggers found is different than expected') # TODO : automatize tps correction - # CALL SPLIT2PHYS, give it BlueprintInput object and lists - run_idx = split2phys(phys_in, num_timepoints_expected, tr) + # CALL run4phys, give it BlueprintInput object and lists + run_idx = split4phys(phys_in, num_timepoints_expected, tr) # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # ideally, we'd want to have a figure for each run + # OK NOW, HOW DO WE DEAL WITH THIS DICT? + # Create trigger plot. If possible, to have multiple outputs in the same # place, adds sub and ses label. # NOTE : and len(tr)<2 ? @@ -339,6 +341,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info(f'Preparing {output_amount} output files.') phys_out = {} # create phys_out dict that will have a + + # BLUEPRINT OBJECTS PER RUN + # blueprint object per frequency # for each different frequency for uniq_freq in uniq_freq_list: diff --git a/phys2bids/split2phys.py b/phys2bids/split2phys.py index b3a71b4c3..e1febcc79 100644 --- a/phys2bids/split2phys.py +++ b/phys2bids/split2phys.py @@ -1,59 +1,70 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" - -A parallel CLI utility to segment the physiological input files. - -Cuts the physiological recording files into multiple runs -with padding at start and end - -""" from phys2bids.physio_obj.BlueprintInput import check_trigger_amount -def split2phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ]): +def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): """ - Parallel workflow of phys2bids. + Utility for phys2bids. + Returns a dictionary item for each run in BlueprintInput object based on user's entries + Each tuple expresses the timestamps of runs in nb of samples(based on trigger channel) + Timestamps are the index of first and last triggers of a run, adjusted with padding + run_start and run_end indexes refer to the samples contained in the whole session - - Arguments + Parameters --------- - + phys_in : object + BlueprintInput object returned by Class in `physio_obj.py` + ntp_list : list + a list of integers given by the user, defined by `num_timepoints_expected` + Default: [0, ] + tr_list : list + a list of float given by the user, defined by `tr` + Default: [1,] Returns -------- - ... + run_timestamps : dictionary + Containing tuples of run start and end indexes for each run, based on trigger channels + In the form of run_timestamps{run_idx:(start, end), run_idx:...} """ # Initialize dictionaries to save phys_in endpoints run_timestamps = {} - + # run_start = 0 for run_idx, run_tps in enumerate(ntp_list): - # initialise Blueprint object with run info + # (re)initialise Blueprint object with current run info - correct time offset phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) - # define padding - 20s * freq of trigger - padding is in nb of samples - padding = 20 * phys_in.freq[0] + # define padding - 9s * freq of trigger - padding is in nb of samples + padding = padding * phys_in.freq[0] + + # initialise start of run as index of first trigger minus the padding + run_start = phys_in.timeseries[0].index(0) - padding - # initialise start of run as index of first trigger (after padd of last run if not first) - run_start = phys_in.trigger_idx + # run length in seconds + end_sec = (run_tps * tr_list[run_idx]) - # LET'S START NOT SUPPORTING MULTIFREQ - end_index is nb of samples in run+first_trig - run_end = run_tps * tr_list[run_idx] * phys_in.freq[0] + phys_in.trigger_idx + # define index of the run's last trigger + run_end = phys_in.timeseries[0].index(end_sec) # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording if phys_in.timeseries[0].shape[0] < (run_end + padding): padding = phys_in.timeseries[0].shape[0] - run_end - # Save start: and end_index in dictionary - # While saving, add the padding - run_timestamps[run_idx] = ((run_start - padding), (run_end + padding)) - - # update obj - SHOULD IT BE THE NEW START IDX OR A TUPLE (new_start,same_end ) - phys_in.__getitem__(run_end) - # NOTE : how do we keep original timestamps ? - + # update the object so that it will look for the first trigger after previous run end + phys_in = phys_in[(run_end + 1):] + + # Save start and end_index in dictionary + # While saving, add the padding for end index + # keeps original timestamps by adjusting the indexes with previous end_index + if run_idx > 0: + run_start = run_start + run_timestamps[run_idx - 1][1] + run_end = run_end + run_timestamps[run_idx - 1][1] + run_timestamps[run_idx] = (run_start, run_end + padding) + else: + run_timestamps[run_idx] = (run_start, run_end + padding) return(run_timestamps) From 80546a100a9ce80f270b730de1b912a35be9260f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 30 Apr 2020 16:15:47 -0400 Subject: [PATCH 029/180] renaming function --- phys2bids/{split2phys.py => split4phys.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename phys2bids/{split2phys.py => split4phys.py} (100%) diff --git a/phys2bids/split2phys.py b/phys2bids/split4phys.py similarity index 100% rename from phys2bids/split2phys.py rename to phys2bids/split4phys.py From 4754d50da0bbf5b195e419e8fd9a0628850903fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 8 May 2020 11:42:15 -0400 Subject: [PATCH 030/180] merging smoia changes and restruc multirun sec in phys2bids --- phys2bids/phys2bids.py | 35 ++++++------- phys2bids/physio_obj.py | 108 +++++++++++++++++++++++++++++++++++++--- phys2bids/split4phys.py | 10 ++-- 3 files changed, 125 insertions(+), 28 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 7990dc74b..462c839f8 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -271,9 +271,25 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, phys_in.freq, infile, chplot) + # Create trigger plot. If possible, to have multiple outputs in the same + # place, adds sub and ses label. + # NOTE : and len(tr)<2 ? + if tr != 0 and num_timepoints_expected != 0: + # Run analysis on trigger channel to get first timepoint and the time offset. + # #!# Get option of no trigger! (which is wrong practice or Respiract) + phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected, tr) + LGR.info('Plot trigger') + plot_path = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + if sub: + plot_path += f'_sub-{sub}' + if ses: + plot_path += f'_ses-{ses}' + viz.plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], + plot_path, tr, phys_in.thr, num_timepoints_expected, filename) # Multi-run section # Check list length, more than 1 means multi-run - if len(num_timepoints_expected) > 1: + elif len(num_timepoints_expected) > 1: # if multiple runs of same sequence in recording - pad the list with arbitrary value # NOTE : we could also duplicate the item by multiplying with len(ntp) @@ -300,22 +316,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # OK NOW, HOW DO WE DEAL WITH THIS DICT? - # Create trigger plot. If possible, to have multiple outputs in the same - # place, adds sub and ses label. - # NOTE : and len(tr)<2 ? - if tr != 0 and num_timepoints_expected != 0: - # Run analysis on trigger channel to get first timepoint and the time offset. - # #!# Get option of no trigger! (which is wrong practice or Respiract) - phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected, tr) - LGR.info('Plot trigger') - plot_path = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) - if sub: - plot_path += f'_sub-{sub}' - if ses: - plot_path += f'_ses-{ses}' - viz.plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], - plot_path, tr, phys_in.thr, num_timepoints_expected, filename) else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using "-ntp" and "-tr" arguments') @@ -340,6 +340,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.warning(f'Found {output_amount} different frequencies in input!') LGR.info(f'Preparing {output_amount} output files.') + phys_out = {} # create phys_out dict that will have a # BLUEPRINT OBJECTS PER RUN diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index b0ff784db..58d13033b 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" -I/O objects for phys2bids. -""" +"""I/O objects for phys2bids.""" import logging from itertools import groupby @@ -81,6 +79,41 @@ def has_size(var, data_size, token): return var +def are_equal(self, other): + """ + Return test of equality between two objects. + + The equality is true if two objects are the same or + if one of the objects is equivalent to the dictionary + format of the other. + + Parameters + ---------- + other: + comparable object. + + Returns + ------- + boolean + """ + try: + return self.__dict__ == other.__dict__ + except ValueError: + return False + except AttributeError: + try: + return self.__dict__ == other + except ValueError: + return False + except AttributeError: + try: + return self == other.__dict__ + except ValueError: + return False + except AttributeError: + return self == other + + class BlueprintInput(): """ Main input object for phys2bids. @@ -176,6 +209,7 @@ def __getitem__(self, idx): The slicing is based on the trigger. If necessary, computes a sort of interpolation to get the right index in multifreq. + If the trigger was not specified, the slicing is based on the time instead. Parameters ---------- @@ -186,29 +220,76 @@ def __getitem__(self, idx): ------- BlueprintInput object a copy of the object with the part of timeseries expressed by idx. + + Raises + ------ + IndexError + If the idx, represented as a slice, is out of bounds. + + Notes + ----- + If idx is an integer, it returns an instantaneous moment for all channels. + If it's a slicing, it always return the full slice. This means that + potentially, depending on the frequencies, BlueprintInput[1] and + BlueprintInput[1:2] might return different results. """ - sliced_timeseries = [] + sliced_timeseries = [None] * self.ch_amount + return_instant = False + trigger_length = len(self.timeseries[self.trigger_idx]) + if not self.trigger_idx: + self.trigger_idx = 0 + + # If idx is an integer, return an "instantaneous slice" and initialise slice if isinstance(idx, int): + return_instant = True + if idx < 0: + idx = trigger_length + idx + idx = slice(idx, idx + 1) - if not self.trigger_idx: - self.trigger_idx = 0 + if idx.start >= trigger_length or idx.stop > trigger_length: + raise IndexError(f'slice ({idx.start}, {idx.stop}) is out of ' + f'bounds for channel {self.trigger_idx} ' + f'with size {trigger_length}') + # Operate on each channel on its own for n, channel in enumerate(self.timeseries): idx_dict = {'start': idx.start, 'stop': idx.stop, 'step': idx.step} + # Adapt the slicing indexes to the right requency for i in ['start', 'stop', 'step']: if idx_dict[i]: idx_dict[i] = int(np.floor(self.freq[n] / self.freq[self.trigger_idx] * idx_dict[i])) + # Correct the slicing stop if necessary + if idx_dict['start'] == idx_dict['stop'] or return_instant: + idx_dict['stop'] = idx_dict['start'] + 1 + elif trigger_length == idx.stop: + idx_dict['stop'] = len(channel) + new_idx = slice(idx_dict['start'], idx_dict['stop'], idx_dict['step']) sliced_timeseries[n] = channel[new_idx] return BlueprintInput(sliced_timeseries, self.freq, self.ch_name, self.units, self.trigger_idx) + def __eq__(self, other): + """ + Return test of equality between two objects. + + Parameters + ---------- + other: + comparable object. + + Returns + ------- + boolean + """ + return are_equal(self, other) + def rename_channels(self, new_names): """ Rename the channels. @@ -450,6 +531,21 @@ def ch_amount(self): """ return self.timeseries.shape[1] + def __eq__(self, other): + """ + Return test of equality between two objects. + + Parameters + ---------- + other: + comparable object. + + Returns + ------- + boolean + """ + return are_equal(self, other) + def return_index(self, idx): """ Return the list entries of all the object properties, given an index. diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index e1febcc79..3e6c82110 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -8,20 +8,20 @@ def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): """ Utility for phys2bids. - Returns a dictionary item for each run in BlueprintInput object based on user's entries - Each tuple expresses the timestamps of runs in nb of samples(based on trigger channel) + Returns dictionary keys for each run in BlueprintInput object based on user's entries + Each key has a tuple expressing the timestamps of run in nb of samples(based on trigger chan) Timestamps are the index of first and last triggers of a run, adjusted with padding run_start and run_end indexes refer to the samples contained in the whole session Parameters --------- phys_in : object - BlueprintInput object returned by Class in `physio_obj.py` + Object returned by BlueprintInput class ntp_list : list - a list of integers given by the user, defined by `num_timepoints_expected` + a list of integers given by the user as `ntp` input Default: [0, ] tr_list : list - a list of float given by the user, defined by `tr` + a list of float given by the user as `tr` input Default: [1,] Returns -------- From 6d445ae667c7d703bb1cffb7ab35aa9cedcf622e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 8 May 2020 12:42:19 -0400 Subject: [PATCH 031/180] adding plot for runs --- phys2bids/phys2bids.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 462c839f8..c732112be 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -34,7 +34,7 @@ from numpy import savetxt, ones -from phys2bids import utils, viz, _version, split2phys +from phys2bids import utils, viz, _version, split4phys from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput @@ -193,7 +193,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): """ Main workflow of phys2bids. - Run the parser, does some checks on input, then imports + + Runs the parser, does some checks on input, then imports the right interface file to read the input. If only info is required, it returns a summary onscreen. Otherwise, it operates on the input to return a .tsv.gz file, possibily @@ -273,7 +274,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Create trigger plot. If possible, to have multiple outputs in the same # place, adds sub and ses label. - # NOTE : and len(tr)<2 ? if tr != 0 and num_timepoints_expected != 0: # Run analysis on trigger channel to get first timepoint and the time offset. # #!# Get option of no trigger! (which is wrong practice or Respiract) @@ -287,6 +287,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, plot_path += f'_ses-{ses}' viz.plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], plot_path, tr, phys_in.thr, num_timepoints_expected, filename) + # Multi-run section # Check list length, more than 1 means multi-run elif len(num_timepoints_expected) > 1: @@ -312,10 +313,23 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # CALL run4phys, give it BlueprintInput object and lists run_idx = split4phys(phys_in, num_timepoints_expected, tr) # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} - # ideally, we'd want to have a figure for each run - - # OK NOW, HOW DO WE DEAL WITH THIS DICT? + # ideally, we'd want to have a figure for each run + for idx, times in enumerate(run_idx): + LGR.info('Plot trigger') + plot_path = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + if sub: + plot_path += f'_sub-{sub}' + if ses: + plot_path += f'_ses-{ses}' + + # adjust filename to run indexes + + viz.plot_trigger(phys_in[times[0]:times[1]].timeseries[0], + phys_in[times[0]:times[1]].timeseries[chtrig], + plot_path, tr[idx], phys_in.thr, + num_timepoints_expected[idx], filename) else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using "-ntp" and "-tr" arguments') From 519884027cb1ebbca8aeee142c2163fa779bfd9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 8 May 2020 13:02:30 -0400 Subject: [PATCH 032/180] lintering --- phys2bids/cli/run.py | 7 +++---- phys2bids/phys2bids.py | 2 +- phys2bids/split4phys.py | 7 ++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index 670050a1a..0b3b8cf3c 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -""" -Parser for phys2bids -""" +"""Parser for phys2bids.""" + import argparse @@ -10,7 +9,7 @@ def _get_parser(): """ - Parses command line inputs for this function + Parse command line inputs for this function. Returns ------- diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index c732112be..3ea0aedc3 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -192,7 +192,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, sub=None, ses=None, chtrig=0, chsel=None, num_timepoints_expected=0, tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): """ - Main workflow of phys2bids. + Run main workflow of phys2bids. Runs the parser, does some checks on input, then imports the right interface file to read the input. If only info is required, diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 3e6c82110..9c6912af7 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from phys2bids.physio_obj.BlueprintInput import check_trigger_amount - def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): """ - Utility for phys2bids. + Split runs for phys2bids. - Returns dictionary keys for each run in BlueprintInput object based on user's entries + Returns dictionary key for each run in BlueprintInput object based on user's entries Each key has a tuple expressing the timestamps of run in nb of samples(based on trigger chan) Timestamps are the index of first and last triggers of a run, adjusted with padding run_start and run_end indexes refer to the samples contained in the whole session @@ -29,7 +27,6 @@ def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): Containing tuples of run start and end indexes for each run, based on trigger channels In the form of run_timestamps{run_idx:(start, end), run_idx:...} """ - # Initialize dictionaries to save phys_in endpoints run_timestamps = {} # run_start = 0 From d7fd362e00b6f8d37b90c0b3f1414d1e93f0feeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 19 May 2020 18:15:12 -0400 Subject: [PATCH 033/180] minor comment --- phys2bids/split4phys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 9c6912af7..37e4ca39f 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -27,7 +27,7 @@ def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): Containing tuples of run start and end indexes for each run, based on trigger channels In the form of run_timestamps{run_idx:(start, end), run_idx:...} """ - # Initialize dictionaries to save phys_in endpoints + # Initialize dictionaries to save phys_in slices run_timestamps = {} # run_start = 0 for run_idx, run_tps in enumerate(ntp_list): @@ -38,7 +38,7 @@ def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): # define padding - 9s * freq of trigger - padding is in nb of samples padding = padding * phys_in.freq[0] - # initialise start of run as index of first trigger minus the padding + # initialise start of run as index of first trigger (starts at 0 sec) minus the padding run_start = phys_in.timeseries[0].index(0) - padding # run length in seconds From 1bf790523aa12ace33c2d5ca02b787fa25ec2974 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 22 May 2020 15:07:42 +0200 Subject: [PATCH 034/180] Reorganising some code --- phys2bids/phys2bids.py | 133 ++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 3ea0aedc3..844a6e3be 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -272,67 +272,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, phys_in.freq, infile, chplot) - # Create trigger plot. If possible, to have multiple outputs in the same - # place, adds sub and ses label. - if tr != 0 and num_timepoints_expected != 0: - # Run analysis on trigger channel to get first timepoint and the time offset. - # #!# Get option of no trigger! (which is wrong practice or Respiract) - phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected, tr) - LGR.info('Plot trigger') - plot_path = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) - if sub: - plot_path += f'_sub-{sub}' - if ses: - plot_path += f'_ses-{ses}' - viz.plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], - plot_path, tr, phys_in.thr, num_timepoints_expected, filename) - - # Multi-run section - # Check list length, more than 1 means multi-run - elif len(num_timepoints_expected) > 1: - - # if multiple runs of same sequence in recording - pad the list with arbitrary value - # NOTE : we could also duplicate the item by multiplying with len(ntp) - if len(tr) == 1: - tr = ones() * len(num_timepoints_expected) - # Check equivalency of length - elif len(num_timepoints_expected) != len(tr): - raise Exception('Number of sequence types listed with TR doesn\'t ' - 'match expected number of runs in the session') - # Sum of values in ntp_list should be equivalent to num_timepoints_found - phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, - num_timepoints_expected=sum(num_timepoints_expected), - tr=1) # TODO : define a non-hard-coded value - - # Check that sum(ntp_list) is equivalent to num_timepoints_found, else call split2phys - if phys_in.num_timepoints_found != sum(num_timepoints_expected): - raise Exception('The number of triggers found is different than expected') - # TODO : automatize tps correction - - # CALL run4phys, give it BlueprintInput object and lists - run_idx = split4phys(phys_in, num_timepoints_expected, tr) - # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} - - # ideally, we'd want to have a figure for each run - for idx, times in enumerate(run_idx): - LGR.info('Plot trigger') - plot_path = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) - if sub: - plot_path += f'_sub-{sub}' - if ses: - plot_path += f'_ses-{ses}' - - # adjust filename to run indexes - - viz.plot_trigger(phys_in[times[0]:times[1]].timeseries[0], - phys_in[times[0]:times[1]].timeseries[chtrig], - plot_path, tr[idx], phys_in.thr, - num_timepoints_expected[idx], filename) - else: - LGR.warning('Skipping trigger pulse count. If you want to run it, ' - 'call phys2bids using "-ntp" and "-tr" arguments') + # The next few lines remove the undesired channels from phys_in. if chsel: @@ -346,6 +286,77 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info('Renaming channels with given names') phys_in.rename_channels(ch_name) + # Create trigger plot. If possible, to have multiple outputs in the same + # place, adds sub and ses label. + if tr != [0, ] and num_timepoints_expected != [0, ]: + # Multi-run section + # Check list length, more than 1 means multi-run + if len(num_timepoints_expected) > 1: + # Comment #!# + if len(tr) == 1: + tr = ones(len(num_timepoints_expected)) * tr[0] + # Check equivalency of length + elif len(num_timepoints_expected) != len(tr): + raise Exception('Number of sequence types listed with TR ' + 'doesn\'t match expected number of runs in ' + 'the session') + + # Sum of values in ntp_list should be equivalent to num_timepoints_found + phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, + num_timepoints_expected=sum(num_timepoints_expected), + tr=1) + + # Check that sum(ntp_list) is equivalent to num_timepoints_found, + # else call split2phys + if phys_in.num_timepoints_found != sum(num_timepoints_expected): + raise Exception('The number of triggers found is different ' + 'than expected. Better stop now than breaking ' + 'something.') + + # CALL run4phys, give it BlueprintInput object and lists + run_idx = split4phys(phys_in, num_timepoints_expected, tr) + # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} + + # ideally, we'd want to have a figure for each run + for idx, times in enumerate(run_idx): + # The following 14 lines should become a function + LGR.info('Plot trigger') + plot_path = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + if sub: + plot_path += f'_sub-{sub}' + if ses: + plot_path += f'_ses-{ses}' + + # adjust filename to run indexes + + viz.plot_trigger(phys_in[times[0]:times[1]].timeseries[0], + phys_in[times[0]:times[1]].timeseries[chtrig], + plot_path, tr[idx], phys_in.thr, + num_timepoints_expected[idx], filename) + + else: + # Run analysis on trigger channel to get first timepoint + # and the time offset. + phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected[0], + tr[0]) + + # The following 9 lines should become a function (same as previous block) + LGR.info('Plot trigger') + plot_path = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + if sub: + plot_path += f'_sub-{sub}' + if ses: + plot_path += f'_ses-{ses}' + viz.plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], + plot_path, tr, phys_in.thr, num_timepoints_expected, + filename) + + else: + LGR.warning('Skipping trigger pulse count. If you want to run it, ' + 'call phys2bids using both "-ntp" and "-tr" arguments') + # The next few lines create a dictionary of different BlueprintInput # objects, one for each unique frequency in phys_in uniq_freq_list = set(phys_in.freq) From e535845fa7b2d1c598a7b8636a37eca0ec716296 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 22 May 2020 15:14:29 +0200 Subject: [PATCH 035/180] Adding back info stop --- phys2bids/phys2bids.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 844a6e3be..ff04a6d5c 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -272,7 +272,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, phys_in.freq, infile, chplot) - + # If only info were asked, end here. + if info: + return # The next few lines remove the undesired channels from phys_in. if chsel: From c018571582dd37b91f7b9a0390864c360362fd76 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 22 May 2020 15:27:06 +0200 Subject: [PATCH 036/180] Change dictionary loop --- phys2bids/phys2bids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index ff04a6d5c..cf59d36b2 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -320,7 +320,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # ideally, we'd want to have a figure for each run - for idx, times in enumerate(run_idx): + for idx in run_idx.keys(): # The following 14 lines should become a function LGR.info('Plot trigger') plot_path = os.path.join(outdir, @@ -330,8 +330,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if ses: plot_path += f'_ses-{ses}' + times = run_idx[idx] # adjust filename to run indexes - viz.plot_trigger(phys_in[times[0]:times[1]].timeseries[0], phys_in[times[0]:times[1]].timeseries[chtrig], plot_path, tr[idx], phys_in.thr, From ff0d992ac75cdbb1ea4b3aa04622f8b3f8314ab6 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 22 May 2020 15:35:55 +0200 Subject: [PATCH 037/180] Correct dictionary enumeration --- phys2bids/phys2bids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index cf59d36b2..2c356af30 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -320,7 +320,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # ideally, we'd want to have a figure for each run - for idx in run_idx.keys(): + for idx, key in enumerate(run_idx.keys()): # The following 14 lines should become a function LGR.info('Plot trigger') plot_path = os.path.join(outdir, @@ -330,7 +330,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if ses: plot_path += f'_ses-{ses}' - times = run_idx[idx] + times = run_idx[key] # adjust filename to run indexes viz.plot_trigger(phys_in[times[0]:times[1]].timeseries[0], phys_in[times[0]:times[1]].timeseries[chtrig], From bfe07a29fef4b91f2fac4a92c9c2ecb808631de7 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 22 May 2020 16:06:38 +0200 Subject: [PATCH 038/180] Code review --- phys2bids/split4phys.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 9c6912af7..d3f696045 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from numpy import where -def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): + +def split4phys(phys_in, ntp_list, tr_list, padding=9): """ Split runs for phys2bids. @@ -30,22 +32,28 @@ def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): # Initialize dictionaries to save phys_in endpoints run_timestamps = {} # run_start = 0 + + # define padding - 9s * freq of trigger - padding is in nb of samples + padding = padding * phys_in.freq[0] + for run_idx, run_tps in enumerate(ntp_list): # (re)initialise Blueprint object with current run info - correct time offset phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) - # define padding - 9s * freq of trigger - padding is in nb of samples - padding = padding * phys_in.freq[0] - # initialise start of run as index of first trigger minus the padding - run_start = phys_in.timeseries[0].index(0) - padding + if run_idx == 0: + run_start = 0 + else: + # use where + run_start = phys_in.timeseries[0].index(0) - padding # run length in seconds end_sec = (run_tps * tr_list[run_idx]) # define index of the run's last trigger - run_end = phys_in.timeseries[0].index(end_sec) + # run_end = find index of phys_in.timeseries[0] > end_sec + run_end = where(phys_in.timeseries[0] > end_sec) # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording @@ -59,9 +67,10 @@ def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): # While saving, add the padding for end index # keeps original timestamps by adjusting the indexes with previous end_index if run_idx > 0: - run_start = run_start + run_timestamps[run_idx - 1][1] - run_end = run_end + run_timestamps[run_idx - 1][1] - run_timestamps[run_idx] = (run_start, run_end + padding) - else: - run_timestamps[run_idx] = (run_start, run_end + padding) - return(run_timestamps) + previous_end_index = run_timestamps[run_idx - 1][1] + run_start = run_start + previous_end_index + run_end = run_end + previous_end_index + + run_timestamps[run_idx] = (run_start, run_end + padding) + + return run_timestamps From 422b4d12b626e2ee95f3d376bb56507166d09e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 24 May 2020 10:43:21 -0400 Subject: [PATCH 039/180] removing parralel workflow --- phys2bids/cli/run.py | 154 ------------------------------------------- 1 file changed, 154 deletions(-) delete mode 100644 phys2bids/cli/run.py diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py deleted file mode 100644 index 0b3b8cf3c..000000000 --- a/phys2bids/cli/run.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -"""Parser for phys2bids.""" - - -import argparse - -from phys2bids import __version__ - - -def _get_parser(): - """ - Parse command line inputs for this function. - - Returns - ------- - parser.parse_args() : argparse dict - - Notes - ----- - # Argument parser follow template provided by RalphyZ. - # https://stackoverflow.com/a/43456577 - """ - parser = argparse.ArgumentParser() - optional = parser._action_groups.pop() - required = parser.add_argument_group('Required Argument:') - required.add_argument('-in', '--input-file', - dest='filename', - type=str, - help='The name of the file containing physiological data, with or ' - 'without extension.', - required=True) - optional.add_argument('-info', '--info', - dest='info', - action='store_true', - help='Only output info about the file, don\'t process. ' - 'Default is to process.', - default=False) - optional.add_argument('-indir', '--input-dir', - dest='indir', - type=str, - help='Folder containing input. ' - 'Default is current folder.', - default='.') - optional.add_argument('-outdir', '--output-dir', - dest='outdir', - type=str, - help='Folder where output should be placed. ' - 'Default is current folder. ' - 'If \"-heur\" is used, it\'ll become ' - 'the site folder. Requires \"-sub\". ' - 'Optional to specify \"-ses\".', - default='.') - optional.add_argument('-heur', '--heuristic', - dest='heur_file', - type=str, - help='File containing heuristic, with or without ' - 'extension. This file is needed in order to ' - 'convert your input file to BIDS format! ' - 'If no path is specified, it assumes the file is ' - 'in the current folder. Edit the heur_ex.py file in ' - 'heuristics folder.', - default=None) - # optional.add_argument('-hdir', '--heur-dir', - # dest='heurdir', - # type=str, - # help='Folder containing heuristic file.', - # default='.') - optional.add_argument('-sub', '--subject', - dest='sub', - type=str, - help='Specify alongside \"-heur\". Code of ' - 'subject to process.', - default=None) - optional.add_argument('-ses', '--session', - dest='ses', - type=str, - help='Specify alongside \"-heur\". Code of ' - 'session to process.', - default=None) - # optional.add_argument('-run', '--multi-run', - # dest= run, - # help='' - # - # - optional.add_argument('-chtrig', '--channel-trigger', - dest='chtrig', - type=int, - help='The column number of the trigger channel. ' - 'Channel numbering starts with 0. ' - 'Default is 0.', - default=0) - optional.add_argument('-chsel', '--channel-selection', - dest='chsel', - nargs='*', - type=int, - help='The column numbers of the channels to process. ' - 'Default is to process all channels.', - default=None) - optional.add_argument('-ntp', '--numtps', - dest='num_timepoints_expected', - type=list, - help='Number of expected trigger timepoints (TRs). ' - 'Default is 0. Note: the estimation of beggining of ' - 'neuroimaging acquisition cannot take place with this default.' - 'Give a list of each expected ntp for multi-run recordings.', - default=[0, ]) - optional.add_argument('-tr', '--tr', - dest='tr', - type=list, - help='TR of sequence in seconds. ' - 'Default is 0 second.' - 'You can list each TR used throughout the session', - default=[0, ]) - optional.add_argument('-thr', '--threshold', - dest='thr', - type=float, - help='Threshold to use for trigger detection. ' - 'If "ntp" and "TR" are specified, phys2bids automatically computes ' - 'a threshold to detect the triggers. Use this parameter to set it ' - 'manually', - default=None) - optional.add_argument('-chnames', '--channel-names', - dest='ch_name', - nargs='*', - type=str, - help='Column header (for json file output).', - default=[]) - optional.add_argument('-chplot', '--channels-plot', - dest='chplot', - type=str, - help='full path to store channels plot ', - default='') - optional.add_argument('-debug', '--debug', - dest='debug', - action='store_true', - help='Only print debugging info to log file. Default is False.', - default=False) - optional.add_argument('-quiet', '--quiet', - dest='quiet', - action='store_true', - help='Only print warnings to log file. Default is False.', - default=False) - optional.add_argument('-v', '--version', action='version', - version=('%(prog)s ' + __version__)) - - parser._action_groups.append(optional) - - return parser - - -if __name__ == '__main__': - raise RuntimeError('phys2bids/cli/run.py should not be run directly;\n' - 'Please `pip install` phys2bids and use the ' - '`phys2bids` command') From 45d27b7a579340fffffd9f5d4f336303c591a671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 24 May 2020 10:49:40 -0400 Subject: [PATCH 040/180] comments and doc string --- phys2bids/split4phys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 37e4ca39f..0c6df718f 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -44,7 +44,7 @@ def split4phys(phys_in=None, ntp_list=[0, ], tr_list=[1, ], padding=9): # run length in seconds end_sec = (run_tps * tr_list[run_idx]) - # define index of the run's last trigger + # define index of the run's last trigger with user input run_end = phys_in.timeseries[0].index(end_sec) # if the padding is too much for the remaining timeseries length From 2420294e57cff7489f74f3c2952bd1caed275221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 24 May 2020 10:50:44 -0400 Subject: [PATCH 041/180] linter and multi-run section --- phys2bids/phys2bids.py | 58 +++++------------------------------------- 1 file changed, 7 insertions(+), 51 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 3ea0aedc3..0162a566c 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -32,9 +32,9 @@ from copy import deepcopy from pathlib import Path -from numpy import savetxt, ones +from numpy import savetxt -from phys2bids import utils, viz, _version, split4phys +from phys2bids import utils, viz, _version from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput @@ -192,7 +192,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, sub=None, ses=None, chtrig=0, chsel=None, num_timepoints_expected=0, tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): """ - Run main workflow of phys2bids. + Main workflow of phys2bids. Runs the parser, does some checks on input, then imports the right interface file to read the input. If only info is required, @@ -271,13 +271,16 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if chplot != '' or info: viz.plot_all(phys_in.ch_name, phys_in.timeseries, phys_in.units, phys_in.freq, infile, chplot) + # If only info were asked, end here. + if info: + return # Create trigger plot. If possible, to have multiple outputs in the same # place, adds sub and ses label. if tr != 0 and num_timepoints_expected != 0: # Run analysis on trigger channel to get first timepoint and the time offset. # #!# Get option of no trigger! (which is wrong practice or Respiract) - phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected, tr) + phys_in.check_trigger_amount(thr, num_timepoints_expected, tr) LGR.info('Plot trigger') plot_path = os.path.join(outdir, os.path.splitext(os.path.basename(filename))[0]) @@ -287,49 +290,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, plot_path += f'_ses-{ses}' viz.plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], plot_path, tr, phys_in.thr, num_timepoints_expected, filename) - - # Multi-run section - # Check list length, more than 1 means multi-run - elif len(num_timepoints_expected) > 1: - - # if multiple runs of same sequence in recording - pad the list with arbitrary value - # NOTE : we could also duplicate the item by multiplying with len(ntp) - if len(tr) == 1: - tr = ones() * len(num_timepoints_expected) - # Check equivalency of length - elif len(num_timepoints_expected) != len(tr): - raise Exception('Number of sequence types listed with TR doesn\'t ' - 'match expected number of runs in the session') - # Sum of values in ntp_list should be equivalent to num_timepoints_found - phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, - num_timepoints_expected=sum(num_timepoints_expected), - tr=1) # TODO : define a non-hard-coded value - - # Check that sum(ntp_list) is equivalent to num_timepoints_found, else call split2phys - if phys_in.num_timepoints_found != sum(num_timepoints_expected): - raise Exception('The number of triggers found is different than expected') - # TODO : automatize tps correction - - # CALL run4phys, give it BlueprintInput object and lists - run_idx = split4phys(phys_in, num_timepoints_expected, tr) - # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} - - # ideally, we'd want to have a figure for each run - for idx, times in enumerate(run_idx): - LGR.info('Plot trigger') - plot_path = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) - if sub: - plot_path += f'_sub-{sub}' - if ses: - plot_path += f'_ses-{ses}' - - # adjust filename to run indexes - - viz.plot_trigger(phys_in[times[0]:times[1]].timeseries[0], - phys_in[times[0]:times[1]].timeseries[chtrig], - plot_path, tr[idx], phys_in.thr, - num_timepoints_expected[idx], filename) else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using "-ntp" and "-tr" arguments') @@ -354,11 +314,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.warning(f'Found {output_amount} different frequencies in input!') LGR.info(f'Preparing {output_amount} output files.') - phys_out = {} # create phys_out dict that will have a - - # BLUEPRINT OBJECTS PER RUN - # blueprint object per frequency # for each different frequency for uniq_freq in uniq_freq_list: From 0b21fadcb4e39165ec2ec98742282796120a26b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 24 May 2020 11:07:37 -0400 Subject: [PATCH 042/180] changing argument name for chtrig --- phys2bids/interfaces/acq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/interfaces/acq.py b/phys2bids/interfaces/acq.py index 0e5ee8b82..7ea3e4ca7 100644 --- a/phys2bids/interfaces/acq.py +++ b/phys2bids/interfaces/acq.py @@ -50,4 +50,4 @@ def populate_phys_input(filename, chtrig): units.append(ch.units) names.append(ch.name) - return BlueprintInput(timeseries, freq, names, units) + return BlueprintInput(timeseries, freq, names, units, chtrig) From 632d404dbe1a08ec5ff83facd7e17a6b36e3efa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 24 May 2020 11:10:26 -0400 Subject: [PATCH 043/180] merging smoia improvements --- phys2bids/physio_obj.py | 108 +++------------------------------------- 1 file changed, 6 insertions(+), 102 deletions(-) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 58d13033b..b0ff784db 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""I/O objects for phys2bids.""" +""" +I/O objects for phys2bids. +""" import logging from itertools import groupby @@ -79,41 +81,6 @@ def has_size(var, data_size, token): return var -def are_equal(self, other): - """ - Return test of equality between two objects. - - The equality is true if two objects are the same or - if one of the objects is equivalent to the dictionary - format of the other. - - Parameters - ---------- - other: - comparable object. - - Returns - ------- - boolean - """ - try: - return self.__dict__ == other.__dict__ - except ValueError: - return False - except AttributeError: - try: - return self.__dict__ == other - except ValueError: - return False - except AttributeError: - try: - return self == other.__dict__ - except ValueError: - return False - except AttributeError: - return self == other - - class BlueprintInput(): """ Main input object for phys2bids. @@ -209,7 +176,6 @@ def __getitem__(self, idx): The slicing is based on the trigger. If necessary, computes a sort of interpolation to get the right index in multifreq. - If the trigger was not specified, the slicing is based on the time instead. Parameters ---------- @@ -220,76 +186,29 @@ def __getitem__(self, idx): ------- BlueprintInput object a copy of the object with the part of timeseries expressed by idx. - - Raises - ------ - IndexError - If the idx, represented as a slice, is out of bounds. - - Notes - ----- - If idx is an integer, it returns an instantaneous moment for all channels. - If it's a slicing, it always return the full slice. This means that - potentially, depending on the frequencies, BlueprintInput[1] and - BlueprintInput[1:2] might return different results. """ - sliced_timeseries = [None] * self.ch_amount - return_instant = False - trigger_length = len(self.timeseries[self.trigger_idx]) + sliced_timeseries = [] - if not self.trigger_idx: - self.trigger_idx = 0 - - # If idx is an integer, return an "instantaneous slice" and initialise slice if isinstance(idx, int): - return_instant = True - if idx < 0: - idx = trigger_length + idx - idx = slice(idx, idx + 1) - if idx.start >= trigger_length or idx.stop > trigger_length: - raise IndexError(f'slice ({idx.start}, {idx.stop}) is out of ' - f'bounds for channel {self.trigger_idx} ' - f'with size {trigger_length}') + if not self.trigger_idx: + self.trigger_idx = 0 - # Operate on each channel on its own for n, channel in enumerate(self.timeseries): idx_dict = {'start': idx.start, 'stop': idx.stop, 'step': idx.step} - # Adapt the slicing indexes to the right requency for i in ['start', 'stop', 'step']: if idx_dict[i]: idx_dict[i] = int(np.floor(self.freq[n] / self.freq[self.trigger_idx] * idx_dict[i])) - # Correct the slicing stop if necessary - if idx_dict['start'] == idx_dict['stop'] or return_instant: - idx_dict['stop'] = idx_dict['start'] + 1 - elif trigger_length == idx.stop: - idx_dict['stop'] = len(channel) - new_idx = slice(idx_dict['start'], idx_dict['stop'], idx_dict['step']) sliced_timeseries[n] = channel[new_idx] return BlueprintInput(sliced_timeseries, self.freq, self.ch_name, self.units, self.trigger_idx) - def __eq__(self, other): - """ - Return test of equality between two objects. - - Parameters - ---------- - other: - comparable object. - - Returns - ------- - boolean - """ - return are_equal(self, other) - def rename_channels(self, new_names): """ Rename the channels. @@ -531,21 +450,6 @@ def ch_amount(self): """ return self.timeseries.shape[1] - def __eq__(self, other): - """ - Return test of equality between two objects. - - Parameters - ---------- - other: - comparable object. - - Returns - ------- - boolean - """ - return are_equal(self, other) - def return_index(self, idx): """ Return the list entries of all the object properties, given an index. From 16e8598626e41b0ca3f0a078b1bd8f11b7d5c667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 24 May 2020 11:11:43 -0400 Subject: [PATCH 044/180] redefining first run_start - is not idx 0 --- phys2bids/split4phys.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index bee462f5b..9422b08f3 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -42,11 +42,8 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) # initialise start of run as index of first trigger minus the padding - if run_idx == 0: - run_start = 0 - else: - # the first trigger is always at 0 seconds - run_start = where(phys_in.timeseries[0] == 0) - padding + # the first trigger is always at 0 seconds + run_start = where(phys_in.timeseries[0] == 0) - padding # run length in seconds end_sec = (run_tps * tr_list[run_idx]) From 09bda51095ef3a0b7a4c8f0d04d0c0331c2e759e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 26 May 2020 17:30:03 -0400 Subject: [PATCH 045/180] using where method to find values in array --- phys2bids/split4phys.py | 1 + 1 file changed, 1 insertion(+) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 9422b08f3..0e0e08185 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -28,6 +28,7 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): run_timestamps : dictionary Containing tuples of run start and end indexes for each run, based on trigger channels In the form of run_timestamps{run_idx:(start, end), run_idx:...} + call an internal function and feed it the dictionary instead """ # Initialize dictionaries to save phys_in slices run_timestamps = {} From 11e7ba404823c5f76af08643f67995f92b0f0227 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 12:57:59 +0200 Subject: [PATCH 046/180] Revert "removing parralel workflow" This reverts commit 422b4d12b626e2ee95f3d376bb56507166d09e79. --- phys2bids/cli/run.py | 154 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 phys2bids/cli/run.py diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py new file mode 100644 index 000000000..0b3b8cf3c --- /dev/null +++ b/phys2bids/cli/run.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +"""Parser for phys2bids.""" + + +import argparse + +from phys2bids import __version__ + + +def _get_parser(): + """ + Parse command line inputs for this function. + + Returns + ------- + parser.parse_args() : argparse dict + + Notes + ----- + # Argument parser follow template provided by RalphyZ. + # https://stackoverflow.com/a/43456577 + """ + parser = argparse.ArgumentParser() + optional = parser._action_groups.pop() + required = parser.add_argument_group('Required Argument:') + required.add_argument('-in', '--input-file', + dest='filename', + type=str, + help='The name of the file containing physiological data, with or ' + 'without extension.', + required=True) + optional.add_argument('-info', '--info', + dest='info', + action='store_true', + help='Only output info about the file, don\'t process. ' + 'Default is to process.', + default=False) + optional.add_argument('-indir', '--input-dir', + dest='indir', + type=str, + help='Folder containing input. ' + 'Default is current folder.', + default='.') + optional.add_argument('-outdir', '--output-dir', + dest='outdir', + type=str, + help='Folder where output should be placed. ' + 'Default is current folder. ' + 'If \"-heur\" is used, it\'ll become ' + 'the site folder. Requires \"-sub\". ' + 'Optional to specify \"-ses\".', + default='.') + optional.add_argument('-heur', '--heuristic', + dest='heur_file', + type=str, + help='File containing heuristic, with or without ' + 'extension. This file is needed in order to ' + 'convert your input file to BIDS format! ' + 'If no path is specified, it assumes the file is ' + 'in the current folder. Edit the heur_ex.py file in ' + 'heuristics folder.', + default=None) + # optional.add_argument('-hdir', '--heur-dir', + # dest='heurdir', + # type=str, + # help='Folder containing heuristic file.', + # default='.') + optional.add_argument('-sub', '--subject', + dest='sub', + type=str, + help='Specify alongside \"-heur\". Code of ' + 'subject to process.', + default=None) + optional.add_argument('-ses', '--session', + dest='ses', + type=str, + help='Specify alongside \"-heur\". Code of ' + 'session to process.', + default=None) + # optional.add_argument('-run', '--multi-run', + # dest= run, + # help='' + # + # + optional.add_argument('-chtrig', '--channel-trigger', + dest='chtrig', + type=int, + help='The column number of the trigger channel. ' + 'Channel numbering starts with 0. ' + 'Default is 0.', + default=0) + optional.add_argument('-chsel', '--channel-selection', + dest='chsel', + nargs='*', + type=int, + help='The column numbers of the channels to process. ' + 'Default is to process all channels.', + default=None) + optional.add_argument('-ntp', '--numtps', + dest='num_timepoints_expected', + type=list, + help='Number of expected trigger timepoints (TRs). ' + 'Default is 0. Note: the estimation of beggining of ' + 'neuroimaging acquisition cannot take place with this default.' + 'Give a list of each expected ntp for multi-run recordings.', + default=[0, ]) + optional.add_argument('-tr', '--tr', + dest='tr', + type=list, + help='TR of sequence in seconds. ' + 'Default is 0 second.' + 'You can list each TR used throughout the session', + default=[0, ]) + optional.add_argument('-thr', '--threshold', + dest='thr', + type=float, + help='Threshold to use for trigger detection. ' + 'If "ntp" and "TR" are specified, phys2bids automatically computes ' + 'a threshold to detect the triggers. Use this parameter to set it ' + 'manually', + default=None) + optional.add_argument('-chnames', '--channel-names', + dest='ch_name', + nargs='*', + type=str, + help='Column header (for json file output).', + default=[]) + optional.add_argument('-chplot', '--channels-plot', + dest='chplot', + type=str, + help='full path to store channels plot ', + default='') + optional.add_argument('-debug', '--debug', + dest='debug', + action='store_true', + help='Only print debugging info to log file. Default is False.', + default=False) + optional.add_argument('-quiet', '--quiet', + dest='quiet', + action='store_true', + help='Only print warnings to log file. Default is False.', + default=False) + optional.add_argument('-v', '--version', action='version', + version=('%(prog)s ' + __version__)) + + parser._action_groups.append(optional) + + return parser + + +if __name__ == '__main__': + raise RuntimeError('phys2bids/cli/run.py should not be run directly;\n' + 'Please `pip install` phys2bids and use the ' + '`phys2bids` command') From 689cf2c3aff204d4e1afd088d703dfda8d4d7757 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 12:58:43 +0200 Subject: [PATCH 047/180] Delete alternative workflow CLI --- phys2bids/cli/split.py | 88 ------------------------------------------ 1 file changed, 88 deletions(-) delete mode 100644 phys2bids/cli/split.py diff --git a/phys2bids/cli/split.py b/phys2bids/cli/split.py deleted file mode 100644 index 8e407d8d6..000000000 --- a/phys2bids/cli/split.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Parser for split2phys. - -A parallel CLI script to segment multiple runs in physiological recording -""" - -import argparse - -from phys2bids import __version__ - - -def _get_parser(): - """ - - Parse command line inputs for this function. - - Returns - ------- - parser.parse_args() : argparse dict - - Notes - ----- - # Argument parser follow template provided by RalphyZ. - # https://stackoverflow.com/a/43456577 - """ - parser = argparse.ArgumentParser() - optional = parser._action_groups.pop() - required = parser.add_argument_group('Required Argument:') - required.add_argument('-in', '--input-file', - dest='filename', - type=str, - help='The name of the file containing physiological data, with or ' - 'without extension.', - required=True) - - optional.add_argument('-info', '--info', - dest='info', - action='store_true', - help='Only output info about the file, don\'t process. ' - 'Default is to process.', - default=False) - - optional.add_argument('-indir', '--input-dir', - dest='indir', - type=str, - help='Folder containing input. ' - 'Default is current folder.', - default='.') - - optional.add_argument('-outdir', '--output-dir', - dest='outdir', - type=str, - help='Folder where output should be placed. ' - 'Default is current folder. ' - 'If \"-heur\" is used, it\'ll become ' - 'the site folder. Requires \"-sub\". ' - 'Optional to specify \"-ses\".', - default='.') - - required.add_argument('-tr_ls', '--tr_list', - dest='tr_list', - type=list, - help='A list containing the TR(s) of the sequences' - ' used in the different ' - 'runs contained in the file', - required=True) - - required.add_argument('-ntp_ls', '--numtps_list', - dest='ntp_list', - type=list, - help='A list containing the number of trigger time points in each run', - required=True) - - required.add_argument('-thr', '--threshold', - dest='thr', - type=float, - help='Threshold to use for trigger detection. ' - 'If "ntp" and "TR" are specified, phys2bids automatically computes ' - 'a threshold to detect the triggers. Use this parameter to set it ' - 'manually', - default=None) - optional.add_argument('-v', '--version', action='version', - version=('%(prog)s ' + __version__)) - - parser._action_groups.append(optional) - - return parser From ef6657bfce5e6cf062b0383d8c870d5167fdeb9e Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 13:07:08 +0200 Subject: [PATCH 048/180] Add chtrig in BlueprintInput initialisation --- phys2bids/interfaces/txt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/interfaces/txt.py b/phys2bids/interfaces/txt.py index 9ba3bf54b..8b0c79f81 100644 --- a/phys2bids/interfaces/txt.py +++ b/phys2bids/interfaces/txt.py @@ -151,7 +151,7 @@ def process_labchart(channel_list, chtrig, header=[]): units = units + orig_units freq = [1 / interval[0]] * len(timeseries) freq = check_multifreq(timeseries, freq) - return BlueprintInput(timeseries, freq, names, units) + return BlueprintInput(timeseries, freq, names, units, chtrig) def process_acq(channel_list, chtrig, header=[]): @@ -251,7 +251,7 @@ def process_acq(channel_list, chtrig, header=[]): t_ch = np.ogrid[0:duration:interval[0]][:-1] # create time channel timeseries = [t_ch, ] + timeseries freq = check_multifreq(timeseries, freq) - return BlueprintInput(timeseries, freq, names, units) + return BlueprintInput(timeseries, freq, names, units, chtrig) def read_header_and_channels(filename, chtrig): From b2cc386c76431aa9ae0c8a17fcb12d3af2e684c6 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 13:15:39 +0200 Subject: [PATCH 049/180] Add split4phys and ones import --- phys2bids/phys2bids.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index a96f20797..363e577f8 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -32,11 +32,12 @@ from copy import deepcopy from pathlib import Path -from numpy import savetxt +from numpy import savetxt, ones from phys2bids import utils, viz, _version from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput +from phys2bids.split4phys import split4phys LGR = logging.getLogger(__name__) From 9e0798e6e7de5d9efc317aead61d2341de1a5dec Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 13:16:11 +0200 Subject: [PATCH 050/180] Move comments to right place --- phys2bids/phys2bids.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 363e577f8..2111eb93c 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -292,8 +292,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info('Renaming channels with given names') phys_in.rename_channels(ch_name) - # Create trigger plot. If possible, to have multiple outputs in the same - # place, adds sub and ses label. if tr != [0, ] and num_timepoints_expected != [0, ]: # Multi-run section # Check list length, more than 1 means multi-run @@ -352,6 +350,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info('Plot trigger') plot_path = os.path.join(outdir, os.path.splitext(os.path.basename(filename))[0]) + + # Create trigger plot. If possible, to have multiple outputs in the same + # place, adds sub and ses label. if sub: plot_path += f'_sub-{sub}' if ses: @@ -372,9 +373,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.warning(f'Found {output_amount} different frequencies in input!') LGR.info(f'Preparing {output_amount} output files.') - phys_out = {} # create phys_out dict that will have a - # blueprint object per frequency - # for each different frequency + # create phys_out dict that will have a blueprint object for each different frequency + phys_out = {} + for uniq_freq in uniq_freq_list: # copy the phys_in object to the new dict entry phys_out[uniq_freq] = deepcopy(phys_in) From deb5c93ba2303843f2a2df08024b4a678048d389 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 13:27:35 +0200 Subject: [PATCH 051/180] Init new function --- phys2bids/split4phys.py | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 9422b08f3..b6c51452f 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -4,9 +4,9 @@ from numpy import where -def split4phys(phys_in, ntp_list, tr_list, padding=9): +def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): """ - Split runs for phys2bids. + Find beginning and ending of each "run" in a multirun recording. Returns dictionary key for each run in BlueprintInput object based on user's entries Each key has a tuple expressing the timestamps of run in nb of samples(based on trigger chan) @@ -25,7 +25,7 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): Default: [1,] Returns -------- - run_timestamps : dictionary + run_timestamps : dictionary of tuples Containing tuples of run start and end indexes for each run, based on trigger channels In the form of run_timestamps{run_idx:(start, end), run_idx:...} """ @@ -71,3 +71,33 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): run_timestamps[run_idx] = (run_start, run_end + padding) return run_timestamps + + +def split4phys(phys_in, ntp_list, tr_list, padding=9): + """ + Split runs for phys2bids. + + Returns a dictionary containing one BlueprintInput per run. + + Parameters + --------- + phys_in : object + Object returned by BlueprintInput class + ntp_list : list + a list of integers given by the user as `ntp` input + Default: [0, ] + tr_list : list + a list of float given by the user as `tr` input + Default: [1,] + Returns + -------- + phys_in: dictionary of BlueprintInput objects + Containing tuples of run start and end indexes for each run, based on trigger channels + In the form of run_timestamps{run_idx:(start, end), run_idx:...} + + """ + run_timestamps = find_run_timestamps(phys_in, ntp_list, tr_list, padding=9) + + # do stuff + + return phys_in From 7c7ddb46ce1a2ccdbf86202703f1781b6d3464f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 09:09:13 -0400 Subject: [PATCH 052/180] ob2split --- phys2bids/split4phys.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 0e0e08185..6accddc3d 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -71,4 +71,15 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): run_timestamps[run_idx] = (run_start, run_end + padding) - return run_timestamps + multiphys_in = _split_obj(run_timestamps) + + return multiphys_in + + +def obj2split(run_timestamps): + """ + Internal to split4phys. + + run_timestamps: dictionary + key is index of runs and elements are run start and end index as tuple + """ From 350581c45fef9ff347abdb052a5e70a4b64ce39b Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 15:16:27 +0200 Subject: [PATCH 053/180] Remove duplicate info --- phys2bids/phys2bids.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 2111eb93c..d2dde4305 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -276,10 +276,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if info: return - # If only info were asked, end here. - if info: - return - # The next few lines remove the undesired channels from phys_in. if chsel: LGR.info('Dropping unselected channels') From e1faeaa8ae91f018f7589bc5cf9988ab2a3e51db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 09:22:22 -0400 Subject: [PATCH 054/180] last changes --- phys2bids/split4phys.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 6accddc3d..b89796705 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -74,12 +74,3 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): multiphys_in = _split_obj(run_timestamps) return multiphys_in - - -def obj2split(run_timestamps): - """ - Internal to split4phys. - - run_timestamps: dictionary - key is index of runs and elements are run start and end index as tuple - """ From fb9bea2298d4c8a0748e080a3855948127a2bf36 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 16:37:10 +0200 Subject: [PATCH 055/180] Update after gitpod --- phys2bids/split4phys.py | 47 +++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 6accddc3d..5ae0f8ca7 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -4,7 +4,7 @@ from numpy import where -def split4phys(phys_in, ntp_list, tr_list, padding=9): +def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): """ Split runs for phys2bids. @@ -38,12 +38,17 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): padding = padding * phys_in.freq[0] for run_idx, run_tps in enumerate(ntp_list): + # Make run_idx human friendly :) + run_idx += 1 # (re)initialise Blueprint object with current run info - correct time offset phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) # initialise start of run as index of first trigger minus the padding # the first trigger is always at 0 seconds + + ### CHECK THAT YOU HAVE ENOUGH PADDING AT THE BEGINNING + ### REMEMBER NOT TO OVERWRITE padding run_start = where(phys_in.timeseries[0] == 0) - padding # run length in seconds @@ -55,31 +60,47 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording + ### NOW THIS IS NOT OPTIMAL ANYMORE SINCE IT OVERWRITES padding + ### BETTER TO CHANGE run_end HERE!!! if phys_in.timeseries[0].shape[0] < (run_end + padding): padding = phys_in.timeseries[0].shape[0] - run_end - # update the object so that it will look for the first trigger after previous run end - phys_in = phys_in[(run_end + 1):] - # Save start and end_index in dictionary # keep original timestamps by adjusting the indexes with previous end_index + # Except if it's the first run # While saving, add the padding for end index - if run_idx > 0: + if run_idx > 1: previous_end_index = run_timestamps[run_idx - 1][1] + phys_in.time_offset = phys_in.time_offset + run_timestamps[run_idx - 1][2] run_start = run_start + previous_end_index run_end = run_end + previous_end_index - run_timestamps[run_idx] = (run_start, run_end + padding) + ### TUPLE BECOMES FOUR ITEMS, THE LAST ARE related to check_trigger_amount + run_timestamps[run_idx] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) - multiphys_in = _split_obj(run_timestamps) + # update the object so that it will look for the first trigger after previous run end + phys_in = phys_in[(run_end + 1):] - return multiphys_in + return run_timestamps -def obj2split(run_timestamps): +def split4phys(phys_in, ntp_list, tr_list, padding=9): """ - Internal to split4phys. - - run_timestamps: dictionary - key is index of runs and elements are run start and end index as tuple """ + multiphys_in = {} + + # Find the timestamps + run_timestamps = find_run_timestamps(phys_in, ntp_list, tr_list, padding=9) + + for run in run_timestamps.keys(): + # Read the run_timestamps[run] + + # add item to multiphys_in that contains a slice of phys_in accordingly + # The key of the item is "run" + + # Overwrite attributes phys_in.time_offset and phys_in.num_timepoints_found with the ones in the tuple (item 2 and 3) + + # return a dictionary that contains the sliced object + # the key will be the internal run + return multiphys_in + From d5aa6dad8a55178e4f26dd4602c7f3f702812abd Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 16:38:48 +0200 Subject: [PATCH 056/180] Marked function parts --- phys2bids/phys2bids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index d2dde4305..e57010709 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -320,7 +320,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # ideally, we'd want to have a figure for each run for idx, key in enumerate(run_idx.keys()): - # The following 14 lines should become a function + ### The following 14 lines should become a function LGR.info('Plot trigger') plot_path = os.path.join(outdir, os.path.splitext(os.path.basename(filename))[0]) @@ -342,7 +342,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected[0], tr[0]) - # The following 9 lines should become a function (same as previous block) + ### The following 9 lines should become a function (same as previous block) LGR.info('Plot trigger') plot_path = os.path.join(outdir, os.path.splitext(os.path.basename(filename))[0]) From 83dbc2975a6292180bb09013a6f3c6089e3667f6 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 27 May 2020 16:46:13 +0200 Subject: [PATCH 057/180] Solve merge --- phys2bids/split4phys.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 8c2d62ad1..39c8144fa 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -81,7 +81,6 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): # update the object so that it will look for the first trigger after previous run end phys_in = phys_in[(run_end + 1):] -<<<<<<< HEAD return run_timestamps @@ -104,7 +103,3 @@ def split4phys(phys_in, ntp_list, tr_list, padding=9): # return a dictionary that contains the sliced object # the key will be the internal run return multiphys_in - -======= - return multiphys_in ->>>>>>> sangfrois/split_utility From da63d399b4943675140d4809d4af567632186450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 15:25:20 -0400 Subject: [PATCH 058/180] redefining run_start for padding problem + comments --- phys2bids/phys2bids.py | 9 ++++++--- phys2bids/split4phys.py | 4 +--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index e57010709..46466f357 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -288,8 +288,10 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info('Renaming channels with given names') phys_in.rename_channels(ch_name) + # Checking acquisition type via user's input if tr != [0, ] and num_timepoints_expected != [0, ]: - # Multi-run section + + # Multi-run acquisition type section # Check list length, more than 1 means multi-run if len(num_timepoints_expected) > 1: # if multi-run of same sequence type, pad list with ones @@ -307,8 +309,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, num_timepoints_expected=sum(num_timepoints_expected), tr=1) - # Check that sum(ntp_list) is equivalent to num_timepoints_found, - # else call split2phys + # Check that sum of tp expected is equivalent to num_timepoints_found, + # else call split4phys if phys_in.num_timepoints_found != sum(num_timepoints_expected): raise Exception('The number of triggers found is different ' 'than expected. Better stop now than breaking ' @@ -336,6 +338,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, plot_path, tr[idx], phys_in.thr, num_timepoints_expected[idx], filename) + # Single run acquisition type, or : nothing to split workflow else: # Run analysis on trigger channel to get first timepoint # and the time offset. diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 39c8144fa..417a99a19 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -38,8 +38,6 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): padding = padding * phys_in.freq[0] for run_idx, run_tps in enumerate(ntp_list): - # Make run_idx human friendly :) - run_idx += 1 # (re)initialise Blueprint object with current run info - correct time offset phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) @@ -76,7 +74,7 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): run_end = run_end + previous_end_index ### TUPLE BECOMES FOUR ITEMS, THE LAST ARE related to check_trigger_amount - run_timestamps[run_idx] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) + run_timestamps[run_idx+1)] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) # update the object so that it will look for the first trigger after previous run end phys_in = phys_in[(run_end + 1):] From a7a60a06e3e7df45020cfca0abbaa051a33fc843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 15:28:33 -0400 Subject: [PATCH 059/180] redefining run_start for padding problem - last commit is comment --- phys2bids/split4phys.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 417a99a19..1f2540814 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -32,52 +32,53 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): """ # Initialize dictionaries to save phys_in slices run_timestamps = {} - # run_start = 0 - # define padding - 9s * freq of trigger - padding is in nb of samples + # define padding - default : 9s * freq of trigger padding = padding * phys_in.freq[0] + # enumerate user input num_timepoints_expected for run_idx, run_tps in enumerate(ntp_list): # (re)initialise Blueprint object with current run info - correct time offset phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) - # initialise start of run as index of first trigger minus the padding - # the first trigger is always at 0 seconds - - ### CHECK THAT YOU HAVE ENOUGH PADDING AT THE BEGINNING - ### REMEMBER NOT TO OVERWRITE padding - run_start = where(phys_in.timeseries[0] == 0) - padding + # Defining beginning of acquisition + # if -9 s' index doesn't exist, start at beginning + if where(phys_in.timeseries[0] == -9)[0].size < 1: # where returns a tuple + # the first trigger is always at 0 s + run_start = where(phys_in.timeseries[0] == 0) + else: + # initialise start of run as index of first trigger minus the padding + run_start = where(phys_in.timeseries[0] == 0) - padding + # Defining end of acquisition # run length in seconds end_sec = (run_tps * tr_list[run_idx]) - # define index of the run's last trigger - # run_end = find index of phys_in.timeseries[0] > end_sec - run_end = where(phys_in.timeseries[0] > end_sec) + # define index of the run's last trigger + padding + run_end = where(phys_in.timeseries[0] > end_sec) + padding # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording - ### NOW THIS IS NOT OPTIMAL ANYMORE SINCE IT OVERWRITES padding - ### BETTER TO CHANGE run_end HERE!!! - if phys_in.timeseries[0].shape[0] < (run_end + padding): - padding = phys_in.timeseries[0].shape[0] - run_end + if phys_in.timeseries[0].shape[0] < run_end: + run_end = phys_in.timeseries[0].shape[0] # Save start and end_index in dictionary # keep original timestamps by adjusting the indexes with previous end_index # Except if it's the first run - # While saving, add the padding for end index if run_idx > 1: previous_end_index = run_timestamps[run_idx - 1][1] + # adjust time_offset to keep original timing information phys_in.time_offset = phys_in.time_offset + run_timestamps[run_idx - 1][2] run_start = run_start + previous_end_index run_end = run_end + previous_end_index ### TUPLE BECOMES FOUR ITEMS, THE LAST ARE related to check_trigger_amount - run_timestamps[run_idx+1)] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) + run_timestamps[run_idx+1)] = (run_start, run_end, + phys_in.time_offset, phys_in.num_timepoints_found) # update the object so that it will look for the first trigger after previous run end - phys_in = phys_in[(run_end + 1):] + phys_in = phys_in[(run_end):] return run_timestamps From 9961afd907072bf40cea96829c41b1167c7016dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 16:03:10 -0400 Subject: [PATCH 060/180] end_index definition and general reformatting --- phys2bids/split4phys.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 1f2540814..325bc1c9b 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -30,7 +30,7 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): In the form of run_timestamps{run_idx:(start, end), run_idx:...} call an internal function and feed it the dictionary instead """ - # Initialize dictionaries to save phys_in slices + # Initialize dictionaries to save run timestamps and phys_in's attributes run_timestamps = {} # define padding - default : 9s * freq of trigger @@ -63,8 +63,7 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): if phys_in.timeseries[0].shape[0] < run_end: run_end = phys_in.timeseries[0].shape[0] - # Save start and end_index in dictionary - # keep original timestamps by adjusting the indexes with previous end_index + # Adjust timestamps with previous end_index # Except if it's the first run if run_idx > 1: previous_end_index = run_timestamps[run_idx - 1][1] @@ -73,9 +72,11 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): run_start = run_start + previous_end_index run_end = run_end + previous_end_index - ### TUPLE BECOMES FOUR ITEMS, THE LAST ARE related to check_trigger_amount - run_timestamps[run_idx+1)] = (run_start, run_end, - phys_in.time_offset, phys_in.num_timepoints_found) + # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* + # dict key must be readable + run_timestamps["Run {}".format(run_idx + 1)] = (run_start, run_end, + phys_in.time_offset, + phys_in.num_timepoints_found) # update the object so that it will look for the first trigger after previous run end phys_in = phys_in[(run_end):] From 876df71139c8026880c09c98f9d7d5e0510c734a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 16:04:34 -0400 Subject: [PATCH 061/180] initializing main function that will return sliced obj --- phys2bids/split4phys.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 325bc1c9b..35f47e150 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -4,7 +4,7 @@ from numpy import where -def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): +def find_runs(phys_in, ntp_list, tr_list, padding=9): """ Split runs for phys2bids. @@ -84,19 +84,26 @@ def find_run_timestamps(phys_in, ntp_list, tr_list, padding=9): return run_timestamps -def split4phys(phys_in, ntp_list, tr_list, padding=9): +def slice4phys(phys_in, ntp_list, tr_list, padding=9): """ """ - multiphys_in = {} + sliced_phys_in = {} # Find the timestamps - run_timestamps = find_run_timestamps(phys_in, ntp_list, tr_list, padding=9) + run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding=9) for run in run_timestamps.keys(): - # Read the run_timestamps[run] + + # tmp variable to collect run's info + run_attributes = run_timestamps[run] + + run_obj = phys_in[run_attributes[0]:run_attributes[1]] + + run_ # add item to multiphys_in that contains a slice of phys_in accordingly # The key of the item is "run" + sliced_phys_in[run] = # Overwrite attributes phys_in.time_offset and phys_in.num_timepoints_found with the ones in the tuple (item 2 and 3) From ad281d1cd415bd5b983377e4204494c05466c16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 16:06:47 -0400 Subject: [PATCH 062/180] renaming function and outputs so it's more intuitive --- phys2bids/phys2bids.py | 2 +- phys2bids/split4phys.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 46466f357..b4b4ee8dd 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -317,7 +317,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, 'something.') # CALL run4phys, give it BlueprintInput object and lists - run_idx = split4phys(phys_in, num_timepoints_expected, tr) + phys_in_slices = slice4phys(phys_in, num_timepoints_expected, tr) # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # ideally, we'd want to have a figure for each run diff --git a/phys2bids/split4phys.py b/phys2bids/split4phys.py index 35f47e150..9a1a6b1e1 100644 --- a/phys2bids/split4phys.py +++ b/phys2bids/split4phys.py @@ -87,7 +87,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): def slice4phys(phys_in, ntp_list, tr_list, padding=9): """ """ - sliced_phys_in = {} + phys_in_slices = {} # Find the timestamps run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding=9) @@ -103,7 +103,7 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): # add item to multiphys_in that contains a slice of phys_in accordingly # The key of the item is "run" - sliced_phys_in[run] = + phys_in_slices[run] = # Overwrite attributes phys_in.time_offset and phys_in.num_timepoints_found with the ones in the tuple (item 2 and 3) From c169e14203c049c339368192021e9461fc9046ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 16:09:20 -0400 Subject: [PATCH 063/180] renaming file also --- phys2bids/{split4phys.py => slice4phys.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename phys2bids/{split4phys.py => slice4phys.py} (100%) diff --git a/phys2bids/split4phys.py b/phys2bids/slice4phys.py similarity index 100% rename from phys2bids/split4phys.py rename to phys2bids/slice4phys.py From be278aff465f0e4cc18d8914e39360f1dbe62a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 27 May 2020 16:19:58 -0400 Subject: [PATCH 064/180] iterate through timestamps dict, slice and overwrite attributes --- phys2bids/slice4phys.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 9a1a6b1e1..591ff0ea1 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -99,14 +99,13 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): run_obj = phys_in[run_attributes[0]:run_attributes[1]] - run_ + # Overwrite current run phys_in attributes + # 3rd item of run_attributes is adjusted time offset + run_obj.time_offset = run_attributes[2] + # 4th item of run_attributes is the nb of tp found by check_trigger_amount + run_obj.num_timepoints_found = run_attributes[3] - # add item to multiphys_in that contains a slice of phys_in accordingly - # The key of the item is "run" - phys_in_slices[run] = + # save the phys_in slice in dictionary + phys_in_slices[run] = run_obj - # Overwrite attributes phys_in.time_offset and phys_in.num_timepoints_found with the ones in the tuple (item 2 and 3) - - # return a dictionary that contains the sliced object - # the key will be the internal run - return multiphys_in + return phys_in_slices From 7f628791edde88a281e5086bcbf8db807ebe678c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 09:15:50 -0400 Subject: [PATCH 065/180] restructuring docstring and function order --- phys2bids/slice4phys.py | 84 +++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 591ff0ea1..bebc50fa5 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -4,14 +4,9 @@ from numpy import where -def find_runs(phys_in, ntp_list, tr_list, padding=9): +def slice4phys(phys_in, ntp_list, tr_list, padding=9): """ - Split runs for phys2bids. - - Returns dictionary key for each run in BlueprintInput object based on user's entries - Each key has a tuple expressing the timestamps of run in nb of samples(based on trigger chan) - Timestamps are the index of first and last triggers of a run, adjusted with padding - run_start and run_end indexes refer to the samples contained in the whole session + Slice runs for phys2bids. Parameters --------- @@ -23,11 +18,55 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): tr_list : list a list of float given by the user as `tr` input Default: [1,] + padding : int + extra time at beginning and end of timeseries, expressed in seconds (s) + Default: 9 + Returns + -------- + phys_in_slices : dict + keys start by `run 1` until last (`run n`). + items are slices of BlueprintInput objects based on timestamps returned by + internal function (`find_runs` takes the same arguments as `slice4phys`) + """ + phys_in_slices = {} + + # Find the timestamps + run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding=9) + + for run in run_timestamps.keys(): + + # tmp variable to collect run's info + run_attributes = run_timestamps[run] + + run_obj = phys_in[run_attributes[0]:run_attributes[1]] + + # Overwrite current run phys_in attributes + # 3rd item of run_attributes is adjusted time offset + run_obj.time_offset = run_attributes[2] + # 4th item of run_attributes is the nb of tp found by check_trigger_amount + run_obj.num_timepoints_found = run_attributes[3] + + # save the phys_in slice in dictionary + phys_in_slices[run] = run_obj + + return phys_in_slices + + +def find_runs(phys_in, ntp_list, tr_list, padding=9): + """ + Split runs for phys2bids. + + Returns dictionary key for each run in BlueprintInput object based on user's entries + Each key has a tuple of 4 elements expressing the timestamps of run in nb of samples + Timestamps are the index of first and last triggers of a run, adjusted with padding + run_start and run_end indexes refer to the samples contained in the whole session + time offset and nb of triggers in a run are also indicated + Returns -------- run_timestamps : dictionary Containing tuples of run start and end indexes for each run, based on trigger channels - In the form of run_timestamps{run_idx:(start, end), run_idx:...} + In the form of run_timestamps{Run 1:(start, end, time offset, nb of triggers), Run 2:()} call an internal function and feed it the dictionary instead """ # Initialize dictionaries to save run timestamps and phys_in's attributes @@ -73,7 +112,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): run_end = run_end + previous_end_index # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* - # dict key must be readable + # dict key must be readable by human run_timestamps["Run {}".format(run_idx + 1)] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) @@ -82,30 +121,3 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): phys_in = phys_in[(run_end):] return run_timestamps - - -def slice4phys(phys_in, ntp_list, tr_list, padding=9): - """ - """ - phys_in_slices = {} - - # Find the timestamps - run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding=9) - - for run in run_timestamps.keys(): - - # tmp variable to collect run's info - run_attributes = run_timestamps[run] - - run_obj = phys_in[run_attributes[0]:run_attributes[1]] - - # Overwrite current run phys_in attributes - # 3rd item of run_attributes is adjusted time offset - run_obj.time_offset = run_attributes[2] - # 4th item of run_attributes is the nb of tp found by check_trigger_amount - run_obj.num_timepoints_found = run_attributes[3] - - # save the phys_in slice in dictionary - phys_in_slices[run] = run_obj - - return phys_in_slices From c6377a7346106cb20e2a7dfe4c06e602508710b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 09:52:13 -0400 Subject: [PATCH 066/180] minimizing repetition in phys2bids for viz.plot_trigger --- phys2bids/viz.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index d925f6c83..0213b4aad 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -11,10 +11,34 @@ FIGSIZE = (18, 10) +def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, sub, ses): + """ + Save a trigger plot. + + Used in main workflow (`phys2bids`), this function minimizes repetition in code for parallel + workflow (multi-run workflow and default workflow). + """ + LGR.info('Plot trigger') + plot_path = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + # Create trigger plot. If possible, to have multiple outputs in the same + # place, adds sub and ses label. + if sub: + plot_path += f'_sub-{sub}' + if ses: + plot_path += f'_ses-{ses}' + + # adjust for multi run arguments, iterate through acquisition attributes + plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], + plot_path, tr, phys_in.thr, + num_timepoints_expected, filename) + + def plot_trigger(time, trigger, fileprefix, tr, thr, num_timepoints_expected, filename, figsize=FIGSIZE, dpi=SET_DPI): """ - Produces a figure with three plots: + Produce a figure with three plots. + 1. Plots the triggers in blue, a block in orange that indicates the time from the first trigger to the last, and a red line showing the threshold used for trigger detection From 966c593344aa40b65babdccfb381f7d248109307 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 29 May 2020 15:52:15 +0200 Subject: [PATCH 067/180] Add comments and some suggestions --- phys2bids/slice4phys.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index bebc50fa5..84dc4fa8f 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -3,7 +3,7 @@ from numpy import where - +### Put this definition second def slice4phys(phys_in, ntp_list, tr_list, padding=9): """ Slice runs for phys2bids. @@ -31,23 +31,20 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): phys_in_slices = {} # Find the timestamps - run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding=9) + run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding) for run in run_timestamps.keys(): # tmp variable to collect run's info run_attributes = run_timestamps[run] - run_obj = phys_in[run_attributes[0]:run_attributes[1]] + phys_in_slices[run] = phys_in[run_attributes[0]:run_attributes[1]] # Overwrite current run phys_in attributes # 3rd item of run_attributes is adjusted time offset - run_obj.time_offset = run_attributes[2] + phys_in_slices[run].time_offset = run_attributes[2] # 4th item of run_attributes is the nb of tp found by check_trigger_amount - run_obj.num_timepoints_found = run_attributes[3] - - # save the phys_in slice in dictionary - phys_in_slices[run] = run_obj + phys_in_slices[run].num_timepoints_found = run_attributes[3] return phys_in_slices @@ -72,30 +69,28 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): # Initialize dictionaries to save run timestamps and phys_in's attributes run_timestamps = {} - # define padding - default : 9s * freq of trigger + # Express the padding in samples equivalent padding = padding * phys_in.freq[0] # enumerate user input num_timepoints_expected for run_idx, run_tps in enumerate(ntp_list): - # (re)initialise Blueprint object with current run info - correct time offset + # correct time offset for this iteration's object phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) - # Defining beginning of acquisition - # if -9 s' index doesn't exist, start at beginning - if where(phys_in.timeseries[0] == -9)[0].size < 1: # where returns a tuple - # the first trigger is always at 0 s - run_start = where(phys_in.timeseries[0] == 0) + # If it's the very first run, start the run at sample 0, + # otherwise "add" the padding + if run_idx == 0: + run_start = 0 else: - # initialise start of run as index of first trigger minus the padding - run_start = where(phys_in.timeseries[0] == 0) - padding + run_start = where(phys_in.timeseries[0] >= 0)[0] - padding # Defining end of acquisition # run length in seconds end_sec = (run_tps * tr_list[run_idx]) # define index of the run's last trigger + padding - run_end = where(phys_in.timeseries[0] > end_sec) + padding + run_end = where(phys_in.timeseries[0] > end_sec)[0] + padding # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording @@ -104,7 +99,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): # Adjust timestamps with previous end_index # Except if it's the first run - if run_idx > 1: + if run_idx > 0: previous_end_index = run_timestamps[run_idx - 1][1] # adjust time_offset to keep original timing information phys_in.time_offset = phys_in.time_offset + run_timestamps[run_idx - 1][2] @@ -113,6 +108,9 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human + ### Having the run as an integer will help with heuristics and integration. + ### If you want to use a string anyway, use ' instead of " as the rest of phy2bids, + ### make the string an f-string, and possibly express the run as 01 02 03 (leading zero) run_timestamps["Run {}".format(run_idx + 1)] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) From b386f67936af26c0b79d060f5a702bad7def4ceb Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 29 May 2020 15:54:18 +0200 Subject: [PATCH 068/180] Correct function import in main workflow --- phys2bids/phys2bids.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index b4b4ee8dd..61d765084 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -37,7 +37,7 @@ from phys2bids import utils, viz, _version from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput -from phys2bids.split4phys import split4phys +from phys2bids.slice4phys import slice4phys LGR = logging.getLogger(__name__) @@ -310,13 +310,13 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, tr=1) # Check that sum of tp expected is equivalent to num_timepoints_found, - # else call split4phys + # else call slice4phys if phys_in.num_timepoints_found != sum(num_timepoints_expected): raise Exception('The number of triggers found is different ' 'than expected. Better stop now than breaking ' 'something.') - # CALL run4phys, give it BlueprintInput object and lists + # CALL slice4phys, give it BlueprintInput object and lists phys_in_slices = slice4phys(phys_in, num_timepoints_expected, tr) # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} From c474ab0c130ce844aa7d65606ec9152704b8aa19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 10:10:12 -0400 Subject: [PATCH 069/180] reorder slice4phys --- phys2bids/slice4phys.py | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 84dc4fa8f..14c5070a5 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -3,11 +3,16 @@ from numpy import where -### Put this definition second -def slice4phys(phys_in, ntp_list, tr_list, padding=9): + +def find_runs(phys_in, ntp_list, tr_list, padding=9): """ - Slice runs for phys2bids. + Split runs for phys2bids. + Returns dictionary key for each run in BlueprintInput object based on user's entries + Each key has a tuple of 4 elements expressing the timestamps of run in nb of samples + Timestamps are the index of first and last triggers of a run, adjusted with padding + run_start and run_end indexes refer to the samples contained in the whole session + time offset and nb of triggers in a run are also indicated Parameters --------- phys_in : object @@ -21,43 +26,6 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): padding : int extra time at beginning and end of timeseries, expressed in seconds (s) Default: 9 - Returns - -------- - phys_in_slices : dict - keys start by `run 1` until last (`run n`). - items are slices of BlueprintInput objects based on timestamps returned by - internal function (`find_runs` takes the same arguments as `slice4phys`) - """ - phys_in_slices = {} - - # Find the timestamps - run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding) - - for run in run_timestamps.keys(): - - # tmp variable to collect run's info - run_attributes = run_timestamps[run] - - phys_in_slices[run] = phys_in[run_attributes[0]:run_attributes[1]] - - # Overwrite current run phys_in attributes - # 3rd item of run_attributes is adjusted time offset - phys_in_slices[run].time_offset = run_attributes[2] - # 4th item of run_attributes is the nb of tp found by check_trigger_amount - phys_in_slices[run].num_timepoints_found = run_attributes[3] - - return phys_in_slices - - -def find_runs(phys_in, ntp_list, tr_list, padding=9): - """ - Split runs for phys2bids. - - Returns dictionary key for each run in BlueprintInput object based on user's entries - Each key has a tuple of 4 elements expressing the timestamps of run in nb of samples - Timestamps are the index of first and last triggers of a run, adjusted with padding - run_start and run_end indexes refer to the samples contained in the whole session - time offset and nb of triggers in a run are also indicated Returns -------- @@ -119,3 +87,35 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): phys_in = phys_in[(run_end):] return run_timestamps + + +def slice4phys(phys_in, ntp_list, tr_list, padding=9): + """ + Slice runs for phys2bids. + + Returns + -------- + phys_in_slices : dict + keys start by `run 1` until last (`run n`). + items are slices of BlueprintInput objects based on timestamps returned by + internal function (`find_runs` takes the same arguments as `slice4phys`) + """ + phys_in_slices = {} + + # Find the timestamps + run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding) + + for run in run_timestamps.keys(): + + # tmp variable to collect run's info + run_attributes = run_timestamps[run] + + phys_in_slices[run] = phys_in[run_attributes[0]:run_attributes[1]] + + # Overwrite current run phys_in attributes + # 3rd item of run_attributes is adjusted time offset + phys_in_slices[run].time_offset = run_attributes[2] + # 4th item of run_attributes is the nb of tp found by check_trigger_amount + phys_in_slices[run].num_timepoints_found = run_attributes[3] + + return phys_in_slices From 31fa00c5c56ca71ffe8fe648455810144d35e229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 10:27:07 -0400 Subject: [PATCH 070/180] viz.save_plot docstring and comment its use in main workflow --- phys2bids/phys2bids.py | 2 +- phys2bids/viz.py | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 2cbeb884c..c54823879 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -320,7 +320,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in_slices = slice4phys(phys_in, num_timepoints_expected, tr) # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} - # save a figure for each run | give the right acquisition + # save a figure for each run | give the right acquisition parameters for runs for (key, sequence, nb_trigger) in (phys_in_slices.keys(), tr, num_timepoints_expected): viz.save_plot(phys_in[key], num_timepoints_expected[nb_trigger], diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 5bbbe4d81..ad54c0df0 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -16,7 +16,28 @@ def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, su Save a trigger plot. Used in main workflow (`phys2bids`), this function minimizes repetition in code for parallel - workflow (multi-run workflow and default workflow). + workflow (multi-run workflow and default workflow) and maintains readability of code + Parameters + --------- + phys_in : object + Object returned by BlueprintInput class + For multi-run acquisitions, phys_in is a slice of the whole object + num_timepoints_expected : list + a list of integers given by the user as `ntp` input + tr : list + a list of float given by the user as `tr` input + chtrig : int + trigger channel + integer representing the index of the trigger on phys_in.timeseries + outdir : str + directory to save output. + if ses and sub are specified, it can be understood as root directory of dataset + filename : str + name of the input file given by user's entry + sub: str or int + Name of subject. + ses: str or int or None + Name of session. """ LGR.info('Plot trigger') plot_path = os.path.join(outdir, From bda8edfb0dbd381264b77fe9fd351b22a17172f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 10:37:39 -0400 Subject: [PATCH 071/180] implementing smoia comments - run counter --- phys2bids/slice4phys.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 14c5070a5..56a504adb 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -76,12 +76,9 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human - ### Having the run as an integer will help with heuristics and integration. - ### If you want to use a string anyway, use ' instead of " as the rest of phy2bids, - ### make the string an f-string, and possibly express the run as 01 02 03 (leading zero) - run_timestamps["Run {}".format(run_idx + 1)] = (run_start, run_end, - phys_in.time_offset, - phys_in.num_timepoints_found) + run_timestamps[f"Run {run_idx+1:02}"] = (run_start, run_end, + phys_in.time_offset, + phys_in.num_timepoints_found) # update the object so that it will look for the first trigger after previous run end phys_in = phys_in[(run_end):] From ad16b5aa1ca8b9a2686c7411724795cd7a859eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 10:41:18 -0400 Subject: [PATCH 072/180] make sure phys_in update in find_runs dont extend to next run --- phys2bids/slice4phys.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 56a504adb..2231c879c 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -77,11 +77,12 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human run_timestamps[f"Run {run_idx+1:02}"] = (run_start, run_end, - phys_in.time_offset, + phys_in.tim_offset, phys_in.num_timepoints_found) - # update the object so that it will look for the first trigger after previous run end - phys_in = phys_in[(run_end):] + # update the object so that it will look for the first trigger + # after previous run's last trigger. maybe padding extends to next run + phys_in = phys_in[(run_end - padding + 1):] return run_timestamps From 55d956cf0fa784b46deda486121f8d2b3223cf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 10:51:20 -0400 Subject: [PATCH 073/180] minor fixes in comments/doctsrings and lintering --- phys2bids/phys2bids.py | 1 - phys2bids/slice4phys.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index c54823879..c2c7207a1 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -335,7 +335,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # save a figure of the trigger viz.save_plot(phys_in, num_timepoints_expected, outdir, filename, sub, ses) - else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using both "-ntp" and "-tr" arguments') diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 2231c879c..1cb84d272 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -13,6 +13,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): Timestamps are the index of first and last triggers of a run, adjusted with padding run_start and run_end indexes refer to the samples contained in the whole session time offset and nb of triggers in a run are also indicated + Parameters --------- phys_in : object @@ -96,7 +97,7 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): phys_in_slices : dict keys start by `run 1` until last (`run n`). items are slices of BlueprintInput objects based on timestamps returned by - internal function (`find_runs` takes the same arguments as `slice4phys`) + internal function (`slice4phys` takes the same arguments as `find_runs`) """ phys_in_slices = {} From c80fd9b9c80a1822dbd9fd06a1e4087756ae9129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 10:56:14 -0400 Subject: [PATCH 074/180] minor change to docstring --- phys2bids/slice4phys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 1cb84d272..3d0df22cb 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -9,10 +9,10 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): Split runs for phys2bids. Returns dictionary key for each run in BlueprintInput object based on user's entries - Each key has a tuple of 4 elements expressing the timestamps of run in nb of samples + Each key has a tuple of 4 elements. 2 expressing the timestamps of run in nb of samples Timestamps are the index of first and last triggers of a run, adjusted with padding run_start and run_end indexes refer to the samples contained in the whole session - time offset and nb of triggers in a run are also indicated + first trigger time offset and nb of triggers contained in the run are also indicated Parameters --------- From e1128ed261a45212a1fb2564a9c4cec5355eeb54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 11:06:13 -0400 Subject: [PATCH 075/180] minor fix in docstring --- phys2bids/slice4phys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 3d0df22cb..5dc48550a 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -16,7 +16,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): Parameters --------- - phys_in : object + phys_in : BlueprintInput object Object returned by BlueprintInput class ntp_list : list a list of integers given by the user as `ntp` input From 91459e23c3998314219797b8c93422582a0e98aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 14:08:28 -0400 Subject: [PATCH 076/180] slice4phys - adding arguments in docstrings --- phys2bids/slice4phys.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 5dc48550a..f0eea8f57 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -6,7 +6,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): """ - Split runs for phys2bids. + find runs slicing index. Returns dictionary key for each run in BlueprintInput object based on user's entries Each key has a tuple of 4 elements. 2 expressing the timestamps of run in nb of samples @@ -92,6 +92,20 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): """ Slice runs for phys2bids. + Parameters + --------- + phys_in : BlueprintInput object + Object returned by BlueprintInput class + ntp_list : list + a list of integers given by the user as `ntp` input + Default: [0, ] + tr_list : list + a list of float given by the user as `tr` input + Default: [1,] + padding : int + extra time at beginning and end of timeseries, expressed in seconds (s) + Default: 9 + Returns -------- phys_in_slices : dict From 02b097df932478e481b18aab9d42bdadeba8da0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 14:17:05 -0400 Subject: [PATCH 077/180] slice4phys - adding notes in docstrings --- phys2bids/slice4phys.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index f0eea8f57..611440a95 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -33,7 +33,10 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): run_timestamps : dictionary Containing tuples of run start and end indexes for each run, based on trigger channels In the form of run_timestamps{Run 1:(start, end, time offset, nb of triggers), Run 2:()} - call an internal function and feed it the dictionary instead + Notes + ----- + find_runs is an internal function to slice4phys + it feeds it dictionary in order to slice BlueprintInput """ # Initialize dictionaries to save run timestamps and phys_in's attributes run_timestamps = {} From 50afdb46c40d0e6bd6f16c77fc18689b25853667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 29 May 2020 14:17:18 -0400 Subject: [PATCH 078/180] slice4phys - adding notes in docstrings --- phys2bids/slice4phys.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 611440a95..d420978e4 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -32,7 +32,10 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): -------- run_timestamps : dictionary Containing tuples of run start and end indexes for each run, based on trigger channels - In the form of run_timestamps{Run 1:(start, end, time offset, nb of triggers), Run 2:()} + It also contains run attributes: time offset from session beggining, and nb of triggers + In the form of run_timestamps{"Run 01":(start, end, time offset, nb of triggers), + "Run 02":(...), + } Notes ----- find_runs is an internal function to slice4phys From e102c81c204877f51d572f5be41d6e8682506672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 2 Jun 2020 10:07:02 -0400 Subject: [PATCH 079/180] some more docstring info --- phys2bids/slice4phys.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index d420978e4..b64fb18d3 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -38,8 +38,9 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): } Notes ----- - find_runs is an internal function to slice4phys - it feeds it dictionary in order to slice BlueprintInput + find_runs is an internal function to slice4phys + it feeds it dictionary in order to slice BlueprintInput + See also: """ # Initialize dictionaries to save run timestamps and phys_in's attributes run_timestamps = {} From 0f8a919aefb3fed6859052bd5d71665316bf15a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 2 Jun 2020 12:29:24 -0400 Subject: [PATCH 080/180] initializing change in docs for split --- docs/howto.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/howto.rst b/docs/howto.rst index a08ee7ccd..cd777849c 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -28,17 +28,28 @@ For the tutorial, we will assume the repository was downloaded in ``/home/arthur cd /home/arthurdent/git/ -What is in the tutorial text file? +What is in the tutorial files? ################################## - +Text file +----------- The file can be found in ``phys2bids/phys2bids/tests/data/tutorial_file.txt``. This file has header information (first 9 lines) which phys2bids will use to process this file, alongside information directly inputted by the user. Following this header information, the data in the file is stored in a column format. In this example, we have time (column 1), MRI volume trigger pulse (column 2), CO2 (column 3), O2 (column 4) and cardiac pulse (column 5) recordings. Each column was sampled at 1000Hz (Interval = 0.001 s). +.. literalinclude:: ../phys2bids/tests/data/tutorial_file.txt + :linenos: + :lines: 1-15 + + +Acknowledge file +----------------- +The file can be found under ``phys2bids/phys2bids/tests/data/tutorial_multirun.acq`` + .. literalinclude:: ../phys2bids/tests/data/tutorial_file.txt :linenos: :lines: 1-15 .. note:: - time is not a "real" channel recorded by LabChart or AcqKnowledge. For this reason, ``phys2bids`` treats it as a hidden channel, always in position 0. Channel 1 will be classed as the first channel recorded in either software. + time is not a "real" channel recorded by LabChart or AcqKnowledge. For this reason, ``phys2bids`` treats it as a hidden channel, always in position 0. Channel 1 will be classed as the first channel recorded in either software. + Using the -info option ###################### From 35a6eea4a791b9d547b9085cf31fc3ae27057e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 2 Jun 2020 16:26:01 -0400 Subject: [PATCH 081/180] changing type in cli for -ntp, -tr - list doesn't work --- phys2bids/cli/run.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index 0b3b8cf3c..9b94d1cb0 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -98,7 +98,8 @@ def _get_parser(): default=None) optional.add_argument('-ntp', '--numtps', dest='num_timepoints_expected', - type=list, + nargs='*', + type=int, help='Number of expected trigger timepoints (TRs). ' 'Default is 0. Note: the estimation of beggining of ' 'neuroimaging acquisition cannot take place with this default.' @@ -106,9 +107,9 @@ def _get_parser(): default=[0, ]) optional.add_argument('-tr', '--tr', dest='tr', - type=list, + nargs='*', + type=float, help='TR of sequence in seconds. ' - 'Default is 0 second.' 'You can list each TR used throughout the session', default=[0, ]) optional.add_argument('-thr', '--threshold', From bfdb07c7450957b46d39616a92fd6d5e07bc257d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 2 Jun 2020 17:11:28 -0400 Subject: [PATCH 082/180] adding microsiemens for eda --- phys2bids/bids_units.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phys2bids/bids_units.py b/phys2bids/bids_units.py index b1eb67138..f03a1e01b 100644 --- a/phys2bids/bids_units.py +++ b/phys2bids/bids_units.py @@ -17,6 +17,8 @@ '°c': '°C', '°celsius': '°C', 'celsius': '°C', # ampere: electric current 'a': 'A', 'ampere': 'A', 'amp': 'A', 'amps': 'A', + # siemens: electric conductance (e.g. EDA) + 'siemens': 'S', # second: time and hertzs '1/hz': 's', '1/hertz': 's', 'hz': 'Hz', '1/s': 'Hz', '1/second': 'Hz', '1/seconds': 'Hz', From 2229c1c3bde22c8d5f0764bfbfcf41eb49550447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 2 Jun 2020 17:12:30 -0400 Subject: [PATCH 083/180] changing argumets for check_trigger, slice4phys --- phys2bids/phys2bids.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 9f997bb25..bb2d34262 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -37,7 +37,7 @@ from phys2bids import utils, viz, _version from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput -from phys2bids import slice4phys +from phys2bids.slice4phys import slice4phys from phys2bids.bids_units import bidsify_units LGR = logging.getLogger(__name__) @@ -308,7 +308,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, 'the session') # Sum of values in ntp_list should be equivalent to num_timepoints_found - phys_in.check_trigger_amount(chtrig=chtrig, thr=thr, + phys_in.check_trigger_amount(thr=thr, num_timepoints_expected=sum(num_timepoints_expected), tr=1) @@ -320,7 +320,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, 'something.') # slice the recording based on user's entries - phys_in_slices = slice4phys(phys_in, num_timepoints_expected, tr) + phys_in_slices = slice4phys(phys_in, num_timepoints_expected, tr, thr) # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # save a figure for each run | give the right acquisition parameters for runs From b6fbb05e3695b888c3153ef345cc79c64f8d0167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 2 Jun 2020 17:13:48 -0400 Subject: [PATCH 084/180] adding argument and changing run_end definition --- phys2bids/slice4phys.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index b64fb18d3..ac2b2ece4 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -4,7 +4,7 @@ from numpy import where -def find_runs(phys_in, ntp_list, tr_list, padding=9): +def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): """ find runs slicing index. @@ -24,6 +24,8 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): tr_list : list a list of float given by the user as `tr` input Default: [1,] + thr : int + inherit threshold for detection of trigger given by user padding : int extra time at beginning and end of timeseries, expressed in seconds (s) Default: 9 @@ -52,7 +54,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): for run_idx, run_tps in enumerate(ntp_list): # correct time offset for this iteration's object - phys_in.check_trigger_amount(ntp=run_tps, tr=tr_list[run_idx]) + phys_in.check_trigger_amount(thr=thr, num_timepoints_expected=run_tps, tr=tr_list[run_idx]) # If it's the very first run, start the run at sample 0, # otherwise "add" the padding @@ -66,7 +68,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): end_sec = (run_tps * tr_list[run_idx]) # define index of the run's last trigger + padding - run_end = where(phys_in.timeseries[0] > end_sec)[0] + padding + run_end = where(phys_in.timeseries[0] == end_sec)[0] + padding # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording @@ -85,7 +87,7 @@ def find_runs(phys_in, ntp_list, tr_list, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human run_timestamps[f"Run {run_idx+1:02}"] = (run_start, run_end, - phys_in.tim_offset, + phys_in.time_offset, phys_in.num_timepoints_found) # update the object so that it will look for the first trigger From a21196c3449eabd50a7ae37eac8e9e5d663e90a7 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 2 Jun 2020 23:54:06 +0200 Subject: [PATCH 085/180] Reorder imports --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index bb2d34262..76acf3b1f 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -35,10 +35,10 @@ from numpy import savetxt, ones from phys2bids import utils, viz, _version +from phys2bids.bids_units import bidsify_units from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput from phys2bids.slice4phys import slice4phys -from phys2bids.bids_units import bidsify_units LGR = logging.getLogger(__name__) From b736b039a254cb99da53dedd49687b05a763db19 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 00:45:17 +0200 Subject: [PATCH 086/180] Following general style --- phys2bids/slice4phys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index ac2b2ece4..d906b444f 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -86,7 +86,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human - run_timestamps[f"Run {run_idx+1:02}"] = (run_start, run_end, + run_timestamps[f'Run {run_idx+1:02}'] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) From 32db15b8b71812f7329462df44bbaa362bb2e6b7 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 00:54:36 +0200 Subject: [PATCH 087/180] Convert dict keys to number for easier integration --- phys2bids/slice4phys.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index d906b444f..43caebde6 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -86,9 +86,9 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human - run_timestamps[f'Run {run_idx+1:02}'] = (run_start, run_end, - phys_in.time_offset, - phys_in.num_timepoints_found) + run_timestamps[run_idx+1] = (run_start, run_end, + phys_in.time_offset, + phys_in.num_timepoints_found) # update the object so that it will look for the first trigger # after previous run's last trigger. maybe padding extends to next run From 37a872419b2d3aea4b5f063b1802e9e5ce5000cf Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 01:06:58 +0200 Subject: [PATCH 088/180] Transform phys_in in dictionary for multirun support, adapt phys_out consequently --- phys2bids/phys2bids.py | 122 ++++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 55 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 76acf3b1f..570f8c4c1 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -267,8 +267,11 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info(f'Reading the file {infile}') phys_in = populate_phys_input(infile, chtrig) + + LGR.info('Checking that units of measure are BIDS compatible') for index, unit in enumerate(phys_in.units): phys_in.units[index] = bidsify_units(unit) + LGR.info('Reading infos') phys_in.print_info(filename) # #!# Here the function viz.plot_channel should be called @@ -320,11 +323,12 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, 'something.') # slice the recording based on user's entries - phys_in_slices = slice4phys(phys_in, num_timepoints_expected, tr, thr) + # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY + phys_in = slice4phys(phys_in, num_timepoints_expected, tr, thr) # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # save a figure for each run | give the right acquisition parameters for runs - for (key, sequence, nb_trigger) in (phys_in_slices.keys(), + for (key, sequence, nb_trigger) in (phys_in.keys(), tr, num_timepoints_expected): viz.save_plot(phys_in[key], num_timepoints_expected[nb_trigger], tr[sequence], chtrig, outdir, filename, sub, ses) @@ -338,13 +342,16 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # save a figure of the trigger viz.save_plot(phys_in, num_timepoints_expected, outdir, filename, sub, ses) + # Reassign phys_in as dictionary + # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY + phys_in = {1: phys_in} else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using both "-ntp" and "-tr" arguments') # The next few lines create a dictionary of different BlueprintInput # objects, one for each unique frequency in phys_in - uniq_freq_list = set(phys_in.freq) + uniq_freq_list = set(phys_in[1].freq) output_amount = len(uniq_freq_list) if output_amount > 1: LGR.warning(f'Found {output_amount} different frequencies in input!') @@ -352,61 +359,66 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info(f'Preparing {output_amount} output files.') # create phys_out dict that will have a blueprint object for each different frequency phys_out = {} + # Also initialise a string to have a + + # Export a (set of) phys_out for each element in phys_in + for run in phys_in.keys(): + for uniq_freq in uniq_freq_list: + # Initialise the key for the (possibly huge amount of) dictionary entries + key = f'{run}_{uniq_freq}' + # copy the phys_in object to the new dict entry + phys_out[key] = deepcopy(phys_in[run]) + # this counter will take into account how many channels are eliminated + count = 0 + # for each channel in the original phys_in object + # take the frequency + for idx, i in enumerate(phys_in[run].freq): + # if that frequency is different than the frequency of the phys_obj entry + if i != uniq_freq: + # eliminate that channel from the dict since we only want channels + # with the same frequency + phys_out[key].delete_at_index(idx - count) + # take into acount the elimination so in the next eliminated channel we + # eliminate correctly + count += 1 + # Also create a BlueprintOutput object for each unique frequency found. + # Populate it with the corresponding blueprint input and replace it + # in the dictionary. + phys_out[key] = BlueprintOutput.init_from_blueprint(phys_out[key]) - for uniq_freq in uniq_freq_list: - # copy the phys_in object to the new dict entry - phys_out[uniq_freq] = deepcopy(phys_in) - # this counter will take into account how many channels are eliminated - count = 0 - # for each channel in the original phys_in object - # take the frequency - for idx, i in enumerate(phys_in.freq): - # if that frequency is different than the frequency of the phys_obj entry - if i != uniq_freq: - # eliminate that channel from the dict since we only want channels - # with the same frequency - phys_out[uniq_freq].delete_at_index(idx - count) - # take into acount the elimination so in the next eliminated channel we - # eliminate correctly - count += 1 - # Also create a BlueprintOutput object for each unique frequency found. - # Populate it with the corresponding blueprint input and replace it - # in the dictionary. - phys_out[uniq_freq] = BlueprintOutput.init_from_blueprint(phys_out[uniq_freq]) - - if heur_file and sub: - LGR.info(f'Preparing BIDS output using {heur_file}') - elif heur_file and not sub: - LGR.warning('While "-heur" was specified, option "-sub" was not.\n' - 'Skipping BIDS formatting.') - - # Preparing output parameters: name and folder. - for uniq_freq in uniq_freq_list: - # If possible, prepare bids renaming. if heur_file and sub: - if output_amount > 1: - # Add "recording-freq" to filename if more than one freq - outfile = use_heuristic(heur_file, sub, ses, filename, - outdir, uniq_freq) - else: - outfile = use_heuristic(heur_file, sub, ses, filename, outdir) + LGR.info(f'Preparing BIDS output using {heur_file}') + elif heur_file and not sub: + LGR.warning('While "-heur" was specified, option "-sub" was not.\n' + 'Skipping BIDS formatting.') + + # Preparing output parameters: name and folder. + for uniq_freq in uniq_freq_list: + # If possible, prepare bids renaming. + if heur_file and sub: + if output_amount > 1: + # Add "recording-freq" to filename if more than one freq + outfile = use_heuristic(heur_file, sub, ses, filename, + outdir, uniq_freq) + else: + outfile = use_heuristic(heur_file, sub, ses, filename, outdir) - else: - outfile = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) - if output_amount > 1: - # Append "freq" to filename if more than one freq - outfile = f'{outfile}_{uniq_freq}' - - LGR.info(f'Exporting files for freq {uniq_freq}') - savetxt(outfile + '.tsv.gz', phys_out[uniq_freq].timeseries, - fmt='%.8e', delimiter='\t') - print_json(outfile, phys_out[uniq_freq].freq, - phys_out[uniq_freq].start_time, - phys_out[uniq_freq].ch_name) - print_summary(filename, num_timepoints_expected, - phys_in.num_timepoints_found, uniq_freq, - phys_out[uniq_freq].start_time, outfile) + else: + outfile = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + if output_amount > 1: + # Append "freq" to filename if more than one freq + outfile = f'{outfile}_{uniq_freq}' + + LGR.info(f'Exporting files for freq {uniq_freq}') + savetxt(outfile + '.tsv.gz', phys_out[key].timeseries, + fmt='%.8e', delimiter='\t') + print_json(outfile, phys_out[key].freq, + phys_out[key].start_time, + phys_out[key].ch_name) + print_summary(filename, num_timepoints_expected, + phys_in[run].num_timepoints_found, uniq_freq, + phys_out[key].start_time, outfile) def _main(argv=None): From 11cbfd7a0ec3546a8543b2f6057a4f9ec215e502 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 01:11:01 +0200 Subject: [PATCH 089/180] Add filename property to BlueprintOutput class --- phys2bids/physio_obj.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 1dfe3c31c..328e5afb7 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -163,21 +163,21 @@ class BlueprintInput(): Attributes ---------- - timeseries : (ch, [tps]) list + timeseries: (ch, [tps]) list List of numpy 1d arrays - one for channel, plus one for time. Time channel has to be the first. Contains all the timeseries recorded. Supports different frequencies! - freq : (ch) list of floats + freq: (ch) list of floats List of floats - one per channel. Contains all the frequencies of the recorded channel. Support different frequencies! - ch_name : (ch) list of strings + ch_name: (ch) list of strings List of names of the channels - can be the header of the columns in the output files. - units : (ch) list of strings + units: (ch) list of strings List of the units of the channels. - trigger_idx : int + trigger_idx: int The trigger index. Optional. Default is 0. num_timepoints_found: int or None Amount of timepoints found in the automatic count. @@ -534,21 +534,23 @@ class BlueprintOutput(): Attributes ---------- - timeseries : (ch x tps) :obj:`numpy.ndarray` + timeseries: (ch x tps) :obj:`numpy.ndarray` Numpy 2d array of timeseries Contains all the timeseries recorded. Impose same frequency! - freq : float + freq: float Shared frequency of the object. Properties - ch_name : (ch) list of strings + ch_name: (ch) list of strings List of names of the channels - can be the header of the columns in the output files. - units : (ch) list of strings + units: (ch) list of strings List of the units of the channels. - start_time : float + start_time: float Starting time of acquisition (equivalent to first TR, or to the opposite sign of the time offset). + filename: string + Filename the object will be saved with. Init as empty string Methods ------- @@ -564,13 +566,14 @@ class BlueprintOutput(): method to populate from input blueprint instead of init """ - def __init__(self, timeseries, freq, ch_name, units, start_time): + def __init__(self, timeseries, freq, ch_name, units, start_time, filename=''): """Initialise BlueprintOutput (see class docstring).""" self.timeseries = is_valid(timeseries, np.ndarray) self.freq = is_valid(freq, (int, float)) self.ch_name = has_size(ch_name, self.ch_amount, 'unknown') self.units = has_size(units, self.ch_amount, '[]') self.start_time = start_time + self.filename = is_valid(filename, str) @property def ch_amount(self): From e3e6fada8fc9d30142f1222b504e9d602f1438ed Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 01:20:59 +0200 Subject: [PATCH 090/180] Add output filename as object property --- phys2bids/phys2bids.py | 45 +++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 570f8c4c1..6d691e801 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -350,16 +350,16 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, 'call phys2bids using both "-ntp" and "-tr" arguments') # The next few lines create a dictionary of different BlueprintInput - # objects, one for each unique frequency in phys_in + # objects, one for each unique frequency for each run in phys_in uniq_freq_list = set(phys_in[1].freq) - output_amount = len(uniq_freq_list) - if output_amount > 1: - LGR.warning(f'Found {output_amount} different frequencies in input!') + freq_amount = len(uniq_freq_list) + run_amount = len(phys_in) + if freq_amount > 1: + LGR.warning(f'Found {freq_amount} different frequencies in input!') - LGR.info(f'Preparing {output_amount} output files.') + LGR.info(f'Preparing {freq_amount*run_amount} output files.') # create phys_out dict that will have a blueprint object for each different frequency phys_out = {} - # Also initialise a string to have a # Export a (set of) phys_out for each element in phys_in for run in phys_in.keys(): @@ -396,29 +396,34 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, for uniq_freq in uniq_freq_list: # If possible, prepare bids renaming. if heur_file and sub: - if output_amount > 1: + if freq_amount > 1: # Add "recording-freq" to filename if more than one freq - outfile = use_heuristic(heur_file, sub, ses, filename, - outdir, uniq_freq) + phys_out[key].filename = use_heuristic(heur_file, sub, ses, + filename, outdir, + uniq_freq) else: - outfile = use_heuristic(heur_file, sub, ses, filename, outdir) + phys_out[key].filename = use_heuristic(heur_file, sub, ses, + filename, outdir) else: - outfile = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) - if output_amount > 1: - # Append "freq" to filename if more than one freq - outfile = f'{outfile}_{uniq_freq}' - - LGR.info(f'Exporting files for freq {uniq_freq}') - savetxt(outfile + '.tsv.gz', phys_out[key].timeseries, + phys_out[key].filename = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + # Append "run" to filename if more than one run + if run_amount > 1: + phys_out[key].filename = f'{phys_out[key].filename}_{run:02d}' + # Append "freq" to filename if more than one freq + if freq_amount > 1: + phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq}' + + LGR.info(f'Exporting files for run {run} freq {uniq_freq}') + savetxt(phys_out[key].filename + '.tsv.gz', phys_out[key].timeseries, fmt='%.8e', delimiter='\t') - print_json(outfile, phys_out[key].freq, + print_json(phys_out[key].filename, phys_out[key].freq, phys_out[key].start_time, phys_out[key].ch_name) print_summary(filename, num_timepoints_expected, phys_in[run].num_timepoints_found, uniq_freq, - phys_out[key].start_time, outfile) + phys_out[key].start_time, phys_out[key].filename) def _main(argv=None): From 0c171172ecce75f12979a5755715f2e2fa203426 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 01:33:45 +0200 Subject: [PATCH 091/180] Add run as an optional variable in use_heuristic --- phys2bids/phys2bids.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 6d691e801..92ec6ef45 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -112,7 +112,7 @@ def print_json(outfile, samp_freq, time_offset, ch_name): utils.writejson(outfile, summary, indent=4, sort_keys=False) -def use_heuristic(heur_file, sub, ses, filename, outdir, record_label=''): +def use_heuristic(heur_file, sub, ses, filename, outdir, run='', record_label=''): """ Import and use the heuristic specified by the user to rename the file. @@ -146,7 +146,7 @@ def use_heuristic(heur_file, sub, ses, filename, outdir, record_label=''): # Initialise a dictionary of bids_keys that has already "recording" bids_keys = {'sub': '', 'ses': '', 'task': '', 'acq': '', 'ce': '', - 'dir': '', 'rec': '', 'run': '', 'recording': record_label} + 'dir': '', 'rec': '', 'run': run, 'recording': record_label} # Start filling bids_keys dictionary and path with subject and session if sub.startswith('sub-'): @@ -400,7 +400,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Add "recording-freq" to filename if more than one freq phys_out[key].filename = use_heuristic(heur_file, sub, ses, filename, outdir, - uniq_freq) + record_label=uniq_freq) else: phys_out[key].filename = use_heuristic(heur_file, sub, ses, filename, outdir) From e6be352c0fc8693075232c5c1cf5e9a30c03711a Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 01:37:24 +0200 Subject: [PATCH 092/180] Rename bids_unit.py as bids.py --- phys2bids/{bids_units.py => bids.py} | 0 phys2bids/phys2bids.py | 2 +- phys2bids/tests/{test_bids_units.py => test_bids.py} | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename phys2bids/{bids_units.py => bids.py} (100%) rename phys2bids/tests/{test_bids_units.py => test_bids.py} (88%) diff --git a/phys2bids/bids_units.py b/phys2bids/bids.py similarity index 100% rename from phys2bids/bids_units.py rename to phys2bids/bids.py diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 92ec6ef45..2bae49f58 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -35,7 +35,7 @@ from numpy import savetxt, ones from phys2bids import utils, viz, _version -from phys2bids.bids_units import bidsify_units +from phys2bids.bids import bidsify_units from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput from phys2bids.slice4phys import slice4phys diff --git a/phys2bids/tests/test_bids_units.py b/phys2bids/tests/test_bids.py similarity index 88% rename from phys2bids/tests/test_bids_units.py rename to phys2bids/tests/test_bids.py index b985951df..c92812778 100644 --- a/phys2bids/tests/test_bids_units.py +++ b/phys2bids/tests/test_bids.py @@ -1,5 +1,5 @@ -from phys2bids.bids_units import bidsify_units -from phys2bids.bids_units import UNIT_ALIASES +from phys2bids.bids import bidsify_units +from phys2bids.bids import UNIT_ALIASES def test_bidsify_units(): From 6310cf167523d51f612f8a2345a448aeebb05b20 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 01:42:33 +0200 Subject: [PATCH 093/180] Move use_heuristic from phys2bids.py to bids.py --- phys2bids/bids.py | 82 +++++++++++++++++++++++++++++++ phys2bids/phys2bids.py | 81 +----------------------------- phys2bids/tests/test_bids.py | 41 +++++++++++++++- phys2bids/tests/test_phys2bids.py | 37 -------------- 4 files changed, 123 insertions(+), 118 deletions(-) diff --git a/phys2bids/bids.py b/phys2bids/bids.py index 3ec4fc825..bc1c4889c 100644 --- a/phys2bids/bids.py +++ b/phys2bids/bids.py @@ -1,4 +1,8 @@ import logging +import os +from pathlib import Path + +from phys2bids import utils LGR = logging.getLogger(__name__) @@ -91,3 +95,81 @@ def bidsify_units(orig_unit): LGR.warning(f'The given unit {orig_unit} does not have aliases, ' f'passing it as is') return orig_unit + + +def use_heuristic(heur_file, sub, ses, filename, outdir, run='', record_label=''): + """ + Import and use the heuristic specified by the user to rename the file. + + Parameters + ---------- + heur_file: path + Fullpath to heuristic file. + sub: str or int + Name of subject. + ses: str or int or None + Name of session. + filename: path + Name of the input of phys2bids. + outdir: str or path + Path to the directory that will become the "site" folder + ("root" folder of BIDS database). + record_label: str + Optional label for the "record" entry of BIDS. + + Returns + ------- + heurpath: str or path + Returned fullpath to tsv.gz new file (post BIDS formatting). + + Raises + ------ + KeyError + if `bids_keys['task']` is empty + """ + utils.check_file_exists(heur_file) + + # Initialise a dictionary of bids_keys that has already "recording" + bids_keys = {'sub': '', 'ses': '', 'task': '', 'acq': '', 'ce': '', + 'dir': '', 'rec': '', 'run': run, 'recording': record_label} + + # Start filling bids_keys dictionary and path with subject and session + if sub.startswith('sub-'): + bids_keys['sub'] = sub[4:] + fldr = os.path.join(outdir, sub) + else: + bids_keys['sub'] = sub + fldr = os.path.join(outdir, f'sub-{sub}') + + if ses: + if ses.startswith('ses-'): + bids_keys['ses'] = ses[4:] + fldr = os.path.join(fldr, ses) + else: + bids_keys['ses'] = ses + fldr = os.path.join(fldr, f'ses-{ses}') + + # Load heuristic and use it to fill dictionary + heur = utils.load_heuristic(heur_file) + bids_keys.update(heur.heur(Path(filename).stem)) + + # If bids_keys['task'] is still empty, stop the program + if not bids_keys['task']: + LGR.warning(f'The heuristic {heur_file} could not deal with' + f'{Path(filename).stem}') + raise KeyError('No "task" attribute found') + + # Compose name by looping in the bids_keys dictionary + # and adding nonempty keys + name = '' + for key in bids_keys: + if bids_keys[key] != '': + name = f'{name}{key}-{bids_keys[key]}_' + + # Finish path, create it, add filename, export + fldr = os.path.join(fldr, 'func') + utils.path_exists_or_make_it(fldr) + + heurpath = os.path.join(fldr, f'{name}physio') + + return heurpath diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 2bae49f58..6a126a71a 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -30,12 +30,11 @@ import datetime import logging from copy import deepcopy -from pathlib import Path from numpy import savetxt, ones from phys2bids import utils, viz, _version -from phys2bids.bids import bidsify_units +from phys2bids.bids import bidsify_units, use_heuristic from phys2bids.cli.run import _get_parser from phys2bids.physio_obj import BlueprintOutput from phys2bids.slice4phys import slice4phys @@ -112,84 +111,6 @@ def print_json(outfile, samp_freq, time_offset, ch_name): utils.writejson(outfile, summary, indent=4, sort_keys=False) -def use_heuristic(heur_file, sub, ses, filename, outdir, run='', record_label=''): - """ - Import and use the heuristic specified by the user to rename the file. - - Parameters - ---------- - heur_file: path - Fullpath to heuristic file. - sub: str or int - Name of subject. - ses: str or int or None - Name of session. - filename: path - Name of the input of phys2bids. - outdir: str or path - Path to the directory that will become the "site" folder - ("root" folder of BIDS database). - record_label: str - Optional label for the "record" entry of BIDS. - - Returns - ------- - heurpath: str or path - Returned fullpath to tsv.gz new file (post BIDS formatting). - - Raises - ------ - KeyError - if `bids_keys['task']` is empty - """ - utils.check_file_exists(heur_file) - - # Initialise a dictionary of bids_keys that has already "recording" - bids_keys = {'sub': '', 'ses': '', 'task': '', 'acq': '', 'ce': '', - 'dir': '', 'rec': '', 'run': run, 'recording': record_label} - - # Start filling bids_keys dictionary and path with subject and session - if sub.startswith('sub-'): - bids_keys['sub'] = sub[4:] - fldr = os.path.join(outdir, sub) - else: - bids_keys['sub'] = sub - fldr = os.path.join(outdir, f'sub-{sub}') - - if ses: - if ses.startswith('ses-'): - bids_keys['ses'] = ses[4:] - fldr = os.path.join(fldr, ses) - else: - bids_keys['ses'] = ses - fldr = os.path.join(fldr, f'ses-{ses}') - - # Load heuristic and use it to fill dictionary - heur = utils.load_heuristic(heur_file) - bids_keys.update(heur.heur(Path(filename).stem)) - - # If bids_keys['task'] is still empty, stop the program - if not bids_keys['task']: - LGR.warning(f'The heuristic {heur_file} could not deal with' - f'{Path(filename).stem}') - raise KeyError('No "task" attribute found') - - # Compose name by looping in the bids_keys dictionary - # and adding nonempty keys - name = '' - for key in bids_keys: - if bids_keys[key] != '': - name = f'{name}{key}-{bids_keys[key]}_' - - # Finish path, create it, add filename, export - fldr = os.path.join(fldr, 'func') - utils.path_exists_or_make_it(fldr) - - heurpath = os.path.join(fldr, f'{name}physio') - - return heurpath - - def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, sub=None, ses=None, chtrig=0, chsel=None, num_timepoints_expected=0, tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): diff --git a/phys2bids/tests/test_bids.py b/phys2bids/tests/test_bids.py index c92812778..7b541aae6 100644 --- a/phys2bids/tests/test_bids.py +++ b/phys2bids/tests/test_bids.py @@ -1,4 +1,9 @@ -from phys2bids.bids import bidsify_units +import os +from pkg_resources import resource_filename + +import pytest + +from phys2bids.bids import bidsify_units, use_heuristic from phys2bids.bids import UNIT_ALIASES @@ -18,3 +23,37 @@ def test_bidsify_units(): # test there is not problem with every unit in the dict for unit_key in UNIT_ALIASES.keys(): assert bidsify_units(unit_key) == UNIT_ALIASES[unit_key] + + +@pytest.mark.parametrize('test_sub', ['SBJ01', 'sub-006', '006']) +@pytest.mark.parametrize('test_ses', ['', 'S05', 'ses-42', '42']) +def test_use_heuristic(tmpdir, test_sub, test_ses): + test_heur_path = resource_filename('phys2bids', 'heuristics') + test_heur_file = 'heur_test_acq.py' + test_full_heur_path = os.path.join(test_heur_path, test_heur_file) + test_input_path = resource_filename('phys2bids', 'tests/data') + test_input_file = 'Test_belt_pulse_samefreq.acq' + test_full_input_path = os.path.join(test_input_path, test_input_file) + test_outdir = tmpdir + test_record_label = 'test' + + heur_path = use_heuristic(test_full_heur_path, test_sub, test_ses, + test_full_input_path, test_outdir, test_record_label) + + if test_sub[:4] == 'sub-': + test_sub = test_sub[4:] + + test_result_path = (f'{tmpdir}/sub-{test_sub}') + test_result_name = (f'sub-{test_sub}') + + if test_ses[:4] == 'ses-': + test_ses = test_ses[4:] + + if test_ses: + test_result_path = (f'{test_result_path}/ses-{test_ses}') + test_result_name = (f'{test_result_name}_ses-{test_ses}') + + test_result = (f'{test_result_path}/func/{test_result_name}' + f'_task-test_rec-biopac_run-01_recording-test_physio') + + assert os.path.normpath(test_result) == os.path.normpath(str(heur_path)) diff --git a/phys2bids/tests/test_phys2bids.py b/phys2bids/tests/test_phys2bids.py index dd4ea28f2..aea1f59c1 100644 --- a/phys2bids/tests/test_phys2bids.py +++ b/phys2bids/tests/test_phys2bids.py @@ -4,9 +4,6 @@ import json import os -from pkg_resources import resource_filename - -import pytest from phys2bids import phys2bids @@ -44,37 +41,3 @@ def test_print_json(tmpdir): loaded_data = json.load(src) assert test_json_data == loaded_data - - -@pytest.mark.parametrize('test_sub', ['SBJ01', 'sub-006', '006']) -@pytest.mark.parametrize('test_ses', ['', 'S05', 'ses-42', '42']) -def test_use_heuristic(tmpdir, test_sub, test_ses): - test_heur_path = resource_filename('phys2bids', 'heuristics') - test_heur_file = 'heur_test_acq.py' - test_full_heur_path = os.path.join(test_heur_path, test_heur_file) - test_input_path = resource_filename('phys2bids', 'tests/data') - test_input_file = 'Test_belt_pulse_samefreq.acq' - test_full_input_path = os.path.join(test_input_path, test_input_file) - test_outdir = tmpdir - test_record_label = 'test' - - heur_path = phys2bids.use_heuristic(test_full_heur_path, test_sub, test_ses, - test_full_input_path, test_outdir, test_record_label) - - if test_sub[:4] == 'sub-': - test_sub = test_sub[4:] - - test_result_path = (f'{tmpdir}/sub-{test_sub}') - test_result_name = (f'sub-{test_sub}') - - if test_ses[:4] == 'ses-': - test_ses = test_ses[4:] - - if test_ses: - test_result_path = (f'{test_result_path}/ses-{test_ses}') - test_result_name = (f'{test_result_name}_ses-{test_ses}') - - test_result = (f'{test_result_path}/func/{test_result_name}' - f'_task-test_rec-biopac_run-01_recording-test_physio') - - assert os.path.normpath(test_result) == os.path.normpath(str(heur_path)) From 524024153eb694fcd1bd9f9d50f39ca2f1cf5211 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 02:08:51 +0200 Subject: [PATCH 094/180] Adapt call to use_heuristic for possible multirun and multifreq --- phys2bids/phys2bids.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 6a126a71a..9d5cf4350 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -272,14 +272,21 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # The next few lines create a dictionary of different BlueprintInput # objects, one for each unique frequency for each run in phys_in + # they also save the amount of runs and unique frequencies uniq_freq_list = set(phys_in[1].freq) freq_amount = len(uniq_freq_list) run_amount = len(phys_in) if freq_amount > 1: LGR.warning(f'Found {freq_amount} different frequencies in input!') + # If heuristics are used, init a dict of arguments to pass to use_heuristic + if heur_file and sub: + heur_args = {'heur_file': heur_file, 'sub': sub, 'ses': ses, + 'filename': filename, 'outdir': outdir, 'run': '', + 'record_label': ''} + LGR.info(f'Preparing {freq_amount*run_amount} output files.') - # create phys_out dict that will have a blueprint object for each different frequency + # Create phys_out dict that will have a blueprint object for each different frequency phys_out = {} # Export a (set of) phys_out for each element in phys_in @@ -317,14 +324,15 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, for uniq_freq in uniq_freq_list: # If possible, prepare bids renaming. if heur_file and sub: + # Dictionary heur_args was initialised before the for loops + # Add run info to heur_args if more than one run is present + if run_amount > 1: + heur_args['run'] = f'{run:02d}' + # Add "recording-freq" to filename if more than one freq if freq_amount > 1: - # Add "recording-freq" to filename if more than one freq - phys_out[key].filename = use_heuristic(heur_file, sub, ses, - filename, outdir, - record_label=uniq_freq) - else: - phys_out[key].filename = use_heuristic(heur_file, sub, ses, - filename, outdir) + heur_args['record_label'] = f'freq{uniq_freq}' + + phys_out[key].filename = use_heuristic(**heur_args) else: phys_out[key].filename = os.path.join(outdir, From 90681a4550b8664177c76e2158d2e22ec732f543 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 02:28:31 +0200 Subject: [PATCH 095/180] Avoid overwriting --- phys2bids/phys2bids.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 9d5cf4350..8a3af0777 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -324,16 +324,28 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, for uniq_freq in uniq_freq_list: # If possible, prepare bids renaming. if heur_file and sub: - # Dictionary heur_args was initialised before the for loops # Add run info to heur_args if more than one run is present if run_amount > 1: heur_args['run'] = f'{run:02d}' - # Add "recording-freq" to filename if more than one freq + + # Append "recording-freq" to filename if more than one freq if freq_amount > 1: heur_args['record_label'] = f'freq{uniq_freq}' phys_out[key].filename = use_heuristic(**heur_args) + # If any filename exists already because of multirun, append labels + # But warn about the non-validity of this BIDS-like name. + if run_amount > 1: + if any([phys.filename == phys_out[key].filename + for phys in phys_out.values()]): + phys_out[key].filename = (f'{phys_out[key].filename}' + '_take-{run}') + LGR.warning('Identified multiple outputs with the same name.\n' + 'Adding a fake label to avoid overwriting.\n' + '!!! ATTENTION !!! the output is not BIDS compliant.\n' + 'Please check heuristics to solve the problem.') + else: phys_out[key].filename = os.path.join(outdir, os.path.splitext(os.path.basename(filename))[0]) From 1a2d1b9230f307ca4e89e7645a9ada1649262435 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 02:29:16 +0200 Subject: [PATCH 096/180] Change warning --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 8a3af0777..d4324aea8 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -342,7 +342,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out[key].filename = (f'{phys_out[key].filename}' '_take-{run}') LGR.warning('Identified multiple outputs with the same name.\n' - 'Adding a fake label to avoid overwriting.\n' + 'Appending fake label to avoid overwriting.\n' '!!! ATTENTION !!! the output is not BIDS compliant.\n' 'Please check heuristics to solve the problem.') From d687147a47046f773b652d8598e3f4cdd032ff1a Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 3 Jun 2020 02:33:14 +0200 Subject: [PATCH 097/180] Pass the run information to heuristics --- phys2bids/bids.py | 2 +- phys2bids/heuristics/heur_euskalibur.py | 2 +- phys2bids/heuristics/heur_test_acq.py | 2 +- phys2bids/heuristics/heur_tutorial.py | 2 +- phys2bids/tests/test_bids.py | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/phys2bids/bids.py b/phys2bids/bids.py index bc1c4889c..ed2ab449f 100644 --- a/phys2bids/bids.py +++ b/phys2bids/bids.py @@ -151,7 +151,7 @@ def use_heuristic(heur_file, sub, ses, filename, outdir, run='', record_label='' # Load heuristic and use it to fill dictionary heur = utils.load_heuristic(heur_file) - bids_keys.update(heur.heur(Path(filename).stem)) + bids_keys.update(heur.heur(Path(filename).stem, run)) # If bids_keys['task'] is still empty, stop the program if not bids_keys['task']: diff --git a/phys2bids/heuristics/heur_euskalibur.py b/phys2bids/heuristics/heur_euskalibur.py index 8c0e1d5e2..75ae8c6bf 100644 --- a/phys2bids/heuristics/heur_euskalibur.py +++ b/phys2bids/heuristics/heur_euskalibur.py @@ -1,7 +1,7 @@ import fnmatch -def heur(physinfo): +def heur(physinfo, run=''): """ Set of if .. elif statements to fill BIDS names. diff --git a/phys2bids/heuristics/heur_test_acq.py b/phys2bids/heuristics/heur_test_acq.py index 9f553dcfb..883611997 100644 --- a/phys2bids/heuristics/heur_test_acq.py +++ b/phys2bids/heuristics/heur_test_acq.py @@ -1,7 +1,7 @@ import fnmatch -def heur(physinfo): +def heur(physinfo, run=''): """ Set of if .. elif statements to fill BIDS names. diff --git a/phys2bids/heuristics/heur_tutorial.py b/phys2bids/heuristics/heur_tutorial.py index eebc08259..694c6330e 100644 --- a/phys2bids/heuristics/heur_tutorial.py +++ b/phys2bids/heuristics/heur_tutorial.py @@ -1,7 +1,7 @@ import fnmatch -def heur(physinfo): +def heur(physinfo, run=''): """ Set of if .. elif statements to fill BIDS names. diff --git a/phys2bids/tests/test_bids.py b/phys2bids/tests/test_bids.py index 7b541aae6..828bcd26b 100644 --- a/phys2bids/tests/test_bids.py +++ b/phys2bids/tests/test_bids.py @@ -38,7 +38,8 @@ def test_use_heuristic(tmpdir, test_sub, test_ses): test_record_label = 'test' heur_path = use_heuristic(test_full_heur_path, test_sub, test_ses, - test_full_input_path, test_outdir, test_record_label) + test_full_input_path, test_outdir, + record_label=test_record_label) if test_sub[:4] == 'sub-': test_sub = test_sub[4:] From 6a35b3b76af0a02ec2f7a088fa9504cbc47d9ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 3 Jun 2020 17:44:50 -0400 Subject: [PATCH 098/180] minor changes for troubleshooting multirun workflow --- phys2bids/bids_units.py | 86 +++++++++++++++++++++++++++++++++++++++++ phys2bids/phys2bids.py | 8 ++-- phys2bids/physio_obj.py | 6 ++- phys2bids/slice4phys.py | 23 ++++++----- phys2bids/viz.py | 2 +- 5 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 phys2bids/bids_units.py diff --git a/phys2bids/bids_units.py b/phys2bids/bids_units.py new file mode 100644 index 000000000..f03a1e01b --- /dev/null +++ b/phys2bids/bids_units.py @@ -0,0 +1,86 @@ +import logging + +LGR = logging.getLogger(__name__) + +unit_aliases = { + # kelvin: thermodynamic temperature + 'k': 'K', 'kelvin': 'K', 'kelvins': 'K', + # mole: amount of substance + 'mol': 'mol', 'mole': 'mol', + # newton: force, weight + 'newton': 'N', 'newtons': 'N', 'n': 'N', + # pascal: pressure, stress + 'pascal': 'Pa', 'pascals': 'Pa', 'pa': 'Pa', + # volt: voltage (electrical potential), emf + 'v': 'V', 'volt': 'V', 'volts': 'V', + # degree Celsius: temperature relative to 273.15 K + '°c': '°C', '°celsius': '°C', 'celsius': '°C', + # ampere: electric current + 'a': 'A', 'ampere': 'A', 'amp': 'A', 'amps': 'A', + # siemens: electric conductance (e.g. EDA) + 'siemens': 'S', + # second: time and hertzs + '1/hz': 's', '1/hertz': 's', 'hz': 'Hz', + '1/s': 'Hz', '1/second': 'Hz', '1/seconds': 'Hz', + '1/sec': 'Hz', '1/secs': 'Hz', 'hertz': 'Hz', + 'second': 's', 'seconds': 's', 'sec': 's', + 'secs': 's', 's': 's', +} + +# Init dictionary of aliases for multipliers. Entries are still lowercase +prefix_aliases = { + # Multiples - skip "mega" and only up to "tera" + 'da': 'da', 'deca': 'da', 'h': 'h', 'hecto': 'h', + 'k': 'k', 'kilo': 'k', 'g': 'G', 'giga': 'G', 't': 'T', + 'tera': 'T', + # Submultipliers + 'd': 'd', 'deci': 'd', 'c': 'c', 'centi': 'c', + 'milli': 'm', 'm': 'm', 'µ': 'µ', 'micro': 'µ', + 'n': 'n', 'nano': 'n', 'p': 'p', 'pico': 'p', + 'f': 'f', 'femto': 'f', 'a': 'a', 'atto': 'a', + 'z': 'z', 'zepto': 'z', 'y': 'y', 'yocto': 'y', +} + + +def bidsify_units(orig_unit): + """ + Read the input unit of measure and use the dictionary of aliases + to bidsify its value. + It is possible to make simple conversions + + Parameters + ---------- + unit: string + Unit of measure, might or might not be BIDS compliant. + + Returns + ------- + new_unit: str + BIDSified alias of input unit + + Notes + ----- + This function should implement a double check, one for unit and + the other for prefixes (e.g. "milli"). However, that is going to be tricky, + unless there is a weird way to multiply two dictionaries together. + """ + # call prefix and unit dicts + # for every unit alias in the dict + orig_unit = orig_unit.lower() + for u_key in unit_aliases.keys(): + if orig_unit.endswith(u_key): + new_unit = unit_aliases[u_key] + unit = orig_unit[:-len(u_key)] + if unit != '': + # for every prefix alias + prefix = prefix_aliases.get(unit, '') + if prefix == '': + LGR.warning(f'The given unit prefix {unit} does not have aliases, ' + f'passing it as is') + prefix = orig_unit[:len(unit)] + return prefix + new_unit + else: + return new_unit + LGR.warning(f'The given unit {orig_unit} does not have aliases, ' + f'passing it as is') + return orig_unit diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index d4324aea8..e7a89d9b6 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -249,10 +249,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # save a figure for each run | give the right acquisition parameters for runs - for (key, sequence, nb_trigger) in (phys_in.keys(), - tr, num_timepoints_expected): - viz.save_plot(phys_in[key], num_timepoints_expected[nb_trigger], - tr[sequence], chtrig, outdir, filename, sub, ses) + for key, sequence, nb_trigger in zip(phys_in.keys(), tr, num_timepoints_expected): + viz.save_plot(phys_in[key], nb_trigger, sequence, + chtrig, outdir, f'{filename}-Run{key+1:02}', sub, ses) # Single run acquisition type, or : nothing to split workflow else: @@ -290,6 +289,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out = {} # Export a (set of) phys_out for each element in phys_in + # what's the run key ??? 1 or 0 for run in phys_in.keys(): for uniq_freq in uniq_freq_list: # Initialise the key for the (possibly huge amount of) dictionary entries diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 328e5afb7..5e8501ad4 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -287,8 +287,10 @@ def __getitem__(self, idx): self.trigger_idx = 0 trigger_length = len(self.timeseries[self.trigger_idx]) - + # LGRinfo instead of print + print(idx, trigger_length) # If idx is an integer, return an "instantaneous slice" and initialise slice + # we have to deal with phys_in[idx:], cause idx is already slice(idx, None, None) if isinstance(idx, int): return_instant = True @@ -297,7 +299,7 @@ def __getitem__(self, idx): idx = trigger_length + idx idx = slice(idx, idx + 1) - + # #### this statement cannot be interpreted if idx is None if idx.start >= trigger_length or idx.stop > trigger_length: raise IndexError(f'slice ({idx.start}, {idx.stop}) is out of ' f'bounds for channel {self.trigger_idx} ' diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 43caebde6..cdd1fa564 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -61,14 +61,17 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): if run_idx == 0: run_start = 0 else: - run_start = where(phys_in.timeseries[0] >= 0)[0] - padding + run_start = where(phys_in.timeseries[0] <= 0)[0][0] - padding # Defining end of acquisition # run length in seconds - end_sec = (run_tps * tr_list[run_idx]) + end_sec = run_tps * tr_list[run_idx] - # define index of the run's last trigger + padding - run_end = where(phys_in.timeseries[0] == end_sec)[0] + padding + # define index of the run's last trigger + padding (HAS TO BE INT type) + # pick first value of time array that is over specified run length + # where returns list of values over end_sec and its dtype, choose [list][first value] + run_end = int(where(phys_in.timeseries[0] > end_sec)[0][0] + padding) + update = int(run_end - padding + 1) # if the padding is too much for the remaining timeseries length # then the padding stops at the end of recording @@ -86,13 +89,15 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human - run_timestamps[run_idx+1] = (run_start, run_end, - phys_in.time_offset, - phys_in.num_timepoints_found) + # LGRinfo + print(run_idx) + run_timestamps[run_idx] = (run_start, run_end, + phys_in.time_offset, + phys_in.num_timepoints_found) - # update the object so that it will look for the first trigger + # update the object so that next iteration will look for the first trigger # after previous run's last trigger. maybe padding extends to next run - phys_in = phys_in[(run_end - padding + 1):] + phys_in = phys_in[update:-1] return run_timestamps diff --git a/phys2bids/viz.py b/phys2bids/viz.py index ad54c0df0..ff05fb1ef 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -168,7 +168,7 @@ def ntr2time(x): ax2.set_title('Ending triggers for selected threshold') subplot.plot(time, trigger, '-', time, block, '-') subplot.fill_between(time, block, where=block >= d, interpolate=True, color='#ffbb6e') - plt.savefig(fileprefix + '_trigger_time.png', dpi=dpi) + plt.savefig(fileprefix + filename + '_trigger_time.png', dpi=dpi) plt.close() From 66fe785329929402e5fb78098cf26387ad00f696 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 00:54:46 +0200 Subject: [PATCH 099/180] Make run_idx human readable in print and in dictionary. Reformat docstrings to be more "flakey", add comments ### --- phys2bids/slice4phys.py | 58 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index cdd1fa564..f388a0b35 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -6,38 +6,39 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): """ - find runs slicing index. + Find runs slicing index. - Returns dictionary key for each run in BlueprintInput object based on user's entries - Each key has a tuple of 4 elements. 2 expressing the timestamps of run in nb of samples - Timestamps are the index of first and last triggers of a run, adjusted with padding - run_start and run_end indexes refer to the samples contained in the whole session - first trigger time offset and nb of triggers contained in the run are also indicated + Returns dictionary key for each run in BlueprintInput object based on + user's entries. Each key has a tuple of 4 elements, 2 expressing the + timestamps of run in nb of samples. Timestamps are the index of first and + last triggers of a run, adjusted with padding. run_start and run_end + indexes refer to the samples contained in the whole session. + First trigger time offset and nb of triggers contained in the run are also indicated. Parameters --------- - phys_in : BlueprintInput object + phys_in: BlueprintInput object Object returned by BlueprintInput class - ntp_list : list + ntp_list: list a list of integers given by the user as `ntp` input Default: [0, ] - tr_list : list + tr_list: list a list of float given by the user as `tr` input Default: [1,] - thr : int + thr: int inherit threshold for detection of trigger given by user - padding : int + padding: int extra time at beginning and end of timeseries, expressed in seconds (s) Default: 9 Returns -------- - run_timestamps : dictionary - Containing tuples of run start and end indexes for each run, based on trigger channels - It also contains run attributes: time offset from session beggining, and nb of triggers - In the form of run_timestamps{"Run 01":(start, end, time offset, nb of triggers), - "Run 02":(...), - } + run_timestamps: dictionary + Containing tuples of run start and end indexes for each run, based on + trigger channels. It also contains run attributes: time offset from + session beggining, and nb of triggers in the form of + run_timestamps{1:(start, end, time offset, nb of triggers), + 2:(...), ... } Notes ----- find_runs is an internal function to slice4phys @@ -90,10 +91,15 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human # LGRinfo - print(run_idx) - run_timestamps[run_idx] = (run_start, run_end, - phys_in.time_offset, - phys_in.num_timepoints_found) + ### Indeed, you need to import logging and add LGR = logging.getLogger(__name__) + ### at the beginning of the script, to be able to log with the rest of phys2bids, + ### then ALL THE PRINTS should either become LGR.info or LGR.warnings. + ### Also, please consider to make the run_idx human readable (start from 1), + ### Also, please make the message more informative. + print(run_idx + 1) + run_timestamps[run_idx + 1] = (run_start, run_end, + phys_in.time_offset, + phys_in.num_timepoints_found) # update the object so that next iteration will look for the first trigger # after previous run's last trigger. maybe padding extends to next run @@ -108,21 +114,21 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): Parameters --------- - phys_in : BlueprintInput object + phys_in: BlueprintInput object Object returned by BlueprintInput class - ntp_list : list + ntp_list: list a list of integers given by the user as `ntp` input Default: [0, ] - tr_list : list + tr_list: list a list of float given by the user as `tr` input Default: [1,] - padding : int + padding: int extra time at beginning and end of timeseries, expressed in seconds (s) Default: 9 Returns -------- - phys_in_slices : dict + phys_in_slices: dict keys start by `run 1` until last (`run n`). items are slices of BlueprintInput objects based on timestamps returned by internal function (`slice4phys` takes the same arguments as `find_runs`) From fc398990875db5a21153369e65d727f60ac46710 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 00:59:09 +0200 Subject: [PATCH 100/180] Change " into ' --- phys2bids/viz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index ff05fb1ef..1463e193b 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -124,7 +124,7 @@ def ntr2time(x): subplot.set_ylabel('Volts') subplot.plot(time, trigger, '-', time, thrline, 'r-.', time, block, '-') subplot.fill_between(time, block, where=block >= d, interpolate=True, color='#ffbb6e') - subplot.legend(["trigger", "Trigger detection threshold", "time block"], loc='upper right') + subplot.legend(['trigger', 'Trigger detection threshold', 'time block'], loc='upper right') # plot the first spike according to the user threshold subplot = fig.add_subplot(223) subplot.set_xlim([-tr * 4, tr * 4]) @@ -222,7 +222,7 @@ def plot_all(ch_name, timeseries, units, freq, infile, outfile='', dpi=SET_DPI, ax[row].set_ylabel(units[row + 1]) ax[row].xlim = 30 * 60 * freq[0] # maximum display of half an hour ax[row].grid() - ax[row].set_xlabel("seconds") + ax[row].set_xlabel('seconds') if outfile == '': outfile = os.path.splitext(os.path.basename(infile))[0] + '.png' LGR.info(f'saving channel plot to {outfile}') From 9aa8706a5d45baae6a906329bd69e3307053c4f3 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:02:02 +0200 Subject: [PATCH 101/180] Flake the docstring --- phys2bids/interfaces/acq.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/phys2bids/interfaces/acq.py b/phys2bids/interfaces/acq.py index 7ea3e4ca7..fd6139892 100644 --- a/phys2bids/interfaces/acq.py +++ b/phys2bids/interfaces/acq.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" -phys2bids interface for acqknowledge files. -""" +"""phys2bids interface for acqknowledge files.""" import logging import warnings From 4ddf4fe51f45fece5c8de5a59bb1b72bfe45be6c Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:03:55 +0200 Subject: [PATCH 102/180] Solve a merge conflict, more 'flaking' for docstrings --- phys2bids/interfaces/txt.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/phys2bids/interfaces/txt.py b/phys2bids/interfaces/txt.py index bbaa1f41d..31cc644ca 100644 --- a/phys2bids/interfaces/txt.py +++ b/phys2bids/interfaces/txt.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" -phys2bids interface for txt files. -""" +"""phys2bids interface for txt files.""" import logging from collections import Counter @@ -250,11 +248,7 @@ def process_acq(channel_list, chtrig, header=[]): t_ch = np.ogrid[0:duration:interval[0]][:-1] # create time channel timeseries = [t_ch, ] + timeseries freq = check_multifreq(timeseries, freq) -<<<<<<< HEAD - return BlueprintInput(timeseries, freq, names, units, chtrig) -======= return BlueprintInput(timeseries, freq, names, units, chtrig + 1) ->>>>>>> 4dadc7e9a0a3d050635ab4e6ff569e235cceebdb def read_header_and_channels(filename, chtrig): From a41efe276a337879f512c8bc938127faea894746 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:09:49 +0200 Subject: [PATCH 103/180] Add ### comment --- phys2bids/viz.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 1463e193b..873665e47 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -10,7 +10,8 @@ SET_DPI = 100 FIGSIZE = (18, 10) - +### This definition needs a more intuitive name ('export_trigger_plot' for instance?) +### AS it depends on the one after, please put it after it. def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, sub, ses): """ Save a trigger plot. From e40ae20a168d01bbb1f624900234d11badfeebb6 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:13:56 +0200 Subject: [PATCH 104/180] More ### comment and remove nonexistent defaults --- phys2bids/slice4phys.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index f388a0b35..a88b32444 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -3,7 +3,9 @@ from numpy import where - +### If you mean this function *not* to be used outside of slice4phys, then you can prefix it with '_'. +### However, I don't see the reason why this shouldn't be used elsewhere if necessary! +### Think about removing the Notes if you agree. Also, if you don't need the see also, remove it please. def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): """ Find runs slicing index. @@ -21,10 +23,8 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): Object returned by BlueprintInput class ntp_list: list a list of integers given by the user as `ntp` input - Default: [0, ] tr_list: list a list of float given by the user as `tr` input - Default: [1,] thr: int inherit threshold for detection of trigger given by user padding: int @@ -45,7 +45,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): it feeds it dictionary in order to slice BlueprintInput See also: """ - # Initialize dictionaries to save run timestamps and phys_in's attributes + # Initialize dictionaries to save run timestamps and phys_in attributes run_timestamps = {} # Express the padding in samples equivalent From 1a8469b492eeda28938571c932e52bbce539a545 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:15:01 +0200 Subject: [PATCH 105/180] More ### comments --- phys2bids/viz.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 873665e47..114a74058 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -11,7 +11,8 @@ FIGSIZE = (18, 10) ### This definition needs a more intuitive name ('export_trigger_plot' for instance?) -### AS it depends on the one after, please put it after it. +### Since it depends on the one after, please put it after it. +### Think also about reordering the inputs, either by importance or by order of use. def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, sub, ses): """ Save a trigger plot. From aa5f687c48712b7098e9a1624dcbfeda2f61b430 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:32:21 +0200 Subject: [PATCH 106/180] Change viz.save_plot call loop for readability, also more ### comments --- phys2bids/phys2bids.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index e7a89d9b6..8a82cf4f2 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -249,9 +249,14 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} # save a figure for each run | give the right acquisition parameters for runs - for key, sequence, nb_trigger in zip(phys_in.keys(), tr, num_timepoints_expected): - viz.save_plot(phys_in[key], nb_trigger, sequence, - chtrig, outdir, f'{filename}-Run{key+1:02}', sub, ses) + ### If I understand, you're basically getting key, sequence, nb_trigger from each element in + ### phys_in.keys(), tr, num_timepoints_expected. + ### If it's not the case let me know, otherwise I'm sorry to remove the cool implementation, + ### but readability favours enumerate. + ### Also moving the run at the end of the filename (sub_ses_run) + for i, run in enumerate(phys_in.keys()): + viz.save_plot(phys_in[run], num_timepoints_expected[i], tr[i], + chtrig, outdir, filename, sub, ses, run) # Single run acquisition type, or : nothing to split workflow else: From baee891f3f7f8db24414384b3a229e54f3d88d77 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:33:15 +0200 Subject: [PATCH 107/180] More ### comments, add defaults and parameters. --- phys2bids/viz.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 114a74058..cb49e6a99 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -11,9 +11,10 @@ FIGSIZE = (18, 10) ### This definition needs a more intuitive name ('export_trigger_plot' for instance?) -### Since it depends on the one after, please put it after it. +### Since it depends on the one after, please put it second. It might be old school, but the rest of phys2bids is like that. ### Think also about reordering the inputs, either by importance or by order of use. -def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, sub, ses): +def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, + sub=None, ses=None, run=None, figsize=FIGSIZE, dpi=SET_DPI): """ Save a trigger plot. @@ -37,9 +38,17 @@ def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, su filename : str name of the input file given by user's entry sub: str or int - Name of subject. + Name of subject. Default is None ses: str or int or None - Name of session. + Name of session. Default is None + run: int or None + Run number. Default is None + figsize: tuple or list of floats + Size of the figure expressed as (size_x, size_y), + Default is {FIGSIZE} + dpi: int + Desired DPI of the figure, + Default is {SET_DPI} """ LGR.info('Plot trigger') plot_path = os.path.join(outdir, @@ -50,11 +59,13 @@ def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, su plot_path += f'_sub-{sub}' if ses: plot_path += f'_ses-{ses}' + if run: + plot_path += f'_run-{run:02d}' # adjust for multi run arguments, iterate through acquisition attributes plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], plot_path, tr, phys_in.thr, - num_timepoints_expected, filename) + num_timepoints_expected, filename, figsize, dpi) def plot_trigger(time, trigger, fileprefix, tr, thr, num_timepoints_expected, From 733098ff2ffa9436dc028f09f8857c224cc2df2b Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 4 Jun 2020 01:44:38 +0200 Subject: [PATCH 108/180] Deal with slices like [:i] and [i:], more ### comments --- phys2bids/physio_obj.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 5e8501ad4..f32c2798b 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -288,9 +288,12 @@ def __getitem__(self, idx): trigger_length = len(self.timeseries[self.trigger_idx]) # LGRinfo instead of print + ### I don't think this print is necessary HERE. + ### It would be more interesting to report the TIME the different runs start and end + ### That can be done in slice4phys.py in a LGR.info. Please remove this print. print(idx, trigger_length) + # If idx is an integer, return an "instantaneous slice" and initialise slice - # we have to deal with phys_in[idx:], cause idx is already slice(idx, None, None) if isinstance(idx, int): return_instant = True @@ -299,9 +302,16 @@ def __getitem__(self, idx): idx = trigger_length + idx idx = slice(idx, idx + 1) - # #### this statement cannot be interpreted if idx is None + + # If idx.start or stop are None, make them 0 or trigger length. + if not idx.start: + idx.start = 0 + if not idx.stop: + idx.stop = trigger_length + + # Check that the indexes are not out of bounds if idx.start >= trigger_length or idx.stop > trigger_length: - raise IndexError(f'slice ({idx.start}, {idx.stop}) is out of ' + raise IndexError(f'Slice ({idx.start}, {idx.stop}) is out of ' f'bounds for channel {self.trigger_idx} ' f'with size {trigger_length}') From dd7eaa53f7dfc734c7adf56c98239857d79dbcc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 09:52:47 -0400 Subject: [PATCH 109/180] applying changes from smoia comments --- phys2bids/phys2bids.py | 7 +-- phys2bids/viz.py | 113 ++++++++++++++++++++--------------------- 2 files changed, 57 insertions(+), 63 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index ab6196030..d25231ec8 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -246,14 +246,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # slice the recording based on user's entries # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY phys_in = slice4phys(phys_in, num_timepoints_expected, tr, thr) - # returns a dictionary in the form {run_idx: (startpoint, endpoint), run_idx:...} + # returns a dictionary in the form {run_idx: phys_in[startpoint, endpoint]} # save a figure for each run | give the right acquisition parameters for runs - ### If I understand, you're basically getting key, sequence, nb_trigger from each element in - ### phys_in.keys(), tr, num_timepoints_expected. - ### If it's not the case let me know, otherwise I'm sorry to remove the cool implementation, - ### but readability favours enumerate. - ### Also moving the run at the end of the filename (sub_ses_run) for i, run in enumerate(phys_in.keys()): viz.save_plot(phys_in[run], num_timepoints_expected[i], tr[i], chtrig, outdir, filename, sub, ses, run) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index cb49e6a99..0b892cd59 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -10,63 +10,6 @@ SET_DPI = 100 FIGSIZE = (18, 10) -### This definition needs a more intuitive name ('export_trigger_plot' for instance?) -### Since it depends on the one after, please put it second. It might be old school, but the rest of phys2bids is like that. -### Think also about reordering the inputs, either by importance or by order of use. -def save_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, - sub=None, ses=None, run=None, figsize=FIGSIZE, dpi=SET_DPI): - """ - Save a trigger plot. - - Used in main workflow (`phys2bids`), this function minimizes repetition in code for parallel - workflow (multi-run workflow and default workflow) and maintains readability of code - Parameters - --------- - phys_in : object - Object returned by BlueprintInput class - For multi-run acquisitions, phys_in is a slice of the whole object - num_timepoints_expected : list - a list of integers given by the user as `ntp` input - tr : list - a list of float given by the user as `tr` input - chtrig : int - trigger channel - integer representing the index of the trigger on phys_in.timeseries - outdir : str - directory to save output. - if ses and sub are specified, it can be understood as root directory of dataset - filename : str - name of the input file given by user's entry - sub: str or int - Name of subject. Default is None - ses: str or int or None - Name of session. Default is None - run: int or None - Run number. Default is None - figsize: tuple or list of floats - Size of the figure expressed as (size_x, size_y), - Default is {FIGSIZE} - dpi: int - Desired DPI of the figure, - Default is {SET_DPI} - """ - LGR.info('Plot trigger') - plot_path = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) - # Create trigger plot. If possible, to have multiple outputs in the same - # place, adds sub and ses label. - if sub: - plot_path += f'_sub-{sub}' - if ses: - plot_path += f'_ses-{ses}' - if run: - plot_path += f'_run-{run:02d}' - - # adjust for multi run arguments, iterate through acquisition attributes - plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], - plot_path, tr, phys_in.thr, - num_timepoints_expected, filename, figsize, dpi) - def plot_trigger(time, trigger, fileprefix, tr, thr, num_timepoints_expected, filename, figsize=FIGSIZE, dpi=SET_DPI): @@ -185,6 +128,62 @@ def ntr2time(x): plt.close() +def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, + sub=None, ses=None, run=None, figsize=FIGSIZE, dpi=SET_DPI): + """ + Save a trigger plot. + + Used in main workflow (`phys2bids`), this function minimizes repetition in code for parallel + workflow (multi-run workflow and default workflow) and maintains readability of code + Parameters + --------- + phys_in : object + Object returned by BlueprintInput class + For multi-run acquisitions, phys_in is a slice of the whole object + num_timepoints_expected : list + a list of integers given by the user as `ntp` input + tr : list + a list of float given by the user as `tr` input + chtrig : int + trigger channel + integer representing the index of the trigger on phys_in.timeseries + outdir : str + directory to save output. + if ses and sub are specified, it can be understood as root directory of dataset + filename : str + name of the input file given by user's entry + sub: str or int + Name of subject. Default is None + ses: str or int or None + Name of session. Default is None + run: int or None + Run number. Default is None + figsize: tuple or list of floats + Size of the figure expressed as (size_x, size_y), + Default is {FIGSIZE} + dpi: int + Desired DPI of the figure, + Default is {SET_DPI} + """ + LGR.info('Plot trigger') + plot_path = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + # Create trigger plot. If possible, to have multiple outputs in the same + # place, adds sub and ses label. + if sub: + plot_path += f'_sub-{sub}' + if ses: + plot_path += f'_ses-{ses}' + # add run to filename + if run: + filename += f'_run-{run:02d}' + + # adjust for multi run arguments, iterate through acquisition attributes + plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], + plot_path, tr, phys_in.thr, num_timepoints_expected, + filename, figsize, dpi) + + def plot_all(ch_name, timeseries, units, freq, infile, outfile='', dpi=SET_DPI, size=FIGSIZE): """ Plot all the channels for visualizations and saves them in outfile. From 83de39c38be1e6d1e0ab9e47d0a8ac5886b0db46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 10:51:30 -0400 Subject: [PATCH 110/180] adding statement to deal with single channel rec fo viz --- phys2bids/viz.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 0b892cd59..bdad7f3ae 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -221,8 +221,12 @@ def plot_all(ch_name, timeseries, units, freq, infile, outfile='', dpi=SET_DPI, https://phys2bids.readthedocs.io/en/latest/howto.html matplotlib.pyploy.figsize """ - ch_num = len(ch_name) # get number of channels: - fig, ax = plt.subplots(ch_num - 1, 1, figsize=size, sharex=True) + # if there's only one channel ch_num 0 will give TypeError for subplot + if len(ch_name) > 1: + ch_num = len(ch_name) - 1 # get number of channels + else: + ch_num = len(ch_name) + fig, ax = plt.subplots(ch_num, 1, figsize=size, sharex=True) time = timeseries[0] # assume time is first channel fig.suptitle(os.path.basename(infile)) for row, timeser in enumerate(timeseries[1:]): From 1770fc5adccc12651680169c9e2b0ae1dc06a48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 14:44:18 -0400 Subject: [PATCH 111/180] reverting some changes made for testing --- docs/howto.rst | 17 +++-------------- phys2bids/viz.py | 8 ++------ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/docs/howto.rst b/docs/howto.rst index cd777849c..a08ee7ccd 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -28,28 +28,17 @@ For the tutorial, we will assume the repository was downloaded in ``/home/arthur cd /home/arthurdent/git/ -What is in the tutorial files? +What is in the tutorial text file? ################################## -Text file ------------ -The file can be found in ``phys2bids/phys2bids/tests/data/tutorial_file.txt``. This file has header information (first 9 lines) which phys2bids will use to process this file, alongside information directly inputted by the user. Following this header information, the data in the file is stored in a column format. In this example, we have time (column 1), MRI volume trigger pulse (column 2), CO2 (column 3), O2 (column 4) and cardiac pulse (column 5) recordings. Each column was sampled at 1000Hz (Interval = 0.001 s). - -.. literalinclude:: ../phys2bids/tests/data/tutorial_file.txt - :linenos: - :lines: 1-15 - -Acknowledge file ------------------ -The file can be found under ``phys2bids/phys2bids/tests/data/tutorial_multirun.acq`` +The file can be found in ``phys2bids/phys2bids/tests/data/tutorial_file.txt``. This file has header information (first 9 lines) which phys2bids will use to process this file, alongside information directly inputted by the user. Following this header information, the data in the file is stored in a column format. In this example, we have time (column 1), MRI volume trigger pulse (column 2), CO2 (column 3), O2 (column 4) and cardiac pulse (column 5) recordings. Each column was sampled at 1000Hz (Interval = 0.001 s). .. literalinclude:: ../phys2bids/tests/data/tutorial_file.txt :linenos: :lines: 1-15 .. note:: - time is not a "real" channel recorded by LabChart or AcqKnowledge. For this reason, ``phys2bids`` treats it as a hidden channel, always in position 0. Channel 1 will be classed as the first channel recorded in either software. - + time is not a "real" channel recorded by LabChart or AcqKnowledge. For this reason, ``phys2bids`` treats it as a hidden channel, always in position 0. Channel 1 will be classed as the first channel recorded in either software. Using the -info option ###################### diff --git a/phys2bids/viz.py b/phys2bids/viz.py index bdad7f3ae..0b892cd59 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -221,12 +221,8 @@ def plot_all(ch_name, timeseries, units, freq, infile, outfile='', dpi=SET_DPI, https://phys2bids.readthedocs.io/en/latest/howto.html matplotlib.pyploy.figsize """ - # if there's only one channel ch_num 0 will give TypeError for subplot - if len(ch_name) > 1: - ch_num = len(ch_name) - 1 # get number of channels - else: - ch_num = len(ch_name) - fig, ax = plt.subplots(ch_num, 1, figsize=size, sharex=True) + ch_num = len(ch_name) # get number of channels: + fig, ax = plt.subplots(ch_num - 1, 1, figsize=size, sharex=True) time = timeseries[0] # assume time is first channel fig.suptitle(os.path.basename(infile)) for row, timeser in enumerate(timeseries[1:]): From 3e411d8cb9177582916ef0936558283fcd451725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 16:06:11 -0400 Subject: [PATCH 112/180] changing filename with run --- phys2bids/viz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 0b892cd59..4ec853e3f 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -176,7 +176,7 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi plot_path += f'_ses-{ses}' # add run to filename if run: - filename += f'_run-{run:02d}' + filename = f'{filename}_run-{run:02d}' # adjust for multi run arguments, iterate through acquisition attributes plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], From 96b80f04d490604031e3fa4fdc34b0b7eff965c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 16:11:39 -0400 Subject: [PATCH 113/180] reverting filename --- phys2bids/viz.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 4ec853e3f..9141066b6 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -174,9 +174,6 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi plot_path += f'_sub-{sub}' if ses: plot_path += f'_ses-{ses}' - # add run to filename - if run: - filename = f'{filename}_run-{run:02d}' # adjust for multi run arguments, iterate through acquisition attributes plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], From 6d9d8b15c3730c0a7440d79a044a7e91175da406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 16:24:41 -0400 Subject: [PATCH 114/180] reverting filename --- phys2bids/viz.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 9141066b6..4ec853e3f 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -174,6 +174,9 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi plot_path += f'_sub-{sub}' if ses: plot_path += f'_ses-{ses}' + # add run to filename + if run: + filename = f'{filename}_run-{run:02d}' # adjust for multi run arguments, iterate through acquisition attributes plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], From a897c156f2253aebc6411f91b2d770854f3d7199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 16:25:24 -0400 Subject: [PATCH 115/180] documentation --- docs/howto.rst | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/howto.rst b/docs/howto.rst index a08ee7ccd..1c27d2008 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -279,6 +279,58 @@ By looking at this figure, we can work out that we need a smaller threshold in o Tip: Time 0 is the time of first trigger ------------------------------------------------ +Splitting your input file into multiple run output files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If your file contains more than one (f)MRI acquisition (or runs), you can give multiple values to ``-ntp`` and ``tr`` arguments in order to get multiple ``.tsv.gz`` outputs. + +By specifying the number of timepoints in each acquisition, ``phys2bids`` will recursively cut the input file by detecting the first trigger of the entire session and the ones after the cutting point you specified. + +.. code-block:: shell + phys2bids -in two_scans_samefreq_all.txt -chtrig 2 -ntp 536 398 -tr 1.2 -thr 2 + +Now, instead of counting the trigger timepoints once, ``physbids`` will check the trigger channel recursively with all the values listed in ``-ntp``. The logger will inform you about the number of timepoints left at each iteration. + +.. code-block:: shell + INFO:phys2bids.physio_obj:Counting trigger points + INFO:phys2bids.physio_obj:The trigger is in channel 2 + INFO:phys2bids.physio_obj:The number of timepoints found with the manual threshold of 2.0000 is 934 + INFO:phys2bids.physio_obj:Checking number of timepoints + INFO:phys2bids.physio_obj:Found just the right amount of timepoints! + WARNING:phys2bids.slice4phys: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + phys2bids will split the input file according to the given -tr and -ntp arguments + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + INFO:phys2bids.physio_obj:Counting trigger points + INFO:phys2bids.physio_obj:The trigger is in channel 2 + INFO:phys2bids.physio_obj:The number of timepoints found with the manual threshold of 2.0000 is 934 + INFO:phys2bids.physio_obj:Checking number of timepoints + WARNING:phys2bids.physio_obj:Found 398 timepoints more than expected! + Assuming extra timepoints are at the end (try again with a more liberal thr) + INFO:phys2bids.slice4phys: + -------------------------------------------------------------- + Slicing between 0.0 seconds and 961.381 seconds + -------------------------------------------------------------- + INFO:phys2bids.physio_obj:Counting trigger points + INFO:phys2bids.physio_obj:The trigger is in channel 2 + INFO:phys2bids.physio_obj:The number of timepoints found with the manual threshold of 2.0000 is 400 + INFO:phys2bids.physio_obj:Checking number of timepoints + WARNING:phys2bids.physio_obj:Found 2 timepoints more than expected! + Assuming extra timepoints are at the end (try again with a more liberal thr) + INFO:phys2bids.slice4phys: + -------------------------------------------------------------- + Slicing between 952.381 seconds and 1817.96 seconds + -------------------------------------------------------------- + INFO:phys2bids.viz:Plot trigger + INFO:phys2bids.viz:Plot trigger + INFO:phys2bids.phys2bids:Preparing 2 output files. + INFO:phys2bids.phys2bids:Exporting files for run 1 freq 1000.0 + +The logger also notifies the user about the slicing points (the first always being at the beginning of session, or at 0 s). The user can also check the resulting slice by looking at the plot of the trigger channel for each run. + +What if I have multiple acquisition types ? +***************************************** +The user can also benefit from this utility when dealing with multiple ***acquisition types*** such as structural and functional (i.e. different TR). Like ``-ntp``, ``-tr`` takes multiple values. **Though, they have to be of the same length**. The idea is simple : if you only have one acquisition type, the one ``-tr`` input you gave will be broadcasted through all runs, but if there are different acquisition types, you have to list them all in order. + Generating outputs in BIDs format ################################# From 8953278d5a509fa00af7379de6151e799ec118a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 16:26:14 -0400 Subject: [PATCH 116/180] adding logger info and improve readability of run --- phys2bids/slice4phys.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index a88b32444..818208384 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -2,6 +2,9 @@ # -*- coding: utf-8 -*- from numpy import where +import logging + +LGR = logging.getLogger(__name__) ### If you mean this function *not* to be used outside of slice4phys, then you can prefix it with '_'. ### However, I don't see the reason why this shouldn't be used elsewhere if necessary! @@ -82,21 +85,20 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # Adjust timestamps with previous end_index # Except if it's the first run if run_idx > 0: - previous_end_index = run_timestamps[run_idx - 1][1] + previous_end_index = run_timestamps[run_idx][1] # adjust time_offset to keep original timing information - phys_in.time_offset = phys_in.time_offset + run_timestamps[run_idx - 1][2] - run_start = run_start + previous_end_index - run_end = run_end + previous_end_index + phys_in.time_offset = phys_in.time_offset + run_timestamps[run_idx][2] + run_start = int(run_start + previous_end_index) + run_end = int(run_end + previous_end_index) # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human # LGRinfo - ### Indeed, you need to import logging and add LGR = logging.getLogger(__name__) - ### at the beginning of the script, to be able to log with the rest of phys2bids, - ### then ALL THE PRINTS should either become LGR.info or LGR.warnings. - ### Also, please consider to make the run_idx human readable (start from 1), - ### Also, please make the message more informative. - print(run_idx + 1) + LGR.info("\n--------------------------------------------------------------\n" + f"Slicing between {(run_start/phys_in.freq[phys_in.trigger_idx])} seconds and " + f"{run_end/phys_in.freq[phys_in.trigger_idx]} seconds\n" + "--------------------------------------------------------------") + run_timestamps[run_idx + 1] = (run_start, run_end, phys_in.time_offset, phys_in.num_timepoints_found) @@ -134,7 +136,11 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): internal function (`slice4phys` takes the same arguments as `find_runs`) """ phys_in_slices = {} - + # inform the user + LGR.warning("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + "\nphys2bids will split the input file according to the given -tr and -ntp" + " arguments" + "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") # Find the timestamps run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding) From 12341c2ec85578ac7aade13e605542438ade75e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Fri, 5 Jun 2020 16:28:35 -0400 Subject: [PATCH 117/180] erasing print for tests, idx.start still not working --- phys2bids/phys2bids.py | 6 +++--- phys2bids/physio_obj.py | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index d25231ec8..4aefe7201 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -250,8 +250,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # save a figure for each run | give the right acquisition parameters for runs for i, run in enumerate(phys_in.keys()): - viz.save_plot(phys_in[run], num_timepoints_expected[i], tr[i], - chtrig, outdir, filename, sub, ses, run) + viz.export_trigger_plot(phys_in[run], num_timepoints_expected[i], tr[i], + chtrig, outdir, filename, sub, ses, run) # Single run acquisition type, or : nothing to split workflow else: @@ -260,7 +260,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected[0], tr[0]) # save a figure of the trigger - viz.save_plot(phys_in, num_timepoints_expected, outdir, filename, sub, ses) + viz.export_trigger_plot(phys_in, num_timepoints_expected, outdir, filename, sub, ses) # Reassign phys_in as dictionary # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 4dad53d3a..3e445b036 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -287,11 +287,6 @@ def __getitem__(self, idx): self.trigger_idx = 0 trigger_length = len(self.timeseries[self.trigger_idx]) - # LGRinfo instead of print - ### I don't think this print is necessary HERE. - ### It would be more interesting to report the TIME the different runs start and end - ### That can be done in slice4phys.py in a LGR.info. Please remove this print. - print(idx, trigger_length) # If idx is an integer, return an "instantaneous slice" and initialise slice if isinstance(idx, int): @@ -304,8 +299,8 @@ def __getitem__(self, idx): idx = slice(idx, idx + 1) # If idx.start or stop are None, make them 0 or trigger length. - if not idx.start: - idx.start = 0 + #if not idx.start: + # idx.start = 0 if not idx.stop: idx.stop = trigger_length From a1200e4e607bf2e43d9cc1d94d2ed6672c8bcbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 11:01:37 -0400 Subject: [PATCH 118/180] uncommenting section --- phys2bids/physio_obj.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 3e445b036..d06a882cc 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -299,8 +299,8 @@ def __getitem__(self, idx): idx = slice(idx, idx + 1) # If idx.start or stop are None, make them 0 or trigger length. - #if not idx.start: - # idx.start = 0 + if not idx.start: + idx.start = 0 if not idx.stop: idx.stop = trigger_length From 06af0a89dd3e08d9f17d4130c33b8d59ec0e0651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 11:06:48 -0400 Subject: [PATCH 119/180] linting --- phys2bids/slice4phys.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 818208384..4a3229ac4 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -6,9 +6,7 @@ LGR = logging.getLogger(__name__) -### If you mean this function *not* to be used outside of slice4phys, then you can prefix it with '_'. -### However, I don't see the reason why this shouldn't be used elsewhere if necessary! -### Think about removing the Notes if you agree. Also, if you don't need the see also, remove it please. + def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): """ Find runs slicing index. @@ -42,11 +40,6 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): session beggining, and nb of triggers in the form of run_timestamps{1:(start, end, time offset, nb of triggers), 2:(...), ... } - Notes - ----- - find_runs is an internal function to slice4phys - it feeds it dictionary in order to slice BlueprintInput - See also: """ # Initialize dictionaries to save run timestamps and phys_in attributes run_timestamps = {} @@ -132,15 +125,15 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): -------- phys_in_slices: dict keys start by `run 1` until last (`run n`). - items are slices of BlueprintInput objects based on timestamps returned by + items are slices of BlueprintInput objects based on run attributes returned by internal function (`slice4phys` takes the same arguments as `find_runs`) """ phys_in_slices = {} # inform the user - LGR.warning("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + LGR.warning("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" "\nphys2bids will split the input file according to the given -tr and -ntp" " arguments" - "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") # Find the timestamps run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding) From 09ed5d90f29c4f448fb77213579fc13ba01eec4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 11:57:25 -0400 Subject: [PATCH 120/180] adding last element to docs --- docs/howto.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/howto.rst b/docs/howto.rst index 1c27d2008..59ed7286c 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -283,7 +283,7 @@ Splitting your input file into multiple run output files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If your file contains more than one (f)MRI acquisition (or runs), you can give multiple values to ``-ntp`` and ``tr`` arguments in order to get multiple ``.tsv.gz`` outputs. -By specifying the number of timepoints in each acquisition, ``phys2bids`` will recursively cut the input file by detecting the first trigger of the entire session and the ones after the cutting point you specified. +By specifying the number of timepoints in each acquisition, ``phys2bids`` will recursively cut the input file by detecting the first trigger of the entire session and the ones after the number of timepoints you specified. .. code-block:: shell phys2bids -in two_scans_samefreq_all.txt -chtrig 2 -ntp 536 398 -tr 1.2 -thr 2 @@ -325,12 +325,16 @@ Now, instead of counting the trigger timepoints once, ``physbids`` will check th INFO:phys2bids.phys2bids:Preparing 2 output files. INFO:phys2bids.phys2bids:Exporting files for run 1 freq 1000.0 -The logger also notifies the user about the slicing points (the first always being at the beginning of session, or at 0 s). The user can also check the resulting slice by looking at the plot of the trigger channel for each run. +The logger also notifies the user about the slicing points used (the first always being from the beginning of session, until the specified number of timepoints after the first trigger). The user can also check the resulting slice by looking at the plot of the trigger channel for each run. Each slice is adjusted with a padding of 9 seconds after the last trigger. This padding is also applied at the beginning (-9s before first trigger of run) of the 2nd to last run. What if I have multiple acquisition types ? ***************************************** -The user can also benefit from this utility when dealing with multiple ***acquisition types*** such as structural and functional (i.e. different TR). Like ``-ntp``, ``-tr`` takes multiple values. **Though, they have to be of the same length**. The idea is simple : if you only have one acquisition type, the one ``-tr`` input you gave will be broadcasted through all runs, but if there are different acquisition types, you have to list them all in order. +The user can also benefit from this utility when dealing with multiple ***acquisition types*** such as structural and functional (i.e. different TR). Like ``-ntp``, ``-tr`` takes multiple values. **Though, they have to be the same length**. The idea is simple : if you only have one acquisition type, the one ``-tr`` input you gave will be broadcasted through all runs, but if there are different acquisition types, you have to list them all in order. +.. warning:: + There are currently no ``multi-run tutorial files`` available along with the package (under ``phys2bids/tests/data``). Although, you can visit `phys2bids OSF `_ storage to access a LabChart physiological recording with multiple fMRI acquisitions. Find it under ``labchart/chicago``. +.. note:: + **Why would I have more than one fMRI acquisition in the physiological recording?** The idea is to reduce human error. Synchronization between start of both fMRI and physiological acquisitions can be difficult, so it is safer to have a only one physiological recording with multiple imaging sequences. The next step will be to reduce user's inputs in ``phys2bids`` by detecting the number of triggers in each run automatically. Either by looking in the DICOM registry or directly computing the time difference between subsequent triggers. That way, an individual file will require less attention to pass through phys2bids workflow, and the user will be able to process batches of files. Generating outputs in BIDs format ################################# From e4d72b6f8abbaa295ba33e9b651895241d1bdaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 12:03:42 -0400 Subject: [PATCH 121/180] linting on phys2bids --- phys2bids/phys2bids.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 4aefe7201..fa571306b 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -115,7 +115,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, sub=None, ses=None, chtrig=0, chsel=None, num_timepoints_expected=0, tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): """ - Main workflow of phys2bids. + Run main workflow of phys2bids. Runs the parser, does some checks on input, then imports the right interface file to read the input. If only info is required, @@ -348,7 +348,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, else: phys_out[key].filename = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) + os.path.splitext(os.path.basename(filename) + )[0]) # Append "run" to filename if more than one run if run_amount > 1: phys_out[key].filename = f'{phys_out[key].filename}_{run:02d}' From 6d73743e38c0bb2eb2b5c0eb2ca6e1f6bceaee6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 12:14:34 -0400 Subject: [PATCH 122/180] changing -ntp and -tr types for integration --- phys2bids/tests/test_integration.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index 1ce893b0a..fbf037dba 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -61,8 +61,8 @@ def test_integration_tutorial(): test_full_path = os.path.join(test_path, test_filename) test_chtrig = 1 test_outdir = test_path - test_ntp = 158 - test_tr = 1.2 + test_ntp = [158, ] + test_tr = [1.2, ] test_thr = 0.735 phys2bids(filename=test_full_path, chtrig=test_chtrig, outdir=test_outdir, num_timepoints_expected=test_ntp, tr=test_tr, thr=test_thr) @@ -109,7 +109,7 @@ def test_integration_acq(samefreq_full_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=1) + chtrig=test_chtrig, num_timepoints_expected=[1, ]) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz', '_trigger_time.png']: @@ -238,8 +238,8 @@ def test_integration_heuristic(): test_full_path = os.path.join(test_path, test_filename) test_chtrig = 1 test_outdir = test_path - test_ntp = 158 - test_tr = 1.2 + test_ntp = [158, ] + test_tr = [1.2, ] test_thr = 0.735 heur_path = resource_filename('phys2bids', 'heuristics') test_heur = os.path.join(heur_path, 'heur_tutorial.py') @@ -297,8 +297,8 @@ def test_integration_info(): test_filename = 'tutorial_file.txt' test_chtrig = 1 test_outdir = test_path - test_ntp = 158 - test_tr = 1.2 + test_ntp = [158, ] + test_tr = [1.2, ] test_thr = 0.735 # Move into folder From 5916cdd554baf7143539b82e7c750793a9ac7e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 12:19:09 -0400 Subject: [PATCH 123/180] fixing types and error for CI --- phys2bids/tests/test_phys2bids.py | 2 +- phys2bids/viz.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/tests/test_phys2bids.py b/phys2bids/tests/test_phys2bids.py index aea1f59c1..73dd56bc9 100644 --- a/phys2bids/tests/test_phys2bids.py +++ b/phys2bids/tests/test_phys2bids.py @@ -10,7 +10,7 @@ def test_print_summary(tmpdir): test_filename = 'input.txt' - test_ntp_expected = 10 + test_ntp_expected = [10, ] test_ntp_found = 5 test_samp_freq = 0.2 test_time_offset = 0.8 diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 4ec853e3f..1df92daad 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -124,7 +124,7 @@ def ntr2time(x): ax2.set_title('Ending triggers for selected threshold') subplot.plot(time, trigger, '-', time, block, '-') subplot.fill_between(time, block, where=block >= d, interpolate=True, color='#ffbb6e') - plt.savefig(fileprefix + filename + '_trigger_time.png', dpi=dpi) + plt.savefig(fileprefix + '_trigger_time.png', dpi=dpi) plt.close() From 6183d195d2b0478e0ce631ffe922940acced9442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 12:32:40 -0400 Subject: [PATCH 124/180] changing types for integration --- phys2bids/phys2bids.py | 3 +-- phys2bids/tests/test_integration.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index fa571306b..3337838f5 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -257,8 +257,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, else: # Run analysis on trigger channel to get first timepoint # and the time offset. - phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected[0], - tr[0]) + phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected[0], tr[0]) # save a figure of the trigger viz.export_trigger_plot(phys_in, num_timepoints_expected, outdir, filename, sub, ses) diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index fbf037dba..637bfdaff 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -156,7 +156,7 @@ def test_integration_multifreq(multifreq_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=1) + chtrig=test_chtrig, num_timepoints_expected=[1, ]) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz']: From 0a700d0e58c3275f85038c2446b581374fd1c67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 12:42:36 -0400 Subject: [PATCH 125/180] fixing integration errors --- phys2bids/tests/test_integration.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index 637bfdaff..ab9c6b34b 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -65,7 +65,7 @@ def test_integration_tutorial(): test_tr = [1.2, ] test_thr = 0.735 phys2bids(filename=test_full_path, chtrig=test_chtrig, outdir=test_outdir, - num_timepoints_expected=test_ntp, tr=test_tr, thr=test_thr) + num_timepoints_expected=test_ntp[0], tr=test_tr[0], thr=test_thr) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz', '_trigger_time.png']: @@ -109,7 +109,7 @@ def test_integration_acq(samefreq_full_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=[1, ]) + chtrig=test_chtrig, num_timepoints_expected=1) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz', '_trigger_time.png']: @@ -156,7 +156,7 @@ def test_integration_multifreq(multifreq_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=[1, ]) + chtrig=test_chtrig, num_timepoints_expected=1) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz']: @@ -244,7 +244,7 @@ def test_integration_heuristic(): heur_path = resource_filename('phys2bids', 'heuristics') test_heur = os.path.join(heur_path, 'heur_tutorial.py') phys2bids(filename=test_full_path, chtrig=test_chtrig, outdir=test_outdir, - num_timepoints_expected=test_ntp, tr=test_tr, thr=test_thr, sub='006', + num_timepoints_expected=test_ntp[0], tr=test_tr[0], thr=test_thr, sub='006', ses='01', heur_file=test_heur) test_path_output = os.path.join(test_path, 'sub-006/ses-01/func') From 9fa664c9d901186a97818d38e892de28cb52f4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 12:57:14 -0400 Subject: [PATCH 126/180] reverting phys2bids test arguments --- phys2bids/tests/test_integration.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index ab9c6b34b..637bfdaff 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -65,7 +65,7 @@ def test_integration_tutorial(): test_tr = [1.2, ] test_thr = 0.735 phys2bids(filename=test_full_path, chtrig=test_chtrig, outdir=test_outdir, - num_timepoints_expected=test_ntp[0], tr=test_tr[0], thr=test_thr) + num_timepoints_expected=test_ntp, tr=test_tr, thr=test_thr) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz', '_trigger_time.png']: @@ -109,7 +109,7 @@ def test_integration_acq(samefreq_full_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=1) + chtrig=test_chtrig, num_timepoints_expected=[1, ]) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz', '_trigger_time.png']: @@ -156,7 +156,7 @@ def test_integration_multifreq(multifreq_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=1) + chtrig=test_chtrig, num_timepoints_expected=[1, ]) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz']: @@ -244,7 +244,7 @@ def test_integration_heuristic(): heur_path = resource_filename('phys2bids', 'heuristics') test_heur = os.path.join(heur_path, 'heur_tutorial.py') phys2bids(filename=test_full_path, chtrig=test_chtrig, outdir=test_outdir, - num_timepoints_expected=test_ntp[0], tr=test_tr[0], thr=test_thr, sub='006', + num_timepoints_expected=test_ntp, tr=test_tr, thr=test_thr, sub='006', ses='01', heur_file=test_heur) test_path_output = os.path.join(test_path, 'sub-006/ses-01/func') From 6da16dfaf8af25145be05b77719599175f5d0ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Sun, 7 Jun 2020 13:08:29 -0400 Subject: [PATCH 127/180] changing run_amount definition --- phys2bids/phys2bids.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 3337838f5..2fa361240 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -252,6 +252,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, for i, run in enumerate(phys_in.keys()): viz.export_trigger_plot(phys_in[run], num_timepoints_expected[i], tr[i], chtrig, outdir, filename, sub, ses, run) + # define run amount + run_amount = len(phys_in) # Single run acquisition type, or : nothing to split workflow else: @@ -264,6 +266,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Reassign phys_in as dictionary # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY phys_in = {1: phys_in} + # define run amount + run_amount = 1 + else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using both "-ntp" and "-tr" arguments') @@ -273,7 +278,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # they also save the amount of runs and unique frequencies uniq_freq_list = set(phys_in[1].freq) freq_amount = len(uniq_freq_list) - run_amount = len(phys_in) if freq_amount > 1: LGR.warning(f'Found {freq_amount} different frequencies in input!') From e146162dfba472fcb9e2de7acbed03050e6c5215 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:07:58 +0200 Subject: [PATCH 128/180] Update phys2bids/bids.py Co-authored-by: Ross Markello --- phys2bids/bids.py | 1 - 1 file changed, 1 deletion(-) diff --git a/phys2bids/bids.py b/phys2bids/bids.py index ed2ab449f..79cc0abc4 100644 --- a/phys2bids/bids.py +++ b/phys2bids/bids.py @@ -22,7 +22,6 @@ # ampere: electric current 'ampere': 'A', 'amp': 'A', 'amps': 'A', # second: time and hertzs: frequency - 'a': 'A', 'ampere': 'A', 'amp': 'A', 'amps': 'A', # siemens: electric conductance (e.g. EDA) 'siemens': 'S', # second: time and hertzs From 2a29ac60dc5a51109db3785486ae3e022b69be27 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:09:52 +0200 Subject: [PATCH 129/180] Update phys2bids/phys2bids.py Co-authored-by: Ross Markello --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 2fa361240..20ffc87c5 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -259,7 +259,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, else: # Run analysis on trigger channel to get first timepoint # and the time offset. - phys_in.check_trigger_amount(chtrig, thr, num_timepoints_expected[0], tr[0]) + phys_in.check_trigger_amount(thr, num_timepoints_expected[0], tr[0]) # save a figure of the trigger viz.export_trigger_plot(phys_in, num_timepoints_expected, outdir, filename, sub, ses) From 0b98410ce3ffac40d3286e2df47e2286ac54afb7 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:11:11 +0200 Subject: [PATCH 130/180] Update phys2bids/phys2bids.py Co-authored-by: Ross Markello --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 20ffc87c5..807c0fbac 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -282,7 +282,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.warning(f'Found {freq_amount} different frequencies in input!') # If heuristics are used, init a dict of arguments to pass to use_heuristic - if heur_file and sub: + if heur_file is not None and sub is not None: heur_args = {'heur_file': heur_file, 'sub': sub, 'ses': ses, 'filename': filename, 'outdir': outdir, 'run': '', 'record_label': ''} From 9c01088f5f320f2a14116e566d7e74b3aa56aa12 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:12:02 +0200 Subject: [PATCH 131/180] Update phys2bids/phys2bids.py Co-authored-by: Ross Markello --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 807c0fbac..f578468ca 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -317,7 +317,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # in the dictionary. phys_out[key] = BlueprintOutput.init_from_blueprint(phys_out[key]) - if heur_file and sub: + if heur_file is not None and sub is not None: LGR.info(f'Preparing BIDS output using {heur_file}') elif heur_file and not sub: LGR.warning('While "-heur" was specified, option "-sub" was not.\n' From 8ad2ba934717e73d1ca047a7e09103a632b3e4dc Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:12:35 +0200 Subject: [PATCH 132/180] Update phys2bids/phys2bids.py Co-authored-by: Ross Markello --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index f578468ca..6bc05440d 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -319,7 +319,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if heur_file is not None and sub is not None: LGR.info(f'Preparing BIDS output using {heur_file}') - elif heur_file and not sub: + elif heur_file is not None and sub is None: LGR.warning('While "-heur" was specified, option "-sub" was not.\n' 'Skipping BIDS formatting.') From 52964d46c6b7ed31225d73dce055f83ca81ef496 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:13:04 +0200 Subject: [PATCH 133/180] Update phys2bids/phys2bids.py Co-authored-by: Ross Markello --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 6bc05440d..a17a9b2eb 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -326,7 +326,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Preparing output parameters: name and folder. for uniq_freq in uniq_freq_list: # If possible, prepare bids renaming. - if heur_file and sub: + if heur_file is not None and sub is not None: # Add run info to heur_args if more than one run is present if run_amount > 1: heur_args['run'] = f'{run:02d}' From 7901296cd885a56f374bdde87dec485e22197a45 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:14:27 +0200 Subject: [PATCH 134/180] Update phys2bids/viz.py Co-authored-by: Ross Markello --- phys2bids/viz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 1df92daad..770bd03d8 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -170,7 +170,7 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi os.path.splitext(os.path.basename(filename))[0]) # Create trigger plot. If possible, to have multiple outputs in the same # place, adds sub and ses label. - if sub: + if sub is not None: plot_path += f'_sub-{sub}' if ses: plot_path += f'_ses-{ses}' From 75b01db5f6c4b17ba4d993a094c9a14bcd679689 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:15:00 +0200 Subject: [PATCH 135/180] Update phys2bids/viz.py Co-authored-by: Ross Markello --- phys2bids/viz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 770bd03d8..987d2bcd1 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -172,7 +172,7 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi # place, adds sub and ses label. if sub is not None: plot_path += f'_sub-{sub}' - if ses: + if ses is not None: plot_path += f'_ses-{ses}' # add run to filename if run: From 054286384e0aeebbdbcf4ee9ddfc36bd274c7ca0 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 9 Jun 2020 20:15:28 +0200 Subject: [PATCH 136/180] Update phys2bids/viz.py Co-authored-by: Ross Markello --- phys2bids/viz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 987d2bcd1..3ff5a4a2b 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -175,7 +175,7 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi if ses is not None: plot_path += f'_ses-{ses}' # add run to filename - if run: + if run is not None: filename = f'{filename}_run-{run:02d}' # adjust for multi run arguments, iterate through acquisition attributes From 2c9a9f897fd720ccde676d318a39ba1bc105a931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 9 Jun 2020 16:39:06 -0400 Subject: [PATCH 137/180] Update phys2bids/phys2bids.py Co-authored-by: Ross Markello --- phys2bids/phys2bids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index a17a9b2eb..d51fbd08c 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -112,8 +112,8 @@ def print_json(outfile, samp_freq, time_offset, ch_name): def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, - sub=None, ses=None, chtrig=0, chsel=None, num_timepoints_expected=0, - tr=1, thr=None, ch_name=[], chplot='', debug=False, quiet=False): + sub=None, ses=None, chtrig=0, chsel=None, num_timepoints_expected=None, + tr=None, thr=None, ch_name=[], chplot='', debug=False, quiet=False): """ Run main workflow of phys2bids. From bd14f6c9e23d4418421a1ca71feef58b282a7706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 9 Jun 2020 16:41:04 -0400 Subject: [PATCH 138/180] Update phys2bids/phys2bids.py Co-authored-by: Ross Markello --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index d51fbd08c..b61ed7061 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -216,7 +216,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in.rename_channels(ch_name) # Checking acquisition type via user's input - if tr != [0, ] and num_timepoints_expected != [0, ]: + if tr is not None and num_timepoints_expected is not None: # Multi-run acquisition type section # Check list length, more than 1 means multi-run From 13df1c5be60a9ec1d69f98a693b73bd2b235f0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 9 Jun 2020 16:41:34 -0400 Subject: [PATCH 139/180] Update phys2bids/tests/test_integration.py Co-authored-by: Ross Markello --- phys2bids/tests/test_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index 637bfdaff..9cead7673 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -61,7 +61,7 @@ def test_integration_tutorial(): test_full_path = os.path.join(test_path, test_filename) test_chtrig = 1 test_outdir = test_path - test_ntp = [158, ] + test_ntp = [158] test_tr = [1.2, ] test_thr = 0.735 phys2bids(filename=test_full_path, chtrig=test_chtrig, outdir=test_outdir, From f0917a1b7f49de1635e66a0348137f9be8a12391 Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Tue, 9 Jun 2020 18:10:29 -0400 Subject: [PATCH 140/180] Fix multirun errors --- .gitignore | 1 + phys2bids/cli/run.py | 14 ++------------ phys2bids/phys2bids.py | 16 +++++++++++----- phys2bids/physio_obj.py | 8 ++++---- phys2bids/tests/test_integration.py | 18 +++++++++--------- phys2bids/viz.py | 1 + 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 7c01937d9..e49c5499e 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,4 @@ dmypy.json .pyre/ .vscode/ +phys2bids/tests/data/* diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index 9b94d1cb0..f94f3d82f 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -60,11 +60,6 @@ def _get_parser(): 'in the current folder. Edit the heur_ex.py file in ' 'heuristics folder.', default=None) - # optional.add_argument('-hdir', '--heur-dir', - # dest='heurdir', - # type=str, - # help='Folder containing heuristic file.', - # default='.') optional.add_argument('-sub', '--subject', dest='sub', type=str, @@ -77,11 +72,6 @@ def _get_parser(): help='Specify alongside \"-heur\". Code of ' 'session to process.', default=None) - # optional.add_argument('-run', '--multi-run', - # dest= run, - # help='' - # - # optional.add_argument('-chtrig', '--channel-trigger', dest='chtrig', type=int, @@ -104,14 +94,14 @@ def _get_parser(): 'Default is 0. Note: the estimation of beggining of ' 'neuroimaging acquisition cannot take place with this default.' 'Give a list of each expected ntp for multi-run recordings.', - default=[0, ]) + default=None) optional.add_argument('-tr', '--tr', dest='tr', nargs='*', type=float, help='TR of sequence in seconds. ' 'You can list each TR used throughout the session', - default=[0, ]) + default=None) optional.add_argument('-thr', '--threshold', dest='thr', type=float, diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index b61ed7061..12e8a5dc4 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -127,8 +127,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, ------ NotImplementedError If the file extension is not supported yet. - """ + # Check options to make them internally coherent pt. I # #!# This can probably be done while parsing? outdir = utils.check_input_dir(outdir) @@ -177,6 +177,11 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, infile = os.path.join(indir, filename) utils.check_file_exists(infile) + if isinstance(num_timepoints_expected, int): + num_timepoints_expected = [num_timepoints_expected] + if isinstance(tr, (int, float)): + tr = [tr] + # Read file! if ftype == 'acq': from phys2bids.interfaces.acq import populate_phys_input @@ -216,6 +221,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in.rename_channels(ch_name) # Checking acquisition type via user's input + run_amount = 1 # default number of runs, unless otherwise determined if tr is not None and num_timepoints_expected is not None: # Multi-run acquisition type section @@ -261,17 +267,17 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # and the time offset. phys_in.check_trigger_amount(thr, num_timepoints_expected[0], tr[0]) # save a figure of the trigger - viz.export_trigger_plot(phys_in, num_timepoints_expected, outdir, filename, sub, ses) + viz.export_trigger_plot(phys_in, num_timepoints_expected[0], tr[0], chtrig, + outdir, filename, sub, ses) # Reassign phys_in as dictionary # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY phys_in = {1: phys_in} - # define run amount - run_amount = 1 else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using both "-ntp" and "-tr" arguments') + phys_in = {1: phys_in} # The next few lines create a dictionary of different BlueprintInput # objects, one for each unique frequency for each run in phys_in @@ -279,7 +285,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, uniq_freq_list = set(phys_in[1].freq) freq_amount = len(uniq_freq_list) if freq_amount > 1: - LGR.warning(f'Found {freq_amount} different frequencies in input!') + LGR.info(f'Found {freq_amount} different frequencies in input!') # If heuristics are used, init a dict of arguments to pass to use_heuristic if heur_file is not None and sub is not None: diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index d06a882cc..d4176c49f 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -299,10 +299,10 @@ def __getitem__(self, idx): idx = slice(idx, idx + 1) # If idx.start or stop are None, make them 0 or trigger length. - if not idx.start: - idx.start = 0 - if not idx.stop: - idx.stop = trigger_length + if idx.start is None: + idx = slice(0, idx.stop) + if idx.stop is None: + idx = slice(idx.start, trigger_length) # Check that the indexes are not out of bounds if idx.start >= trigger_length or idx.stop > trigger_length: diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index 9cead7673..42c167f78 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -40,7 +40,7 @@ def test_logger(): f'-chtrig {test_chtrig} -outdir {test_outdir}', shell=True, check=True) # Read logger file - logger_file = glob.glob(os.path.join(test_path, '*phys2bids*'))[0] + logger_file = sorted(glob.glob(os.path.join(test_path, '*phys2bids*')))[-1] with open(logger_file) as logger_info: logger_info = logger_info.readlines() @@ -61,8 +61,8 @@ def test_integration_tutorial(): test_full_path = os.path.join(test_path, test_filename) test_chtrig = 1 test_outdir = test_path - test_ntp = [158] - test_tr = [1.2, ] + test_ntp = 158 + test_tr = 1.2 test_thr = 0.735 phys2bids(filename=test_full_path, chtrig=test_chtrig, outdir=test_outdir, num_timepoints_expected=test_ntp, tr=test_tr, thr=test_thr) @@ -109,7 +109,7 @@ def test_integration_acq(samefreq_full_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=[1, ]) + chtrig=test_chtrig, num_timepoints_expected=1, tr=1) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz', '_trigger_time.png']: @@ -156,7 +156,7 @@ def test_integration_multifreq(multifreq_acq_file): test_chtrig = 3 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=[1, ]) + chtrig=test_chtrig, num_timepoints_expected=1, tr=1) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz']: @@ -238,8 +238,8 @@ def test_integration_heuristic(): test_full_path = os.path.join(test_path, test_filename) test_chtrig = 1 test_outdir = test_path - test_ntp = [158, ] - test_tr = [1.2, ] + test_ntp = 158 + test_tr = 1.2 test_thr = 0.735 heur_path = resource_filename('phys2bids', 'heuristics') test_heur = os.path.join(heur_path, 'heur_tutorial.py') @@ -297,8 +297,8 @@ def test_integration_info(): test_filename = 'tutorial_file.txt' test_chtrig = 1 test_outdir = test_path - test_ntp = [158, ] - test_tr = [1.2, ] + test_ntp = 158 + test_tr = 1.2 test_thr = 0.735 # Move into folder diff --git a/phys2bids/viz.py b/phys2bids/viz.py index 3ff5a4a2b..cfab47340 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -135,6 +135,7 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi Used in main workflow (`phys2bids`), this function minimizes repetition in code for parallel workflow (multi-run workflow and default workflow) and maintains readability of code + Parameters --------- phys_in : object From 2e65f28fe4c6144730d9329c965166f99187b086 Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Wed, 10 Jun 2020 10:20:48 -0400 Subject: [PATCH 141/180] Fixes final multifreq error --- phys2bids/phys2bids.py | 1 + phys2bids/tests/test_integration.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 12e8a5dc4..bf8f7df96 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -331,6 +331,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Preparing output parameters: name and folder. for uniq_freq in uniq_freq_list: + key = f'{run}_{uniq_freq}' # If possible, prepare bids renaming. if heur_file is not None and sub is not None: # Add run info to heur_args if more than one run is present diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index 42c167f78..27919f587 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -307,7 +307,7 @@ def test_integration_info(): command_str = (f'phys2bids -in {test_filename} -indir {test_path} ', f'-chtrig {test_chtrig} -outdir {test_outdir} ', f'-tr {test_tr} -ntp {test_ntp} -thr {test_thr} ', - f'-info') + '-info') command_str = ''.join(command_str) subprocess.run(command_str, shell=True, check=True) From 4a14d931e0f8f88b9a567f3465001f2b5aa85d92 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 11 Jun 2020 15:41:20 +0200 Subject: [PATCH 142/180] Add padding as optional argument, correct split4phys call --- phys2bids/cli/run.py | 30 +++++++++++++++++++++--------- phys2bids/phys2bids.py | 5 +++-- phys2bids/slice4phys.py | 20 ++++++++++---------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index f94f3d82f..08fd58ede 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -26,8 +26,8 @@ def _get_parser(): required.add_argument('-in', '--input-file', dest='filename', type=str, - help='The name of the file containing physiological data, with or ' - 'without extension.', + help='The name of the file containing physiological ' + 'data, with or without extension.', required=True) optional.add_argument('-info', '--info', dest='info', @@ -91,25 +91,37 @@ def _get_parser(): nargs='*', type=int, help='Number of expected trigger timepoints (TRs). ' - 'Default is 0. Note: the estimation of beggining of ' - 'neuroimaging acquisition cannot take place with this default.' - 'Give a list of each expected ntp for multi-run recordings.', + 'Default is None. Note: the estimation of beggining of ' + 'neuroimaging acquisition cannot take place with this default. ' + 'If you\'re running phys2bids on a multi-run recording, ' + 'give a list of each expected ntp for each run.', default=None) optional.add_argument('-tr', '--tr', dest='tr', nargs='*', type=float, help='TR of sequence in seconds. ' - 'You can list each TR used throughout the session', + 'If you\'re running phys2bids on a multi-run recording, ' + 'you can give a list of each expected ntp for each run, ' + 'or just one TR if it is consistent throughout the session.', default=None) optional.add_argument('-thr', '--threshold', dest='thr', type=float, help='Threshold to use for trigger detection. ' - 'If "ntp" and "TR" are specified, phys2bids automatically computes ' - 'a threshold to detect the triggers. Use this parameter to set it ' - 'manually', + 'If "ntp" and "TR" are specified, phys2bids ' + 'automatically computes a threshold to detect ' + 'the triggers. Use this parameter to set it manually. ' + 'If you\'re running phys2bids on a multi-run recording, ' + 'you NEED to set this.', default=None) + optional.add_argument('-pad', '--padding', + dest='pad', + type=float, + help='Padding in seconds used around a single run ' + 'when separating multi-run session files. ' + 'Default is 9 seconds.', + default=9) optional.add_argument('-chnames', '--channel-names', dest='ch_name', nargs='*', diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index bf8f7df96..25776a248 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -113,7 +113,7 @@ def print_json(outfile, samp_freq, time_offset, ch_name): def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, sub=None, ses=None, chtrig=0, chsel=None, num_timepoints_expected=None, - tr=None, thr=None, ch_name=[], chplot='', debug=False, quiet=False): + tr=None, thr=None, pad=9, ch_name=[], chplot='', debug=False, quiet=False): """ Run main workflow of phys2bids. @@ -251,7 +251,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # slice the recording based on user's entries # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY - phys_in = slice4phys(phys_in, num_timepoints_expected, tr, thr) + phys_in = slice4phys(phys_in, num_timepoints_expected, tr, + phys_in.thr, pad) # returns a dictionary in the form {run_idx: phys_in[startpoint, endpoint]} # save a figure for each run | give the right acquisition parameters for runs diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 4a3229ac4..490ef6428 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -87,10 +87,10 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human # LGRinfo - LGR.info("\n--------------------------------------------------------------\n" - f"Slicing between {(run_start/phys_in.freq[phys_in.trigger_idx])} seconds and " - f"{run_end/phys_in.freq[phys_in.trigger_idx]} seconds\n" - "--------------------------------------------------------------") + LGR.info('\n--------------------------------------------------------------\n' + f'Slicing between {(run_start/phys_in.freq[phys_in.trigger_idx])} seconds and ' + f'{run_end/phys_in.freq[phys_in.trigger_idx]} seconds\n' + '--------------------------------------------------------------') run_timestamps[run_idx + 1] = (run_start, run_end, phys_in.time_offset, @@ -103,7 +103,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): return run_timestamps -def slice4phys(phys_in, ntp_list, tr_list, padding=9): +def slice4phys(phys_in, ntp_list, tr_list, thr, padding=9): """ Slice runs for phys2bids. @@ -130,12 +130,12 @@ def slice4phys(phys_in, ntp_list, tr_list, padding=9): """ phys_in_slices = {} # inform the user - LGR.warning("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - "\nphys2bids will split the input file according to the given -tr and -ntp" - " arguments" - "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + LGR.warning('\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' + '\nphys2bids will split the input file according to the given -tr and -ntp' + ' arguments' + '\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') # Find the timestamps - run_timestamps = find_runs(phys_in, ntp_list, tr_list, padding) + run_timestamps = find_runs(phys_in, ntp_list, tr_list, thr, padding) for run in run_timestamps.keys(): From 1f178bd8016feb9922e97ded829c650e625c462e Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 11 Jun 2020 15:48:20 +0200 Subject: [PATCH 143/180] Revert "Add filename property to BlueprintOutput class" This reverts commit 11cbfd7a0ec3546a8543b2f6057a4f9ec215e502. --- phys2bids/physio_obj.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index d4176c49f..91847a7db 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -163,21 +163,21 @@ class BlueprintInput(): Attributes ---------- - timeseries: (ch, [tps]) list + timeseries : (ch, [tps]) list List of numpy 1d arrays - one for channel, plus one for time. Time channel has to be the first. Contains all the timeseries recorded. Supports different frequencies! - freq: (ch) list of floats + freq : (ch) list of floats List of floats - one per channel. Contains all the frequencies of the recorded channel. Support different frequencies! - ch_name: (ch) list of strings + ch_name : (ch) list of strings List of names of the channels - can be the header of the columns in the output files. - units: (ch) list of strings + units : (ch) list of strings List of the units of the channels. - trigger_idx: int + trigger_idx : int The trigger index. Optional. Default is 0. num_timepoints_found: int or None Amount of timepoints found in the automatic count. @@ -541,23 +541,21 @@ class BlueprintOutput(): Attributes ---------- - timeseries: (ch x tps) :obj:`numpy.ndarray` + timeseries : (ch x tps) :obj:`numpy.ndarray` Numpy 2d array of timeseries Contains all the timeseries recorded. Impose same frequency! - freq: float + freq : float Shared frequency of the object. Properties - ch_name: (ch) list of strings + ch_name : (ch) list of strings List of names of the channels - can be the header of the columns in the output files. - units: (ch) list of strings + units : (ch) list of strings List of the units of the channels. - start_time: float + start_time : float Starting time of acquisition (equivalent to first TR, or to the opposite sign of the time offset). - filename: string - Filename the object will be saved with. Init as empty string Methods ------- From a0b68f80bc2934b91da3839eb7f3637bbc061329 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 11 Jun 2020 15:49:13 +0200 Subject: [PATCH 144/180] Add filename to docstring --- phys2bids/physio_obj.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phys2bids/physio_obj.py b/phys2bids/physio_obj.py index 91847a7db..24fb923b3 100644 --- a/phys2bids/physio_obj.py +++ b/phys2bids/physio_obj.py @@ -556,6 +556,9 @@ class BlueprintOutput(): start_time : float Starting time of acquisition (equivalent to first TR, or to the opposite sign of the time offset). + filename : string + Filename the object will be saved with. Init as empty string + Methods ------- From 0c6cca79a5bd2b778f7d7ebe42ba95bbc91c22ce Mon Sep 17 00:00:00 2001 From: Ross Markello Date: Thu, 11 Jun 2020 10:28:21 -0400 Subject: [PATCH 145/180] Use import numpy as np syntax --- phys2bids/phys2bids.py | 8 ++++---- phys2bids/slice4phys.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 25776a248..0b48dacf3 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -31,7 +31,7 @@ import logging from copy import deepcopy -from numpy import savetxt, ones +import numpy as np from phys2bids import utils, viz, _version from phys2bids.bids import bidsify_units, use_heuristic @@ -230,7 +230,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # if multi-run of same sequence type, pad list with ones # and multiply array with user's input if len(tr) == 1: - tr = ones(len(num_timepoints_expected)) * tr[0] + tr = np.ones(len(num_timepoints_expected)) * tr[0] # Check equivalency of length elif len(num_timepoints_expected) != len(tr): raise Exception('Number of sequence types listed with TR ' @@ -369,8 +369,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq}' LGR.info(f'Exporting files for run {run} freq {uniq_freq}') - savetxt(phys_out[key].filename + '.tsv.gz', phys_out[key].timeseries, - fmt='%.8e', delimiter='\t') + np.savetxt(phys_out[key].filename + '.tsv.gz', phys_out[key].timeseries, + fmt='%.8e', delimiter='\t') print_json(phys_out[key].filename, phys_out[key].freq, phys_out[key].start_time, phys_out[key].ch_name) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 490ef6428..d62b5a4ef 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from numpy import where +import numpy as np import logging LGR = logging.getLogger(__name__) @@ -58,7 +58,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): if run_idx == 0: run_start = 0 else: - run_start = where(phys_in.timeseries[0] <= 0)[0][0] - padding + run_start = np.where(phys_in.timeseries[0] <= 0)[0][0] - padding # Defining end of acquisition # run length in seconds @@ -67,7 +67,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # define index of the run's last trigger + padding (HAS TO BE INT type) # pick first value of time array that is over specified run length # where returns list of values over end_sec and its dtype, choose [list][first value] - run_end = int(where(phys_in.timeseries[0] > end_sec)[0][0] + padding) + run_end = int(np.where(phys_in.timeseries[0] > end_sec)[0][0] + padding) update = int(run_end - padding + 1) # if the padding is too much for the remaining timeseries length From 67b7a862bf129a0b9e0d475a7a953faebb7e31fb Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 11 Jun 2020 19:37:50 +0200 Subject: [PATCH 146/180] Correct documentation and short rephrasing --- docs/howto.rst | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/howto.rst b/docs/howto.rst index 59ed7286c..cb87b78e3 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -280,17 +280,20 @@ By looking at this figure, we can work out that we need a smaller threshold in o ------------------------------------------------ Splitting your input file into multiple run output files -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If your file contains more than one (f)MRI acquisition (or runs), you can give multiple values to ``-ntp`` and ``tr`` arguments in order to get multiple ``.tsv.gz`` outputs. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your file contains more than one (f)MRI acquisition (or runs), you can provide multiple values to ``-ntp`` in order to get multiple ``.tsv.gz`` outputs. If the TR of the entire session is consistent (i.e. all your acquisitions have the same TR), then you can specify one value after ``-tr``. By specifying the number of timepoints in each acquisition, ``phys2bids`` will recursively cut the input file by detecting the first trigger of the entire session and the ones after the number of timepoints you specified. .. code-block:: shell - phys2bids -in two_scans_samefreq_all.txt -chtrig 2 -ntp 536 398 -tr 1.2 -thr 2 + + phys2bids -in two_scans_samefreq_all.txt -chtrig 2 -ntp 536 398 -tr 1.2 -thr 2 Now, instead of counting the trigger timepoints once, ``physbids`` will check the trigger channel recursively with all the values listed in ``-ntp``. The logger will inform you about the number of timepoints left at each iteration. .. code-block:: shell + INFO:phys2bids.physio_obj:Counting trigger points INFO:phys2bids.physio_obj:The trigger is in channel 2 INFO:phys2bids.physio_obj:The number of timepoints found with the manual threshold of 2.0000 is 934 @@ -325,16 +328,25 @@ Now, instead of counting the trigger timepoints once, ``physbids`` will check th INFO:phys2bids.phys2bids:Preparing 2 output files. INFO:phys2bids.phys2bids:Exporting files for run 1 freq 1000.0 -The logger also notifies the user about the slicing points used (the first always being from the beginning of session, until the specified number of timepoints after the first trigger). The user can also check the resulting slice by looking at the plot of the trigger channel for each run. Each slice is adjusted with a padding of 9 seconds after the last trigger. This padding is also applied at the beginning (-9s before first trigger of run) of the 2nd to last run. +The logger also notifies the user about the slicing points used (the first always being from the beginning of session, until the specified number of timepoints after the first trigger). The user can also check the resulting slice by looking at the plot of the trigger channel for each run. Each slice is adjusted with a padding after the last trigger. Such padding can be specified while calling ``phys2bids`` with ``-pad``. If nothing is specified, the default value of 9 seconds will be used. This padding is also applied at the beginning (before the first trigger of the run) of the 2nd to last run. What if I have multiple acquisition types ? -***************************************** -The user can also benefit from this utility when dealing with multiple ***acquisition types*** such as structural and functional (i.e. different TR). Like ``-ntp``, ``-tr`` takes multiple values. **Though, they have to be the same length**. The idea is simple : if you only have one acquisition type, the one ``-tr`` input you gave will be broadcasted through all runs, but if there are different acquisition types, you have to list them all in order. +******************************************* + +The user can also benefit from this utility when dealing with multiple **acquisition types** such as different functional scans with different TRs. Like ``-ntp``, ``-tr`` can take multiple values. **Though, if more than one value is specified, they require the same amount of values**. The idea is simple: if you only have one acquisition type, the one ``-tr`` input you gave will be broadcast through all runs, but if there are different acquisition types, you have to list all of them in order. .. warning:: There are currently no ``multi-run tutorial files`` available along with the package (under ``phys2bids/tests/data``). Although, you can visit `phys2bids OSF `_ storage to access a LabChart physiological recording with multiple fMRI acquisitions. Find it under ``labchart/chicago``. + .. note:: - **Why would I have more than one fMRI acquisition in the physiological recording?** The idea is to reduce human error. Synchronization between start of both fMRI and physiological acquisitions can be difficult, so it is safer to have a only one physiological recording with multiple imaging sequences. The next step will be to reduce user's inputs in ``phys2bids`` by detecting the number of triggers in each run automatically. Either by looking in the DICOM registry or directly computing the time difference between subsequent triggers. That way, an individual file will require less attention to pass through phys2bids workflow, and the user will be able to process batches of files. + **Why would I have more than one fMRI acquisition in the physiological recording?** + + The idea is to reduce human error and have a good padding around you fMRI scan! + + Synchronization between start of both fMRI and physiological acquisitions can be difficult, so it is safer to have as few physiological recordings as possible, with multiple imaging sequences. + + Moreover, if you want to correct for recording/physiological delays, you will love *that bit of recorded information* around your fMRI scan! + Generating outputs in BIDs format ################################# From 32a84cfd69503824351f6bded67e1568f51704b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 11 Jun 2020 17:08:43 -0400 Subject: [PATCH 147/180] Update phys2bids/phys2bids.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eneko Uruñuela <13706448+eurunuela@users.noreply.github.com> --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 0b48dacf3..0e8ab2d9c 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -246,7 +246,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # if it passes call slice4phys if phys_in.num_timepoints_found != sum(num_timepoints_expected): raise Exception('The number of triggers found is different ' - 'than expected. Better stop now than breaking ' + 'than expected. Better stop now than break ' 'something.') # slice the recording based on user's entries From 366e2e8e1db633f86d631cfb8bc4d48131d19fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Thu, 11 Jun 2020 17:09:44 -0400 Subject: [PATCH 148/180] Update phys2bids/cli/run.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eneko Uruñuela <13706448+eurunuela@users.noreply.github.com> --- phys2bids/cli/run.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index 08fd58ede..5fa833e00 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -112,8 +112,7 @@ def _get_parser(): 'If "ntp" and "TR" are specified, phys2bids ' 'automatically computes a threshold to detect ' 'the triggers. Use this parameter to set it manually. ' - 'If you\'re running phys2bids on a multi-run recording, ' - 'you NEED to set this.', + 'This parameter is necessary for multi-run recordings. ' default=None) optional.add_argument('-pad', '--padding', dest='pad', From 3a866841b21eafc4199d51d0402be6653a6df496 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 15:24:18 +0200 Subject: [PATCH 149/180] Update docs/howto.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eneko Uruñuela <13706448+eurunuela@users.noreply.github.com> --- docs/howto.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto.rst b/docs/howto.rst index cb87b78e3..55318096f 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -333,7 +333,7 @@ The logger also notifies the user about the slicing points used (the first alway What if I have multiple acquisition types ? ******************************************* -The user can also benefit from this utility when dealing with multiple **acquisition types** such as different functional scans with different TRs. Like ``-ntp``, ``-tr`` can take multiple values. **Though, if more than one value is specified, they require the same amount of values**. The idea is simple: if you only have one acquisition type, the one ``-tr`` input you gave will be broadcast through all runs, but if there are different acquisition types, you have to list all of them in order. +The user can also benefit from this utility when dealing with multiple **acquisition types** such as different functional scans with different TRs. Like ``-ntp``, ``-tr`` can take multiple values. **Though, if more than one value is specified, they require the same amount of values**. The idea is simple: if you only have one acquisition type, the one ``-tr`` input you gave will be broadcast through all runs, but if you have different acquisition types, you have to list all of them in order. .. warning:: There are currently no ``multi-run tutorial files`` available along with the package (under ``phys2bids/tests/data``). Although, you can visit `phys2bids OSF `_ storage to access a LabChart physiological recording with multiple fMRI acquisitions. Find it under ``labchart/chicago``. From 1e3d9b15f7ba9af033faeb10a1551a38abebbf57 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 15:25:01 +0200 Subject: [PATCH 150/180] Make things personal --- docs/howto.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto.rst b/docs/howto.rst index cb87b78e3..a01316afc 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -328,7 +328,7 @@ Now, instead of counting the trigger timepoints once, ``physbids`` will check th INFO:phys2bids.phys2bids:Preparing 2 output files. INFO:phys2bids.phys2bids:Exporting files for run 1 freq 1000.0 -The logger also notifies the user about the slicing points used (the first always being from the beginning of session, until the specified number of timepoints after the first trigger). The user can also check the resulting slice by looking at the plot of the trigger channel for each run. Each slice is adjusted with a padding after the last trigger. Such padding can be specified while calling ``phys2bids`` with ``-pad``. If nothing is specified, the default value of 9 seconds will be used. This padding is also applied at the beginning (before the first trigger of the run) of the 2nd to last run. +The logger also notifies you about the slicing points used (the first always being from the beginning of session, until the specified number of timepoints after the first trigger). The user can also check the resulting slice by looking at the plot of the trigger channel for each run. Each slice is adjusted with a padding after the last trigger. Such padding can be specified while calling ``phys2bids`` with ``-pad``. If nothing is specified, the default value of 9 seconds will be used. This padding is also applied at the beginning (before the first trigger of the run) of the 2nd to last run. What if I have multiple acquisition types ? ******************************************* From be52689f9e2dc8e6a42c2882cf18de48fe78db92 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 15:25:17 +0200 Subject: [PATCH 151/180] Update docs/howto.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eneko Uruñuela <13706448+eurunuela@users.noreply.github.com> --- docs/howto.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto.rst b/docs/howto.rst index 55318096f..02216dcfe 100644 --- a/docs/howto.rst +++ b/docs/howto.rst @@ -341,7 +341,7 @@ The user can also benefit from this utility when dealing with multiple **acquisi .. note:: **Why would I have more than one fMRI acquisition in the physiological recording?** - The idea is to reduce human error and have a good padding around you fMRI scan! + The idea is to reduce human error and have a good padding around your fMRI scan! Synchronization between start of both fMRI and physiological acquisitions can be difficult, so it is safer to have as few physiological recordings as possible, with multiple imaging sequences. From 6396e67eecd882b38bc4d4b397150394c6cd54c4 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 15:56:05 +0200 Subject: [PATCH 152/180] Check tr ntp length earlier --- phys2bids/phys2bids.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 0e8ab2d9c..a0fa54edb 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -182,6 +182,13 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if isinstance(tr, (int, float)): tr = [tr] + if tr is not None and num_timepoints_expected is not None: + # If tr and ntp were specified, check that tr is either length one or ntp. + if len(num_timepoints_expected) != len(tr) and len(tr) != 1: + raise Exception('Number of sequence types listed with TR ' + 'doesn\'t match expected number of runs in ' + 'the session') + # Read file! if ftype == 'acq': from phys2bids.interfaces.acq import populate_phys_input @@ -221,7 +228,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in.rename_channels(ch_name) # Checking acquisition type via user's input - run_amount = 1 # default number of runs, unless otherwise determined if tr is not None and num_timepoints_expected is not None: # Multi-run acquisition type section @@ -231,11 +237,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # and multiply array with user's input if len(tr) == 1: tr = np.ones(len(num_timepoints_expected)) * tr[0] - # Check equivalency of length - elif len(num_timepoints_expected) != len(tr): - raise Exception('Number of sequence types listed with TR ' - 'doesn\'t match expected number of runs in ' - 'the session') # Sum of values in ntp_list should be equivalent to num_timepoints_found phys_in.check_trigger_amount(thr=thr, @@ -274,6 +275,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Reassign phys_in as dictionary # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY phys_in = {1: phys_in} + run_amount = 1 else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' From 80554b854c9c9a8266e75ee3be5e40071c50fd67 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 16:05:13 +0200 Subject: [PATCH 153/180] Bug fix on threshold entry --- phys2bids/cli/run.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phys2bids/cli/run.py b/phys2bids/cli/run.py index 5fa833e00..bdea8c0e6 100644 --- a/phys2bids/cli/run.py +++ b/phys2bids/cli/run.py @@ -112,15 +112,15 @@ def _get_parser(): 'If "ntp" and "TR" are specified, phys2bids ' 'automatically computes a threshold to detect ' 'the triggers. Use this parameter to set it manually. ' - 'This parameter is necessary for multi-run recordings. ' - default=None) + 'This parameter is necessary for multi-run recordings. ', + default=None) optional.add_argument('-pad', '--padding', dest='pad', type=float, help='Padding in seconds used around a single run ' 'when separating multi-run session files. ' 'Default is 9 seconds.', - default=9) + default=9) optional.add_argument('-chnames', '--channel-names', dest='ch_name', nargs='*', From d2d2e97687f0b42626a070ba56c8d4473996bbb5 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 16:05:37 +0200 Subject: [PATCH 154/180] Run keys start from 1 --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index a0fa54edb..b40391241 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -301,7 +301,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out = {} # Export a (set of) phys_out for each element in phys_in - # what's the run key ??? 1 or 0 + # run keys start from 1 (human friendly) for run in phys_in.keys(): for uniq_freq in uniq_freq_list: # Initialise the key for the (possibly huge amount of) dictionary entries From 8e6157730f4205595ea3ad4819abd6291675fe90 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 17:13:45 +0200 Subject: [PATCH 155/180] Change plot filename --- phys2bids/phys2bids.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index b40391241..0de6b7021 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -258,8 +258,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # save a figure for each run | give the right acquisition parameters for runs for i, run in enumerate(phys_in.keys()): + plot_filename = f'{filename}_{run}' viz.export_trigger_plot(phys_in[run], num_timepoints_expected[i], tr[i], - chtrig, outdir, filename, sub, ses, run) + chtrig, outdir, plot_filename, sub, ses, run) # define run amount run_amount = len(phys_in) From 4178b8a2718ed729c08239cb5bc715f236f0a301 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 18:53:51 +0200 Subject: [PATCH 156/180] Correct plot printing --- phys2bids/phys2bids.py | 14 ++++++++++---- phys2bids/viz.py | 32 +++++++++++++------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 0de6b7021..e1028e557 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -259,8 +259,11 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # save a figure for each run | give the right acquisition parameters for runs for i, run in enumerate(phys_in.keys()): plot_filename = f'{filename}_{run}' - viz.export_trigger_plot(phys_in[run], num_timepoints_expected[i], tr[i], - chtrig, outdir, plot_filename, sub, ses, run) + fileprefix = os.path.join(outdir, + os.path.splitext(os.path.basename(plot_filename))[0]) + viz.export_trigger_plot(phys_in[run], chtrig, fileprefix, tr[i], + num_timepoints_expected[i], filename, + sub, ses) # define run amount run_amount = len(phys_in) @@ -270,8 +273,11 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # and the time offset. phys_in.check_trigger_amount(thr, num_timepoints_expected[0], tr[0]) # save a figure of the trigger - viz.export_trigger_plot(phys_in, num_timepoints_expected[0], tr[0], chtrig, - outdir, filename, sub, ses) + fileprefix = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) + viz.export_trigger_plot(phys_in, chtrig, fileprefix, tr[0], + num_timepoints_expected[0], filename, + sub, ses) # Reassign phys_in as dictionary # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY diff --git a/phys2bids/viz.py b/phys2bids/viz.py index cfab47340..7ae53872c 100644 --- a/phys2bids/viz.py +++ b/phys2bids/viz.py @@ -128,8 +128,9 @@ def ntr2time(x): plt.close() -def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, filename, - sub=None, ses=None, run=None, figsize=FIGSIZE, dpi=SET_DPI): +def export_trigger_plot(phys_in, chtrig, fileprefix, tr, num_timepoints_expected, + filename, sub=None, ses=None, figsize=FIGSIZE, + dpi=SET_DPI): """ Save a trigger plot. @@ -141,24 +142,22 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi phys_in : object Object returned by BlueprintInput class For multi-run acquisitions, phys_in is a slice of the whole object - num_timepoints_expected : list - a list of integers given by the user as `ntp` input - tr : list - a list of float given by the user as `tr` input chtrig : int trigger channel integer representing the index of the trigger on phys_in.timeseries - outdir : str - directory to save output. - if ses and sub are specified, it can be understood as root directory of dataset + fileprefix: str or path + A string representing a file name or a fullpath + to a file, WITHOUT extension + tr : list + a list of float given by the user as `tr` input + num_timepoints_expected : list + a list of integers given by the user as `ntp` input filename : str name of the input file given by user's entry sub: str or int Name of subject. Default is None ses: str or int or None Name of session. Default is None - run: int or None - Run number. Default is None figsize: tuple or list of floats Size of the figure expressed as (size_x, size_y), Default is {FIGSIZE} @@ -167,21 +166,16 @@ def export_trigger_plot(phys_in, num_timepoints_expected, tr, chtrig, outdir, fi Default is {SET_DPI} """ LGR.info('Plot trigger') - plot_path = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) # Create trigger plot. If possible, to have multiple outputs in the same # place, adds sub and ses label. if sub is not None: - plot_path += f'_sub-{sub}' + fileprefix += f'_sub-{sub}' if ses is not None: - plot_path += f'_ses-{ses}' - # add run to filename - if run is not None: - filename = f'{filename}_run-{run:02d}' + fileprefix += f'_ses-{ses}' # adjust for multi run arguments, iterate through acquisition attributes plot_trigger(phys_in.timeseries[0], phys_in.timeseries[chtrig], - plot_path, tr, phys_in.thr, num_timepoints_expected, + fileprefix, tr, phys_in.thr, num_timepoints_expected, filename, figsize, dpi) From 68508faa048202faae19883e653d2fb95d2e14b4 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 19:03:41 +0200 Subject: [PATCH 157/180] Moving back run_amount --- phys2bids/phys2bids.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index e1028e557..b98118188 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -227,6 +227,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info('Renaming channels with given names') phys_in.rename_channels(ch_name) + # Default amount of runs + run_amount = 1 # Checking acquisition type via user's input if tr is not None and num_timepoints_expected is not None: @@ -282,7 +284,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Reassign phys_in as dictionary # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY phys_in = {1: phys_in} - run_amount = 1 else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' From 3cdaf2cfbcf6421971463e614c59ee6f391cfb3b Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 19:09:25 +0200 Subject: [PATCH 158/180] Update pytest to make travis work --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 14039808a..746427493 100755 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ before_install: pip install flake8; fi - if [ "${CHECK_TYPE}" == "test" ]; then - pip install "pytest>=3.6" pytest-cov coverage coveralls codecov; + pip install "pytest>=4.6" pytest-cov coverage coveralls codecov; fi - if [ "${CHECK_TYPE}" == "docdoctest" ]; then pip install "sphinx>2.0" sphinx_rtd_theme pandas sphinx-argparse; From 8858fedcf0d1c48ab3d14e8a28c1f433f056f8be Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Fri, 12 Jun 2020 20:11:13 +0200 Subject: [PATCH 159/180] Correct fileprefix name --- phys2bids/phys2bids.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index b98118188..69a6c0bb2 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -259,11 +259,11 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # returns a dictionary in the form {run_idx: phys_in[startpoint, endpoint]} # save a figure for each run | give the right acquisition parameters for runs + fileprefix = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) for i, run in enumerate(phys_in.keys()): - plot_filename = f'{filename}_{run}' - fileprefix = os.path.join(outdir, - os.path.splitext(os.path.basename(plot_filename))[0]) - viz.export_trigger_plot(phys_in[run], chtrig, fileprefix, tr[i], + plot_fileprefix = f'{fileprefix}_{run}' + viz.export_trigger_plot(phys_in[run], chtrig, plot_fileprefix, tr[i], num_timepoints_expected[i], filename, sub, ses) # define run amount From 01b597508afd8669bf8e865675870eeb79bda3da Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Tue, 16 Jun 2020 23:32:11 +0200 Subject: [PATCH 160/180] Deepcopy the object, then run again check_trigger_amount This solves the issue of the timeseries going negative! --- phys2bids/slice4phys.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index d62b5a4ef..397ba4155 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import numpy as np import logging +from copy import deepcopy + +import numpy as np LGR = logging.getLogger(__name__) @@ -51,7 +53,8 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): for run_idx, run_tps in enumerate(ntp_list): # correct time offset for this iteration's object - phys_in.check_trigger_amount(thr=thr, num_timepoints_expected=run_tps, tr=tr_list[run_idx]) + phys_in.check_trigger_amount(thr=thr, num_timepoints_expected=run_tps, + tr=tr_list[run_idx]) # If it's the very first run, start the run at sample 0, # otherwise "add" the padding @@ -98,7 +101,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # update the object so that next iteration will look for the first trigger # after previous run's last trigger. maybe padding extends to next run - phys_in = phys_in[update:-1] + phys_in = deepcopy(phys_in[update:-1]) return run_timestamps @@ -137,17 +140,16 @@ def slice4phys(phys_in, ntp_list, tr_list, thr, padding=9): # Find the timestamps run_timestamps = find_runs(phys_in, ntp_list, tr_list, thr, padding) - for run in run_timestamps.keys(): + for n, run in enumerate(run_timestamps.keys()): # tmp variable to collect run's info run_attributes = run_timestamps[run] - phys_in_slices[run] = phys_in[run_attributes[0]:run_attributes[1]] + phys_in_slices[run] = deepcopy(phys_in[run_attributes[0]:run_attributes[1]]) - # Overwrite current run phys_in attributes - # 3rd item of run_attributes is adjusted time offset - phys_in_slices[run].time_offset = run_attributes[2] - # 4th item of run_attributes is the nb of tp found by check_trigger_amount - phys_in_slices[run].num_timepoints_found = run_attributes[3] + # Run check_trigger amount + phys_in_slices[run].check_trigger_amount(thr=thr, + num_timepoints_expected=ntp_list[n], + tr=tr_list[n]) return phys_in_slices From d7c5bd2b97a0bdf610b4daa0b3c6a91fb57adaa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Tue, 16 Jun 2020 18:37:52 -0400 Subject: [PATCH 161/180] redef of run start --- phys2bids/slice4phys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 397ba4155..3fa6a9216 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -61,7 +61,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): if run_idx == 0: run_start = 0 else: - run_start = np.where(phys_in.timeseries[0] <= 0)[0][0] - padding + run_start = np.where(np.isclose(phys_in.timeseries[0], 0))[0][0] - padding # Defining end of acquisition # run length in seconds From 174d4debfbcfc1670a17d19d418577af9c919047 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 17 Jun 2020 09:46:25 +0200 Subject: [PATCH 162/180] Add tr in test call --- phys2bids/tests/test_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index bbe7b958b..25dc0bada 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -60,7 +60,7 @@ def test_integration_txt(samefreq_short_txt_file): test_chtrig = 2 phys2bids(filename=test_filename, indir=test_path, outdir=test_path, - chtrig=test_chtrig, num_timepoints_expected=1) + chtrig=test_chtrig, num_timepoints_expected=1, tr=1) # Check that files are generated for suffix in ['.log', '.json', '.tsv.gz', '_trigger_time.png']: From 187f262b7276c2d6204bae0d6a7eaca5c1885370 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 17 Jun 2020 10:46:35 +0200 Subject: [PATCH 163/180] fix paddings --- phys2bids/slice4phys.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 3fa6a9216..fcbb1b488 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -55,13 +55,12 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # correct time offset for this iteration's object phys_in.check_trigger_amount(thr=thr, num_timepoints_expected=run_tps, tr=tr_list[run_idx]) - # If it's the very first run, start the run at sample 0, - # otherwise "add" the padding + # otherwise find the index of time 0 if run_idx == 0: run_start = 0 else: - run_start = np.where(np.isclose(phys_in.timeseries[0], 0))[0][0] - padding + run_start = int(np.where(np.isclose(phys_in.timeseries[0], 0))[0]) # Defining end of acquisition # run length in seconds @@ -84,8 +83,10 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): previous_end_index = run_timestamps[run_idx][1] # adjust time_offset to keep original timing information phys_in.time_offset = phys_in.time_offset + run_timestamps[run_idx][2] - run_start = int(run_start + previous_end_index) - run_end = int(run_end + previous_end_index) + # update run_start, removing 2 paddings (one for this run, one for the previous) + run_start = int(run_start + previous_end_index - 2*padding) + # update run_end, removing the padding of the previous end + run_end = int(run_end + previous_end_index - padding) # Save *start* and *end_index* in dictionary along with *time_offset* and *ntp found* # dict key must be readable by human @@ -139,7 +140,6 @@ def slice4phys(phys_in, ntp_list, tr_list, thr, padding=9): '\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') # Find the timestamps run_timestamps = find_runs(phys_in, ntp_list, tr_list, thr, padding) - for n, run in enumerate(run_timestamps.keys()): # tmp variable to collect run's info From 1c8898162ad4ed74355907fd10e543e4e6ec1773 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Wed, 17 Jun 2020 11:07:31 +0200 Subject: [PATCH 164/180] Linter --- phys2bids/slice4phys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index fcbb1b488..8291d1231 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -84,7 +84,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): # adjust time_offset to keep original timing information phys_in.time_offset = phys_in.time_offset + run_timestamps[run_idx][2] # update run_start, removing 2 paddings (one for this run, one for the previous) - run_start = int(run_start + previous_end_index - 2*padding) + run_start = int(run_start + previous_end_index - 2 * padding) # update run_end, removing the padding of the previous end run_end = int(run_end + previous_end_index - padding) From cc7bacf5e4556482d05b5a1a1fbf97e5510fe43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Lespinasse?= Date: Wed, 17 Jun 2020 10:22:39 -0400 Subject: [PATCH 165/180] empty commit --- phys2bids/slice4phys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/slice4phys.py b/phys2bids/slice4phys.py index 8291d1231..573a7d1dc 100644 --- a/phys2bids/slice4phys.py +++ b/phys2bids/slice4phys.py @@ -56,7 +56,7 @@ def find_runs(phys_in, ntp_list, tr_list, thr=None, padding=9): phys_in.check_trigger_amount(thr=thr, num_timepoints_expected=run_tps, tr=tr_list[run_idx]) # If it's the very first run, start the run at sample 0, - # otherwise find the index of time 0 + # otherwise start is first trigger (adjust with padding later) if run_idx == 0: run_start = 0 else: From ec3bc0dded75491714a292929801049dbe079df3 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Thu, 18 Jun 2020 22:10:37 +0200 Subject: [PATCH 166/180] Fix paths --- phys2bids/phys2bids.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 47eb96e3d..62b6adec1 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -148,11 +148,9 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, outdir = utils.check_input_dir(outdir) utils.path_exists_or_make_it(outdir) utils.path_exists_or_make_it(os.path.join(outdir, 'code')) - conversion_path = os.path.join(outdir, 'code/conversion') + conversion_path = os.path.join(outdir, 'code', 'conversion') utils.path_exists_or_make_it(conversion_path) - # generate extra path - extra_dir = os.path.join(outdir, 'bids_ignore') - utils.path_exists_or_make_it(extra_dir) + # Create logfile name basename = 'phys2bids_' extension = 'tsv' @@ -284,7 +282,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # returns a dictionary in the form {run_idx: phys_in[startpoint, endpoint]} # save a figure for each run | give the right acquisition parameters for runs - fileprefix = os.path.join(outdir, + fileprefix = os.path.join(conversion_path, os.path.splitext(os.path.basename(filename))[0]) for i, run in enumerate(phys_in.keys()): plot_fileprefix = f'{fileprefix}_{run}' @@ -300,7 +298,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # and the time offset. phys_in.check_trigger_amount(thr, num_timepoints_expected[0], tr[0]) # save a figure of the trigger - fileprefix = os.path.join(outdir, + fileprefix = os.path.join(conversion_path, os.path.splitext(os.path.basename(filename))[0]) viz.export_trigger_plot(phys_in, chtrig, fileprefix, tr[0], num_timepoints_expected[0], filename, @@ -396,16 +394,14 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, if any([phys.filename == phys_out[key].filename for phys in phys_out.values()]): phys_out[key].filename = (f'{phys_out[key].filename}' - '_take-{run}') + f'_take-{run}') LGR.warning('Identified multiple outputs with the same name.\n' 'Appending fake label to avoid overwriting.\n' '!!! ATTENTION !!! the output is not BIDS compliant.\n' 'Please check heuristics to solve the problem.') else: - phys_out[key].filename = os.path.join(outdir, - os.path.splitext(os.path.basename(filename) - )[0]) + phys_out[key].filename = os.path.splitext(os.path.basename(filename))[0] # Append "run" to filename if more than one run if run_amount > 1: phys_out[key].filename = f'{phys_out[key].filename}_{run:02d}' @@ -414,15 +410,15 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq}' LGR.info(f'Exporting files for run {run} freq {uniq_freq}') - np.savetxt(phys_out[key].filename + '.tsv.gz', phys_out[key].timeseries, - fmt='%.8e', delimiter='\t') - print_json(phys_out[key].filename, phys_out[key].freq, - phys_out[key].start_time, + np.savetxt(os.path.join(outdir, phys_out[key].filename + '.tsv.gz'), + phys_out[key].timeseries, fmt='%.8e', delimiter='\t') + print_json(os.path.join(outdir, phys_out[key].filename), + phys_out[key].freq, phys_out[key].start_time, phys_out[key].ch_name) print_summary(filename, num_timepoints_expected, phys_in[run].num_timepoints_found, uniq_freq, - phys_out[key].start_time, os.path.join(conversion_path, - phys_out[key].filename)) + phys_out[key].start_time, + os.path.join(conversion_path, phys_out[key].filename)) def _main(argv=None): From c62a1be764ee55759ef7e97f081cc1835d536d7f Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 00:32:27 +0200 Subject: [PATCH 167/180] Adapt use_heuristic call to new heuristic call, remove unused library --- phys2bids/tests/test_bids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/tests/test_bids.py b/phys2bids/tests/test_bids.py index f733431ad..6c062ee1e 100644 --- a/phys2bids/tests/test_bids.py +++ b/phys2bids/tests/test_bids.py @@ -7,7 +7,6 @@ from phys2bids import bids from phys2bids.bids import UNIT_ALIASES -from phys2bids.utils import append_list_as_row def test_bidsify_units(): @@ -41,7 +40,8 @@ def test_use_heuristic(tmpdir, test_sub, test_ses): test_record_label = 'test' heur_path = bids.use_heuristic(test_full_heur_path, test_sub, test_ses, - test_full_input_path, test_outdir, test_record_label) + test_full_input_path, test_outdir, + record_label=test_record_label) if test_sub[:4] == 'sub-': test_sub = test_sub[4:] From 5da8f0931cabc4af63b6cfd1bf80d350e78d0e96 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 00:34:23 +0200 Subject: [PATCH 168/180] Add "run" as argument in new heuristic --- phys2bids/heuristics/heur_test_multifreq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/heuristics/heur_test_multifreq.py b/phys2bids/heuristics/heur_test_multifreq.py index c6bdbbd5d..bd91a6983 100644 --- a/phys2bids/heuristics/heur_test_multifreq.py +++ b/phys2bids/heuristics/heur_test_multifreq.py @@ -1,7 +1,7 @@ import fnmatch -def heur(physinfo): +def heur(physinfo, run=''): """ Set of if .. elif statements to fill BIDS names. From 797401fd6334d43c02de1496865a454573e997b8 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 00:44:04 +0200 Subject: [PATCH 169/180] Format filename frequency as integer --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 62b6adec1..7ed30aaba 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -407,7 +407,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out[key].filename = f'{phys_out[key].filename}_{run:02d}' # Append "freq" to filename if more than one freq if freq_amount > 1: - phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq}' + phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq:.0f}' LGR.info(f'Exporting files for run {run} freq {uniq_freq}') np.savetxt(os.path.join(outdir, phys_out[key].filename + '.tsv.gz'), From 4595150b18197d02b55a276ccae26d2766ab9e34 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 00:45:11 +0200 Subject: [PATCH 170/180] Format frequency as integer for heuristic --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 7ed30aaba..9656e0595 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -384,7 +384,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Append "recording-freq" to filename if more than one freq if freq_amount > 1: - heur_args['record_label'] = f'freq{uniq_freq}' + heur_args['record_label'] = f'freq{uniq_freq:.0f}' phys_out[key].filename = bids.use_heuristic(**heur_args) From 747e9bbf21ef040e9b33012768ebebe367748671 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:00:33 +0200 Subject: [PATCH 171/180] Optimise run_amount --- phys2bids/phys2bids.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 9656e0595..7904af12f 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -250,8 +250,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, LGR.info('Renaming channels with given names') phys_in.rename_channels(ch_name) - # Default amount of runs - run_amount = 1 # Checking acquisition type via user's input if tr is not None and num_timepoints_expected is not None: @@ -289,8 +287,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, viz.export_trigger_plot(phys_in[run], chtrig, plot_fileprefix, tr[i], num_timepoints_expected[i], filename, sub, ses) - # define run amount - run_amount = len(phys_in) # Single run acquisition type, or : nothing to split workflow else: From 30e3f142425f0c51f0703c359bf0b46776d93fc4 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:01:23 +0200 Subject: [PATCH 172/180] Optimise metadata creation --- phys2bids/phys2bids.py | 45 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 7904af12f..8315933aa 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -307,26 +307,45 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, else: LGR.warning('Skipping trigger pulse count. If you want to run it, ' 'call phys2bids using both "-ntp" and "-tr" arguments') + # !!! ATTENTION: PHYS_IN GETS OVERWRITTEN AS DICTIONARY phys_in = {1: phys_in} # The next few lines create a dictionary of different BlueprintInput # objects, one for each unique frequency for each run in phys_in # they also save the amount of runs and unique frequencies + run_amount = len(phys_in) uniq_freq_list = set(phys_in[1].freq) freq_amount = len(uniq_freq_list) if freq_amount > 1: LGR.info(f'Found {freq_amount} different frequencies in input!') - # If heuristics are used, init a dict of arguments to pass to use_heuristic - if heur_file is not None and sub is not None: - heur_args = {'heur_file': heur_file, 'sub': sub, 'ses': ses, - 'filename': filename, 'outdir': outdir, 'run': '', - 'record_label': ''} + if run_amount > 1: + LGR.info(f'Found {run_amount} different scans in input!') LGR.info(f'Preparing {freq_amount*run_amount} output files.') # Create phys_out dict that will have a blueprint object for each different frequency phys_out = {} + if heur_file is not None and sub is not None: + LGR.info(f'Preparing BIDS output using {heur_file}') + # If heuristics are used, init a dict of arguments to pass to use_heuristic + heur_args = {'heur_file': heur_file, 'sub': sub, 'ses': ses, + 'filename': filename, 'outdir': outdir, 'run': '', + 'record_label': ''} + # Generate participants.tsv file if it doesn't exist already. + # Update the file if the subject is not in the file. + # Do not update if the subject is already in the file. + bids.participants_file(outdir, yml, sub) + # Generate dataset_description.json file if it doesn't exist already. + bids.dataset_description_file(outdir) + # Generate README file if it doesn't exist already. + bids.readme_file(outdir) + cp(heur_file, os.path.join(conversion_path, + os.path.splitext(os.path.basename(heur_file))[0] + '.py')) + elif heur_file is not None and sub is None: + LGR.warning('While "-heur" was specified, option "-sub" was not.\n' + 'Skipping BIDS formatting.') + # Export a (set of) phys_out for each element in phys_in # run keys start from 1 (human friendly) for run in phys_in.keys(): @@ -353,22 +372,6 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # in the dictionary. phys_out[key] = BlueprintOutput.init_from_blueprint(phys_out[key]) - if heur_file is not None and sub is not None: - LGR.info(f'Preparing BIDS output using {heur_file}') - # Generate participants.tsv file if it doesn't exist already. - # Update the file if the subject is not in the file. - # Do not update if the subject is already in the file. - bids.participants_file(outdir, yml, sub) - # Generate dataset_description.json file if it doesn't exist already. - bids.dataset_description_file(outdir) - # Generate README file if it doesn't exist already. - bids.readme_file(outdir) - cp(heur_file, os.path.join(conversion_path, - os.path.splitext(os.path.basename(heur_file))[0] + '.py')) - elif heur_file is not None and sub is None: - LGR.warning('While "-heur" was specified, option "-sub" was not.\n' - 'Skipping BIDS formatting.') - # Preparing output parameters: name and folder. for uniq_freq in uniq_freq_list: key = f'{run}_{uniq_freq}' From c379fc75c34d6c85f4e98cff955f3b323b1ad0d2 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:01:32 +0200 Subject: [PATCH 173/180] Fix paths --- phys2bids/phys2bids.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 8315933aa..7cec703a8 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -400,7 +400,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, 'Please check heuristics to solve the problem.') else: - phys_out[key].filename = os.path.splitext(os.path.basename(filename))[0] + phys_out[key].filename = os.path.join(outdir, + os.path.splitext(os.path.basename(filename))[0]) # Append "run" to filename if more than one run if run_amount > 1: phys_out[key].filename = f'{phys_out[key].filename}_{run:02d}' @@ -409,15 +410,15 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq:.0f}' LGR.info(f'Exporting files for run {run} freq {uniq_freq}') - np.savetxt(os.path.join(outdir, phys_out[key].filename + '.tsv.gz'), + np.savetxt(phys_out[key].filename + '.tsv.gz', phys_out[key].timeseries, fmt='%.8e', delimiter='\t') - print_json(os.path.join(outdir, phys_out[key].filename), - phys_out[key].freq, phys_out[key].start_time, - phys_out[key].ch_name) + print_json(phys_out[key].filename, phys_out[key].freq, + phys_out[key].start_time, phys_out[key].ch_name) print_summary(filename, num_timepoints_expected, phys_in[run].num_timepoints_found, uniq_freq, phys_out[key].start_time, - os.path.join(conversion_path, phys_out[key].filename)) + os.path.join(conversion_path, + os.path.splitext(os.path.basename(filename))[0])) def _main(argv=None): From c59c40b9f7d64062899780ad98e6e6b5ff104e50 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:04:39 +0200 Subject: [PATCH 174/180] Fix log name --- phys2bids/phys2bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 7cec703a8..540ef82bf 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -418,7 +418,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in[run].num_timepoints_found, uniq_freq, phys_out[key].start_time, os.path.join(conversion_path, - os.path.splitext(os.path.basename(filename))[0])) + os.path.splitext(os.path.basename(phys_out[key].filename))[0])) def _main(argv=None): From 5aedebb121bf6e919027744e8581c29583eac125 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:15:06 +0200 Subject: [PATCH 175/180] Rename multifreq files as {freq}Hz --- phys2bids/phys2bids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 540ef82bf..264fdbf76 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -383,7 +383,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, # Append "recording-freq" to filename if more than one freq if freq_amount > 1: - heur_args['record_label'] = f'freq{uniq_freq:.0f}' + heur_args['record_label'] = f'{uniq_freq:.0f}Hz' phys_out[key].filename = bids.use_heuristic(**heur_args) @@ -407,7 +407,7 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_out[key].filename = f'{phys_out[key].filename}_{run:02d}' # Append "freq" to filename if more than one freq if freq_amount > 1: - phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq:.0f}' + phys_out[key].filename = f'{phys_out[key].filename}_{uniq_freq:.0f}Hz' LGR.info(f'Exporting files for run {run} freq {uniq_freq}') np.savetxt(phys_out[key].filename + '.tsv.gz', From 4d2d15deb00f218861870f015885a7de4fb2de43 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:20:32 +0200 Subject: [PATCH 176/180] Adapt names --- phys2bids/tests/test_integration.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py index 527a422b9..d8f8d79de 100644 --- a/phys2bids/tests/test_integration.py +++ b/phys2bids/tests/test_integration.py @@ -120,8 +120,8 @@ def test_integration_heuristic(multifreq_lab_file): # Check that files are generated in conversion path for freq in ['40', '100', '500', '1000']: assert isfile(join(conversion_path, - 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-' - + freq + '.log')) + 'sub-006_ses-01_task-test_rec-biopac_run-01_' + f'recording-{freq}Hz_physio.log')) assert isfile(join(conversion_path, 'Test1_multifreq_onescan_sub-006_ses-01_trigger_time.png')) assert isfile(join(conversion_path, 'Test1_multifreq_onescan.png')) @@ -131,12 +131,13 @@ def test_integration_heuristic(multifreq_lab_file): # Check that files are generated base_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-' for suffix in ['.json', '.tsv.gz']: - for freq in ['40.0', '100.0', '500.0', '1000.0']: - assert isfile(join(test_path_output, base_filename + freq + '_physio' + suffix)) + for freq in ['40', '100', '500', '1000']: + assert isfile(join(test_path_output, + f'{base_filename}{freq}Hz_physio{suffix}')) # ##### Checks for 40 Hz files # Read log file (note that this file is not the logger file) - log_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-40.log' + log_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-40Hz_physio.log' with open(join(conversion_path, log_filename)) as log_info: log_info = log_info.readlines() @@ -152,7 +153,7 @@ def test_integration_heuristic(multifreq_lab_file): assert check_string(log_info, 'first trigger', 'Time 0', is_num=False) # Checks json file - json_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-40.0_physio.json' + json_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-40Hz_physio.json' with open(join(test_path_output, json_filename)) as json_file: json_data = json.load(json_file) @@ -163,7 +164,7 @@ def test_integration_heuristic(multifreq_lab_file): # ##### Checks for 100 Hz files # Read log file (note that this file is not the logger file) - log_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-100.log' + log_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-100Hz_physio.log' with open(join(conversion_path, log_filename)) as log_info: log_info = log_info.readlines() @@ -179,7 +180,7 @@ def test_integration_heuristic(multifreq_lab_file): assert check_string(log_info, 'first trigger', 'Time 0', is_num=False) # Checks json file - json_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-100.0_physio.json' + json_filename = 'sub-006_ses-01_task-test_rec-biopac_run-01_recording-100Hz_physio.json' with open(join(test_path_output, json_filename)) as json_file: json_data = json.load(json_file) From aaf8624f7553eb0e07fb94f8eacf99af39f24d34 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:27:05 +0200 Subject: [PATCH 177/180] Lint --- phys2bids/phys2bids.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py index 264fdbf76..1a4e316f0 100644 --- a/phys2bids/phys2bids.py +++ b/phys2bids/phys2bids.py @@ -401,7 +401,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, else: phys_out[key].filename = os.path.join(outdir, - os.path.splitext(os.path.basename(filename))[0]) + os.path.splitext(os.path.basename(filename) + )[0]) # Append "run" to filename if more than one run if run_amount > 1: phys_out[key].filename = f'{phys_out[key].filename}_{run:02d}' @@ -418,7 +419,8 @@ def phys2bids(filename, info=False, indir='.', outdir='.', heur_file=None, phys_in[run].num_timepoints_found, uniq_freq, phys_out[key].start_time, os.path.join(conversion_path, - os.path.splitext(os.path.basename(phys_out[key].filename))[0])) + os.path.splitext(os.path.basename(phys_out[key].filename) + )[0])) def _main(argv=None): From bf943eb67ef1cd27c931f3540ca042429e6a6a2a Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 01:54:03 +0200 Subject: [PATCH 178/180] Force pip to skip release candidates --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3118cec3e..5b5aea555 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -numpy>=1.9.3 -matplotlib>=3.1.1 -PyYAML \ No newline at end of file +numpy>=1.9.3, !=*rc* +matplotlib>=3.1.1 ,!=*rc* +PyYAML!=*rc* \ No newline at end of file From 4694d4dbead7aecba38f8556c420d25aa8c0b8c3 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 02:11:31 +0200 Subject: [PATCH 179/180] Force pip to skip release candidates --- setup.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index a941818ba..a9532a019 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,9 +22,9 @@ provides = [options] python_requires = >=3.6.1 install_requires = - numpy >=1.9.3 - matplotlib >=3.1.1 - PyYAML + numpy >=1.9.3, !=*rc* + matplotlib >=3.1.1, !=*rc* + PyYAML !=*rc* tests_require = pytest >=3.6 test_suite = pytest From 2d56fd35359b7f3baacbf8d36f966f0d06e54ad2 Mon Sep 17 00:00:00 2001 From: Stefano Moia Date: Mon, 22 Jun 2020 02:15:48 +0200 Subject: [PATCH 180/180] Force pip to skip matplotlib 3.3.0rc1 --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5b5aea555..c8772ca16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ numpy>=1.9.3, !=*rc* -matplotlib>=3.1.1 ,!=*rc* +matplotlib>=3.1.1 ,!=3.3.0rc1 PyYAML!=*rc* \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index a9532a019..1c921e44c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,7 @@ provides = python_requires = >=3.6.1 install_requires = numpy >=1.9.3, !=*rc* - matplotlib >=3.1.1, !=*rc* + matplotlib >=3.1.1, !=3.3.0rc1 PyYAML !=*rc* tests_require = pytest >=3.6