001package icy.sequence;
002
003import icy.image.IcyBufferedImage;
004import icy.type.DataType;
005import icy.type.collection.array.Array1DUtil;
006import icy.type.collection.array.Array2DUtil;
007
008/**
009 * This class is intended for plugins that construct new sequences by:<br>
010 *  - first, specifying a data-type and a 5D-size for the sequence to be created<br>
011 *  - second, visiting each sample (x,y,z,t,c) to set its value.<br>
012 * <br>
013 * A typical example of such situation would be a plugin that take a sequence
014 * in input and return a copy of that sequence in output.
015 * 
016 * For such plugins, the SequenceBuilder class provides some tools to ease the
017 * creation of the output sequence, especially in a multi-thread context. To
018 * cut a long story short, here is an example of how the above-mentioned copy
019 * algorithm could be implemented with SequenceBuilder:<br>
020 * <br>
021 *  <pre>
022 *   // Input sequence
023 *   Sequence in = ...
024 *   
025 *   // Create a SequenceBuilder object, that will be in charge of allocating
026 *   // and feeding the output sequence.
027 *   SequenceBuilder builder = new SequenceBuilder
028 *   (
029 *     // The output sequence will have the same size and data-type than the input
030 *     in.getSizeX(), in.getSizeY(), in.getSizeZ(), in.getSizeT(), in.getSizeC(),
031 *     in.getDataType_()
032 *   );
033 *   
034 *   // Start the building process.
035 *   builder.beginUpdate();
036 *   try
037 *   {
038 *     // Here, the copy can be multi-threaded. No synchronization or mutex-locking
039 *     // is required on the side of the SequenceBuilder object.
040 *     
041 *     ...
042 *     
043 *     // Thread 1
044 *     forall(int t,z,c in listOfXYPlanesToBeDoneByThread1)
045 *     {
046 *       // Retrieve the array that will hold the pixel values corresponding to
047 *       // the XY-plane at coordinates (t, z, c) in the output sequence. If this
048 *       // object does not exist, it is created. The object returned by the
049 *       // method getData is actually an instance of byte[], short[], int[],
050 *       // float[] or double[], depending on the data-type specified in the
051 *       // SequenceBuilder constructor.
052 *       // 
053 *       // Remark: depending on the context, it may be more convenient to call
054 *       // builder.getDataAsDouble, builder.getDataAsByte, etc...
055 *       // 
056 *       Object buffer = builder.getData(t, z, c);
057 *       
058 *       // Execute the copy. For example:
059 *       { Array1DUtil.arrayToArray(in.getDataXY(t, z, c), buffer); }
060 *       
061 *       // Mark the array returned by the previous call to builder.getData(t, z, c)
062 *       // as ready to be incorporated in the output sequence.
063 *       // 
064 *       // Remark: there should only be one call to the method validateData
065 *       // per set of coordinates (t, z, c).
066 *       //
067 *       builder.validateData(t, z, c);
068 *     }
069 *     
070 *     ...
071 *     
072 *     // Thread 2
073 *     forall(int t,z,c in listOfXYPlanesToBeDoneByThread2) {
074 *       // etc...
075 *     }
076 *     
077 *     ...
078 *     
079 *   }
080 *   
081 *   // Finish the building process (each call to beginUpdate() must be followed
082 *   // by a call to endUpdate()).
083 *   finally {
084 *     builder.endUpdate();
085 *   }
086 *   
087 *   // Return the sequence that have been created by the SequenceBuilder object.
088 *   return builder.getResult();
089 * </pre>
090 * 
091 * @author Yoann Le Montagner
092 */
093public class SequenceBuilder
094{
095        private int               _sizeX    ;
096        private int               _sizeY    ;
097        private int               _sizeZ    ;
098        private int               _sizeT    ;
099        private int               _sizeC    ;
100        private DataType          _dataType ;
101        private Sequence          _result   ;
102        private SequenceAllocator _allocator;
103        
104        /**
105         * Allocate a new sequence that will have the given size, dataType and an empty name
106         */
107        public SequenceBuilder(int sizeX, int sizeY, int sizeZ, int sizeT, int sizeC, DataType dataType)
108        {
109                this(sizeX, sizeY, sizeZ, sizeT, sizeC, dataType, null);
110        }
111        
112        /**
113         * If non-null, the 'target' argument will be used to store the result of the
114         * sequence building process, and no new sequence will be created.<br>
115         * <br>
116         * Two situations may occur:
117         * <li>The sequence 'target' has the same size and data-type than specified by
118         * the arguments passed to the SequenceBuilder object. In that case, no new
119         * buffer/image allocation is performed, and the sequence is only modified
120         * through calls to the method Sequence.getDataXY().
121         * </li>
122         * <li>    
123         * Otherwise, the sequence 'target' will be completly cleared (through a
124         * call to the method Sequence.removeAllImages()) when first calling the
125         * method SequenceBuilder.beginUpdate().
126         * </li> 
127         */
128        public SequenceBuilder(int sizeX, int sizeY, int sizeZ, int sizeT, int sizeC, DataType dataType, Sequence target)
129        {
130                _sizeX     = sizeX   ;
131                _sizeY     = sizeY   ;
132                _sizeZ     = sizeZ   ;
133                _sizeT     = sizeT   ;
134                _sizeC     = sizeC   ;
135                _dataType  = dataType;
136                _result    = target==null ? new Sequence() : target;
137                _allocator = null;
138        }
139        
140        /**
141         * Size X of the sequence to be created
142         */
143        public int getSizeX()
144        {
145                return _sizeX;
146        }
147
148        /**
149         * Size Y of the sequence to be created
150         */
151        public int getSizeY()
152        {
153                return _sizeY;
154        }
155
156        /**
157         * Size Z of the sequence to be created
158         */
159        public int getSizeZ()
160        {
161                return _sizeZ;
162        }
163
164        /**
165         * Size T of the sequence to be created
166         */
167        public int getSizeT()
168        {
169                return _sizeT;
170        }
171
172        /**
173         * Size C of the sequence to be created
174         */
175        public int getSizeC()
176        {
177                return _sizeC;
178        }
179        
180        /**
181         * Data-type of the sequence to be created
182         */
183        public DataType getDataType()
184        {
185                return _dataType;
186        }
187        
188        /**
189         * Return the output sequence
190         */
191        public Sequence getResult()
192        {
193                return _result;
194        }
195        
196        /**
197         * Check if the sequence is pre-allocated, i.e. if it already has the proper
198         * size and data-type that was specified by the argument passed to the
199         * SequenceBuilder constructor 
200         */
201        public boolean isPreAllocated()
202        {
203                return
204                        
205                        // Match the size ...
206                        _result.getSizeX()==_sizeX &&
207                        _result.getSizeY()==_sizeY &&
208                        _result.getSizeZ()==_sizeZ &&
209                        _result.getSizeT()==_sizeT &&
210                        _result.getSizeC()==_sizeC &&
211                        
212                        // ... and the data-type in the case of non-empty sequences
213                        (_result.getDataType_()==_dataType
214                                || _sizeX==0
215                                || _sizeY==0
216                                || _sizeZ==0
217                                || _sizeT==0
218                                || _sizeC==0
219                        );
220        }
221        
222        /**
223         * Start building the sequence
224         * 
225         * @throws IllegalStateException if the object is already in an "updating" state
226         */
227        public void beginUpdate()
228        {
229                if(_allocator!=null) {
230                        throw new IllegalStateException("The SequenceBuilder object is already in an update state.");
231                }
232                _result.beginUpdate();
233                
234                // If the sequence has already the requested size and data-type, it can simply
235                // be modified in-place: there is no need for new buffer allocation.
236                if(isPreAllocated()) {
237                        _allocator = new PreAllocatedAllocator(_result);
238                }
239                
240                // Otherwise, the sequence is cleared, and dynamically rebuilt.
241                else {
242                        _result.removeAllImages();
243                        _allocator = new OnFlyAllocator(_sizeX, _sizeY, _sizeZ, _sizeT, _sizeC, _dataType, _result);
244                }
245        }
246        
247        /**
248         * Finish building the sequence.<br>
249         * Nothing happens if beginUpdate() has not been called previously
250         */
251        public void endUpdate()
252        {
253                if(_allocator==null) {
254                        return;
255                }
256                _result.endUpdate();
257                _allocator = null;
258        }
259        
260        /**
261         * Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
262         * @throws NullPointerException if the method beginUpdate() has not been called previously.
263         */
264        public double[] getDataAsDouble(int t, int z, int c)
265        {
266                return (double[])_allocator.getData(t, z, c);
267        }
268        
269        /**
270         * Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
271         * @throws NullPointerException if the method beginUpdate() has not been called previously.
272         */
273        public float[] getDataAsFloat(int t, int z, int c)
274        {
275                return (float[])_allocator.getData(t, z, c);
276        }
277        
278        /**
279         * Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
280         * @throws NullPointerException if the method beginUpdate() has not been called previously.
281         */
282        public byte[] getDataAsByte(int t, int z, int c)
283        {
284                return (byte[])_allocator.getData(t, z, c);
285        }
286        
287        /**
288         * Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
289         * @throws NullPointerException if the method beginUpdate() has not been called previously.
290         */
291        public short[] getDataAsShort(int t, int z, int c)
292        {
293                return (short[])_allocator.getData(t, z, c);
294        }
295        
296        /**
297         * Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
298         * @throws NullPointerException if the method beginUpdate() has not been called previously.
299         */
300        public int[] getDataAsInt(int t, int z, int c)
301        {
302                return (int[])_allocator.getData(t, z, c);
303        }
304        
305        /**
306         * Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
307         * @throws NullPointerException if the method beginUpdate() has not been called previously.
308         */
309        public Object getData(int t, int z, int c)
310        {
311                return _allocator.getData(t, z, c);
312        }
313        
314        /**
315         * Validate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
316         * @throws NullPointerException if the method beginUpdate() has not been called previously.
317         */
318        public void validateData(int t, int z, int c)
319        {
320                _allocator.validateData(t, z, c);
321        }
322
323        
324        /**
325         * Interface to access the data of the targeted sequence
326         */
327        private interface SequenceAllocator
328        {
329                /**
330                 * Retrieve or allocate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
331                 */
332                public Object getData(int t, int z, int c);
333                
334                /**
335                 * Validate the buffer for the XY plane corresponding to the given (t,z,c) coordinates
336                 */
337                public void validateData(int t, int z, int c);
338        }
339        
340        
341        /**
342         * Virtual allocator dedicated to pre-allocated sequences
343         */
344        private static class PreAllocatedAllocator implements SequenceAllocator
345        {
346                private Sequence _target;
347                
348                public PreAllocatedAllocator(Sequence target)
349                {
350                        _target = target;
351                }
352                
353                @Override
354                public Object getData(int t, int z, int c)
355                {
356                        return _target.getDataXY(t, z, c);
357                }
358
359                @Override
360                public void validateData(int t, int z, int c)
361                {
362                        // Nothing to do
363                }
364        }
365        
366        
367        /**
368         * Allocator use for dynamically allocated sequences
369         */
370        private static class OnFlyAllocator implements SequenceAllocator
371        {
372                private int              _sizeZ;
373                private ImageAllocator[] _image;
374                
375                public OnFlyAllocator(int sizeX, int sizeY, int sizeZ, int sizeT, int sizeC, DataType dataType, Sequence target)
376                {
377                        int offset = 0;
378                        int sizeZT = sizeT*sizeZ;
379                        _sizeZ = sizeZ;
380                        _image = new ImageAllocator[sizeZT];
381                        for(int t=0; t<sizeT; ++t) {
382                                for(int z=0; z<sizeZ; ++z) {
383                                        _image[offset] = new ImageAllocator(sizeX, sizeY, sizeC, dataType, target, t, z);
384                                        ++offset;
385                                }
386                        }
387                }
388
389                @Override
390                public Object getData(int t, int z, int c)
391                {
392                        return _image[z+_sizeZ*t].getData(c);
393                }
394
395                @Override
396                public void validateData(int t, int z, int c)
397                {
398                        _image[z+_sizeZ*t].validateData(c);
399                }
400        }
401        
402        
403        /**
404         * Provide tools to build the several IcyBufferedImage objects that compose
405         * the final sequence
406         */
407        private static class ImageAllocator
408        {
409                private int       _sizeX    ;
410                private int       _sizeY    ;
411                private DataType  _dataType ;
412                private Sequence  _target   ;
413                private int       _t        ;
414                private int       _z        ;
415                private boolean   _done     ;
416                private boolean[] _available;
417                private Object[]  _data     ;
418                
419                /**
420                 * Constructor
421                 */
422                public ImageAllocator(int sizeX, int sizeY, int sizeC, DataType dataType, Sequence target, int t, int z)
423                {
424                        _sizeX     = sizeX   ;
425                        _sizeY     = sizeY   ;
426                        _dataType  = dataType;
427                        _target    = target  ;
428                        _t         = t       ;
429                        _z         = z       ;
430                        _done      = false;
431                        _data      = Array2DUtil.createArray(_dataType, sizeC);
432                        _available = new boolean[sizeC];
433                        for(int c=0; c<sizeC; ++c) {
434                                _available[c] = false;
435                        }
436                }
437                
438                /**
439                 * Allocate the buffer corresponding to the given channel
440                 */
441                public Object getData(int c)
442                {
443                        if(_data[c]==null) {
444                                _data[c] = Array1DUtil.createArray(_dataType, _sizeX*_sizeY);
445                        }
446                        return _data[c];
447                }
448                
449                /**
450                 * Validate the buffer corresponding to the given channel.<br>
451                 * If the results for all the channels are available, try to create the
452                 * final buffered image
453                 */
454                public void validateData(int c)
455                {
456                        // Mark the current channel as valid and available
457                        _available[c] = true;
458                        
459                        // Check whether the results for all the channels are available
460                        for(boolean b : _available) {
461                                if(!b) {
462                                        return;
463                                }
464                        }
465                        
466                        // Synchronization is needed here, as several threads may try to create the
467                        // final image at the same time
468                        synchronized (this)
469                        {
470                                // Nothing to do if the image has been finalized during the synchronization
471                                if(_done) {
472                                        return;
473                                }
474                                
475                                // Create the buffered image, and update the sequence
476                                IcyBufferedImage image = new IcyBufferedImage(_sizeX, _sizeY, _data);
477                                synchronized (_target) {
478                                        _target.setImage(_t, _z, image);
479                                }
480                                
481                                // Flag the image allocator as finalized
482                                _done = true;
483                        }
484                }
485        }
486}