Skip to content
Snippets Groups Projects
Commit 86445049 authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

Merge branch 'enh/smoothest' into 'main'

ENH: New `smoothest` wrapper function

See merge request fsl/fslpy!432
parents e3027f6a 6a6c00ed
No related branches found
No related tags found
No related merge requests found
......@@ -11,6 +11,7 @@ Added
* Added more functions to the :class:`.fslmaths` wrapper (!431).
* New :func:`.smoothest` wrapper function (!432).
3.15.4 (Monday 27th November 2023)
......
......@@ -12,12 +12,70 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import glob
import itertools as it
import os
import os.path as op
import sys
import datetime
date = datetime.date.today()
def check_for_missing_stubs():
docdir = op.dirname(__file__)
basedir = op.join(docdir, '..')
modules = []
def tomodname(f):
if f.endswith('.py'):
f = f[:-3]
return op.relpath(op.join(dirpath, f), basedir).replace(op.sep, '.')
for dirpath, dirnames, filenames in os.walk(op.join(basedir, 'fsl')):
for d in dirnames:
if d == '__pycache__':
continue
if len(glob.glob(op.join(dirpath, d, '**', '*.py'), recursive=True)) == 0:
continue
modules.append(tomodname(d))
for f in filenames:
if not f.endswith('.py'):
continue
if f in ('__init__.py', '__main__.py'):
continue
modules.append(tomodname(f))
modules = [m for m in modules if not m.startswith('fsl.tests')]
# import fsl
# modules = recurse(fsl)
# modules = [m.name for m in modules]
# print()
# print()
# print()
for mod in modules:
docfile = op.join(docdir, f'{mod}.rst')
if not op.exists(docfile):
print(f'No doc file found for module: {mod}')
for docfile in glob.glob(op.join(docdir, '*.rst')):
docfile = op.relpath(docfile, basedir)
mod = op.splitext(op.basename(docfile))[0]
if mod not in modules:
print(f'No module found for doc file: {docfile}')
if __name__ == '__main__':
check_for_missing_stubs()
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
......
``fsl.wrappers.cluster_commands``
=================================
.. automodule:: fsl.wrappers.cluster_commands
:members:
:undoc-members:
:show-inheritance:
``fsl.wrappers.oxford_asl``
===========================
.. automodule:: fsl.wrappers.oxford_asl
:members:
:undoc-members:
:show-inheritance:
......@@ -8,6 +8,7 @@
fsl.wrappers.bedpostx
fsl.wrappers.bet
fsl.wrappers.bianca
fsl.wrappers.cluster_commands
fsl.wrappers.dtifit
fsl.wrappers.eddy
fsl.wrappers.epi_reg
......@@ -23,6 +24,7 @@
fsl.wrappers.fugue
fsl.wrappers.melodic
fsl.wrappers.misc
fsl.wrappers.oxford_asl
fsl.wrappers.randomise
fsl.wrappers.tbss
fsl.wrappers.wrapperutils
......
......@@ -9,10 +9,17 @@ import contextlib
import os
import os.path as op
import sys
import textwrap as tw
from unittest import mock
import numpy as np
from fsl.wrappers.cluster import cluster, _cluster
from fsl.wrappers import wrapperutils as wutils
from fsl.wrappers.cluster_commands import (cluster,
_cluster,
smoothest,
_smoothest)
from . import testenv
from .. import mockFSLDIR
......@@ -67,3 +74,56 @@ def test_cluster():
assert np.all(np.isclose(data, expected))
assert ''.join(titles) == mock_titles
assert result1.stdout == result2.stdout
def test_smoothest_wrapper():
with testenv('smoothest') as smoothest_exe:
result = _smoothest(res='res', zstat='zstat', d=5, V=True)
expected = f'{smoothest_exe} --res=res --zstat=zstat -d 5 -V'
assert result.stdout[0] == expected
# auto detect residuals vs zstat
result = _smoothest('res4d.nii.gz', d=5, V=True)
expected = f'{smoothest_exe} -d 5 -V --res=res4d.nii.gz'
assert result.stdout[0] == expected
result = _smoothest('zstat1.nii.gz', d=5, V=True)
expected = f'{smoothest_exe} -d 5 -V --zstat=zstat1.nii.gz'
assert result.stdout[0] == expected
def test_smoothest():
result = tw.dedent("""
FWHMx = 4.763 mm, FWHMy = 5.06668 mm, FWHMz = 4.71527 mm
DLH 0.324569 voxels^-3
VOLUME 244531 voxels
RESELS 14.224 voxels per resel
DLH 0.324569
VOLUME 244531
RESELS 14.224
FWHMvoxel 2.3815 2.53334 2.35763
FWHMmm 4.763 5.06668 4.71527
""")
result = wutils.FileOrThing.Results((result, ''))
expect = {
'DLH' : 0.324569,
'VOLUME' : 244531,
'RESELS' : 14.224,
'FWHMvoxel' : [2.3815, 2.53334, 2.35763],
'FWHMmm' : [4.763, 5.06668, 4.71527]
}
with mock.patch('fsl.wrappers.cluster_commands._smoothest',
return_value=result):
result = smoothest('inimage')
assert result.keys() == expect.keys()
assert np.isclose(result['DLH'], expect['DLH'])
assert np.isclose(result['VOLUME'], expect['VOLUME'])
assert np.isclose(result['RESELS'], expect['RESELS'])
assert np.all(np.isclose(result['FWHMvoxel'], expect['FWHMvoxel']))
assert np.all(np.isclose(result['FWHMmm'], expect['FWHMmm']))
......@@ -101,7 +101,8 @@ from fsl.wrappers.wrapperutils import (LOAD,
fslwrapper,
funcwrapper)
from fsl.wrappers import (tbss,)
from fsl.wrappers.cluster import (cluster,)
from fsl.wrappers.cluster_commands import (cluster,
smoothest)
from fsl.wrappers.bet import (bet,
robustfov)
from fsl.wrappers.eddy import (eddy,
......
......@@ -66,3 +66,59 @@ def _cluster(infile, thresh, **kwargs):
cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs)
return cmd
def smoothest(inimg=None, **kwargs):
"""Wrapper for the ``smoothest`` command.
The residual or zstatistic image may be passed as the first positional
argument (``inimg``) - its type is inferred from the image file name if
possible. If this is not possible (e.g. non-standard file names or
in-memory images), you must specify residual images via ``res``, or
zstatistic images via ``zstat``.
Returns a dictionary containing the parameters estimated by ``smoothest``,
e.g.::
{
'DLH' : 1.25903,
'VOLUME' : 239991,
'RESELS' : 3.69574,
'FWHMvoxel' : [1.57816, 1.64219, 1.42603],
'FWHMmm' : [3.15631, 3.28437, 2.85206]
}
"""
result = _smoothest(**kwargs)
result = result.stdout[0]
result = result.strip().split('\n')[-5:]
values = {}
for line in result:
key, vals = line.split(maxsplit=1)
vals = [float(v) for v in vals.split()]
if len(vals) == 1:
vals = vals[0]
values[key] = vals
return values
@wutils.fileOrImage('inimg', 'r', 'res', 'z', 'zstat', 'm', 'mask')
@wutils.fslwrapper
def _smoothest(inimg=None, **kwargs):
"""Actual wrapper for the ``smoothest`` command."""
if inimg is not None:
if 'res4d' in inimg: kwargs['res'] = inimg
elif 'zstat' in inimg: kwargs['zstat'] = inimg
else: raise RuntimeError('Cannot infer type of input '
f'image {inimg.name}')
valmap = {
'V' : wutils.SHOW_IF_TRUE,
'verbose' : wutils.SHOW_IF_TRUE,
}
return ['smoothest'] + wutils.applyArgStyle('--=', valmap=valmap, **kwargs)
......@@ -32,8 +32,13 @@ def fnirt(src, **kwargs):
asrt.assertIsNifti(src)
valmap = {
'v' : wutils.SHOW_IF_TRUE,
'verbose' : wutils.SHOW_IF_TRUE,
}
cmd = ['fnirt', '--in={}'.format(src)]
cmd += wutils.applyArgStyle('--=', **kwargs)
cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs)
return cmd
......
......@@ -31,20 +31,28 @@ class fslstats:
present in older versions.
This ``fslstats`` command::
fslstats image -r -p 95 -R
is equivalent to this function call::
fslstats('image').r.p(95).R.run()
Any ``fslstats`` command-line option which does not require any arguments
(e.g. ``-r``) can be set by accessing an attribute on a ``fslstats``
object, e.g.::
stats = fslstats('image.nii.gz')
stats.r
fslstats('image.nii.gz').r.run()
``fslstats`` command-line options which do require additional arguments
(e.g. ``-k``) can be set by calling a method on an ``fslstats`` object,
e.g.::
stats = fslstats('image.nii.gz')
stats.k('mask.nii.gz')
stats = fslstats('image.nii.gz').k('mask.nii.gz').run()
The ``fslstats`` command can be executed via the :meth:`run` method.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment