UNPKG

@niivue/niivue

Version:

minimal webgl2 nifti image viewer

235 lines (230 loc) 9.45 kB
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <title>NiiVue</title> <link rel="stylesheet" href="niivue.css" /> <link rel="icon" href="data:;base64,iVBORw0KGgo=" /> </head> <body> <noscript> <strong>niivue requires JavaScript.</strong> </noscript> <header> <button id="openBtn">Choose an image or mesh with a dialog</button> <label for="fiberRadius">Radius</label> <input type="range" min="0" max="20" value="5" class="slider" id="fiberRadius" /> <label for="fiberDitherSlider">Dither</label> <input type="range" min="0" max="10" value="0" class="slider" id="fiberDitherSlider" /> <label for="fiberColor">Fiber color</label> <select id="fiberColor"> <option value="Global">Global direction</option> <option value="Local">Local direction</option> <option value="Fixed">Fixed</option> <option value="DPV0" selected>First Per Vertex Type (if available)</option> </select> <label for="fiberColormap">Colormap</label> <select id="fiberColormap"> <option value="actc">ACTC</option> <option value="inferno" selected>Inferno</option> <option value="plasma">Plasma</option> <option value="warm">Warm</option> <option value="winter">Winter</option> </select> <label for="fiberCalMin">Color Minimum</label> <input type="range" min="10" max="75" value="25" class="slider" id="fiberCalMin" /> <label for="layoutSelect">Layout</label> <select id="layoutSelect"> <option value="0" selected>Auto</option> <option value="1">Column</option> <option value="2">Grid</option> <option value="3">Row</option> </select> <label for="renderingSelect">MultiplanarRendering</label> <select id="renderingSelect"> <option value="0">Never</option> <option value="1">Always</option> <option value="2" selected>Auto</option> </select> <select id="sliceType"> <option value="0">Axial</option> <option value="1">Coronal</option> <option value="2">Sagittal</option> <option value="4">Render</option> <option value="3" selected>Multiplanar</option> </select> <label for="equalCheck">EqualSize</label> <input type="checkbox" id="equalCheck" checked /> <label for="cornerCheck">CornerText</label> <input type="checkbox" id="cornerCheck" unchecked /> <label for="marginCheck">TileMargin</label> <input type="checkbox" id="marginCheck" unchecked /> <label for="heroSlider">Hero Image</label> <input type="range" min="0" max="9" value="0" class="slider" id="heroSlider" /> </header> <main id="canvas-container"> <div style="display: flex; width: 100%; height: 100%"> <canvas id="gl1"></canvas> </div> </main> <footer> <label id="shaders">Shaders: </label> </footer> <script type="module" async> import { Niivue, NVImage, NVMesh, NVMeshLoaders, SHOW_RENDER, DRAG_MODE } from './niivue/index.ts' async function addVolumeFromFiles(f) { console.log('attempting to open ', f[0].name) console.log('details', f[0]) nv1.loadFromFile(f[0]) } openBtn.onclick = function () { let input = document.createElement('input') input.style.display = 'none' input.type = 'file' document.body.appendChild(input) input.onchange = function (event) { addVolumeFromFiles(event.target.files) } input.click() } fiberRadius.oninput = function () { nv1.setMeshProperty(nv1.meshes[0].id, 'fiberRadius', this.value * 0.1) nv1.updateGLVolume() } fiberColor.onchange = function () { nv1.setMeshProperty(nv1.meshes[0].id, 'fiberColor', this.value) } fiberColormap.onchange = function () { nv1.setMeshProperty(nv1.meshes[0].id, 'colormap', this.value) } fiberDitherSlider.oninput = function () { nv1.setMeshProperty(nv1.meshes[0].id, 'fiberDither', this.value * 0.1) } fiberCalMin.oninput = function () { nv1.meshes[0].dpv[0].cal_min = this.value * 1 //*1 converts string to number nv1.setMeshProperty(nv1.meshes[0].id, 'colormap', fiberColormap.value) } layoutSelect.onchange = function () { nv1.setMultiplanarLayout(Number(this.value)) } renderingSelect.onchange = function () { nv1.opts.multiplanarShowRender = Number(this.value) nv1.drawScene() } equalCheck.onchange = function () { nv1.opts.multiplanarEqualSize = this.checked nv1.drawScene() } marginCheck.onchange = function () { nv1.opts.tileMargin = this.checked ? -1 : 0 nv1.drawScene() } cornerCheck.onchange = function () { //nv1.opts.isCornerOrientationText = this.checked\ nv1.setCornerOrientationText(this.checked) } heroSlider.oninput = function () { nv1.setHeroImage(this.value * 0.1) } sliceType.onchange = function () { let st = parseInt(document.getElementById('sliceType').value) nv1.setSliceType(st) } var volumeList1 = [{ url: '../demos/images/mni152.nii.gz' }] // with roiSelect, show the stats from the ROI in a temporary div const onDragRelease = (data) => { // if there is a div with the id dragReleaseDiv, remove it const divToRemove = document.getElementById('dragReleaseDiv') if (divToRemove) { divToRemove.remove() } console.log('drag release', data) let obj = nv1.getDescriptives({ layer: 0, startVox: data.voxStart, endVox: data.voxEnd, roiIsMask: true }) console.log(obj) // get dpr const dpr = window.devicePixelRatio // get mouse x,y. Divide by dpr to get the correct position const x = nv1.uiData.dragEnd[0] / dpr const y = nv1.uiData.dragEnd[1] / dpr console.log(x, y) const pad = 1 const div = document.createElement('div') // set id for removal on the next pass div.id = 'dragReleaseDiv' div.style.color = 'red' div.style.position = 'absolute' div.style.backgroundColor = 'black' div.style.opacity = 0.8 div.style.padding = '5px' // set z-index to be well above the canvas div.style.zIndex = 1000 div.innerHTML = `Mean: ${obj.mean}<br>Area: ${obj.area}<br>Stdev: ${obj.stdev}` // set position to be at the mouse x,y relative to the canvas div.style.left = x + pad + 'px' div.style.top = y + pad + 'px' // get the canvas element reference from niivue const canvas = nv1.gl.canvas // niivue should always be in a container, so get the parent of the canvas const parentDiv = canvas.parentElement parentDiv.appendChild(div) } const onLocationChange = (data) => { // remove the stats div if it exists when the user clicks away const divToRemove = document.getElementById('dragReleaseDiv') if (divToRemove) { divToRemove.remove() } } let defaults = { backColor: [0, 0.2, 0.4, 1], show3Dcrosshair: true, loglevel: 'debug', isRuler: true, dragMode: DRAG_MODE.measurement, // onDragRelease: onDragRelease, onLocationChange: onLocationChange, measureTextJustify: 'start', showMeasureUnits: true, measureTextColor: [0, 1, 0, 1], measureLineColor: [1, 0, 0, 1], measureTextHeight: 0.04 } var nv1 = new Niivue(defaults) await nv1.attachToCanvas(gl1) // multiplanarShowRender is the preferred option now, // but multiplanarForceRender is still available for backwards compatibility // previous use was: nv1.opts.multiplanarForceRender = true; nv1.opts.multiplanarShowRender = SHOW_RENDER.AUTO nv1.opts.yoke3Dto2DZoom = true await nv1.loadVolumes(volumeList1) var layerList = [{ url: '../demos/images/mni152.SLF1_R.tsf' }] await nv1.loadMeshes([{ url: '../demos/images/tract.SLF1_R.tck', layers: layerList }]) nv1.setMeshProperty(nv1.meshes[0].id, 'fiberColor', 'DPV0') nv1.setMeshProperty(nv1.meshes[0].id, 'fiberDither', 0) nv1.setMeshProperty(nv1.meshes[0].id, 'fiberRadius', 0.5) nv1.setMeshProperty(nv1.meshes[0].id, 'rgba255', [0, 255, 255, 255]) //color for fixed nv1.setMeshShader(nv1.meshes[0].id, 'diffuse') console.log('fiber vertex intensity range:', nv1.meshes[0].dpv[0].global_min, nv1.meshes[0].dpv[0].global_max) nv1.setSelectionBoxColor([0, 1, 0, 0.5]) nv1.setClipPlane([0.2, 0, 120]) let cmaps = nv1.meshShaderNames() let cmapEl = document.getElementById('shaders') for (let i = 0; i < cmaps.length; i++) { let btn = document.createElement('button') btn.innerHTML = cmaps[i] btn.onclick = function () { nv1.setMeshShader(nv1.meshes[0].id, cmaps[i]) } cmapEl.appendChild(btn) } equalCheck.onchange() </script> </body> </html>