diff --git a/dist/boostlet.min.js b/dist/boostlet.min.js index 50050861..caabe331 100644 --- a/dist/boostlet.min.js +++ b/dist/boostlet.min.js @@ -53,18 +53,51 @@ var $3okZG = parcelRequire("3okZG"); var $1lpSl = parcelRequire("1lpSl"); class Util { + // check if an object resembles a niivue instance + // instanceof breaks since boostlet loads its own copy of the module + static looks_like_niivue(obj) { + if (!obj || typeof obj !== "object") return false; + if (!Array.isArray(obj.volumes)) return false; + if (!obj.scene || typeof obj.scene !== "object") return false; + if (!obj.opts || obj.opts.sliceType === undefined) return false; + if (typeof obj.updateGLVolume !== "function") return false; + return true; + } + // scan all window properties for anything that looks like a niivue instance + // runs only if the known name checks above miss + static scan_window_for_niivue() { + const seen = new Set(); + for (const key of Object.getOwnPropertyNames(window)){ + let val; + try { + val = window[key]; + } catch (e) { + continue; + } + if (!val || typeof val !== "object") continue; + if (seen.has(val)) continue; + seen.add(val); + if (Util.looks_like_niivue(val)) return val; + } + return null; + } static detect_framework() { let framework = null; - if (Util.is_defined(window.nv)) framework = new (0, $lBIzH.NiiVue)(window.nv); - else if (Util.is_defined(window.niivue)) framework = new (0, $lBIzH.NiiVue)(window.niivue); - else if (Util.is_defined(window.cornerstone)) framework = new (0, $lRZZ2.Cornerstone2D)(window.cornerstone); - else if (Util.is_defined(window.r)) framework = new (0, $jYzCB.Xtk)(window.r); - else if (Util.is_defined(window.OpenSeadragon)) framework = new (0, $7Srn3.OpenSeaDragon)(window.OpenSeadragon); - else if (Util.is_defined(window.papayaContainers)) framework = new (0, $3okZG.Papaya)(window.papayaContainers); + // fast path: check known names first + if (Util.looks_like_niivue(window.nv)) framework = new (0, $lBIzH.NiiVue)(window.nv); + else if (Util.looks_like_niivue(window.niivue)) framework = new (0, $lBIzH.NiiVue)(window.niivue); else { - // Canvas fallback - console.log("No framework detected, falling back to canvas rendering"); - framework = new (0, $1lpSl.CanvasFallback)(); + // this is the slow path which scans window for any object that structurally matches niivue + const found = Util.scan_window_for_niivue(); + if (found) framework = new (0, $lBIzH.NiiVue)(found); + else if (Util.is_defined(window.cornerstone)) framework = new (0, $lRZZ2.Cornerstone2D)(window.cornerstone); + else if (Util.is_defined(window.r)) framework = new (0, $jYzCB.Xtk)(window.r); + else if (Util.is_defined(window.OpenSeadragon)) framework = new (0, $7Srn3.OpenSeaDragon)(window.OpenSeadragon); + else if (Util.is_defined(window.papayaContainers)) framework = new (0, $3okZG.Papaya)(window.papayaContainers); + else { + console.log("No framework detected, falling back to canvas rendering"); + framework = new (0, $1lpSl.CanvasFallback)(); + } } return framework; } @@ -116,11 +149,6 @@ class Util { offscreen_ctx.restore(); } let base64 = offscreen.toDataURL("image/png"); - // for debugging, download image - // const link = window.document.createElement("a"); - // link.href = base64; - // link.download = 'test.png'; - // link.click(); base64 = base64.replace("data:image/png;base64,", ""); let pngpixels = Uint8Array.from(atob(base64), (c)=>c.charCodeAt(0)); return pngpixels; @@ -783,6 +811,70 @@ class $2783e5ed66ad5478$export$26cab353fe186c2e extends (0, $bsgU1.Framework) { var $gk9zP = parcelRequire("gk9zP"); parcelRequire("bsgU1"); +// stats.js — numpy-ts powered statistics for Boostlet +// Lazy-loads numpy-ts once on first call, then reuses the instance. +// +// Usage from a boostlet: +// const stats = await Boostlet.stats(); +// const result = stats.all(data, nv.volumes[0]); +const $edf78858489332f9$var$NUMPY_TS_URL = "https://esm.run/numpy-ts"; +let $edf78858489332f9$var$_np = null; +// only load numpy-ts once, cache it in _np +async function $edf78858489332f9$var$_load() { + if (!$edf78858489332f9$var$_np) $edf78858489332f9$var$_np = await import($edf78858489332f9$var$NUMPY_TS_URL); + return $edf78858489332f9$var$_np; +} +async function $edf78858489332f9$export$6d8fb4b23c50da2f() { + const np = await $edf78858489332f9$var$_load(); + return { + // convert typed array to a numpy-ts array so we can run ops on it + _wrap (data) { + return np.array(Array.from(data), "float32"); + }, + // apply nifti slope/intercept: display = raw * slope + inter + _applyScaling (data, volume) { + if (!volume) return this._wrap(data); + const slope = volume.hdr.scl_slope || 1; + const inter = volume.hdr.scl_inter || 0; + const a = this._wrap(data); + return a.multiply(slope).add(inter); + }, + // numpy-ts returns array objects not plain numbers, unwrap to a scalar + _scalar (val) { + return typeof val === "number" ? val : val.tolist ? val.tolist() : Number(val); + }, + mean (data, volume) { + return this._scalar(np.mean(this._applyScaling(data, volume))); + }, + std (data, volume) { + return this._scalar(np.std(this._applyScaling(data, volume))); + }, + min (data, volume) { + return this._scalar(np.min(this._applyScaling(data, volume))); + }, + max (data, volume) { + return this._scalar(np.max(this._applyScaling(data, volume))); + }, + percentile (data, p, volume) { + return this._scalar(np.percentile(this._applyScaling(data, volume), p)); + }, + // returns all stats in one pass + // pass volume as second arg to apply slope/intercept + all (data, volume) { + const a = this._applyScaling(data, volume); + return { + mean: this._scalar(np.mean(a)), + std: this._scalar(np.std(a)), + min: this._scalar(np.min(a)), + max: this._scalar(np.max(a)), + p25: this._scalar(np.percentile(a, 25)), + p75: this._scalar(np.percentile(a, 75)) + }; + } + }; +} + + class $c41144187e031f80$export$a1c8e230980baa0 { constructor(){ this.framework = null; @@ -860,11 +952,17 @@ class $c41144187e031f80$export$a1c8e230980baa0 { return (0, $gk9zP.Util).filter(pixels, width, height, kernel); } /** - * Displays a small div located at the top left corner of the screen with message and will disappear after the specified time (ms). - * + * Displays a small div located at the top left corner of the screen + * with message and will disappear after the specified time (ms). */ hint(message, duration) { return (0, $gk9zP.Util).hint(message, duration); } + // const stats = await Boostlet.stats(); + // const result = stats.all(subvolumeData); + // gives you { mean, std, min, max, p25, p75 } + async stats() { + return (0, $edf78858489332f9$export$6d8fb4b23c50da2f)(); + } } diff --git a/dist/boostlet.min.js.map b/dist/boostlet.min.js.map index 0db105ec..dfb2136b 100644 --- a/dist/boostlet.min.js.map +++ b/dist/boostlet.min.js.map @@ -1 +1 @@ -{"mappings":"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","sources":["src/util.js","src/framework.js","src/frameworks/cornerstone2d.js","src/frameworks/canvasFallback.js","src/frameworks/niivue.js","src/frameworks/openseadragon.js","src/frameworks/xtk.js","src/frameworks/papaya.js","src/index.js","src/boostlet.js"],"sourcesContent":["import {Framework} from './framework.js';\r\nimport {Cornerstone2D} from './frameworks/cornerstone2d.js';\r\nimport {NiiVue} from './frameworks/niivue.js';\r\nimport { OpenSeaDragon } from './frameworks/openseadragon.js';\r\nimport { Xtk } from './frameworks/xtk.js';\r\nimport { Papaya } from './frameworks/papaya.js';\r\nimport { CanvasFallback } from './frameworks/canvasFallback.js';\r\n\r\nexport class Util {\r\n \r\n static detect_framework() {\r\n\r\n let framework = null;\r\n\r\n if (Util.is_defined(window.nv)) {\r\n \r\n framework = new NiiVue(window.nv);\r\n \r\n } else if (Util.is_defined(window.niivue)) {\r\n \r\n framework = new NiiVue(window.niivue);\r\n\r\n } else if (Util.is_defined(window.cornerstone)) {\r\n\r\n framework = new Cornerstone2D(window.cornerstone);\r\n\r\n } else if (Util.is_defined(window.r)) {\r\n \r\n framework = new Xtk(window.r);\r\n \r\n } else if (Util.is_defined(window.OpenSeadragon)) {\r\n\r\n framework = new OpenSeaDragon(window.OpenSeadragon);\r\n \r\n } else if (Util.is_defined(window.papayaContainers)) {\r\n \r\n framework = new Papaya(window.papayaContainers)\r\n\r\n } else {\r\n // Canvas fallback\r\n \r\n console.log(\"No framework detected, falling back to canvas rendering\");\r\n framework = new CanvasFallback();\r\n \r\n }\r\n\r\n return framework;\r\n\r\n }\r\n\r\n static async load_script(url, callback) {\r\n\r\n if (typeof window.Object.global === 'undefined') {\r\n // introducing hack to make it work for openneuro\r\n // but only do it once\r\n window.Object.defineProperty(window.Object.prototype, 'global', {\r\n get( ){\r\n return window;\r\n },\r\n set(newGlobal) {\r\n globalThis = newGlobal;\r\n }\r\n });\r\n }\r\n\r\n const script = window.document.createElement(\"script\")\r\n script.type = \"text/javascript\"\r\n script.src = url;\r\n\r\n if (Util.is_defined(callback)) {\r\n script.onload = callback;\r\n }\r\n\r\n window.document.head.appendChild(script);\r\n eval(script);\r\n\r\n }\r\n\r\n static async send_http_post(url, data, callback) {\r\n\r\n let xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", url);\r\n xhr.onreadystatechange = function () {\r\n if (xhr.readyState === 4) {\r\n // request done\r\n callback( xhr.response );\r\n\r\n return;\r\n\r\n }\r\n }\r\n\r\n xhr.send(data)\r\n\r\n }\r\n\r\n static convert_to_png(uint8array, width, height, flip) {\r\n\r\n // we are using an offscreen canvas for this\r\n let offscreen = window.document.createElement('canvas');\r\n offscreen.width = width;\r\n offscreen.height = height;\r\n\r\n let offscreen_ctx = offscreen.getContext('2d');\r\n\r\n let imgdata = offscreen_ctx.createImageData(offscreen.width, offscreen.height);\r\n let pxdata = imgdata.data;\r\n\r\n for (var i =0; i c.charCodeAt(0));\r\n\r\n return pngpixels;\r\n\r\n }\r\n\r\n static filter(pixels, width, height, kernel) {\r\n\r\n const kernelSize = Math.sqrt(kernel.length);\r\n const halfKernelSize = Math.floor(kernelSize / 2);\r\n\r\n const new_pixels = pixels.slice();\r\n\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n const dstIndex = y * width + x;\r\n\r\n let newValue = 0;\r\n for (let ky = 0; ky < kernelSize; ky++) {\r\n for (let kx = 0; kx < kernelSize; kx++) {\r\n const srcX = x + kx - halfKernelSize;\r\n const srcY = y + ky - halfKernelSize;\r\n const srcIndex = srcY * width + srcX;\r\n\r\n const kernelValue = kernel[ky * kernelSize + kx];\r\n newValue += pixels[srcIndex] * kernelValue;\r\n }\r\n }\r\n\r\n new_pixels[dstIndex] = newValue;\r\n\r\n }\r\n }\r\n\r\n return new_pixels;\r\n\r\n }\r\n\r\n static grayscale_to_rgba(grayscale) {\r\n\r\n const rgba = new Uint8Array(grayscale.length * 4);\r\n\r\n for (let i = 0; i < grayscale.length; i++) {\r\n const g = grayscale[i];\r\n const index = i * 4;\r\n\r\n rgba[index] = g;\r\n rgba[index + 1] = g;\r\n rgba[index + 2] = g;\r\n rgba[index + 3] = 255; \r\n }\r\n\r\n return rgba;\r\n\r\n }\r\n\r\n static rgba_to_grayscale(rgba) {\r\n\r\n const grayscale = new Uint8Array(rgba.length / 4);\r\n\r\n for (let i = 0; i < rgba.length; i += 4) {\r\n\r\n grayscale[i / 4] = rgba[i];\r\n\r\n }\r\n\r\n return grayscale;\r\n\r\n }\r\n\r\n /**\r\n * Harden a mask into a grayscale pixel array.\r\n * \r\n * pixels needs to be RGBA\r\n * \r\n * and mask binary.\r\n * \r\n * maskcolor is optional and falls back to blue.\r\n * \r\n **/\r\n static harden_mask(pixels, mask, maskcolor) {\r\n\r\n // Modified from: https://github.com/facebookresearch/segment-anything/blob/40df6e4046d8b07ab8c4519e083408289eb43032/demo/src/components/helpers/maskUtils.tsx\r\n // Copyright (c) Meta Platforms, Inc. and affiliates.\r\n // All rights reserved.\r\n\r\n\r\n let maskcolor_ = [0, 114, 189, 255];\r\n\r\n if (Util.is_defined(maskcolor)) {\r\n \r\n maskcolor_ = maskcolor;\r\n \r\n } \r\n\r\n for (var i = 0; i < mask.length; i++) {\r\n\r\n if (mask[i] > 0.0) {\r\n pixels[4 * i + 0] = maskcolor_[0];\r\n pixels[4 * i + 1] = maskcolor_[1];\r\n pixels[4 * i + 2] = maskcolor_[2];\r\n pixels[4 * i + 3] = maskcolor_[3];\r\n }\r\n\r\n }\r\n\r\n return pixels;\r\n\r\n }\r\n\r\n static is_defined(variable) {\r\n\r\n return (typeof variable != 'undefined');\r\n\r\n }\r\n \r\n // \"Boostlet Tooltips\" - This is a hint mechanism that allows to display a message for a certain amount of time (ms).\r\n static hint(message, duration) {\r\n\r\n let hint = window.document.createElement('div');\r\n hint.id = 'BoostletHint';\r\n\r\n hint.style.position = 'fixed';\r\n hint.style.left = '10px';\r\n hint.style.top = '10px';\r\n hint.style.padding = '10px';\r\n hint.style.background = '#fff';\r\n hint.style.color = '#000';\r\n hint.style.zIndex = '100000';\r\n hint.style.border = '1px solid #007ec6';\r\n hint.style.borderRadius = '5px';\r\n hint.style.boxShadow = '0px 0px 20px 5px rgba(0,0,0, 0.3)';\r\n hint.style.fontSize = '14px';\r\n hint.style.fontWeight = 'bold';\r\n hint.style.textAlign = 'center';\r\n\r\n hint.innerHTML = message;\r\n \r\n window.document.body.appendChild(hint);\r\n\r\n if (typeof duration === 'number' && duration > 0) {\r\n setTimeout(function() {\r\n hint.remove();\r\n }, duration);\r\n }\r\n\r\n }\r\n\r\n}","import {Util} from './util.js';\r\n\r\nexport class Framework {\r\n\r\n constructor(instance) {\r\n\r\n this.name = 'generic';\r\n this.instance = instance;\r\n\r\n this.flip_on_png = false;\r\n\r\n }\r\n\r\n get_image(from_canvas) {\r\n\r\n throw \"Missing Implementation.\";\r\n\r\n }\r\n\r\n set_image(new_pixels) {\r\n\r\n throw \"Missing Implementation.\";\r\n\r\n }\r\n\r\n set_mask(new_mask) {\r\n\r\n throw \"Missing Implementation.\";\r\n \r\n }\r\n\r\n select_box(callback) {\r\n\r\n throw \"Missing Implementation.\";\r\n\r\n }\r\n\r\n convert_to_png(uint8array, width, height) {\r\n\r\n return Util.convert_to_png(uint8array, width, height, this.flip_on_png);\r\n\r\n }\r\n\r\n}\r\n","import {Framework} from '../framework.js';\r\n\r\nimport {Util} from '../util.js';\r\n\r\nimport {CanvasFallback} from './canvasFallback.js';\r\n\r\nexport class Cornerstone2D extends Framework {\r\n \r\n constructor(instance) {\r\n\r\n super(instance);\r\n this.name = 'cornerstone2D';\r\n this.canvasFallback = new CanvasFallback();\r\n\r\n this.cornerstonetools_instance = null;\r\n\r\n if (typeof window.cornerstoneTools != 'undefined') {\r\n\r\n // TODO probably not too robust\r\n this.cornerstonetools_instance = window.cornerstoneTools;\r\n\r\n }\r\n\r\n this.flip_on_png = false;\r\n\r\n }\r\n\r\n get_image(from_canvas) {\r\n\r\n let element = this.instance.getEnabledElements()[0];\r\n let pixels = null;\r\n let width = null;\r\n let height = null;\r\n\r\n if (typeof from_canvas != 'undefined') {\r\n\r\n // TODO this is hacky going through the canvas\r\n // later should grab the real volume data\r\n\r\n let canvas = element.canvas;\r\n width = canvas.width;\r\n height = canvas.height;\r\n\r\n let ctx = canvas.getContext('2d');\r\n\r\n let imagedata = ctx.getImageData(0, 0, width, height);\r\n pixels = imagedata.data;\r\n\r\n } else {\r\n\r\n // this is the real image slice data\r\n let imagedata = element.image;\r\n pixels = imagedata.getPixelData();\r\n width = imagedata.width;\r\n height = imagedata.height;\r\n\r\n }\r\n\r\n return {'data':pixels, 'width':width, 'height':height};\r\n\r\n }\r\n\r\n set_image(new_pixels) {\r\n\r\n let element = this.instance.getEnabledElements()[0];\r\n let pixels = element.image.getPixelData();\r\n\r\n // Set the new pixel values\r\n pixels.set(new_pixels);\r\n\r\n // Re-render the current slice\r\n cornerstone.renderGrayscaleImage(element, true);\r\n\r\n }\r\n\r\n set_mask(new_mask) {\r\n return this.canvasFallback.set_mask(new_mask);\r\n \r\n }\r\n\r\n select_box(callback) {\r\n\r\n if(this.cornerstonetools_instance.RectangleRoiTool === undefined) {\r\n console.log(\"Using Boxcraft library to handle box selection.\");\r\n let element = this.instance.getEnabledElements()[0];\r\n let canvas = element.canvas;\r\n\r\n // Disable the Wwwc tool\r\n cornerstoneTools.setToolDisabled(\"Wwwc\");\r\n\r\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\r\n callback(topleft, bottomright);\r\n });\r\n\r\n BoxCraft.createResizableBBox(canvas, function(topleft, bottomright) {\r\n console.log(\"Inside Draggable BBox\",topleft, bottomright);\r\n callback(topleft, bottomright);\r\n });\r\n\r\n // return this.canvasFallback.select_box(callback);\r\n\r\n\r\n }\r\n else{\r\n console.log(\"Using Cornerstonetools to handle box selection.\");\r\n this.cornerstonetools_instance.setToolActive(\"RectangleRoi\", {\r\n mouseButtonMask: 1,\r\n });\r\n\r\n let element = this.instance.getEnabledElements()[0];\r\n let canvas = element.canvas;\r\n\r\n canvas.onmouseup = function () {\r\n let state =\r\n this.cornerstonetools_instance.globalImageIdSpecificToolStateManager.saveToolState();\r\n\r\n let topleft =\r\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.start;\r\n let bottomright =\r\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.end;\r\n\r\n let topleft_c = this.instance.pixelToCanvas(element.element, topleft);\r\n let bottomright_c = this.instance.pixelToCanvas(\r\n element.element,\r\n bottomright\r\n );\r\n\r\n this.cornerstonetools_instance.clearToolState(\r\n element.element,\r\n \"RectangleRoi\"\r\n );\r\n this.instance.renderGrayscaleImage(element, true);\r\n callback(topleft_c, bottomright_c);\r\n }.bind(this);\r\n }\r\n\r\n }\r\n\r\n}","import { Framework } from \"../framework.js\";\r\n\r\nimport { Util } from \"../util.js\";\r\n\r\n\r\n\r\nexport class CanvasFallback extends Framework {\r\n constructor() {\r\n super();\r\n this.name = \"canvasFallback\";\r\n }\r\n\r\n get_canvas() {\r\n let canvases = document.querySelectorAll('canvas');\r\n let largestCanvas = canvases[0];\r\n let largestArea = largestCanvas.width * largestCanvas.height;\r\n\r\n for (let i = 1; i < canvases.length; i++) {\r\n let area = canvases[i].width * canvases[i].height;\r\n if (area > largestArea) {\r\n largestCanvas = canvases[i];\r\n largestArea = area;\r\n }\r\n }\r\n\r\n return largestCanvas;\r\n }\r\n\r\n get_image(from_canvas) {\r\n\r\n let canvas = this.get_canvas();\r\n\r\n let ctx = canvas.getContext(\"2d\");\r\n\r\n let image = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n let rgba_image = Util.rgba_to_grayscale(image.data);\r\n\r\n if (from_canvas) {\r\n return { data: image.data, width: image.width, height: image.height };\r\n } else {\r\n return { data: rgba_image, width: image.width, height: image.height };\r\n }\r\n }\r\n\r\n set_image(new_pixels) {\r\n let originalcanvas = this.get_canvas();\r\n\r\n let newcanvas = window.document.createElement(\"canvas\");\r\n\r\n newcanvas.width = originalcanvas.width;\r\n newcanvas.height = originalcanvas.height;\r\n\r\n let ctx = newcanvas.getContext(\"2d\");\r\n\r\n let newPixelsRgba = Util.grayscale_to_rgba(new_pixels);\r\n\r\n let newPixelsClamped = new Uint8ClampedArray(newPixelsRgba);\r\n\r\n let newImageData = new ImageData(\r\n newPixelsClamped,\r\n newcanvas.width,\r\n newcanvas.height\r\n );\r\n\r\n // Draw the new image data onto the canvas\r\n ctx.putImageData(newImageData, 0, 0);\r\n\r\n newcanvas.onclick = function () {\r\n // on click, we will restore the nv canvas\r\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\r\n };\r\n\r\n // replace nv canvas with new one\r\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\r\n }\r\n\r\n set_mask(new_mask) {\r\n let image = this.get_image(true);\r\n\r\n let originalcanvas = this.get_canvas();\r\n\r\n let newcanvas = window.document.createElement('canvas');\r\n\r\n newcanvas.width = originalcanvas.width;\r\n newcanvas.height = originalcanvas.height;\r\n\r\n let ctx = newcanvas.getContext('2d');\r\n\r\n let imageclamped = new Uint8ClampedArray(image.data);\r\n\r\n let newImageData = new ImageData(\r\n imageclamped,\r\n newcanvas.width,\r\n newcanvas.height\r\n );\r\n\r\n // Draw the new image data onto the canvas\r\n ctx.putImageData(newImageData, 0, 0);\r\n\r\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\r\n\r\n let masked_image = Util.harden_mask(image.data, new_mask);\r\n\r\n let masked_image_as_imagedata = new ImageData(\r\n masked_image,\r\n newcanvas.width,\r\n newcanvas.height\r\n );\r\n\r\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\r\n\r\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\r\n }\r\n\r\n select_box(callback) {\r\n let scriptBoxCraft = document.createElement(\"script\");\r\n scriptBoxCraft.type = \"text/javascript\";\r\n scriptBoxCraft.src = \"https://boostlet.org/dist/boxcraft.min.js\";\r\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/BoxCraft/dist/boxCraft.min.js\";\r\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/boostlet/dist/boxcraft.min.js\";\r\n // scriptBoxCraft.src = \"http://localhost:8000/dist/boxcraft.min.js\";\r\n let canvas = this.get_canvas();\r\n document.head.appendChild(scriptBoxCraft);\r\n\r\n scriptBoxCraft.onload = function() {\r\n\r\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\r\n console.log(\"Inside Draggable BBox\", topleft, bottomright);\r\n callback(topleft, bottomright);\r\n });\r\n }\r\n\r\n }\r\n\r\n}\r\n","import {Framework} from '../framework.js';\n\nimport {Util} from '../util.js';\nimport {CanvasFallback} from './canvasFallback.js';\n\nexport class NiiVue extends Framework {\n \n constructor(instance) {\n\n super(instance);\n this.name = 'niivue';\n this.canvasFallback = new CanvasFallback();\n\n this.flip_on_png = true;\n\n this.onMouseDown = false;\n this.x1 = null;\n this.y1 = null;\n this.x2 = null;\n this.y2 = null;\n\n }\n\n get_image(from_canvas) {\n\n \n let pixels = null;\n let width = null;\n let height = null;\n\n if (!Util.is_defined(from_canvas)) {\n\n // use the canvas\n\n let element = this.instance.canvas;\n\n let old_crosshaircolor = this.instance.opts.crosshairColor;\n let old_crosshairwidth = this.instance.opts.crosshairWidth;\n\n this.instance.setCrosshairColor([0,0,0,0]);\n this.instance.opts.crosshairWidth=0;\n this.instance.updateGLVolume();\n\n\n let ctx = this.instance.gl;\n\n \n width = ctx.drawingBufferWidth;\n height = ctx.drawingBufferHeight;\n\n pixels = new Uint8Array(width * height * 4);\n ctx.readPixels(\n 0, \n 0, \n width, \n height, \n ctx.RGBA, \n ctx.UNSIGNED_BYTE, \n pixels);\n\n // restore crosshairs\n this.instance.setCrosshairColor(old_crosshaircolor);\n this.instance.opts.crosshairWidth = old_crosshairwidth;\n\n\n // convert rgba pixels to grayscale\n pixels = Util.rgba_to_grayscale(pixels);\n\n\n } else {\n\n // grab the real pixels of the current slice and return it\n // set width and height accordingly\n\n let v = this.instance.volumes[0];\n let currentSlice = this.instance.opts.sliceType;\n let currentCrossHairPos = this.instance.scene.crosshairPos;\n \n \n // Default to full volume\n let start = [0, 0, 0];\n let end = [v.dims[1], v.dims[2], v.dims[3]];\n\n if(currentSlice == 0){\n // axial — slice along Z\n let z_index = Math.floor(currentCrossHairPos[2] * v.dims[3]);\n width = v.dims[1];\n height = v.dims[2];\n start = [0, 0, z_index];\n end = [v.dims[1], v.dims[2], z_index + 1];\n }\n else if(currentSlice == 1){\n // coronal — slice along Y\n let y_index = Math.floor(currentCrossHairPos[1] * v.dims[2]);\n width = v.dims[1];\n height = v.dims[3];\n start = [0, y_index, 0];\n end = [v.dims[1], y_index + 1 , v.dims[3]];\n }\n else if(currentSlice == 2){\n // sagittal — slice along X\n let x_index = Math.floor(currentCrossHairPos[0] * v.dims[1]);\n width = v.dims[2];\n height = v.dims[3];\n start = [x_index, 0, 0];\n end = [x_index + 1, v.dims[2], v.dims[3]];\n }\n else {\n // multiplanar (sliceType 3) — no single slice to extract,\n // return the full volume\n width = v.dims[1];\n height = v.dims[2];\n }\n\n pixels = v.getVolumeData(start, end)[0];\n\n }\n\n return {'data':pixels, 'width':width, 'height':height};\n\n }\n\n\n\n get_subvolume(start, end) {\n const [data, dims] = this.instance.volumes[0].getVolumeData(start, end);\n return { data, dims };\n }\n\n get_dims(){\n const v = this.instance.volumes[0];\n const currentSlice = this.instance.opts.sliceType;\n\n if(currentSlice == 0) return [v.dims[1], v.dims[2], 1]; //axial\n if(currentSlice == 1 ) return [v.dims[1], v.dims[3], 1 ]; // coronal\n if(currentSlice == 2) return [v.dims[2], v.dims[3], 1 ]; // saggital\n return [v.dims[1], v.dims[2], 1]; // multiplanar fallback\n } \n\n /**\n * Sets the NiiVue.js image.\n * \n * If is_rgba==true, we do *not* convert to RGBA before setting on canvas.\n **/\n set_image(new_pixels, is_rgba, no_flip) {\n\n // TODO this is hacky since we dont work with the real volume yet\n // create new canvas\n // put pixels on canvas\n // show canvas\n // hide on click\n\n let originalcanvas = this.instance.canvas;\n\n let newcanvas = window.document.createElement('canvas');\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n\n // put new_pixels down\n let ctx = newcanvas.getContext('2d');\n\n let new_pixels_rgba = null;\n\n if (Util.is_defined(is_rgba)) {\n\n new_pixels_rgba = new_pixels;\n\n } else {\n\n new_pixels_rgba = Util.grayscale_to_rgba(new_pixels);\n\n\n }\n\n let new_pixels_clamped = new Uint8ClampedArray(new_pixels_rgba);\n\n let new_image_data = new ImageData(new_pixels_clamped, newcanvas.width, newcanvas.height);\n \n\n ctx.putImageData(new_image_data, 0, 0);\n\n if (!Util.is_defined(no_flip)) {\n // some flipping action\n ctx.save();\n ctx.scale(1, -1);\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\n ctx.restore();\n }\n\n\n newcanvas.onclick = function() {\n\n // on click, we will restore the nv canvas\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\n\n }\n\n // replace nv canvas with new one\n // originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n newcanvas.style.width = originalcanvas.clientWidth+'px';\n newcanvas.style.height = originalcanvas.clientHeight+'px';\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n\n }\n\n set_mask(new_mask) {\n\n // merge image + mask\n // and then call set_image with that information\n\n let image = this.get_image(true);\n\n // TODO here we need to flip one more time, this is until\n // we use the official niivue infrastructure for adding\n // a segmentation layer\n let originalcanvas = this.instance.canvas;\n\n let newcanvas = window.document.createElement('canvas');\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n // put new_pixels down\n let ctx = newcanvas.getContext('2d');\n let imageclamped = new Uint8ClampedArray(image.data);\n let imagedata = new ImageData(imageclamped, image.width, image.height);\n ctx.putImageData(imagedata, 0, 0);\n ctx.save();\n ctx.scale(1, -1);\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\n ctx.restore();\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\n // end of flip\n\n let masked_image = Util.harden_mask(image.data, new_mask);\n\n this.set_image(masked_image, true, true); // rgba data, no flip\n\n\n }\n\n select_box(callback) {\n return this.canvasFallback.select_box(callback);\n\n }\n\n}\n","import {Framework} from '../framework.js';\r\n\r\nimport {Util} from '../util.js';\r\n\r\nimport {CanvasFallback} from './canvasFallback.js';\r\n\r\nexport class OpenSeaDragon extends Framework {\r\n constructor(instance) {\r\n super(instance);\r\n this.name = 'opensedragon';\r\n this.canvasFallback = new CanvasFallback();\r\n\r\n }\r\n\r\n get_image(from_canvas) {\r\n return this.canvasFallback.get_image(from_canvas);\r\n }\r\n\r\n set_image(new_pixels) {\r\n return this.canvasFallback.set_image(new_pixels);\r\n }\r\n\r\n set_mask(new_mask) {\r\n // return this.canvasFallback.set_mask(new_mask);\r\n\r\n let viewer = null;\r\n let vs = this.instance._viewers;\r\n vs.forEach(function (e) {\r\n if (e.id == \"viewer\") {\r\n viewer = e;\r\n }\r\n });\r\n\r\n if (!viewer) {\r\n throw \"OpenSeaDragon viewer not found.\";\r\n }\r\n\r\n let canvas = viewer.canvas.children[0];\r\n width = canvas.width;\r\n height = canvas.height;\r\n\r\n let ctx = canvas.getContext(\"2d\");\r\n let imagedata = ctx.getImageData(0, 0, width, height);\r\n let pixels = imagedata.data;\r\n\r\n let masked_image = Util.harden_mask(pixels, new_mask);\r\n let masked_image_as_imagedata = new ImageData(masked_image, width, height);\r\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\r\n }\r\n\r\n select_box(callback) {\r\n\r\n console.log(\"Using Boxcraft library to handle box selection.\");\r\n let viewer = null;\r\n let vs = this.instance._viewers;\r\n vs.forEach(function (e) {\r\n if (e.id == \"viewer\") {\r\n viewer = e;\r\n }\r\n });\r\n\r\n if (!viewer) {\r\n throw \"OpenSeaDragon viewer not found.\";\r\n }\r\n\r\n let canvas = viewer.canvas;\r\n\r\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\r\n callback(topleft, bottomright);\r\n });\r\n\r\n }\r\n\r\n \r\n\r\n}\r\n \r\n","import { Framework } from \"../framework.js\";\r\n\r\nimport { Util } from \"../util.js\";\r\n\r\nimport { CanvasFallback } from \"./canvasFallback.js\";\r\n\r\nexport class Xtk extends Framework {\r\n constructor(instance) {\r\n super(instance);\r\n this.name = \"xtk\";\r\n this.canvasFallback = new CanvasFallback();\r\n }\r\n\r\n get_image(from_canvas) {\r\n return this.canvasFallback.get_image(from_canvas);\r\n }\r\n\r\n set_image(new_pixels) {\r\n return this.canvasFallback.set_image(new_pixels);\r\n }\r\n\r\n set_mask(new_mask) {\r\n return this.canvasFallback.set_mask(new_mask);\r\n }\r\n\r\n select_box(callback) {\r\n return this.canvasFallback.select_box(callback);\r\n }\r\n}\r\n","import { Framework } from \"../framework.js\";\r\n\r\nimport { Util } from \"../util.js\";\r\n\r\nimport { CanvasFallback } from \"./canvasFallback.js\";\r\n\r\nexport class Papaya extends Framework {\r\n constructor(instance) {\r\n super(instance);\r\n this.name = \"papaya\";\r\n this.canvasFallback = new CanvasFallback();\r\n }\r\n\r\n get_image(from_canvas) {\r\n return this.canvasFallback.get_image(from_canvas);\r\n }\r\n\r\n set_image(new_pixels) {\r\n return this.canvasFallback.set_image(new_pixels);\r\n }\r\n\r\n set_mask(new_mask) {\r\n return this.canvasFallback.set_mask(new_mask);\r\n }\r\n\r\n select_box(callback) {\r\n return this.canvasFallback.select_box(callback);\r\n }\r\n}\r\n","\r\nimport {Boostlet} from \"./boostlet.js\"\r\n\r\nwindow.console.log('BOOSTLET VERSION 0.1-beta');\r\n\r\n// register global namespace with a new boostlet instance\r\n// later we might want to support multiple active boostlets\r\nwindow.Boostlet = new Boostlet();\r\n","import {Util} from './util.js';\n\nimport {Framework} from './framework.js';\n\nexport class Boostlet {\n\n constructor() {\n this.framework = null;\n }\n\n /**\n * Initializes the Boostlet.\n * \n * This includes several steps such as identifying the \n * visualization/rendering framework that is available. \n * \n * TODO: Later we want to have fallbacks in place if the framework\n * is not detected.\n * \n */\n init(name, instance) {\n\n if (typeof name != 'undefined' && typeof instance != 'undefined') {\n\n console.log('Framework forced by user!');\n throw \"Forced Framework Not Implemented.\";\n // TODO\n\n } else {\n\n this.framework = Util.detect_framework();\n\n }\n\n if (this.framework) {\n\n console.log('Found', this.framework, '!')\n \n } else {\n\n throw \"Framework Not Found.\";\n\n }\n\n }\n\n /**\n * Let's the user select a region of interest box.\n */\n async select_box(callback) {\n\n this.framework.select_box(callback);\n\n }\n\n /**\n * Let's the user select (multiple) seeds.\n */\n async select_seed(howmany) {\n\n throw \"Missing Implementation.\";\n\n }\n\n /**\n * Loads an external javascript file asynchronously. \n */\n async load_script(url, callback) {\n\n Util.load_script(url, callback);\n\n }\n\n /**\n * Sends a HTTP POST request to a url with some data.\n */\n async send_http_post(url, data, callback) {\n\n Util.send_http_post(url, data, callback);\n\n }\n\n /**\n * Gets the current image (2D).\n * \n * TODO: Optional bounding box should be supported.\n * \n */\n get_image(from_canvas) {\n\n return this.framework.get_image(from_canvas);\n\n }\n\n /**\n * Sets the current image (2D).\n * \n */\n set_image(new_pixels) {\n\n return this.framework.set_image(new_pixels);\n\n }\n\n /**\n * Sets the current mask (2D).\n * \n */\n set_mask(new_mask) {\n\n return this.framework.set_mask(new_mask);\n\n }\n\n get_dims() {\n return this.framework.get_dims();\n }\n\n get_subvolume(start, end) {\n return this.framework.get_subvolume(start, end);\n }\n\n /**\n * Encode raw image data to PNG.\n */\n convert_to_png(uint8array, width, height) {\n\n return this.framework.convert_to_png(uint8array, width, height);\n\n }\n\n filter(pixels, width, height, kernel) {\n\n return Util.filter(pixels, width, height, kernel);\n\n }\n\n /**\n * Displays a small div located at the top left corner of the screen with message and will disappear after the specified time (ms).\n * \n */\n hint(message, duration) {\n\n return Util.hint(message, duration);\n\n }\n\n}\n"],"names":[],"version":3,"file":"boostlet.min.js.map"} \ No newline at end of file +{"mappings":"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","sources":["src/util.js","src/framework.js","src/frameworks/cornerstone2d.js","src/frameworks/canvasFallback.js","src/frameworks/niivue.js","src/frameworks/openseadragon.js","src/frameworks/xtk.js","src/frameworks/papaya.js","src/index.js","src/boostlet.js","src/stats.js"],"sourcesContent":["import {Framework} from './framework.js';\nimport {Cornerstone2D} from './frameworks/cornerstone2d.js';\nimport {NiiVue} from './frameworks/niivue.js';\nimport { OpenSeaDragon } from './frameworks/openseadragon.js';\nimport { Xtk } from './frameworks/xtk.js';\nimport { Papaya } from './frameworks/papaya.js';\nimport { CanvasFallback } from './frameworks/canvasFallback.js';\n\nexport class Util {\n\n // check if an object resembles a niivue instance\n // instanceof breaks since boostlet loads its own copy of the module\n static looks_like_niivue(obj) {\n if (!obj || typeof obj !== 'object') return false\n if (!Array.isArray(obj.volumes)) return false\n if (!obj.scene || typeof obj.scene !== 'object') return false\n if (!obj.opts || obj.opts.sliceType === undefined) return false\n if (typeof obj.updateGLVolume !== 'function') return false\n return true\n }\n\n // scan all window properties for anything that looks like a niivue instance\n // runs only if the known name checks above miss\n static scan_window_for_niivue() {\n const seen = new Set()\n for (const key of Object.getOwnPropertyNames(window)) {\n let val\n try {\n val = window[key]\n } catch (e) {\n // some globals throw SecurityError on access which is annoying\n continue\n }\n if (!val || typeof val !== 'object') continue\n if (seen.has(val)) continue\n seen.add(val)\n if (Util.looks_like_niivue(val)) return val\n }\n return null\n }\n \n static detect_framework() {\n\n let framework = null;\n\n // fast path: check known names first\n if (Util.looks_like_niivue(window.nv)) {\n\n framework = new NiiVue(window.nv);\n\n } else if (Util.looks_like_niivue(window.niivue)) {\n\n framework = new NiiVue(window.niivue);\n\n } else {\n\n // this is the slow path which scans window for any object that structurally matches niivue\n const found = Util.scan_window_for_niivue()\n if (found) {\n\n framework = new NiiVue(found);\n\n } else if (Util.is_defined(window.cornerstone)) {\n\n framework = new Cornerstone2D(window.cornerstone);\n\n } else if (Util.is_defined(window.r)) {\n\n framework = new Xtk(window.r);\n\n } else if (Util.is_defined(window.OpenSeadragon)) {\n\n framework = new OpenSeaDragon(window.OpenSeadragon);\n\n } else if (Util.is_defined(window.papayaContainers)) {\n\n framework = new Papaya(window.papayaContainers)\n\n } else {\n\n console.log(\"No framework detected, falling back to canvas rendering\");\n framework = new CanvasFallback();\n\n }\n\n }\n\n return framework;\n\n }\n\n static async load_script(url, callback) {\n\n if (typeof window.Object.global === 'undefined') {\n // introducing hack to make it work for openneuro\n // but only do it once\n window.Object.defineProperty(window.Object.prototype, 'global', {\n get( ){\n return window;\n },\n set(newGlobal) {\n globalThis = newGlobal;\n }\n });\n }\n\n const script = window.document.createElement(\"script\")\n script.type = \"text/javascript\"\n script.src = url;\n\n if (Util.is_defined(callback)) {\n script.onload = callback;\n }\n\n window.document.head.appendChild(script);\n eval(script);\n\n }\n\n static async send_http_post(url, data, callback) {\n\n let xhr = new XMLHttpRequest();\n xhr.open(\"POST\", url);\n xhr.onreadystatechange = function () {\n if (xhr.readyState === 4) {\n // request done\n callback( xhr.response );\n\n return;\n\n }\n }\n\n xhr.send(data)\n\n }\n\n static convert_to_png(uint8array, width, height, flip) {\n\n // we are using an offscreen canvas for this\n let offscreen = window.document.createElement('canvas');\n offscreen.width = width;\n offscreen.height = height;\n\n let offscreen_ctx = offscreen.getContext('2d');\n\n let imgdata = offscreen_ctx.createImageData(offscreen.width, offscreen.height);\n let pxdata = imgdata.data;\n\n for (var i =0; i c.charCodeAt(0));\n\n return pngpixels;\n\n }\n\n static filter(pixels, width, height, kernel) {\n\n const kernelSize = Math.sqrt(kernel.length);\n const halfKernelSize = Math.floor(kernelSize / 2);\n\n const new_pixels = pixels.slice();\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const dstIndex = y * width + x;\n\n let newValue = 0;\n for (let ky = 0; ky < kernelSize; ky++) {\n for (let kx = 0; kx < kernelSize; kx++) {\n const srcX = x + kx - halfKernelSize;\n const srcY = y + ky - halfKernelSize;\n const srcIndex = srcY * width + srcX;\n\n const kernelValue = kernel[ky * kernelSize + kx];\n newValue += pixels[srcIndex] * kernelValue;\n }\n }\n\n new_pixels[dstIndex] = newValue;\n\n }\n }\n\n return new_pixels;\n\n }\n\n static grayscale_to_rgba(grayscale) {\n\n const rgba = new Uint8Array(grayscale.length * 4);\n\n for (let i = 0; i < grayscale.length; i++) {\n const g = grayscale[i];\n const index = i * 4;\n\n rgba[index] = g;\n rgba[index + 1] = g;\n rgba[index + 2] = g;\n rgba[index + 3] = 255; \n }\n\n return rgba;\n\n }\n\n static rgba_to_grayscale(rgba) {\n\n const grayscale = new Uint8Array(rgba.length / 4);\n\n for (let i = 0; i < rgba.length; i += 4) {\n\n grayscale[i / 4] = rgba[i];\n\n }\n\n return grayscale;\n\n }\n\n /**\n * Harden a mask into a grayscale pixel array.\n * \n * pixels needs to be RGBA\n * \n * and mask binary.\n * \n * maskcolor is optional and falls back to blue.\n * \n **/\n static harden_mask(pixels, mask, maskcolor) {\n\n // Modified from: https://github.com/facebookresearch/segment-anything/blob/40df6e4046d8b07ab8c4519e083408289eb43032/demo/src/components/helpers/maskUtils.tsx\n // Copyright (c) Meta Platforms, Inc. and affiliates.\n // All rights reserved.\n\n\n let maskcolor_ = [0, 114, 189, 255];\n\n if (Util.is_defined(maskcolor)) {\n \n maskcolor_ = maskcolor;\n \n } \n\n for (var i = 0; i < mask.length; i++) {\n\n if (mask[i] > 0.0) {\n pixels[4 * i + 0] = maskcolor_[0];\n pixels[4 * i + 1] = maskcolor_[1];\n pixels[4 * i + 2] = maskcolor_[2];\n pixels[4 * i + 3] = maskcolor_[3];\n }\n\n }\n\n return pixels;\n\n }\n\n static is_defined(variable) {\n\n return (typeof variable != 'undefined');\n\n }\n \n // \"Boostlet Tooltips\" - This is a hint mechanism that allows to display a message for a certain amount of time (ms).\n static hint(message, duration) {\n\n let hint = window.document.createElement('div');\n hint.id = 'BoostletHint';\n\n hint.style.position = 'fixed';\n hint.style.left = '10px';\n hint.style.top = '10px';\n hint.style.padding = '10px';\n hint.style.background = '#fff';\n hint.style.color = '#000';\n hint.style.zIndex = '100000';\n hint.style.border = '1px solid #007ec6';\n hint.style.borderRadius = '5px';\n hint.style.boxShadow = '0px 0px 20px 5px rgba(0,0,0, 0.3)';\n hint.style.fontSize = '14px';\n hint.style.fontWeight = 'bold';\n hint.style.textAlign = 'center';\n\n hint.innerHTML = message;\n \n window.document.body.appendChild(hint);\n\n if (typeof duration === 'number' && duration > 0) {\n setTimeout(function() {\n hint.remove();\n }, duration);\n }\n\n }\n\n}","import {Util} from './util.js';\n\nexport class Framework {\n\n constructor(instance) {\n\n this.name = 'generic';\n this.instance = instance;\n\n this.flip_on_png = false;\n\n }\n\n get_image(from_canvas) {\n\n throw \"Missing Implementation.\";\n\n }\n\n set_image(new_pixels) {\n\n throw \"Missing Implementation.\";\n\n }\n\n set_mask(new_mask) {\n\n throw \"Missing Implementation.\";\n \n }\n\n select_box(callback) {\n\n throw \"Missing Implementation.\";\n\n }\n\n convert_to_png(uint8array, width, height) {\n\n return Util.convert_to_png(uint8array, width, height, this.flip_on_png);\n\n }\n\n}\n","import {Framework} from '../framework.js';\n\nimport {Util} from '../util.js';\n\nimport {CanvasFallback} from './canvasFallback.js';\n\nexport class Cornerstone2D extends Framework {\n \n constructor(instance) {\n\n super(instance);\n this.name = 'cornerstone2D';\n this.canvasFallback = new CanvasFallback();\n\n this.cornerstonetools_instance = null;\n\n if (typeof window.cornerstoneTools != 'undefined') {\n\n // TODO probably not too robust\n this.cornerstonetools_instance = window.cornerstoneTools;\n\n }\n\n this.flip_on_png = false;\n\n }\n\n get_image(from_canvas) {\n\n let element = this.instance.getEnabledElements()[0];\n let pixels = null;\n let width = null;\n let height = null;\n\n if (typeof from_canvas != 'undefined') {\n\n // TODO this is hacky going through the canvas\n // later should grab the real volume data\n\n let canvas = element.canvas;\n width = canvas.width;\n height = canvas.height;\n\n let ctx = canvas.getContext('2d');\n\n let imagedata = ctx.getImageData(0, 0, width, height);\n pixels = imagedata.data;\n\n } else {\n\n // this is the real image slice data\n let imagedata = element.image;\n pixels = imagedata.getPixelData();\n width = imagedata.width;\n height = imagedata.height;\n\n }\n\n return {'data':pixels, 'width':width, 'height':height};\n\n }\n\n set_image(new_pixels) {\n\n let element = this.instance.getEnabledElements()[0];\n let pixels = element.image.getPixelData();\n\n // Set the new pixel values\n pixels.set(new_pixels);\n\n // Re-render the current slice\n cornerstone.renderGrayscaleImage(element, true);\n\n }\n\n set_mask(new_mask) {\n return this.canvasFallback.set_mask(new_mask);\n \n }\n\n select_box(callback) {\n\n if(this.cornerstonetools_instance.RectangleRoiTool === undefined) {\n console.log(\"Using Boxcraft library to handle box selection.\");\n let element = this.instance.getEnabledElements()[0];\n let canvas = element.canvas;\n\n // Disable the Wwwc tool\n cornerstoneTools.setToolDisabled(\"Wwwc\");\n\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\n callback(topleft, bottomright);\n });\n\n BoxCraft.createResizableBBox(canvas, function(topleft, bottomright) {\n console.log(\"Inside Draggable BBox\",topleft, bottomright);\n callback(topleft, bottomright);\n });\n\n // return this.canvasFallback.select_box(callback);\n\n\n }\n else{\n console.log(\"Using Cornerstonetools to handle box selection.\");\n this.cornerstonetools_instance.setToolActive(\"RectangleRoi\", {\n mouseButtonMask: 1,\n });\n\n let element = this.instance.getEnabledElements()[0];\n let canvas = element.canvas;\n\n canvas.onmouseup = function () {\n let state =\n this.cornerstonetools_instance.globalImageIdSpecificToolStateManager.saveToolState();\n\n let topleft =\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.start;\n let bottomright =\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.end;\n\n let topleft_c = this.instance.pixelToCanvas(element.element, topleft);\n let bottomright_c = this.instance.pixelToCanvas(\n element.element,\n bottomright\n );\n\n this.cornerstonetools_instance.clearToolState(\n element.element,\n \"RectangleRoi\"\n );\n this.instance.renderGrayscaleImage(element, true);\n callback(topleft_c, bottomright_c);\n }.bind(this);\n }\n\n }\n\n}","import { Framework } from \"../framework.js\";\n\nimport { Util } from \"../util.js\";\n\n\n\nexport class CanvasFallback extends Framework {\n constructor() {\n super();\n this.name = \"canvasFallback\";\n }\n\n get_canvas() {\n let canvases = document.querySelectorAll('canvas');\n let largestCanvas = canvases[0];\n let largestArea = largestCanvas.width * largestCanvas.height;\n\n for (let i = 1; i < canvases.length; i++) {\n let area = canvases[i].width * canvases[i].height;\n if (area > largestArea) {\n largestCanvas = canvases[i];\n largestArea = area;\n }\n }\n\n return largestCanvas;\n }\n\n get_image(from_canvas) {\n\n let canvas = this.get_canvas();\n\n let ctx = canvas.getContext(\"2d\");\n\n let image = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let rgba_image = Util.rgba_to_grayscale(image.data);\n\n if (from_canvas) {\n return { data: image.data, width: image.width, height: image.height };\n } else {\n return { data: rgba_image, width: image.width, height: image.height };\n }\n }\n\n set_image(new_pixels) {\n let originalcanvas = this.get_canvas();\n\n let newcanvas = window.document.createElement(\"canvas\");\n\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n\n let ctx = newcanvas.getContext(\"2d\");\n\n let newPixelsRgba = Util.grayscale_to_rgba(new_pixels);\n\n let newPixelsClamped = new Uint8ClampedArray(newPixelsRgba);\n\n let newImageData = new ImageData(\n newPixelsClamped,\n newcanvas.width,\n newcanvas.height\n );\n\n // Draw the new image data onto the canvas\n ctx.putImageData(newImageData, 0, 0);\n\n newcanvas.onclick = function () {\n // on click, we will restore the nv canvas\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\n };\n\n // replace nv canvas with new one\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n }\n\n set_mask(new_mask) {\n let image = this.get_image(true);\n\n let originalcanvas = this.get_canvas();\n\n let newcanvas = window.document.createElement('canvas');\n\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n\n let ctx = newcanvas.getContext('2d');\n\n let imageclamped = new Uint8ClampedArray(image.data);\n\n let newImageData = new ImageData(\n imageclamped,\n newcanvas.width,\n newcanvas.height\n );\n\n // Draw the new image data onto the canvas\n ctx.putImageData(newImageData, 0, 0);\n\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\n\n let masked_image = Util.harden_mask(image.data, new_mask);\n\n let masked_image_as_imagedata = new ImageData(\n masked_image,\n newcanvas.width,\n newcanvas.height\n );\n\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\n\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n }\n\n select_box(callback) {\n let scriptBoxCraft = document.createElement(\"script\");\n scriptBoxCraft.type = \"text/javascript\";\n scriptBoxCraft.src = \"https://boostlet.org/dist/boxcraft.min.js\";\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/BoxCraft/dist/boxCraft.min.js\";\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/boostlet/dist/boxcraft.min.js\";\n // scriptBoxCraft.src = \"http://localhost:8000/dist/boxcraft.min.js\";\n let canvas = this.get_canvas();\n document.head.appendChild(scriptBoxCraft);\n\n scriptBoxCraft.onload = function() {\n\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\n console.log(\"Inside Draggable BBox\", topleft, bottomright);\n callback(topleft, bottomright);\n });\n }\n\n }\n\n}\n","import {Framework} from '../framework.js';\n\nimport {Util} from '../util.js';\nimport {CanvasFallback} from './canvasFallback.js';\n\nexport class NiiVue extends Framework {\n \n constructor(instance) {\n\n super(instance);\n this.name = 'niivue';\n this.canvasFallback = new CanvasFallback();\n\n this.flip_on_png = true;\n\n this.onMouseDown = false;\n this.x1 = null;\n this.y1 = null;\n this.x2 = null;\n this.y2 = null;\n\n }\n\n get_image(from_canvas) {\n\n \n let pixels = null;\n let width = null;\n let height = null;\n\n if (!Util.is_defined(from_canvas)) {\n\n // use the canvas\n\n let element = this.instance.canvas;\n\n let old_crosshaircolor = this.instance.opts.crosshairColor;\n let old_crosshairwidth = this.instance.opts.crosshairWidth;\n\n this.instance.setCrosshairColor([0,0,0,0]);\n this.instance.opts.crosshairWidth=0;\n this.instance.updateGLVolume();\n\n\n let ctx = this.instance.gl;\n\n \n width = ctx.drawingBufferWidth;\n height = ctx.drawingBufferHeight;\n\n pixels = new Uint8Array(width * height * 4);\n ctx.readPixels(\n 0, \n 0, \n width, \n height, \n ctx.RGBA, \n ctx.UNSIGNED_BYTE, \n pixels);\n\n // restore crosshairs\n this.instance.setCrosshairColor(old_crosshaircolor);\n this.instance.opts.crosshairWidth = old_crosshairwidth;\n\n\n // convert rgba pixels to grayscale\n pixels = Util.rgba_to_grayscale(pixels);\n\n\n } else {\n\n // grab the real pixels of the current slice and return it\n // set width and height accordingly\n\n let v = this.instance.volumes[0];\n let currentSlice = this.instance.opts.sliceType;\n let currentCrossHairPos = this.instance.scene.crosshairPos;\n \n \n // Default to full volume\n let start = [0, 0, 0];\n let end = [v.dims[1], v.dims[2], v.dims[3]];\n\n if(currentSlice == 0){\n // axial — slice along Z\n let z_index = Math.floor(currentCrossHairPos[2] * v.dims[3]);\n width = v.dims[1];\n height = v.dims[2];\n start = [0, 0, z_index];\n end = [v.dims[1], v.dims[2], z_index + 1];\n }\n else if(currentSlice == 1){\n // coronal — slice along Y\n let y_index = Math.floor(currentCrossHairPos[1] * v.dims[2]);\n width = v.dims[1];\n height = v.dims[3];\n start = [0, y_index, 0];\n end = [v.dims[1], y_index + 1 , v.dims[3]];\n }\n else if(currentSlice == 2){\n // sagittal — slice along X\n let x_index = Math.floor(currentCrossHairPos[0] * v.dims[1]);\n width = v.dims[2];\n height = v.dims[3];\n start = [x_index, 0, 0];\n end = [x_index + 1, v.dims[2], v.dims[3]];\n }\n else {\n // multiplanar (sliceType 3) — no single slice to extract,\n // return the full volume\n width = v.dims[1];\n height = v.dims[2];\n }\n\n pixels = v.getVolumeData(start, end)[0];\n\n }\n\n return {'data':pixels, 'width':width, 'height':height};\n\n }\n\n\n\n get_subvolume(start, end) {\n const [data, dims] = this.instance.volumes[0].getVolumeData(start, end);\n return { data, dims };\n }\n\n get_dims(){\n const v = this.instance.volumes[0];\n const currentSlice = this.instance.opts.sliceType;\n\n if(currentSlice == 0) return [v.dims[1], v.dims[2], 1]; //axial\n if(currentSlice == 1 ) return [v.dims[1], v.dims[3], 1 ]; // coronal\n if(currentSlice == 2) return [v.dims[2], v.dims[3], 1 ]; // saggital\n return [v.dims[1], v.dims[2], 1]; // multiplanar fallback\n } \n\n /**\n * Sets the NiiVue.js image.\n * \n * If is_rgba==true, we do *not* convert to RGBA before setting on canvas.\n **/\n set_image(new_pixels, is_rgba, no_flip) {\n\n // TODO this is hacky since we dont work with the real volume yet\n // create new canvas\n // put pixels on canvas\n // show canvas\n // hide on click\n\n let originalcanvas = this.instance.canvas;\n\n let newcanvas = window.document.createElement('canvas');\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n\n // put new_pixels down\n let ctx = newcanvas.getContext('2d');\n\n let new_pixels_rgba = null;\n\n if (Util.is_defined(is_rgba)) {\n\n new_pixels_rgba = new_pixels;\n\n } else {\n\n new_pixels_rgba = Util.grayscale_to_rgba(new_pixels);\n\n\n }\n\n let new_pixels_clamped = new Uint8ClampedArray(new_pixels_rgba);\n\n let new_image_data = new ImageData(new_pixels_clamped, newcanvas.width, newcanvas.height);\n \n\n ctx.putImageData(new_image_data, 0, 0);\n\n if (!Util.is_defined(no_flip)) {\n // some flipping action\n ctx.save();\n ctx.scale(1, -1);\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\n ctx.restore();\n }\n\n\n newcanvas.onclick = function() {\n\n // on click, we will restore the nv canvas\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\n\n }\n\n // replace nv canvas with new one\n // originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n newcanvas.style.width = originalcanvas.clientWidth+'px';\n newcanvas.style.height = originalcanvas.clientHeight+'px';\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n\n }\n\n set_mask(new_mask) {\n\n // merge image + mask\n // and then call set_image with that information\n\n let image = this.get_image(true);\n\n // TODO here we need to flip one more time, this is until\n // we use the official niivue infrastructure for adding\n // a segmentation layer\n let originalcanvas = this.instance.canvas;\n\n let newcanvas = window.document.createElement('canvas');\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n // put new_pixels down\n let ctx = newcanvas.getContext('2d');\n let imageclamped = new Uint8ClampedArray(image.data);\n let imagedata = new ImageData(imageclamped, image.width, image.height);\n ctx.putImageData(imagedata, 0, 0);\n ctx.save();\n ctx.scale(1, -1);\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\n ctx.restore();\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\n // end of flip\n\n let masked_image = Util.harden_mask(image.data, new_mask);\n\n this.set_image(masked_image, true, true); // rgba data, no flip\n\n\n }\n\n select_box(callback) {\n return this.canvasFallback.select_box(callback);\n\n }\n\n}\n","import {Framework} from '../framework.js';\n\nimport {Util} from '../util.js';\n\nimport {CanvasFallback} from './canvasFallback.js';\n\nexport class OpenSeaDragon extends Framework {\n constructor(instance) {\n super(instance);\n this.name = 'opensedragon';\n this.canvasFallback = new CanvasFallback();\n\n }\n\n get_image(from_canvas) {\n return this.canvasFallback.get_image(from_canvas);\n }\n\n set_image(new_pixels) {\n return this.canvasFallback.set_image(new_pixels);\n }\n\n set_mask(new_mask) {\n // return this.canvasFallback.set_mask(new_mask);\n\n let viewer = null;\n let vs = this.instance._viewers;\n vs.forEach(function (e) {\n if (e.id == \"viewer\") {\n viewer = e;\n }\n });\n\n if (!viewer) {\n throw \"OpenSeaDragon viewer not found.\";\n }\n\n let canvas = viewer.canvas.children[0];\n width = canvas.width;\n height = canvas.height;\n\n let ctx = canvas.getContext(\"2d\");\n let imagedata = ctx.getImageData(0, 0, width, height);\n let pixels = imagedata.data;\n\n let masked_image = Util.harden_mask(pixels, new_mask);\n let masked_image_as_imagedata = new ImageData(masked_image, width, height);\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\n }\n\n select_box(callback) {\n\n console.log(\"Using Boxcraft library to handle box selection.\");\n let viewer = null;\n let vs = this.instance._viewers;\n vs.forEach(function (e) {\n if (e.id == \"viewer\") {\n viewer = e;\n }\n });\n\n if (!viewer) {\n throw \"OpenSeaDragon viewer not found.\";\n }\n\n let canvas = viewer.canvas;\n\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\n callback(topleft, bottomright);\n });\n\n }\n\n \n\n}\n \n","import { Framework } from \"../framework.js\";\n\nimport { Util } from \"../util.js\";\n\nimport { CanvasFallback } from \"./canvasFallback.js\";\n\nexport class Xtk extends Framework {\n constructor(instance) {\n super(instance);\n this.name = \"xtk\";\n this.canvasFallback = new CanvasFallback();\n }\n\n get_image(from_canvas) {\n return this.canvasFallback.get_image(from_canvas);\n }\n\n set_image(new_pixels) {\n return this.canvasFallback.set_image(new_pixels);\n }\n\n set_mask(new_mask) {\n return this.canvasFallback.set_mask(new_mask);\n }\n\n select_box(callback) {\n return this.canvasFallback.select_box(callback);\n }\n}\n","import { Framework } from \"../framework.js\";\n\nimport { Util } from \"../util.js\";\n\nimport { CanvasFallback } from \"./canvasFallback.js\";\n\nexport class Papaya extends Framework {\n constructor(instance) {\n super(instance);\n this.name = \"papaya\";\n this.canvasFallback = new CanvasFallback();\n }\n\n get_image(from_canvas) {\n return this.canvasFallback.get_image(from_canvas);\n }\n\n set_image(new_pixels) {\n return this.canvasFallback.set_image(new_pixels);\n }\n\n set_mask(new_mask) {\n return this.canvasFallback.set_mask(new_mask);\n }\n\n select_box(callback) {\n return this.canvasFallback.select_box(callback);\n }\n}\n","\nimport {Boostlet} from \"./boostlet.js\"\n\nwindow.console.log('BOOSTLET VERSION 0.1-beta');\n\n// register global namespace with a new boostlet instance\n// later we might want to support multiple active boostlets\nwindow.Boostlet = new Boostlet();\n","import {Util} from './util.js';\nimport {Framework} from './framework.js';\nimport {loadStats} from './stats.js';\n\nexport class Boostlet {\n\n constructor() {\n this.framework = null;\n }\n\n /**\n * Initializes the Boostlet.\n * \n * This includes several steps such as identifying the \n * visualization/rendering framework that is available. \n * \n * TODO: Later we want to have fallbacks in place if the framework\n * is not detected.\n * \n */\n init(name, instance) {\n\n if (typeof name != 'undefined' && typeof instance != 'undefined') {\n\n console.log('Framework forced by user!');\n throw \"Forced Framework Not Implemented.\";\n // TODO\n\n } else {\n\n this.framework = Util.detect_framework();\n\n }\n\n if (this.framework) {\n\n console.log('Found', this.framework, '!')\n \n } else {\n\n throw \"Framework Not Found.\";\n\n }\n\n }\n\n /**\n * Let's the user select a region of interest box.\n */\n async select_box(callback) {\n\n this.framework.select_box(callback);\n\n }\n\n /**\n * Let's the user select (multiple) seeds.\n */\n async select_seed(howmany) {\n\n throw \"Missing Implementation.\";\n\n }\n\n /**\n * Loads an external javascript file asynchronously. \n */\n async load_script(url, callback) {\n\n Util.load_script(url, callback);\n\n }\n\n /**\n * Sends a HTTP POST request to a url with some data.\n */\n async send_http_post(url, data, callback) {\n\n Util.send_http_post(url, data, callback);\n\n }\n\n /**\n * Gets the current image (2D).\n * \n * TODO: Optional bounding box should be supported.\n * \n */\n get_image(from_canvas) {\n\n return this.framework.get_image(from_canvas);\n\n }\n\n /**\n * Sets the current image (2D).\n * \n */\n set_image(new_pixels) {\n\n return this.framework.set_image(new_pixels);\n\n }\n\n /**\n * Sets the current mask (2D).\n * \n */\n set_mask(new_mask) {\n\n return this.framework.set_mask(new_mask);\n\n }\n\n get_dims() {\n return this.framework.get_dims();\n }\n\n get_subvolume(start, end) {\n return this.framework.get_subvolume(start, end);\n }\n\n /**\n * Encode raw image data to PNG.\n */\n convert_to_png(uint8array, width, height) {\n\n return this.framework.convert_to_png(uint8array, width, height);\n\n }\n\n filter(pixels, width, height, kernel) {\n\n return Util.filter(pixels, width, height, kernel);\n\n }\n\n /**\n * Displays a small div located at the top left corner of the screen \n * with message and will disappear after the specified time (ms).\n */\n hint(message, duration) {\n\n return Util.hint(message, duration);\n\n }\n\n \n \n // const stats = await Boostlet.stats();\n // const result = stats.all(subvolumeData);\n // gives you { mean, std, min, max, p25, p75 }\n async stats() {\n\n return loadStats();\n\n }\n\n}\n","// stats.js — numpy-ts powered statistics for Boostlet\n// Lazy-loads numpy-ts once on first call, then reuses the instance.\n//\n// Usage from a boostlet:\n// const stats = await Boostlet.stats();\n// const result = stats.all(data, nv.volumes[0]);\n\nconst NUMPY_TS_URL = 'https://esm.run/numpy-ts';\n\nlet _np = null;\n\n// only load numpy-ts once, cache it in _np\nasync function _load() {\n if (!_np) {\n _np = await import(NUMPY_TS_URL);\n }\n return _np;\n}\n\nexport async function loadStats() {\n const np = await _load();\n\n return {\n\n // convert typed array to a numpy-ts array so we can run ops on it\n _wrap(data) {\n return np.array(Array.from(data), 'float32');\n },\n\n // apply nifti slope/intercept: display = raw * slope + inter\n _applyScaling(data, volume) {\n if (!volume) return this._wrap(data);\n const slope = volume.hdr.scl_slope || 1;\n const inter = volume.hdr.scl_inter || 0;\n const a = this._wrap(data);\n return a.multiply(slope).add(inter);\n },\n\n // numpy-ts returns array objects not plain numbers, unwrap to a scalar\n _scalar(val) {\n return typeof val === 'number' ? val : val.tolist ? val.tolist() : Number(val);\n },\n\n mean(data, volume) {\n return this._scalar(np.mean(this._applyScaling(data, volume)));\n },\n\n std(data, volume) {\n return this._scalar(np.std(this._applyScaling(data, volume)));\n },\n\n min(data, volume) {\n return this._scalar(np.min(this._applyScaling(data, volume)));\n },\n\n max(data, volume) {\n return this._scalar(np.max(this._applyScaling(data, volume)));\n },\n\n percentile(data, p, volume) {\n return this._scalar(np.percentile(this._applyScaling(data, volume), p));\n },\n\n // returns all stats in one pass\n // pass volume as second arg to apply slope/intercept\n all(data, volume) {\n const a = this._applyScaling(data, volume);\n return {\n mean : this._scalar(np.mean(a)),\n std : this._scalar(np.std(a)),\n min : this._scalar(np.min(a)),\n max : this._scalar(np.max(a)),\n p25 : this._scalar(np.percentile(a, 25)),\n p75 : this._scalar(np.percentile(a, 75)),\n };\n }\n\n };\n}\n"],"names":[],"version":3,"file":"boostlet.min.js.map"} \ No newline at end of file diff --git a/examples/PixelExplorer.js b/examples/PixelExplorer.js index f2ee9704..634f82fe 100644 --- a/examples/PixelExplorer.js +++ b/examples/PixelExplorer.js @@ -1,10 +1,23 @@ CATEGORY = "Visualization" +if (typeof window._PixelExplorerLoaded === 'undefined') { +window._PixelExplorerLoaded = true; + +(function() { + const script = document.createElement("script"); script.type = "text/javascript"; script.src = "https://boostlet.org/dist/boostlet.min.js"; - -script.onload = run; +script.onload = function() { + /// wait for nv and volumes before calling init + // calling init too early means niivue won't be detected yet + var poll = setInterval(function() { + if (window.nv && nv.volumes && nv.volumes.length > 0) { + clearInterval(poll); + run(); + } + }, 300); +}; document.head.appendChild(script); const HALF = 4; @@ -18,35 +31,32 @@ function run() { } const nv = Boostlet.framework.instance; + setup(nv); +} - if (!nv.volumes.length) { - alert('No volume loaded yet!'); - return; - } - - // Create the panel once +function setup(nv) { + // create the panel once plot(); - // Save the existing onLocationChange so we don't clobber it + // save the existing onLocationChange so we don't clobber it const existingOnLocationChange = nv.onLocationChange; - // Hook into location change — fires on every crosshair move / slice change + // hook into location change — fires on every crosshair move / slice change nv.onLocationChange = function(loc) { - // call the original if it existed if (existingOnLocationChange) existingOnLocationChange(loc); - if (!nv.volumes.length) return; + if (!nv.volumes || !nv.volumes.length) return; - const v = [Math.round(loc.vox[0]), Math.round(loc.vox[1]), Math.round(loc.vox[2])]; // Cursor position in voxel coordinates. + const v = [Math.round(loc.vox[0]), Math.round(loc.vox[1]), Math.round(loc.vox[2])]; const start = [v[0] - HALF, v[1] - HALF, v[2] - HALF]; const end = [v[0] + HALF, v[1] + HALF, v[2] + HALF]; - // collapses the slice plane axis to a single voxel - // axial(0) Z=2, coronal(1) Y=1, sagittal(2) X=0 so that you don't get a 3D chunk of data - const fixedAxis = nv.opts.sliceType === nv.sliceTypeSagittal ? 0 - : nv.opts.sliceType === nv.sliceTypeCoronal ? 1 : 2; - start[fixedAxis] = end[fixedAxis] = Math.round((start[fixedAxis] + end[fixedAxis]) / 2); // Flattens axis, finds midpoint + // collapse the axis perpendicular to the current slice to a single voxel + // 2 = sagittal (x axis), 1 = coronal (y axis), default axial (z axis) + const fixedAxis = nv.opts.sliceType === 2 ? 0 + : nv.opts.sliceType === 1 ? 1 : 2; + start[fixedAxis] = end[fixedAxis] = Math.round((start[fixedAxis] + end[fixedAxis]) / 2); const { data, dims } = Boostlet.get_subvolume(start, end); if (data && dims) { @@ -60,7 +70,7 @@ function run() { function drawMatrix(data, dims, mc) { const nv = Boostlet.framework.instance; const { cal_min, cal_max } = nv.volumes[0]; - const range = cal_max - cal_min || 1; // range between display values. + const range = cal_max - cal_min || 1; const ctx = mc.getContext('2d'); // apply nifti slope/intercept to convert raw values to display values. @@ -70,14 +80,13 @@ function drawMatrix(data, dims, mc) { const inter = nv.volumes[0].hdr.scl_inter || 0; // getVolumeData returns dims as X Y Z and the collapsed axis has size 1 - // the first non 1 dim is cols second is rows + // the first non-1 dim is cols, second is rows const [cols, rows] = dims.filter(d => d > 1); if (!cols || !rows) return; - const cell = Math.floor(Math.min(mc.width / cols, mc.height / rows)); // picks between max cell width and max cell height. picks the smallest + const cell = Math.floor(Math.min(mc.width / cols, mc.height / rows)); if (cell < 1) return; - // Figures out whether to show a label or not. needs to be changed IMO const showLabels = cell >= 8; if (showLabels) { ctx.font = `${Math.max(Math.floor(cell * 0.28), 6)}px monospace`; @@ -87,13 +96,11 @@ function drawMatrix(data, dims, mc) { ctx.clearRect(0, 0, mc.width, mc.height); - // Loops through every voxel in the region, draws a grey square for each one based on how bright it is. If the cells are - // big enough, it prints out raw number on top. for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const raw = data[c + (rows - 1 - r) * cols]; - const val = raw * slope + inter; // convert raw to display value - const norm = (val - cal_min) / range; // Gives a range from 0 - 1, gets normalized to 0-255 in the line below + const val = raw * slope + inter; + const norm = (val - cal_min) / range; const g = Math.round(norm * 255); ctx.fillStyle = `rgb(${g},${g},${g})`; @@ -108,7 +115,7 @@ function drawMatrix(data, dims, mc) { } function plot() { - // Remove existing panel if re ran + // remove existing panel if re-run const existing = document.getElementById('PixelExplorerDiv'); if (existing) existing.remove(); @@ -124,7 +131,7 @@ function plot() { mc.style.cssText = 'display:block; width:400px; height:400px;'; container.appendChild(mc); - // Drag logic + // drag logic let startX, startY, startLeft, startTop; container.addEventListener('mousedown', function(e) { startX = e.clientX; @@ -143,4 +150,7 @@ function plot() { document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }); -} \ No newline at end of file +} + +})(); +} diff --git a/examples/roiStats.js b/examples/roiStats.js new file mode 100644 index 00000000..c90eadf1 --- /dev/null +++ b/examples/roiStats.js @@ -0,0 +1,126 @@ +(function() { + +const CATEGORY = "Statistics"; + +// inject boostlet, but wait for nv and volumes before calling init +// calling init too early means niivue won't be detected yet +const script = document.createElement("script"); +script.type = "text/javascript"; +script.src = "https://boostlet.org/dist/boostlet.min.js"; +script.onload = function() { + var poll = setInterval(function() { + if (window.nv && nv.volumes && nv.volumes.length > 0) { + clearInterval(poll); + run(); + } + }, 300); +}; +document.head.appendChild(script); + +// half-width of the roi in voxels, so the full neighborhood is (2*HALF+1)^2 +const HALF = 4; + +async function run() { + Boostlet.init(); + + if (Boostlet.framework.name !== 'niivue') { + alert('Only niivue is supported right now :('); + return; + } + + const nv = Boostlet.framework.instance; + setup(nv); +} + +async function setup(nv) { + plot(); + + // load numpy-ts via stats wrapper + const stats = await Boostlet.stats(); + + // save whatever handler was already on nv so we can chain it + const existingOnLocationChange = nv.onLocationChange; + + nv.onLocationChange = async function(loc) { + if (existingOnLocationChange) existingOnLocationChange(loc); + if (!nv.volumes || !nv.volumes.length) return; + + const v = [Math.round(loc.vox[0]), Math.round(loc.vox[1]), Math.round(loc.vox[2])]; + + const start = [v[0] - HALF, v[1] - HALF, v[2] - HALF]; + const end = [v[0] + HALF, v[1] + HALF, v[2] + HALF]; + + // collapse the axis perpendicular to the current slice to a single voxel + // 2 = sagittal (x axis), 1 = coronal (y axis), default axial (z axis) + const fixedAxis = nv.opts.sliceType === 2 ? 0 + : nv.opts.sliceType === 1 ? 1 : 2; + start[fixedAxis] = end[fixedAxis] = Math.round((start[fixedAxis] + end[fixedAxis]) / 2); + + const { data } = Boostlet.get_subvolume(start, end); + if (!data || data.length === 0) return; + + const result = stats.all(data, nv.volumes[0]); + updatePanel(result); + }; +} + +// write computed stats into the panel dom elements +function updatePanel(s) { + document.getElementById('roi-mean').textContent = s.mean.toFixed(2); + document.getElementById('roi-std').textContent = s.std.toFixed(2); + document.getElementById('roi-min').textContent = s.min.toFixed(2); + document.getElementById('roi-max').textContent = s.max.toFixed(2); + document.getElementById('roi-p25').textContent = s.p25.toFixed(2); + document.getElementById('roi-p75').textContent = s.p75.toFixed(2); +} + +// create and inject the stats panel, remove any existing one first +function plot() { + const existing = document.getElementById('RoiStatsDiv'); + if (existing) existing.remove(); + + const div = document.createElement('div'); + div.id = 'RoiStatsDiv'; + div.style.cssText = ` + position: fixed; top: 10px; right: 10px; z-index: 1000; + background: rgba(0,0,0,0.85); color: #fff; + font-family: monospace; font-size: 13px; + padding: 12px 16px; border-radius: 6px; + border: 1px solid #444; min-width: 160px; + cursor: move; line-height: 2; + `; + + div.innerHTML = ` +
ROI Stats (${HALF*2+1}×${HALF*2+1})
+
mean
+
std
+
min
+
max
+
p25
+
p75
+ `; + + document.body.appendChild(div); + + // drag logic + let startX, startY, startRight, startTop; + div.addEventListener('mousedown', function(e) { + startX = e.clientX; + startY = e.clientY; + startRight = div.offsetLeft; + startTop = div.offsetTop; + function onMove(e) { + div.style.left = (startRight + e.clientX - startX) + 'px'; + div.style.top = (startTop + e.clientY - startY) + 'px'; + div.style.right = 'auto'; + } + function onUp() { + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + } + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }); +} + +})(); diff --git a/index.html b/index.html index 9b0d88f3..176490d1 100644 --- a/index.html +++ b/index.html @@ -229,19 +229,9 @@ window.onload = function() { - - - // return; - - - var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'meshAR.js', 'fibersAR.js', 'plotly.js', 'trako.js', 'melanoma.js', 'webllm.js', 'brainchop.js', 'CatVSDog.js', 'PixelExplorer.js']; + var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'meshAR.js', 'fibersAR.js', 'plotly.js', 'trako.js', 'melanoma.js', 'webllm.js', 'brainchop.js', 'CatVSDog.js', 'PixelExplorer.js', 'roiStats.js']; var baseurl = 'https://raw.githubusercontent.com/mpsych/boostlet/main/examples/'; - // var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/examples/'; - // var baseurl = 'http://localhost:5500/examples/'; - // var baseurl = 'http://localhost:8000/examples/'; - // var baseurl = 'https://raw.githubusercontent.com/shrutivarade/boostlet/main/examples/'; - for (e in examples) { @@ -251,8 +241,6 @@ } - // var codeurl = 'http://localhost:5500/examples/'; - // var codeurl = 'https://github.com/shrutivarade/boostlet/blob/main/examples/'; var codeurl = 'https://github.com/mpsych/boostlet/blob/main/examples/'; for (e in examples) { @@ -319,26 +307,25 @@

- - + +
diff --git a/package-lock.json b/package-lock.json index 39151d91..d8712661 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3152,102 +3152,6 @@ "node": ">= 8" } }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-select/node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/css-select/node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/css-select/node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/css-select/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -3260,45 +3164,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/data-uri-to-buffer": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", @@ -4845,14 +4710,6 @@ "node": ">=0.10.0" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -6401,17 +6258,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -6589,33 +6435,6 @@ "node": ">=8" } }, - "node_modules/svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, "node_modules/tar-fs": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", diff --git a/src/boostlet.js b/src/boostlet.js index 6d33f4a5..b9be5774 100644 --- a/src/boostlet.js +++ b/src/boostlet.js @@ -1,6 +1,6 @@ import {Util} from './util.js'; - import {Framework} from './framework.js'; +import {loadStats} from './stats.js'; export class Boostlet { @@ -113,7 +113,7 @@ export class Boostlet { } get_dims() { - return this.framework.get_dims(); + return this.framework.get_dims(); } get_subvolume(start, end) { @@ -136,8 +136,8 @@ export class Boostlet { } /** - * Displays a small div located at the top left corner of the screen with message and will disappear after the specified time (ms). - * + * Displays a small div located at the top left corner of the screen + * with message and will disappear after the specified time (ms). */ hint(message, duration) { @@ -145,4 +145,15 @@ export class Boostlet { } + + + // const stats = await Boostlet.stats(); + // const result = stats.all(subvolumeData); + // gives you { mean, std, min, max, p25, p75 } + async stats() { + + return loadStats(); + + } + } diff --git a/src/stats.js b/src/stats.js new file mode 100644 index 00000000..a8f61e91 --- /dev/null +++ b/src/stats.js @@ -0,0 +1,79 @@ +// stats.js — numpy-ts powered statistics for Boostlet +// Lazy-loads numpy-ts once on first call, then reuses the instance. +// +// Usage from a boostlet: +// const stats = await Boostlet.stats(); +// const result = stats.all(data, nv.volumes[0]); + +const NUMPY_TS_URL = 'https://esm.run/numpy-ts'; + +let _np = null; + +// only load numpy-ts once, cache it in _np +async function _load() { + if (!_np) { + _np = await import(NUMPY_TS_URL); + } + return _np; +} + +export async function loadStats() { + const np = await _load(); + + return { + + // convert typed array to a numpy-ts array so we can run ops on it + _wrap(data) { + return np.array(Array.from(data), 'float32'); + }, + + // apply nifti slope/intercept: display = raw * slope + inter + _applyScaling(data, volume) { + if (!volume) return this._wrap(data); + const slope = volume.hdr.scl_slope || 1; + const inter = volume.hdr.scl_inter || 0; + const a = this._wrap(data); + return a.multiply(slope).add(inter); + }, + + // numpy-ts returns array objects not plain numbers, unwrap to a scalar + _scalar(val) { + return typeof val === 'number' ? val : val.tolist ? val.tolist() : Number(val); + }, + + mean(data, volume) { + return this._scalar(np.mean(this._applyScaling(data, volume))); + }, + + std(data, volume) { + return this._scalar(np.std(this._applyScaling(data, volume))); + }, + + min(data, volume) { + return this._scalar(np.min(this._applyScaling(data, volume))); + }, + + max(data, volume) { + return this._scalar(np.max(this._applyScaling(data, volume))); + }, + + percentile(data, p, volume) { + return this._scalar(np.percentile(this._applyScaling(data, volume), p)); + }, + + // returns all stats in one pass + // pass volume as second arg to apply slope/intercept + all(data, volume) { + const a = this._applyScaling(data, volume); + return { + mean : this._scalar(np.mean(a)), + std : this._scalar(np.std(a)), + min : this._scalar(np.min(a)), + max : this._scalar(np.max(a)), + p25 : this._scalar(np.percentile(a, 25)), + p75 : this._scalar(np.percentile(a, 75)), + }; + } + + }; +} diff --git a/src/util.js b/src/util.js index 4ca5add5..24cfb56a 100644 --- a/src/util.js +++ b/src/util.js @@ -7,41 +7,82 @@ import { Papaya } from './frameworks/papaya.js'; import { CanvasFallback } from './frameworks/canvasFallback.js'; export class Util { + + // check if an object resembles a niivue instance + // instanceof breaks since boostlet loads its own copy of the module + static looks_like_niivue(obj) { + if (!obj || typeof obj !== 'object') return false + if (!Array.isArray(obj.volumes)) return false + if (!obj.scene || typeof obj.scene !== 'object') return false + if (!obj.opts || obj.opts.sliceType === undefined) return false + if (typeof obj.updateGLVolume !== 'function') return false + return true + } + + // scan all window properties for anything that looks like a niivue instance + // runs only if the known name checks above miss + static scan_window_for_niivue() { + const seen = new Set() + for (const key of Object.getOwnPropertyNames(window)) { + let val + try { + val = window[key] + } catch (e) { + // some globals throw SecurityError on access which is annoying + continue + } + if (!val || typeof val !== 'object') continue + if (seen.has(val)) continue + seen.add(val) + if (Util.looks_like_niivue(val)) return val + } + return null + } static detect_framework() { let framework = null; - if (Util.is_defined(window.nv)) { - + // fast path: check known names first + if (Util.looks_like_niivue(window.nv)) { + framework = new NiiVue(window.nv); - - } else if (Util.is_defined(window.niivue)) { - + + } else if (Util.looks_like_niivue(window.niivue)) { + framework = new NiiVue(window.niivue); - } else if (Util.is_defined(window.cornerstone)) { + } else { - framework = new Cornerstone2D(window.cornerstone); + // this is the slow path which scans window for any object that structurally matches niivue + const found = Util.scan_window_for_niivue() + if (found) { - } else if (Util.is_defined(window.r)) { - - framework = new Xtk(window.r); - - } else if (Util.is_defined(window.OpenSeadragon)) { + framework = new NiiVue(found); - framework = new OpenSeaDragon(window.OpenSeadragon); - - } else if (Util.is_defined(window.papayaContainers)) { - - framework = new Papaya(window.papayaContainers) + } else if (Util.is_defined(window.cornerstone)) { + + framework = new Cornerstone2D(window.cornerstone); + + } else if (Util.is_defined(window.r)) { + + framework = new Xtk(window.r); + + } else if (Util.is_defined(window.OpenSeadragon)) { + + framework = new OpenSeaDragon(window.OpenSeadragon); + + } else if (Util.is_defined(window.papayaContainers)) { + + framework = new Papaya(window.papayaContainers) + + } else { + + console.log("No framework detected, falling back to canvas rendering"); + framework = new CanvasFallback(); + + } - } else { - // Canvas fallback - - console.log("No framework detected, falling back to canvas rendering"); - framework = new CanvasFallback(); - } return framework; @@ -126,12 +167,6 @@ export class Util { let base64 = offscreen.toDataURL('image/png'); - // for debugging, download image - // const link = window.document.createElement("a"); - // link.href = base64; - // link.download = 'test.png'; - // link.click(); - base64 = base64.replace("data:image/png;base64,",""); let pngpixels = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); diff --git a/tests/niivue/pixelexplorer.html b/tests/niivue/pixelexplorer.html new file mode 100644 index 00000000..9d4dbf4a --- /dev/null +++ b/tests/niivue/pixelexplorer.html @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + CACTAS + + + + + + + + + +
+ Tolerance: 30
+ + +
+ + Window/Level:
+
+ +
+ + + + + + + + diff --git a/tests/niivue/roistats.html b/tests/niivue/roistats.html new file mode 100644 index 00000000..7ed6aa41 --- /dev/null +++ b/tests/niivue/roistats.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + CACTAS + + + + + + + +
+ Tolerance: 30
+ + +
+ + Window/Level:
+
+ +
+ + + + + + + + diff --git a/tests/scripts/config.json b/tests/scripts/config.json index e35e81f6..cfe19eb6 100644 --- a/tests/scripts/config.json +++ b/tests/scripts/config.json @@ -22,7 +22,9 @@ "sobel": "http://127.0.0.1:5501/tests/niivue/sobel.html", "plotly" : "http://127.0.0.1:5501/tests/niivue/plotly.html", "segmentanything" : "http://127.0.0.1:5501/tests/niivue/segmentanything.html", - "subvolume" : "http://127.0.0.1:5501/tests/niivue/subvolume.html" + "subvolume" : "http://127.0.0.1:5501/tests/niivue/subvolume.html", + "pixelexplorer" : "http://127.0.0.1:5501/tests/niivue/pixelexplorer.html", + "roistats" : "http://127.0.0.1:5501/tests/niivue/roistats.html" }, "OpenSeaDragon": { "sobel" : "http://127.0.0.1:5501/tests/openseadragon/sobel.html", diff --git a/tests/scripts/images/GroundTruth_Niivue_pixelexplorer.png b/tests/scripts/images/GroundTruth_Niivue_pixelexplorer.png new file mode 100644 index 00000000..55f89657 Binary files /dev/null and b/tests/scripts/images/GroundTruth_Niivue_pixelexplorer.png differ diff --git a/tests/scripts/images/GroundTruth_Niivue_roistats.png b/tests/scripts/images/GroundTruth_Niivue_roistats.png new file mode 100644 index 00000000..b1a7cf6c Binary files /dev/null and b/tests/scripts/images/GroundTruth_Niivue_roistats.png differ