UNPKG

@niivue/niimath

Version:

A javascript library to easily use the WASM build of Chris Rorden's niimath command line program written in C

832 lines (830 loc) 20.3 kB
// src/niimathOperators.json var niimathOperators_default = { bptfm: { args: [ "hp", "lp" ], help: "Same as bptf but does not remove mean (emulates fslmaths < 5.0.7)" }, bwlabel: { args: [ "conn" ], help: "Connected component labelling for non-zero voxels (conn sets neighbors: 6, 18, 26)" }, c2h: { args: [], help: "reverse h2c transform" }, ceil: { args: [], help: "round voxels upwards to the nearest integer" }, ras: { args: [], help: "reorder and flip dimensions to RAS orientation" }, conform: { args: [], help: "reslice to 1mm size in coronal slice direction with 256^3 voxels" }, comply: { args: [ "nx", "ny", "nz", "dx", "dy", "dz", "f_high", "isLinear" ], help: "conform to axial slice with dx*dy*dzmm size and dx*dy*dz voxels. f_high bright clamping (0.98 for top 2%). Linear (1) or nearest-neighbor (0)" }, crop: { args: [ "tmin", "tsize" ], help: "remove volumes, starts with 0 not 1! Inputting -1 for a size will set it to the full range" }, dehaze: { args: [ "mode" ], help: "set dark voxels to zero (mode 1..5; higher yields more surviving voxels)" }, detrend: { args: [], help: "remove linear trend (and mean) from input" }, demean: { args: [], help: "remove average signal across volumes (requires 4D input)" }, edt: { args: [], help: "estimate Euler Distance Transform (distance field). Assumes isotropic input" }, close: { args: [ "thr", "dx1", "dx2" ], help: "morphological close that binarizes with `thr`, dilates with `dx1` and erodes with `dx2` (fills bubbles with `thr`)" }, floor: { args: [], help: "round voxels downwards to the nearest integer" }, gz: { args: [ "mode" ], help: "NIfTI gzip mode (0=uncompressed, 1=compressed, else FSL environment; default -1)" }, h2c: { args: [], help: "convert CT scans from 'Hounsfield' to 'Cormack' units to emphasize soft tissue contrast" }, mesh: { args: [], help: "meshify requires 'd'ark, 'm'edium, 'b'right or numeric isosurface ('niimath bet -mesh -i d mesh.gii')", subOperations: { i: { args: [ "isovalue" ], help: "'d'ark, 'm'edium, 'b'right or numeric isosurface" }, a: { args: [ "atlasFile" ], help: "roi based atlas to mesh" }, b: { args: [ "fillBubbles" ], help: "fill bubbles" }, l: { args: [ "onlyLargest" ], help: "only largest" }, o: { args: [ "originalMC" ], help: "original marching cubes" }, q: { args: [ "quality" ], help: "quality" }, s: { args: [ "postSmooth" ], help: "post smooth" }, r: { args: [ "reduceFraction" ], help: "reduce fraction" }, v: { args: [ "verbose" ], help: "verbose" } } }, hollow: { args: [ "threshold", "thickness" ], help: "hollow out a mesh" }, mod: { args: [], help: "modulus fractional remainder - same as '-rem' but includes fractions" }, otsu: { args: [ "mode" ], help: "binarize image using Otsu's method (mode 1..5; higher yields more bright voxels)" }, power: { args: [ "exponent" ], help: "raise the current image by following exponent" }, qform: { args: [ "code" ], help: "set qform_code" }, sform: { args: [ "code" ], help: "set sform_code" }, p: { args: [ "threads" ], help: "set maximum number of parallel threads. DISABLED: recompile for OpenMP support" }, resize: { args: [ "X", "Y", "Z", "m" ], help: "grow (>1) or shrink (<1) image. Method <m> (0=nearest,1=linear,2=spline,3=Lanczos,4=Mitchell)" }, round: { args: [], help: "round voxels to the nearest integer" }, sobel: { args: [], help: "fast edge detection" }, sobel_binary: { args: [], help: "sobel creating binary edge" }, tensor_2lower: { args: [], help: "convert FSL style upper triangle image to NIfTI standard lower triangle order" }, tensor_2upper: { args: [], help: "convert NIfTI standard lower triangle image to FSL style upper triangle order" }, tensor_decomp_lower: { args: [], help: "as tensor_decomp except input stores lower diagonal (AFNI, ANTS, Camino convention)" }, trunc: { args: [], help: "truncates the decimal value from floating point value and returns integer value" }, unsharp: { args: [ "sigma", "scl" ], help: "edge enhancing unsharp mask (sigma in mm, not voxels [1 is typical]; scl is amount [0.5 medium, 1.0 heavy])" }, dog: { args: [ "sPos", "sNeg" ], help: "difference of gaussian with zero-crossing edges (positive and negative sigma mm)" }, dogr: { args: [ "sPos", "sNeg" ], help: "as dog, without zero-crossing (raw rather than binarized data)" }, dogx: { args: [ "sPos", "sNeg" ], help: "as dog, zero-crossing for 2D sagittal slices" }, dogy: { args: [ "sPos", "sNeg" ], help: "as dog, zero-crossing for 2D coronal slices" }, dogz: { args: [ "sPos", "sNeg" ], help: "as dog, zero-crossing for 2D axial slices" }, add: { args: [ "input" ], help: "add following input to current image" }, sub: { args: [ "input" ], help: "subtract following input from current image" }, mul: { args: [ "input" ], help: "multiply current image by following input" }, div: { args: [ "input" ], help: "divide current image by following input" }, rem: { args: [ "number" ], help: "modulus remainder - divide current image by following input and take remainder" }, mas: { args: [ "file" ], help: "use (following image>0) to mask current image" }, thr: { args: [ "number" ], help: "use following number to threshold current image (zero anything below the number)" }, thrp: { args: [ "input" ], help: "use following percentage (0-100) of ROBUST RANGE to threshold current image (zero anything below the number)" }, thrP: { args: [ "input" ], help: "use following percentage (0-100) of ROBUST RANGE of non-zero voxels and threshold below" }, uthr: { args: [ "number" ], help: "use following number to upper-threshold current image (zero anything above the number)" }, uthrp: { args: [ "input" ], help: "use following percentage (0-100) of ROBUST RANGE to upper-threshold current image (zero anything above the number)" }, uthrP: { args: [ "input" ], help: "use following percentage (0-100) of ROBUST RANGE of non-zero voxels and threshold above" }, clamp: { args: [ "input" ], help: "use following percentage (0-100) of ROBUST RANGE to threshold current image (anything below set to this threshold)" }, uclamp: { args: [ "input" ], help: "use following percentage (0-100) of ROBUST RANGE to threshold current image (anything above set to this threshold)" }, max: { args: [ "input" ], help: "take maximum of following input and current image" }, min: { args: [ "input" ], help: "take minimum of following input and current image" }, seed: { args: [ "number" ], help: "seed random number generator with following number" }, restart: { args: [ "file" ], help: "replace the current image with input for future processing operations" }, save: { args: [], help: "save the current working image to the input filename" }, inm: { args: [ "mean" ], help: "(-i i ip.c) intensity normalisation (per 3D volume mean)" }, ing: { args: [ "mean" ], help: "(-I i ip.c) intensity normalisation, global 4D mean)" }, s: { args: [ "sigma" ], help: "create a gauss kernel of sigma mm and perform mean filtering" }, exp: { args: [], help: "exponential" }, log: { args: [], help: "natural logarithm" }, sin: { args: [], help: "sine function" }, cos: { args: [], help: "cosine function" }, tan: { args: [], help: "tangent function" }, asin: { args: [], help: "arc sine function" }, acos: { args: [], help: "arc cosine function" }, atan: { args: [], help: "arc tangent function" }, sqr: { args: [], help: "square" }, sqrt: { args: [], help: "square root" }, recip: { args: [], help: "reciprocal (1/current image)" }, abs: { args: [], help: "absolute value" }, bin: { args: [], help: "use (current image>0) to binarise" }, binv: { args: [], help: "binarise and invert (binarisation and logical inversion)" }, fillh: { args: [], help: "fill holes in a binary mask (holes are internal - i.e. do not touch the edge of the FOV)" }, fillh26: { args: [], help: "fill holes using 26 connectivity" }, index: { args: [], help: "replace each nonzero voxel with a unique (subject to wrapping) index number" }, grid: { args: [ "value", "spacing" ], help: "add a 3D grid of intensity <value> with grid spacing <spacing>" }, edge: { args: [], help: "edge strength" }, tfce: { args: [ "H", "E", "connectivity" ], help: "enhance with TFCE, e.g. -tfce 2 0.5 6 (maybe change 6 to 26 for skeletons)" }, tfceS: { args: [ "H", "E", "connectivity", "X", "Y", "Z", "tfce_thresh" ], help: "show support area for voxel (X,Y,Z)" }, nan: { args: [], help: "replace NaNs (improper numbers) with 0" }, nanm: { args: [], help: "make NaN (improper number) mask with 1 for NaN voxels, 0 otherwise" }, rand: { args: [], help: "add uniform noise (range 0:1)" }, randn: { args: [], help: "add Gaussian noise (mean=0 sigma=1)" }, range: { args: [], help: "set the output calmin/max to full data range" }, tensor_decomp: { args: [], help: "convert a 4D (6-timepoint )tensor image into L1,2,3,FA,MD,MO,V1,2,3 (remaining image in pipeline is FA)" }, kernel: { subOperations: { "3D": { args: [], help: "3x3x3 box centered on target voxel (set as default kernel)" }, "2D": { args: [], help: "3x3x1 box centered on target voxel" }, box: { args: [ "size" ], help: "all voxels in a cube of width <size> mm centered on target voxel" }, boxv: { args: [ "size" ], help: "all voxels in a cube of width <size> voxels centered on target voxel, CAUTION: size should be an odd number" }, boxv3: { args: [ "X", "Y", "Z" ], help: "all voxels in a cuboid of dimensions X x Y x Z centered on target voxel, CAUTION: size should be an odd number" }, gauss: { args: [ "sigma" ], help: "gaussian kernel (sigma in mm, not voxels)" }, sphere: { args: [ "size" ], help: "all voxels in a sphere of radius <size> mm centered on target voxel" }, file: { args: [ "filename" ], help: "use external file as kernel" } } }, dilM: { args: [], help: "Mean Dilation of non-zero voxels" }, dilD: { args: [], help: "Maximum Dilation of non-zero voxels (emulating output of fslmaths 6.0.1, max not modal)" }, dilF: { args: [], help: "Maximum filtering of all voxels" }, dilall: { args: [], help: "Apply -dilM repeatedly until the entire FOV is covered" }, ero: { args: [], help: "Erode by zeroing non-zero voxels when zero voxels found in kernel" }, eroF: { args: [], help: "Minimum filtering of all voxels" }, fmedian: { args: [], help: "Median Filtering" }, fmean: { args: [], help: "Mean filtering, kernel weighted (conventionally used with gauss kernel)" }, fmeanu: { args: [], help: "Mean filtering, kernel weighted, un-normalized (gives edge effects)" }, subsamp2: { args: [], help: "downsamples image by a factor of 2 (keeping new voxels centered on old)" }, subsamp2offc: { args: [], help: "downsamples image by a factor of 2 (non-centered)" }, Tmean: { args: [], help: "mean across time" }, Tstd: { args: [], help: "standard deviation across time" }, Tmax: { args: [], help: "max across time" }, Tmaxn: { args: [], help: "time index of max across time" }, Tmin: { args: [], help: "min across time" }, Tmedian: { args: [], help: "median across time" }, Tperc: { args: [ "percentage" ], help: "nth percentile (0-100) of FULL RANGE across time" }, Tar1: { args: [], help: "temporal AR(1) coefficient (use -odt float and probably demean first)" }, pval: { args: [], help: "Nonparametric uncorrected P-value, assuming timepoints are the permutations; first timepoint is actual (unpermuted) stats image" }, pval0: { args: [], help: "Same as -pval, but treat zeros as missing data" }, cpval: { args: [], help: "Same as -pval, but gives FWE corrected P-values" }, ztop: { args: [], help: "Convert Z-stat to (uncorrected) P" }, ptoz: { args: [], help: "Convert (uncorrected) P to Z" }, ztopc: { args: [], help: "Convert Z-stat to (uncorrected but clamped) P" }, ptozc: { args: [], help: "Convert (uncorrected but clamped) P to Z" }, rank: { args: [], help: "Convert data to ranks (over T dim)" }, ranknorm: { args: [], help: "Transform to Normal dist via ranks" }, roi: { args: [ "xmin", "xsize", "ymin", "ysize", "zmin", "zsize", "tmin", "tsize" ], help: "zero outside roi (using voxel coordinates). Inputting -1 for a size will set it to the full image extent for that dimension" }, bptf: { args: [ "hp_sigma", "lp_sigma" ], help: "(-t in ip.c) Bandpass temporal filtering; nonlinear highpass and Gaussian linear lowpass (with sigmas in volumes, not seconds); set either sigma<0 to skip that filter" }, roc: { args: [ "AROC-thresh", "outfile", "truth" ], help: "take (normally binary) truth and test current image in ROC analysis against truth. <AROC-thresh> is usually 0.05 and is limit of Area-under-ROC measure FP axis. <outfile> is a text file of the ROC curve (triplets of values: FP TP threshold). If the truth image contains negative voxels these get excluded from all calculations. If <AROC-thresh> is positive then the [4Dnoiseonly] option needs to be set, and the FP rate is determined from this noise-only data, and is set to be the fraction of timepoints where any FP (anywhere) is seen, as found in the noise-only 4d-dataset. This is then controlling the FWE rate. If <AROC-thresh> is negative the FP rate is calculated from the zero-value parts of the <truth> image, this time averaging voxelwise FP rate over all timepoints. In both cases the TP rate is the average fraction of truth=positive voxels correctly found" } }; // src/index.js var Niimath = class { constructor() { this.worker = null; this.operators = niimathOperators_default; this.outputDataType = "float"; this.dataTypes = { char: "char", short: "short", int: "int", float: "float", double: "double", input: "input" }; } init() { this.worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" }); return new Promise((resolve, reject) => { this.worker.onmessage = (event) => { if (event.data && event.data.type === "ready") { resolve(true); } }; this.worker.onerror = (error) => { reject(new Error(`Worker failed to load: ${error.message}`)); }; }); } setOutputDataType(type) { if (Object.values(this.dataTypes).includes(type)) { this.outputDataType = type; } else { throw new Error(`Invalid data type: ${type}`); } } image(file) { return new ImageProcessor({ worker: this.worker, file, operators: this.operators, outputDataType: this.outputDataType }); } }; var ImageProcessor = class { constructor({ worker, file, operators, outputDataType }) { this.worker = worker; this.file = file; this.operators = operators; this.commands = []; this.outputDataType = outputDataType ? outputDataType : "float"; this._generateMethods(); } _addCommand(cmd, ...args) { this.commands.push(cmd, ...args.map(String)); return this; } _generateMethods() { Object.keys(this.operators).forEach((methodName) => { const definition = this.operators[methodName]; if (methodName === "kernel") { Object.keys(definition.subOperations).forEach((subOpName) => { const subOpDefinition = definition.subOperations[subOpName]; this[`kernel${subOpName.charAt(0).toUpperCase() + subOpName.slice(1)}`] = (...args) => { if (args.length !== subOpDefinition.args.length) { throw new Error(`Expected ${subOpDefinition.args.length} arguments for kernel ${subOpName}, but got ${args.length}`); } return this._addCommand("-kernel", subOpName, ...args); }; }); } else if (methodName === "mesh") { this.mesh = (options = {}) => { const subCommands = []; Object.keys(options).forEach((subOptionKey) => { if (definition.subOperations[subOptionKey]) { const subOpDefinition = definition.subOperations[subOptionKey]; const subOptionValue = options[subOptionKey]; if (subOpDefinition.args.length > 0 && subOptionValue === void 0) { throw new Error(`Sub-option -${subOptionKey} requires a value.`); } subCommands.push(`-${subOptionKey}`); if (subOpDefinition.args.length > 0) { subCommands.push(subOptionValue); } } else { throw new Error(`Invalid sub-option -${subOptionKey} for mesh.`); } }); return this._addCommand("-mesh", ...subCommands); }; } else { this[methodName] = (...args) => { if (args.length < definition.args.length || !definition.optional && args.length > definition.args.length) { throw new Error(`Expected ${definition.args.length} arguments for ${methodName}, but got ${args.length}`); } return this._addCommand(`-${methodName}`, ...args); }; } }); } async run(outName = "output.nii") { return new Promise((resolve, reject) => { this.worker.onmessage = (e) => { if (e.data.type === "error") { reject(new Error(e.data.message)); } else { const { blob, exitCode } = e.data; if (exitCode === 0) { resolve(blob); } else { reject(new Error(`niimath processing failed with exit code ${exitCode}`)); } } }; const args = [this.file.name, ...this.commands, outName, "-odt", this.outputDataType]; if (this.worker === null) { reject(new Error("Worker not initialized. Did you await the init() method?")); } this.worker.postMessage({ blob: this.file, cmd: args, outName }); }); } }; export { Niimath };