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}