spec/grid/Grid2DSpec.js

/** Rigorous tests for Grid2D-specific methods. See GridExtensionSpec.js for
 * more general tests.
 *
 * @test {Grid2D}*/
describe("Grid2D", function () {
	let CPM = require("../../build/artistoo-cjs.js")
	//eslint-disable-next-line no-unused-vars
	let grid2d, grid2dFloat, allPixelArray, simpleNeumanni, randomPixel,
		samplePixels, borderPixelSample


	let setupGrids = function(){
		grid2d = new CPM.Grid2D([100, 131])
		grid2dFloat = new CPM.Grid2D([200, 100], [false,false], "Float32" )
	}

	beforeEach(function () {
		setupGrids()
		allPixelArray = []
		for( let x = 0; x < grid2d.extents[0]; x++ ){
			for( let y = 0; y < grid2d.extents[1]; y++){
				allPixelArray.push( [x,y] )
			}
		}

		// Return a random pixel
		randomPixel = function( gridObject ) {
			let p = []
			for( let d=0; d < gridObject.ndim; d++ ){
				p.push( Math.round( Math.random()*( gridObject.extents[d]-1 ) ) )
			}
			return p
		}

		// Return a sample of pixels
		samplePixels = function( gridObject, n ){
			let pixelArray = []
			for( let i = 0; i < n; i++ ){
				pixelArray.push( randomPixel(gridObject) )
			}
			return pixelArray
		}

		// Return some border pixels on the grid to check torus handling
		borderPixelSample = function( gridObject ){
			let borderPixels = []
			borderPixels.push( [0,0] )
			borderPixels.push( [gridObject.extents[0]-1, 0 ] )
			borderPixels.push( [0, gridObject.extents[1]-1 ] )
			borderPixels.push( [gridObject.extents[0]-1, gridObject.extents[1]-1 ] )
			borderPixels.push( [0,gridObject.midpoint[1]] )
			borderPixels.push( [gridObject.midpoint[0],0] )
			borderPixels.push( [gridObject.extents[0]-1,gridObject.midpoint[1] ] )
			borderPixels.push( [gridObject.midpoint[0],gridObject.extents[1]-1 ] )
			return borderPixels
		}

		// A simple function for the neumann neighborhood is used for testing.
		simpleNeumanni = function* (i, grid){
			let p = grid.i2p(i), x = p[0], y = p[1]

			// left
			let l = [ x-1, y ]
			if( l[0] >= 0 ){
				yield grid.p2i(l)
			} else {
				if( grid.torus[0] ){
					l[0] += grid.extents[0]
					yield grid.p2i(l)
				}
			}

			// right
			let r = [x+1,y]
			if( r[0] < grid.extents[0] ){
				yield grid.p2i(r)
			} else {
				if( grid.torus[0] ){
					r[0] -= grid.extents[0]
					yield grid.p2i(r)
				}
			}

			// top
			let t = [ x, y-1 ]
			if( t[1] >= 0 ){
				yield grid.p2i(t)
			} else {
				if( grid.torus[1] ){
					t[1] += grid.extents[1]
					yield grid.p2i(t)
				}
			}

			// bottom
			let b = [x,y+1]
			if( b[1] < grid.extents[1] ){
				yield grid.p2i(b)
			} else {
				if( grid.torus[1] ){
					b[1] -= grid.extents[1]
					yield grid.p2i(b)
				}
			}
		}
	})

	describe( " [ unit tests ] ", function () {

		/** @test {Grid2D#pixelsi} */
		it( "pixelsi generator reports correct IndexCoordinates", function(){
			let k = 0
			for( let i of grid2d.pixelsi() ){
				expect( grid2d.i2p(i) ).toEqual( allPixelArray[k] )
				k++
			}
		})

		/** @test {Grid2D#neighi}
		 * @test {Grid2D#neighiSimple} */
		it( "neighi method should return same results as neighiSimple", function(){

			let checkNeighi = function( gridObject ){

				// Test neighi for a sample of pixels on the grid.
				for( let pix of samplePixels( gridObject, 100 ) ){
					const i = gridObject.p2i( pix )
					let neigh1 = gridObject.neighiSimple( i )
					let neigh2 = gridObject.neighi( i )
					expect( neigh1.sort() ).toEqual( neigh2.sort() )
				}

				// Specifically check some pixels at the borders as well
				for( let p of borderPixelSample( gridObject ) ){
					const i = gridObject.p2i( p )
					let neigh1 = gridObject.neighiSimple( i )
					let neigh2 = gridObject.neighi( i )
					expect( neigh1.sort() ).toEqual( neigh2.sort() )
				}
			}

			checkNeighi( grid2d )
			checkNeighi( grid2dFloat )
		})

		/** @test {Grid2D#neighi}*/
		describe( "neighi method should return correct neighbors for specific cases: ", function(){

			let checkExpected = function( gridObject, p, torus, expectedNbh ){
				const nbh = gridObject.neighi( gridObject.p2i(p), torus )
				const expectedNbh2 = expectedNbh.map( function(p) {
					return gridObject.p2i(p)
				} )
				expect( nbh.length ).toEqual( expectedNbh2.length )
				expect( nbh.sort() ).toEqual( expectedNbh2.sort() )
			}

			// Check if neighborhoods are correct for different torus settings,
			// for all four corners of a gridObject.
			let checkNeighi = function( gridObject ) {

				const xMax = gridObject.extents[0] - 1, yMax = gridObject.extents[1] - 1
				it("...corner [0,0] ", function () {
					// case 1: corner point [0,0]
					checkExpected(gridObject, [0, 0], [false, false],
						[[0, 1], [1, 1], [1, 0]])
					checkExpected(gridObject, [0, 0], [false, true],
						[[0, 1], [1, 1], [1, 0], [0, yMax], [1, yMax]])
					checkExpected(gridObject, [0, 0], [true, false],
						[[0, 1], [1, 1], [1, 0], [xMax, 0], [xMax, 1]])
					checkExpected(gridObject, [0, 0], [true, true],
						[[0, 1], [1, 1], [1, 0],
							[xMax, 0], [xMax, 1], [0, yMax], [1, yMax], [xMax, yMax]])
				})
				it("...corner [xMax,0] ", function () {
					// case 2 : corner point [xMax,0]
					checkExpected( gridObject, [xMax, 0], [false, false],
						[[xMax - 1, 0], [xMax - 1, 1], [xMax, 1]])
					checkExpected( gridObject, [xMax,0], [false,true],
						[ [xMax - 1, 0], [xMax - 1, 1], [xMax, 1],
							[xMax-1,yMax], [xMax,yMax] ] )
					checkExpected( gridObject, [xMax,0], [true,false],
						[ [xMax - 1, 0], [xMax - 1, 1], [xMax, 1],
							[0,0], [0,1] ] )
					checkExpected( gridObject, [xMax,0], [true,true],
						[ [xMax - 1, 0], [xMax - 1, 1], [xMax, 1],
							[0,0], [0,1], [xMax-1,yMax], [xMax,yMax], [0,yMax] ] )
				})
				it("...corner [0,yMax] ", function () {
					checkExpected( gridObject, [0,yMax], [false, false],
						[[0, yMax-1], [1,yMax], [1,yMax-1]])
					checkExpected( gridObject, [0,yMax], [false,true],
						[ [0, yMax-1], [1,yMax], [1,yMax-1],
							[0,0], [1,0] ] )
					checkExpected( gridObject, [0,yMax], [true,false],
						[ [0, yMax-1], [1,yMax], [1,yMax-1],
							[xMax,yMax], [xMax,yMax-1] ] )
					checkExpected( gridObject, [0,yMax], [true,true],
						[  [0, yMax-1], [1,yMax], [1,yMax-1],
							[0,0], [1,0], [xMax,yMax-1], [xMax,yMax], [xMax,0] ] )
				})
				it("...corner [xMax,yMax] ", function () {
					checkExpected( gridObject, [xMax,yMax], [false, false],
						[[xMax, yMax-1], [xMax-1,yMax], [xMax-1,yMax-1]])
					checkExpected( gridObject, [xMax,yMax], [false, true],
						[[xMax, yMax-1], [xMax-1,yMax], [xMax-1,yMax-1],
							[xMax,0], [xMax-1,0] ] )
					checkExpected( gridObject, [xMax,yMax], [true,false],
						[[xMax, yMax-1], [xMax-1,yMax], [xMax-1,yMax-1],
							[0,yMax], [0,yMax-1] ] )
					checkExpected( gridObject, [xMax,yMax], [true,true],
						[[xMax, yMax-1], [xMax-1,yMax], [xMax-1,yMax-1],
							[0,yMax], [0,yMax-1], [xMax,0], [xMax-1,0], [0,0] ] )
				})

			}

			setupGrids()
			checkNeighi( grid2d )
			checkNeighi( grid2dFloat )

		})

		/** @test {Grid2D#neighNeumanni} */
		it( "neighNeumanni method should return correct neighbors", function(){

			let checkNeumanni = function( gridObject ){
				// test neighborhood of 100 random pixels
				for( let pix of samplePixels( gridObject, 100 ) ){
					const i = gridObject.p2i( pix )
					let arr1=[], arr2=[]
					for( let n of gridObject.neighNeumanni(i) ){ arr1.push(n) }
					for( let n of simpleNeumanni(i,gridObject) ){ arr2.push(n) }
					expect( arr1 ).toEqual( arr2 )
				}

				// Specifically check some pixels at the borders as well
				for( let p of borderPixelSample( gridObject ) ){
					const i = gridObject.p2i( p )
					let arr1=[], arr2=[]
					for( let n of gridObject.neighNeumanni(i) ){ arr1.push(n) }
					for( let n of simpleNeumanni(i,gridObject) ){ arr2.push(n) }
					expect( arr1 ).toEqual( arr2 )
				}
			}

			checkNeumanni( grid2d )
			checkNeumanni( grid2dFloat )

		})
		/** @test {Grid2D#neighNeumanni}*/
		describe( "neighNeumanni method should return correct neighbors for specific cases: ", function(){

			let checkExpected = function( gridObject, p, torus, expectedNbh ){
				let nbh = []
				for( let n of gridObject.neighNeumanni( gridObject.p2i(p), torus ) ){
					nbh.push( n )
				}
				const expectedNbh2 = expectedNbh.map( function(p) {
					return gridObject.p2i(p)
				} )
				expect( nbh.length ).toEqual( expectedNbh2.length )
				expect( nbh.sort() ).toEqual( expectedNbh2.sort() )
			}

			// Check if neighborhoods are correct for different torus settings,
			// for all four corners of a gridObject.
			let checkNeighNeumanni = function( gridObject ) {

				const xMax = gridObject.extents[0] - 1, yMax = gridObject.extents[1] - 1
				it("...corner [0,0] ", function () {
					// case 1: corner point [0,0]
					checkExpected(gridObject, [0, 0], [false, false],
						[[0, 1], [1, 0]])
					checkExpected(gridObject, [0, 0], [false, true],
						[[0, 1], [1, 0], [0, yMax] ])
					checkExpected(gridObject, [0, 0], [true, false],
						[[0, 1], [1, 0], [xMax, 0] ])
					checkExpected(gridObject, [0, 0], [true, true],
						[[0, 1], [1, 0], [xMax, 0], [0, yMax] ])
				})
				it("...corner [xMax,0] ", function () {
					// case 2 : corner point [xMax,0]
					checkExpected( gridObject, [xMax, 0], [false, false],
						[[xMax - 1, 0], [xMax, 1]])
					checkExpected( gridObject, [xMax,0], [false,true],
						[ [xMax - 1, 0], [xMax, 1], [xMax,yMax] ] )
					checkExpected( gridObject, [xMax,0], [true,false],
						[ [xMax - 1, 0], [xMax, 1], [0,0] ] )
					checkExpected( gridObject, [xMax,0], [true,true],
						[ [xMax - 1, 0], [xMax, 1], [0,0], [xMax,yMax] ] )
				})
				it("...corner [0,yMax] ", function () {
					checkExpected( gridObject, [0,yMax], [false, false],
						[[0, yMax-1], [1,yMax] ])
					checkExpected( gridObject, [0,yMax], [false,true],
						[ [0, yMax-1], [1,yMax], [0,0] ] )
					checkExpected( gridObject, [0,yMax], [true,false],
						[ [0, yMax-1], [1,yMax], [xMax,yMax] ] )
					checkExpected( gridObject, [0,yMax], [true,true],
						[  [0, yMax-1], [1,yMax], [0,0], [xMax,yMax] ] )
				})
				it("...corner [xMax,yMax] ", function () {
					checkExpected( gridObject, [xMax,yMax], [false, false],
						[[xMax, yMax-1], [xMax-1,yMax] ])
					checkExpected( gridObject, [xMax,yMax], [false, true],
						[[xMax, yMax-1], [xMax-1,yMax], [xMax,0] ] )
					checkExpected( gridObject, [xMax,yMax], [true,false],
						[[xMax, yMax-1], [xMax-1,yMax], [0,yMax] ] )
					checkExpected( gridObject, [xMax,yMax], [true,true],
						[[xMax, yMax-1], [xMax-1,yMax],
							[0,yMax], [xMax,0] ] )
				})

			}

			setupGrids()
			checkNeighNeumanni( grid2d )
			checkNeighNeumanni( grid2dFloat )

		})

		/** @test {Grid2D#gradienti} */
		it( "gradienti method should return correct gradients", function(){

			let simpleGradienti = function(i, grid){

				let p = grid.i2p(i)
				let vi = grid.pixti(i), dx, dy

				let nbh = []
				for( let n of simpleNeumanni(i,grid) ){
					nbh.push(n)
				}

				let xNeighbors = [NaN, NaN]
				let yNeighbors = [NaN, NaN]
				// check which neighbors there are
				for( let n of nbh ){
					let pn = grid.i2p(n)
					// x neighbors
					if( pn[1] === p[1] ){
						if( pn[0] < p[0] ){
							xNeighbors[0] = n
						} else {
							xNeighbors[1] = n
						}
					}

					// y neighbors
					if( pn[0] === p[0] ){
						if( pn[1] < p[1]){
							yNeighbors[0] = n
						} else {
							yNeighbors[1] = n
						}
					}
				}

				// this shouldn't happen.
				if( isNaN( xNeighbors[0] ) && isNaN(xNeighbors[1] ) ){
					throw( "no x neighbors found!")
				}
				if( isNaN( yNeighbors[0] ) && isNaN(yNeighbors[1] ) ){
					throw( "no y neighbors found!")
				}

				// compute gradients
				let grad = function( nArr ){
					if( isNaN( nArr[0] ) ){
						return grid.pixti(nArr[1]) - vi
					}
					if( isNaN( nArr[1] ) ){
						return vi - grid.pixti(nArr[0])
					}
					return ( ( grid.pixti(nArr[1]) - vi  ) + ( vi - grid.pixti(nArr[0])  ) )/2
				}

				dx = grad( xNeighbors )
				dy = grad( yNeighbors )

				return [dx,dy]
			}


			let checkGradienti = function( gridObject ){

				// set some random values on the float grid
				for( let i of gridObject.pixelsi() ){
					gridObject.setpixi( i, Math.random()*1000 )
				}

				// test neighborhood of 100 random pixels
				for( let pix of samplePixels( gridObject, 100 ) ){
					const i = gridObject.p2i( pix )
					expect( gridObject.gradienti(i) ).toEqual( simpleGradienti(i,gridObject) )
				}


				// Specifically check some pixels at the borders as well
				for( let p of borderPixelSample( gridObject ) ){
					const i = gridObject.p2i( p )
					expect( gridObject.gradienti(i) ).toEqual( simpleGradienti(i,gridObject) )
				}
			}

			checkGradienti( grid2dFloat )

		})

	})
})