Commit 8e8d16fe authored by Taylor Hanayik's avatar Taylor Hanayik
Browse files

working bet with niivue

parent c8722903
......@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FSL dashboard</title>
<title>BET</title>
</head>
<body>
<div id="root"></div>
......
{
"name": "fsl-gui-dashboard",
"name": "fsl-gui-bet",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "fsl-gui-dashboard",
"name": "fsl-gui-bet",
"version": "0.0.0",
"dependencies": {
"@emotion/react": "^11.7.1",
......@@ -13,6 +13,7 @@
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.2.4",
"@mui/material": "^5.2.4",
"@niivue/niivue": "^0.20.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"socket.io-client": "^4.4.0"
......@@ -984,6 +985,17 @@
"react": "^17.0.2"
}
},
"node_modules/@niivue/niivue": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@niivue/niivue/-/niivue-0.20.1.tgz",
"integrity": "sha512-QBj/XfAE6/1xiwku7QBZtNoYDGTc64ozd8ydZV8OBHCMs6UR8daJpiTVg6AJ5jyqHRcj9ejGJownxfriaUYm5Q==",
"dependencies": {
"gl-matrix": "^3.4.3",
"nifti-reader-js": "^0.5.4",
"rxjs": "^7.4.0",
"uuid": "^8.3.2"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
......@@ -1575,6 +1587,11 @@
"node": ">=6.9.0"
}
},
"node_modules/gl-matrix": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
......@@ -1724,6 +1741,14 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/nifti-reader-js": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/nifti-reader-js/-/nifti-reader-js-0.5.4.tgz",
"integrity": "sha1-poNfYlq6P9C5HSVU2ZVFoA0PIWE=",
"dependencies": {
"pako": "*"
}
},
"node_modules/node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
......@@ -1737,6 +1762,11 @@
"node": ">=0.10.0"
}
},
"node_modules/pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz",
"integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
......@@ -1932,6 +1962,14 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rxjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz",
"integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
......@@ -2023,6 +2061,19 @@
"node": ">=4"
}
},
"node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.7.3.tgz",
......@@ -2788,6 +2839,17 @@
"react-is": "^17.0.2"
}
},
"@niivue/niivue": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@niivue/niivue/-/niivue-0.20.1.tgz",
"integrity": "sha512-QBj/XfAE6/1xiwku7QBZtNoYDGTc64ozd8ydZV8OBHCMs6UR8daJpiTVg6AJ5jyqHRcj9ejGJownxfriaUYm5Q==",
"requires": {
"gl-matrix": "^3.4.3",
"nifti-reader-js": "^0.5.4",
"rxjs": "^7.4.0",
"uuid": "^8.3.2"
}
},
"@popperjs/core": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
......@@ -3204,6 +3266,11 @@
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
},
"gl-matrix": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
......@@ -3316,6 +3383,14 @@
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
"dev": true
},
"nifti-reader-js": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/nifti-reader-js/-/nifti-reader-js-0.5.4.tgz",
"integrity": "sha1-poNfYlq6P9C5HSVU2ZVFoA0PIWE=",
"requires": {
"pako": "*"
}
},
"node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
......@@ -3326,6 +3401,11 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz",
"integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg=="
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
......@@ -3473,6 +3553,14 @@
"fsevents": "~2.3.2"
}
},
"rxjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz",
"integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==",
"requires": {
"tslib": "^2.1.0"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
......@@ -3543,6 +3631,16 @@
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"vite": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.7.3.tgz",
......
......@@ -3,8 +3,9 @@
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
"build": "vite build && npm run devinstall",
"preview": "vite preview",
"devinstall": "mkdir -p $HOME/.fslgui/bet && cp -r dist/* $HOME/.fslgui/bet/"
},
"dependencies": {
"@emotion/react": "^11.7.1",
......@@ -12,6 +13,7 @@
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.2.4",
"@mui/material": "^5.2.4",
"@niivue/niivue": "^0.20.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"socket.io-client": "^4.4.0"
......
{
"title": "BET",
"description": "Brain extraction tool",
"keywords": "bet, brain, extraction",
"gui": "bet",
"gui_server": "",
"disabled": false
}
html {
height: 100%;
}
body {
height: 100%;
}
canvas:focus {
outline: none;
}
import { useState, useEffect } from 'react'
import { useState, useEffect, useRef, useCallback } from 'react'
import Box from '@mui/material/Box'
import { Card, CardContent } from '@mui/material'
import { Container, CssBaseline, TextField, Typography, Grid } from '@mui/material'
import { Button, Card, CardContent, Slider, Input, Collapse, Snackbar, FormControlLabel, Checkbox } from '@mui/material'
import { Container, CssBaseline, TextField, Typography, Grid, Divider } from '@mui/material'
import { Niivue } from '@niivue/niivue'
import { io } from "socket.io-client";
import "./App.css"
const nv = new Niivue()
const NiiVue = ({images}) => {
const canvas = useRef()
useEffect(() => {
nv.attachToCanvas(canvas.current)
}, [])
function GUICard(props) {
return (
<Card>
<CardContent sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Typography>{props.gui.title} </Typography>
</CardContent>
<CardContent sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Typography>{props.gui.description} </Typography>
</CardContent>
</Card>
<Grid container item xs={12} m={2} alignItems='center' justifyContent='center' spacing={0} direction='row'>
<canvas ref={canvas} height={200} width={640}></canvas>
</Grid>
)
}
function InputField({text, updateBetOptsValue, socket}) {
return (
<Grid container item xs={12} alignItems='center' spacing={0}>
<Grid item xs={9}>
<TextField
fullWidth
size="small"
label='input file'
onChange={(e) => {updateBetOptsValue('input', e.target.value)}}
value={text}>
</TextField>
</Grid>
<Grid item xs={3} sx={{justifyContent: 'flex-end'}}>
<Button
variant='contained'
style={{margin:0, marginLeft: 12}}
onClick={()=>{socket.emit('files')}}
>
Choose
</Button>
</Grid>
</Grid>
)
}
function Title({text}) {
return (
<Typography variant='h5' sx={{margin: '20px'}}>
{text}
</Typography>
)
}
function OutputField({text, updateBetOptsValue, socket}) {
return (
<Grid container item xs={12} alignItems='center' spacing={0}>
<Grid item xs={9}>
<TextField
fullWidth
size="small"
label='output file'
onChange={(e) => {updateBetOptsValue('output', e.target.value)}}
value={text}
>
</TextField>
</Grid>
<Grid item xs={3} sx={{justifyContent: 'center'}}>
<Button variant='contained' style={{margin:0, marginLeft: 12}}>Choose</Button>
</Grid>
</Grid>
)
}
function GvalueField({g, updateBetOptsValue}) {
return (
<Grid container item xs={12} sx={{marginLeft:'2px'}} alignItems='center' spacing={0}>
<Grid item xs={9}>
<Typography>g value</Typography>
<Slider
onChange={(e) => {updateBetOptsValue('-g', e.target.value)}}
value={g ?? 0}
min={-1.0}
max={1.0}
step={-0.01}
>
</Slider>
</Grid>
<Grid item xs={3} sx={{justifyContent: 'center'}}>
<Input
size='small'
style={{margin:0, marginLeft:12}}
value={g ?? 0}
inputProps={{
step: 0.1,
min: -1,
max: 1,
type: 'number'
}}
onChange={(e) => {updateBetOptsValue('-g', e.target.value)}}
/>
</Grid>
</Grid>
)
}
function FvalueField({f, updateBetOptsValue}) {
return (
<Grid container item xs={12} sx={{marginLeft:'2px'}} alignItems='center' spacing={0}>
<Grid item xs={9}>
<Typography>f value</Typography>
<Slider
onChange={(e) => {updateBetOptsValue('-f', e.target.value)}}
value={f ?? 0.5}
min={0.0}
max={1.0}
step={0.01}
>
</Slider>
</Grid>
<Grid item xs={3} sx={{justifyContent: 'center'}}>
<Input
size='small'
style={{margin:0, marginLeft:12}}
value={f ?? 0.5}
inputProps={{
step: 0.1,
min: 0,
max: 1,
type: 'number'
}}
onChange={(e) => {updateBetOptsValue('-f', e.target.value)}}
/>
</Grid>
</Grid>
)
}
function ActionButtons({handleMoreOptions, commandString, socket}) {
return (
<Grid container item xs={12} alignItems='center' justifyContent='center' spacing={0} direction='row'>
<Button
variant='contained'
style={{margin:0}}
onClick={()=>{socket.emit('run', {'run': commandString})}}
>
Run
</Button>
<Button style={{margin:0}} onClick={handleMoreOptions}>more options</Button>
</Grid>
)
}
export default function App() {
function BetOptionCheckBox({optionValue, updateBetOptsValue, label}) {
return (
<FormControlLabel
control={<Checkbox checked={optionValue} onChange={updateBetOptsValue}/>}
label={label}
/>
)
}
function OptionsContainer({moreOptions, options, updateBetOptsValue}) {
return (
<Collapse in={moreOptions} timeout="auto" unmountOnExit>
<Grid container item xs={12} m={2} spacing={0} direction='column'>
<BetOptionCheckBox
optionValue={options['-o']}
label={'create brain outline image'}
updateBetOptsValue={() => {updateBetOptsValue('-o', !options['-o'])}}
/>
<BetOptionCheckBox
optionValue={options['-m']}
label={'output binary brain mask'}
updateBetOptsValue={() => {updateBetOptsValue('-m', !options['-m'])}}
/>
<BetOptionCheckBox
optionValue={options['-s']}
label={'output approximate skull image'}
updateBetOptsValue={() => {updateBetOptsValue('-s', !options['-s'])}}
/>
<BetOptionCheckBox
optionValue={options['-n']}
label={'do not output final brain image'}
updateBetOptsValue={() => {updateBetOptsValue('-n', !options['-n'])}}
/>
<BetOptionCheckBox
optionValue={options['-t']}
label={'apply thresholding to brain and mask'}
updateBetOptsValue={() => {updateBetOptsValue('-t', !options['-t'])}}
/>
<BetOptionCheckBox
optionValue={options['-e']}
label={'save brain surface mesh'}
updateBetOptsValue={() => {updateBetOptsValue('-e', !options['-e'])}}
/>
<BetOptionCheckBox
optionValue={options['-v']}
label={'switch on diagnostic messages'}
updateBetOptsValue={() => {updateBetOptsValue('-v', !options['-v'])}}
/>
<BetOptionCheckBox
optionValue={options['-d']}
label={'debug: do not delete intermediate images'}
updateBetOptsValue={() => {updateBetOptsValue('-d', !options['-d'])}}
/>
</Grid>
</Collapse>
)
}
function CommandStringPreview({commandString}) {
return (
<Grid
container
item
xs={12}
alignItems='center'
justifyContent='center'
spacing={0}
direction='row'
>
<Typography>
{`command: ${commandString}`}
</Typography>
</Grid>
)
}
export default function Bet() {
const title = "BET"
const defaultBetOpts = {
'input': 'input', // input file path
'output': 'output', // output file path
'-o': false,
'-m': false,
'-s': false,
'-n': false,
'-f': 0.5,
'-g': 0.0,
'-r': null,
'-c': null,
'-t': false,
'-e': false,
'-R': null,
'-S': null,
'-B': null,
'-Z': null,
'-F': null,
'-A': null,
'-A2': null,
'-v': false,
'-d': false
}
const [betOpts, setBetOpts] = useState(defaultBetOpts)
// the boolean variable to show or hide the snackbar
const [showSnackBar, setShowSnackBar] = useState(false)
// the message, and message setter for the snackbar
const [snackBarMessage, setSnackBarMessage] = useState('')
const [moreOptions, setMoreOptions] = useState(false)
const [commandString, setCommandString] = useState('')
//const [socket, setSocket] = useState(null)
let urlParams
let host = ''
let port = ''
useEffect( () => {
console.log(window.location.search)
urlParams = new URLSearchParams(window.location.search)
host = urlParams.get('host')
port = urlParams.get('port')
console.log(host, port)
} , [])
const title = "BET"
let socketServerPort = ''
let fileServerPort = ''
urlParams = new URLSearchParams(window.location.search)
host = urlParams.get('host')
socketServerPort = urlParams.get('socketServerPort')
fileServerPort = urlParams.get('fileServerPort')
console.log(host, socketServerPort, fileServerPort)
if (host === null || socketServerPort === null || fileServerPort === null){
setSnackBarMessage('unable to contact backend application')
}
const socket = io(`ws://${host}:${socketServerPort}`)
socket.on('files', (data) => {
console.log(data)
updateBetOptsValue('input', data[0])
nv.loadVolumes([
{url: `http://${host}:${fileServerPort}/file/?filename=${data[0]}`}
])
})
socket.on('run', (data) => {
console.log('run', data)
nv.loadVolumes([
{url: `http://${host}:${fileServerPort}/file/?filename=${betOpts['output']}`}
])
})
function handleSnackBarClose() {
setShowSnackBar(false)
}
function showSnackBarIfMsg() {
if (snackBarMessage !== '') {
setShowSnackBar(true)
}
}
// whenever the snackbar message changes, run the effect (e.g. show the snackbar to the user when the message is updated)
useEffect(() => {showSnackBarIfMsg()}, [snackBarMessage])
useEffect(() => {updateCommandString()}, [betOpts])
function setOutputName(fileName) {
if (fileName === '') {
return ''
}
let extIdx = fileName.lastIndexOf('.nii')
if (extIdx < 0) {
return fileName + '_brain.nii.gz'
} else {
return fileName.substring(0, extIdx) + '_brain.nii.gz'
}
}
function updateCommandString() {
let command = 'bet '
for (let [key, value] of Object.entries({...betOpts})) {
if (value != null) {
if (value == false){
continue
} else if (value == true) {
// if true the just pass the key to set boolean bet values
value = ''
}
command += `${(key=='input' || key=='output') ? '': key} ${value} `