src/stats/Centroids.js



import Stat from "./Stat.js"
import PixelsByCell from "./PixelsByCell.js"

/**	This Stat computes the centroid of a cell. When the cell resides on a torus, the
	centroid may be well outside the cell, and other stats may be preferable (e.g.
	{@link CentroidsWithTorusCorrection}).
	
	@example
	* let CPM = require( "path/to/build" )
	*
	* // Make a CPM, seed two cells, run a little, and get their centroids
	* let C = new CPM.CPM( [100,100], { 
	* 	T:20,
	*	torus:[false,false],
	* 	J:[[0,20],[20,10]],
	* 	V:[0,200],
	* 	LAMBDA_V:[0,2]
	* } )
	* let gm = new CPM.GridManipulator( C )
	* gm.seedCell(1)
	* gm.seedCell(1)
	* for( let t = 0; t < 100; t++ ){ C.timeStep() }
	*
	* C.getStat( CPM.Centroids ) 
*/
class Centroids extends Stat {

	/** The set model method of class CentroidsWithTorusCorrection.
	@param {GridBasedModel} M - the model to compute centroids on. */
	set model( M ){
	
		/** The model to compute centroids on. 
		@type {GridBasedModel}*/
		this.M = M
		
		/* Check if the grid has a torus; if so, warn that this method may not be
		appropriate. */
		let torus = false
		for( let d = 0; d < this.M.ndim; d++ ){
			if( this.M.grid.torus[d] ){
				torus = true
				break
			}
		}
		
		if(torus){
			// eslint-disable-next-line no-console
			console.warn( "Your model grid has a torus, and the 'Centroids' stat is not compatible with torus! Consider using 'CentroidsWithTorusCorrection' instead." )
		}
		
		// Half the grid dimensions; if pixels with the same cellid are further apart,
		// we assume they are on the border of the grid and that we need to correct
		// their positions to compute the centroid.
		/** @ignore */
		this.halfsize = new Array( this.M.ndim).fill(0)
		for( let i = 0 ; i < this.M.ndim ; i ++ ){
			this.halfsize[i] = this.M.extents[i]/2
		}
	}
	/** @ignore */
	constructor( conf ){
		super(conf)
	}
	/** This method computes the centroid of a specific cell. 
		@param {CellId} cellid the unique cell id of the cell to get centroid of.
		@param {CellArrayObject} cellpixels object produced by {@link PixelsByCell}, 
		with keys for each cellid
		and as corresponding value the pixel coordinates of their pixels.
		@returns {ArrayCoordinate} coordinate of the centroid.
	*/
	computeCentroidOfCell( cellid, cellpixels  ){
	
		//let cellpixels = this.M.getStat( PixelsByCell ) 
	
		const pixels = cellpixels[ cellid ]
		
		// cvec will contain the x, y, (z) coordinate of the centroid.
		// Loop over the dimensions to compute each element separately.
		let cvec = new Array(this.M.ndim).fill(0)
		for( let dim = 0 ; dim < this.M.ndim ; dim ++ ){
			
			let mi = 0.
			// Loop over the pixels;
			// compute mean position per dimension with online algorithm
			for( let j = 0 ; j < pixels.length ; j ++ ){
				// Check distance of current pixel to the accumulated mean in this dim.
				// Check if this distance is greater than half the grid size in this
				// dimension; if so, this indicates that the cell has moved to the
				// other end of the grid because of the torus. Note that this only
				// holds AFTER the first pixel (so for j > 0), when we actually have
				// an idea of where the cell is.
				let dx = pixels[j][dim] - mi
				// Update the mean with the appropriate weight. 
				mi += dx/(j+1)
			}			
			// Set the mean position in the cvec vector.
			cvec[dim] = mi
		}
		return cvec
		
	}
		
	/** Compute centroids for all cells on the grid. 
	@return {CellObject} with an {@link ArrayCoordinate} of the centroid for each cell
	 on the grid (see {@link computeCentroidOfCell}). */
	compute(){
		// Get object with arrays of pixels for each cell on the grid, and get
		// the array for the current cell.
		let cellpixels = this.M.getStat( PixelsByCell ) 
		
		// Create an object for the centroids. Add the centroid array for each cell.
		let centroids = {}
		for( let cid of this.M.cellIDs() ){
			centroids[cid] = this.computeCentroidOfCell( cid, cellpixels )
		}
		return centroids
	}
}

export default Centroids