@brainchop/mindgrab

中文文档
0.1.20261029发布于 2026/10/31MITTypeScriptES Module近 30 天下载 1.7万

Browser MeshNet brain segmentation (MindGrab skull stripping, 18- and 104-class parcellation, GM/WM/CSF fractions) on WebGPU, with WebGL2 and threaded-CPU fallbacks

原始使用文档
文档目录10 节
  1. Continuous tissue maps
  2. Backends
  3. The CPU fallback needs two headers
  4. Requirements
  5. Models
  6. Options
  7. Bundlers
  8. Errors
  9. Accuracy
  10. Building

@brainchop/mindgrab

Browser MeshNet brain segmentation on WebGPU, with WebGL2 and threaded-CPU fallbacks: MindGrab skull stripping, 18-class parcellation at two widths (16chan18cls, mindmap), and a 104-class Desikan-Killiany parcellation (mindsnap), over a fixed 256³ grid. A NIfTI ArrayBuffer goes in and a NIfTI ArrayBuffer comes out.

The whole pipeline — conform, normalise, the 13 or 25 MeshNet layers, classify, largest-component, reslice — runs inside a wasm module compiled from brainchop-c. This package owns only what that module deliberately does not: gzip, WebGPU device and WebGL2 context acquisition, the cross-origin-isolation probe, and the decision to refuse.

import { segment } from '@brainchop/mindgrab'

const input = await file.arrayBuffer()          // .nii or .nii.gz
const { image, elapsedMs } = await segment(input, { model: 'mindgrab' })
// image is gzipped if the input was

MindMap now filters selected tissue classes and refills suppressed speckles from surviving neighboring labels by default. Use { model: 'mindmap', legacyCleanup: true } for the original whole-brain-only cleanup. This requires wasm assets rebuilt from the same revision as the wrapper.

Categorical native-space output uses the browser's eight-sample majority vote, including background votes outside the source and first-sample tie breaking. Masks still use nearest neighbor; continuous tissue maps use linear resampling. Invalid, empty, more than 95% filled, or excessively fragmented categorical results fail with inference-failed before cleanup can disguise the failure.

Continuous tissue maps

import { segmentTissues } from '@brainchop/mindgrab'

const { tissues, backend } = await segmentTissues(input, {
  model: 'mindmap', worker: true,
})
// tissues.gm, tissues.wm, tissues.csf and tissues.brain (GM + WM) are NIfTI
// ArrayBuffers. Native geometry and input gzip encoding are preserved by default.

tissues.brain's 0.5 isosurface is a sub-voxel pial surface, ready to mesh.

segmentTissues supports mindmap and 16chan18cls on WebGPU, WebGL2 and CPU. The GPU paths compute grouped priors, blur and support gating on the device; the shared C CAT-lite fitter runs on CPU. Fitting uses the MindMap-calibrated constants for both models. Model16 coverage checks implementation agreement, not tissue-model calibration. saveConform and gzipOutput work as for segment; mask, border and legacy categorical cleanup options are refused. The WebGL2 module reserves a 768 MiB wasm heap for the host-side tissue fit.

Backends

auto (the default) tries hardware webgpu, hardware webgl2, then cpu (cross-origin isolated pages only), reports which one ran in result.backend, and throws no-webgpu if none can. It never picks a renderer identified as software (SwiftShader, llvmpipe, lavapipe, WARP, or a WebGPU fallback adapter): they pass every capability probe but ran MindGrab in 990 s against the CPU module's ~10 s. checkSupport() and checkWebgl2Support() report software: true for them. A masked WebGL2 renderer string cannot be identified (software: undefined), so it runs only when cpu cannot. Whatever runs, an empty MindGrab mask rejects with inference-failed, as empty labels do. A named backend still runs a software one, but outlives the default timeoutMs.

MindGrab on an M4 Pro, as time inside the wasm module and as what a segment() call actually costs. The difference is this package's own work — fetching and instantiating the module, gzip and gunzip, and copying the volume into and out of the module's filesystem — not the segmentation, which is wholly inside the first number:

backendneedsin the moduleper call
'webgpu'WebGPU with shader-f16, 512 MiB buffers, a secure context1.6 s2.4 s
'webgl2'WebGL2 with EXT_color_buffer_float3.4 s4.1 s, and it BLOCKS the calling thread — use worker: true
'cpu'a cross-origin isolated page (see below) and ~2.15 GB of growable shared memory~10 s on 14 cores~12.6 s

The CPU row's 2.6 s of wrapper time is about 3× the GPU rows' 0.7–0.8 s, which is not an arithmetic slip: it is a separate and larger module, and instantiating it spins up a worker per core.

