001/* 002 * Copyright 2010-2018 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.file; 020 021import java.awt.Point; 022import java.awt.Rectangle; 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import javax.swing.filechooser.FileFilter; 031 032import icy.common.exception.UnsupportedFormatException; 033import icy.file.SequenceFileSticher.SequenceFileGroup; 034import icy.file.SequenceFileSticher.SequenceIdent; 035import icy.file.SequenceFileSticher.SequencePosition; 036import icy.file.SequenceFileSticher.SequenceType; 037import icy.gui.dialog.LoaderDialog; 038import icy.image.AbstractImageProvider; 039import icy.image.IcyBufferedImage; 040import icy.image.ImageUtil; 041import icy.image.colormap.IcyColorMap; 042import icy.sequence.MetaDataUtil; 043import icy.system.IcyExceptionHandler; 044import icy.type.collection.CollectionUtil; 045import icy.type.collection.array.Array1DUtil; 046import icy.util.OMEUtil; 047import icy.util.StringUtil; 048import loci.formats.FormatTools; 049import loci.formats.MetadataTools; 050import loci.formats.ome.OMEXMLMetadataImpl; 051import ome.xml.meta.OMEXMLMetadata; 052import ome.xml.model.Channel; 053import ome.xml.model.Pixels; 054import ome.xml.model.Plane; 055import plugins.kernel.importer.LociImporterPlugin; 056 057/** 058 * Special importer able to group a list of path ({@link SequenceFileGroup}) to build a single Sequence out of it.<br> 059 * Note that this importer is limited to single series group, we don't allow group mixing several series. 060 */ 061public class SequenceFileGroupImporter extends AbstractImageProvider implements SequenceFileImporter 062{ 063 static final int MAX_IMPORTER = 16; 064 065 class FileCursor 066 { 067 public final SequencePosition position; 068 public final int index; 069 public final int internalZ; 070 public final int internalT; 071 public final int internalC; 072 073 public FileCursor(SequencePosition position, int index, int z, int t, int c) 074 { 075 super(); 076 077 this.position = position; 078 this.index = index; 079 this.internalZ = z; 080 this.internalT = t; 081 this.internalC = c; 082 } 083 } 084 085 class TileIndex 086 { 087 public final Rectangle region; 088 public final int index; 089 090 public TileIndex(Rectangle region, int index) 091 { 092 super(); 093 094 this.region = region; 095 this.index = index; 096 } 097 } 098 099 protected SequenceFileGroup currentGroup; 100 protected OMEXMLMetadata currentMetadata; 101 // position index array (stored in XYZTC order) to quickly find an image given its (XY)ZTC position 102 protected SequencePosition[] positions; 103 104 protected int openFlags; 105 106 // internals 107 protected int indYMul; 108 protected int indZMul; 109 protected int indTMul; 110 protected int indCMul; 111 protected int indSMul; 112 113 /** 114 * Shared importer for multi threading 115 */ 116 protected final Map<String, SequenceFileImporter> importersPool; 117 118 public SequenceFileGroupImporter() 119 { 120 super(); 121 122 currentGroup = null; 123 currentMetadata = null; 124 importersPool = new HashMap<String, SequenceFileImporter>(); 125 } 126 127 @Override 128 public boolean acceptFile(String path) 129 { 130 boolean result = false; 131 132 if ((currentGroup != null) && (currentGroup.ident.importer != null)) 133 result = currentGroup.ident.importer.acceptFile(path); 134 135 // use Loader to test it 136 if (!result) 137 result = Loader.isSupportedImageFile(path); 138 139 return result; 140 } 141 142 @Override 143 public List<FileFilter> getFileFilters() 144 { 145 // return a generic image file filter here 146 final List<FileFilter> result = new ArrayList<FileFilter>(); 147 result.add(LoaderDialog.allImagesFileFilter); 148 return result; 149 } 150 151 /** 152 * @return <code>true</code> if a opened is currently opened 153 */ 154 public boolean isOpen() 155 { 156 return getOpenedGroup() != null; 157 } 158 159 /** 160 * Return the current / last loaded image without the image group 161 */ 162 @Override 163 public String getOpened() 164 { 165 if (currentGroup != null) 166 return currentGroup.ident.base; 167 168 return null; 169 } 170 171 /** 172 * @return current opened group 173 */ 174 public SequenceFileGroup getOpenedGroup() 175 { 176 return currentGroup; 177 } 178 179 /** 180 * @deprecated Better to use {@link #open(Collection, int)} or {@link #open(SequenceFileGroup, int)} for this importer 181 */ 182 @Deprecated 183 @Override 184 public boolean open(String path, int flags) throws UnsupportedFormatException, IOException 185 { 186 open(CollectionUtil.createArrayList(path), flags); 187 188 return true; 189 } 190 191 /** 192 * Open a list of ids 193 * 194 * @param ids 195 */ 196 public void open(Collection<String> ids, int flags) 197 { 198 open(SequenceFileSticher.groupFiles(null, ids, true, null), flags); 199 } 200 201 /** 202 * Open a SequenceFileGroup 203 */ 204 public void open(SequenceFileGroup group, int flags) 205 { 206 try 207 { 208 close(); 209 } 210 catch (IOException e) 211 { 212 // should not prevent from opening 213 IcyExceptionHandler.showErrorMessage(e, true, true); 214 } 215 216 // can't open null group 217 if (group == null) 218 return; 219 220 currentGroup = group; 221 // we need to rebuild metadata 222 currentMetadata = null; 223 224 // store flags for later 225 openFlags = flags; 226 227 buildIndexes(); 228 } 229 230 /** 231 * Open the specified importer to the given path (internal use only). 232 * 233 * @throws IOException 234 * @throws UnsupportedFormatException 235 * @see #releaseImporter(String, SequenceFileImporter) 236 */ 237 protected boolean openImporter(SequenceFileImporter result, String path) 238 throws UnsupportedFormatException, IOException 239 { 240 // importer is already opened ? --> close it first 241 if (!StringUtil.isEmpty(result.getOpened())) 242 result.close(); 243 244 // disable grouping for LOCI importer as we do it here 245 if (result instanceof LociImporterPlugin) 246 ((LociImporterPlugin) result).setGroupFiles(false); 247 248 return result.open(path, openFlags); 249 } 250 251 /** 252 * Create and open a new importer for the given path (internal use only) 253 */ 254 protected SequenceFileImporter createImporter(String path) 255 throws InstantiationException, IllegalAccessException, UnsupportedFormatException, IOException 256 { 257 if (!isOpen()) 258 return null; 259 260 // we should always use the same importer for a file group 261 final SequenceFileImporter result = currentGroup.ident.importer.getClass().newInstance(); 262 263 // open the importer for given path 264 openImporter(result, path); 265 266 // // try to use the default group importer 267 // SequenceFileImporter result = currentGroup.ident.importer; 268 // 269 // // can use default one ? --> clone it 270 // if ((result != null) && result.acceptFile(path)) 271 // result = result.getClass().newInstance(); 272 // else 273 // { 274 // // get importer for this path 275 // result = Loader.getSequenceFileImporter(path, true); 276 // } 277 // 278 // // try to open it 279 // if (result != null) 280 // openImporter(result, path); 281 282 return result; 283 } 284 285 /** 286 * Return the path of the first available importer (internal use only) 287 * 288 * @throws IOException 289 * @throws UnsupportedFormatException 290 */ 291 protected String getFirstImporterPath() throws IOException, UnsupportedFormatException 292 { 293 synchronized (importersPool) 294 { 295 if (!importersPool.isEmpty()) 296 return importersPool.keySet().iterator().next(); 297 } 298 299 for (SequencePosition pos : currentGroup.positions) 300 { 301 if (pos != null) 302 return pos.getPath(); 303 } 304 305 return null; 306 } 307 308 /** 309 * Return an opened importer for given path.<br> 310 * Note that you should call {@link #releaseImporter(String, SequenceFileImporter)} when you're done with it. 311 * 312 * @throws IOException 313 * @throws UnsupportedFormatException 314 * @see #releaseImporter(String, SequenceFileImporter) 315 */ 316 @SuppressWarnings("resource") 317 public SequenceFileImporter getImporter(String path) throws IOException, UnsupportedFormatException 318 { 319 if (StringUtil.isEmpty(path)) 320 return null; 321 322 try 323 { 324 final int numImporter; 325 SequenceFileImporter result; 326 327 synchronized (importersPool) 328 { 329 numImporter = importersPool.size(); 330 result = importersPool.remove(path); 331 } 332 333 // no available importer for this path ? 334 if (result == null) 335 { 336 // we have already enough importers (we don't want to create too much of them) 337 if (numImporter >= MAX_IMPORTER) 338 { 339 // recycle first importer found one 340 result = getImporter(getFirstImporterPath()); 341 342 // correctly 343 if (result != null) 344 openImporter(result, path); 345 } 346 347 // need to create a new importer 348 if (result == null) 349 result = createImporter(path); 350 } 351 352 return result; 353 } 354 catch (InstantiationException e) 355 { 356 // better to re-throw as RuntimeException 357 throw new RuntimeException(e.getMessage()); 358 } 359 catch (IllegalAccessException e) 360 { 361 // better to re-throw as RuntimeException 362 throw new RuntimeException(e.getMessage()); 363 } 364 } 365 366 /** 367 * Release the importer obtained through {@link #getImporter(String)} to the importer pool. 368 * 369 * @see #getImporter(String) 370 */ 371 public void releaseImporter(String path, SequenceFileImporter importer) 372 { 373 synchronized (importersPool) 374 { 375 // it's better to specify path instead of using SequenceFileImporter.getOpened() as internally format can change 376 importersPool.put(path, importer); 377 } 378 } 379 380 protected void buildIndexes() 381 { 382 final SequenceFileGroup group = currentGroup; 383 final SequenceType baseType = group.ident.baseType; 384 385 // compute index multipliers 386 indYMul = group.totalSizeX / baseType.sizeX; 387 indZMul = indYMul * (group.totalSizeY / baseType.sizeY); 388 indTMul = indZMul * (group.totalSizeZ / baseType.sizeZ); 389 indCMul = indTMul * (group.totalSizeT / baseType.sizeT); 390 391 final int totalLen = indCMul * (group.totalSizeC / baseType.sizeC); 392 393 // allocate index array 394 positions = new SequencePosition[totalLen]; 395 396 // find maxX and maxY 397 for (SequencePosition pos : currentGroup.positions) 398 { 399 // compute index 400 final int ind = getIdIndex(pos.getIndexX(), pos.getIndexY(), pos.getIndexZ(), pos.getIndexT(), 401 pos.getIndexC()); 402 // set path for this index 403 positions[ind] = pos; 404 } 405 } 406 407 /** 408 * @return <code>true</code> if this image group requires several images to build a full XY plan image. 409 */ 410 public boolean isStitchedImage() 411 { 412 if (!isOpen()) 413 return false; 414 415 // if total size XY != single image size XY --> we have a stitched image 416 return (currentGroup.totalSizeX != currentGroup.ident.baseType.sizeX) 417 || (currentGroup.totalSizeY != currentGroup.ident.baseType.sizeY); 418 } 419 420 /** 421 * @return cursor for the given [z,t,c] position 422 */ 423 protected FileCursor getCursor(int z, int t, int c) 424 { 425 final SequenceType baseType = currentGroup.ident.baseType; 426 427 final int externalZ = z / baseType.sizeZ; 428 final int externalT = t / baseType.sizeT; 429 final int externalC = c / baseType.sizeC; 430 431 final int internalZ = z % baseType.sizeZ; 432 final int internalT = t % baseType.sizeT; 433 final int internalC = c % baseType.sizeC; 434 435 final int idInd = getIdIndex(0, 0, externalZ, externalT, externalC); 436 437 return new FileCursor(positions[idInd], idInd, internalZ, internalT, internalC); 438 } 439 440 /** 441 * @return all required TileIndex for the given XY region 442 */ 443 protected List<TileIndex> getTileIndexes(Rectangle xyRegion) 444 { 445 if (!isStitchedImage()) 446 return CollectionUtil.createArrayList(new TileIndex(xyRegion, 0)); 447 448 final List<TileIndex> result = new ArrayList<TileIndex>(); 449 450 final SequenceType baseType = currentGroup.ident.baseType; 451 final int tsx = baseType.sizeX; 452 final int tsy = baseType.sizeY; 453 454 // get tile list 455 final List<Rectangle> tiles = ImageUtil.getTileList(xyRegion, tsx, tsy); 456 457 // each tile represent a single image 458 for (Rectangle tile : tiles) 459 { 460 // find the image index 461 final int x = tile.x / tsx; 462 final int y = tile.y / tsy; 463 464 // add tile info 465 result.add(new TileIndex(tile.intersection(xyRegion), x + (y * indYMul))); 466 } 467 468 return result; 469 } 470 471 /** 472 * @return file path for the given [z,t,c] position 473 */ 474 public String getPath(int z, int t, int c) 475 { 476 if (!isOpen()) 477 return ""; 478 479 final SequencePosition pos = getCursor(z, t, c).position; 480 481 if (pos != null) 482 return pos.getPath(); 483 484 return ""; 485 } 486 487 protected int getIdIndex(int x, int y, int z, int t, int c) 488 { 489 return x + (y * indYMul) + (z * indZMul) + (t * indTMul) + (c * indCMul); 490 } 491 492 /** 493 * Close all opened internals importer without closing the Group Importer itself (importer remaing active) 494 */ 495 public void closeInternalsImporters() throws IOException 496 { 497 synchronized (importersPool) 498 { 499 // close all importers 500 for (SequenceFileImporter imp : importersPool.values()) 501 imp.close(); 502 503 importersPool.clear(); 504 } 505 } 506 507 @Override 508 public void close() throws IOException 509 { 510 closeInternalsImporters(); 511 512 // release position indexes array 513 positions = null; 514 // release everything 515 currentMetadata = null; 516 currentGroup = null; 517 } 518 519 @SuppressWarnings("deprecation") 520 @Override 521 public OMEXMLMetadataImpl getMetaData() throws UnsupportedFormatException, IOException 522 { 523 return (OMEXMLMetadataImpl) getOMEXMLMetaData(); 524 } 525 526 @Override 527 public OMEXMLMetadata getOMEXMLMetaData() throws UnsupportedFormatException, IOException 528 { 529 if (!isOpen()) 530 return null; 531 532 // need to build metadata ? 533 if (currentMetadata == null) 534 currentMetadata = buildMetaData(); 535 536 return currentMetadata; 537 } 538 539 @SuppressWarnings({"static-access", "resource"}) 540 protected OMEXMLMetadata buildMetaData() throws UnsupportedFormatException, IOException 541 { 542 final SequenceFileGroup group = currentGroup; 543 final SequenceIdent ident = group.ident; 544 final SequenceType baseType = ident.baseType; 545 final String name = FileUtil.getFileName(ident.base, false); 546 final OMEXMLMetadata result = MetaDataUtil.createMetadata(name); 547 548 // minimum metadata 549 MetaDataUtil.setMetaData(result, currentGroup.totalSizeX, currentGroup.totalSizeY, currentGroup.totalSizeC, 550 currentGroup.totalSizeZ, currentGroup.totalSizeT, baseType.dataType, true); 551 // pixel size & time interval 552 if (baseType.pixelSizeX > 0d) 553 MetaDataUtil.setPixelSizeX(result, 0, baseType.pixelSizeX); 554 if (baseType.pixelSizeY > 0d) 555 MetaDataUtil.setPixelSizeY(result, 0, baseType.pixelSizeY); 556 if (baseType.pixelSizeZ > 0d) 557 MetaDataUtil.setPixelSizeZ(result, 0, baseType.pixelSizeZ); 558 if (baseType.timeInterval > 0d) 559 MetaDataUtil.setTimeInterval(result, 0, baseType.timeInterval); 560 561 // get OME Pixels object (easier to deal with) 562 final Pixels resultPixels = MetaDataUtil.getPixels(result, 0); 563 // allocate Plane objects 564 MetaDataUtil.ensurePlane(resultPixels, group.totalSizeT - 1, group.totalSizeZ - 1, group.totalSizeC - 1); 565 566 // default metadata 567 if ((openFlags & SequenceFileImporter.FLAG_METADATA_MASK) != SequenceFileImporter.FLAG_METADATA_MINIMUM) 568 { 569 // iterate over all dimension 570 for (int c = 0; c < group.totalSizeC; c++) 571 { 572 for (int z = 0; z < group.totalSizeZ; z++) 573 { 574 for (int t = 0; t < group.totalSizeT; t++) 575 { 576 final FileCursor cursor = getCursor(z, t, c); 577 final SequencePosition position = cursor.position; 578 579 // do we have an image for this position ? 580 if (position != null) 581 { 582 // first ZT plane ? --> fill channel data 583 if ((z == 0) && (t == 0)) 584 { 585 final SequenceFileImporter imp = getImporter(position.getPath()); 586 587 if (imp != null) 588 { 589 try 590 { 591 // get metadata for this file 592 final OMEXMLMetadata meta = imp.getOMEXMLMetaData(); 593 // get OME Pixels object (easier to deal with) 594 final Pixels metaPixels = MetaDataUtil.getPixels(meta, 0); 595 596 final Channel metaChannel; 597 598 // get origin channel (or create it if needed) 599 if (cursor.internalC < metaPixels.sizeOfChannelList()) 600 metaChannel = metaPixels.getChannel(cursor.internalC); 601 else 602 metaChannel = new Channel(); 603 604 // get Channel object directly from source metadata 605 resultPixels.setChannel(c, metaChannel); 606 // change the path 607 result.setChannelID(MetadataTools.createLSID("Channel", 0, c), 0, c); 608 } 609 finally 610 { 611 releaseImporter(position.getPath(), imp); 612 } 613 } 614 } 615 616 Plane metaPlane = null; 617 618 // first ZT plane or supplementary metadata wanted ? --> get original plane metadata 619 if (((z == 0) && (t == 0)) || ((openFlags 620 & SequenceFileImporter.FLAG_METADATA_MASK) == SequenceFileImporter.FLAG_METADATA_ALL)) 621 { 622 final SequenceFileImporter imp = getImporter(position.getPath()); 623 624 if (imp != null) 625 { 626 try 627 { 628 // get metadata for this file 629 final OMEXMLMetadata meta = imp.getOMEXMLMetaData(); 630 // get OME Pixels object (easier to deal with) 631 final Pixels metaPixels = MetaDataUtil.getPixels(meta, 0); 632 633 // retrieve the original Plane object 634 metaPlane = MetaDataUtil.getPlane(metaPixels, cursor.internalT, 635 cursor.internalZ, cursor.internalC); 636 637 if (metaPlane != null) 638 { 639 // remove linked annotation from plane 640 for (int a = (metaPlane.sizeOfLinkedAnnotationList() - 1); a >= 0; a--) 641 metaPlane.unlinkAnnotation(metaPlane.getLinkedAnnotation(a)); 642 } 643 } 644 finally 645 { 646 releaseImporter(position.getPath(), imp); 647 } 648 } 649 } 650 651 // plane not retrieved from original metadata ? --> create a new empty one 652 if (metaPlane == null) 653 metaPlane = new Plane(); 654 655 // retrieve plane index (use FormatsTools to get it as metadata is not yet complete here) 656 final int resultPlaneInd = FormatTools.getIndex( 657 result.getPixelsDimensionOrder(0).getValue(), group.totalSizeZ, group.totalSizeC, 658 group.totalSizeT, group.totalSizeZ * group.totalSizeC * group.totalSizeT, z, c, t); 659 660 // set plane 661 resultPixels.setPlane(resultPlaneInd, metaPlane); 662 663 // adjust CZT info 664 metaPlane.setTheC(OMEUtil.getNonNegativeInteger(c)); 665 metaPlane.setTheZ(OMEUtil.getNonNegativeInteger(z)); 666 metaPlane.setTheT(OMEUtil.getNonNegativeInteger(t)); 667 } 668 } 669 } 670 } 671 } 672 673 return result; 674 } 675 676 @SuppressWarnings("resource") 677 @Override 678 public int getTileHeight(int series) throws UnsupportedFormatException, IOException 679 { 680 if (!isOpen()) 681 return 0; 682 683 // total size Y 684 final int tsy = currentGroup.totalSizeY; 685 // image size Y 686 final int isy = currentGroup.ident.baseType.sizeY; 687 688 // single image for XY plane or stitched image with sizeY > 2048 --> use image tile height 689 if ((tsy == isy) || (isy > 2048)) 690 { 691 final String path = getFirstImporterPath(); 692 final SequenceFileImporter imp = getImporter(path); 693 694 if (imp != null) 695 { 696 try 697 { 698 return imp.getTileHeight(series); 699 } 700 finally 701 { 702 releaseImporter(path, imp); 703 } 704 } 705 } 706 707 // use image size Y as tile height 708 return isy; 709 } 710 711 @SuppressWarnings("resource") 712 @Override 713 public int getTileWidth(int series) throws UnsupportedFormatException, IOException 714 { 715 if (!isOpen()) 716 return 0; 717 718 // total size X 719 final int tsx = currentGroup.totalSizeX; 720 // image size X 721 final int isx = currentGroup.ident.baseType.sizeX; 722 723 // single image for XY plane or stitched image with sizeX > 2048 --> use image tile width 724 if ((tsx == isx) || (isx > 2048)) 725 { 726 final String path = getFirstImporterPath(); 727 final SequenceFileImporter imp = getImporter(path); 728 729 if (imp != null) 730 { 731 try 732 { 733 return imp.getTileWidth(series); 734 } 735 finally 736 { 737 releaseImporter(path, imp); 738 } 739 } 740 } 741 742 // use image size X as tile width 743 return isx; 744 } 745 746 // internal use only 747 @SuppressWarnings("resource") 748 private Object getPixelsInternal(SequencePosition pos, int series, int resolution, Rectangle region, int z, int t, 749 int c) throws UnsupportedFormatException, IOException 750 { 751 if (pos == null) 752 { 753 final SequenceType bt = currentGroup.ident.baseType; 754 System.err.println("SequenceIdGroupImporter.getPixelsInternal: no image for tile [" + (region.x / bt.sizeX) 755 + "," + (region.y / bt.sizeY) + "] !"); 756 return null; 757 } 758 759 // get importer for this image 760 final SequenceFileImporter imp = getImporter(pos.getPath()); 761 762 if (imp == null) 763 { 764 System.err.println("SequenceIdGroupImporter.getPixelsInternal: cannot get importer for image '" 765 + pos.getPath() + "' !"); 766 return null; 767 } 768 769 try 770 { 771 // get pixels from importer (it actually represents a tile of the resulting image) 772 return imp.getPixels(series, resolution, region, z, t, c); 773 } 774 finally 775 { 776 // release importer 777 releaseImporter(pos.getPath(), imp); 778 } 779 } 780 781 @Override 782 public Object getPixels(int series, int resolution, Rectangle rectangle, int z, int t, int c) 783 throws UnsupportedFormatException, IOException 784 { 785 if (!isOpen()) 786 return null; 787 788 final SequenceFileGroup group = currentGroup; 789 final SequenceIdent ident = group.ident; 790 final SequenceType baseType = ident.baseType; 791 792 // define XY region to load (original resolution) 793 Rectangle region = new Rectangle(group.totalSizeX, group.totalSizeY); 794 if (rectangle != null) 795 region = region.intersection(rectangle); 796 797 // get cursor and tile indexes 798 final FileCursor cursor = getCursor(z, t, c); 799 final List<TileIndex> tiles = getTileIndexes(region); 800 801 // single tile ? 802 if (tiles.size() == 1) 803 return getPixelsInternal(positions[cursor.index + tiles.get(0).index], series, resolution, region, 804 cursor.internalZ, cursor.internalT, cursor.internalC); 805 806 // define XY region to load (wanted resolution) 807 final Rectangle finalRegion = new Rectangle(region.x >> resolution, region.y >> resolution, 808 region.width >> resolution, region.height >> resolution); 809 810 // multiple tiles, create result buffer 811 final Object result = Array1DUtil.createArray(baseType.dataType, finalRegion.width * finalRegion.height); 812 final boolean signed = baseType.dataType.isSigned(); 813 // deltas to put tile to region origin 814 final int dx = -finalRegion.x; 815 final int dy = -finalRegion.y; 816 817 // each tile represent a single image 818 for (TileIndex tile : tiles) 819 { 820 // adjusted tile region 821 final Rectangle tileRegion = tile.region.intersection(region); 822 // get tile pixels 823 final Object pixels = getPixelsInternal(positions[cursor.index + tile.index], series, resolution, 824 tileRegion, cursor.internalZ, cursor.internalT, cursor.internalC); 825 826 // cannot retrieve pixels for this tile ? --> ignore 827 if (pixels == null) 828 continue; 829 830 // tile region (wanted resolution) 831 final Rectangle finalTileRegion = new Rectangle(tileRegion.x >> resolution, tileRegion.y >> resolution, 832 tileRegion.width >> resolution, tileRegion.height >> resolution); 833 // destination 834 final Point pt = finalTileRegion.getLocation(); 835 pt.translate(dx, dy); 836 837 // copy tile to result 838 Array1DUtil.copyRect(pixels, finalTileRegion.getSize(), null, result, finalRegion.getSize(), pt, signed); 839 } 840 841 // return full region pixels object 842 return result; 843 } 844 845 // internal use only 846 private IcyBufferedImage getImageInternal(SequencePosition pos, int series, int resolution, Rectangle region, int z, 847 int t, int c) throws UnsupportedFormatException, IOException 848 { 849 if (pos == null) 850 { 851 final SequenceType bt = currentGroup.ident.baseType; 852 System.err.println("SequenceIdGroupImporter.getImageInternal: no image for tile [" + (region.x / bt.sizeX) 853 + "," + (region.y / bt.sizeY) + "] !"); 854 return null; 855 } 856 857 // get importer for this image 858 final SequenceFileImporter imp = getImporter(pos.getPath()); 859 860 if (imp == null) 861 { 862 System.err.println("SequenceIdGroupImporter.getImageInternal: cannot get importer for image '" 863 + pos.getPath() + "' !"); 864 return null; 865 } 866 867 try 868 { 869 // get image from importer (it actually represents a tile of the resulting image) 870 return imp.getImage(series, resolution, region, z, t, c); 871 } 872 finally 873 { 874 // release importer 875 releaseImporter(pos.getPath(), imp); 876 } 877 } 878 879 // internal use only, at this point c cannot be -1 880 private IcyBufferedImage getImageInternal(int series, int resolution, Rectangle rectangle, int z, int t, int c) 881 throws UnsupportedFormatException, IOException 882 { 883 if (!isOpen()) 884 return null; 885 886 final SequenceFileGroup group = currentGroup; 887 final SequenceIdent ident = group.ident; 888 final SequenceType baseType = ident.baseType; 889 890 // define XY region to load 891 Rectangle region = new Rectangle(group.totalSizeX, group.totalSizeY); 892 if (rectangle != null) 893 region = region.intersection(rectangle); 894 895 // get cursor and tile indexes 896 final FileCursor cursor = getCursor(z, t, c); 897 final List<TileIndex> tiles = getTileIndexes(region); 898 899 // single tile ? 900 if (tiles.size() == 1) 901 return getImageInternal(positions[cursor.index + tiles.get(0).index], series, resolution, region, 902 cursor.internalZ, cursor.internalT, cursor.internalC); 903 904 // define XY region to load (wanted resolution) 905 final Rectangle finalRegion = new Rectangle(region.x >> resolution, region.y >> resolution, 906 region.width >> resolution, region.height >> resolution); 907 908 // multiple tiles, create result image 909 final IcyBufferedImage result = new IcyBufferedImage(finalRegion.width, finalRegion.height, 1, 910 baseType.dataType); 911 // colormap save 912 IcyColorMap colormap = null; 913 // deltas to put tile to region origin 914 final int dx = -finalRegion.x; 915 final int dy = -finalRegion.y; 916 917 // each tile represent a single image 918 for (TileIndex tile : tiles) 919 { 920 // adjusted tile region 921 final Rectangle tileRegion = tile.region.intersection(region); 922 // get tile pixels 923 final IcyBufferedImage image = getImageInternal(positions[cursor.index + tile.index], series, resolution, 924 tileRegion, cursor.internalZ, cursor.internalT, cursor.internalC); 925 926 // cannot retrieve pixels for this tile ? --> ignore 927 if (image == null) 928 continue; 929 930 // store colormap 931 if (colormap == null) 932 colormap = image.getColorMap(0); 933 934 // destination 935 final Point pt = new Point(tileRegion.x >> resolution, tileRegion.y >> resolution); 936 pt.translate(dx, dy); 937 938 // copy tile to image result 939 result.copyData(image, null, pt); 940 } 941 942 // set colormap 943 if (colormap != null) 944 result.setColorMap(0, colormap); 945 946 // return full image region 947 return result; 948 } 949 950 @Override 951 public IcyBufferedImage getImage(int series, int resolution, Rectangle rectangle, int z, int t, int c) 952 throws UnsupportedFormatException, IOException 953 { 954 if (!isOpen()) 955 return null; 956 957 final SequenceFileGroup group = currentGroup; 958 final int sizeC = (c == -1) ? group.totalSizeC : 1; 959 final List<IcyBufferedImage> result = new ArrayList<IcyBufferedImage>(); 960 961 // multi channel ? 962 if (sizeC > 1) 963 { 964 // handle channel independently we can have channel in separate file 965 for (int ch = 0; ch < sizeC; ch++) 966 result.add(getImageInternal(series, resolution, rectangle, z, t, ch)); 967 } 968 // single channel 969 else 970 result.add(getImageInternal(series, resolution, rectangle, z, t, (c == -1) ? 0 : c)); 971 972 // we have a null image in the result ? 973 if (result.contains(null)) 974 { 975 final SequenceIdent ident = group.ident; 976 final SequenceType baseType = ident.baseType; 977 978 // define XY region to load 979 Rectangle region = new Rectangle(group.totalSizeX, group.totalSizeY); 980 if (rectangle != null) 981 region = region.intersection(rectangle); 982 983 final int sizeX = region.width >> resolution; 984 final int sizeY = region.height >> resolution; 985 986 // replace null by empty image 987 for (int i = 0; i < result.size(); i++) 988 if (result.get(i) == null) 989 result.set(i, new IcyBufferedImage(sizeX, sizeY, 1, baseType.dataType)); 990 } 991 992 // then build a single image from all channels 993 return IcyBufferedImage.createFrom(result); 994 } 995 996 @SuppressWarnings("resource") 997 @Override 998 public IcyBufferedImage getThumbnail(int series) throws UnsupportedFormatException, IOException 999 { 1000 final OMEXMLMetadata meta = getOMEXMLMetaData(); 1001 1002 // probably no group opened 1003 if (meta == null) 1004 return null; 1005 1006 // stitched image ? --> use default implementation (downscale middle image) 1007 if (isStitchedImage()) 1008 return super.getThumbnail(series); 1009 1010 final int sizeZ = MetaDataUtil.getSizeZ(meta, series); 1011 final int sizeT = MetaDataUtil.getSizeT(meta, series); 1012 1013 // get cursor for image at middle position 1014 final FileCursor cursor = getCursor(sizeZ / 2, sizeT / 2, 0); 1015 final String path; 1016 1017 if (cursor.position != null) 1018 // get path for this image 1019 path = cursor.position.getPath(); 1020 else 1021 // no image for this position ? --> get first available path 1022 path = getFirstImporterPath(); 1023 1024 final SequenceFileImporter imp = getImporter(path); 1025 1026 if (imp == null) 1027 { 1028 // should not happen 1029 System.err.println("SequenceIdGroupImporter.getThumbnail: cannot find importer..."); 1030 return null; 1031 } 1032 1033 try 1034 { 1035 return imp.getThumbnail(series); 1036 } 1037 finally 1038 { 1039 releaseImporter(path, imp); 1040 } 1041 } 1042}