Skip to content

Commit 973bb22

Browse files
committed
Minor tweaks
1 parent 2fb93d7 commit 973bb22

File tree

9 files changed

+48
-42
lines changed

9 files changed

+48
-42
lines changed

bidscoin/bids.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -985,9 +985,9 @@ def load_bidsmap(yamlfile: Path=Path(), folder: Path=templatefolder, plugins:Ite
985985
for dataformat in bidsmap:
986986
if dataformat in ('$schema', 'Options'): continue
987987
bidsmap[dataformat]['session'] = bidsmap[dataformat]['session'] or '' # Session-less data repositories
988-
for datatype in bidsmap[dataformat]:
989-
if not isinstance(bidsmap[dataformat][datatype], list): continue # E.g. 'subject', 'session' and empty datatypes
990-
for index, run in enumerate(bidsmap[dataformat][datatype]):
988+
for datatype in bidsmap[dataformat] or []:
989+
if datatype in ('subject', 'session'): continue
990+
for index, run in enumerate(bidsmap[dataformat][datatype] or []):
991991

992992
# Add missing provenance info
993993
if not run.get('provenance'):
@@ -1045,12 +1045,11 @@ def save_bidsmap(filename: Path, bidsmap: Bidsmap) -> None:
10451045
:return:
10461046
"""
10471047

1048-
# Remove the added DataSource object
1048+
# Remove the added DataSource objects
10491049
bidsmap = copy.deepcopy(bidsmap)
10501050
for dataformat in bidsmap:
10511051
if dataformat in ('$schema', 'Options'): continue
1052-
if not bidsmap[dataformat]: continue
1053-
for datatype in bidsmap[dataformat]:
1052+
for datatype in bidsmap[dataformat] or []:
10541053
if not isinstance(bidsmap[dataformat][datatype], list): continue # E.g. 'subject' and 'session'
10551054
for run in bidsmap[dataformat][datatype]:
10561055
run.pop('datasource', None)
@@ -1091,8 +1090,7 @@ def validate_bidsmap(bidsmap: Bidsmap, level: int=1) -> bool:
10911090
LOGGER.info(f"bids-validator {bids_validator.__version__} test results (* = in .bidsignore):")
10921091
for dataformat in bidsmap:
10931092
if dataformat in ('$schema', 'Options'): continue
1094-
if not bidsmap[dataformat]: continue
1095-
for datatype in bidsmap[dataformat]:
1093+
for datatype in bidsmap[dataformat] or []:
10961094
if not isinstance(bidsmap[dataformat][datatype], list): continue # E.g. 'subject' and 'session'
10971095
for run in bidsmap[dataformat][datatype]:
10981096
bidsname = get_bidsname(f"sub-{sanitize(dataformat)}", '', run, False)
@@ -1135,8 +1133,7 @@ def check_bidsmap(bidsmap: Bidsmap, checks: Tuple[bool, bool, bool]=(True, True,
11351133
LOGGER.info('Checking the bidsmap run-items:')
11361134
for dataformat in bidsmap:
11371135
if dataformat in ('$schema', 'Options'): continue # TODO: Check Options
1138-
if not bidsmap[dataformat]: continue
1139-
for datatype in bidsmap[dataformat]:
1136+
for datatype in bidsmap[dataformat] or []:
11401137
if not isinstance(bidsmap[dataformat][datatype], list): continue # E.g. 'subject' and 'session'
11411138
if datatype in bidsmap['Options']['bidscoin']['ignoretypes']: continue # E.g. 'exclude'
11421139
if check_ignore(datatype, bidsmap['Options']['bidscoin']['bidsignore']): continue
@@ -1178,7 +1175,7 @@ def check_template(bidsmap: Bidsmap) -> bool:
11781175
LOGGER.verbose('Checking the template bidsmap datatypes:')
11791176
for dataformat in bidsmap:
11801177
if dataformat in ('$schema', 'Options'): continue
1181-
for datatype in bidsmap[dataformat]:
1178+
for datatype in bidsmap[dataformat] or []:
11821179
if not isinstance(bidsmap[dataformat][datatype], list): continue # Skip datatype = 'subject'/'session'
11831180
if not (datatype in bidsdatatypesdef or datatype in ignoretypes or check_ignore(datatype, bidsignore)):
11841181
LOGGER.warning(f"Invalid {dataformat} datatype: '{datatype}' (you may want to add it to the 'bidsignore' list)")
@@ -1427,7 +1424,7 @@ def create_run(datasource: DataSource=None, bidsmap: Bidsmap=None) -> Run:
14271424
return Run(dict(provenance = str(datasource.path),
14281425
properties = {'filepath':'', 'filename':'', 'filesize':'', 'nrfiles':None},
14291426
attributes = {},
1430-
bids = {},
1427+
bids = {'suffix':''},
14311428
meta = {},
14321429
datasource = datasource))
14331430

@@ -1518,21 +1515,21 @@ def find_run(bidsmap: Bidsmap, provenance: str, dataformat: str='', datatype: st
15181515
return Run({})
15191516

15201517

1521-
def delete_run(bidsmap: Bidsmap, provenance: Union[Run, str], datatype: str= '', dataformat: str='') -> None:
1518+
def delete_run(bidsmap: Bidsmap, provenance: Union[Run, str], datatype: str= '', dataformat: str='') -> bool:
15221519
"""
15231520
Delete the first matching run from the BIDS map
15241521
15251522
:param bidsmap: Full bidsmap data structure, with all options, BIDS labels and attributes, etc.
15261523
:param provenance: The provenance identifier of/or the run-item that is deleted
15271524
:param datatype: The datatype that of the deleted run_item (can be different from run_item['datasource']), e.g. 'anat'
15281525
:param dataformat: The dataformat section in the bidsmap in which the run is deleted, e.g. 'DICOM'
1529-
:return:
1526+
:return: True if successful, False otherwise
15301527
"""
15311528

15321529
if isinstance(provenance, str):
15331530
run_item = find_run(bidsmap, provenance, dataformat)
15341531
if not run_item:
1535-
return
1532+
return False
15361533
else:
15371534
run_item = provenance
15381535
provenance = run_item['provenance']
@@ -1545,17 +1542,19 @@ def delete_run(bidsmap: Bidsmap, provenance: Union[Run, str], datatype: str= '',
15451542
for index, run in enumerate(bidsmap[dataformat].get(datatype,[])):
15461543
if Path(run['provenance']) == Path(provenance):
15471544
del bidsmap[dataformat][datatype][index]
1548-
return
1545+
return True
15491546

15501547
LOGGER.error(f"Could not find (and delete) this [{dataformat}][{datatype}] run: '{provenance}")
1548+
return False
15511549

15521550

1553-
def append_run(bidsmap: Bidsmap, run: Run) -> None:
1551+
def insert_run(bidsmap: Bidsmap, run: Run, position: int=None) -> None:
15541552
"""
1555-
Append a cleaned-up run to the BIDS map
1553+
Inserts a cleaned-up run to the BIDS map
15561554
15571555
:param bidsmap: Full bidsmap data structure, with all options, BIDS labels and attributes, etc.
15581556
:param run: The run (listitem) that is appended to the datatype
1557+
:param position: The position at which the run is inserted. The run is appended at the end if position is None
15591558
:return:
15601559
"""
15611560

@@ -1574,7 +1573,7 @@ def append_run(bidsmap: Bidsmap, run: Run) -> None:
15741573
if not bidsmap.get(dataformat).get(datatype):
15751574
bidsmap[dataformat][datatype] = [run]
15761575
else:
1577-
bidsmap[dataformat][datatype].append(run)
1576+
bidsmap[dataformat][datatype].insert(len(bidsmap[dataformat][datatype]) if position is None else position, run)
15781577

15791578

15801579
def update_bidsmap(bidsmap: Bidsmap, source_datatype: str, run: Run) -> None:
@@ -1610,7 +1609,7 @@ def update_bidsmap(bidsmap: Bidsmap, source_datatype: str, run: Run) -> None:
16101609
delete_run(bidsmap, run, source_datatype)
16111610

16121611
# Append the (cleaned-up) target run
1613-
append_run(bidsmap, run)
1612+
insert_run(bidsmap, run)
16141613

16151614
else:
16161615
for index, run_ in enumerate(bidsmap[dataformat][run_datatype]):

bidscoin/bidscoiner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,11 @@ def bidscoiner(sourcefolder: str, bidsfolder: str, participant: list=(), force:
270270
subid, sesid = datasource.subid_sesid(subid, sesid or '')
271271
bidssession = bidsfolder/subid/sesid # TODO: Support DICOMDIR with multiple subjects (as in PYDICOMDIR)
272272
if not force and bidssession.is_dir():
273-
datatypes = []
273+
datatypes = set()
274274
for dataformat in dataformats:
275275
for datatype in lsdirs(bidssession): # See what datatypes we already have in the bids session-folder
276276
if list(datatype.iterdir()) and bidsmap[dataformat].get(datatype.name): # See if we are going to add data for this datatype
277-
datatypes.append(datatype.name)
277+
datatypes.add(datatype.name)
278278
if datatypes:
279279
LOGGER.info(f">>> Skipping processed session: {bidssession} already has {datatypes} data (you can carefully use the -f option to overrule)")
280280
continue

bidscoin/bidsmapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def bidsmapper(sourcefolder: str, bidsfolder: str, bidsmap: str, template: str,
9191
unzip = bidsmap_new['Options']['bidscoin'].get('unzip','')
9292
for dataformat in bidsmap_new:
9393
if dataformat in ('$schema', 'Options'): continue
94-
for datatype in bidsmap_new[dataformat]:
94+
for datatype in bidsmap_new[dataformat] or []:
9595
if datatype not in ('subject', 'session'):
9696
bidsmap_new[dataformat][datatype] = []
9797

@@ -248,7 +248,7 @@ def main():
248248

249249
trackusage('bidsmapper')
250250
try:
251-
bidsmapper(**args(args))
251+
bidsmapper(**vars(args))
252252

253253
except Exception:
254254
trackusage('bidsmapper_exception')

bidscoin/heuristics/schema.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3-
"$id": "http://bidscoin.templates/schema.json",
3+
"$id": "file://bidscoin/heuristics/schema.json",
44
"title": "Schema for validating template bidsmaps",
55
"type": "object",
66
"required": ["Options"],
@@ -53,8 +53,14 @@
5353
"required": ["subject", "session"],
5454

5555
"properties": {
56-
"subject": { "type": "string" },
57-
"session": { "type": ["string", "integer", "null"] }
56+
"subject": {
57+
"type": "string",
58+
"description": "The participant label (typically a dynamic value)"
59+
},
60+
"session": {
61+
"type": ["string", "integer", "null"],
62+
"description": "The session label (typically a dynamic value)"
63+
}
5864
},
5965

6066
"additionalProperties": {
@@ -63,7 +69,7 @@
6369
"items": {
6470
"description": "Run-item (containing the bidsmappings)",
6571
"type": "object",
66-
"required": ["bids"],
72+
"required": ["provenance", "bids"],
6773
"properties": {
6874
"provenance": {
6975
"description": "The fullpath name of the data source. Serves also as a unique identifier to find a run-item in the bidsmap",

bidscoin/plugins/dcm2niix2bids.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def bidsmapper_plugin(session: Path, bidsmap_new: Bidsmap, bidsmap_old: Bidsmap,
196196
run['datasource'].path = targetfile
197197

198198
# Copy the filled-in run over to the new bidsmap
199-
bids.append_run(bidsmap_new, run)
199+
bids.insert_run(bidsmap_new, run)
200200

201201
else:
202202
LOGGER.bcdebug(f"Existing/duplicate '{datasource.datatype}' {dataformat} sample: {sourcefile}")
@@ -372,7 +372,7 @@ def bidscoiner_plugin(session: Path, bidsmap: Bidsmap, bidsses: Path) -> Union[N
372372
dcm2niixpostfixes = ('_c', '_i', '_Eq', '_real', '_imaginary', '_MoCo', '_t', '_Tilt', '_e', '_ph', '_ADC', '_fieldmaphz') #_c%d, _e%d and _ph (and any combination of these in that order) are for multi-coil data, multi-echo data and phase data
373373
dcm2niixfiles = sorted(set([dcm2niixfile for dcm2niixpostfix in dcm2niixpostfixes for dcm2niixfile in outfolder.glob(f"{bidsname}*{dcm2niixpostfix}*.nii*")]))
374374
dcm2niixfiles = [dcm2niixfile for dcm2niixfile in dcm2niixfiles if not (re.match(r'sub-.*_echo-[0-9]*\.nii', dcm2niixfile.name) or
375-
re.match(r'sub-.*_phase(diff|[12])\.nii', dcm2niixfile.name))] # Skip false-positive (-> glob) dcm2niixfiles, e.g. postfix = 'echo-1' (see Github issue #232)
375+
re.match(r'sub-.*_phase(diff|[12])\.nii', dcm2niixfile.name))] # Skip false-positive (-> glob) dcm2niixfiles, e.g. postfix = 'echo-1' (see GitHub issue #232)
376376

377377
# Rename all dcm2niix files that got additional postfixes (i.e. store the postfixes in the bidsname)
378378
for dcm2niixfile in dcm2niixfiles:

bidscoin/plugins/nibabel2bids.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def bidsmapper_plugin(session: Path, bidsmap_new: Bidsmap, bidsmap_old: Bidsmap,
152152
run['datasource'].path = targetfile
153153

154154
# Copy the filled-in run over to the new bidsmap
155-
bids.append_run(bidsmap_new, run)
155+
bids.insert_run(bidsmap_new, run)
156156

157157
else:
158158
LOGGER.bcdebug(f"Existing/duplicate '{datasource.datatype}' {datasource.dataformat} sample: {sourcefile}")

bidscoin/plugins/spec2nii2bids.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def bidsmapper_plugin(session: Path, bidsmap_new: Bidsmap, bidsmap_old: Bidsmap,
157157
run['datasource'].path = targetfile
158158

159159
# Copy the filled-in run over to the new bidsmap
160-
bids.append_run(bidsmap_new, run)
160+
bids.insert_run(bidsmap_new, run)
161161

162162
else:
163163
LOGGER.bcdebug(f"Existing/duplicate '{datasource.datatype}' {dataformat} sample: {sourcefile}")

docs/preparation.rst

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ Data organization
44
Supported source data structures
55
--------------------------------
66

7-
Out of the box, BIDScoin requires that the source data repository is organized according to a ``subject/[session]/data`` structure (the ``session`` subfolder is always optional). The ``data`` folder(s) can be structured in various ways (depending on the plugin and/or dataformat), as illustrated by the following examples:
7+
Out of the box, BIDScoin requires that the source data repository is organized according to a ``subject/[session]/data`` structure (the ``session`` subfolder is always optional). The ``data`` folder(s) can be structured in various ways (depending on the plugin and/or dataformat), as illustrated by in the sections below.
8+
9+
.. note::
10+
You can store your session data as zipped (``.zip``) or tarzipped (e.g. ``.tar.gz``) archive files. BIDScoin `workflow tools <./workflow.html>`__ will automatically unpack/unzip those archive files in a temporary folder and then process your session data from there. For flat/DICOMDIR data, BIDScoin tools (i.e. the bidsmapper and the bidscoiner) will automatically run `dicomsort <./utilities.html#dicomsort>`__ in a temporary folder to sort them in seriesfolders. Depending on the data and file system, repeatedly unzipping data in the workflow may come with a significant processing speed penalty. BIDScoin plugins will skip (Linux-style hidden) files and folders of which the name starts with a ``.`` (dot) character.
811

912
1. A DICOM Series layout
1013
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -142,12 +145,6 @@ The above layouts are supported by the (default) `dcm2niix2bids <./plugins.html#
142145
| [..]
143146
[..]
144147

145-
.. note::
146-
You can store your session data in any of the above data layouts as zipped (``.zip``) or tarzipped (e.g. ``.tar.gz``) archive files. BIDScoin `workflow tools <./workflow.html>`__ will automatically unpack/unzip those archive files in a temporary folder and then process your session data from there. For flat/DICOMDIR data, BIDScoin tools (i.e. the bidsmapper and the bidscoiner) will automatically run `dicomsort <./utilities.html#dicomsort>`__ in a temporary folder to sort them in seriesfolders. Depending on the data and file system, repeatedly unzipping data in the workflow may come with a significant processing speed penalty.
147-
148-
.. tip::
149-
BIDScoin plugins will typically skip (Linux-style hidden) files and folders of which the name starts with a ``.`` (dot) character. You can use this feature to flexibly omit subjects, sessions or runs from your bids repository, for instance when you restarted an MRI scan because something went wrong with the stimulus presentation and you don't want that data to be converted and enumerated as ``run-1``, ``run-2``.
150-
151148
Recommended data acquisition conventions
152149
----------------------------------------
153150

docs/troubleshooting.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Troubleshooting
33

44
Installation
55
------------
6-
A first step when encountering execution errors is to test whether your installation is working correctly. An easy way to test the working of various BIDScoin components is to run ``bidscoin -t`` in your terminal. Some commonly seen messages are:
6+
When encountering execution errors it is helpful to know whether your BIDScoin installation is working correctly. An easy way to test the working of its various components is to run ``bidscoin -t`` in your terminal. Some commonly reported issues are:
77

88
The "dcm2niix" command is not recognized
99
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -41,7 +41,7 @@ The fix comes from these resources:
4141

4242
Workflow
4343
--------
44-
The first step in troubleshooting is to look at the warnings and messages printed out in the terminal (they are also save to disk in the ``bidsfolder/code/bidscoin`` output folder). Make sure you are OK with the warnings (they are meaningful and not to be ignored) and do not continue with a next step until all errors are resolved.
44+
The first step in workflow troubleshooting is to look at the warnings and messages printed out in the terminal (they are also saved to disk in the ``bidsfolder/code/bidscoin`` output folder). Make sure you are OK with the warnings (they are meaningful and not to be ignored) and do not continue with a next step until all errors are resolved. Below you can find answers to some commonly reported issues.
4545

4646
My bidsmap is empty
4747
^^^^^^^^^^^^^^^^^^^
@@ -105,6 +105,10 @@ My source-files can no longer be found
105105
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
106106
You may get the warning "Cannot reliably change the data type and/or suffix because the source file '..' can no longer be found". This warning is generated when (1) your source data moved to a different location, or (2) your data is zipped or in DICOMDIR format. This warning can be ignored if you do not need to change the data type of your run-items anymore (in the bidseditor), because in that case BIDScoin may need access to the source data (to read new properties or attributes). To restore data access for (1), move the data to it's original location and for (2) use the ``--store`` option of bidsmapper to store local copies of the source data samples in the bids output folder.
107107

108+
Some acquisitions went wrong and need to be excluded
109+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
110+
BIDScoin plugins will skip (Linux-style hidden) files and folders of which the name starts with a ``.`` (dot) character. You can use this feature to flexibly omit subjects, sessions or runs from your bids repository, for instance when you restarted an MRI scan because something went wrong with the stimulus presentation and you don't want that data to be converted and enumerated as ``run-1``, ``run-2``.
111+
108112
I have duplicated field maps because of an interrupted session
109113
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
110114
It may happen that due to irregularities during data acquisition you had to reacquire your field-map for part of your data. In that case the ``IntendedFor`` and ``B0FieldIdentifier``/``B0FieldSource`` semantics become ambiguous. To handle this situation, you can use json sidecar files to extend the source attributes (see below) or use the limited ``IntendedFor`` search as described `here <./bidsmap.html#intendedfor>`__ and `here <https://github.com/Donders-Institute/bidscoin/issues/123>`__.
@@ -136,4 +140,4 @@ You can simply use the ``bidseditor`` to make changes to your bidsmap, delete al
136140
137141
More help
138142
---------
139-
If this guide does not help to solve your problem, then you can `search on github <https://github.com/Donders-Institute/bidscoin/issues?q=>`__ for open and/or closed issues to see if anyone else has encountered similar problems before. If not, feel free to help yourself and others by opening a new github issue.
143+
If this guide does not help to solve your problem, then you can `search on github <https://github.com/Donders-Institute/bidscoin/issues?q=>`__ for open and/or closed issues to see if anyone else has encountered similar problems before. If not, feel free to help yourself and others by opening a new github issue or post your question on `NeuroStars <https://neurostars.org/tag/bidscoin>`__ (tag: #bidscoin)

0 commit comments

Comments
 (0)