WebGL2 is what Linux Firefox and Chrome take today, since neither enables WebGPU by default; those stacks are known to run correctly but have never been timed, so the figures above are macOS. Naming a backend explicitly makes an unsupported choice an error rather than a silent downgrade. The CPU module is the same fp32 C the command-line executables run and is bit-exact with them; the two GPU paths are fp16 and carry the voxel budgets under Accuracy.

The CPU fallback needs two headers

It is a pthreads build, so it imports shared memory, and a page that is not cross-origin isolated cannot instantiate it — the module hangs rather than failing, which is why this is checked before it is fetched. Serve the top-level document with:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Putting them on the .wasm response instead does nothing: isolation is a property of the document. Note the cost, which is the usual reason a deployment turns isolation back off again: under require-corp every cross-origin subresource the page loads — images, fonts, analytics, embeds — must itself send Cross-Origin-Resource-Policy or proper CORS, or it will be blocked. Then:

import { checkCpuSupport } from '@brainchop/mindgrab'

const cpu = checkCpuSupport()
if (!cpu.supported) console.warn(cpu.reasons.join('\n'))

Playwright's patched Firefox Nightly runs this module about 20× slower than Chromium, but installed Firefox 153.0.1 ran the same bit-exact model16 case in 7.6 s. The Nightly result is retained as a harness/compiler anomaly, not a warning about Firefox users generally.

Requirements

For the WebGPU path: WebGPU with the shader-f16 feature, maxStorageBufferBindingSize and maxBufferSize of at least 512 MiB (one f16 activation buffer), and a secure context — https or localhost. about:blank and file:// are not secure contexts, and there navigator.gpu is undefined, which looks exactly like a browser with no WebGPU at all.

Ask before you offer the user a button:

import { checkSupport } from '@brainchop/mindgrab'

const report = await checkSupport()
if (!report.supported) console.warn(report.reasons.join('\n'))

Models

modeldoesoutput
'mindgrab'skull stripping in any modalitythe input image with non-brain voxels floored
'16chan18cls'18-class brain segmentationlabel volume
'mindmap'18-class brain segmentation (24-channel)label volume
'mindsnap'104-class Desikan-Killiany parcellationlabel volume

Each is a separate wasm module with its weights compiled in, fetched only when you ask for it. Options are per-model and the mismatched ones are refused before anything is downloaded, because an executable never advertises what it would decline:

await segment(buf, { model: 'mindgrab', saveConform: true })
// BrainchopError: mindgrab does not support `saveConform`: its output is the
// input image with non-brain voxels floored, so it is inherently in the input space

Each MODELS entry (and listModels()) carries colormap: { R, G, B, labels }, one entry per output value, index 0 background (for mindgrab, its mask). It is the model's own colormap.json, in the shape viewers such as NiiVue take directly: nv.setColormapLabel(1, MODELS.mindsnap.colormap).

Options

segment(input: ArrayBuffer | ArrayBufferView, options: SegmentOptions)
optionmodelsmeaning
modelall fourrequired; there is no sensible default among them
ctall fourinput is CT; convert Hounsfield to Cormack units
complyall fourinsert the compliance transform before conform
saveConform16chan18cls, mindmap, mindsnapreturn the conformed 256³ volume instead of reslicing to the input grid
maskmindgrabalso return a binary brain mask in result.mask
borderMmmindgrabgrow the mask border, in mm; implies mask
gzipOutputall fouroverride the default, which matches the input
workerall fourrun the whole segmentation in a Web Worker. Recommended, and the only way to keep a page responsive during a webgl2 run, which is synchronous
deviceall fourreuse a GPUDevice you already own
glContextall fourreuse a WebGL2RenderingContext you already own; implies webgl2
assetPathall fourbase URL the worker and model's .js/.wasm are served from, when the host serves dist/ itself; bundlers need none, see below
timeoutMsall fourinference limit (default 120000; 900000 for cpu, and for auto under worker: true, where the backend is chosen after the timer is armed). Module loading/instantiation has its own limit: the lesser of timeoutMs and two minutes, so a stalled pthread startup cannot masquerade as a slow CPU run. A lost GPU device otherwise never settles; only worker: true cancels for real
onLogall fourreceives the module's output lines

Output is in the input image's own space by default, for every model. This inverts brainchop-cli, where the conformed grid is the default.

--crop and --export-classes, which the native executables offer, are deliberately absent. Both force the CPU engine inside the GPU modules, which is compiled without threads there and takes 141–266 s per volume; accepting them would turn a two-second call into a four-minute one with no warning. The cpu backend is a different module and does not change this.

Bundlers

