/*
* Copyright (C) 2010-2014 - Andreas Keil
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/
package edu.stanford.rsl.conrad.data.numeric;
import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
import edu.stanford.rsl.conrad.geometry.transforms.Transform;
import edu.stanford.rsl.conrad.geometry.transforms.Transformable;
import edu.stanford.rsl.conrad.utils.VisualizationUtil;
/**
* The two-dimensional version of a Grid.
*
* @see Grid
*
* @author Andreas Keil
*/
public class Grid2D extends NumericGrid implements Transformable {
/** The actual data buffer. This is supposed to be initialized outside of the class. */
protected float[] buffer;
/** The offsets in the linear memory for each column. */
protected int[] columnOffsets;
/** Link to the underlying columns represented as 1D grids */
protected Grid1D[] subGrids;
public Grid2D(int width, int height) {
assert width > 0 && height > 0;
this.buffer = new float[width*height];
this.initialize(width, height);
notifyAfterWrite();
}
public Grid2D(float[] buffer, int[] size) {
this(buffer, size[0], size[1]);
notifyAfterWrite();
}
public Grid2D(float[] buffer, int width, int height) {
assert buffer.length == width*height;
this.buffer = buffer;
this.initialize(width, height);
notifyAfterWrite();
}
protected void initialize(int width, int height)
{
this.size = new int[] {width, height};
this.spacing = new double[2];
this.origin = new double[2];
for (int i = 0; i < 2; ++i) {
assert size[i] > 0 : "Size values have to be greater than zero!";
}
columnOffsets = new int[this.size[1]];
subGrids = new Grid1D[size[1]];
for (int i = 0; i < columnOffsets.length; ++i) {
columnOffsets[i] = i*size[0];
subGrids[i] = new Grid1D(this.buffer, columnOffsets[i], size[0]);
}
}
public Grid2D(Grid2D input){
assert input.getWidth()*input.getHeight() == this.buffer.length;
this.size = input.size.clone();
this.spacing = input.spacing.clone();
this.origin = input.origin.clone();
this.buffer = input.getBuffer().clone();
this.columnOffsets = new int[this.size[1]];
this.subGrids = new Grid1D[size[1]];
for (int i = 0; i < columnOffsets.length; ++i) {
this.columnOffsets[i] = i*this.size[0];
this.subGrids[i] = new Grid1D(this.buffer, columnOffsets[i], size[0]);
}
}
/**
* Returns a reference to the linear buffer containing the 2D image in a row-first manner.
* @return the buffer as float array
*/
public float[] getBuffer() {
notifyBeforeRead();
return this.buffer;
}
/**
* Returns the corresponding Grid1D object that points on the linear 2D row memory
* @param j The row-index (y-index, height-index)
* @return the sub grid
*/
public Grid1D getSubGrid(int j) {
notifyBeforeRead();
return subGrids[j];
}
@Deprecated
/**
* Set the corresponding Grid1D object on the linear 2D row memory
*
* CAUTION: We set absolute values and no references.
* If the input Grid1D subGrid is changed at a later point,
* it won't change the values in the Grid2D.
* This is due to "float buffer" nature of Grid2D,
* which is a fundamental difference to the structure of Grid3D,
* which is defined by an ArrayList of Grid2Ds.
*
* You can do a little workaround, when adding the following code:
*
* myGrid2D.setSubgrid(j, Grid1D myGrid1D);
* myGrid1D = myGrid2D.getSubGrid(j);
*
* This code is deprecated since no final solution was found
* to solve the prior menioned problem.
*
* @param j The row-index (y-index, height-index)
* @param subGrid
*/
public void setSubGrid(int j, Grid1D subGrid) {
for (int i=0; i<subGrid.getSize()[0]; ++i) {
setAtIndex(j, i, subGrid.getAtIndex(i));
}
notifyAfterWrite();
}
public double[] indexToPhysical(double i, double j) {
return new double[] {
i * this.spacing[0] + this.origin[0],
j * this.spacing[1] + this.origin[1]
};
}
public double[] physicalToIndex(double x, double y) {
return new double[] {
(x - this.origin[0]) / this.spacing[0],
(y - this.origin[1]) / this.spacing[1]
};
}
public float getAtIndex(int i, int j) {
notifyBeforeRead();
//FIXME (maybe use getPixelValue instead)
return this.getPixelValue(i, j);
}
public void setAtIndex(int i, int j, float val) {
//FIXME (maybe use putPixelValue instead)
this.putPixelValue(i, j, val);
notifyAfterWrite();
}
public void addAtIndex(int i, int j, float val){
notifyBeforeRead();
this.buffer[j*this.size[0]+i] += val;
notifyAfterWrite();
}
public void multiplyAtIndex(int i, int j, float val){
notifyBeforeRead();
this.buffer[j*this.size[0]+i] *= val;
notifyAfterWrite();
}
public void subAtIndex(int i, int j, float val){
notifyBeforeRead();
this.addAtIndex(i,j,val*-1.f);
notifyAfterWrite();
}
public void divideAtIndex(int i, int j, float val){
notifyBeforeRead();
this.multiplyAtIndex(i, j, 1.f/val);
notifyAfterWrite();
}
public String toStringMatlab() {
notifyBeforeRead();
String result = new String();
result += "[";
for (int i = 0; i < this.buffer.length; ++i) {
if (i != 0) result += ", ";
result += this.buffer[i];
}
result += "]";
return result;
}
@Override
public String toString(){
String result = "Grid 2D " + size[0] + "x" + size[1];
/*if(debug){
float min = this.getGridOperator().min(this);
float max = this.getGridOperator().max(this);
result += " " + min + ":" + max;
}*/
return result;
}
public void show(String title){
notifyBeforeRead();
VisualizationUtil.showGrid2D(this, title);
}
public void show(){
notifyBeforeRead();
show("Grid2D");
}
public float [] getPixels(){
return null;
}
/**
* Gets the grid's width
* @return the width
*/
public int getWidth()
{
return this.size[0];
}
/**
* Gets the grid's height
* @return the height
*/
public int getHeight()
{
return this.size[1];
}
/**
* Set a pixel value at position (x,y)
* @param x The value's x position
* @param y The value's y position
* @param value The value to set
*/
public void putPixelValue(int x, int y, float value) {
if (y>=size[1] || x >= size[0]) return;
this.buffer[y*this.size[0]+x]=value;
notifyAfterWrite();
}
/**
* Set a pixel value at position (x,y)
* @param x The value's x position
* @param y The value's y position
* @param value The value to set
*/
public void putPixelValue(int x, int y, double value) {
this.putPixelValue(x, y, (float)value);
}
/**
* Get the pixel value at position (x,y)
* This method uses linear indices to access the buffer and does not perform range checking!
* @param x The value's x position
* @param y The value's y position
* @return the value of the pixel
*/
public float getPixelValue(int x, int y) {
notifyBeforeRead();
//System.out.println("getPixelValue - Min: " + this.getGridOperator().min(this) + " Max: " + this.getGridOperator().max(this)); // TODO test
return this.buffer[y*this.size[0]+x];
}
public float getPixelValue(int count) {
int x = count % this.size[0];
int y = count / this.size[0];
return getPixelValue(x, y);
}
@Override
public float getValue(int[] idx) {
return this.getAtIndex(idx[0], idx[1]);
}
@Override
public NumericGrid clone() {
notifyBeforeRead();
return (new Grid2D(this));
}
@Override
public void applyTransform(Transform t) {
notifyBeforeRead();
Grid2D tmp = new Grid2D(this);
for (int i=0; i < getSize()[0]; ++i){
for (int j=0; j < getSize()[1]; ++j){
PointND newPos = new PointND(indexToPhysical(i, j));
newPos.applyTransform(t);
double[] pos = physicalToIndex(newPos.get(0),newPos.get(1));
float val = InterpolationOperators.interpolateLinear(tmp, pos[0], pos[1]);
this.setAtIndex(i, j, val);
}
}
notifyAfterWrite();
}
@Override
public void setSpacing(double... spacing){
super.setSpacing(spacing);
for (Grid1D rows : subGrids)
rows.setSpacing(spacing[0]);
}
@Override
public void setOrigin(double... origin){
super.setOrigin(origin);
for (Grid1D rows : subGrids)
rows.setOrigin(origin[0]);
}
@Override
public void setValue(float val, int[] idx) {
setAtIndex(idx[0], idx[1], val);
}
}