""" Utility functions. """
import re
import os
from pathlib import Path
[docs]
def listify(obj):
''' Wraps all non-list or tuple objects in a list; provides a simple way
to accept flexible arguments. '''
return obj if isinstance(obj, (list, tuple, type(None))) else [obj]
[docs]
def matches_entities(obj, entities, strict=False):
''' Checks whether an object's entities match the input. '''
if strict and set(obj.entities.keys()) != set(entities.keys()):
return False
comm_ents = list(set(obj.entities.keys()) & set(entities.keys()))
for k in comm_ents:
current = obj.entities[k]
target = entities[k]
if isinstance(target, (list, tuple)):
if current not in target:
return False
elif current != target:
return False
return True
def natural_sort(l, field=None):
'''
based on snippet found at https://stackoverflow.com/a/4836734/2445984
'''
convert = lambda text: int(text) if text.isdigit() else text.lower()
def alphanum_key(key):
if field is not None:
key = getattr(key, field)
if not isinstance(key, str):
key = str(key)
return [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(l, key=alphanum_key)
[docs]
def convert_JSON(j):
""" Recursively convert CamelCase keys to snake_case.
From: https://stackoverflow.com/questions/17156078/
converting-identifier-naming-between-camelcase-and-
underscores-during-json-seria
"""
def camel_to_snake(s):
a = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))')
return a.sub(r'_\1', s).lower()
def convertArray(a):
newArr = []
for i in a:
if isinstance(i, list):
newArr.append(convertArray(i))
elif isinstance(i, dict):
newArr.append(convert_JSON(i))
else:
newArr.append(i)
return newArr
out = {}
for k, value in j.items():
newK = camel_to_snake(k)
# Replace transformation uses a dict, so skip lower-casing
if isinstance(value, dict) and k != 'Replace':
out[newK] = convert_JSON(value)
elif isinstance(value, list):
out[newK] = convertArray(value)
else:
out[newK] = value
return out
def splitext(path):
"""splitext for paths with directories that may contain dots.
From https://stackoverflow.com/questions/5930036/separating-file-extensions-using-python-os-path-module"""
li = []
path_without_extensions = os.path.join(os.path.dirname(path),
os.path.basename(path).split(os.extsep)[0])
extensions = os.path.basename(path).split(os.extsep)[1:]
li.append(path_without_extensions)
# li.append(extensions) if you want extensions in another list inside the list that is returned.
li.extend(extensions)
return li
[docs]
def make_bidsfile(filename):
"""Create a BIDSFile instance of the appropriate class. """
from .layout import models
# Extract all extensions from filename (a.tar.gz -> .tar.gz, not just .gz)
ext = ''.join(Path(filename).suffixes)
if ext.endswith(('.nii', '.nii.gz', '.gii')):
cls = 'BIDSImageFile'
elif ext in ['.tsv', '.tsv.gz']:
cls = 'BIDSDataFile'
elif ext == '.json':
cls = 'BIDSJSONFile'
else:
cls = 'BIDSFile'
Cls = getattr(models, cls)
return Cls(filename)
def collect_associated_files(layout, files, extra_entities=()):
"""Collect and group BIDSFiles with multiple files per acquisition.
Parameters
----------
layout
files : list of BIDSFile
extra_entities
Returns
-------
collected_files : list of list of BIDSFile
"""
MULTICONTRAST_ENTITIES = ['echo', 'part', 'ch', 'direction']
MULTICONTRAST_SUFFIXES = [
('bold', 'phase'),
('phase1', 'phase2', 'phasediff', 'magnitude1', 'magnitude2'),
]
if len(extra_entities):
MULTICONTRAST_ENTITIES += extra_entities
collected_files = []
for f in files:
if len(collected_files) and any(f in filegroup for filegroup in collected_files):
continue
ents = f.get_entities()
ents = {k: v for k, v in ents.items() if k not in MULTICONTRAST_ENTITIES}
# Group files with differing multi-contrast entity values, but same
# everything else.
all_suffixes = ents['suffix']
for mcs in MULTICONTRAST_SUFFIXES:
if ents['suffix'] in mcs:
all_suffixes = mcs
break
ents.pop('suffix')
associated_files = layout.get(suffix=all_suffixes, **ents)
collected_files.append(associated_files)
return collected_files
def validate_multiple(val, retval=None):
"""Any click.Option with the multiple flag will return an empty tuple if not set.
This helper method converts empty tuples to a desired return value (default: None).
This helper method selects the first item in single-item tuples.
"""
assert isinstance(val, tuple)
if val == tuple():
return retval
if len(val) == 1:
return val[0]
return val