No copying and no assetPath: the worker and each model's glue are imported with literal new URL('./worker.js', import.meta.url) and import('./<glue>.js') specifiers, and each glue locates its .wasm the same way, so Vite, Rollup and webpack emit all of them (hashed) and a caller still downloads only the model and backend it runs. Vite needs two settings: worker.format: 'es' for vite build (the default iife cannot hold the worker's top-level await), and optimizeDeps.exclude for vite dev (the dev pre-bundler moves the package into .vite/deps/, where the worker URL 404s):

// vite.config.js
export default {
  optimizeDeps: { exclude: ['@brainchop/mindgrab'] },
  worker: { format: 'es' },
}

A bundler emits every module it can see, so @brainchop/mindgrab deploys all twelve (~10 MB). A host that runs one model imports that model's entry instead, which has the same API and deploys only its three modules (MindGrab: ~2.6 MB under vite build):

import { segment } from '@brainchop/mindgrab/mindgrab'   // or /16chan18cls, /mindmap, /mindsnap

listModels() there returns that one model (MODELS still describes all four), and any other model is refused with unsupported-option (unless assetPath is set, which loads by URL).

esbuild and webpack also follow the imports but do not stub the Node-only node:* imports that the CPU modules carry (they also run under Node): mark them external (esbuild --external:'node:*', webpack externals: /^node:/). esbuild does not emit new URL() assets, so an esbuild host also sets assetPath.

assetPath remains for hosts that serve dist/ themselves: copy the worker and the model's .js and .wasm somewhere served and point assetPath at it. It must be same-origin; it feeds a dynamic import().

Errors

Every package error throws a BrainchopError with a code you can branch on: no-webgpu, no-adapter, device-too-small, no-f16, unsupported-option, bad-input, initialization-failed, inference-failed. initialization-failed means the module never started, so retrying can be meaningful; inference-failed means it started and did not finish. There is no fallback behind any of them, by decision — a silently downgraded segmentation is worse than a refusal. auto choosing between backends is not an exception: it happens before anything is fetched and only when you expressed no preference.

no-webgpu is also what a cpu refusal reports — "this page is not cross-origin isolated" arrives under that code rather than a fourth one, so read the message, or call checkCpuSupport() first.

Accuracy

The cpu backend is held to zero differing voxels against frozen CPU references. The model16 native-space majority fixture has a separate wasm reference: one pre-existing native/wasm conformed label difference is sampled by the new vote. It was reproduced from unmodified merged main and the new fixture independently verified against the browser voting function. See reference provenance. The two GPU paths are fp16, held to the same per-voxel budgets as the project's Metal backend rather than to bit-exactness. Historical nearest-neighbor results on the project's fixtures, as voxels of 16.7M (2 mm case, 1.6M):

casebudgetwebgpuwebgl2
MindGrab643733
18-class (16-channel)1024682710
18-class (16-channel), native 2 mm2568894
mindmap (24-channel, 18-class)1024564610
mindmap, native 2 mm2568886
mindsnap (104-way argmax)204812801219
mindsnap, native 2 mm512198184

The current majority-vote checks retain these budgets. Tissue maps retain the existing 2/255 RMSE gate over the nonzero union and exact NIfTI geometry.

Building

npm install
npm run build     # build all three backends' modules, stage into dist/, types, bundle
npm test          # drives the built package in a real browser

The test runs headed — headless Chromium on macOS has no GPU — and needs a checkout with playwright installed (BC_PLAYWRIGHT_ROOT).

版本与发布记录

@brainchop/mindgrab 共发布 7 个版本,首次发布于 2026/08/13;过去 12 个月发布了 7 个版本。

版本发布日期距今
0.1.20261029latest2026/10/318 天前
0.1.20261021beta2026/10/2217 天前
0.1.202610082026/10/081 个月前
0.1.202609252026/09/251 个月前
0.1.202609232026/09/231 个月前
0.1.202609162026/09/171 个月前

关于 @brainchop/mindgrab 的常见问题

@brainchop/mindgrab 是什么?

Browser MeshNet brain segmentation (MindGrab skull stripping, 18- and 104-class parcellation, GM/WM/CSF fractions) on WebGPU, with WebGL2 and threaded-CPU fallbacks

如何安装 @brainchop/mindgrab?

在项目目录运行 npm install @brainchop/mindgrab,或使用 pnpm add / yarn add / bun add。

@brainchop/mindgrab 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@brainchop/mindgrab 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@brainchop/mindgrab 可以商用吗?

@brainchop/mindgrab 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@brainchop/mindgrab 的下载量有多少?

根据 npm 官方下载统计,@brainchop/mindgrab 在 2026/10/09 – 2026/11/07 这 30 天内被下载 17,367 次,日均约 579 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@brainchop/mindgrab 还在维护吗?

最新版本 0.1.20261029 发布于 2026/10/31(8 天前);至今共发布 7 个版本,其中过去 12 个月发布了 7 个;首次发布于 2026/08/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。