1

试图将 dicom mono22 渲染到 HTML5 画布上

  1. 从灰度到画布 rgb 的正确像素映射是什么?

    • 当前使用不正确的映射

         const ctx = canvas.getContext( '2d' )
         const imageData = ctx.createImageData( 512, 512 )
         const pixelData = getPixelData( dataSet )
      
         let rgbaIdx = 0
         let rgbIdx = 0
         let pixelCount = 512 * 512
         for ( let idx = 0; idx < pixelCount; idx++ ) {
             imageData.data[ rgbaIdx ] = pixelData[ rgbIdx ]
             imageData.data[ rgbaIdx + 1 ] = pixelData[ rgbIdx + 1 ]
             imageData.data[ rgbaIdx + 2 ] = 0
             imageData.data[ rgbaIdx + 3 ] = 255
             rgbaIdx += 4
             rgbIdx += 2
         }
         ctx.putImageData( imageData, 0, 0 )        
      
  2. 通读开源库,不太清楚如何渲染,能否请您建议清楚地介绍如何渲染?

不正确的映射

图 1. 不正确的映射

正确的单色2

图 2. 正确映射,IrfanView 中显示的 dicom

4

2 回答 2

3

这里有两个问题:您的单色数据具有比 RGB 显示的更高的分辨率(例如值范围),因此您不能直接将像素数据映射到 RGB 数据。
值范围取决于Bits Stored标签 - 对于 12 的典型值,数据范围将是 4096。最简单的实现可能只是缩小数字,在本例中为 16。

您的代码的第二个问题:要表示 RGB 中的单色值,您必须添加 3 个具有相同值的颜色分量:

let rgbaIdx = 0
let rgbIdx = 0
let pixelCount = 512 * 512
let scaleFactor = 16 // has to be calculated in real code
for ( let idx = 0; idx < pixelCount; idx++ ) {
    # assume Little Endian
    let pixelValue = pixelData[ rgbIdx ] + pixelData[ rgbIdx + 1 ] * 256
    let displayValue = Math.round(pixelValue / scaleFactor)
    imageData.data[ rgbaIdx ] = displayValue
    imageData.data[ rgbaIdx + 1 ] = displayValue
    imageData.data[ rgbaIdx + 2 ] = displayValue
    imageData.data[ rgbaIdx + 3 ] = 255
    rgbaIdx += 4
    rgbIdx += 2
}

为了获得更好的表示,您必须考虑 VOI LUT 而不仅仅是缩小比例。如果您定义了Window Center/Window Width标签,您可以计算最小值和最大值并从该范围获取比例因子:

let minValue = windowCenter - windowWidth / 2
let maxValue = windowCenter + windowWidth / 2
let scaleFactor = (maxValue - minValue) / 256
...
   let pixelValue = pixelData[ rgbIdx ] + pixelData[ rgbIdx + 1 ] * 256
   let displayValue = max((pixelValue - minValue) / scaleFactor), 255)
   ...

编辑:正如@WilfRosenbaum 所观察到的:如果您没有VOI LUT(如WindowCenter 和WindowWidth 的空值所建议的那样),您最好自己计算一个。为此,您必须计算像素数据的最小值/最大值:

let minValue = 1 >> 16
let maxValue = 0
for ( let idx = 0; idx < pixelCount; idx++ ) {
    let pixelValue = pixelData[ rgbIdx ] + pixelData[ rgbIdx + 1 ] * 256
    minValue = min(minValue, pixelValue)
    maxValue = max(maxValue, pixelValue)
}
let scaleFactor = (maxValue - minValue) / 256

然后使用与 VOI LUT 相同的代码。

几点注意事项:

  • 如果您有模态 LUT,则必须在 VOI LUT 之前应用它;CT图像通常有一个(RescaleSlope/RescaleIntercept),虽然这个只有一个身份LUT,所以你可以忽略它
  • 您可以有多个WindowCenter/WindowWindow值对,或者可以有一个 VOI LUT 序列,这里也不考虑
  • 代码不在我的脑海里,所以它可能有错误
于 2020-07-19T09:36:32.793 回答
1

原来需要做 4 件主要事情(通读 fo-dicom 源代码找出这些事情)

  1. 准备 Monochrome2 LUT

    export const LutMonochrome2 = () => {
    
        let lut = []
        for ( let idx = 0, byt = 255; idx < 256; idx++, byt-- ) {
            // r, g, b, a
            lut.push( [byt, byt, byt, 0xff] )
        }
        return lut
    }
    
  2. 将像素数据解释为 unsigned short

     export const bytesToShortSigned = (bytes) => {
     let byteA = bytes[ 1 ]
     let byteB = bytes[ 0 ]
     let pixelVal
    
     const sign = byteA & (1 << 7);
     pixelVal = (((byteA & 0xFF) << 8) | (byteB & 0xFF));
     if (sign) {
         pixelVal = 0xFFFF0000 | pixelVal;  // fill in most significant bits with 1's
     }
     return pixelVal
    

    }

  3. 获取最小和最大像素值,然后计算 WindowWidth 以最终将每个像素映射到 Monochrome2 颜色图

    export const getMinMax = ( pixelData ) => {
    
        let pixelCount = pixelData.length
        let min = 0, max = 0
    
        for ( let idx = 0; idx < pixelCount; idx += 2 ) {
            let pixelVal = bytesToShortSigned( [
                pixelData[idx],
                pixelData[idx+1]
            ]  )
    
            if (pixelVal < min)
                min = pixelVal
    
            if (pixelVal > max)
                max = pixelVal
        }
        return { min, max }
    }
    
  4. 最后画

    export const draw = ( { dataSet, canvas } ) => {
    
     const monochrome2 = LutMonochrome2()
    
     const ctx = canvas.getContext( '2d' )
     const imageData = ctx.createImageData( 512, 512 )
     const pixelData = getPixelData( dataSet )
     let pixelCount = pixelData.length
    
     let { min: minPixel, max: maxPixel } = getMinMax( pixelData )
    
     let windowWidth = Math.abs( maxPixel - minPixel );
     let windowCenter = ( maxPixel + minPixel ) / 2.0;
    
     console.debug( `minPixel: ${minPixel} , maxPixel: ${maxPixel}` )
    
     let rgbaIdx = 0
     for ( let idx = 0; idx < pixelCount; idx += 2 ) {
         let pixelVal = bytesToShortSigned( [
             pixelData[idx],
             pixelData[idx+1]
         ]  )
    
    
         let binIdx = Math.floor( (pixelVal - minPixel) / windowWidth * 256 );
    
         let displayVal = monochrome2[ binIdx ]
         if ( displayVal == null )
             displayVal = [ 0, 0, 0, 255]
    
         imageData.data[ rgbaIdx ] = displayVal[0]
         imageData.data[ rgbaIdx + 1 ] = displayVal[1]
         imageData.data[ rgbaIdx + 2 ] = displayVal[2]
         imageData.data[ rgbaIdx + 3 ] = displayVal[3]
         rgbaIdx += 4
     }
     ctx.putImageData( imageData, 0, 0 )
    
    }
    
于 2020-08-02T05:24:19.003 回答