import { getColorByChannelIndex } from "./constants/colors" ; function clamp ( val , cmin , cmax ) { return Math . min ( Math . max ( cmin , val ) , cmax ) ; } function controlPointToRGBA ( controlPoint ) { return [ controlPoint . color [ 0 ] , controlPoint . color [ 1 ] , controlPoint . color [ 2 ] , Math . floor ( controlPoint . opacity * 255 ) ] ; } function lerp ( xmin , xmax , a ) { return a * ( xmax - xmin ) + xmin ; } const LUT_ENTRIES = 256 ; const LUT_ARRAY_LENGTH = LUT_ENTRIES * 4 ; typedef ControlPoint property { number } x property { number } opacity property { Array . < number > } color typedef Lut property { Array . < number > } lut property { Array . < ControlPoint > } controlPoints export type ControlPoint = { x : number ; opacity : number ; color : [ number , number , number ] ; } ; export type Lut = { lut : Uint8Array ; controlPoints : ControlPoint [ ] ; } ; class param { Array . < number > } data export default class Histogram { private bins : Uint32Array ; private dataMin : number ; private dataMax : number ; private maxBin : number ; private nonzeroPixelCount : number ; constructor ( data ) { this . bins = new Uint32Array ( 256 ) ; this . bins . fill ( 0 ) ; this . dataMin = 255 ; this . dataMax = 0 ; this . maxBin = 0 ; for ( let i = 0 ; i < data . length ; ++ i ) { this . bins [ data [ i ] ] ++ ; } for ( let i = 1 ; i < this . bins . length ; i ++ ) { if ( this . bins [ i ] > 0 ) { this . dataMin = i ; break ; } } for ( let i = this . bins . length - 1 ; i >= 1 ; i -- ) { if ( this . bins [ i ] > 0 ) { this . dataMax = i ; break ; } } this . nonzeroPixelCount = data . length - this . bins [ 0 ] ; this . maxBin = 1 ; let max = this . bins [ 1 ] ; for ( let i = 1 ; i < this . bins . length ; i ++ ) { if ( this . bins [ i ] > max ) { this . maxBin = i ; max = this . bins [ i ] ; } } } return { number } getMin ( ) { return this . dataMin ; } return { number } getMax ( ) { return this . dataMax ; } return { Lut } param { number } wnd param { number } lvl lutGenerator_windowLevel ( wnd , lvl ) { const b = lvl - wnd * 0.5 ; const e = lvl + wnd * 0.5 ; return this . lutGenerator_minMax ( b * 255 , e * 255 ) ; } return { Lut } param { number } b param { number } e lutGenerator_minMax ( b , e ) { if ( e < b ) { const tmp = e ; e = b ; b = tmp ; } const lut = new Uint8Array ( LUT_ARRAY_LENGTH ) ; for ( let x = 0 ; x < lut . length / 4 ; ++ x ) { lut [ x * 4 + 0 ] = 255 ; lut [ x * 4 + 1 ] = 255 ; lut [ x * 4 + 2 ] = 255 ; if ( x > e ) { lut [ x * 4 + 3 ] = 255 ; } else if ( x <= b ) { lut [ x * 4 + 3 ] = 0 ; } else { if ( e === b ) { lut [ x * 4 + 3 ] = 255 ; } else { const a = ( x - b ) / ( e - b ) ; lut [ x * 4 + 3 ] = lerp ( 0 , 255 , a ) ; } } } if ( b < 0 && e < 0 ) { return { lut : lut , controlPoints : [ { x : 0 , opacity : 1 , color : [ 255 , 255 , 255 ] } , { x : 255 , opacity : 1 , color : [ 255 , 255 , 255 ] } , ] , } ; } if ( b >= 255 && e >= 255 ) { return { lut : lut , controlPoints : [ { x : 0 , opacity : 0 , color : [ 255 , 255 , 255 ] } , { x : 255 , opacity : 0 , color : [ 255 , 255 , 255 ] } , ] , } ; } const controlPoints = [ ] ; let startVal = 0 ; if ( b < 0 ) { startVal = - b / ( e - b ) ; } controlPoints . push ( { x : 0 , opacity : startVal , color : [ 255 , 255 , 255 ] } ) ; if ( b > 0 ) { controlPoints . push ( { x : b , opacity : 0 , color : [ 255 , 255 , 255 ] } ) ; } if ( e < 255 ) { if ( e === b ) { controlPoints . push ( { x : b + 0.5 , opacity : 1 , color : [ 255 , 255 , 255 ] } ) ; } else { controlPoints . push ( { x : e , opacity : 1 , color : [ 255 , 255 , 255 ] } ) ; } } let endVal = 1 ; if ( e > 255 ) { endVal = ( 255 - b ) / ( e - b ) ; } controlPoints . push ( { x : 255 , opacity : endVal , color : [ 255 , 255 , 255 ] } ) ; return { lut : lut , controlPoints : controlPoints , } ; } return { Lut } lutGenerator_fullRange ( ) { const lut = new Uint8Array ( LUT_ARRAY_LENGTH ) ; for ( let x = 0 ; x < lut . length / 4 ; ++ x ) { lut [ x * 4 + 0 ] = 255 ; lut [ x * 4 + 1 ] = 255 ; lut [ x * 4 + 2 ] = 255 ; lut [ x * 4 + 3 ] = x ; } return { lut : lut , controlPoints : [ { x : 0 , opacity : 0 , color : [ 255 , 255 , 255 ] } , { x : 255 , opacity : 1 , color : [ 255 , 255 , 255 ] } , ] , } ; } return { Lut } lutGenerator_dataRange ( ) { const b = this . dataMin ; const e = this . dataMax ; return this . lutGenerator_minMax ( b , e ) ; } return { Lut } lutGenerator_labelColors ( ) { const lut = new Uint8Array ( LUT_ARRAY_LENGTH ) . fill ( 0 ) ; const controlPoints = [ ] ; controlPoints . push ( { x : 0 , opacity : 0 , color : [ 0 , 0 , 0 ] } ) ; let lastr = 0 ; let lastg = 0 ; let lastb = 0 ; let lasta = 0 ; let r = 0 ; let g = 0 ; let b = 0 ; let a = 0 ; for ( let i = 1 ; i < this . bins . length ; ++ i ) { if ( this . bins [ i ] > 0 ) { const rgb = getColorByChannelIndex ( i ) ; lut [ i * 4 + 0 ] = rgb [ 0 ] ; lut [ i * 4 + 1 ] = rgb [ 1 ] ; lut [ i * 4 + 2 ] = rgb [ 2 ] ; lut [ i * 4 + 3 ] = 255 ; r = rgb [ 0 ] ; g = rgb [ 1 ] ; b = rgb [ 2 ] ; a = 1 ; } else { r = 0 ; g = 0 ; b = 0 ; a = 0 ; } if ( r !== lastr || g !== lastg || b !== lastb || a !== lasta ) { if ( lasta === 0 ) { controlPoints . push ( { x : i - 0.5 , opacity : lasta , color : [ lastr , lastg , lastb ] } ) ; } controlPoints . push ( { x : i , opacity : a , color : [ r , g , b ] } ) ; lastr = r ; lastg = g ; lastb = b ; lasta = a ; } } return { lut : lut , controlPoints : controlPoints , } ; } return { number } param { number } pct findBinOfPercentile ( pct ) { const pixcount = this . nonzeroPixelCount + this . bins [ 0 ] ; const limit = pixcount * pct ; let i = 0 ; let count = 0 ; for ( i = 0 ; i < this . bins . length ; ++ i ) { count += this . bins [ i ] ; if ( count > limit ) { break ; } } return i ; } return { Lut } param { number } pmin param { number } pmax lutGenerator_percentiles ( pmin , pmax ) { const hmin = this . findBinOfPercentile ( pmin ) ; const hmax = this . findBinOfPercentile ( pmax ) ; return this . lutGenerator_minMax ( hmin , hmax ) ; } return { Lut } lutGenerator_bestFit ( ) { const pixcount = this . nonzeroPixelCount ; const limit = pixcount / 10 ; let i = 0 ; let count = 0 ; for ( i = 1 ; i < this . bins . length ; ++ i ) { count += this . bins [ i ] ; if ( count > limit ) { break ; } } const hmin = i ; count = 0 ; for ( i = this . bins . length - 1 ; i >= 1 ; -- i ) { count += this . bins [ i ] ; if ( count > limit ) { break ; } } const hmax = i ; return this . lutGenerator_minMax ( hmin , hmax ) ; } return { Lut } lutGenerator_auto2 ( ) { const AUTO_THRESHOLD = 5000 ; const pixcount = this . nonzeroPixelCount ; const limit = pixcount / 10 ; const threshold = pixcount / AUTO_THRESHOLD ; let hmin = this . bins . length - 1 ; let hmax = 1 ; for ( let i = 1 ; i < this . bins . length ; ++ i ) { if ( this . bins [ i ] > threshold && this . bins [ i ] <= limit ) { hmin = i ; break ; } } for ( let i = this . bins . length - 1 ; i >= 1 ; -- i ) { if ( this . bins [ i ] > threshold && this . bins [ i ] <= limit ) { hmax = i ; break ; } } if ( hmax < hmin ) { return this . lutGenerator_fullRange ( ) ; } else { return this . lutGenerator_minMax ( hmin , hmax ) ; } } return { Lut } lutGenerator_auto ( ) { const PERCENTAGE = 0.1 ; const th = Math . floor ( this . bins [ this . maxBin ] * PERCENTAGE ) ; let b = 0 ; let e = this . bins . length - 1 ; for ( let x = 1 ; x < this . bins . length ; ++ x ) { if ( this . bins [ x ] > th ) { b = x ; break ; } } for ( let x = this . bins . length - 1 ; x >= 1 ; -- x ) { if ( this . bins [ x ] > th ) { e = x ; break ; } } return this . lutGenerator_minMax ( b , e ) ; } return { Lut } lutGenerator_equalize ( ) { const map = [ ] ; for ( let i = 0 ; i < this . bins . length ; ++ i ) { map [ i ] = 0 ; } map [ 0 ] = this . bins [ 0 ] ; for ( let i = 1 ; i < this . bins . length ; ++ i ) { map [ i ] = map [ i - 1 ] + this . bins [ i ] ; } const div = map [ map . length - 1 ] - map [ 0 ] ; if ( div > 0 ) { const lut = new Uint8Array ( LUT_ARRAY_LENGTH ) ; const lutControlPoints = [ { x : 0 , opacity : 0 , color : [ 255 , 255 , 255 ] } ] ; lut [ 0 ] = 255 ; lut [ 1 ] = 255 ; lut [ 2 ] = 255 ; lut [ 3 ] = 0 ; let slope = 0 ; let lastSlope = 0 ; let opacity = 0 ; let lastOpacity = 0 ; for ( let i = 1 ; i < lut . length / 4 ; ++ i ) { lut [ i * 4 + 0 ] = 255 ; lut [ i * 4 + 1 ] = 255 ; lut [ i * 4 + 2 ] = 255 ; lastOpacity = opacity ; opacity = clamp ( Math . round ( 255 * ( map [ i ] - map [ 0 ] ) ) , 0 , 255 ) ; lut [ i * 4 + 3 ] = opacity ; slope = opacity - lastOpacity ; if ( slope != lastSlope ) { lutControlPoints . push ( { x : i - 1 , opacity : lastOpacity / 255.0 , color : [ 255 , 255 , 255 ] } ) ; lastSlope = slope ; } } lutControlPoints . push ( { x : 255 , opacity : 1 , color : [ 255 , 255 , 255 ] } ) ; return { lut : lut , controlPoints : lutControlPoints , } ; } else { return this . lutGenerator_fullRange ( ) ; } } lutGenerator_fromControlPoints ( controlPoints ) { const lut = new Uint8Array ( LUT_ARRAY_LENGTH ) . fill ( 0 ) ; if ( controlPoints . length === 0 ) { return { lut : lut , controlPoints : controlPoints } ; } controlPoints . sort ( ( a , b ) => a . x - b . x ) ; if ( controlPoints . length === 1 ) { const rgba = controlPointToRGBA ( controlPoints [ 0 ] ) ; for ( let x = controlPoints [ 0 ] . x ; x < 256 ; ++ x ) { lut [ x * 4 + 0 ] = rgba [ 0 ] ; lut [ x * 4 + 1 ] = rgba [ 1 ] ; lut [ x * 4 + 2 ] = rgba [ 2 ] ; lut [ x * 4 + 3 ] = rgba [ 3 ] ; } return { lut : lut , controlPoints : controlPoints } ; } let c0 = controlPoints [ 0 ] ; let c1 = controlPoints [ 1 ] ; let color0 = controlPointToRGBA ( c0 ) ; let color1 = controlPointToRGBA ( c1 ) ; let lastIndex = 1 ; let a = 0 ; for ( let x = c0 . x ; x < 256 ; ++ x ) { while ( x > c1 . x ) { c0 = c1 ; color0 = color1 ; lastIndex ++ ; if ( lastIndex >= controlPoints . length ) { c1 = { x : 255 , color : c1 . color , opacity : c1 . opacity } ; } else { c1 = controlPoints [ lastIndex ] ; } color1 = controlPointToRGBA ( c1 ) ; } if ( c1 . x === c0 . x ) { a = 1.0 ; } else { a = ( x - c0 . x ) / ( c1 . x - c0 . x ) ; } lut [ x * 4 + 0 ] = lerp ( color0 [ 0 ] , color1 [ 0 ] , a ) ; lut [ x * 4 + 1 ] = lerp ( color0 [ 1 ] , color1 [ 1 ] , a ) ; lut [ x * 4 + 2 ] = lerp ( color0 [ 2 ] , color1 [ 2 ] , a ) ; lut [ x * 4 + 3 ] = lerp ( color0 [ 3 ] , color1 [ 3 ] , a ) ; } return { lut : lut , controlPoints : controlPoints } ; } } export { LUT_ARRAY_LENGTH } ;