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 ...@@ -11,6 +11,7 @@ Added
* Added more functions to the :class:`.fslmaths` wrapper (!431). * Added more functions to the :class:`.fslmaths` wrapper (!431).
* New :func:`.smoothest` wrapper function (!432).
3.15.4 (Monday 27th November 2023) 3.15.4 (Monday 27th November 2023)
......
...@@ -12,12 +12,70 @@ ...@@ -12,12 +12,70 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import glob
import itertools as it
import os import os
import os.path as op
import sys import sys
import datetime import datetime
date = datetime.date.today() 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, # 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 # 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. # 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 @@ ...@@ -8,6 +8,7 @@
fsl.wrappers.bedpostx fsl.wrappers.bedpostx
fsl.wrappers.bet fsl.wrappers.bet
fsl.wrappers.bianca fsl.wrappers.bianca
fsl.wrappers.cluster_commands
fsl.wrappers.dtifit fsl.wrappers.dtifit
fsl.wrappers.eddy fsl.wrappers.eddy
fsl.wrappers.epi_reg fsl.wrappers.epi_reg
...@@ -23,6 +24,7 @@ ...@@ -23,6 +24,7 @@
fsl.wrappers.fugue fsl.wrappers.fugue
fsl.wrappers.melodic fsl.wrappers.melodic
fsl.wrappers.misc fsl.wrappers.misc
fsl.wrappers.oxford_asl
fsl.wrappers.randomise fsl.wrappers.randomise
fsl.wrappers.tbss fsl.wrappers.tbss
fsl.wrappers.wrapperutils fsl.wrappers.wrapperutils
......
...@@ -9,10 +9,17 @@ import contextlib ...@@ -9,10 +9,17 @@ import contextlib
import os import os
import os.path as op import os.path as op
import sys import sys
import textwrap as tw
from unittest import mock
import numpy as np 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 testenv
from .. import mockFSLDIR from .. import mockFSLDIR
...@@ -67,3 +74,56 @@ def test_cluster(): ...@@ -67,3 +74,56 @@ def test_cluster():
assert np.all(np.isclose(data, expected)) assert np.all(np.isclose(data, expected))
assert ''.join(titles) == mock_titles assert ''.join(titles) == mock_titles
assert result1.stdout == result2.stdout 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, ...@@ -101,7 +101,8 @@ from fsl.wrappers.wrapperutils import (LOAD,
fslwrapper, fslwrapper,
funcwrapper) funcwrapper)
from fsl.wrappers import (tbss,) 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, from fsl.wrappers.bet import (bet,
robustfov) robustfov)
from fsl.wrappers.eddy import (eddy, from fsl.wrappers.eddy import (eddy,
......
...@@ -66,3 +66,59 @@ def _cluster(infile, thresh, **kwargs): ...@@ -66,3 +66,59 @@ def _cluster(infile, thresh, **kwargs):
cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs) cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs)
return cmd 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): ...@@ -32,8 +32,13 @@ def fnirt(src, **kwargs):
asrt.assertIsNifti(src) asrt.assertIsNifti(src)
valmap = {
'v' : wutils.SHOW_IF_TRUE,
'verbose' : wutils.SHOW_IF_TRUE,
}
cmd = ['fnirt', '--in={}'.format(src)] cmd = ['fnirt', '--in={}'.format(src)]
cmd += wutils.applyArgStyle('--=', **kwargs) cmd += wutils.applyArgStyle('--=', valmap=valmap, **kwargs)
return cmd return cmd
......
...@@ -31,20 +31,28 @@ class fslstats: ...@@ -31,20 +31,28 @@ class fslstats:
present in older versions. 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 Any ``fslstats`` command-line option which does not require any arguments
(e.g. ``-r``) can be set by accessing an attribute on a ``fslstats`` (e.g. ``-r``) can be set by accessing an attribute on a ``fslstats``
object, e.g.:: object, e.g.::
stats = fslstats('image.nii.gz') fslstats('image.nii.gz').r.run()
stats.r
``fslstats`` command-line options which do require additional arguments ``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. ``-k``) can be set by calling a method on an ``fslstats`` object,
e.g.:: e.g.::
stats = fslstats('image.nii.gz') stats = fslstats('image.nii.gz').k('mask.nii.gz').run()
stats.k('mask.nii.gz')
The ``fslstats`` command can be executed via the :meth:`run` method. 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