package icy.sequence;
import icy.image.IcyBufferedImage;
import icy.type.DataType;
import icy.type.collection.array.Array1DUtil;
import icy.type.collection.array.Array2DUtil;
/**
* This class is intended for plugins that construct new sequences by:<br>
* - first, specifying a data-type and a 5D-size for the sequence to be created<br>
* - second, visiting each sample (x,y,z,t,c) to set its value.<br>
* <br>
* A typical example of such situation would be a plugin that take a sequence
* in input and return a copy of that sequence in output.
*
* For such plugins, the SequenceBuilder class provides some tools to ease the
* creation of the output sequence, especially in a multi-thread context. To
* cut a long story short, here is an example of how the above-mentioned copy
* algorithm could be implemented with SequenceBuilder:<br>
* <br>
* <pre>
* // Input sequence
* Sequence in = ...
*
* // Create a SequenceBuilder object, that will be in charge of allocating
* // and feeding the output sequence.
* SequenceBuilder builder = new SequenceBuilder
* (
* // The output sequence will have the same size and data-type than the input
* in.getSizeX(), in.getSizeY(), in.getSizeZ(), in.getSizeT(), in.getSizeC(),
* in.getDataType_()
* );
*
* // Start the building process.
* builder.beginUpdate();
* try
* {
* // Here, the copy can be multi-threaded. No synchronization or mutex-locking
* // is required on the side of the SequenceBuilder object.
*
* ...
*
* // Thread 1
* forall(int t,z,c in listOfXYPlanesToBeDoneByThread1)
* {
* // Retrieve the array that will hold the pixel values corresponding to
* // the XY-plane at coordinates (t, z, c) in the output sequence. If this
* // object does not exist, it is created. The object returned by the
* // method getData is actually an instance of byte[], short[], int[],
* // float[] or double[], depending on the data-type specified in the
* // SequenceBuilder constructor.
* //
* // Remark: depending on the context, it may be more convenient to call
* // builder.getDataAsDouble, builder.getDataAsByte, etc...
* //
* Object buffer = builder.getData(t, z, c);
*
* // Execute the copy. For example:
* { Array1DUtil.arrayToArray(in.getDataXY(t, z, c), buffer); }
*
* // Mark the array returned by the previous call to builder.getData(t, z, c)
* // as ready to be incorporated in the output sequence.
* //
* // Remark: there should only be one call to the method validateData
* // per set of coordinates (t, z, c).
* //
* builder.validateData(t, z, c);
* }
*
* ...
*
* // Thread 2
* forall(int t,z,c in listOfXYPlanesToBeDoneByThread2) {
* // etc...
* }
*
* ...
*
* }
*
* // Finish the building process (each call to beginUpdate() must be followed
* // by a call to endUpdate()).
* finally {
* builder.endUpdate();
* }
*
* // Return the sequence that have been created by the SequenceBuilder object.
* return builder.getResult();
* </pre>
*
* @author Yoann Le Montagner
*/
public class SequenceBuilder
{
private int _sizeX ;
private int _sizeY ;
private int _sizeZ ;
private int _sizeT ;
private int _sizeC ;
private DataType _dataType ;
private Sequence _result ;
private SequenceAllocator _allocator;
/**
* Allocate a new sequence that will have the given size, dataType and an empty name
*/
public SequenceBuilder(int sizeX, int sizeY, int sizeZ, int sizeT, int sizeC, DataType dataType)
{
this(sizeX, sizeY, sizeZ, sizeT, sizeC, dataType, null);
}
/**
* If non-null, the 'target' argument will be used to store the result of the
* sequence building process, and no new sequence will be created.<br>
* <br>
* Two situations may occur:
* <li>The sequence 'target' has the same size and data-type than specified by
* the arguments passed to the SequenceBuilder object. In that case, no new
* buffer/image allocation is performed, and the sequence is only modified
* through calls to the method Sequence.getDataXY().
* </li>
* <li>
* Otherwise, the sequence 'target' will be completly cleared (through a
* call to the method Sequence.removeAllImages()) when first calling the
* method SequenceBuilder.beginUpdate().
* </li>
*/
public SequenceBuilder(int sizeX, int sizeY, int sizeZ, int sizeT, int sizeC, DataType dataType, Sequence target)
{
_sizeX = sizeX ;
_sizeY = sizeY ;
_sizeZ = sizeZ ;
_sizeT = sizeT ;
_sizeC = sizeC ;
_dataType = dataType;
_result = target==null ? new Sequence() : target;
_allocator = null;
}
/**
* Size X of the sequence to be created
*/
public int getSizeX()
{
return _sizeX;
}
/**
* Size Y of the sequence to be created
*/
public int getSizeY()
{
return _sizeY;
}
/**
* Size Z of the sequence to be created
*/
public int getSizeZ()
{
return _sizeZ;
}
/**
* Size T of the sequence to be created
*/
public int getSizeT()
{
return _sizeT;
}
/**
* Size C of the sequence to be created
*/
public int getSizeC()
{
return _sizeC;
}
/**
* Data-type of the sequence to be created
*/
public DataType getDataType()
{
return _dataType;
}
/**
* Return the output sequence
*/
public Sequence getResult()
{
return _result;
}
/**
* Check if the sequence is pre-allocated, i.e. if it already has the proper
* size and data-type that was specified by the argument passed to the
* SequenceBuilder constructor
*/
public boolean isPreAllocated()
{
return
// Match the size ...
_result.getSizeX()==_sizeX &&
_result.getSizeY()==_sizeY &&
_result.getSizeZ()==_sizeZ &&
_result.getSizeT()==_sizeT &&
_result.getSizeC()==_sizeC &&
// ... and the data-type in the case of non-empty sequences
(_result.getDataType_()==_dataType
|| _sizeX==0
|| _sizeY==0
|| _sizeZ==0
|| _sizeT==0
|| _sizeC==0
);
}
/**
* Start building the sequence
*
* @throws IllegalStateException if the object is already in an "updating" state
*/
public void beginUpdate()
{
if(_allocator!=null) {
throw new IllegalStateException("The SequenceBuilder object is already in an update state.");
}
_result.beginUpdate();
// If the sequence has already the requested size and data-type, it can simply
// be modified in-place: there is no need for new buffer allocation.
if(isPreAllocated()) {
_allocator = new PreAllocatedAllocator(_result);
}
// Otherwise, the sequence is cleared, and dynamically rebuilt.
else {
_result.removeAllImages();
_allocator = new OnFlyAllocator(_sizeX, _sizeY, _sizeZ, _sizeT, _sizeC, _dataType, _result);
}
}
/**
* Finish building the sequence.<br>
* Nothing happens if beginUpdate() has not been called previously
*/
public void endUpdate()
{
if(_allocator==null) {
return;
}
_result.endUpdate();
_allocator = null;
}
/**
* Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
* @throws NullPointerException if the method beginUpdate() has not been called previously.
*/
public double[] getDataAsDouble(int t, int z, int c)
{
return (double[])_allocator.getData(t, z, c);
}
/**
* Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
* @throws NullPointerException if the method beginUpdate() has not been called previously.
*/
public float[] getDataAsFloat(int t, int z, int c)
{
return (float[])_allocator.getData(t, z, c);
}
/**
* Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
* @throws NullPointerException if the method beginUpdate() has not been called previously.
*/
public byte[] getDataAsByte(int t, int z, int c)
{
return (byte[])_allocator.getData(t, z, c);
}
/**
* Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
* @throws NullPointerException if the method beginUpdate() has not been called previously.
*/
public short[] getDataAsShort(int t, int z, int c)
{
return (short[])_allocator.getData(t, z, c);
}
/**
* Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
* @throws NullPointerException if the method beginUpdate() has not been called previously.
*/
public int[] getDataAsInt(int t, int z, int c)
{
return (int[])_allocator.getData(t, z, c);
}
/**
* Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
* @throws NullPointerException if the method beginUpdate() has not been called previously.
*/
public Object getData(int t, int z, int c)
{
return _allocator.getData(t, z, c);
}
/**
* Validate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
* @throws NullPointerException if the method beginUpdate() has not been called previously.
*/
public void validateData(int t, int z, int c)
{
_allocator.validateData(t, z, c);
}
/**
* Interface to access the data of the targeted sequence
*/
private interface SequenceAllocator
{
/**
* Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
*/
public Object getData(int t, int z, int c);
/**
* Validate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
*/
public void validateData(int t, int z, int c);
}
/**
* Virtual allocator dedicated to pre-allocated sequences
*/
private static class PreAllocatedAllocator implements SequenceAllocator
{
private Sequence _target;
public PreAllocatedAllocator(Sequence target)
{
_target = target;
}
@Override
public Object getData(int t, int z, int c)
{
return _target.getDataXY(t, z, c);
}
@Override
public void validateData(int t, int z, int c)
{
// Nothing to do
}
}
/**
* Allocator use for dynamically allocated sequences
*/
private static class OnFlyAllocator implements SequenceAllocator
{
private int _sizeZ;
private ImageAllocator[] _image;
public OnFlyAllocator(int sizeX, int sizeY, int sizeZ, int sizeT, int sizeC, DataType dataType, Sequence target)
{
int offset = 0;
int sizeZT = sizeT*sizeZ;
_sizeZ = sizeZ;
_image = new ImageAllocator[sizeZT];
for(int t=0; t<sizeT; ++t) {
for(int z=0; z<sizeZ; ++z) {
_image[offset] = new ImageAllocator(sizeX, sizeY, sizeC, dataType, target, t, z);
++offset;
}
}
}
@Override
public Object getData(int t, int z, int c)
{
return _image[z+_sizeZ*t].getData(c);
}
@Override
public void validateData(int t, int z, int c)
{
_image[z+_sizeZ*t].validateData(c);
}
}
/**
* Provide tools to build the several IcyBufferedImage objects that compose
* the final sequence
*/
private static class ImageAllocator
{
private int _sizeX ;
private int _sizeY ;
private DataType _dataType ;
private Sequence _target ;
private int _t ;
private int _z ;
private boolean _done ;
private boolean[] _available;
private Object[] _data ;
/**
* Constructor
*/
public ImageAllocator(int sizeX, int sizeY, int sizeC, DataType dataType, Sequence target, int t, int z)
{
_sizeX = sizeX ;
_sizeY = sizeY ;
_dataType = dataType;
_target = target ;
_t = t ;
_z = z ;
_done = false;
_data = Array2DUtil.createArray(_dataType, sizeC);
_available = new boolean[sizeC];
for(int c=0; c<sizeC; ++c) {
_available[c] = false;
}
}
/**
* Allocate the buffer corresponding to the given channel
*/
public Object getData(int c)
{
if(_data[c]==null) {
_data[c] = Array1DUtil.createArray(_dataType, _sizeX*_sizeY);
}
return _data[c];
}
/**
* Validate the buffer corresponding to the given channel.<br>
* If the results for all the channels are available, try to create the
* final buffered image
*/
public void validateData(int c)
{
// Mark the current channel as valid and available
_available[c] = true;
// Check whether the results for all the channels are available
for(boolean b : _available) {
if(!b) {
return;
}
}
// Synchronization is needed here, as several threads may try to create the
// final image at the same time
synchronized (this)
{
// Nothing to do if the image has been finalized during the synchronization
if(_done) {
return;
}
// Create the buffered image, and update the sequence
IcyBufferedImage image = new IcyBufferedImage(_sizeX, _sizeY, _data);
synchronized (_target) {
_target.setImage(_t, _z, image);
}
// Flag the image allocator as finalized
_done = true;
}
}
}
}