Search code examples
javascripthtmldicommonochrome

Pixel Mapping for Rendering DICOM Monochrome2


Trying to render dicom monochrome2 onto HTML5 canvas

  1. what is the correct pixel mapping from grayscale to canvas rgb ?

    • Currently using incorrect mapping of

         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. Reading through open source libraries, not very clear how, could you please suggest a clear introduction of how to render?

incorrect mapping

Fig 1. incorrect mapping

correct monochrome2

Fig 2. correct mapping, dicom displayed in IrfanView


Solution

  • Turned out 4 main things needed to be done (reading through fo-dicom source code to find these things out)

    1. Prepare 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. Interpret pixel data as 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. Get Minimum and Maximum Pixel Value and then compute WindowWidth to eventually map each pixel to Monochrome2 color map

      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. Finally draw

      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 )
      
      }