Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hanayik committed Apr 23, 2024
0 parents commit c596c7d
Show file tree
Hide file tree
Showing 57 changed files with 8,542 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/ghpages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Build and Deploy
on:
push:
branches:
- main
jobs:
build-and-publish-live-demo:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install and Build
run: |
npm install
npm run build
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: demo # The branch the action should deploy to.
folder: dist # The folder the action should deploy.
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
23 changes: 23 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
MIT License

Copyright (c) 2021 neuroneural/brainchop

Ported to NiiVue 2024 NiiVue developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# NiiVue brainchop

This is an experimental version of [brainchop](https://github.com/neuroneural/brainchop) that uses [NiiVue](https://github.com/niivue/niivue) to segment and visualize voxel based images.

## Live preview

https://niivue.github.io/niivue-brainchop/

## Getting started

### To run

```bash
git clone https://github.com/niivue/niivue-brainchop
cd niivue-brainchop
npm install
npm run dev
```

### To build

```bash
npm run build
```


2,671 changes: 2,671 additions & 0 deletions brainchop.js

Large diffs are not rendered by default.

276 changes: 276 additions & 0 deletions bwlabels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
export class bwlabeler {
// port of https://github.com/rordenlab/niimath/blob/master/src/bwlabel.c
// return voxel address given row A, column B, and slice C
idx(A, B, C, DIM) {
return C * DIM[0] * DIM[1] + B * DIM[0] + A;
} // idx()
// determine if voxels below candidate voxel have already been assigned a label
check_previous_slice(bw, il, r, c, sl, dim, conn, tt) {
// const nabo: number[] = [];
const nabo = new Uint32Array(27);
let nr_set = 0;
if (!sl) {
return 0;
}
const val = bw[this.idx(r, c, sl, dim)];
if (conn >= 6) {
const idx = this.idx(r, c, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (conn >= 18) {
if (r) {
const idx = this.idx(r - 1, c, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (c) {
const idx = this.idx(r, c - 1, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (r < dim[0] - 1) {
const idx = this.idx(r + 1, c, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (c < dim[1] - 1) {
const idx = this.idx(r, c + 1, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
}
if (conn === 26) {
if (r && c) {
const idx = this.idx(r - 1, c - 1, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (r < dim[0] - 1 && c) {
const idx = this.idx(r + 1, c - 1, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (r && c < dim[1] - 1) {
const idx = this.idx(r - 1, c + 1, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (r < dim[0] - 1 && c < dim[1] - 1) {
const idx = this.idx(r + 1, c + 1, sl - 1, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
}
if (nr_set) {
this.fill_tratab(tt, nabo, nr_set);
return nabo[0];
}
else {
return 0;
}
} // check_previous_slice()
// provisionally label all voxels in volume
do_initial_labelling(bw, dim, conn) {
let label = 1;
const kGrowArrayBy = 8192;
let ttn = kGrowArrayBy;
let tt = new Uint32Array(ttn).fill(0);
const il = new Uint32Array(dim[0] * dim[1] * dim[2]).fill(0);
const nabo = new Uint32Array(27);
for (let sl = 0; sl < dim[2]; sl++) {
for (let c = 0; c < dim[1]; c++) {
for (let r = 0; r < dim[0]; r++) {
let nr_set = 0;
const val = bw[this.idx(r, c, sl, dim)];
if (val === 0) {
continue;
}
nabo[0] = this.check_previous_slice(bw, il, r, c, sl, dim, conn, tt);
if (nabo[0]) {
nr_set += 1;
}
if (conn >= 6) {
if (r) {
const idx = this.idx(r - 1, c, sl, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (c) {
const idx = this.idx(r, c - 1, sl, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
}
if (conn >= 18) {
if (c && r) {
const idx = this.idx(r - 1, c - 1, sl, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
if (c && r < dim[0] - 1) {
const idx = this.idx(r + 1, c - 1, sl, dim);
if (val === bw[idx]) {
nabo[nr_set++] = il[idx];
}
}
}
if (nr_set) {
il[this.idx(r, c, sl, dim)] = nabo[0];
this.fill_tratab(tt, nabo, nr_set);
}
else {
il[this.idx(r, c, sl, dim)] = label;
if (label >= ttn) {
ttn += kGrowArrayBy;
const ext = new Uint32Array(ttn);
ext.set(tt);
tt = ext;
}
tt[label - 1] = label;
label++;
}
}
}
}
for (let i = 0; i < label - 1; i++) {
let j = i;
while (tt[j] !== j + 1) {
j = tt[j] - 1;
}
tt[i] = j + 1;
}
return [label - 1, tt, il];
} // do_initial_labelling()
// translation table unifies a region that has been assigned multiple classes
fill_tratab(tt, nabo, nr_set) {
let cntr = 0;
const tn = new Uint32Array(nr_set + 5).fill(0);
const INT_MAX = 2147483647;
let ltn = INT_MAX;
for (let i = 0; i < nr_set; i++) {
let j = nabo[i];
cntr = 0;
while (tt[j - 1] !== j) {
j = tt[j - 1];
cntr++;
if (cntr > 100) {
console.log('\nOoh no!!');
break;
}
}
tn[i] = j;
ltn = Math.min(ltn, j);
}
for (let i = 0; i < nr_set; i++) {
tt[tn[i] - 1] = ltn;
}
} // fill_tratab()
// remove any residual gaps so label numbers are dense rather than sparse
translate_labels(il, dim, tt, ttn) {
const nvox = dim[0] * dim[1] * dim[2];
let ml = 0;
const l = new Uint32Array(nvox).fill(0);
for (let i = 0; i < ttn; i++) {
ml = Math.max(ml, tt[i]);
}
const fl = new Uint32Array(ml).fill(0);
let cl = 0;
for (let i = 0; i < nvox; i++) {
if (il[i]) {
if (!fl[tt[il[i] - 1] - 1]) {
cl += 1;
fl[tt[il[i] - 1] - 1] = cl;
}
l[i] = fl[tt[il[i] - 1] - 1];
}
}
return [cl, l];
} // translate_labels()
// retain only the largest cluster for each region
largest_original_cluster_labels(bw, cl, ls) {
const nvox = bw.length;
const ls2bw = new Uint32Array(cl + 1).fill(0);
const sumls = new Uint32Array(cl + 1).fill(0);
for (let i = 0; i < nvox; i++) {
const bwVal = bw[i];
const lsVal = ls[i];
ls2bw[lsVal] = bwVal;
sumls[lsVal]++;
}
let mxbw = 0;
for (let i = 0; i < cl + 1; i++) {
const bwVal = ls2bw[i];
mxbw = Math.max(mxbw, bwVal);
// see if this is largest cluster of this bw-value
for (let j = 0; j < cl + 1; j++) {
if (j === i) {
continue;
}
if (bwVal !== ls2bw[j]) {
continue;
}
if (sumls[i] < sumls[j]) {
ls2bw[i] = 0;
}
else if (sumls[i] === sumls[j] && i < j) {
ls2bw[i] = 0;
} // ties: arbitrary winner
}
}
const vxs = new Uint32Array(nvox).fill(0);
for (let i = 0; i < nvox; i++) {
vxs[i] = ls2bw[ls[i]];
}
return [mxbw, vxs];
}
// given a 3D image, return a clustered label map
// for an explanation and optimized C code see
// https://github.com/seung-lab/connected-components-3d
bwlabel(img, dim, conn = 26, binarize = false, onlyLargestClusterPerClass = false) {
const start = Date.now();
const nvox = dim[0] * dim[1] * dim[2];
const bw = new Uint32Array(nvox).fill(0);
if (![6, 18, 26].includes(conn)) {
console.log('bwlabel: conn must be 6, 18 or 26.');
return [0, bw];
}
if (dim[0] < 2 || dim[1] < 2 || dim[2] < 1) {
console.log('bwlabel: img must be 2 or 3-dimensional');
return [0, bw];
}
if (binarize) {
for (let i = 0; i < nvox; i++) {
if (img[i] !== 0.0) {
bw[i] = 1;
}
}
}
else {
bw.set(img);
}
let [ttn, tt, il] = this.do_initial_labelling(bw, dim, conn);
if (tt === undefined) {
tt = new Uint32Array(0);
}
const [cl, ls] = this.translate_labels(il, dim, tt, ttn);
console.log(conn + ' neighbor clustering into ' + cl + ' regions in ' + (Date.now() - start) + 'ms');
if (onlyLargestClusterPerClass) {
const [nbw, bwMx] = this.largest_original_cluster_labels(bw, cl, ls);
return [nbw, bwMx];
}
return [cl, ls];
} // bwlabel()
}
Loading

0 comments on commit c596c7d

Please sign in to comment.