#!/usr/bin/env python
#
# fslmaths.py - Wrapper for fslmaths.
#
# Author: Sean Fitzgibbon <sean.fitzgibbon@ndcn.ox.ac.uk>
#
"""This module provides the :class:`fslmaths` class, which acts as a wrapper
for the ``fslmaths`` command-line tool.
"""
from . import wrapperutils as wutils
[docs]
class fslmaths:
"""Perform mathematical manipulation of images.
``fslmaths`` is unlike the other FSL wrapper tools in that it provides an
object-oriented method-chaining interface, which is hopefully easier to
use than constructing a ``fslmaths`` command-line call. For example, the
following call to the ``fslmaths`` wrapper function::
fslmaths('input.nii').thr(0.25).mul(-1).run('output.nii')
will be translated into the following command-line call::
fslmaths input.nii -thr 0.25 -mul -1 output.nii
The ``fslmaths`` wrapper function can also be used with in-memory
images. If no output file name is passed to the :meth:`run` method, the
result is loaded into memory and returned as a ``nibabel`` image. For
example::
import nibabel as nib
input = nib.load('input.nii')
output = fslmaths(input).thr(0.25).mul(-1).run()
"""
[docs]
def __init__(self, input, dt=None):
"""Constructor."""
self.__input = input
self.__args = []
if dt is not None:
self.__args.extend(('-dt', dt))
[docs]
def addargs(func):
"""Decorator used by fslmaths methods. Allows them to just return
a list of arguments to add to the command invocation.
"""
def wrapper(self, *args, **kwargs):
args = func(self, *args, **kwargs)
self.__args.extend(args)
return self
return wrapper
# Binary operations
[docs]
@addargs
def add(self, image):
"""Add input to current image."""
return ['-add', image]
[docs]
@addargs
def sub(self, image):
"""Subtract image from current image."""
return ["-sub", image]
[docs]
@addargs
def mul(self, image):
"""Multiply current image by image."""
return ["-mul", image]
[docs]
@addargs
def div(self, image):
"""Divide current image by image."""
return ["-div", image]
[docs]
@addargs
def rem(self, image):
"""Divide current image by following image and take remainder."""
return ["-rem", image]
[docs]
@addargs
def mas(self, image):
"""Use image (>0) to mask current image."""
return ["-mas", image]
[docs]
@addargs
def thr(self, image):
"""threshold below the following number (zero anything below the
number)"""
return ["-thr", image]
[docs]
@addargs
def thrp(self, perc):
"""threshold below the following percentage (0-100) of ROBUST RANGE"""
return ["-thrp", perc]
[docs]
@addargs
def thrP(self, perc):
"""threshold below the following percentage (0-100) of the positive
voxels' ROBUST RANGE"""
return ["-thrP", perc]
[docs]
@addargs
def uthr(self, image):
"""use image number to upper-threshold current image (zero
anything above the number)."""
return ["-uthr", image]
[docs]
@addargs
def uthrp(self, perc):
"""upper-threshold above the following percentage (0-100) of the
ROBUST RANGE"""
return ["-uthrp", perc]
[docs]
@addargs
def uthrP(self, perc):
"""upper-threshold above the following percentage (0-100) of the
positive voxels' ROBUST RANGE"""
return ["-uthrP", perc]
[docs]
@addargs
def max(self, image):
"""take maximum of following input and current image."""
return ["-max", image]
[docs]
@addargs
def min(self, image):
"""take minimum of following input and current image."""
return ["-min", image]
[docs]
@addargs
def seed(self, seed):
"""seed random number generator with following number"""
return ['-seed', seed]
[docs]
@addargs
def restart(self, image):
"""replace the current image with input for future processing
operations"""
return ['-restart', image]
[docs]
@addargs
def save(self, filename):
"""save the current working image to the input filename"""
return ['-save', filename]
# Basic unary operations
[docs]
@addargs
def exp(self):
"""exponential"""
return ["-exp"]
[docs]
@addargs
def log(self):
"""Natural logarithm."""
return ["-log"]
[docs]
@addargs
def sin(self):
"""sine function"""
return ["-sin"]
[docs]
@addargs
def cos(self):
"""cosine function"""
return ["-cos"]
[docs]
@addargs
def tan(self):
"""tangent function"""
return ["-tan"]
[docs]
@addargs
def asin(self):
"""arc sine function"""
return ["-asin"]
[docs]
@addargs
def acos(self):
"""arc cosine function"""
return ["-acos"]
[docs]
@addargs
def atan(self):
"""arc tangent function"""
return ["-atan"]
[docs]
@addargs
def sqr(self):
"""Square."""
return ["-sqr"]
[docs]
@addargs
def sqrt(self):
"""Square root."""
return ["-sqrt"]
[docs]
@addargs
def recip(self):
"""Reciprocal (1/current image)."""
return ["-recip"]
[docs]
@addargs
def abs(self):
"""Absolute value."""
return ["-abs"]
[docs]
@addargs
def bin(self):
"""Use (current image>0) to binarise."""
return ["-bin"]
[docs]
@addargs
def binv(self):
"""Binarise and invert (binarisation and logical inversion)."""
return ["-binv"]
[docs]
@addargs
def fillh(self):
"""fill holes in a binary mask (holes are internal - i.e. do not touch
the edge of the FOV)."""
return ["-fillh"]
[docs]
@addargs
def fillh26(self):
"""fill holes using 26 connectivity"""
return ["-fillh26"]
[docs]
@addargs
def index(self):
"""replace each nonzero voxel with a unique (subject to wrapping) index
number"""
return ["-index"]
[docs]
@addargs
def grid(self, value, spacing):
"""add a 3D grid of intensity <value> with grid spacing <spacing>"""
return ['-grid', value, spacing]
[docs]
@addargs
def edge(self):
"""edge strength"""
return ["-edge"]
[docs]
@addargs
def dog_edge(self, sigma1, sigma2):
"""difference of gaussians edge filter. Typical sigma1 is 1.0 and
sigma2 is 1.6
"""
return ['-dog_edge', sigma1, sigma2]
[docs]
@addargs
def tfce(self, h, e, connectivity):
"""enhance with TFCE, e.g. -tfce 2 0.5 6 (maybe change 6 to 26 for
skeletons)
"""
return ['-tfce', h, e, connectivity]
[docs]
@addargs
def tfceS(self, h, e, connectivity, x, y, z, tfce_thresh):
"""show support area for voxel (X,Y,Z)"""
return ['-tfceS', h, e, connectivity, x, y, z, tfce_thresh]
[docs]
@addargs
def nan(self):
"""replace NaNs (improper numbers) with 0"""
return ["-nan"]
[docs]
@addargs
def nanm(self):
"""make NaN (improper number) mask with 1 for NaN voxels, 0 otherwise"""
return ["-nanm"]
[docs]
@addargs
def rand(self):
"""add uniform noise (range 0:1)"""
return ["-rand"]
[docs]
@addargs
def randn(self):
"""add Gaussian noise (mean=0 sigma=1)"""
return ["-randn"]
[docs]
@addargs
def inm(self, image):
"""Intensity normalisation (per 3D volume mean)"""
return ["-inm", image]
[docs]
@addargs
def ing(self, image):
"""intensity normalisation, global 4D mean)"""
return ["-ing", image]
[docs]
@addargs
def range(self):
"""Set the output calmin/max to full data range."""
return ["-range"]
# Matrix operations
[docs]
@addargs
def tensor_decomp(self):
"""convert a 4D (6-timepoint )tensor image into L1,2,3,FA,MD,MO,V1,2,3
(remaining image in pipeline is FA)
"""
return ["-tensor_decomp"]
[docs]
@addargs
def kernel(self, *args):
"""Perform a kernel operation"""
return ["-kernel"] + list(args)
# Spatial filtering
[docs]
@addargs
def dilM(self, repeat=1):
"""Mean Dilation of non-zero voxels."""
return ['-dilM'] * repeat
[docs]
@addargs
def dilD(self, repeat=1):
"""Modal Dilation of non-zero voxels."""
return ["-dilD"] * repeat
[docs]
@addargs
def dilF(self, repeat=1):
"""Maximum filtering of all voxels."""
return ["-dilF"] * repeat
[docs]
@addargs
def dilall(self):
"""Apply -dilM repeatedly until the entire FOV is covered"""
return ["-dilall"]
[docs]
@addargs
def ero(self, repeat=1):
"""Erode by zeroing non-zero voxels when zero voxels in kernel."""
return ["-ero"] * repeat
[docs]
@addargs
def eroF(self, repeat=1):
"""Minimum filtering of all voxels"""
return ["-eroF"] * repeat
[docs]
@addargs
def fmean(self):
"""Mean filtering, kernel weighted, (conventionally used with gauss kernel)"""
return ["-fmean"]
[docs]
@addargs
def fmeanu(self):
"""Mean filtering, kernel weighted, un-normalised (gives edge effects)"""
return ["-fmeanu"]
[docs]
@addargs
def s(self, sigma):
"""Create a gauss kernel of sigma mm and perform mean filtering"""
return ["-s", sigma]
# alias for -s
smooth = s
[docs]
@addargs
def subsamp2(self):
"""downsamples image by a factor of 2 (keeping new voxels centred on
old)"""
return ["-subsamp2"]
[docs]
@addargs
def subsamp2offc(self):
"""downsamples image by a factor of 2 (non-centred)"""
return ["-subsamp2offc"]
# Dimensionality reduction operations
[docs]
@addargs
def Tmean(self):
"""Mean across time."""
return ["-Tmean"]
[docs]
@addargs
def Tstd(self):
"""Standard deviation across time."""
return ["-Tstd"]
[docs]
@addargs
def Tmin(self):
"""Min across time."""
return ["-Tmin"]
[docs]
@addargs
def Tmax(self):
"""Max across time."""
return ["-Tmax"]
[docs]
@addargs
def Tmaxn(self):
"""time index of max across time."""
return ["-Tmaxn"]
[docs]
@addargs
def Tperc(self, percentage):
"""nth percentile (0-100) of FULL RANGE across time"""
return ["-Tperc", percentage]
[docs]
@addargs
def Tar1(self):
"""temporal AR(1) coefficient (use -odt float and probably demean
first)"""
return ["-Tar1"]
# Basic statistical operations
[docs]
@addargs
def pval(self):
"""Nonparametric uncorrected P-value"""
return ['-pval']
[docs]
@addargs
def pval0(self):
"""Same as -pval, but treat zeros as missing data"""
return ['-pval0']
[docs]
@addargs
def cpval(self):
"""Same as -pval, but gives FWE corrected P-values"""
return ['-cpval']
[docs]
@addargs
def ztop(self):
"""Convert Z-stat to (uncorrected) P"""
return ['-ztop']
[docs]
@addargs
def ptoz(self):
"""Convert (uncorrected) P to Z"""
return ['-ptoz']
[docs]
@addargs
def rank(self):
"""Convert (uncorrected) P to Z"""
return ['-rank']
[docs]
@addargs
def ranknorm(self):
"""Transform to Normal dist via ranks"""
return ['-ranknorm']
# Multi-argument operations
[docs]
@addargs
def roi(self, xmin, xsize, ymin, ysize, zmin, zsize, tmin=0, tsize=-1):
"""Zero outside ROI (using voxel coordinates). """
return ['-roi', xmin, xsize, ymin, ysize,
zmin, zsize, tmin, tsize]
[docs]
@addargs
def bptf(self, hp_sigma, lp_sigma):
"""Bandpass temporal filtering; nonlinear highpass and Gaussian linear
lowpass (with sigmas in volumes, not seconds); set either sigma<0 to
skip that filter."""
return ["-bptf", hp_sigma, lp_sigma]
[docs]
def run(self, output=None, odt=None, **kwargs):
"""Save output of operations to image. Set ``output`` to a filename to have
the result saved to file, or omit ``output`` entirely to have the
result returned as a ``nibabel`` image.
All other arguments are ultimately passed through to the
:func:`fsl.utils.run.run` function.
"""
cmd = ['fslmaths', self.__input] + self.__args
if output is None:
output = wutils.LOAD
cmd += [output]
if odt is not None:
cmd.extend(('-odt', odt))
result = self.__run(*cmd, **kwargs)
# if output is LOADed, there
# will only be one entry in
# the result dict.
if output == wutils.LOAD: return list(result.values())[0]
else: return result
@wutils.fileOrImage()
@wutils.fslwrapper
def __run(self, *cmd):
"""Run the given ``fslmaths`` command. """
return [str(c) for c in cmd]