spec/grid/GridSpec.js
- /** @test {Grid}*/
- describe("Grid", function () {
- let CPM = require("../../build/artistoo-cjs.js")
- //eslint-disable-next-line no-unused-vars
- let grid2d, grid3d, grid
-
- beforeEach(function () {
- grid2d = new CPM.Grid2D([100, 100])
- grid3d = new CPM.Grid3D([100, 100, 100])
- })
-
-
- describe( " [ unit tests ] ", function (){
- /** @test {Grid#constructor} */
- describe( " constructor ", function(){
- /* Checking errors thrown by the constructor*/
- it("should throw an error when torus is specified for an incorrect number of dimensions", function () {
- expect(function () {
- grid = new CPM.Grid2D([100, 100], [true])
- }).toThrow("Torus should be specified for each dimension, or not at all!")
- expect(function () {
- //noinspection JSCheckFunctionSignatures
- grid = new CPM.Grid2D([100, 100], true)
- }).toThrow("Torus should be specified for each dimension, or not at all!")
- expect(function () {
- grid = new CPM.Grid2D([100, 100], [true, true, true])
- }).toThrow("Torus should be specified for each dimension, or not at all!")
- expect(function () {
- grid = new CPM.Grid3D([100, 100, 100], [true, true])
- }).toThrow("Torus should be specified for each dimension, or not at all!")
- })
-
- /* Checking properties set by the constructor*/
- it("should set a size for each dimension", function () {
- expect(grid2d.ndim).toEqual(grid2d.extents.length)
- expect(grid3d.ndim).toEqual(grid3d.extents.length)
- })
-
- it("should set a torus property in each dimension", function () {
- expect(grid2d.ndim).toEqual(grid2d.torus.length)
- expect(grid3d.ndim).toEqual(grid3d.torus.length)
- })
-
- it("should by default set torus = true in each dimension", function () {
- for (let i = 0; i < grid2d.ndim; i++) {
- expect(grid2d.torus[i] = true)
- }
- for (let i = 0; i < grid3d.ndim; i++) {
- expect(grid3d.torus[i] = true)
- }
- })
-
- it("should be able to handle different torus settings for each dimension", function () {
- let grid = new CPM.Grid2D([100, 100], [true, false])
- expect(grid.torus[0]).toBe(true)
- expect(grid.torus[1]).toBe(false)
- })
-
- it("should be able to handle a different size in each dimension", function () {
- let grid = new CPM.Grid2D([100, 300])
- expect(grid.extents[0]).not.toEqual(grid.extents[1])
- })
-
- it("should compute a midpoint at the correct position", function () {
- expect(grid2d.midpoint.length).toEqual(2)
- expect(grid3d.midpoint.length).toEqual(3)
-
- grid2d = new CPM.Grid2D([101, 101])
- expect((grid2d.midpoint[0] - grid2d.extents[0]/2) <= 1).toBeTruthy()
- expect((grid2d.midpoint[1] - grid2d.extents[1]/2) <= 1).toBeTruthy()
- expect((grid3d.midpoint[2] - grid3d.extents[2]/2) <= 1).toBeTruthy()
- })
- })
-
- /** @test {Grid#neigh} */
- describe( " neigh method ", function(){
- let g2D
- beforeEach( function() {
- // Create a grid 2D object with mock functions except neigh to test the
- // functionality of the Grid class independently of the
- // Grid2D and Grid3D subclasses.
- g2D = new CPM.Grid2D( [100,100] )
- //g2D = jasmine.createSpyObj("g2D", [ "neighi","p2i","i2p" ])
- // A mock neighi method for the unit test
- spyOn( g2D, "neighi" ).and.callFake( function ( i, torus ){
- let arr = []
- if( (i-1) >= 0 ){
- arr.push( i-1 )
- } else {
- for( let d = 0; d < torus.length; d++ ){
- if( torus[d] ) { arr.push( -(1+d )) }
- }
- }
- arr.push( i + 1 )
-
- return arr
- })
- spyOn( g2D, "i2p" ).and.callFake( function(i) {
- return( [0,i] )
- })
- spyOn( g2D, "p2i" ).and.callFake( function(p) {
- return p[1]
- })
- })
-
- it( " should return an array coordinate", function(){
- let nbh = g2D.neigh( [0,0] )
- for( let i = 0; i < nbh.length; i++ ){
- let n = nbh[i]
- expect( n.length ).toEqual( 2 )
- }
- })
-
- it( " should listen to torus for each dimension",
- function() {
- // the mock function adds a neighbor for each dim with a torus.
- // the mock function should return only one neighbor for [0,0]
- // when there is no torus.
- let nNeighbors = g2D.neigh( [0,0], [false,false] ).length
- expect( nNeighbors ).toEqual(1)
- nNeighbors = g2D.neigh( [0,0], [false,true] ).length
- expect( nNeighbors ).toEqual( 2 )
- nNeighbors = g2D.neigh( [0,0], [true,false] ).length
- expect( nNeighbors ).toEqual( 2 )
- nNeighbors = g2D.neigh( [0,0], [true,true] ).length
- expect( nNeighbors ).toEqual( 3 )
-
- // torus doesn't matter when not at 'border'
- nNeighbors = g2D.neigh( [1,1], [false,false] ).length
- expect( nNeighbors ).toEqual( 2 )
- nNeighbors = g2D.neigh( [1,1], [true,true] ).length
- expect( nNeighbors ).toEqual( 2 )
- })
- })
-
- /** @test {Grid#setpix}
- * @test {Grid#setpixi} */
- describe( " setpix(i) methods ", function() {
- let grid2D, grid2Db
-
- beforeEach( function(){
- grid2D = new CPM.Grid2D( [50,50] )
- grid2Db = new CPM.Grid2D( [50,50], [false,false],"Float32" )
- // mock functions of the p2i and i2p implemented in the subclass.
- spyOn( grid2D, "p2i" ).and.returnValue( 0 )
- spyOn( grid2D, "i2p" ).and.returnValue( [0,0] )
- spyOn( grid2Db, "p2i" ).and.returnValue( 0 )
- spyOn( grid2Db, "i2p" ).and.returnValue( [0,0] )
- })
-
- /**
- * @test {Grid#setpix}
- * @test {Grid#setpixi}
- * */
- it( "can be called", function(){
-
- expect( grid2D.pixti( 0 ) ).toEqual( 0 )
- expect( function(){ grid2D.setpixi( 0, 1 ) } ).not.toThrow()
- expect( function(){ grid2D.setpix( [0,0], 2 ) } ).not.toThrow()
- expect( function(){ grid2Db.setpix( [0,0], -1 ) } ).not.toThrow()
- })
-
- /**
- * @test {Grid#setpix}
- * @test {Grid#setpixi}
- * @test {Grid#_isValidValue}
- * */
- it( " should prohibit setting an invalid type on the grid to avoid bugs", function() {
- expect( function(){ grid2D.setpix( [0,0], -1 ) } ).toThrow()
- expect( function(){ grid2D.setpix( [0,0], 2.5 ) } ).toThrow()
- expect( function(){ grid2D.setpixi( 0, -1 ) } ).toThrow()
- expect( function(){ grid2D.setpixi( 0, 2.5 ) } ).toThrow()
-
- // but small numeric differences are tolerated
- expect( function(){ grid2D.setpixi( 0, 1.00000001 )}).not.toThrow()
- expect( function(){ grid2D.setpix( [0,0], 1.00000001 )}).not.toThrow()
- })
-
- /**
- * @test {Grid#setpix}
- * @test {Grid#setpixi}
- * */
- it( "store values in the Grid correctly", function(){
-
- // ---- Case 1 : Uint16 grid
- // Before setpix, values are zero:
- expect( grid2D.pixti( 0 ) ).toEqual( 0 )
- let randomInt1 = Math.round( Math.random()*100 )
- grid2D.setpixi( 0, randomInt1 )
- expect( grid2D.pixti( 0 ) ).toEqual( randomInt1 )
-
- let randomInt2 = Math.round( Math.random()*100 )
- grid2D.setpix( [0,0], randomInt2 )
- expect( grid2D.pixti( 0 ) ).toEqual( randomInt2 )
-
- // --- Case 2 : Float32 grid
- // It should be possible to set a negative value.
- expect( function(){ grid2Db.setpix( [0,0], -1 ) } ).not.toThrow()
- expect( grid2Db.pixti( 0 ) ).toEqual( -1 )
-
- // ... Or a floating point number
- let value = 2.345
- expect( function(){ grid2Db.setpix( [0,0], value ) } ).not.toThrow()
- expect( grid2Db.pixti( 0 ) ).toBeCloseTo( value, 6 )
- })
-
-
-
- })
-
- /** @test {Grid#pixt}
- * @test {Grid#pixti} */
- describe( " pixt(i) methods ", function() {
- let grid2D
-
- beforeEach( function(){
- grid2D = new CPM.Grid2D( [50,50] )
- // mock functions of the p2i and i2p implemented in the subclass.
- spyOn( grid2D, "p2i" ).and.returnValue( 0 )
- spyOn( grid2D, "i2p" ).and.returnValue( [0,0] )
- })
-
- /**
- * @test {Grid#pixt}
- * @test {Grid#pixti}
- * */
- it( "pixt(i) can show types on the grid.", function(){
- // before change, types are always zero.
- expect( grid2D.pixti( 0 ) ).toEqual( 0 )
- // pixt uses internally the p2i method from the grid subclass
- // (but not i2p)
- expect( grid2D.p2i ).not.toHaveBeenCalled()
- expect( grid2D.pixt( [0,0] ) ).toEqual( 0 )
- expect( grid2D.p2i ).toHaveBeenCalledWith( [0,0] )
- expect( grid2D.i2p ).not.toHaveBeenCalled()
- })
- })
-
- /** @test {Grid#pixelsBuffer} */
- describe( " pixelsBuffer method ", function() {
- let grid2D, grid2Db
-
- beforeEach( function(){
- grid2D = new CPM.Grid2D( [50,50] )
- grid2Db = new CPM.Grid2D( [100,100], [false,false], "Float32" )
- })
-
- it( "should work on Float32 and Uint16 grids", function(){
- // before change, there is no buffer yet
- expect( grid2D._pixelsbuffer === undefined ).toBeTruthy()
- expect( grid2Db._pixelsbuffer === undefined ).toBeTruthy()
-
- // after calling the method, there is.
- expect( function(){ grid2D.pixelsBuffer() } ).not.toThrow()
- expect( function(){ grid2Db.pixelsBuffer() } ).not.toThrow()
- expect( grid2D._pixelsbuffer === undefined ).toBeFalse()
- expect( grid2Db._pixelsbuffer === undefined ).toBeFalse()
-
- })
- })
-
- /** @test {Grid#laplaciani} */
- describe( " laplacian(i) method ", function() {
- let grid2Db, grid2D
-
- beforeEach( function(){
- grid2D = new CPM.Grid2D( [100,100] )
- grid2Db = new CPM.Grid2D( [100,100], [false,false], "Float32" )
- })
-
- it( "should work on Float32 grids", function(){
- expect( function(){ grid2Db.laplaciani(1) } ).not.toThrow()
- expect( function(){ grid2Db.laplacian([1,1] ) } ).not.toThrow()
- })
-
- it( "should throw error when you try to call it on Uint16 grid", function(){
- expect( function(){ grid2D.laplaciani(1) } ).toThrow()
- expect( function(){ grid2D.laplacian([1,1] ) } ).toThrow()
- })
-
- describe( " should compute laplacian correctly ", function() {
-
- // spy on the neighNeumanni method by returning always pixels 1,2,3,4
- beforeEach( function (){
- grid2Db.neighNeumanni = function * (){
- for( let i = 0; i < 4; i++ ){
- yield i+1
- }
- }
- })
-
-
- it( " case 1 : everything zero ", function(){
- // spy on pixti method to let it always return 0
- spyOn( grid2Db, "pixti" ).and.returnValue(0)
-
- // check that the spying works, this value of 1000 should not
- // be detected.
- grid2Db.setpixi( 1, 1000 )
-
- // check that laplacian returns zero
- expect( grid2Db.laplaciani(0) ).toEqual(0)
-
- })
-
- it( " case 2 : everything a positive value ", function(){
-
- spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
- return i*1000
- })
- expect( grid2Db.laplaciani(0) ).toEqual( 10000 )
-
- })
-
- it( " case 3 : everything a negative value ", function(){
- spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
- return -i*1000
- })
- expect( grid2Db.laplaciani(0) ).toEqual( -10000 )
- })
-
- it( " case 4 : floating point values ", function(){
- spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
- return i*1000 + 0.1* Math.random()
- })
- expect( grid2Db.laplaciani(0) ).toBeCloseTo( 10000, 0 )
- })
-
- it( " case 4 : positive and negative floating point values ", function(){
- spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
- if( i >= 1 && i <= 4 ){
- let num = 1000 + 0.1* Math.random()
- if( i % 2 === 0 ){ return -num }
- return num
- }
- return 0
- })
- expect( grid2Db.laplaciani(0) ).toBeCloseTo( 0, 0 )
- })
-
- })
-
- })
-
- /** @test {Grid#diffusion} */
- describe( " diffusion method ", function() {
- let grid2Db, grid2D
-
- beforeEach(function () {
- grid2D = new CPM.Grid2D([200, 200])
- grid2Db = new CPM.Grid2D([200, 200], [false, false], "Float32")
- })
-
- it("can be called on Float32 grids", function () {
- expect(function () {
- grid2Db.diffusion(0.01)
- }).not.toThrow()
- })
-
- it("should throw error when you try to call it on Uint16 grid", function () {
- let message = "Diffusion/laplacian methods do not work on a Uint16 grid! " +
- "Consider setting datatype='Float32'."
- expect(function () {
- grid2D.diffusion(1)
- }).toThrow(message)
- })
-
- })
-
-
- })
-
- describe( " [ class extension ] ", function (){
- let g
-
- class MyGrid extends CPM.Grid {
- myMethod(){
- return 1
- }
- }
- beforeEach( function(){
- g = new MyGrid( [50,50] )
- })
-
- it( "should be possible to extend with a method", function() {
- expect( g.myMethod() ).toEqual(1)
- })
-
- it( "should be possible to build a custom Grid subclass" , function(){
- //eslint-disable-next-line no-unused-vars
- expect( function(){ new MyGrid( [ 50,50 ] ) } ).not.toThrow()
- let g = new MyGrid( [ 50,50 ] )
- expect( g.extents[0] ).toEqual(50)
- })
-
- /** @test {Grid#_pixels} */
- it( "should throw an error when _pixelArray is not set in subclass", function(){
- expect( function(){ g._pixels }).toThrow()
- })
-
- /**
- * @test {Grid#p2i}
- * @test {Grid#i2p}
- */
- it( "superclass should throw error when p2i/i2p not implemented", function() {
- expect( function(){ g.p2i( [0,0] ) }).toThrow()
- expect( function(){ g.i2p( 0 )}).toThrow()
- })
-
- /** @test{Grid#neighi}
- * @test {Grid#neigh}
- * */
- it( "should throw error when neighi not implemented", function(){
- expect( function(){g.neigh([0,0])}).toThrow()
- expect( function(){g.neighi(0)}).toThrow()
-
- // but it should work as soon as p2i, i2p, and neighi are defined.
- spyOn( g, "p2i" ).and.returnValue( 0 )
- spyOn( g, "i2p" ).and.returnValue( [0,0] )
- spyOn( g, "neighi" ).and.returnValue( [0] )
- expect( function(){ g.neigh([0,0])} ).not.toThrow()
- })
-
- /** @test{Grid#pixelsi}
- * @test {Grid#pixels}
- * */
- it( "should throw error when pixels/pixelsi not implemented", function(){
-
- let pixels = []
- expect( function(){ for( let p of g.pixels() ){ pixels.push(p) } }).toThrow()
- expect( function(){ for( let p of g.pixelsi() ){ pixels.push(p) } }).toThrow()
- expect( pixels.length ).toEqual(0)
- })
-
- /** @test{Grid#gradienti}
- * @test {Grid#gradient}
- * */
- it( "should throw error when gradienti not implemented", function(){
- expect( function(){g.gradient([0,0])}).toThrow()
- expect( function(){g.gradienti(0)}).toThrow()
- })
-
- /** @test{Grid#gradienti}
- * @test {Grid#gradient}
- * */
- it( "gradient should work as soon as gradienti and p2i are implemented.", function(){
- spyOn( g, "p2i" ).and.returnValue( 0 )
- spyOn( g, "gradienti" ).and.callFake( function(i){ return i + 10 } )
- expect( function(){ g.gradient([0,0] ) } ).not.toThrow()
- expect( g.gradient( [0,0] ) ).toEqual(10)
- })
-
- /** @test{Grid#neighNeumanni}
- * @test {Grid#laplacian}
- * @test {Grid#laplaciani}
- * */
- it( "laplaciani should throw error when neighNeumanni not implemented", function(){
- let message = "Trying to call the method neighNeumanni, but you haven't " +
- "implemented this method in the Grid subclass you are using!"
- expect( function(){g.laplaciani(0)}).toThrow(message)
- })
-
- /** @test{Grid#neighNeumanni}
- * @test {Grid#laplacian}
- * @test {Grid#laplaciani}
- * */
- it( "laplacian should throw error when p2i/neighNeumanni not implemented", function(){
- let message = "A p2i method should be implemented in every Grid subclass!"
- expect( function(){g.laplacian([0,0] ) } ).toThrow(message)
-
- // should still throw error when p2i is implemented but neighNeumanni is not
- spyOn( g, "p2i" ).and.returnValue(0)
- message = "Trying to call the method neighNeumanni, but you haven't " +
- "implemented this method in the Grid subclass you are using!"
- expect( function(){g.laplacian([0,0] ) } ).toThrow(message)
- })
-
- /** @test{Grid#neighNeumanni}
- * @test {Grid#laplacian}
- * @test {Grid#laplaciani}
- * */
- it( "laplacian(i) should work if p2i, neighNeumanni, and _pixelArray exist", function(){
-
- spyOn( g, "p2i" ).and.returnValue(0)
- g.neighNeumanni = function* (){
- for( let k = 0; k < 4; k++ ){ yield k + 1 }
- }
- g._pixelArray = new Float32Array(1000)
- expect( function(){g.laplacian([0,0] ) } ).not.toThrow()
-
- })
-
- })
-
-
- })