File size: 3,703 Bytes
227ab66 491d824 227ab66 491d824 227ab66 491d824 227ab66 491d824 227ab66 491d824 227ab66 491d824 227ab66 491d824 227ab66 491d824 227ab66 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { Niivue } from '@niivue/niivue';
import { Block } from "@gradio/atoms";
import { StatusTracker } from "@gradio/statustracker";
import type { LoadingStatus } from "@gradio/statustracker";
import type { Gradio } from "@gradio/utils";
interface Props {
value?: { background_url: string | null; overlay_url: string | null } | null;
label?: string;
show_label?: boolean;
loading_status?: LoadingStatus;
elem_id?: string;
elem_classes?: string[];
visible?: boolean;
height?: number;
container?: boolean;
scale?: number;
min_width?: number;
// CRITICAL: gradio prop provides i18n, autoscroll, dispatch
gradio: Gradio<{ change: never }>;
}
let {
value = null,
label,
show_label = true,
loading_status,
elem_id = "",
elem_classes = [],
visible = true,
height = 500,
container = true,
scale = null,
min_width = undefined,
gradio
}: Props = $props();
let div_container: HTMLDivElement;
let nv: Niivue | null = null;
let canvas: HTMLCanvasElement;
let initialized = false;
onMount(async () => {
try {
nv = new Niivue({
backColor: [0, 0, 0, 1],
show3Dcrosshair: true,
logging: false
});
await nv.attachToCanvas(canvas);
// Don't call loadVolumes() here - let $effect handle it reactively
// Setting initialized = true triggers $effect which calls loadVolumes()
initialized = true;
} catch (error) {
console.error('[NiiVue] Initialization failed:', error);
}
});
onDestroy(() => {
// Release WebGL resources and event listeners
if (nv) {
nv.cleanup();
nv = null;
}
});
async function loadVolumes() {
if (!nv) return;
// Clear existing volumes
// nv.volumes is the internal array.
// The safest way to clear is to remove volumes one by one or re-init.
// However, loading new volumes usually requires removing old ones if we want a fresh state.
// NiiVue doesn't have a clearVolumes() method exposed easily in all versions,
// but iterating and removing works.
while (nv.volumes.length > 0) {
nv.removeVolume(nv.volumes[0]);
}
if (!value) {
nv.drawScene();
return;
}
const volumes = [];
if (value.background_url) {
volumes.push({ url: value.background_url });
}
if (value.overlay_url) {
volumes.push({
url: value.overlay_url,
colormap: 'red',
opacity: 0.5,
});
}
if (volumes.length > 0) {
await nv.loadVolumes(volumes);
} else {
nv.drawScene();
}
}
// Reactive effect: Re-load volumes when `value` changes (only after init)
$effect(() => {
if (initialized && value !== undefined) {
loadVolumes();
}
});
</script>
<Block
{visible}
variant={"solid"}
padding={false}
{elem_id}
{elem_classes}
{height}
allow_overflow={false}
{container}
{scale}
{min_width}
>
{#if loading_status}
<StatusTracker
autoscroll={gradio.autoscroll}
i18n={gradio.i18n}
{...loading_status}
/>
{/if}
<div bind:this={div_container} class="niivue-container" style="height: {height}px;">
<canvas bind:this={canvas}></canvas>
</div>
</Block>
<style>
.niivue-container {
width: 100%;
background: #000;
position: relative;
border-radius: var(--radius-lg);
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
outline: none;
display: block;
}
</style>
|