license import * as util from '../util' ; type PadType = 'SAME' | 'VALID' | 'NUMBER' | 'EXPLICIT' ; export type ExplicitPadding = [ [ number , number ] , [ number , number ] , [ number , number ] , [ number , number ] ] ; export type PadInfo = { top : number ; left : number ; right : number ; bottom : number ; type : PadType ; } ; export type PadInfo3D = { top : number ; left : number ; right : number ; bottom : number ; front : number ; back : number ; type : PadType ; } ; /**
 * Information about the forward pass of a convolution/pooling operation.
 * It includes input and output shape, strides, filter size and padding
 * information.
 */ export type Conv2DInfo = { batchSize : number ; inHeight : number ; inWidth : number ; inChannels : number ; outHeight : number ; outWidth : number ; outChannels : number ; dataFormat : 'channelsFirst' | 'channelsLast' ; strideHeight : number ; strideWidth : number ; dilationHeight : number ; dilationWidth : number ; filterHeight : number ; filterWidth : number ; effectiveFilterHeight : number ; effectiveFilterWidth : number ; padInfo : PadInfo ; inShape : [ number , number , number , number ] ; outShape : [ number , number , number , number ] ; filterShape : [ number , number , number , number ] ; } ; param inputShape param filterShape param strides param pad param dataFormat param dilations export function computeDilation2DInfo ( inputShape , filterShape , strides , pad , dataFormat = 'NHWC' , dilations ) { const inputChannels = inputShape [ 3 ] ; const $filterShape = [ ... filterShape , inputChannels ] as [ number , number , number , number ] ; const $dataFormat = convertConv2DDataFormat ( dataFormat ) ; return computeConv2DInfo ( inputShape , $filterShape , strides , dilations , pad , null , null , $dataFormat ) ; } export function computePool2DInfo ( inShape , filterSize , strides , dilations , pad , roundingMode ? , dataFormat = 'channelsLast' ) { const [ filterHeight , filterWidth ] = parseTupleParam ( filterSize ) ; let filterShape ; if ( dataFormat === 'channelsLast' ) { filterShape = [ filterHeight , filterWidth , inShape [ 3 ] , inShape [ 3 ] ] ; } else if ( dataFormat === 'channelsFirst' ) { filterShape = [ filterHeight , filterWidth , inShape [ 1 ] , inShape [ 1 ] ] ; } else { throw new Error ( `Unknown dataFormat ${ dataFormat }` ) ; } return computeConv2DInfo ( inShape , filterShape , strides , dilations , pad , roundingMode , false , dataFormat ) ; } /**
 * Computes the information for a forward pass of a pooling3D operation.
 */ export function computePool3DInfo ( inShape , filterSize , strides , dilations , pad , roundingMode ? , dataFormat = 'NDHWC' ) { const [ filterDepth , filterHeight , filterWidth ] = parse3TupleParam ( filterSize ) ; let filterShape ; let $dataFormat ; if ( dataFormat === 'NDHWC' ) { $dataFormat = 'channelsLast' ; filterShape = [ filterDepth , filterHeight , filterWidth , inShape [ 4 ] , inShape [ 4 ] ] ; } else if ( dataFormat === 'NCDHW' ) { $dataFormat = 'channelsFirst' ; filterShape = [ filterDepth , filterHeight , filterWidth , inShape [ 1 ] , inShape [ 1 ] ] ; } else { throw new Error ( `Unknown dataFormat ${ dataFormat }` ) ; } return computeConv3DInfo ( inShape , filterShape , strides , dilations , pad , false , $dataFormat , roundingMode ) ; } /**
 * Computes the information for a forward pass of a convolution/pooling
 * operation.
 */ export function computeConv2DInfo ( inShape , filterShape , strides , dilations , pad , roundingMode ? , depthwise = false , dataFormat = 'channelsLast' ) { let [ batchSize , inHeight , inWidth , inChannels ] = [ - 1 , - 1 , - 1 , - 1 ] ; if ( dataFormat === 'channelsLast' ) { [ batchSize , inHeight , inWidth , inChannels ] = inShape ; } else if ( dataFormat === 'channelsFirst' ) { [ batchSize , inChannels , inHeight , inWidth ] = inShape ; } else { throw new Error ( `Unknown dataFormat ${ dataFormat }` ) ; } const [ filterHeight , filterWidth , , filterChannels ] = filterShape ; const [ strideHeight , strideWidth ] = parseTupleParam ( strides ) ; const [ dilationHeight , dilationWidth ] = parseTupleParam ( dilations ) ; const effectiveFilterHeight = getEffectiveFilterSize ( filterHeight , dilationHeight ) ; const effectiveFilterWidth = getEffectiveFilterSize ( filterWidth , dilationWidth ) ; const { padInfo , outHeight , outWidth } = getPadAndOutInfo ( pad , inHeight , inWidth , strideHeight , strideWidth , effectiveFilterHeight , effectiveFilterWidth , roundingMode , dataFormat ) ; const outChannels = depthwise ? filterChannels * inChannels : filterChannels ; let outShape ; if ( dataFormat === 'channelsFirst' ) { outShape = [ batchSize , outChannels , outHeight , outWidth ] ; } else if ( dataFormat === 'channelsLast' ) { outShape = [ batchSize , outHeight , outWidth , outChannels ] ; } return { batchSize , dataFormat , inHeight , inWidth , inChannels , outHeight , outWidth , outChannels , padInfo , strideHeight , strideWidth , filterHeight , filterWidth , effectiveFilterHeight , effectiveFilterWidth , dilationHeight , dilationWidth , inShape , outShape , filterShape } ; } /**
 * Information about the forward pass of a 3D convolution/pooling operation.
 * It includes input and output shape, strides, filter size and padding
 * information.
 */ export type Conv3DInfo = { batchSize : number ; inDepth : number ; inHeight : number ; inWidth : number ; inChannels : number ; outDepth : number ; outHeight : number ; outWidth : number ; outChannels : number ; dataFormat : 'channelsFirst' | 'channelsLast' ; strideDepth : number ; strideHeight : number ; strideWidth : number ; dilationDepth : number ; dilationHeight : number ; dilationWidth : number ; filterDepth : number ; filterHeight : number ; filterWidth : number ; effectiveFilterDepth : number ; effectiveFilterHeight : number ; effectiveFilterWidth : number ; padInfo : PadInfo3D ; inShape : [ number , number , number , number , number ] ; outShape : [ number , number , number , number , number ] ; filterShape : [ number , number , number , number , number ] ; } ; /**
 * Computes the information for a forward pass of a 3D convolution/pooling
 * operation.
 */ export function computeConv3DInfo ( inShape , filterShape , strides , dilations , pad , depthwise = false , dataFormat = 'channelsLast' , roundingMode ? ) { let [ batchSize , inDepth , inHeight , inWidth , inChannels ] = [ - 1 , - 1 , - 1 , - 1 , - 1 ] ; if ( dataFormat === 'channelsLast' ) { [ batchSize , inDepth , inHeight , inWidth , inChannels ] = inShape ; } else if ( dataFormat === 'channelsFirst' ) { [ batchSize , inChannels , inDepth , inHeight , inWidth ] = inShape ; } else { throw new Error ( `Unknown dataFormat ${ dataFormat }` ) ; } const [ filterDepth , filterHeight , filterWidth , , filterChannels ] = filterShape ; const [ strideDepth , strideHeight , strideWidth ] = parse3TupleParam ( strides ) ; const [ dilationDepth , dilationHeight , dilationWidth ] = parse3TupleParam ( dilations ) ; const effectiveFilterDepth = getEffectiveFilterSize ( filterDepth , dilationDepth ) ; const effectiveFilterHeight = getEffectiveFilterSize ( filterHeight , dilationHeight ) ; const effectiveFilterWidth = getEffectiveFilterSize ( filterWidth , dilationWidth ) ; const { padInfo , outDepth , outHeight , outWidth } = get3DPadAndOutInfo ( pad , inDepth , inHeight , inWidth , strideDepth , strideHeight , strideWidth , effectiveFilterDepth , effectiveFilterHeight , effectiveFilterWidth , roundingMode ) ; const outChannels = depthwise ? filterChannels * inChannels : filterChannels ; let outShape ; if ( dataFormat === 'channelsFirst' ) { outShape = [ batchSize , outChannels , outDepth , outHeight , outWidth ] ; } else if ( dataFormat === 'channelsLast' ) { outShape = [ batchSize , outDepth , outHeight , outWidth , outChannels ] ; } return { batchSize , dataFormat , inDepth , inHeight , inWidth , inChannels , outDepth , outHeight , outWidth , outChannels , padInfo , strideDepth , strideHeight , strideWidth , filterDepth , filterHeight , filterWidth , effectiveFilterDepth , effectiveFilterHeight , effectiveFilterWidth , dilationDepth , dilationHeight , dilationWidth , inShape , outShape , filterShape } ; } function computeOutputShape2D ( inShape , fieldSize , stride , zeroPad ? , roundingMode ? ) { if ( zeroPad == null ) { zeroPad = computeDefaultPad ( inShape , fieldSize , stride ) ; } const inputRows = inShape [ 0 ] ; const inputCols = inShape [ 1 ] ; const outputRows = round ( ( inputRows - fieldSize + 2 * zeroPad ) / stride + 1 , roundingMode ) ; const outputCols = round ( ( inputCols - fieldSize + 2 * zeroPad ) / stride + 1 , roundingMode ) ; return [ outputRows , outputCols ] ; } function computeOutputShape4D ( inShape , fieldSize , outChannels , stride , zeroPad ? , roundingMode ? ) { if ( zeroPad == null ) { zeroPad = computeDefaultPad ( inShape , fieldSize , stride ) ; } const inputDepth = inShape [ 0 ] ; const inputRows = inShape [ 1 ] ; const inputCols = inShape [ 2 ] ; const outputDepths = round ( ( inputDepth - fieldSize + 2 * zeroPad ) / stride + 1 , roundingMode ) ; const outputRows = round ( ( inputRows - fieldSize + 2 * zeroPad ) / stride + 1 , roundingMode ) ; const outputCols = round ( ( inputCols - fieldSize + 2 * zeroPad ) / stride + 1 , roundingMode ) ; return [ outputDepths , outputRows , outputCols , outChannels ] ; } export function computeDefaultPad ( inputShape , fieldSize , stride , dilation = 1 ) { const effectiveFieldSize = getEffectiveFilterSize ( fieldSize , dilation ) ; return Math . floor ( ( inputShape [ 0 ] * ( stride - 1 ) - stride + effectiveFieldSize ) / 2 ) ; } function parseTupleParam ( param ) { if ( typeof param === 'number' ) { return [ param , param , param ] ; } if ( param . length === 2 ) { return [ param [ 0 ] , param [ 1 ] , 1 ] ; } return param as [ number , number , number ] ; } function parse3TupleParam ( param ) { return typeof param === 'number' ? [ param , param , param ] : param ; } function getEffectiveFilterSize ( filterSize , dilation ) { if ( dilation <= 1 ) { return filterSize ; } return filterSize + ( filterSize - 1 ) * ( dilation - 1 ) ; } function getPadAndOutInfo ( pad , inHeight , inWidth , strideHeight , strideWidth , filterHeight , filterWidth , roundingMode , dataFormat ) { let padInfo ; let outHeight ; let outWidth ; if ( typeof pad === 'number' ) { const padType = ( pad === 0 ) ? 'VALID' : 'NUMBER' ; padInfo = { top : pad , bottom : pad , left : pad , right : pad , type : padType } ; const outShape = computeOutputShape2D ( [ inHeight , inWidth ] , filterHeight , strideHeight , pad , roundingMode ) ; outHeight = outShape [ 0 ] ; outWidth = outShape [ 1 ] ; } else if ( pad === 'same' ) { outHeight = Math . ceil ( inHeight / strideHeight ) ; outWidth = Math . ceil ( inWidth / strideWidth ) ; const padAlongHeight = Math . max ( 0 , ( outHeight - 1 ) * strideHeight + filterHeight - inHeight ) ; const padAlongWidth = Math . max ( 0 , ( outWidth - 1 ) * strideWidth + filterWidth - inWidth ) ; const top = Math . floor ( padAlongHeight / 2 ) ; const bottom = padAlongHeight - top ; const left = Math . floor ( padAlongWidth / 2 ) ; const right = padAlongWidth - left ; padInfo = { top , bottom , left , right , type : 'SAME' } ; } else if ( pad === 'valid' ) { padInfo = { top : 0 , bottom : 0 , left : 0 , right : 0 , type : 'VALID' } ; outHeight = Math . ceil ( ( inHeight - filterHeight + 1 ) / strideHeight ) ; outWidth = Math . ceil ( ( inWidth - filterWidth + 1 ) / strideWidth ) ; } else if ( typeof pad === 'object' ) { const top = dataFormat === 'channelsLast' ? pad [ 1 ] [ 0 ] : pad [ 2 ] [ 0 ] ; const bottom = dataFormat === 'channelsLast' ? pad [ 1 ] [ 1 ] : pad [ 2 ] [ 1 ] ; const left = dataFormat === 'channelsLast' ? pad [ 2 ] [ 0 ] : pad [ 3 ] [ 0 ] ; const right = dataFormat === 'channelsLast' ? pad [ 2 ] [ 1 ] : pad [ 3 ] [ 1 ] ; const padType = ( top === 0 && bottom === 0 && left === 0 && right === 0 ) ? 'VALID' : 'EXPLICIT' ; padInfo = { top , bottom , left , right , type : padType } ; outHeight = round ( ( inHeight - filterHeight + top + bottom ) / strideHeight + 1 , roundingMode ) ; outWidth = round ( ( inWidth - filterWidth + left + right ) / strideWidth + 1 , roundingMode ) ; } else { throw Error ( `Unknown padding parameter: ${ pad }` ) ; } return { padInfo , outHeight , outWidth } ; } function get3DPadAndOutInfo ( pad , inDepth , inHeight , inWidth , strideDepth , strideHeight , strideWidth , filterDepth , filterHeight , filterWidth , roundingMode ? ) { let padInfo ; let outDepth ; let outHeight ; let outWidth ; if ( typeof pad === 'number' ) { const padType = ( pad === 0 ) ? 'VALID' : 'NUMBER' ; padInfo = { top : pad , bottom : pad , left : pad , right : pad , front : pad , back : pad , type : padType } ; const outShape = computeOutputShape4D ( [ inDepth , inHeight , inWidth , 1 ] , filterDepth , 1 , strideDepth , pad , roundingMode ) ; outDepth = outShape [ 0 ] ; outHeight = outShape [ 1 ] ; outWidth = outShape [ 2 ] ; } else if ( pad === 'same' ) { outDepth = Math . ceil ( inDepth / strideDepth ) ; outHeight = Math . ceil ( inHeight / strideHeight ) ; outWidth = Math . ceil ( inWidth / strideWidth ) ; const padAlongDepth = ( outDepth - 1 ) * strideDepth + filterDepth - inDepth ; const padAlongHeight = ( outHeight - 1 ) * strideHeight + filterHeight - inHeight ; const padAlongWidth = ( outWidth - 1 ) * strideWidth + filterWidth - inWidth ; const front = Math . floor ( padAlongDepth / 2 ) ; const back = padAlongDepth - front ; const top = Math . floor ( padAlongHeight / 2 ) ; const bottom = padAlongHeight - top ; const left = Math . floor ( padAlongWidth / 2 ) ; const right = padAlongWidth - left ; padInfo = { top , bottom , left , right , front , back , type : 'SAME' } ; } else if ( pad === 'valid' ) { padInfo = { top : 0 , bottom : 0 , left : 0 , right : 0 , front : 0 , back : 0 , type : 'VALID' } ; outDepth = Math . ceil ( ( inDepth - filterDepth + 1 ) / strideDepth ) ; outHeight = Math . ceil ( ( inHeight - filterHeight + 1 ) / strideHeight ) ; outWidth = Math . ceil ( ( inWidth - filterWidth + 1 ) / strideWidth ) ; } else { throw Error ( `Unknown padding parameter: ${ pad }` ) ; } return { padInfo , outDepth , outHeight , outWidth } ; } param value param roundingMode function round ( value , roundingMode ? ) { if ( ! roundingMode ) { return Math . trunc ( value ) ; } switch ( roundingMode ) { case 'round' : return Math . round ( value ) ; case 'ceil' : return Math . ceil ( value ) ; case 'floor' : return Math . floor ( value ) ; default : throw new Error ( `Unknown roundingMode ${ roundingMode }` ) ; } } export function tupleValuesAreOne ( param ) { const [ dimA , dimB , dimC ] = parseTupleParam ( param ) ; return dimA === 1 && dimB === 1 && dimC === 1 ; } export function eitherStridesOrDilationsAreOne ( strides , dilations ) { return tupleValuesAreOne ( strides ) || tupleValuesAreOne ( dilations ) ; } param dataFormat return throws export function convertConv2DDataFormat ( dataFormat ) { if ( dataFormat === 'NHWC' ) { return 'channelsLast' ; } else if ( dataFormat === 'NCHW' ) { return 'channelsFirst' ; } else { throw new Error ( `Unknown dataFormat ${ dataFormat }` ) ; } } param opDesc param pad param dimRoundingMode throws export function checkPadOnDimRoundingMode ( opDesc , pad , dimRoundingMode ? ) { if ( dimRoundingMode != null ) { if ( typeof pad === 'string' ) { throw Error ( `Error in ${ opDesc }: pad must be an integer when using ` + `dimRoundingMode ${ dimRoundingMode } but got pad ${ pad }.` ) ; } else if ( typeof pad === 'number' ) { util . assert ( util . isInt ( pad ) , ( ) => `Error in ${ opDesc }: pad must be an integer when using ` + `dimRoundingMode ${ dimRoundingMode } but got pad ${ pad }.` ) ; } else if ( typeof pad === 'object' ) { ( pad as ExplicitPadding ) . forEach ( p => { p . forEach ( v => { util . assert ( util . isInt ( v ) , ( ) => `Error in ${ opDesc }: pad must be an integer when using ` + `dimRoundingMode ${ dimRoundingMode } but got pad ${ v }.` ) ; } ) ; } ) ; } else { throw Error ( `Error in ${ opDesc }: Unknown padding parameter: ${ pad }` ) ; } } }