# -*- coding: utf-8 -*-
"""Functions for comparing data."""
import nibabel as nib
import numpy as np
from neuromaps import transforms
from neuromaps.datasets import ALIAS, DENSITIES
from neuromaps.images import load_gifti, load_nifti
_resampling_docs = dict(
resample_in="""\
src, trg : str or os.PathLike or niimg_like or nib.GiftiImage or tuple
Input data to be resampled
src_space, trg_space : str
Template space of input data
method : {'nearest', 'linear'}, optional
Method for resampling. Specify 'nearest' if `data` are label images.
Default: 'linear'\
""",
hemi="""\
hemi : {'L', 'R'}, optional
If `src` and `trg` are not tuples this specifies the hemisphere the data
represent. Default: None\
""",
resample_out="""\
src, trg : tuple-of-nib.GiftiImage
Resampled images\
"""
)
def downsample_only(src, trg, src_space, trg_space, method='linear', hemi=None): # noqa: D103
src_den, trg_den = transforms._estimate_density((src, trg), hemi)
src_num, trg_num = int(src_den[:-1]), int(trg_den[:-1])
src_space, trg_space = src_space.lower(), trg_space.lower()
if src_num >= trg_num: # resample to `trg`
func = getattr(transforms, f'{src_space}_to_{trg_space}')
src = func(src, trg_den, hemi=hemi, method=method)
elif src_num < trg_num: # resample to `src`
func = getattr(transforms, f'{trg_space}_to_{src_space}')
trg = func(trg, src_den, hemi=hemi, method=method)
return src, trg
downsample_only.__doc__ = """\
Resample `src` and `trg` to match such that neither is upsampled.
If density of `src` is greater than `trg` then `src` is resampled to
`trg`; otherwise, `trg` is resampled to `src`
Parameters
----------
{resample_in}
{hemi}
Returns
-------
{resample_out}
""".format(**_resampling_docs)
def transform_to_src(src, trg, src_space, trg_space, method='linear', hemi=None): # noqa: D103
src_den, trg_den = transforms._estimate_density((src, trg), hemi)
func = getattr(transforms, f'{trg_space.lower()}_to_{src_space.lower()}')
trg = func(trg, src_den, hemi=hemi, method=method)
return src, trg
transform_to_src.__doc__ = """\
Resample `trg` to match space and density of `src`.
Parameters
----------
{resample_in}
{hemi}
Returns
-------
{resample_out}
""".format(**_resampling_docs)
def transform_to_trg(src, trg, src_space, trg_space, hemi=None, method='linear'): # noqa: D103
src_den, trg_den = transforms._estimate_density((src, trg), hemi)
func = getattr(transforms, f'{src_space.lower()}_to_{trg_space.lower()}')
src = func(src, trg_den, hemi=hemi, method=method)
return src, trg
transform_to_trg.__doc__ = """\
Resample `trg` to match space and density of `src`.
Parameters
----------
{resample_in}
Returns
-------
{resample_out}
""".format(**_resampling_docs)
def transform_to_alt(src, trg, src_space, trg_space, method='linear', # noqa: D103
hemi=None, alt_space='fsaverage', alt_density='41k'):
src, _ = transform_to_trg(src, alt_density, src_space, alt_space,
hemi=hemi, method=method)
trg, _ = transform_to_trg(trg, alt_density, trg_space, alt_space,
hemi=hemi, method=method)
return src, trg
transform_to_alt.__doc__ = """\
Resample `src` and `trg` to `alt_space` and `alt_density`.
Parameters
----------
{resample_in}
{hemi}
alt_space : {{'fsaverage', 'fsLR', 'civet'}}, optional
Alternative space to which `src` and `trg` should be transformed. Default:
'fsaverage'
alt_density : str, optional
Resolution to which `src` and `trg` should be resampled. Must be valid
with `alt_space`. Default: '41k'
Returns
-------
{resample_out}
""".format(**_resampling_docs)
def mni_transform(src, trg, src_space, trg_space, method='linear', hemi=None): # noqa: D103
if src_space != 'MNI152':
raise ValueError('Cannot perform MNI transformation when src_space is '
f'not "MNI152." Received: {src_space}.')
trg_den = trg
if trg_space != 'MNI152':
trg_den, = transforms._estimate_density((trg_den,), hemi)
func = getattr(transforms, f'mni152_to_{trg_space.lower()}')
src = func(src, trg_den, method=method)
return src, trg
mni_transform.__doc__ = """\
Resample `src` in MNI152 to `trg` space.
Parameters
----------
{resample_in}
hemi : {{'L', 'R'}}, optional
If `trg_space` is not "MNI152' and `trg` is not a tuple this specifies the
hemisphere the data represent. Default: None
Returns
-------
{resample_out}
""".format(**_resampling_docs)
def _check_altspec(spec):
"""
Confirm that specified alternative `spec` is valid (space, density) format.
Parameters
----------
spec : (2,) tuple-of-str
Where entries are (space, density) of desired target space
Returns
-------
spec : (2,) tuple-of-str
Unmodified input `spec`
Raises
------
ValueError
If `spec` is not valid format
"""
invalid_spec = spec is None or len(spec) != 2
if not invalid_spec:
space, den = spec
space = ALIAS.get(space, space)
valid = DENSITIES.get(space)
invalid_spec = valid is None or den not in valid
if invalid_spec:
raise ValueError('Must provide valid alternative specification of '
f'format (space, density). Received: {spec}')
return (space, den)
[docs]def resample_images(src, trg, src_space, trg_space, method='linear', # noqa: D103
hemi=None, resampling='downsample_only', alt_spec=None):
resamplings = ('downsample_only', 'transform_to_src', 'transform_to_trg',
'transform_to_alt')
if resampling not in resamplings:
raise ValueError(f'Invalid method: {resampling}')
src_space = ALIAS.get(src_space, src_space)
trg_space = ALIAS.get(trg_space, trg_space)
# all this input handling just to deal with volumetric images :face_palm:
opts, err = {}, None
if resampling == 'transform_to_alt':
opts['alt_space'], opts['alt_density'] = _check_altspec(alt_spec)
if (opts['alt_space'] == 'MNI152'
and (src_space != 'MNI152' or trg_space != 'MNI152')):
raise ValueError('Cannot transform to "MNI152" system if either '
'`src` or `trg` are not in "MNI152" system.')
elif (resampling == 'transform_to_src' and src_space == 'MNI152'
and trg_space != 'MNI152'):
err = ('Specified `src_space` cannot be "MNI152" when `resampling` is '
'"transform_to_src"')
elif (resampling == 'transform_to_trg' and src_space != 'MNI152'
and trg_space == 'MNI152'):
err = ('Specified `trg_space` cannot be "MNI152" when `resampling` is '
'"transform_to_trg"')
elif (resampling == 'transform_to_alt' and opts['alt_space'] == 'MNI152'
and (src_space != 'MNI152' or trg_space != 'MNI152')):
err = ('Specified `alt_space` cannot be "MNI152" when `resampling` is '
'"transform_to_alt"')
if err is not None:
raise ValueError(err)
# handling volumetric data is annoying...
if ((src_space == "MNI152" or trg_space == "MNI152")
and resampling == 'transform_to_alt'):
func = mni_transform if src_space == 'MNI152' else transform_to_trg
src = func(src, opts['alt_density'], src_space, opts['alt_space'],
method=method, hemi=hemi)[0]
func = mni_transform if trg_space == 'MNI152' else transform_to_trg
trg = func(trg, opts['alt_density'], trg_space, opts['alt_space'],
method=method, hemi=hemi)[0]
elif src_space == 'MNI152' and trg_space != 'MNI152':
src, trg = mni_transform(src, trg, src_space, trg_space,
method=method, hemi=hemi)
elif trg_space == 'MNI152' and src_space != 'MNI152':
trg, src = mni_transform(trg, src, trg_space, src_space,
method=method, hemi=hemi)
elif src_space == 'MNI152' and trg_space == 'MNI152':
src, trg = load_nifti(src), load_nifti(trg)
srcres = np.prod(nib.affines.voxel_sizes(src.affine))
trgres = np.prod(nib.affines.voxel_sizes(trg.affine))
if ((resampling == 'downsample_only' and srcres > trgres)
or resampling == 'transform_to_src'):
trg, src = mni_transform(trg, src, trg_space, src_space,
method=method)
elif ((resampling == 'downsample_only' and srcres <= trgres)
or resampling == 'transform_to_trg'):
src, trg = mni_transform(src, trg, src_space, trg_space,
method=method)
else:
func = globals()[resampling]
src, trg = func(src, trg, src_space, trg_space, hemi=hemi,
method=method, **opts)
src, _ = zip(*transforms._check_hemi(src, hemi))
trg, _ = zip(*transforms._check_hemi(trg, hemi))
src = tuple(load_gifti(s) for s in src)
trg = tuple(load_gifti(t) for t in trg)
return src, trg
resample_images.__doc__ = """\
Resample images `src` and `trg` to same space/density with `resampling` method.
Parameters
----------
{resample_in}
{hemi}
resampling : str, optional
Name of resampling function to resample `src` and `trg`. Must be one of:
'downsample_only', 'transform_to_src', 'transform_to_trg',
'transform_to_alt'. See Notes for more info. Default: 'downsample_only'
alt_spec : (2,) tuple-of-str
Where entries are (space, density) of desired target space. Only used if
`resampling='transform_to_alt'`. Default: None
Returns
-------
{resample_out}
Notes
-----
The four available `resampling` strategies will control how `src` and/or `trg`
are resampled prior to correlation. Options include:
1. `resampling='downsample_only'`
Data from `src` and `trg` are resampled to the lower resolution of the two
input datasets
2. `resampling='transform_to_src'`
Data from `trg` are always resampled to match `src` space and resolution
3. `resampling='transform_to_trg'`
Data from `src` are always resampled to match `trg` space and resolution
4. `resampling='transform_to_alt'`
Data from `trg` and `src` are resampled to the space and resolution
specified by `alt_spec` (space, density)
""".format(**_resampling_docs)