Repository: physiopy/phys2bids

Issue:
0- vs 1-indexing the `run` keyword in `use_heuristic()`
In https://github.com/physiopy/phys2bids/pull/76#discussion_r356095934 a brief discussion was started about whether the `run` keyword should be 0- or 1-indexed when users provide a `heuristic` to convert their data to BIDS format. We are currently using a 0-indexing approach; I personally think a 1-indexing approach makes more sense here, but am open to discussion on this point!

@smoia found a relevant portion of the [BIDS spec](https://bids-specification.readthedocs.io/en/stable/02-common-principles.html#participant-names-and-other-labels) that touches on this, but the spec is not prescriptive on this point. I'm happy to hear others thoughts and go about issuing a change if it's decided that's warranted!

Discussion:

Patch:
diff --git a/docs/heuristic.rst b/docs/heuristic.rst
index 539c634..08d70a6 100644
--- a/docs/heuristic.rst
+++ b/docs/heuristic.rst
@@ -22,36 +22,35 @@ Initialisation
 
 .. literalinclude:: ../phys2bids/heuristics/heur_tutorial.py
    :linenos:
-   :lines: 1-4
+   :lines: 1-43
 
 It's important **not to modify this part of the file**. Instead, you can copy and paste it into your own heuristic file.
 
-This file looks like a python function, initialised by two mandatory parameters:
--``physinfo`` is the information used to label your file. **At the moment, it corresponds to the name of the input file itself**. This is what you need to build your heuristic.
--``name`` is an argument passed by the main script that contains part of the name of the file. Don't worry about this.
+This file looks like a python function, initialised by a mandatory parameter, ``physinfo``.
+| ``physinfo`` is the information used to label your file. **At the moment, it corresponds to the name of the input file itself**. This is what you need to build your heuristic.
 
-The file also has another bunch of optional arguments that are empty by default. These are the labels you can add to your dictionaries, in order to construct the BIDsified name of your files.
+The function initialises ``info``, a `python dictionary <https://www.w3schools.com/python/python_dictionaries.asp>`_ that contains the BIDS keys, such as `sub` and `ses`, as well as all the possible keys you can add to your heuristics. This is what you will work with in creating your heuristic.
 
-The scripts imports ``fnmatch``, a nice python module that lets you use bash-like wildcards.
+The scripts also imports ``fnmatch``, a nice python module that lets you use bash-like wildcards.
 
 Dictionaries
 ^^^^^^^^^^^^
 
 .. literalinclude:: ../phys2bids/heuristics/heur_tutorial.py
    :linenos:
-   :lines: 5-29
-   :lineno-start: 5
+   :lines: 44-67
+   :lineno-start: 44
    :dedent: 4
 
-This is the core of the function, and the part that should be adapted to process your files. In practice, it's the beginning of a |statement|_.
-| You need an ``if`` or ``elif`` statement for each file that you want to process, that will test if the ``physinfo`` is similar to a string (first case) or exactly matches a string (second case). The content of the statement is a set of `variable initialisations as a string <https://www.w3schools.com/python/python_strings.asp>`_.
-| The list of possible variables is in the comment above, and corresponds to the list of possible entities of the `BIDs specification <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/06-physiological-and-other-continuous-recordings.html>`_:
+| This is the core of the function, and the part that should be adapted to process your files. In practice, it's a |statement|_.
+| You need an ``if`` or ``elif`` statement for each file that you want to process, that will test if the ``physinfo`` is similar to a string (first case) or exactly matches a string (second case). The content of the statement is a set of `variable initialisations as a string <https://www.w3schools.com/python/python_strings.asp>`_, with the only difference that you're populating a dictionary here. This means that instead of declaring something like ``var = 'something'``, you will declare something like ``info['var'] = 'something'``
+| The list of possible keys is in the comment above, and corresponds to the list of possible entities of the `BIDs specification <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/06-physiological-and-other-continuous-recordings.html>`_:
 
-- ``task`` stands for the name of the task. **It's the only required entity**, and it should match the task of the neuroimaging file associated to the physiological data.
+- ``task`` stands for the name of the task. **It's the only required entity**, and it should match the task of the neuroimaging file associated to the physiological data. If this is missing **the program will stop running**.
 - ``run`` is the optional entity for the `index of the scan in a group of same modalities <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#the-run-entity>`_ (e.g. 2 resting states).
 - ``rec`` is the optional entity for the `reconstruction algorithm <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#the-rec-entity>`_.
 - ``acq`` is the optional entity for the `set of acquisition parameters <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#the-acq-entity>`_.
-- ``direct`` is the equivalent of the ``dic`` entity, an optional entity for the phase encoding direction (see `here <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#task-including-resting-state-imaging-data>`_).
+- ``dir`` is the optional entity for the phase encoding direction (see `here <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#task-including-resting-state-imaging-data>`_).
 
 Note that one mandatory BIDs entity is missing: the **``sub`` entity**, correspondent to the subject label. This is because it has to be specified while calling ``phys2bids``, as it's explained in the tutorial section `"generating-outputs-in-bids-format" <howto.html#generating-outputs-in-bids-format>`_. The **session entity** can be specified in the same way. Moreover, if you have a **multifrequency file** there will be another entity, ``recording`` automatically added to those specified here, and containing the sample frequency of the different outputs.
 
@@ -70,13 +69,12 @@ Functional code
 
 .. literalinclude:: ../phys2bids/heuristics/heur_tutorial.py
    :linenos:
-   :lines: 27-
-   :lineno-start: 27
+   :lines: 68-
+   :lineno-start: 68
    :dedent: 4
 
-This part contains some code that composes the heuristic function output.
+This part is very simple: it returns the dictionary populated by the correct statement to the main program.
 It's important **not to modify this part of the file**. Instead, you can copy and paste it into your own heuristic file.
-There's a warning that will raise if the file wasn't able to process the input file, and an error that will raise if the mandatory ``task`` entity is still empty after the dictionary attribution.
 
 Using the heuristic file
 ------------------------
diff --git a/docs/howto.rst b/docs/howto.rst
index eb4b505..8dcdfb5 100644
--- a/docs/howto.rst
+++ b/docs/howto.rst
@@ -195,6 +195,7 @@ In the second row, we see the first and last trigger (or expected first and last
 
 .. warning::
     If you have another file that was collected in an identical way, the threshold is likely to be same. However, it is *very* important to calibrate the threshold in a couple of files before assuming this. This still *won't* necessarily mean that it's the right threshold for all the files, but there's a chance that it's ok(ish) for most of them.
+
 If for some reason ``-ntp`` and the number of timepoints found by ``phys2bids`` is not the same there are two possible reasons:
 
 1. You didn't properly count the number of timepoints. Check this again; you can use the trigger png file to help you.
@@ -281,8 +282,8 @@ Alright, now the really interesting part! This section will explain how to use t
 
 .. literalinclude:: ../phys2bids/heuristics/heur_tutorial.py
    :linenos:
-   :lines: 4-22
-   :lineno-start: 4
+   :lines: 44-62
+   :lineno-start: 44
 
 The heuristic file has to be written accordingly, with a set of rules that could work for all the files in your dataset. You can learn more about it if you check the `guide on how to set it up <heuristic.html>`_.
 In this case, our heuristic file looks for a file that contains the name ``tutorial``. It corresponds to the task ``test`` and run ``00``. Note that **only the task is required**, all the other fields are optional - look them up in the BIDs documentation and see if you need them.
diff --git a/phys2bids/heuristics/heur.py b/phys2bids/heuristics/heur.py
deleted file mode 100644
index 0091a0b..0000000
--- a/phys2bids/heuristics/heur.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# import sys
-import fnmatch
-
-
-def heur(physinfo, name, task='', acq='', direct='', rec='', run=''):
-    # ############################## #
-    # ##       Modify here!       ## #
-    # ##                          ## #
-    # ##  Possible variables are: ## #
-    # ##    -task (required)      ## #
-    # ##    -run                  ## #
-    # ##    -rec                  ## #
-    # ##    -acq                  ## #
-    # ##    -direct               ## #
-    # ##                          ## #
-    # ##                          ## #
-    # ##    See example below     ## #
-    # ############################## #
-
-    if physinfo == 'origfilename1':
-        task = 'newname1'
-    elif physinfo == 'origfilename2':
-        task = 'newname2'
-        run = 'runnum'
-    elif physinfo == 'BH4':
-        task = 'breathhold'
-    elif fnmatch.fnmatchcase(physinfo, 'MOTOR?'):
-        task = 'motor'
-    elif fnmatch.fnmatchcase(physinfo, 'LOCALIZER?'):
-        task = 'pinel'
-    elif fnmatch.fnmatchcase(physinfo, 'SIMON?'):
-        task = 'simon'
-    elif physinfo == 'RS1':
-        task = 'rest'
-        run = '01'
-    elif physinfo == 'RS2':
-        task = 'rest'
-        run = '02'
-    elif physinfo == 'RS3':
-        task = 'rest'
-        run = '03'
-    elif physinfo == 'RS4':
-        task = 'rest'
-        run = '04'
-        # ############################## #
-        # ## Don't modify below this! ## #
-        # ############################## #
-    else:
-        # #!# Transform sys.exit in debug warnings or raiseexceptions!
-        # #!# Make all of the above a dictionary
-        raise Warning(f'The heuristic {__file__} could not deal with {physinfo}')
-
-    if not task:
-        raise KeyError(f'No "task" attribute found')
-
-    name = name + '_task-' + task
-
-    # filename spec: sub-<label>[_ses-<label>]_task-<label>[_acq-<label>] ...
-    #                ... [_ce-<label>][_dir-<label>][_rec-<label>] ...
-    #                ... [_run-<index>][_recording-<label>]_physio
-    if acq:
-        name = name + '_acq-' + acq
-
-    if direct:
-        name = name + '_dir-' + direct
-
-    if rec:
-        name = name + '_rec-' + rec
-
-    if run:
-        name = name + '_run-' + run
-
-    return name
diff --git a/phys2bids/heuristics/heur_euskalibur.py b/phys2bids/heuristics/heur_euskalibur.py
new file mode 100644
index 0000000..8c0e1d5
--- /dev/null
+++ b/phys2bids/heuristics/heur_euskalibur.py
@@ -0,0 +1,88 @@
+import fnmatch
+
+
+def heur(physinfo):
+    """
+    Set of if .. elif statements to fill BIDS names.
+
+    It requires the user (you!) to adjust it accordingly!
+    It needs an ``if`` or ``elif`` statement for each file that
+    needs to be processed.
+    The statement will test if the ``physinfo``:
+        - is similar to a string (first case), or
+        - exactly matches a string (second case).
+
+    Parameters
+    ----------
+    physinfo: str
+        Name of an input file that should be bidsified (See Notes)
+
+    Returns
+    -------
+    info: dictionary of str
+        Dictionary containing BIDS keys
+
+    Notes
+    -----
+    The `if ..` structure should always be similar to
+    ```
+    if physinfo == 'somepattern':
+        info['var'] = 'somethingelse'
+    ```
+    or, in case it's a partial match
+    ```
+    if fnmatch.fnmatchcase(physinfo, '*somepattern?'):
+        info['var'] = 'somethingelse'
+    ```
+    Where:
+        - `physinfo` and `info` are dedicated keywords,
+        - 'somepattern' is the name of the file,
+        - 'var' is a bids key in the list below
+        - 'somethingelse' is the value of the key
+    """
+    info = {}
+    # ################################# #
+    # ##        Modify here!         ## #
+    # ##                             ## #
+    # ##  Possible variables are:    ## #
+    # ##    -info['task'] (required) ## #
+    # ##    -info['run']             ## #
+    # ##    -info['rec']             ## #
+    # ##    -info['acq']             ## #
+    # ##    -info['dir']             ## #
+    # ##                             ## #
+    # ##  Remember that they are     ## #
+    # ##  dictionary keys            ## #
+    # ##  See example below          ## #
+    # ################################# #
+
+    if physinfo == 'origfilename1':
+        info['task'] = 'newname1'
+    elif physinfo == 'origfilename2':
+        info['task'] = 'newname2'
+        info['run'] = 'runnum'
+    elif physinfo == 'BH4':
+        info['task'] = 'breathhold'
+    elif fnmatch.fnmatchcase(physinfo, 'MOTOR?'):
+        info['task'] = 'motor'
+    elif fnmatch.fnmatchcase(physinfo, 'LOCALIZER?'):
+        info['task'] = 'pinel'
+    elif fnmatch.fnmatchcase(physinfo, 'SIMON?'):
+        info['task'] = 'simon'
+    elif physinfo == 'RS1':
+        info['task'] = 'rest'
+        info['run'] = '01'
+    elif physinfo == 'RS2':
+        info['task'] = 'rest'
+        info['run'] = '02'
+    elif physinfo == 'RS3':
+        info['task'] = 'rest'
+        info['run'] = '03'
+    elif physinfo == 'RS4':
+        info['task'] = 'rest'
+        info['run'] = '04'
+
+    # ############################## #
+    # ## Don't modify below this! ## #
+    # ############################## #
+    return info
diff --git a/phys2bids/heuristics/heur_ex.py b/phys2bids/heuristics/heur_ex.py
deleted file mode 100644
index 57c8721..0000000
--- a/phys2bids/heuristics/heur_ex.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# import sys
-# import fnmatch
-
-
-def heur(physinfo, name, task='', acq='', direct='', rec='', run=''):
-    # ############################## #
-    # ##       Modify here!       ## #
-    # ##                          ## #
-    # ##  Possible variables are: ## #
-    # ##    -task (required)      ## #
-    # ##    -run                  ## #
-    # ##    -rec                  ## #
-    # ##    -acq                  ## #
-    # ##    -direct               ## #
-    # ##                          ## #
-    # ##                          ## #
-    # ##    See example below     ## #
-    # ############################## #
-
-    if physinfo == 'origfilename1':
-        task = 'newname1'
-    elif physinfo == 'origfilename2':
-        task = 'newname2'
-        run = 'runnum'
-    elif physinfo == 'Example':
-        task = 'rest'
-        run = '01'
-        acq = 'resp'
-        # ############################## #
-        # ## Don't modify below this! ## #
-        # ############################## #
-    else:
-        # #!# Transform sys.exit in debug warnings or raiseexceptions!
-        # #!# Make all of the above a dictionary
-        raise Warning(f'The heuristic {__file__} could not deal with {physinfo}')
-
-    if not task:
-        raise KeyError(f'No "task" attribute found')
-
-    name = name + '_task-' + task
-
-    # filename spec: sub-<label>[_ses-<label>]_task-<label>[_acq-<label>] ...
-    #                ... [_ce-<label>][_dir-<label>][_rec-<label>] ...
-    #                ... [_run-<index>][_recording-<label>]_physio
-    if acq:
-        name = name + '_acq-' + acq
-
-    if direct:
-        name = name + '_dir-' + direct
-
-    if rec:
-        name = name + '_rec-' + rec
-
-    if run:
-        name = name + '_run-' + run
-
-    return name
diff --git a/phys2bids/heuristics/heur_tutorial.py b/phys2bids/heuristics/heur_tutorial.py
index e4fad14..eebc082 100644
--- a/phys2bids/heuristics/heur_tutorial.py
+++ b/phys2bids/heuristics/heur_tutorial.py
@@ -1,55 +1,71 @@
 import fnmatch
 
 
-def heur(physinfo, name, task='', acq='', direct='', rec='', run=''):
-    # ############################## #
-    # ##       Modify here!       ## #
-    # ##                          ## #
-    # ##  Possible variables are: ## #
-    # ##    -task (required)      ## #
-    # ##    -run                  ## #
-    # ##    -rec                  ## #
-    # ##    -acq                  ## #
-    # ##    -direct               ## #
-    # ##                          ## #
-    # ##                          ## #
-    # ##    See example below     ## #
-    # ############################## #
+def heur(physinfo):
+    """
+    Set of if .. elif statements to fill BIDS names.
+
+    It requires the user (you!) to adjust it accordingly!
+    It needs an ``if`` or ``elif`` statement for each file that
+    needs to be processed.
+    The statement will test if the ``physinfo``:
+        - is similar to a string (first case), or
+        - exactly matches a string (second case).
+
+    Parameters
+    ----------
+    physinfo: str
+        Name of an input file that should be bidsified (See Notes)
+
+    Returns
+    -------
+    info: dictionary of str
+        Dictionary containing BIDS keys
+
+    Notes
+    -----
+    The `if ..` structure should always be similar to
+    ```
+    if physinfo == 'somepattern':
+        info['var'] = 'somethingelse'
+    ```
+    or, in case it's a partial match
+    ```
+    if fnmatch.fnmatchcase(physinfo, '*somepattern?'):
+        info['var'] = 'somethingelse'
+    ```
+    Where:
+        - `physinfo` and `info` are dedicated keywords,
+        - 'somepattern' is the name of the file,
+        - 'var' is a bids key in the list below
+        - 'somethingelse' is the value of the key
+    """
+    info = {}
+    # ################################# #
+    # ##        Modify here!         ## #
+    # ##                             ## #
+    # ##  Possible variables are:    ## #
+    # ##    -info['task'] (required) ## #
+    # ##    -info['run']             ## #
+    # ##    -info['rec']             ## #
+    # ##    -info['acq']             ## #
+    # ##    -info['dir']             ## #
+    # ##                             ## #
+    # ##  Remember that they are     ## #
+    # ##  dictionary keys            ## #
+    # ##  See example below          ## #
+    # ################################# #
 
     if fnmatch.fnmatchcase(physinfo, '*tutorial*'):
-        task = 'test'
-        run = '00'
-        rec = 'labchart'
+        info['task'] = 'test'
+        info['run'] = '01'
+        info['rec'] = 'labchart'
     elif physinfo == 'Example':
-        task = 'rest'
-        run = '01'
-        acq = 'resp'
-        # ############################## #
-        # ## Don't modify below this! ## #
-        # ############################## #
-    else:
-        # #!# Transform sys.exit in debug warnings or raiseexceptions!
-        # #!# Make all of the above a dictionary
-        raise Warning(f'The heuristic {__file__} could not deal with {physinfo}')
-
-    if not task:
-        raise KeyError(f'No "task" attribute found')
-
-    name = name + '_task-' + task
-
-    # filename spec: sub-<label>[_ses-<label>]_task-<label>[_acq-<label>] ...
-    #                ... [_ce-<label>][_dir-<label>][_rec-<label>] ...
-    #                ... [_run-<index>][_recording-<label>]_physio
-    if acq:
-        name = name + '_acq-' + acq
-
-    if direct:
-        name = name + '_dir-' + direct
-
-    if rec:
-        name = name + '_rec-' + rec
-
-    if run:
-        name = name + '_run-' + run
-
-    return name
+        info['task'] = 'rest'
+        info['run'] = '01'
+        info['acq'] = 'resp'
+
+    # ############################## #
+    # ## Don't modify below this! ## #
+    # ############################## #
+    return info
diff --git a/phys2bids/phys2bids.py b/phys2bids/phys2bids.py
index a127bc4..9169d7f 100644
--- a/phys2bids/phys2bids.py
+++ b/phys2bids/phys2bids.py
@@ -1,8 +1,7 @@
 #!/usr/bin/env python3
 
 """
-Phys2bids is a python3 library meant to set physiological files in BIDS
-standard.
+Phys2bids is a python3 library meant to set physiological files in BIDS standard.
 
 It was born for Acqknowledge files (BIOPAC), and at the moment it supports
 ``.acq`` files and ``.txt`` files obtained by labchart (ADInstruments).
@@ -44,7 +43,7 @@ LGR = logging.getLogger(__name__)
 
 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
     ----------
@@ -85,7 +84,7 @@ def print_summary(filename, ntp_expected, ntp_found, samp_freq, time_offset, out
 
 def print_json(outfile, samp_freq, time_offset, ch_name):
     """
-    Prints the json required by BIDS format.
+    Print the json required by BIDS format.
 
     Parameters
     ----------
@@ -112,10 +111,8 @@ def print_json(outfile, samp_freq, time_offset, ch_name):
 
 
 def use_heuristic(heur_file, sub, ses, filename, outdir, record_label=''):
-    utils.check_file_exists(heur_file)
     """
-    Import the heuristic file specified by the user and uses its output
-    to rename the file.
+    Import and use the heuristic specified by the user to rename the file.
 
     Parameters
     ----------
@@ -137,39 +134,56 @@ def use_heuristic(heur_file, sub, ses, filename, outdir, record_label=''):
     -------
     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)
 
-    if sub[:4] != 'sub-':
-        name = f'sub-{sub}'
-    else:
-        name = sub
+    # Initialise a dictionary of bids_keys that has already "recording"
+    bids_keys = {'sub': '', 'ses': '', 'task': '', 'acq': '', 'ce': '',
+                 'dir': '', 'rec': '', 'run': '', 'recording': record_label}
 
-    fldr = os.path.join(outdir, name)
+    # 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[:4] != 'ses-':
-            ses = f'ses-{ses}'
-
-        fldr = os.path.join(fldr, ses)
-        name = f'{name}_{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(f'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)
 
-    cwd = os.getcwd()
-    os.chdir(outdir)
-
-    heur = utils.load_heuristic(heur_file)
-    name = heur.heur(Path(filename).stem, name)
-
-    recording = ''
-    if record_label:
-        recording = f'_recording-{record_label}'
-
-    heurpath = os.path.join(fldr, f'{name}{recording}_physio')
-    # for ext in ['.tsv.gz', '.json', '.log']:
-    #     move_file(outfile, heurpath, ext)
-    os.chdir(cwd)
+    heurpath = os.path.join(fldr, f'{name}physio')
 
     return heurpath
 
@@ -179,6 +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.
+
     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.


Test patch:
diff --git a/phys2bids/heuristics/heur_test_acq.py b/phys2bids/heuristics/heur_test_acq.py
index eb8ba67..9f553dc 100644
--- a/phys2bids/heuristics/heur_test_acq.py
+++ b/phys2bids/heuristics/heur_test_acq.py
@@ -1,55 +1,71 @@
 import fnmatch
 
 
-def heur(physinfo, name, task='', acq='', direct='', rec='', run=''):
-    # ############################## #
-    # ##       Modify here!       ## #
-    # ##                          ## #
-    # ##  Possible variables are: ## #
-    # ##    -task (required)      ## #
-    # ##    -run                  ## #
-    # ##    -rec                  ## #
-    # ##    -acq                  ## #
-    # ##    -direct               ## #
-    # ##                          ## #
-    # ##                          ## #
-    # ##    See example below     ## #
-    # ############################## #
+def heur(physinfo):
+    """
+    Set of if .. elif statements to fill BIDS names.
+
+    It requires the user (you!) to adjust it accordingly!
+    It needs an ``if`` or ``elif`` statement for each file that
+    needs to be processed.
+    The statement will test if the ``physinfo``:
+        - is similar to a string (first case), or
+        - exactly matches a string (second case).
+
+    Parameters
+    ----------
+    physinfo: str
+        Name of an input file that should be bidsified (See Notes)
+
+    Returns
+    -------
+    info: dictionary of str
+        Dictionary containing BIDS keys
+
+    Notes
+    -----
+    The `if ..` structure should always be similar to
+    ```
+    if physinfo == 'somepattern':
+        info['var'] = 'somethingelse'
+    ```
+    or, in case it's a partial match
+    ```
+    if fnmatch.fnmatchcase(physinfo, '*somepattern?'):
+        info['var'] = 'somethingelse'
+    ```
+    Where:
+        - `physinfo` and `info` are dedicated keywords,
+        - 'somepattern' is the name of the file,
+        - 'var' is a bids key in the list below
+        - 'somethingelse' is the value of the key
+    """
+    info = {}
+    # ################################# #
+    # ##        Modify here!         ## #
+    # ##                             ## #
+    # ##  Possible variables are:    ## #
+    # ##    -info['task'] (required) ## #
+    # ##    -info['run']             ## #
+    # ##    -info['rec']             ## #
+    # ##    -info['acq']             ## #
+    # ##    -info['dir']             ## #
+    # ##                             ## #
+    # ##  Remember that they are     ## #
+    # ##  dictionary keys            ## #
+    # ##  See example below          ## #
+    # ################################# #
 
     if fnmatch.fnmatchcase(physinfo, '*samefreq*'):
-        task = 'test'
-        run = '00'
-        rec = 'biopac'
+        info['task'] = 'test'
+        info['run'] = '01'
+        info['rec'] = 'biopac'
     elif physinfo == 'Example':
-        task = 'rest'
-        run = '01'
-        acq = 'resp'
-        # ############################## #
-        # ## Don't modify below this! ## #
-        # ############################## #
-    else:
-        # #!# Transform sys.exit in debug warnings or raiseexceptions!
-        # #!# Make all of the above a dictionary
-        raise Warning(f'The heuristic {__file__} could not deal with {physinfo}')
-
-    if not task:
-        raise KeyError(f'No "task" attribute found')
-
-    name = name + '_task-' + task
-
-    # filename spec: sub-<label>[_ses-<label>]_task-<label>[_acq-<label>] ...
-    #                ... [_ce-<label>][_dir-<label>][_rec-<label>] ...
-    #                ... [_run-<index>][_recording-<label>]_physio
-    if acq:
-        name = name + '_acq-' + acq
-
-    if direct:
-        name = name + '_dir-' + direct
-
-    if rec:
-        name = name + '_rec-' + rec
-
-    if run:
-        name = name + '_run-' + run
-
-    return name
+        info['task'] = 'rest'
+        info['run'] = '01'
+        info['acq'] = 'resp'
+
+    # ############################## #
+    # ## Don't modify below this! ## #
+    # ############################## #
+    return info
diff --git a/phys2bids/tests/conftest.py b/phys2bids/tests/conftest.py
index 9d252c8..16d6696 100644
--- a/phys2bids/tests/conftest.py
+++ b/phys2bids/tests/conftest.py
@@ -76,5 +76,5 @@ def multifreq_lab_file(testpath):
 
 @pytest.fixture
 def notime_lab_file(testpath):
-    return fetch_file('u5dq8', testpath,
+    return fetch_file('5829m', testpath,
                       'Test2_trigger_CO2_O2_pulse_1000Hz_534TRs_no_time.txt')
diff --git a/phys2bids/tests/test_integration.py b/phys2bids/tests/test_integration.py
index bf01e96..232e8a0 100644
--- a/phys2bids/tests/test_integration.py
+++ b/phys2bids/tests/test_integration.py
@@ -250,12 +250,12 @@ def test_integration_heuristic():
     test_path_output = os.path.join(test_path, 'sub-006/ses-01/func')
 
     # Check that files are generated
-    base_filename = 'sub-006_ses-01_task-test_rec-labchart_run-00_physio'
+    base_filename = 'sub-006_ses-01_task-test_rec-labchart_run-01_physio'
     for suffix in ['.log', '.json', '.tsv.gz']:
         assert os.path.isfile(os.path.join(test_path_output, base_filename + suffix))
 
     # Read log file (note that this file is not the logger file)
-    log_filename = 'sub-006_ses-01_task-test_rec-labchart_run-00_physio.log'
+    log_filename = 'sub-006_ses-01_task-test_rec-labchart_run-01_physio.log'
     with open(os.path.join(test_path_output, log_filename)) as log_info:
         log_info = log_info.readlines()
 
@@ -271,7 +271,7 @@ def test_integration_heuristic():
     assert check_string(log_info, 'first trigger', 'Time 0', is_num=False)
 
     # Checks json file
-    json_filename = 'sub-006_ses-01_task-test_rec-labchart_run-00_physio.json'
+    json_filename = 'sub-006_ses-01_task-test_rec-labchart_run-01_physio.json'
     with open(os.path.join(test_path_output, json_filename)) as json_file:
         json_data = json.load(json_file)
 
diff --git a/phys2bids/tests/test_phys2bids.py b/phys2bids/tests/test_phys2bids.py
index 7a16b0d..c1b74da 100644
--- a/phys2bids/tests/test_phys2bids.py
+++ b/phys2bids/tests/test_phys2bids.py
@@ -6,6 +6,8 @@ import json
 import os
 from pkg_resources import resource_filename
 
+import pytest
+
 from phys2bids import phys2bids
 
 
@@ -44,12 +46,12 @@ def test_print_json(tmpdir):
     assert test_json_data == loaded_data
 
 
-def test_use_heuristic(tmpdir):
+@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_sub = 'SBJ01'
-    test_ses = 'S05'
     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)
@@ -59,8 +61,20 @@ def test_use_heuristic(tmpdir):
     heur_path = phys2bids.use_heuristic(test_full_heur_path, test_sub, test_ses,
                                         test_full_input_path, test_outdir, test_record_label)
 
-    test_result = ('/sub-SBJ01/ses-S05/func/sub-SBJ01_ses-S05_task-test_rec'
-                   '-biopac_run-00_recording-test_physio')
-    test_result = str(tmpdir) + test_result
+    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 test_result == str(heur_path)