001/** 002 * 003 */ 004package icy.image; 005 006import icy.common.exception.UnsupportedFormatException; 007import icy.common.listener.ProgressListener; 008import icy.image.IcyBufferedImageUtil.FilterType; 009import icy.sequence.MetaDataUtil; 010import icy.system.SystemUtil; 011import icy.system.thread.Processor; 012import icy.type.DataType; 013import icy.type.collection.array.Array1DUtil; 014 015import java.awt.Dimension; 016import java.awt.Point; 017import java.awt.Rectangle; 018import java.io.IOException; 019import java.util.List; 020 021import ome.xml.meta.OMEXMLMetadata; 022 023/** 024 * Abstract implementation of the {@link ImageProvider} interface.<br> 025 * It provide methods wrapper so you only need implement one method the get your importer working.<br> 026 * But free feel to override more methods to provide better support and/or better performance. 027 * 028 * @author Stephane 029 */ 030public abstract class AbstractImageProvider implements ImageProvider 031{ 032 /** 033 * Used for multi thread tile image reading. 034 * 035 * @author Stephane 036 */ 037 class TilePixelsReader implements Runnable 038 { 039 final int series; 040 final int resolution; 041 final Rectangle region; 042 final int z; 043 final int t; 044 final int c; 045 final Object result; 046 final Dimension resDim; 047 final boolean signed; 048 049 boolean done; 050 boolean failed; 051 052 public TilePixelsReader(int series, int resolution, Rectangle region, int z, int t, int c, Object result, 053 Dimension resDim, boolean signed) 054 { 055 super(); 056 057 this.series = series; 058 this.resolution = resolution; 059 this.region = region; 060 this.z = z; 061 this.t = t; 062 this.c = c; 063 this.result = result; 064 this.resDim = resDim; 065 this.signed = signed; 066 done = false; 067 failed = false; 068 } 069 070 @Override 071 public void run() 072 { 073 if (Thread.interrupted()) 074 { 075 failed = true; 076 return; 077 } 078 079 try 080 { 081 // get image tile 082 final Object obj = getPixels(series, resolution, region, z, t, c); 083 // compute resolution divider 084 final int divider = (int) Math.pow(2, resolution); 085 // copy tile to image result 086 Array1DUtil.copyRect(obj, region.getSize(), null, result, resDim, 087 new Point(region.x / divider, region.y / divider), signed); 088 } 089 catch (Exception e) 090 { 091 failed = true; 092 } 093 094 done = true; 095 } 096 } 097 098 public static final int DEFAULT_THUMBNAIL_SIZE = 160; 099 100 // default implementation as ImageProvider interface changed 101 @SuppressWarnings("deprecation") 102 @Override 103 public OMEXMLMetadata getOMEXMLMetaData() throws UnsupportedFormatException, IOException 104 { 105 return getMetaData(); 106 } 107 108 // default implementation, override it if you need specific value for faster tile access 109 @Override 110 public int getTileWidth(int series) throws UnsupportedFormatException, IOException 111 { 112 return MetaDataUtil.getSizeX(getOMEXMLMetaData(), series); 113 } 114 115 // default implementation, override it if you need specific value for faster tile access 116 @Override 117 public int getTileHeight(int series) throws UnsupportedFormatException, IOException 118 { 119 final OMEXMLMetadata meta = getOMEXMLMetaData(); 120 final int sx = MetaDataUtil.getSizeX(meta, series); 121 122 if (sx == 0) 123 return 0; 124 125 // default implementation 126 final int maxHeight = (1024 * 1024) / sx; 127 final int sy = MetaDataUtil.getSizeY(meta, series); 128 129 return Math.min(maxHeight, sy); 130 } 131 132 // default implementation, override it if sub resolution is supported 133 @Override 134 public boolean isResolutionAvailable(int series, int resolution) throws UnsupportedFormatException, IOException 135 { 136 // by default we have only the original resolution 137 return resolution == 0; 138 } 139 140 // default implementation which use the getImage(..) method, override it for better support / performance 141 @Override 142 public IcyBufferedImage getThumbnail(int series) throws UnsupportedFormatException, IOException 143 { 144 final OMEXMLMetadata meta = getOMEXMLMetaData(); 145 int sx = MetaDataUtil.getSizeX(meta, series); 146 int sy = MetaDataUtil.getSizeY(meta, series); 147 final int sz = MetaDataUtil.getSizeZ(meta, series); 148 final int st = MetaDataUtil.getSizeT(meta, series); 149 150 // empty size --> return null 151 if ((sx == 0) || (sy == 0) || (sz == 0) || (st == 0)) 152 return null; 153 154 final double ratio = Math.min((double) DEFAULT_THUMBNAIL_SIZE / (double) sx, 155 (double) DEFAULT_THUMBNAIL_SIZE / (double) sy); 156 157 // final thumbnail size 158 final int tnx = (int) Math.round(sx * ratio); 159 final int tny = (int) Math.round(sy * ratio); 160 final int resolution = getResolutionFactor(sx, sy, DEFAULT_THUMBNAIL_SIZE); 161 162 // take middle image for thumbnail 163 final IcyBufferedImage image = getImage(series, resolution, sz / 2, st / 2); 164 165 // sx = result.getSizeX(); 166 // sy = result.getSizeY(); 167 // // wanted sub resolution of the image 168 // resolution = getResolutionFactor(sx, sy, DEFAULT_THUMBNAIL_SIZE); 169 170 // scale it to desired dimension (fast enough as here we have a small image) 171 final IcyBufferedImage thumbnail = IcyBufferedImageUtil.scale(image, tnx, tny, FilterType.BILINEAR); 172 173 // preserve colormaps 174 thumbnail.setColorMaps(image); 175 176 return thumbnail; 177 } 178 179 // default implementation: use the getImage(..) method then return data. 180 // It should be the opposite side for performance reason, override this method if possible 181 @Override 182 public Object getPixels(int series, int resolution, Rectangle rectangle, int z, int t, int c) 183 throws UnsupportedFormatException, IOException 184 { 185 return getImage(series, resolution, rectangle, z, t, c).getDataXY(0); 186 } 187 188 @Override 189 public IcyBufferedImage getImage(int series, int resolution, Rectangle rectangle, int z, int t) 190 throws UnsupportedFormatException, IOException 191 { 192 return getImage(series, resolution, rectangle, z, t, -1); 193 } 194 195 // default implementation using the region getImage(..) method, better to override 196 @Override 197 public IcyBufferedImage getImage(int series, int resolution, int z, int t, int c) 198 throws UnsupportedFormatException, IOException 199 { 200 return getImage(series, resolution, null, z, t, c); 201 } 202 203 @Override 204 public IcyBufferedImage getImage(int series, int resolution, int z, int t) 205 throws UnsupportedFormatException, IOException 206 { 207 return getImage(series, resolution, null, z, t, -1); 208 } 209 210 @Override 211 public IcyBufferedImage getImage(int series, int z, int t) throws UnsupportedFormatException, IOException 212 { 213 return getImage(series, 0, null, z, t, -1); 214 } 215 216 @Override 217 public IcyBufferedImage getImage(int z, int t) throws UnsupportedFormatException, IOException 218 { 219 return getImage(0, 0, null, z, t, -1); 220 } 221 222 /** 223 * Returns the pixels located at specified position using tile by tile reading (if supported by the importer).<br> 224 * This method is useful to read a sub resolution of a very large image which cannot fit in memory and also to take 225 * advantage of multi threading. 226 * 227 * @param series 228 * Series index for multi series image (use 0 if unsure). 229 * @param resolution 230 * Wanted resolution level for the image (use 0 if unsure).<br> 231 * The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br> 232 * So for instance level 0 is the default image resolution while level 1 is base image 233 * resolution / 2 and so on... 234 * @param region 235 * The 2D region we want to retrieve (considering the original image resolution).<br> 236 * If set to <code>null</code> then the whole image is returned. 237 * @param z 238 * Z position of the image (slice) we want retrieve 239 * @param t 240 * T position of the image (frame) we want retrieve 241 * @param c 242 * C position of the image (channel) we want retrieve (-1 not accepted here).<br> 243 * @param tileW 244 * width of the tile (better to use a multiple of 2).<br> 245 * If <= 0 then tile width is automatically determined 246 * @param tileH 247 * height of the tile (better to use a multiple of 2).<br> 248 * If <= 0 then tile height is automatically determined 249 * @param listener 250 * Progression listener 251 * @see #getPixels(int, int, Rectangle, int, int, int) 252 */ 253 public Object getPixelsByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW, 254 int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException 255 { 256 final OMEXMLMetadata meta = getOMEXMLMetaData(); 257 final int sizeX = MetaDataUtil.getSizeX(meta, series); 258 final int sizeY = MetaDataUtil.getSizeY(meta, series); 259 final DataType type = MetaDataUtil.getDataType(meta, series); 260 final boolean signed = type.isSigned(); 261 262 // define XY region to load 263 Rectangle adjRegion = new Rectangle(sizeX, sizeY); 264 if (region != null) 265 adjRegion = adjRegion.intersection(region); 266 267 // allocate result 268 final Dimension resDim = new Dimension(adjRegion.width >> resolution, adjRegion.height >> resolution); 269 final Object result = Array1DUtil.createArray(type, resDim.width * resDim.height); 270 271 // create processor 272 final Processor readerProcessor = new Processor(Math.max(1, SystemUtil.getNumberOfCPUs() - 1)); 273 readerProcessor.setThreadName("Image tile reader"); 274 275 int tw = tileW; 276 int th = tileH; 277 278 // adjust tile size if needed 279 if (tw <= 0) 280 tw = getTileWidth(series); 281 if (tw <= 0) 282 tw = 512; 283 if (th <= 0) 284 th = getTileHeight(series); 285 if (th <= 0) 286 th = 512; 287 288 final List<Rectangle> tiles = ImageUtil.getTileList(adjRegion, tw, th); 289 290 // submit all tasks 291 for (Rectangle tile : tiles) 292 { 293 // wait a bit if the process queue is full 294 while (readerProcessor.isFull()) 295 { 296 try 297 { 298 Thread.sleep(0); 299 } 300 catch (InterruptedException e) 301 { 302 // interrupt all processes 303 readerProcessor.shutdownNow(); 304 break; 305 } 306 } 307 308 // submit next task 309 readerProcessor.submit(new TilePixelsReader(series, resolution, tile.intersection(adjRegion), z, t, c, 310 result, resDim, signed)); 311 312 // display progression 313 if (listener != null) 314 { 315 // process cancel requested ? 316 if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 317 { 318 // interrupt processes 319 readerProcessor.shutdownNow(); 320 break; 321 } 322 } 323 } 324 325 // wait for completion 326 while (readerProcessor.isProcessing()) 327 { 328 try 329 { 330 Thread.sleep(1); 331 } 332 catch (InterruptedException e) 333 { 334 // interrupt all processes 335 readerProcessor.shutdownNow(); 336 break; 337 } 338 339 // display progression 340 if (listener != null) 341 { 342 // process cancel requested ? 343 if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 344 { 345 // interrupt processes 346 readerProcessor.shutdownNow(); 347 break; 348 } 349 } 350 } 351 352 // last wait for completion just in case we were interrupted 353 readerProcessor.waitAll(); 354 355 return result; 356 } 357 358 // /** 359 // * Returns the image located at specified position using tile by tile reading (if supported by the importer).<br> 360 // * This method is useful to read a sub resolution of a very large image which cannot fit in memory and also to take 361 // * advantage of multi threading. 362 // * 363 // * @param series 364 // * Series index for multi series image (use 0 if unsure). 365 // * @param resolution 366 // * Wanted resolution level for the image (use 0 if unsure).<br> 367 // * The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br> 368 // * So for instance level 0 is the default image resolution while level 1 is base image 369 // * resolution / 2 and so on... 370 // * @param region 371 // * The 2D region we want to retrieve (considering the original image resolution).<br> 372 // * If set to <code>null</code> then the whole image is returned. 373 // * @param z 374 // * Z position of the image (slice) we want retrieve 375 // * @param t 376 // * T position of the image (frame) we want retrieve 377 // * @param c 378 // * C position of the image (channel) we want retrieve.<br> 379 // * -1 is a special value meaning we want all channel. 380 // * @param tileW 381 // * width of the tile (better to use a multiple of 2) 382 // * @param tileH 383 // * height of the tile (better to use a multiple of 2) 384 // * @param listener 385 // * Progression listener 386 // * @see #getImage(int, int, Rectangle, int, int, int) 387 // */ 388 // public IcyBufferedImage getImageByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW, 389 // int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException 390 // { 391 // final OMEXMLMetadata meta = getOMEXMLMetaData(); 392 // final int sizeX = MetaDataUtil.getSizeX(meta, series); 393 // final int sizeY = MetaDataUtil.getSizeY(meta, series); 394 // 395 // // TODO: handle rect 396 // 397 // // resolution divider 398 // final int divider = (int) Math.pow(2, resolution); 399 // // allocate result 400 // final IcyBufferedImage result = new IcyBufferedImage(sizeX / divider, sizeY / divider, 401 // MetaDataUtil.getSizeC(meta, series), MetaDataUtil.getDataType(meta, series)); 402 // // create processor 403 // final Processor readerProcessor = new Processor(Math.max(1, SystemUtil.getNumberOfCPUs() - 1)); 404 // 405 // readerProcessor.setThreadName("Image tile reader"); 406 // result.beginUpdate(); 407 // 408 // try 409 // { 410 // final List<Rectangle> tiles = ImageUtil.getTileList(sizeX, sizeY, tileW, tileH); 411 // 412 // // submit all tasks 413 // for (Rectangle tile : tiles) 414 // { 415 // // wait a bit if the process queue is full 416 // while (readerProcessor.isFull()) 417 // { 418 // try 419 // { 420 // Thread.sleep(0); 421 // } 422 // catch (InterruptedException e) 423 // { 424 // // interrupt all processes 425 // readerProcessor.shutdownNow(); 426 // break; 427 // } 428 // } 429 // 430 // // submit next task 431 // readerProcessor.submit(new TileImageReader(series, resolution, tile, z, t, c, result)); 432 // 433 // // display progression 434 // if (listener != null) 435 // { 436 // // process cancel requested ? 437 // if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 438 // { 439 // // interrupt processes 440 // readerProcessor.shutdownNow(); 441 // break; 442 // } 443 // } 444 // } 445 // 446 // // wait for completion 447 // while (readerProcessor.isProcessing()) 448 // { 449 // try 450 // { 451 // Thread.sleep(1); 452 // } 453 // catch (InterruptedException e) 454 // { 455 // // interrupt all processes 456 // readerProcessor.shutdownNow(); 457 // break; 458 // } 459 // 460 // // display progression 461 // if (listener != null) 462 // { 463 // // process cancel requested ? 464 // if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 465 // { 466 // // interrupt processes 467 // readerProcessor.shutdownNow(); 468 // break; 469 // } 470 // } 471 // } 472 // 473 // // last wait for completion just in case we were interrupted 474 // readerProcessor.waitAll(); 475 // } 476 // finally 477 // { 478 // result.endUpdate(); 479 // } 480 // 481 // return result; 482 // } 483 484 // /** 485 // * @deprecated Use {@link #getImageByTile(int, int, Rectangle, int, int, int, int, int, ProgressListener)} instead. 486 // */ 487 // @Deprecated 488 // public IcyBufferedImage getImageByTile(int series, int resolution, int z, int t, int c, int tileW, int tileH, 489 // ProgressListener listener) throws UnsupportedFormatException, IOException 490 // { 491 // return getImageByTile(series, resolution, null, z, t, c, tileW, tileH, listener); 492 // } 493 // 494 // /** 495 // * @deprecated USe {@link ImageUtil#getTileList(int, int, int, int)} instead 496 // */ 497 // @Deprecated 498 // public static List<Rectangle> getTileList(int sizeX, int sizeY, int tileW, int tileH) 499 // { 500 // return ImageUtil.getTileList(sizeX, sizeY, tileW, tileH); 501 // } 502 503 /** 504 * Returns the sub image resolution which best suit to the desired size. 505 * 506 * @param sizeX 507 * original image width 508 * @param sizeY 509 * original image height 510 * @param wantedSize 511 * wanted size (for the maximum dimension) 512 * @return resolution ratio<br> 513 * 0 = original resolution<br> 514 * 1 = (original resolution / 2)<br> 515 * 2 = (original resolution / 4) 516 */ 517 public static int getResolutionFactor(int sizeX, int sizeY, int wantedSize) 518 { 519 int sx = sizeX / 2; 520 int sy = sizeY / 2; 521 int result = 0; 522 523 while ((sx > wantedSize) || (sy > wantedSize)) 524 { 525 sx /= 2; 526 sy /= 2; 527 result++; 528 } 529 530 return result; 531 } 532 533 /** 534 * Returns the image resolution that best suit to the size resolution. 535 * 536 * @param series 537 * Series index for multi series image (use 0 if unsure). 538 * @param wantedSize 539 * wanted size (for the maximum dimension) 540 * @return resolution ratio<br> 541 * 0 = original resolution<br> 542 * 1 = (original resolution / 2)<br> 543 * 2 = (original resolution / 4) 544 * @throws IOException 545 * @throws UnsupportedFormatException 546 */ 547 public int getResolutionFactor(int series, int wantedSize) throws UnsupportedFormatException, IOException 548 { 549 final OMEXMLMetadata meta = getOMEXMLMetaData(); 550 return getResolutionFactor(MetaDataUtil.getSizeX(meta, series), MetaDataUtil.getSizeY(meta, series), 551 wantedSize); 552 } 553}