Regular Discrete Data Tools User Guide
Overview
This library intends to provide a comprehensive and flexible data model and implementation for arbitrary dimensional floating-point data sampled on a arbitrary dimensional regular grid. This can be used to store discretized functions or floating point image data. Both 32- and 64-bit precisions are supported. The toolkit includes operations to compute various vector and geometric quantities from the data. There are many predefined routines to convert the data objects to and from similar data structures. The toolkit allows file input and output of the data objects.
This model is a good starting point for image processing and numerical solver applications. Additionally, the models are designed to allow special purpose implementations of data objects and operations on those objects.
In the ShapeTools library, there are mathematical function objects. These can be automatically sampled and encoded to this data model. Furthermore, there are classes to create the functions from the sampled data, essentially interpolating the data such that is is defined between samples.
Mathematics
In particular, the data model encodes a sampling of a function
f:R
N -> R
M, where f(x
1, x
2, ..., x
N) = (y
1, y
2, ..., y
M).
The sampling is defined by the sampling "origin", the sampling spacing in each dimension and the number of samples in each dimension. There must be at least one sample in each dimension, but number of samples in each dimension need not be the same. The sample spacing must be positive and may differ in each dimension.
Let (s
1, s
2, ..., s
N) be the sampling "origin".
Let (dx
1, dx
2, ..., dx
N) be the spacing between samples in each dimension.
Let (K
1, K
2, ..., K
N) be the number of samples in each dimension.
The data model then encodes the values of
f(s
1 + k
1 * dx
1, s
2 + k
2 * dx
2, ..., s
k + k
N * dx
N)
for k
n from [0,K
n-1]. The values k
n are called the indices of a sample. Note, the indices are zero based.
Types
The following types constitute the data model:
- ISampleDescriptor - stores the sampling origin, spacing and number of samples.
- ISample - identifies a particular sample by the indices
- ISampleCoordinates - stores the coordinates of a sample in the domain
- IDataPoint - stores the function value at a particular sample
- DataPrecision - an enumeration of the allowed data precisions
- IDiscreteData - a composition of a sample descriptor and the data stored at the samples
- IDiscreteFunction - a function that takes a single discrete data object as input and output
There are several implementations for most of the above interfaces. It is recommended that you use the factory classes instead of the concrete implementation constructors.
File Format
There are routines for encoding discrete data objects to disk in a binary format. The format will be described in terms of Java primitive data types. The implementation of the format in the library uses Java DataOutputSteams, so this class should be used as a reference for binary representations of the Java primitives on file.
Suppose there is a regular discrete data object with a sample dimension of N, starting point (s
1, s
2, ..., s
N), sample spacings (dx
1, dx
2, ..., dx
N), (K
1, K
2, ..., K
N) in each dimension and a data dimension of M. Let the product K
1 * K
2 * ... * K
N = L. The precision may be either double or float, and will be referred to as "pre". The format will have the following form:
4 x char : a magic string, namely "RDDF"
1 x char : the floating point precision, namely 'd' for double and 'f' for single
1 x int : the data dimension
N x pre : the starting point
N x pre : the sample spacings
N x int : the number of samples in each dimension
L x M x pre : the data
The ordering of the values of the data must be chosen in a consistent way. The data values are grouped by point, iterating over the sample indices. For example, the first M values encode the data at sample (0, 0, ..., 0); the second M values encode the data at sample (1, 0, ..., 0); the (K
1 - 1)-th collection of M values encode the data at sample (K
1 - 1, 0, ..., 0); the K
1-th collection of M values encode the data at sample (0, 1, ..., 0); the last M values encode the data at sample (K
1 - 1, K
2 - 1, ..., K
N - 1).
Example
Here is a class demonstrating use of the toolkit. Be sure the directory 'output' exists.
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import edu.ucla.loni.ccb.discretedata.DiscreteDataFactory;
import edu.ucla.loni.ccb.discretedata.SampleDescriptorFactory;
import edu.ucla.loni.ccb.discretedata.io.DiscreteDataIO;
import edu.ucla.loni.ccb.discretedata.models.IDataPoint;
import edu.ucla.loni.ccb.discretedata.models.IDiscreteData;
import edu.ucla.loni.ccb.discretedata.models.ISample;
import edu.ucla.loni.ccb.discretedata.models.ISampleDescriptor;
import edu.ucla.loni.ccb.discretedata.util.ImageDiscreteDataOperations;
import edu.ucla.loni.ccb.shape.math.functions.IFunction;
public class RegularDiscreteDataExample
{
private static final float X_SIZE = 2;
private static final float Y_SIZE = X_SIZE;
private static final float X_ORIGIN = -X_SIZE/2;
private static final float Y_ORIGIN = -Y_SIZE/2;
private static final int X_NUM = 512;
private static final int Y_NUM = 512;
private static final float DX = X_SIZE/(X_NUM-1);
private static final float DY = Y_SIZE/(Y_NUM-1);
private static final String DATA_FILENAME = "output/example_data.rdd";
private static final String IMAGE_FILENAME = "output/example_image.png";
private static final String IMAGE_FORMAT = "PNG";
private static final float MODULATION_FREQ = 2;
public static void main(String[] args) throws FileNotFoundException, IOException
{
/** BASIC */
System.out.println("Creating data object");
// Create the sample descriptor
double[] start = {X_ORIGIN,Y_ORIGIN};
double[] spacings = {DX,DY};
int[] nums = {X_NUM,Y_NUM};
ISampleDescriptor sampling = SampleDescriptorFactory.create(start,spacings,nums);
// Create the function that will be sampled
IFunction function = new ModulatedGaussian();
// Create the data object by sampling the function
IDiscreteData data = DiscreteDataFactory.sample(function, sampling);
/** IO */
System.out.println("Demonstrating IO");
// Write out and read in
DiscreteDataIO.write(new FileOutputStream(DATA_FILENAME),data);
data = DiscreteDataIO.read(new FileInputStream(DATA_FILENAME));
/** DATA ACCESS */
System.out.println("Demonstrating data access");
// Get a data value
ISample sample = sampling.getSamplePrototype();
sample.set(0,X_NUM/4);
sample.set(1,Y_NUM/4);
IDataPoint dp = data.getDataPrototype();
data.get(sample,dp);
// Print the data value
System.out.println("Sample: "+sample+", Data Value: "+dp);
/** IMAGE CREATION */
System.out.println("Demonstrating image creation");
// Create a grayscale image from the data
BufferedImage image = ImageDiscreteDataOperations.createNormalizedGrayscaleBufferedImage(data);
// Write the image out for viewing
ImageIO.write(image,IMAGE_FORMAT,new File(IMAGE_FILENAME));
}
/** A gaussian modulated by a sine wave */
private static class ModulatedGaussian implements IFunction
{
public void evaluate(float[] arg0, float[] arg1)
throws IllegalArgumentException
{
double radius = Math.sqrt(arg0[0] * arg0[0] + arg0[1] * arg0[1]);
arg1[0] = (float) (Math.cos(MODULATION_FREQ*2*Math.PI*radius)*Math.exp(-radius * radius));
}
public int getInputDimension()
{
return 2;
}
public int getOutputDimension()
{
return 1;
}
}
}