UNPKG

@niivue/niivue

Version:

minimal webgl2 nifti image viewer

1,473 lines (1,459 loc) 66.1 kB
import { vec4, vec3, mat4 } from 'gl-matrix'; import { NIFTI1, NIFTI2, NIFTIEXTENSION } from 'nifti-reader-js'; declare enum COLORMAP_TYPE { MIN_TO_MAX = 0, ZERO_TO_MAX_TRANSPARENT_BELOW_MIN = 1, ZERO_TO_MAX_TRANSLUCENT_BELOW_MIN = 2 } type ColorMap = { R: number[]; G: number[]; B: number[]; A: number[]; I: number[]; min?: number; max?: number; labels?: string[]; }; type LUT = { lut: Uint8ClampedArray; min?: number; max?: number; labels?: string[]; }; declare class ColorTables { gamma: number; version: number; cluts: Record<string, ColorMap>; /** * Sets cluts to alphabetically sorted cmaps */ constructor(); addColormap(key: string, cmap: ColorMap): void; colormaps(): Array<keyof typeof this.cluts>; colorMaps(): Array<keyof typeof this.cluts>; colormapFromKey(name: string): ColorMap; colormap(key?: string, isInvert?: boolean): Uint8ClampedArray; makeLabelLut(cm: ColorMap, alphaFill?: number, maxIdx?: number): LUT; makeLabelLutFromUrl(name: string, alphaFill?: number, maxIdx?: number): Promise<LUT>; makeDrawLut(name: string | ColorMap): LUT; makeLut(Rsi: number[], Gsi: number[], Bsi: number[], Asi: number[], Isi: number[], isInvert: boolean): Uint8ClampedArray; } declare const cmapper: ColorTables; declare enum LabelTextAlignment { LEFT = "left", RIGHT = "right", CENTER = "center" } declare enum LabelLineTerminator { NONE = "none", CIRCLE = "circle", RING = "ring" } declare enum LabelAnchorPoint { NONE = 0, TOPLEFT = 9, TOPCENTER = 10, TOPRIGHT = 12, MIDDLELEFT = 17, MIDDLECENTER = 18, MIDDLERIGHT = 20, BOTTOMLEFT = 33, BOTTOMCENTER = 34, BOTTOMRIGHT = 36 } /** * Class representing label style. * @ignore */ declare class NVLabel3DStyle { textColor: number[]; textScale: number; textAlignment?: LabelTextAlignment; lineWidth: number; lineColor: number[]; lineTerminator: LabelLineTerminator; bulletScale?: number; bulletColor?: number[]; backgroundColor?: number[]; /** * @param textColor - Color of text * @param textScale - Text Size (0.0..1.0) * @param lineWidth - Line width * @param lineColor - Line color * @param bulletScale - Bullet size respective of text * @param bulletColor - Bullet color * @param backgroundColor - Background color of label */ constructor(textColor?: number[], textScale?: number, textAlignment?: LabelTextAlignment, lineWidth?: number, lineColor?: number[], lineTerminator?: LabelLineTerminator, bulletScale?: number, bulletColor?: number[], backgroundColor?: number[]); } /** * Label class * @ignore */ declare class NVLabel3D { text: string; style: NVLabel3DStyle; points?: number[] | number[][]; anchor: LabelAnchorPoint; onClick?: (label: NVLabel3D, e?: MouseEvent) => void; /** * @param text - The text of the label * @param style - The style of the label * @param points - An array of points label for label lines */ constructor(text: string, style: NVLabel3DStyle, points?: number[] | number[][], anchor?: LabelAnchorPoint, onClick?: (label: NVLabel3D, e?: MouseEvent) => void); } type NiftiHeader = { littleEndian: boolean; dim_info: number; dims: number[]; pixDims: number[]; intent_p1: number; intent_p2: number; intent_p3: number; intent_code: number; datatypeCode: number; numBitsPerVoxel: number; slice_start: number; vox_offset: number; scl_slope: number; scl_inter: number; slice_end: number; slice_code: number; xyzt_units: number; cal_max: number; cal_min: number; slice_duration: number; toffset: number; description: string; aux_file: string; qform_code: number; sform_code: number; quatern_b: number; quatern_c: number; quatern_d: number; qoffset_x: number; qoffset_y: number; qoffset_z: number; affine: number[][]; intent_name: string; magic: string; }; type Volume = Record<string, any>; type Point = { comments: Array<{ text: string; prefilled?: string; }>; coordinates: { x: number; y: number; z: number; }; }; /** * Represents the vertices of a connectome * @ignore */ type NVConnectomeNode = { name: string; x: number; y: number; z: number; colorValue: number; sizeValue: number; label?: NVLabel3D; }; /** * Represents edges between connectome nodes * @ignore */ type NVConnectomeEdge = { first: number; second: number; colorValue: number; }; type ConnectomeOptions = { name: string; nodeColormap: string; nodeColormapNegative: string; nodeMinColor: number; nodeMaxColor: number; nodeScale: number; edgeColormap: string; edgeColormapNegative: string; edgeMin: number; edgeMax: number; edgeScale: number; legendLineThickness?: number; showLegend?: boolean; }; type Connectome = ConnectomeOptions & { nodes: NVConnectomeNode[]; edges: NVConnectomeEdge[]; }; type LegacyNodes = { names: string[]; prefilled: unknown[]; X: number[]; Y: number[]; Z: number[]; Color: number[]; Size: number[]; }; type LegacyConnectome = Partial<ConnectomeOptions> & { nodes: LegacyNodes; edges: number[]; }; type DragReleaseParams = { fracStart: vec3; fracEnd: vec3; voxStart: vec3; voxEnd: vec3; mmStart: vec4; mmEnd: vec4; mmLength: number; tileIdx: number; axCorSag: SLICE_TYPE; }; type NiiVueLocationValue = { id: string; mm: vec4; name: string; value: number; vox: vec3; }; type NiiVueLocation = { axCorSag: number; frac: vec3; mm: vec4; string: string; values: NiiVueLocationValue[]; vox: vec3; xy: [number, number]; }; type SyncOpts = { '3d'?: boolean; '2d'?: boolean; zoomPan?: boolean; cal_min?: boolean; cal_max?: boolean; gamma?: boolean; useSliceOffset?: boolean; sliceType?: boolean; crosshair?: boolean; clipPlane?: boolean; }; type UIData = { mousedown: boolean; touchdown: boolean; mouseButtonLeftDown: boolean; mouseButtonCenterDown: boolean; mouseButtonRightDown: boolean; mouseDepthPicker: boolean; clickedTile: number; pan2DxyzmmAtMouseDown: vec4; prevX: number; prevY: number; currX: number; currY: number; currentTouchTime: number; lastTouchTime: number; touchTimer: NodeJS.Timeout | null; doubleTouch: boolean; isDragging: boolean; dragStart: number[]; dragEnd: number[]; dragClipPlaneStartDepthAziElev: number[]; lastTwoTouchDistance: number; multiTouchGesture: boolean; dpr?: number; max2D?: number; max3D?: number; windowX: number; windowY: number; activeDragMode: DRAG_MODE | null; activeDragButton: number | null; angleFirstLine: number[]; angleState: 'none' | 'drawing_first_line' | 'drawing_second_line' | 'complete'; activeClipPlaneIndex: number; }; type FontMetrics = { distanceRange: number; size: number; mets: Record<number, { xadv: number; uv_lbwh: number[]; lbwh: number[]; }>; }; type ColormapListEntry = { name: string; min: number; max: number; isColorbarFromZero: boolean; negative: boolean; visible: boolean; invert: boolean; }; type Graph = { LTWH: number[]; plotLTWH?: number[]; opacity: number; vols: number[]; autoSizeMultiplanar: boolean; normalizeValues: boolean; isRangeCalMinMax: boolean; backColor?: number[]; lineColor?: number[]; textColor?: number[]; lineThickness?: number; gridLineThickness?: number; lineAlpha?: number; lines?: number[][]; selectedColumn?: number; lineRGB?: number[][]; }; type Descriptive = { mean: number; stdev: number; nvox: number; volumeMM3: number; volumeML: number; min: number; max: number; meanNot0: number; stdevNot0: number; nvoxNot0: number; minNot0: number; maxNot0: number; cal_min: number; cal_max: number; robust_min: number; robust_max: number; area: number | null; }; type SliceScale = { volScale: number[]; vox: number[]; longestAxis: number; dimsMM: vec3; }; type MvpMatrix2D = { modelViewProjectionMatrix: mat4; modelMatrix: mat4; normalMatrix: mat4; leftTopMM: number[]; fovMM: number[]; }; type MM = { mnMM: vec3; mxMM: vec3; rotation: mat4; fovMM: vec3; }; type SaveImageOptions = { filename: string; isSaveDrawing: boolean; volumeByIndex: number; }; /** * Enum for NIfTI datatype codes * // https://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h */ declare enum NiiDataType { DT_NONE = 0, DT_BINARY = 1, DT_UINT8 = 2, DT_INT16 = 4, DT_INT32 = 8, DT_FLOAT32 = 16, DT_COMPLEX64 = 32, DT_FLOAT64 = 64, DT_RGB24 = 128, DT_INT8 = 256, DT_UINT16 = 512, DT_UINT32 = 768, DT_INT64 = 1024, DT_UINT64 = 1280, DT_FLOAT128 = 1536, DT_COMPLEX128 = 1792, DT_COMPLEX256 = 2048, DT_RGBA32 = 2304 } /** * Enum for supported image types (e.g. NII, NRRD, DICOM) */ declare enum ImageType { UNKNOWN = 0, NII = 1, DCM = 2, DCM_MANIFEST = 3, MIH = 4, MIF = 5, NHDR = 6, NRRD = 7, MHD = 8, MHA = 9, MGH = 10, MGZ = 11, V = 12, V16 = 13, VMR = 14, HEAD = 15, DCM_FOLDER = 16, SRC = 17, FIB = 18, BMP = 19, ZARR = 20, NPY = 21, NPZ = 22, HDR = 23 } type ImageFromUrlOptions = { url: string; urlImageData?: string; headers?: Record<string, string>; name?: string; colorMap?: string; colormap?: string; opacity?: number; cal_min?: number; cal_max?: number; trustCalMinMax?: boolean; percentileFrac?: number; useQFormNotSForm?: boolean; alphaThreshold?: boolean; colormapNegative?: string; colorMapNegative?: string; cal_minNeg?: number; cal_maxNeg?: number; colorbarVisible?: boolean; ignoreZeroVoxels?: boolean; imageType?: ImageType; frame4D?: number; colormapLabel?: LUT | null; pairedImgData?: null; limitFrames4D?: number; isManifest?: boolean; urlImgData?: string; buffer?: ArrayBuffer; zarrLevel?: number; zarrMaxVolumeSize?: number; zarrChannel?: number; /** Convert OME spatial units to millimeters for NIfTI compatibility (default: true) */ zarrConvertUnits?: boolean; /** World-space center [x, y, z] in mm where the zarr volume center should be positioned */ zarrCenterMM?: [number, number, number]; }; type ImageFromFileOptions = { file: File | File[]; name?: string; colormap?: string; opacity?: number; urlImgData?: File | null | FileSystemEntry; cal_min?: number; cal_max?: number; trustCalMinMax?: boolean; percentileFrac?: number; ignoreZeroVoxels?: boolean; useQFormNotSForm?: boolean; colormapNegative?: string; imageType?: ImageType; frame4D?: number; limitFrames4D?: number; }; type ImageFromBase64 = { base64: string; name?: string; colormap?: string; opacity?: number; cal_min?: number; cal_max?: number; trustCalMinMax?: boolean; percentileFrac?: number; ignoreZeroVoxels?: boolean; useQFormNotSForm?: boolean; colormapNegative?: string; frame4D?: number; imageType?: ImageType; cal_minNeg?: number; cal_maxNeg?: number; colorbarVisible?: boolean; colormapLabel?: LUT | null; }; type ImageMetadata = { id: string; datatypeCode: number; nx: number; ny: number; nz: number; nt: number; dx: number; dy: number; dz: number; dt: number; bpv: number; }; declare const NVImageFromUrlOptions: (url: string, urlImageData?: string, name?: string, colormap?: string, opacity?: number, cal_min?: number, cal_max?: number, trustCalMinMax?: boolean, percentileFrac?: number, ignoreZeroVoxels?: boolean, useQFormNotSForm?: boolean, colormapNegative?: string, frame4D?: number, imageType?: ImageType, cal_minNeg?: number, cal_maxNeg?: number, colorbarVisible?: boolean, alphaThreshold?: boolean, colormapLabel?: any) => ImageFromUrlOptions; /** * ZarrChunkCache - LRU cache for zarr chunks (TypedArrays). * * LRU cache that stores TypedArrays. * TypedArrays are garbage collected automatically, so no explicit cleanup needed. */ type TypedArray = Uint8Array | Uint16Array | Int16Array | Int32Array | Uint32Array | Float32Array | Float64Array; declare class ZarrChunkCache { private cache; private loadingSet; private maxChunks; constructor(maxChunks?: number); /** * Generate a unique key for a chunk. * Format: "name:level/x/y" for 2D or "name:level/x/y/z" for 3D */ static getKey(name: string, level: number, x: number, y: number, z?: number): string; /** * Check if a chunk is in the cache */ has(key: string): boolean; /** * Get a chunk from the cache. * Also moves the entry to the end (most recently used). */ get(key: string): TypedArray | undefined; /** * Store a chunk in the cache. * Evicts oldest entries if capacity is exceeded. */ set(key: string, chunk: TypedArray): void; /** * Check if a chunk is currently being loaded */ isLoading(key: string): boolean; /** * Mark a chunk as loading (to prevent duplicate requests) */ startLoading(key: string): void; /** * Mark a chunk as done loading */ doneLoading(key: string): void; /** * Get the number of cached chunks */ get size(): number; /** * Get the number of chunks currently loading */ get loadingCount(): number; /** * Clear the entire cache */ clear(): void; /** * Delete a specific chunk from cache */ delete(key: string): boolean; /** * Get all cached keys */ keys(): IterableIterator<string>; } /** * ZarrChunkClient - HTTP client for fetching zarr array data using zarrita.js. * * Handles pyramid discovery and chunk fetching for OME-ZARR and regular zarr stores. */ interface ZarrChunkClientConfig { /** Base URL for zarr store (e.g., "http://localhost:8090/lightsheet.zarr") */ baseUrl: string; } interface ZarrPyramidLevel { /** Level index (0 = highest resolution) */ index: number; /** Path to this level in the zarr hierarchy (e.g., "/0", "/1") */ path: string; /** Spatial-only shape in OME metadata order (non-spatial dims stripped) */ shape: number[]; /** Spatial-only chunk dimensions matching shape order */ chunks: number[]; /** Data type (e.g., "uint8", "uint16", "float32") */ dtype: string; /** Physical scale factors per spatial axis in OME metadata order from coordinateTransformations */ scales?: number[]; /** Physical translation offsets per spatial axis in OME metadata order from coordinateTransformations */ translations?: number[]; } /** * Mapping from spatial chunk coordinates to full zarr array chunk coordinates. * Handles non-spatial dimensions like channel (c) and time (t). */ interface AxisMapping { /** Total number of dimensions in the original zarr array */ originalNdim: number; /** Indices of spatial axes in the original array, in OME metadata order */ spatialIndices: number[]; /** Names of spatial axes in OME metadata order (e.g., ['x', 'y', 'z'] or ['z', 'y', 'x']) */ spatialAxisNames: string[]; /** Non-spatial axes: their index in the original array, chunk size, and default chunk coord */ nonSpatialAxes: Array<{ index: number; name: string; chunkSize: number; defaultChunkCoord: number; }>; } interface ZarrPyramidInfo { /** Name/URL of the zarr store */ name: string; /** Pyramid levels (index 0 = highest resolution) */ levels: ZarrPyramidLevel[]; /** Whether this is a 3D dataset (based on spatial dimensions) */ is3D: boolean; /** Number of spatial dimensions (2 or 3) */ ndim: number; /** Mapping from spatial to full array coordinates */ axisMapping: AxisMapping; /** Units for spatial axes in OME metadata order (e.g., "micrometer", "millimeter") */ spatialUnits?: string[]; } interface ChunkCoord { /** Pyramid level */ level: number; /** Chunk X index */ x: number; /** Chunk Y index */ y: number; /** Chunk Z index (for 3D) */ z?: number; } declare class ZarrChunkClient { private store; private baseUrl; private arrays; /** Maps level index to actual path in the zarr store */ private levelPaths; /** Axis mapping for coordinate translation */ private axisMapping; constructor(config: ZarrChunkClientConfig); /** * Discover pyramid structure by reading OME-ZARR multiscales metadata, * or falling back to probing for arrays at /0, /1, /2, etc. */ fetchInfo(): Promise<ZarrPyramidInfo>; /** * Build axis mapping from OME axes metadata or infer from array dimensions. * Identifies spatial (x, y, z) vs non-spatial (c, t) dimensions and returns * indices for extracting spatial-only shape/chunks. * Spatial indices are kept in the original OME metadata order (NOT reordered). */ private buildAxisMapping; /** * Open a zarr array at a specific pyramid level. * Uses cached arrays when available. */ private openLevel; /** * Fetch a single chunk by spatial coordinates. * Uses the axis mapping to build full chunk coordinates including non-spatial dims. * Returns the spatial-only decoded TypedArray data. * * @param level - Pyramid level * @param x - Spatial X chunk index * @param y - Spatial Y chunk index * @param z - Spatial Z chunk index (for 3D) * @param nonSpatialCoords - Optional overrides for non-spatial dimensions (e.g., channel index) */ fetchChunk(level: number, x: number, y: number, z?: number, nonSpatialCoords?: Record<string, number>, signal?: AbortSignal): Promise<TypedArray | null>; /** * Fetch multiple chunks in parallel. * Returns a Map from chunk key to TypedArray. */ fetchChunks(name: string, level: number, coords: ChunkCoord[]): Promise<Map<string, TypedArray>>; /** * Fetch a rectangular region using zarr.get with slices. * Useful for fetching exact viewport regions rather than whole chunks. * Uses axis mapping to handle non-spatial dimensions. */ fetchRegion(level: number, region: { xStart: number; xEnd: number; yStart: number; yEnd: number; zStart?: number; zEnd?: number; }): Promise<{ data: TypedArray; shape: number[]; } | null>; /** * Get the zarr store URL */ getUrl(): string; /** * Clear cached array references */ clearArrayCache(): void; } /** * NVZarrHelper - Simplified zarr chunk management for NVImage. * * Attaches to a host NVImage and manages chunked loading of OME-Zarr data. * No zoom, no prefetching - just pan and level switching. * All coordinates are in current-level pixel space. * * Spatial dimensions are kept in OME metadata order throughout. * The mapping to NIfTI layout is: * - OME dim[0] (slowest in C-order) → NIfTI dim 3 (depth, slowest in Fortran-order) * - OME dim[1] → NIfTI dim 2 (height) * - OME dim[2] (fastest in C-order) → NIfTI dim 1 (width, fastest in Fortran-order) * This means chunk data can be copied directly without stride remapping. * The affine matrix maps NIfTI (i, j, k) indices to physical (x, y, z) space * using the OME axis names. */ interface NVZarrHelperOptions { url: string; level: number; maxVolumeSize?: number; maxTextureSize?: number; channel?: number; cacheSize?: number; /** Convert OME spatial units to millimeters for NIfTI compatibility (default: true) */ convertUnitsToMm?: boolean; } declare class NVZarrHelper { private hostImage; private chunkClient; private chunkCache; private pyramidInfo; private datatypeCode; private pyramidLevel; /** Level dimensions in OME metadata order: depth=dim[0], height=dim[1], width=dim[2] */ private levelDims; private volumeDims; private chunkSize; /** Voxel scales in OME metadata order: depth=dim[0], height=dim[1], width=dim[2] */ private voxelScales; /** Voxel translations in OME metadata order */ private voxelTranslations; private hasTranslations; private convertUnitsToMm; private worldOffsetMM; private centerX; private centerY; private centerZ; private channel; private nonSpatialCoords; private isUpdating; private needsUpdate; private currentAbortController; private runningMin; private runningMax; private calibrationDone; private updateDebounceTimer; private readonly UPDATE_DEBOUNCE_MS; private pendingChunkCount; private lastRenderedChunkCount; centerAtDragStart: { x: number; y: number; z: number; } | null; onChunksUpdated?: () => void; onAllChunksLoaded?: () => void; private constructor(); static create(hostImage: NVImage, url: string, options: NVZarrHelperOptions): Promise<NVZarrHelper>; loadInitialChunks(): Promise<void>; private updateLevelInfo; private configureHostImage; /** Get unit-converted voxel scales */ private getConvertedScales; /** Get unit-converted voxel translations */ private getConvertedTranslations; /** * Build the NIfTI affine from OME axis names, scales, and translations. * * NIfTI dimensions map to OME spatial dimensions as: * i (dim 1, width) = OME spatial[-1] (last, fastest in C-order) * j (dim 2, height) = OME spatial[-2] * k (dim 3, depth) = OME spatial[-3] (first, slowest in C-order) * * The affine maps (i, j, k) → physical (x, y, z): * physical_axis = scale * nifti_dim + translation * where nifti_dim is the column index (0=i, 1=j, 2=k) and * physical_axis row is determined by the OME axis name. */ private updateAffine; beginDrag(): void; endDrag(): void; panBy(dx: number, dy: number, dz?: number): Promise<void>; panTo(newCenterX: number, newCenterY: number, newCenterZ?: number): Promise<void>; setPyramidLevel(level: number): Promise<void>; getViewportState(): { centerX: number; centerY: number; centerZ: number; level: number; }; getPyramidInfo(): ZarrPyramidInfo; getPyramidLevel(): number; getLevelDims(): { width: number; height: number; depth: number; }; getVolumeDims(): { width: number; height: number; depth: number; }; getWorldOffset(): [number, number, number]; /** * Set the world-space offset so the full level's center maps to targetMM in world space. * Computes the native physical center of the zarr level, then sets worldOffsetMM * so that center aligns with targetMM. Also centers the viewport on the level center. */ setWorldCenter(targetMM: [number, number, number]): void; /** * Convert physical (mm) coordinates back to real zarr level pixel coordinates. * Inverts the affine: levelPixel = (mm - OME_translation) / scale */ mmToLevelCoords(mmX: number, mmY: number, mmZ: number): { width: number; height: number; depth: number; level: number; levelDims: { width: number; height: number; depth: number; }; }; private clampCenter; private getVisibleChunks; private updateVolume; private clearVolumeData; private assembleVisibleChunks; private assembleChunkIntoVolume; private updateCalibration; /** * Schedule a debounced chunks update callback. * Batches multiple chunk arrivals within UPDATE_DEBOUNCE_MS into a single GPU update. */ private scheduleChunksUpdated; clearCache(): void; refresh(): Promise<void>; } /** * Represents an affine transformation in decomposed form. */ interface AffineTransform { translation: [number, number, number]; rotation: [number, number, number]; scale: [number, number, number]; } /** * Identity transform with no translation, rotation, or scale change. */ declare const identityTransform: AffineTransform; /** * Convert degrees to radians. */ declare function degToRad(degrees: number): number; /** * Create a rotation matrix from Euler angles (XYZ order). * Angles are in degrees. */ declare function eulerToRotationMatrix(rx: number, ry: number, rz: number): mat4; /** * Create a 4x4 transformation matrix from decomposed transform components. * Order: Scale -> Rotate -> Translate */ declare function createTransformMatrix(transform: AffineTransform): mat4; /** * Convert a 2D array (row-major, as used by NIfTI) to gl-matrix mat4 (column-major). */ declare function arrayToMat4(arr: number[][]): mat4; /** * Convert gl-matrix mat4 (column-major) to 2D array (row-major, as used by NIfTI). */ declare function mat4ToArray(m: mat4): number[][]; /** * Multiply a transformation matrix by an affine matrix (as 2D array). * Returns the result as a 2D array. * * The transform is applied to the left: result = transform * original * This means the transform happens in world coordinate space. */ declare function multiplyAffine(original: number[][], transform: mat4): number[][]; /** * Deep copy a 2D affine matrix array. */ declare function copyAffine(affine: number[][]): number[][]; /** * Check if two transforms are approximately equal. */ declare function transformsEqual(a: AffineTransform, b: AffineTransform, epsilon?: number): boolean; type TypedVoxelArray = Float32Array | Uint8Array | Int16Array | Float64Array | Uint16Array | Int32Array | Uint32Array; /** * a NVImage encapsulates some image data and provides methods to query and operate on images */ declare class NVImage { name: string; id: string; url?: string; headers?: Record<string, string>; _colormap: string; _opacity: number; percentileFrac: number; ignoreZeroVoxels: boolean; trustCalMinMax: boolean; colormapNegative: string; colormapLabel: LUT | null; colormapInvert?: boolean; nFrame4D?: number; frame4D: number; nTotalFrame4D?: number; cal_minNeg: number; cal_maxNeg: number; colorbarVisible: boolean; modulationImage: number | null; modulateAlpha: number; series: any; nVox3D?: number; oblique_angle?: number; maxShearDeg?: number; useQFormNotSForm: boolean; colormapType?: number; pixDims?: number[]; matRAS?: mat4; pixDimsRAS?: number[]; obliqueRAS?: mat4; dimsRAS?: number[]; permRAS?: number[]; img2RASstep?: number[]; img2RASstart?: number[]; toRAS?: mat4; toRASvox?: mat4; frac2mm?: mat4; frac2mmOrtho?: mat4; extentsMinOrtho?: number[]; extentsMaxOrtho?: number[]; mm2ortho?: mat4; hdr: NIFTI1 | NIFTI2 | null; extensions?: NIFTIEXTENSION[]; imageType?: ImageType; img?: TypedVoxelArray; imaginary?: Float32Array; v1?: Float32Array; fileObject?: File | File[]; dims?: number[]; onColormapChange: (img: NVImage) => void; onOpacityChange: (img: NVImage) => void; zarrHelper: NVZarrHelper | null; _hasExplicitZarrCenter: boolean; mm000?: vec3; mm100?: vec3; mm010?: vec3; mm001?: vec3; cal_min?: number; cal_max?: number; robust_min?: number; robust_max?: number; global_min?: number; global_max?: number; urlImgData?: string; isManifest?: boolean; limitFrames4D?: number; originalAffine?: number[][]; constructor(dataBuffer?: ArrayBuffer | ArrayBuffer[] | ArrayBufferLike | null, name?: string, colormap?: string, opacity?: number, pairedImgData?: ArrayBuffer | null, cal_min?: number, cal_max?: number, trustCalMinMax?: boolean, percentileFrac?: number, ignoreZeroVoxels?: boolean, useQFormNotSForm?: boolean, colormapNegative?: string, frame4D?: number, imageType?: ImageType, cal_minNeg?: number, cal_maxNeg?: number, colorbarVisible?: boolean, colormapLabel?: LUT | null, colormapType?: number); init(dataBuffer?: ArrayBuffer | ArrayBuffer[] | ArrayBufferLike | null, name?: string, colormap?: string, opacity?: number, _pairedImgData?: ArrayBuffer | null, cal_min?: number, cal_max?: number, trustCalMinMax?: boolean, percentileFrac?: number, ignoreZeroVoxels?: boolean, useQFormNotSForm?: boolean, colormapNegative?: string, frame4D?: number, imageType?: ImageType, cal_minNeg?: number, cal_maxNeg?: number, colorbarVisible?: boolean, colormapLabel?: LUT | null, colormapType?: number, imgRaw?: ArrayBuffer | ArrayBufferLike | null): void; static new(dataBuffer: ArrayBuffer | ArrayBuffer[] | ArrayBufferLike | null, name: string, colormap: string, opacity: number, pairedImgData: ArrayBuffer | null, cal_min: number, cal_max: number, trustCalMinMax: boolean, percentileFrac: number, ignoreZeroVoxels: boolean, useQFormNotSForm: boolean, colormapNegative: string, frame4D: number, imageType: ImageType, cal_minNeg: number, cal_maxNeg: number, colorbarVisible: boolean, colormapLabel: LUT | null, colormapType: number, zarrData: null | unknown): Promise<NVImage>; computeObliqueAngle(mtx44: mat4): number; /** * Convert vector field from Float32 to RGBA representation. * Note: We use RGBA rather than RGB and use least significant bits to store vector polarity. * This allows a single bitmap to store BOTH (unsigned) color magnitude and signed vector direction. * * @param nvImage - The NVImage instance * @param inImg - Input Float32Array containing vector field data * @returns Uint8Array with RGBA encoded vector data */ float32V1asRGBA(inImg: Float32Array): Uint8Array; /** * Load and process diffusion tensor vector (V1) data with optional flips. * The vectors must be of unit length. * Modifies the nvImage.img property with the processed RGBA data. * * @param nvImage - The NVImage instance * @param isFlipX - Flip X component (default: false) * @param isFlipY - Flip Y component (default: false) * @param isFlipZ - Flip Z component (default: false) * @example nv1.loadVolumes(volumeList); nv1.volumes[1].loadImgV1(); * @returns true if successful, false if V1 data is not available * @see {@link https://niivue.com/demos/features/modulate.html | live demo usage} */ loadImgV1(isFlipX?: boolean, isFlipY?: boolean, isFlipZ?: boolean): boolean; calculateOblique(): void; readECAT(buffer: ArrayBuffer): ArrayBuffer; readV16(buffer: ArrayBuffer): ArrayBuffer; readNPY(buffer: ArrayBuffer): Promise<ArrayBuffer>; readNPZ(buffer: ArrayBuffer): Promise<ArrayBuffer | undefined>; imageDataFromArrayBuffer(buffer: ArrayBuffer): Promise<ImageData>; readBMP(buffer: ArrayBuffer): Promise<ArrayBuffer>; readZARR(buffer: ArrayBuffer, zarrData: unknown): Promise<ArrayBufferLike>; readVMR(buffer: ArrayBuffer): ArrayBuffer; readFIB(buffer: ArrayBuffer): Promise<[ArrayBuffer, Float32Array]>; readSRC(buffer: ArrayBuffer): Promise<ArrayBuffer>; readHEAD(dataBuffer: ArrayBuffer, pairedImgData: ArrayBuffer | null): Promise<ArrayBuffer>; readMHA(buffer: ArrayBuffer, pairedImgData: ArrayBuffer | null): Promise<ArrayBuffer>; readMIF(buffer: ArrayBuffer, pairedImgData: ArrayBuffer | null): Promise<ArrayBuffer>; calculateRAS(): void; /** * Get a deep copy of the current affine matrix. * @returns A 4x4 affine matrix as a 2D array (row-major) */ getAffine(): number[][]; /** * Set a new affine matrix and recalculate all derived RAS matrices. * Call updateGLVolume() on the Niivue instance after this to update rendering. * @param affine - A 4x4 affine matrix as a 2D array (row-major) */ setAffine(affine: number[][]): void; /** * Apply a transform (translation, rotation, scale) to the current affine matrix. * The transform is applied in world coordinate space: newAffine = transform * currentAffine * Call updateGLVolume() on the Niivue instance after this to update rendering. * @param transform - Transform to apply with translation (mm), rotation (degrees), and scale */ applyTransform(transform: AffineTransform): void; /** * Reset the affine matrix to its original state when the image was first loaded. * Call updateGLVolume() on the Niivue instance after this to update rendering. */ resetAffine(): void; hdr2RAS(nVolumes?: number): Promise<NIFTI1 | NIFTI2>; img2RAS(nVolume?: number): TypedVoxelArray; vox2mm(XYZ: number[], mtx: mat4): vec3; mm2vox(mm: number[], frac?: boolean): Float32Array | vec3; arrayEquals(a: unknown[], b: unknown[]): boolean; setColormap(cm: string): void; setColormapLabel(cm: ColorMap): void; setColormapLabelFromUrl(url: string): Promise<void>; get colormap(): string; get colorMap(): string; set colormap(cm: string); set colorMap(cm: string); get opacity(): number; set opacity(opacity: number); /** * set contrast/brightness to robust range (2%..98%) * @param vol - volume for estimate (use -1 to use estimate on all loaded volumes; use INFINITY for current volume) * @param isBorder - if true (default) only center of volume used for estimate * @returns volume brightness and returns array [pct2, pct98, mnScale, mxScale] * @see {@link https://niivue.com/demos/features/timeseries2.html | live demo usage} */ calMinMax(vol?: number, isBorder?: boolean): number[]; intensityRaw2Scaled(raw: number): number; intensityScaled2Raw(scaled: number): number; /** * Converts NVImage to NIfTI compliant byte array, potentially compressed. * Delegates to ImageWriter.saveToUint8Array. */ saveToUint8Array(fnm: string, drawing8?: Uint8Array | null): Promise<Uint8Array>; /** * save image as NIfTI volume and trigger download. * Delegates to ImageWriter.saveToDisk. */ saveToDisk(fnm?: string, drawing8?: Uint8Array | null): Promise<Uint8Array>; static fetchDicomData(url: string, headers?: Record<string, string>): Promise<Array<{ name: string; data: ArrayBuffer; }>>; static readFirstDecompressedBytes(stream: ReadableStream<Uint8Array>, minBytes: number): Promise<Uint8Array>; static extractFilenameFromUrl(url: string): string | null; static loadInitialVolumesGz(url?: string, headers?: {}, limitFrames4D?: number): Promise<ArrayBuffer | null>; static loadInitialVolumes(url?: string, headers?: {}, limitFrames4D?: number): Promise<ArrayBuffer | null>; /** * factory function to load and return a new NVImage instance from a given URL */ static loadFromUrl({ url, urlImgData, headers, name, colormap, opacity, cal_min, cal_max, trustCalMinMax, percentileFrac, ignoreZeroVoxels, useQFormNotSForm, colormapNegative, frame4D, isManifest, limitFrames4D, imageType, colorbarVisible, buffer, zarrLevel, zarrMaxVolumeSize, zarrChannel, zarrConvertUnits, zarrCenterMM }?: Partial<Omit<ImageFromUrlOptions, 'url'>> & { url?: string | Uint8Array | ArrayBuffer; }): Promise<NVImage>; /** * Factory method: create a chunked zarr NVImage with an attached NVZarrHelper. */ static createChunkedZarr(url: string, options: { level: number; maxVolumeSize?: number; maxTextureSize?: number; channel?: number; cacheSize?: number; convertUnitsToMm?: boolean; colormap?: string; opacity?: number; zarrCenterMM?: [number, number, number]; }): Promise<NVImage>; static readFileAsync(file: File, bytesToLoad?: number): Promise<ArrayBuffer>; /** * factory function to load and return a new NVImage instance from a file in the browser */ static loadFromFile({ file, // file can be an array of file objects or a single file object name, colormap, opacity, urlImgData, cal_min, cal_max, trustCalMinMax, percentileFrac, ignoreZeroVoxels, useQFormNotSForm, colormapNegative, frame4D, limitFrames4D, imageType }: ImageFromFileOptions): Promise<NVImage>; /** * Creates a Uint8Array representing a NIFTI file (header + optional image data). * Delegates to ImageWriter.createNiftiArray. */ static createNiftiArray(dims?: number[], pixDims?: number[], affine?: number[], datatypeCode?: NiiDataType, img?: TypedVoxelArray | Uint8Array): Uint8Array; /** * Creates a NIFTI1 header object with basic properties. * Delegates to ImageWriter.createNiftiHeader. */ static createNiftiHeader(dims?: number[], pixDims?: number[], affine?: number[], datatypeCode?: NiiDataType): NIFTI1; /** * read a 3D slab of voxels from a volume * @see {@link https://niivue.com/demos/features/slab_selection.html | live demo usage} */ /** * read a 3D slab of voxels from a volume, specified in RAS coordinates. * Delegates to VolumeUtils.getVolumeData. */ getVolumeData(voxStart?: number[], voxEnd?: number[], dataType?: string): [TypedVoxelArray, number[]]; /** * write a 3D slab of voxels from a volume * @see {@link https://niivue.com/demos/features/slab_selection.html | live demo usage} */ /** * write a 3D slab of voxels from a volume, specified in RAS coordinates. * Delegates to VolumeUtils.setVolumeData. * Input slabData is assumed to be in the correct raw data type for the target image. */ setVolumeData(voxStart?: number[], voxEnd?: number[], img?: TypedVoxelArray): void; /** * factory function to load and return a new NVImage instance from a base64 encoded string * @example * myImage = NVImage.loadFromBase64('SomeBase64String') */ static loadFromBase64({ base64, name, colormap, opacity, cal_min, cal_max, trustCalMinMax, percentileFrac, ignoreZeroVoxels, useQFormNotSForm, colormapNegative, frame4D, imageType, cal_minNeg, cal_maxNeg, colorbarVisible, colormapLabel }: ImageFromBase64): Promise<NVImage>; /** * make a clone of a NVImage instance and return a new NVImage * @example * myImage = NVImage.loadFromFile(SomeFileObject) // files can be from dialogs or drag and drop * clonedImage = myImage.clone() */ clone(): NVImage; /** * fill a NVImage instance with zeros for the image data * @example * myImage = NVImage.loadFromFile(SomeFileObject) // files can be from dialogs or drag and drop * clonedImageWithZeros = myImage.clone().zeroImage() */ zeroImage(): void; /** * get nifti specific metadata about the image */ getImageMetadata(): ImageMetadata; /** * a factory function to make a zero filled image given a NVImage as a reference * @example * myImage = NVImage.loadFromFile(SomeFileObject) // files can be from dialogs or drag and drop * newZeroImage = NVImage.zerosLike(myImage) */ static zerosLike(nvImage: NVImage, dataType?: string): NVImage; /** * Returns voxel intensity at specific native coordinates. * Delegates to VolumeUtils.getValue. */ getValue(x: number, y: number, z: number, frame4D?: number, isReadImaginary?: boolean): number; /** * Returns voxel intensities at specific native coordinates. * Delegates to VolumeUtils.getValue. */ getValues(x: number, y: number, z: number, frame4D?: number, isReadImaginary?: boolean): number[]; /** * Update options for image */ applyOptionsUpdate(options: ImageFromUrlOptions): void; getImageOptions(): ImageFromUrlOptions; /** * Converts NVImage to NIfTI compliant byte array. * Handles potential re-orientation of drawing data. * Delegates to ImageWriter.toUint8Array. */ toUint8Array(drawingBytes?: Uint8Array | null): Uint8Array; convertVox2Frac(vox: vec3): vec3; convertFrac2Vox(frac: vec3): vec3; convertFrac2MM(frac: vec3, isForceSliceMM?: boolean): vec4; convertMM2Frac(mm: vec3 | vec4, isForceSliceMM?: boolean): vec3; } type ValuesArray = Array<{ id: string; vals: Float32Array; global_min?: number; global_max?: number; cal_min?: number; cal_max?: number; }>; type AnyNumberArray = number[] | Float64Array | Float32Array | Uint32Array | Uint16Array | Uint8Array | Int32Array | Int16Array | Int8Array; type DefaultMeshType = { positions: Float32Array; indices: Uint32Array; colors?: Float32Array; }; type TRACT = { pts: Float32Array; offsetPt0: Uint32Array; dps: ValuesArray; }; type TT = { pts: Float32Array; offsetPt0: Uint32Array; }; type TRX = { pts: Float32Array; offsetPt0: Uint32Array; dpg: ValuesArray; dps: ValuesArray; dpv: ValuesArray; groups: ValuesArray; header: unknown; }; type TRK = { pts: Float32Array; offsetPt0: Uint32Array; dps: ValuesArray; dpv: ValuesArray; }; type TCK = { pts: Float32Array; offsetPt0: Uint32Array; }; type VTK = DefaultMeshType | { pts: Float32Array; offsetPt0: Uint32Array; }; type ANNOT = Uint32Array | { scalars: Float32Array; colormapLabel: LUT; }; type MZ3 = { positions: Float32Array | null; indices: Uint32Array | null; scalars: Float32Array; colors: Float32Array | null; } | { scalars: Float32Array; colormapLabel: LUT; } | { scalars: Float32Array; }; type GII = { scalars: Float32Array; positions?: Float32Array; indices?: Uint32Array; colormapLabel?: LUT; anatomicalStructurePrimary: string; }; type MGH = AnyNumberArray | { scalars: AnyNumberArray; colormapLabel: LUT; }; type X3D = { positions: Float32Array; indices: Uint32Array; rgba255: Uint8Array; }; /** Enum for text alignment */ declare enum MeshType { MESH = "mesh", CONNECTOME = "connectome", FIBER = "fiber" } type NVMeshLayer = { name?: string; key?: string; url?: string; headers?: Record<string, string>; opacity: number; colormap: string; colormapNegative?: string; colormapInvert?: boolean; colormapLabel?: ColorMap | LUT; useNegativeCmap?: boolean; global_min?: number; global_max?: number; cal_min: number; cal_max: number; cal_minNeg: number; cal_maxNeg: number; isAdditiveBlend?: boolean; frame4D: number; nFrame4D: number; values: AnyNumberArray; outlineBorder?: number; isTransparentBelowCalMin?: boolean; colormapType?: number; base64?: string; colorbarVisible?: boolean; showLegend?: boolean; labels?: NVLabel3D[]; atlasValues?: AnyNumberArray; }; declare const NVMeshLayerDefaults: { colormap: string; opacity: number; nFrame4D: number; frame4D: number; outlineBorder: number; cal_min: number; cal_max: number; cal_minNeg: number; cal_maxNeg: number; colormapType: COLORMAP_TYPE; values: number[]; useNegativeCmap: boolean; showLegend: boolean; }; declare class NVMeshFromUrlOptions { url: string; gl: WebGL2RenderingContext | null; name: string; opacity: number; rgba255: Uint8Array; visible: boolean; layers: NVMeshLayer[]; colorbarVisible: boolean; meshShaderIndex: number; constructor(url?: string, gl?: any, name?: string, opacity?: number, rgba255?: Uint8Array, visible?: boolean, layers?: any[], colorbarVisible?: boolean, meshShaderIndex?: number); } /** * Parameters for loading a base mesh or volume. */ type BaseLoadParams = { /** WebGL rendering context. */ gl: WebGL2RenderingContext; /** Name for this image. Default is an empty string. */ name: string; /** Opacity for this image. Default is 1. */ opacity: number; /** Base color of the mesh in RGBA [0-255]. Default is white. */ rgba255: number[] | Uint8Array; /** Whether this image is visible. */ visible: boolean; /** Layers of the mesh to load. */ layers: NVMeshLayer[]; /** Shader index for mesh rendering. Default is 0 (Phong). */ meshShaderIndex: number; }; type LoadFromUrlParams = Partial<BaseLoadParams> & { url: string; headers?: Record<string, string>; buffer?: ArrayBuffer; }; type LoadFromFileParams = BaseLoadParams & { file: Blob; }; type LoadFromBase64Params = BaseLoadParams & { base64: string; }; /** * a NVMesh encapsulates some mesh data and provides methods to query and operate on meshes */ declare class NVMesh { id: string; name: string; anatomicalStructurePrimary: string; colorbarVisible: boolean; furthestVertexFromOrigin: number; extentsMin: number | number[]; extentsMax: number | number[]; opacity: number; visible: boolean; meshShaderIndex: number; offsetPt0: Uint32Array | null; colormapInvert: boolean; fiberGroupColormap: ColorMap | null; indexBuffer: WebGLBuffer; vertexBuffer: WebGLBuffer; vao: WebGLVertexArrayObject; vaoFiber: WebGLVertexArrayObject; pts: Float32Array; tris?: Uint32Array; layers: NVMeshLayer[]; type: MeshType; data_type?: string; rgba255: Uint8Array; fiberLength?: number; fiberLengths?: Uint32Array; fiberDensity?: Float32Array; fiberDither: number; fiberColor: string; fiberDecimationStride: number; fiberSides: number; fiberRadius: number; fiberOcclusion: number; f32PerVertex: number; dpsThreshold: number; fiberMask?: unknown[]; colormap?: ColorMap | LegacyConnectome | string | null; dpg?: ValuesArray | null; dps?: ValuesArray | null; dpv?: ValuesArray | null; groups?: ValuesArray | null; hasConnectome: boolean; connectome?: LegacyConnectome | string; indexCount?: number; vertexCount: number; nodeScale: number; edgeScale: number; legendLineThickness: number; showLegend: boolean; nodeColormap: string; edgeColormap: string; nodeColormapNegative?: string; edgeColormapNegative?: string; nodeMinColor?: number; nodeMaxColor?: number; edgeMin?: number; edgeMax?: number; nodes?: LegacyNodes | NVConnectomeNode[]; edges?: number[] | NVConnectomeEdge[]; points?: Point[]; /** * @param pts - a 3xN array of vertex positions (X,Y,Z coordinates). * @param tris - a 3xN array of triangle indices (I,J,K; indexed from zero). Each triangle generated from three vertices. * @param name - a name for this image. Default is an empty string * @param rgba255 - the base color of the mesh. RGBA values from 0 to 255. Default is white * @param opacity - the opacity for this mesh. default is 1 * @param visible - whether or not this image is to be visible * @param gl - WebGL rendering context * @param connectome - specify connectome edges and nodes. Default is null (not a connectome). * @param dpg - Data per group for tractography, see TRK format. Default is null (not tractograpgy) * @param dps - Data per streamline for tractography, see TRK format. Default is null (not tractograpgy) * @param dpv - Data per vertex for tractography, see TRK format. Default is null (not tractograpgy) * @param groups - Groups for tractography, see TRK format. Default is null (not tractograpgy) * @param colorbarVisible - does this mesh display a colorbar * @param anatomicalStructurePrimary - region for mesh. Default is an empty string */ constructor(pts: Float32Array, tris: Uint32Array, name: string, rgba255: Uint8Array, opacity: number, visible: boolean, gl: WebGL2RenderingContext, connectome?: LegacyConnectome | string | null, dpg?: ValuesArray | null, dps?: ValuesArray | null, dpv?: ValuesArray | null, groups?: ValuesArray | null, colorbarVisible?: boolean, anatomicalStructurePrimary?: string); initValuesArray(va: ValuesArray): ValuesArray; linesToCylinders(gl: WebGL2RenderingContext, posClrF32: Float32Array, indices: number[]): void; createFiberDensityMap(): void; updateFibers(gl: WebGL2RenderingContext): void; indexNearestXYZmm(Xmm: number, Ymm: number, Zmm: number): number[]; unloadMesh(gl: WebGL2RenderingContext): void; scalars2RGBA(rgba: Uint8ClampedArray, layer: NVMeshLayer, scalars: AnyNumberArray, isNegativeCmap?: boolean): Uint8ClampedArray; blendColormap(u8: Uint8Array, additiveRGBA: Uint8Array, layer: NVMeshLayer, mn: number, mx: number, lut: Uint8ClampedArray, invert?: boolean): void; updateMesh(gl: WebGL2RenderingContext): void; reverseFaces(gl: WebGL2RenderingContext): void; hierarchicalOrder(): number; decimateFaces(n: number, ntarget: number): void; decimateHierarchicalMesh(gl: WebGL2RenderingContext, order?: number): boolean; setLayerProperty(id: number, key: keyof NVMeshLayer, val: number | string | boolean, gl: WebGL2RenderingContext): Promise<void>; setProperty(key: keyof this, val: number | string | boolean | Uint8Array | number[] | ColorMap | LegacyConnectome | Float32Array, gl: WebGL2RenderingContext): void; generatePosNormClr(pts: Float32Array, tris: Uint32Array, rgba255: Uint8Array): Float32Array; static readMesh(buffer: ArrayBuffer, name: string, gl: WebGL2RenderingContext, opacity?: number, rgba255?: Uint8Array, visible?: boolean): Promise<NVMesh>; static loadLayer(layer: NVMeshLayer, nvmesh: NVMesh): Promise<void>;