diff --git a/src/smripost_linc/utils/boilerplate.py b/src/smripost_linc/utils/boilerplate.py new file mode 100644 index 0000000..6763de0 --- /dev/null +++ b/src/smripost_linc/utils/boilerplate.py @@ -0,0 +1,45 @@ +"""Functions for generating boilerplate text.""" + + +def describe_atlases(atlases): + """Build a text description of the atlases that will be used.""" + from smripost_linc.utils.utils import list_to_str + + atlas_descriptions = { + 'Glasser': 'the Glasser atlas [@Glasser_2016]', + 'Gordon': 'the Gordon atlas [@Gordon_2014]', + 'Tian': 'the Tian subcortical atlas [@tian2020topographic]', + 'HCP': 'the HCP CIFTI subcortical atlas [@glasser2013minimal]', + 'MIDB': ( + 'the MIDB precision brain atlas derived from ABCD data and thresholded at 75% ' + 'probability [@hermosillo2022precision]' + ), + 'MyersLabonte': ( + 'the Myers-Labonte infant atlas thresholded at 50% probability [@myers2023functional]' + ), + } + + atlas_strings = [] + described_atlases = [] + atlases_4s = [atlas for atlas in atlases if str(atlas).startswith('4S')] + described_atlases += atlases_4s + if atlases_4s: + parcels = [int(str(atlas[2:-7])) for atlas in atlases_4s] + s = ( + 'the Schaefer Supplemented with Subcortical Structures (4S) atlas ' + '[@Schaefer_2017;@pauli2018high;@king2019functional;@najdenovska2018vivo;' + '@glasser2013minimal] ' + f'at {len(atlases_4s)} different resolutions ({list_to_str(parcels)} parcels)' + ) + atlas_strings.append(s) + + for k, v in atlas_descriptions.items(): + if k in atlases: + atlas_strings.append(v) + described_atlases.append(k) + + undescribed_atlases = [atlas for atlas in atlases if atlas not in described_atlases] + for atlas in undescribed_atlases: + atlas_strings.append(f'the {atlas} atlas') + + return list_to_str(atlas_strings) diff --git a/src/smripost_linc/utils/utils.py b/src/smripost_linc/utils/utils.py index 28bb67f..40085d0 100644 --- a/src/smripost_linc/utils/utils.py +++ b/src/smripost_linc/utils/utils.py @@ -227,3 +227,17 @@ def _convert_to_tsv(in_file): arr = np.loadtxt(in_file) np.savetxt(out_file, arr, delimiter='\t') return out_file + + +def list_to_str(lst): + """Convert a list to a pretty string.""" + if not lst: + raise ValueError('Zero-length list provided.') + + lst_str = [str(item) for item in lst] + if len(lst_str) == 1: + return lst_str[0] + elif len(lst_str) == 2: + return ' and '.join(lst_str) + else: + return f"{', '.join(lst_str[:-1])}, and {lst_str[-1]}" diff --git a/src/smripost_linc/workflows/parcellation.py b/src/smripost_linc/workflows/parcellation.py index d107db8..ae548f0 100644 --- a/src/smripost_linc/workflows/parcellation.py +++ b/src/smripost_linc/workflows/parcellation.py @@ -11,107 +11,6 @@ from smripost_linc.interfaces.bids import BIDSURI -def remove_non_alphabetic(input_string): - """Use regular expression to remove non-alphabetic characters.""" - import re - - clean_string = re.sub(r'[^a-zA-Z_0-9]', '', input_string.replace(' ', '_')) - return clean_string - - -def fake_neuroparc_from_nifti(nifti_file): - """Create a fake neuroparc JSON from a nifti file.""" - import nibabel as nb - import numpy as np - - img = nb.load(nifti_file) - unique_labels = np.unique(img.get_fdata().astype(np.int32)) - return {str(label): {} for label in unique_labels} - - -def fill_missing_parc(spec): - maxval = max(map(int, spec.keys())) - for key in range(maxval): - strkey = str(key) - if strkey not in spec: - spec[strkey] = {'label': 'Unknown'} - return spec - - -def ctab_from_neuroparc_json(neuroparc_json_file=None, atlas_nifti_file=None): - """Extract colors and labels from a neuroparc JSON file.""" - import json - - if neuroparc_json_file is not None: - with open(neuroparc_json_file) as jsonf: - initial_specs = json.load(jsonf) - if 'MetaData' in initial_specs: - del initial_specs['MetaData'] - else: - initial_specs = fake_neuroparc_from_nifti(atlas_nifti_file) - - parc_specs = fill_missing_parc(initial_specs) - # Get a mapping from ints to label names from neuroparc - int_to_label_map = {} - for key, value in parc_specs.items(): - if not key.isnumeric(): - continue - key = int(key) - label = value.get('label') or f'region{key:05d}' - int_to_label_map[key] = label - - int_to_label_map[0] = 'Unknown' - labels = [ - remove_non_alphabetic(int_to_label_map[key]) for key in sorted(int_to_label_map.keys()) - ] - colors = _create_colors(len(labels)) - return colors, labels - - -def create_annots(nifti_file, atlas, json_file=None): - """Create .annot files from a nifti file and a json file.""" - import nibabel as nb - import numpy as np - from neuromaps import transforms - - lh_gii, rh_gii = transforms.mni152_to_fsaverage( - nifti_file, - fsavg_density='164k', - method='nearest', - ) - colors, names = ctab_from_neuroparc_json( - neuroparc_json_file=json_file, - atlas_nifti_file=nifti_file, - ) - lh_annot = f'annots/lh.{atlas}.annot' - nb.freesurfer.write_annot( - lh_annot, - labels=lh_gii.agg_data().astype(np.int32), - ctab=colors, - names=names, - fill_ctab=True, - ) - rh_annot = f'annots/rh.{atlas}.annot' - nb.freesurfer.write_annot( - rh_annot, - labels=rh_gii.agg_data().astype(np.int32), - ctab=colors, - names=names, - fill_ctab=True, - ) - return lh_annot, rh_annot - - -def select_first(inlist): - """Select the first element of a list.""" - return inlist[0] - - -def select_second(inlist): - """Select the second element of a list.""" - return inlist[1] - - def init_load_atlases_wf(name='load_atlases_wf'): """Load atlases, warp them to fsnative, and convert them to annot files.