spec/grid/GridSpec.js

  1. /** @test {Grid}*/
  2. describe("Grid", function () {
  3. let CPM = require("../../build/artistoo-cjs.js")
  4. //eslint-disable-next-line no-unused-vars
  5. let grid2d, grid3d, grid
  6.  
  7. beforeEach(function () {
  8. grid2d = new CPM.Grid2D([100, 100])
  9. grid3d = new CPM.Grid3D([100, 100, 100])
  10. })
  11.  
  12.  
  13. describe( " [ unit tests ] ", function (){
  14. /** @test {Grid#constructor} */
  15. describe( " constructor ", function(){
  16. /* Checking errors thrown by the constructor*/
  17. it("should throw an error when torus is specified for an incorrect number of dimensions", function () {
  18. expect(function () {
  19. grid = new CPM.Grid2D([100, 100], [true])
  20. }).toThrow("Torus should be specified for each dimension, or not at all!")
  21. expect(function () {
  22. //noinspection JSCheckFunctionSignatures
  23. grid = new CPM.Grid2D([100, 100], true)
  24. }).toThrow("Torus should be specified for each dimension, or not at all!")
  25. expect(function () {
  26. grid = new CPM.Grid2D([100, 100], [true, true, true])
  27. }).toThrow("Torus should be specified for each dimension, or not at all!")
  28. expect(function () {
  29. grid = new CPM.Grid3D([100, 100, 100], [true, true])
  30. }).toThrow("Torus should be specified for each dimension, or not at all!")
  31. })
  32.  
  33. /* Checking properties set by the constructor*/
  34. it("should set a size for each dimension", function () {
  35. expect(grid2d.ndim).toEqual(grid2d.extents.length)
  36. expect(grid3d.ndim).toEqual(grid3d.extents.length)
  37. })
  38.  
  39. it("should set a torus property in each dimension", function () {
  40. expect(grid2d.ndim).toEqual(grid2d.torus.length)
  41. expect(grid3d.ndim).toEqual(grid3d.torus.length)
  42. })
  43.  
  44. it("should by default set torus = true in each dimension", function () {
  45. for (let i = 0; i < grid2d.ndim; i++) {
  46. expect(grid2d.torus[i] = true)
  47. }
  48. for (let i = 0; i < grid3d.ndim; i++) {
  49. expect(grid3d.torus[i] = true)
  50. }
  51. })
  52.  
  53. it("should be able to handle different torus settings for each dimension", function () {
  54. let grid = new CPM.Grid2D([100, 100], [true, false])
  55. expect(grid.torus[0]).toBe(true)
  56. expect(grid.torus[1]).toBe(false)
  57. })
  58.  
  59. it("should be able to handle a different size in each dimension", function () {
  60. let grid = new CPM.Grid2D([100, 300])
  61. expect(grid.extents[0]).not.toEqual(grid.extents[1])
  62. })
  63.  
  64. it("should compute a midpoint at the correct position", function () {
  65. expect(grid2d.midpoint.length).toEqual(2)
  66. expect(grid3d.midpoint.length).toEqual(3)
  67.  
  68. grid2d = new CPM.Grid2D([101, 101])
  69. expect((grid2d.midpoint[0] - grid2d.extents[0]/2) <= 1).toBeTruthy()
  70. expect((grid2d.midpoint[1] - grid2d.extents[1]/2) <= 1).toBeTruthy()
  71. expect((grid3d.midpoint[2] - grid3d.extents[2]/2) <= 1).toBeTruthy()
  72. })
  73. })
  74.  
  75. /** @test {Grid#neigh} */
  76. describe( " neigh method ", function(){
  77. let g2D
  78. beforeEach( function() {
  79. // Create a grid 2D object with mock functions except neigh to test the
  80. // functionality of the Grid class independently of the
  81. // Grid2D and Grid3D subclasses.
  82. g2D = new CPM.Grid2D( [100,100] )
  83. //g2D = jasmine.createSpyObj("g2D", [ "neighi","p2i","i2p" ])
  84. // A mock neighi method for the unit test
  85. spyOn( g2D, "neighi" ).and.callFake( function ( i, torus ){
  86. let arr = []
  87. if( (i-1) >= 0 ){
  88. arr.push( i-1 )
  89. } else {
  90. for( let d = 0; d < torus.length; d++ ){
  91. if( torus[d] ) { arr.push( -(1+d )) }
  92. }
  93. }
  94. arr.push( i + 1 )
  95.  
  96. return arr
  97. })
  98. spyOn( g2D, "i2p" ).and.callFake( function(i) {
  99. return( [0,i] )
  100. })
  101. spyOn( g2D, "p2i" ).and.callFake( function(p) {
  102. return p[1]
  103. })
  104. })
  105.  
  106. it( " should return an array coordinate", function(){
  107. let nbh = g2D.neigh( [0,0] )
  108. for( let i = 0; i < nbh.length; i++ ){
  109. let n = nbh[i]
  110. expect( n.length ).toEqual( 2 )
  111. }
  112. })
  113.  
  114. it( " should listen to torus for each dimension",
  115. function() {
  116. // the mock function adds a neighbor for each dim with a torus.
  117. // the mock function should return only one neighbor for [0,0]
  118. // when there is no torus.
  119. let nNeighbors = g2D.neigh( [0,0], [false,false] ).length
  120. expect( nNeighbors ).toEqual(1)
  121. nNeighbors = g2D.neigh( [0,0], [false,true] ).length
  122. expect( nNeighbors ).toEqual( 2 )
  123. nNeighbors = g2D.neigh( [0,0], [true,false] ).length
  124. expect( nNeighbors ).toEqual( 2 )
  125. nNeighbors = g2D.neigh( [0,0], [true,true] ).length
  126. expect( nNeighbors ).toEqual( 3 )
  127.  
  128. // torus doesn't matter when not at 'border'
  129. nNeighbors = g2D.neigh( [1,1], [false,false] ).length
  130. expect( nNeighbors ).toEqual( 2 )
  131. nNeighbors = g2D.neigh( [1,1], [true,true] ).length
  132. expect( nNeighbors ).toEqual( 2 )
  133. })
  134. })
  135.  
  136. /** @test {Grid#setpix}
  137. * @test {Grid#setpixi} */
  138. describe( " setpix(i) methods ", function() {
  139. let grid2D, grid2Db
  140.  
  141. beforeEach( function(){
  142. grid2D = new CPM.Grid2D( [50,50] )
  143. grid2Db = new CPM.Grid2D( [50,50], [false,false],"Float32" )
  144. // mock functions of the p2i and i2p implemented in the subclass.
  145. spyOn( grid2D, "p2i" ).and.returnValue( 0 )
  146. spyOn( grid2D, "i2p" ).and.returnValue( [0,0] )
  147. spyOn( grid2Db, "p2i" ).and.returnValue( 0 )
  148. spyOn( grid2Db, "i2p" ).and.returnValue( [0,0] )
  149. })
  150.  
  151. /**
  152. * @test {Grid#setpix}
  153. * @test {Grid#setpixi}
  154. * */
  155. it( "can be called", function(){
  156.  
  157. expect( grid2D.pixti( 0 ) ).toEqual( 0 )
  158. expect( function(){ grid2D.setpixi( 0, 1 ) } ).not.toThrow()
  159. expect( function(){ grid2D.setpix( [0,0], 2 ) } ).not.toThrow()
  160. expect( function(){ grid2Db.setpix( [0,0], -1 ) } ).not.toThrow()
  161. })
  162.  
  163. /**
  164. * @test {Grid#setpix}
  165. * @test {Grid#setpixi}
  166. * @test {Grid#_isValidValue}
  167. * */
  168. it( " should prohibit setting an invalid type on the grid to avoid bugs", function() {
  169. expect( function(){ grid2D.setpix( [0,0], -1 ) } ).toThrow()
  170. expect( function(){ grid2D.setpix( [0,0], 2.5 ) } ).toThrow()
  171. expect( function(){ grid2D.setpixi( 0, -1 ) } ).toThrow()
  172. expect( function(){ grid2D.setpixi( 0, 2.5 ) } ).toThrow()
  173.  
  174. // but small numeric differences are tolerated
  175. expect( function(){ grid2D.setpixi( 0, 1.00000001 )}).not.toThrow()
  176. expect( function(){ grid2D.setpix( [0,0], 1.00000001 )}).not.toThrow()
  177. })
  178.  
  179. /**
  180. * @test {Grid#setpix}
  181. * @test {Grid#setpixi}
  182. * */
  183. it( "store values in the Grid correctly", function(){
  184.  
  185. // ---- Case 1 : Uint16 grid
  186. // Before setpix, values are zero:
  187. expect( grid2D.pixti( 0 ) ).toEqual( 0 )
  188. let randomInt1 = Math.round( Math.random()*100 )
  189. grid2D.setpixi( 0, randomInt1 )
  190. expect( grid2D.pixti( 0 ) ).toEqual( randomInt1 )
  191.  
  192. let randomInt2 = Math.round( Math.random()*100 )
  193. grid2D.setpix( [0,0], randomInt2 )
  194. expect( grid2D.pixti( 0 ) ).toEqual( randomInt2 )
  195.  
  196. // --- Case 2 : Float32 grid
  197. // It should be possible to set a negative value.
  198. expect( function(){ grid2Db.setpix( [0,0], -1 ) } ).not.toThrow()
  199. expect( grid2Db.pixti( 0 ) ).toEqual( -1 )
  200.  
  201. // ... Or a floating point number
  202. let value = 2.345
  203. expect( function(){ grid2Db.setpix( [0,0], value ) } ).not.toThrow()
  204. expect( grid2Db.pixti( 0 ) ).toBeCloseTo( value, 6 )
  205. })
  206.  
  207.  
  208.  
  209. })
  210.  
  211. /** @test {Grid#pixt}
  212. * @test {Grid#pixti} */
  213. describe( " pixt(i) methods ", function() {
  214. let grid2D
  215.  
  216. beforeEach( function(){
  217. grid2D = new CPM.Grid2D( [50,50] )
  218. // mock functions of the p2i and i2p implemented in the subclass.
  219. spyOn( grid2D, "p2i" ).and.returnValue( 0 )
  220. spyOn( grid2D, "i2p" ).and.returnValue( [0,0] )
  221. })
  222.  
  223. /**
  224. * @test {Grid#pixt}
  225. * @test {Grid#pixti}
  226. * */
  227. it( "pixt(i) can show types on the grid.", function(){
  228. // before change, types are always zero.
  229. expect( grid2D.pixti( 0 ) ).toEqual( 0 )
  230. // pixt uses internally the p2i method from the grid subclass
  231. // (but not i2p)
  232. expect( grid2D.p2i ).not.toHaveBeenCalled()
  233. expect( grid2D.pixt( [0,0] ) ).toEqual( 0 )
  234. expect( grid2D.p2i ).toHaveBeenCalledWith( [0,0] )
  235. expect( grid2D.i2p ).not.toHaveBeenCalled()
  236. })
  237. })
  238.  
  239. /** @test {Grid#pixelsBuffer} */
  240. describe( " pixelsBuffer method ", function() {
  241. let grid2D, grid2Db
  242.  
  243. beforeEach( function(){
  244. grid2D = new CPM.Grid2D( [50,50] )
  245. grid2Db = new CPM.Grid2D( [100,100], [false,false], "Float32" )
  246. })
  247.  
  248. it( "should work on Float32 and Uint16 grids", function(){
  249. // before change, there is no buffer yet
  250. expect( grid2D._pixelsbuffer === undefined ).toBeTruthy()
  251. expect( grid2Db._pixelsbuffer === undefined ).toBeTruthy()
  252.  
  253. // after calling the method, there is.
  254. expect( function(){ grid2D.pixelsBuffer() } ).not.toThrow()
  255. expect( function(){ grid2Db.pixelsBuffer() } ).not.toThrow()
  256. expect( grid2D._pixelsbuffer === undefined ).toBeFalse()
  257. expect( grid2Db._pixelsbuffer === undefined ).toBeFalse()
  258.  
  259. })
  260. })
  261.  
  262. /** @test {Grid#laplaciani} */
  263. describe( " laplacian(i) method ", function() {
  264. let grid2Db, grid2D
  265.  
  266. beforeEach( function(){
  267. grid2D = new CPM.Grid2D( [100,100] )
  268. grid2Db = new CPM.Grid2D( [100,100], [false,false], "Float32" )
  269. })
  270.  
  271. it( "should work on Float32 grids", function(){
  272. expect( function(){ grid2Db.laplaciani(1) } ).not.toThrow()
  273. expect( function(){ grid2Db.laplacian([1,1] ) } ).not.toThrow()
  274. })
  275.  
  276. it( "should throw error when you try to call it on Uint16 grid", function(){
  277. expect( function(){ grid2D.laplaciani(1) } ).toThrow()
  278. expect( function(){ grid2D.laplacian([1,1] ) } ).toThrow()
  279. })
  280.  
  281. describe( " should compute laplacian correctly ", function() {
  282.  
  283. // spy on the neighNeumanni method by returning always pixels 1,2,3,4
  284. beforeEach( function (){
  285. grid2Db.neighNeumanni = function * (){
  286. for( let i = 0; i < 4; i++ ){
  287. yield i+1
  288. }
  289. }
  290. })
  291.  
  292.  
  293. it( " case 1 : everything zero ", function(){
  294. // spy on pixti method to let it always return 0
  295. spyOn( grid2Db, "pixti" ).and.returnValue(0)
  296.  
  297. // check that the spying works, this value of 1000 should not
  298. // be detected.
  299. grid2Db.setpixi( 1, 1000 )
  300.  
  301. // check that laplacian returns zero
  302. expect( grid2Db.laplaciani(0) ).toEqual(0)
  303.  
  304. })
  305.  
  306. it( " case 2 : everything a positive value ", function(){
  307.  
  308. spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
  309. return i*1000
  310. })
  311. expect( grid2Db.laplaciani(0) ).toEqual( 10000 )
  312.  
  313. })
  314.  
  315. it( " case 3 : everything a negative value ", function(){
  316. spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
  317. return -i*1000
  318. })
  319. expect( grid2Db.laplaciani(0) ).toEqual( -10000 )
  320. })
  321.  
  322. it( " case 4 : floating point values ", function(){
  323. spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
  324. return i*1000 + 0.1* Math.random()
  325. })
  326. expect( grid2Db.laplaciani(0) ).toBeCloseTo( 10000, 0 )
  327. })
  328.  
  329. it( " case 4 : positive and negative floating point values ", function(){
  330. spyOn( grid2Db, "pixti" ).and.callFake( function( i ){
  331. if( i >= 1 && i <= 4 ){
  332. let num = 1000 + 0.1* Math.random()
  333. if( i % 2 === 0 ){ return -num }
  334. return num
  335. }
  336. return 0
  337. })
  338. expect( grid2Db.laplaciani(0) ).toBeCloseTo( 0, 0 )
  339. })
  340.  
  341. })
  342.  
  343. })
  344.  
  345. /** @test {Grid#diffusion} */
  346. describe( " diffusion method ", function() {
  347. let grid2Db, grid2D
  348.  
  349. beforeEach(function () {
  350. grid2D = new CPM.Grid2D([200, 200])
  351. grid2Db = new CPM.Grid2D([200, 200], [false, false], "Float32")
  352. })
  353.  
  354. it("can be called on Float32 grids", function () {
  355. expect(function () {
  356. grid2Db.diffusion(0.01)
  357. }).not.toThrow()
  358. })
  359.  
  360. it("should throw error when you try to call it on Uint16 grid", function () {
  361. let message = "Diffusion/laplacian methods do not work on a Uint16 grid! " +
  362. "Consider setting datatype='Float32'."
  363. expect(function () {
  364. grid2D.diffusion(1)
  365. }).toThrow(message)
  366. })
  367.  
  368. })
  369.  
  370.  
  371. })
  372.  
  373. describe( " [ class extension ] ", function (){
  374. let g
  375.  
  376. class MyGrid extends CPM.Grid {
  377. myMethod(){
  378. return 1
  379. }
  380. }
  381. beforeEach( function(){
  382. g = new MyGrid( [50,50] )
  383. })
  384.  
  385. it( "should be possible to extend with a method", function() {
  386. expect( g.myMethod() ).toEqual(1)
  387. })
  388.  
  389. it( "should be possible to build a custom Grid subclass" , function(){
  390. //eslint-disable-next-line no-unused-vars
  391. expect( function(){ new MyGrid( [ 50,50 ] ) } ).not.toThrow()
  392. let g = new MyGrid( [ 50,50 ] )
  393. expect( g.extents[0] ).toEqual(50)
  394. })
  395.  
  396. /** @test {Grid#_pixels} */
  397. it( "should throw an error when _pixelArray is not set in subclass", function(){
  398. expect( function(){ g._pixels }).toThrow()
  399. })
  400.  
  401. /**
  402. * @test {Grid#p2i}
  403. * @test {Grid#i2p}
  404. */
  405. it( "superclass should throw error when p2i/i2p not implemented", function() {
  406. expect( function(){ g.p2i( [0,0] ) }).toThrow()
  407. expect( function(){ g.i2p( 0 )}).toThrow()
  408. })
  409.  
  410. /** @test{Grid#neighi}
  411. * @test {Grid#neigh}
  412. * */
  413. it( "should throw error when neighi not implemented", function(){
  414. expect( function(){g.neigh([0,0])}).toThrow()
  415. expect( function(){g.neighi(0)}).toThrow()
  416.  
  417. // but it should work as soon as p2i, i2p, and neighi are defined.
  418. spyOn( g, "p2i" ).and.returnValue( 0 )
  419. spyOn( g, "i2p" ).and.returnValue( [0,0] )
  420. spyOn( g, "neighi" ).and.returnValue( [0] )
  421. expect( function(){ g.neigh([0,0])} ).not.toThrow()
  422. })
  423.  
  424. /** @test{Grid#pixelsi}
  425. * @test {Grid#pixels}
  426. * */
  427. it( "should throw error when pixels/pixelsi not implemented", function(){
  428.  
  429. let pixels = []
  430. expect( function(){ for( let p of g.pixels() ){ pixels.push(p) } }).toThrow()
  431. expect( function(){ for( let p of g.pixelsi() ){ pixels.push(p) } }).toThrow()
  432. expect( pixels.length ).toEqual(0)
  433. })
  434.  
  435. /** @test{Grid#gradienti}
  436. * @test {Grid#gradient}
  437. * */
  438. it( "should throw error when gradienti not implemented", function(){
  439. expect( function(){g.gradient([0,0])}).toThrow()
  440. expect( function(){g.gradienti(0)}).toThrow()
  441. })
  442.  
  443. /** @test{Grid#gradienti}
  444. * @test {Grid#gradient}
  445. * */
  446. it( "gradient should work as soon as gradienti and p2i are implemented.", function(){
  447. spyOn( g, "p2i" ).and.returnValue( 0 )
  448. spyOn( g, "gradienti" ).and.callFake( function(i){ return i + 10 } )
  449. expect( function(){ g.gradient([0,0] ) } ).not.toThrow()
  450. expect( g.gradient( [0,0] ) ).toEqual(10)
  451. })
  452.  
  453. /** @test{Grid#neighNeumanni}
  454. * @test {Grid#laplacian}
  455. * @test {Grid#laplaciani}
  456. * */
  457. it( "laplaciani should throw error when neighNeumanni not implemented", function(){
  458. let message = "Trying to call the method neighNeumanni, but you haven't " +
  459. "implemented this method in the Grid subclass you are using!"
  460. expect( function(){g.laplaciani(0)}).toThrow(message)
  461. })
  462.  
  463. /** @test{Grid#neighNeumanni}
  464. * @test {Grid#laplacian}
  465. * @test {Grid#laplaciani}
  466. * */
  467. it( "laplacian should throw error when p2i/neighNeumanni not implemented", function(){
  468. let message = "A p2i method should be implemented in every Grid subclass!"
  469. expect( function(){g.laplacian([0,0] ) } ).toThrow(message)
  470.  
  471. // should still throw error when p2i is implemented but neighNeumanni is not
  472. spyOn( g, "p2i" ).and.returnValue(0)
  473. message = "Trying to call the method neighNeumanni, but you haven't " +
  474. "implemented this method in the Grid subclass you are using!"
  475. expect( function(){g.laplacian([0,0] ) } ).toThrow(message)
  476. })
  477.  
  478. /** @test{Grid#neighNeumanni}
  479. * @test {Grid#laplacian}
  480. * @test {Grid#laplaciani}
  481. * */
  482. it( "laplacian(i) should work if p2i, neighNeumanni, and _pixelArray exist", function(){
  483.  
  484. spyOn( g, "p2i" ).and.returnValue(0)
  485. g.neighNeumanni = function* (){
  486. for( let k = 0; k < 4; k++ ){ yield k + 1 }
  487. }
  488. g._pixelArray = new Float32Array(1000)
  489. expect( function(){g.laplacian([0,0] ) } ).not.toThrow()
  490.  
  491. })
  492.  
  493. })
  494.  
  495.  
  496. })