You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WebGL 2中显示16位DICOM图像并完成8位转换

解答你的WebGL2处理16位DICOM图像问题

嘿,我来帮你搞定这两个WebGL2处理16位DICOM图像的问题!先逐个拆解:

问题1:如何在WebGL2中直接显示16位.dcm图像

首先要明确:浏览器原生不认识DICOM格式,没法像加载PNG那样直接用loadImage加载.dcm文件。你需要先解析DICOM提取原始像素数据,再用WebGL2的16位纹理支持来显示。具体步骤如下:

  • 第一步:解析DICOM文件
    用专门的DICOM解析库(比如dicom-parser)读取.dcm文件,提取出16位的像素数组(通常是Uint16Array),同时从DICOM元数据里获取图像的宽高。

  • 第二步:创建WebGL2 16位纹理
    WebGL2支持R16UI(无符号16位整数)格式的纹理,刚好匹配DICOM的16位灰度数据,直接把像素数组上传到纹理即可。

  • 第三步:修改着色器适配16位纹理
    片段着色器需要用usampler2D来采样无符号整数纹理,然后把16位值归一化到0-1的范围转换成灰度颜色。

对应代码修改

首先替换加载逻辑为解析DICOM:

// 替换原代码中的loadImage部分
fetch('Anonymized20190509.dcm')
  .then(response => response.arrayBuffer())
  .then(arrayBuffer => {
    // 解析DICOM数据
    const dataSet = dicomParser.parseDicom(arrayBuffer);
    // 提取16位像素数据
    const pixelData = dataSet.byteArray.slice(
      dataSet.elements.x7fe00010.dataOffset,
      dataSet.elements.x7fe00010.dataOffset + dataSet.elements.x7fe00010.length
    );
    const uint16Array = new Uint16Array(pixelData);
    // 从DICOM元数据获取图像尺寸
    const width = dataSet.elements.x00280010.value[0];
    const height = dataSet.elements.x00280011.value[0];

    // 创建16位纹理
    const texture = gl.createTexture();
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
    // 上传16位灰度数据,使用R16UI格式
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.R16UI, width, height, 0, gl.RED_INTEGER, gl.UNSIGNED_SHORT, uint16Array);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    // 后续渲染逻辑和原代码一致...
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    const matrix = new Float32Array([
      0.5, 0.0, 0.0, 0.0,
      0.0, 0.5, 0.0, 0.0,
      0.0, 0.0, 0.5, 0.0,
      0.0, 0.0, 0.0, 1.0
    ]);
    gl.useProgram(program);
    gl.uniformMatrix4fv(mvpLocation, false, matrix);
    gl.uniform1i(diffuseLocation, 0);
    gl.bindVertexArray(vertexArray);
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    // 清理资源
    gl.deleteBuffer(vertexPosBuffer);
    gl.deleteBuffer(vertexTexBuffer);
    gl.deleteTexture(texture);
    gl.deleteProgram(program);
    gl.deleteVertexArray(vertexArray);
  })
  .catch(err => console.error('DICOM加载/解析失败:', err));

然后修改片段着色器(fs):

#version 300 es
precision highp float;
in vec2 v_texcoord;
uniform usampler2D diffuse; // 用usampler2D采样无符号整数纹理
out vec4 o_fragColor;

void main() {
  uint pixelValue = texture(diffuse, v_texcoord).r;
  // 将16位值归一化到0-1的浮点数,转换为灰度
  float gray = float(pixelValue) / 65535.0;
  o_fragColor = vec4(gray, gray, gray, 1.0);
}

顶点着色器可以保持你原来的逻辑不变。


问题2:将16位DICOM转换为8位格式适配现有代码

如果你想继续用原来处理8位PNG的代码,核心是把16位像素值归一化映射到0-255的8位范围。通常可以用像素数据的最小/最大值来拉伸,或者用DICOM的窗宽窗位(更专业的医学图像显示方式)。

