@niivue/niivue
Version:
minimal webgl2 nifti image viewer
136 lines (114 loc) • 4.63 kB
text/typescript
/**
* Volume modulation functions for handling modulation textures in volume rendering.
*/
import { NVImage, NiiDataType } from '@/nvimage'
import { Shader } from '@/shader'
import { log } from '@/logger'
/**
* Parameters for modulation setup
*/
export interface ModulationParams {
gl: WebGL2RenderingContext
overlayItem: NVImage
hdr: any
volumes: NVImage[]
orientShader: Shader
r8Tex: (texID: WebGLTexture | null, activeID: number, dims: number[], isInit: boolean) => WebGLTexture | null
TEXTURE7: number
}
/**
* Result of modulation setup
*/
export interface ModulationResult {
modulateTexture: WebGLTexture | null
}
/**
* Setup modulation texture for volume rendering
* @param params - Modulation parameters
* @returns Modulation texture
*/
export function setupModulation(params: ModulationParams): ModulationResult {
const { gl, overlayItem, hdr, volumes, orientShader, r8Tex, TEXTURE7 } = params
// Check if modulation is enabled and valid
if (overlayItem.modulationImage === null || overlayItem.modulationImage < 0 || overlayItem.modulationImage >= volumes.length) {
gl.uniform1i(orientShader.uniforms.modulation, 0)
return { modulateTexture: null }
}
const mhdr = volumes[overlayItem.modulationImage].hdr!
// Check dimensions match
if (mhdr.dims[1] !== hdr.dims[1] || mhdr.dims[2] !== hdr.dims[2] || mhdr.dims[3] !== hdr.dims[3]) {
log.debug('Modulation image dimensions do not match target')
gl.uniform1i(orientShader.uniforms.modulation, 0)
return { modulateTexture: null }
}
log.debug('modulating', volumes)
// Set modulation mode
if (overlayItem.modulateAlpha) {
gl.uniform1i(orientShader.uniforms.modulation, 2)
gl.uniform1f(orientShader.uniforms.opacity, 1.0)
} else {
gl.uniform1i(orientShader.uniforms.modulation, 1)
}
// Create modulation texture
const modulateTexture = r8Tex(null, TEXTURE7, hdr.dims, true)
gl.activeTexture(TEXTURE7)
gl.bindTexture(gl.TEXTURE_3D, modulateTexture)
// Prepare modulation volume data
const vx = hdr.dims[1] * hdr.dims[2] * hdr.dims[3]
const modulateVolume = new Uint8Array(vx)
const mn = volumes[overlayItem.modulationImage].cal_min!
const scale = 1.0 / (volumes[overlayItem.modulationImage].cal_max! - mn)
const imgRaw = volumes[overlayItem.modulationImage].img!.buffer
// Get typed array based on datatype
let img: Uint8Array | Int16Array | Float32Array | Float64Array | Uint16Array = new Uint8Array(imgRaw)
switch (mhdr.datatypeCode) {
case NiiDataType.DT_INT16:
img = new Int16Array(imgRaw)
break
case NiiDataType.DT_FLOAT32:
img = new Float32Array(imgRaw)
break
case NiiDataType.DT_FLOAT64:
img = new Float64Array(imgRaw)
break
case NiiDataType.DT_RGB24:
img = new Uint8Array(imgRaw)
break
case NiiDataType.DT_UINT16:
img = new Uint16Array(imgRaw)
break
}
log.debug(volumes[overlayItem.modulationImage])
// Handle negative colormap
const isColormapNegative = volumes[overlayItem.modulationImage].colormapNegative.length > 0
let mnNeg = volumes[overlayItem.modulationImage].cal_min
let mxNeg = volumes[overlayItem.modulationImage].cal_max
if (isFinite(volumes[overlayItem.modulationImage].cal_minNeg) && isFinite(volumes[overlayItem.modulationImage].cal_maxNeg)) {
mnNeg = volumes[overlayItem.modulationImage].cal_minNeg
mxNeg = volumes[overlayItem.modulationImage].cal_maxNeg
}
mnNeg = Math.abs(mnNeg!)
mxNeg = Math.abs(mxNeg!)
if (mnNeg > mxNeg) {
;[mnNeg, mxNeg] = [mxNeg, mnNeg]
}
const scaleNeg = 1.0 / (mxNeg - mnNeg)
let mpow = Math.abs(overlayItem.modulateAlpha)
mpow = Math.max(mpow, 1.0)
// Select the correct frame for 4D volumes
const volOffset = volumes[overlayItem.modulationImage].frame4D * vx
// Fill modulation volume
for (let i = 0; i < vx; i++) {
const vRaw = img[i + volOffset] * mhdr.scl_slope + mhdr.scl_inter
let v = (vRaw - mn) * scale
if (isColormapNegative && vRaw < 0.0) {
v = (Math.abs(vRaw) - mnNeg) * scaleNeg
}
v = Math.min(Math.max(v, 0.0), 1.0)
v = Math.pow(v, mpow) * 255.0
modulateVolume[i] = v
}
// Upload to GPU
gl.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, hdr.dims[1], hdr.dims[2], hdr.dims[3], gl.RED, gl.UNSIGNED_BYTE, modulateVolume)
return { modulateTexture }
}