Сопоставление пикселей для визуализации DICOM Monochrome2

Попытка отобразить dicom монохромный2 на холсте 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. правильное отображение, dicom отображается в IrfanView


person Lydon Ch    schedule 19.07.2020    source источник


Ответы (2)


Здесь есть две проблемы: ваши монохромные данные имеют более высокое разрешение (например, диапазон значений), чем могут отображаться в 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, которая здесь также не рассматривается
  • код вылетел из моей головы, поэтому в нем могут быть ошибки
person MrBean Bremen    schedule 19.07.2020

Выяснилось, что необходимо сделать 4 основные вещи (прочитав исходный код fo-dicom, чтобы выяснить это)

  1. Подготовьте LUT Monochrome2

    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. Интерпретировать пиксельные данные как беззнаковые короткие

     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 )
    
    }
    
person Lydon Ch    schedule 02.08.2020