具体实现步骤

  1. 解析DICOM得到16位像素数组Uint16Array。
  2. 计算像素的最小值minVal和最大值maxVal(如果用窗宽窗位,就计算windowCenter ± windowWidth/2作为有效范围)。
  3. 遍历每个16位像素,将其映射到0-255:8BitVal = Math.round((pixelVal - minVal) / (maxVal - minVal) * 255)。
  4. 把8位灰度数据转换成RGBA格式(因为原代码用gl.RGBA纹理),包装成ImageData传给WebGL。

对应代码修改

替换原代码中的loadImage部分为以下逻辑:

// 替换原loadImage部分
fetch('Anonymized20190509.dcm')
  .then(response => response.arrayBuffer())
  .then(arrayBuffer => {
    // 解析DICOM
    const dataSet = dicomParser.parseDicom(arrayBuffer);
    const pixelData = dataSet.byteArray.slice(
      dataSet.elements.x7fe00010.dataOffset,
      dataSet.elements.x7fe00010.dataOffset + dataSet.elements.x7fe00010.length
    );
    const uint16Array = new Uint16Array(pixelData);
    const width = dataSet.elements.x00280010.value[0];
    const height = dataSet.elements.x00280011.value[0];

    // 步骤1:计算像素的最小最大值
    let minVal = Infinity, maxVal = -Infinity;
    for (const val of uint16Array) {
      if (val < minVal) minVal = val;
      if (val > maxVal) maxVal = val;
    }

    // 步骤2:将16位数据转换为8位
    const uint8Array = new Uint8Array(uint16Array.length);
    for (let i = 0; i < uint16Array.length; i++) {
      // 处理maxVal等于minVal的边界情况,避免除以0
      const normalized = maxVal !== minVal ? (uint16Array[i] - minVal) / (maxVal - minVal) : 0;
      uint8Array[i] = Math.round(normalized * 255);
    }

    // 步骤3:转换为RGBA格式的ImageData(适配原代码的纹理格式)
    const imageData = new ImageData(width, height);
    for (let i = 0; i < uint8Array.length; i++) {
      imageData.data[i * 4] = uint8Array[i];     // R通道
      imageData.data[i * 4 + 1] = uint8Array[i]; // G通道
      imageData.data[i * 4 + 2] = uint8Array[i]; // B通道
      imageData.data[i * 4 + 3] = 255;           // Alpha通道(不透明)
    }

    // 步骤4:初始化纹理(和原代码几乎一致,只是用ImageData代替Image)
    const texture = gl.createTexture();
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageData);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    // 后续渲染逻辑和原代码完全一致...
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    const matrix = new Float32Array([
      0.5, 0.0, 0.0, 0.0,
      0.0, 0.5, 0.0, 0.0,
      0.0, 0.0, 0.5, 0.0,
      0.0, 0.0, 0.0, 1.0
    ]);
    gl.useProgram(program);
    gl.uniformMatrix4fv(mvpLocation, false, matrix);
    gl.uniform1i(diffuseLocation, 0);
    gl.bindVertexArray(vertexArray);
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    // 清理资源
    gl.deleteBuffer(vertexPosBuffer);
    gl.deleteBuffer(vertexTexBuffer);
    gl.deleteTexture(texture);
    gl.deleteProgram(program);
    gl.deleteVertexArray(vertexArray);
  })
  .catch(err => console.error('DICOM加载/解析失败:', err));

注意事项

  • 你需要引入DICOM解析库(比如dicom-parser),可以下载它的本地文件到项目中,再通过<script>标签引入。
  • 如果是医学图像,更推荐用窗宽窗位来映射,而不是简单的最小最大值,这样能更准确地显示病灶信息。比如从DICOM元数据中读取x00281050(窗位)和x00281051(窗宽),然后计算有效范围:lower = windowCenter - windowWidth/2,upper = windowCenter + windowWidth/2,再把超出范围的像素 clamp 到0或255。

内容的提问来源于stack exchange,提问作者user11231694

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:08:01