It is said that
imitation is the sincerest form of flattery. This project emulates the popular fslmaths tool. fslmaths is advertized as a
general image calculator and is not only one of the foundational tools for FSL's brain imaging pipelines (such as FEAT), but has also been widely adopted by many tools. This popularity suggests that it fulfills an important niche. While scientists are often encouraged to discover novel solutions, it sometimes seems that replication is undervalued. Here are some specific reasons for creating this tool:
The Reason to use fslmaths instead of niimath:
correctresult (with comparison to itself). An example of this is
-dilD, where fslmaths generates a blurred and spatially shifted image, which is not what the documentation describes. However, many tools may have been developed to assume this loss of high frequency signal and these tools may not perform well when provided with the result specified in the documentation.
You can get niimath using three methods:
curl -fLO https://github.com/rordenlab/niimath/releases/latest/download/niimath_lnx.zip
curl -fLO https://github.com/rordenlab/niimath/releases/latest/download/niimath_universal.pkg
curl -fLO https://github.com/rordenlab/niimath/releases/latest/download/niimath_win.zip
The easiest way to build niimath on a Unix computer is to use cmake:
git clone https://github.com/rordenlab/niimath.git cd niimath; mkdir build; cd build; cmake .. make
Alternatively, you can compile the software by running the terminal command
make from the project's
Advanced users may want to run
CF=1 OMP=1 make -j to make a version that uses OpenMP (parallel processing) and the CloudFlare accelerated compression library. You may need to edit the
Makefile for your compiler name. On MacOS, the default C compiler is Clang, which has poor OpenMP support. Therefore, MacOS users may want to install the gcc compiler
brew install [email protected].
For Windows, the compilation will look like this (here without the
-DHAVE_ZLIB directive, so gz files will not be supported) :
cl /Feniimath niimath.c core.c tensor.c core32.c core64.c bwlabel.c niftilib/nifti2_io.c znzlib/znzlib.c -I./niftilib -I./znzlib
make in the
src folder should compile niimath on Linux. This should work regardless of if you use the Clang/LLVM or gcc compiler. However, the resulting executable will only work with specific versions of Linux. If you want to make a universal Linux release you can use holy-build-box. Be aware that this uses an old version of the gcc compiler (4.8.5), so the resulting performance may not be optimized for your system.
git clone https://github.com/rordenlab/niimath sudo docker run -t -i --rm -v `pwd`:/io ghcr.io/foobarwidget/holy-build-box-x64 /hbb_exe/activate-exec bash cd /io/niimath/src make exit sudo chown $(whoami) ./niimath/src/niimath
niimath provides the same commands as fslmaths, so you can use it just as you would fslmaths. If you are brave, you can even rename it fslmaths and use it as a drop in replacement. You can also modify your environment variables to unleash advanced features:
FSLOUTPUTTYPEEnvironment Variable to determine output file format. Unix users can specify
export NIFTIfrom the command line or profile to select between compressed (smaller) or uncompressed (faster) results. Windows users can use
export AFNI_COMPRESSOR=PIGZ. If the environment variable
AFNI_COMPRESSORdoes not exist, or is set to any value other than
PIGZyou will get single threaded compresson.
niimath has a few features not provided by fslmaths:
sobel_binary: sobel creating binary edge
tensor_2lower: convert FSL style upper triangle image to NIfTI standard lower triangle order
tensor_2upper: convert NIfTI standard lower triangle image to FSL style upper triangle order
tensor_decomp_lower: as tensor_decomp except input stores lower diagonal (AFNI, ANTS, Camino convention)
--compare: report if images are identical, terminates without saving new image\n");
This project is designed to provide equivalent results to fslmaths. In most cases, the results are identical, virtually all others are equivalent. The results are not always identical as computations are conducted using floating point representations, where the precise order of instructions can generate small rounding differences. As Kernighan and Plauger note
Floating point numbers are like piles of sand; every time you move one you lose a little sand and pick up a little dirt. Raw brain imaging data is typically stored as 16-bit integers (and the signal-to-noise is typically a fraction of this dynamic range), whereas niimath uses single (32-bit) or double (64-bit) floating point representations. Therefore, while niimath may generate results that are not identical, the results are intended to be always comparable. For further information on floating point accuracy, suggested readings include here and here.
This project includes the
--compare argument that allows you to directly the results of niimath and fslmath. A validation repository is also available, which runs hundreds of commands to detect the quality of the output. The validation repository includes two scripts. The
batch.sh script tests functions that generate identical results. The
close.sh script conducts tests on functions that provide equivalent but not identical results. For example, for tensor decomposition the vector [1 0 0] is the functionally identical to [-1 0 0] as for fiber tracking the fiber direction ignores vector polarity. When a difference is detected by the
--compare function, a report is generated allowing the user to determine the equivalence of solutions:
Images Differ: Correlation r = 1, identical voxels 73% Most different voxel -69.3133 vs -69.3133 (difference 1.52588e-05) Most different voxel location 43x17x49 volume 39 Image 1 Descriptives Range: -472.393..491.385 Mean -0.00121971 StDev 6.8898 Image 2 Descriptives Range: -472.393..491.385 Mean -0.00121971 StDev 6.8898 86.29 real 41.08 user 23.41 sys
Some operations do generate known meaningfully different results. These are listed below, with the rationale for the discrepancy provided:
-fillh26will sometimes fill unconnected regions. An example has been provided to the FSL team. niimath provides the correct solution.
-dilDfunction does not do what it claims. It introduces a blurring effect that reduces edge artifacts that plague iterative morphology operations. Unfortunately, this effect is conducted in a consistent order that introduces a spatial shift in signal. In contrast, niimath does the dilation as described. Note there are better solutions for these functions. The niimath '-edt' operation can also be used for dilation.
-rocfunction works differently than described in the help. It appears to ignore voxels near the edge of an image and generates "given object has non-finite elements" if any dimension is less than 12 voxels. When provided with an external noise file, it generates additional columns in the output file that are not described. It does not seem to precisely detect the desired
AROC-thresh, but samples at different stepped intervals. niimath attempts to emulate the stepped intervals for reporting, but determines the precise cutoff.
If you apply a Binary operation (one that takes the current image and a new image together), when one is 3D and the other is 4D, the 3D image is cloned temporally to match the temporal dimensions of the 4D image.This is not the case for -thr or -uthr: if the second item is 4D, only the first volume is used and the output remains 3D. Particularly odd is uthr:
fslmaths 3D -uthr 4D outwill fill input volume 3D with zeros, regardless of mask values.
fslmaths in1 -rem 0 outwill throw an exception. However,
fslmaths in1 -rem in2 outwill throw an exception if any voxel in the image
in2is zero. While this seems understandable, niimath provides a description for this error.
-remreturns the integer modulus remainder. This is unexpected, e.g. in Python
2.7 % 2is 0.7, as is Matlab's
mod(2.7, 2), as is standard C
fmod. niimath clones the fslmaths idiosyncratic behavior, but also includes a new function
-modto return the modulus fractional remainder.
fslstats tfRAS -xwill give coordinates that are incompatible with fslmath's
tfceSfunction. niimath attempts to emulate the behavior of fslmaths for the relevant functions (-index -roi, -tfceS).
-subsamp2offchandle anti-aliasing. Be aware that
-subsamp2offccan exhibit odd edge effects. The problem is simple to describe, for slices in the middle of a volume, and output slice is weighted 50% with the center slice, and 25% for the slice below and the slice above. This makes sense. However, bottom slices (as well as first rows, first columns, last rows, last columns, last slices) the filter weights 75% on the central slice and just 25% on the slice above it. Signal from this 2nd slice is heavily diluted. A better mixture would be 66% edge slice and 33% 2nd slice. This latter solution is used by niimath.
fslmaths ~/test.niim/RAS -add 0 tstwill generate an exception. niimath will recognize that this is a folder name and not a file extension and work correctly. niimath helped detect this anomaly and it is an example of how a clone can help provide feedback to the developers of the original project.
-ztopfails to clamp extreme values.
Finally, it is possible that there are some edge cases where niimath fails to replicate fslmath. This is new software, and many of the operations applied by fslmaths are undocumented. If users detect any problems, they are encouraged to generate a Github issue to report the error.
Here are some examples of speed up factors you can expect. The sample T1-weighted and resting state data use the HCP 3T Imaging Protocol sequences. The tests were run on a laptop with a four core (8 thread, 28w) MacOS laptop:
|Command : Seconds (GZ)||Serial (GZ)||Parallel (GZ)|
|fslmaths rest -s 2.548 out : 270 (424)||5.0x (2.9x)||8.6x (6.3x)|
|fslmaths t1 -kernel boxv 7 -dilM out : 216 (228)||245x (41x)||225x (72x)|
|fslmaths rest -Tmean -mul -1 -add rest out : 101 (328)||2.5x (2.5x)||2.8x (4.5x)|
|niimath rest -demean out (same output as above)||3.5x (3.0x)||4.6x (6.2x)|
|fslmaths rest -bptf 77 8.68 out : 998 (1155)||2.0x (2.0x)||6.8x (6.7x)|
Here are the same testson a desktop computer with twelve cores (24 threads, Ryzen 3900X):
|Command : Seconds (GZ)||Serial (GZ)||Parallel (GZ)|
|fslmaths rest -s 2.548 out : 123 (229)||4.2x (2.4x)||9.9x (12.1x)|
|fslmaths t1 -kernel boxv 7 -dilM out : 156 (159)||371x (37x)||371x (248x)|
|fslmaths rest -Tmean -mul -1 -add rest out : 32 (186)||1.7x (2.5x)||1.8x (7.6x)|
|niimath rest -demean out (same output as above)||2.6x (2.6x)||3.0x (10.8x)|
|fslmaths rest -bptf 77 8.68 out : 887 (1019)||2.6x (2.5x)||23x (23.0x)|
niimath can convert NIfTI images to meshes, suitable for viewing in Surfice, blender, SUMA, FreeSurfer and other tools. The features are based on nii2mesh and the features are almost identical. However, the order of arguments is different to match the expectations of fslmaths/niimath. So the call
nii2mesh -r 1 bet.nii.gz r100.ply becomes
niimath bet.nii.gz -mesh -r 1 r100.ply. As described on the nii2mesh page, you can create independent meshes for each area in an atlas using the command:
niimath D99_atlas_v2.0_right.nii.gz -mesh -p 0 -s 10 -a D99_v2.0_labels_semicolon.txt ./gii/D99s10roi.gii
Both programs allow you to explicitly set the isolevel using the
-i value, so
-i 128 we render a surface for voxels brighter than 128. One minor difference between the programs is that niimath allows you also request
bright using the
-i m and
-i b commands respectively. These use Otsu's method, and typically identify pleasing values. Also, if the user does not specify an isolevel be aware that nii2mesh chooses the middle brightness (the midpoint between the darkest and brightest value) while niimath uses the medium Otsu threshold. The latter is more robust to outliers. Here are examples illustrating this usage:
niimath bet.nii.gz -mesh -i 128 Isolevel128.gii niimath bet.nii.gz -mesh -i d darkIsolevel.gii niimath bet.nii.gz -mesh -i m medIsolevel.gii niimath bet.nii.gz -mesh -i b brightIsolevel.gii
niimath can also be compiled to WebAssembly (Wasm) allowing it to be inserted into web pages and Node.js projects. Here is a live demo with links to source code and instructions.
niimath is licensed under the 2-Clause BSD License. Except where noted, the code was written by Chris Rorden in 2020-2022. The code in
tensor.c was written by Daniel Glen (2004) from the US National Institutes of Health and is not copyrighted (though it is included here with the permission of the author). The FSL team graciously allowed the text strings (help, warning and error messages) to be copied verbatim. The Butterworth Filter Coefficients in
bw.c are from Exstrom Labs and the authors provided permission for it to be included in this project under the LGPL, the file provides additional details. Taylor Hanayik from the FSL group provided pseudo-code for some functions where there is little available documentation. The PolygoniseCube function comes from Cory Bloyd's public domain Marching Cubes example program described here. The bwlabel.cpp file was written by Jesper Andersson, who has explicitly allowed this to be shared using the BSD 2-Clause license. The high performance base64.cpp was written by Jouni Malinen and is distributed under the BSD license. The mesh simplification was written by Sven Forstmann and distributed under the MIT license. It was ported from C++ to C by Chris Rorden. The radixsort.c was written by Cameron Hart (2014) using the zlib license.