/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2015 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.env.data.views.calls; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import omero.ServerError; import omero.api.ThumbnailStorePrx; import org.openmicroscopy.shoola.env.data.OmeroImageService; import org.openmicroscopy.shoola.env.data.model.ThumbnailData; import omero.gateway.SecurityContext; import omero.gateway.exception.RenderingServiceException; import org.openmicroscopy.shoola.env.data.views.BatchCall; import org.openmicroscopy.shoola.env.data.views.BatchCallTree; import omero.log.LogMessage; import org.openmicroscopy.shoola.util.image.geom.Factory; import org.openmicroscopy.shoola.util.image.io.WriterImage; import omero.gateway.model.DataObject; import omero.gateway.model.ImageData; import omero.gateway.model.PixelsData; /** * Command to load a given set of thumbnails. * <p>As thumbnails are retrieved from <i>OMERO</i>, they're posted back to the * caller through <code>DSCallFeedbackEvent</code>s. Each thumbnail will be * posted in a single event; the caller can then invoke the <code> * getPartialResult</code> method to retrieve a <code>ThumbnailData</code> * object for that thumbnail. The final <code>DSCallOutcomeEvent</code> will * have no result.</p> * <p>Thumbnails are generated respecting the <code>X/Y</code> ratio of the * original image and so that their area doesn't exceed <code>maxWidth* * maxHeight</code>, which is specified to the constructor.</p> * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @version 2.2 * @since OME2.2 */ public class ThumbnailLoader extends BatchCallTree { /** The images for which we need thumbnails. */ private Collection<DataObject> images; /** The maximum acceptable width of the thumbnails. */ private int maxWidth; /** The maximum acceptable height of the thumbnails. */ private int maxHeight; /** The lastly retrieved thumbnail. */ private Object currentThumbnail; /** Flag to indicate if the class was invoked for a pixels ID. */ private boolean pixelsCall; /** The id of the pixels set this loader is for. */ private long pixelsID; /** Collection of user IDs. */ private Set<Long> userIDs; /** Helper reference to the image service. */ private OmeroImageService service; /** Load the thumbnail as an full size image. */ private boolean asImage; /** The security context.*/ private SecurityContext ctx; /** * Loads the thumbnail for {@link #images}<code>[index]</code>. * * @param pxd The image the thumbnail for. * @param userID The id of the user the thumbnail is for. * @param store The thumbnail store to use. * @param imageID The id of the image associated to the pixels set. */ private void loadThumbail(PixelsData pxd, long userID, ThumbnailStorePrx store, boolean last, long imageID) { BufferedImage thumbPix = null; boolean valid = true; int sizeX = maxWidth, sizeY = maxHeight; try { if (asImage) { sizeX = pxd.getSizeX(); sizeY = pxd.getSizeY(); } else { Dimension d = Factory.computeThumbnailSize(sizeX, sizeY, pxd.getSizeX(), pxd.getSizeY()); sizeX = d.width; sizeY = d.height; } if (!store.setPixelsId(pxd.getId())) { store.resetDefaults(); store.setPixelsId(pxd.getId()); } if (userID >= 0) { store.setRenderingDefId(service.getRenderingDef(ctx, pxd.getId(), userID)); } thumbPix = WriterImage.bytesToImage( store.getThumbnail(omero.rtypes.rint(sizeX), omero.rtypes.rint(sizeY))); } catch (Throwable e) { thumbPix = null; LogMessage msg = new LogMessage(); msg.print("Cannot retrieve thumbnail"); msg.print(e); context.getLogger().error(this, msg); } finally { if (last) { context.getDataService().closeService(ctx, store); } } if (thumbPix == null) { valid = false; thumbPix = Factory.createDefaultImageThumbnail(sizeX, sizeY); } currentThumbnail = new ThumbnailData(imageID, thumbPix, userID, valid); } /** * Creates a {@link BatchCall} to retrieve rendering control. * * @return The {@link BatchCall}. */ private BatchCall makeBatchCall() { return new BatchCall("Loading thumbnail for: "+pixelsID) { public void doCall() throws Exception { BufferedImage thumbPix = null; try { thumbPix = service.getThumbnail(ctx, pixelsID, maxWidth, maxHeight, -1); } catch (RenderingServiceException e) { context.getLogger().error(this, "Cannot retrieve thumbnail from ID: "+ e.getExtendedMessage()); } if (thumbPix == null) thumbPix = Factory.createDefaultImageThumbnail(-1); currentThumbnail = thumbPix; } }; } /** * Adds a {@link BatchCall} to the tree for each thumbnail to retrieve. * @see BatchCallTree#buildTree() */ protected void buildTree() { if (pixelsCall) { add(makeBatchCall()); return; } String description; Iterator<Long> j = userIDs.iterator(); Long id; Iterator<DataObject> i; DataObject image; PixelsData pxd; while (j.hasNext()) { id = j.next(); final long userID = id; i = images.iterator(); ThumbnailStorePrx store = null; try { store = service.createThumbnailStore(ctx); } catch (Exception e) { context.getLogger().debug(this, "Cannot start thumbnail store."); } try { final ThumbnailStorePrx value = store; int size = images.size()-1; int k = 0; long imageID = -1; while (i.hasNext()) { image = (DataObject) i.next(); if (image instanceof ImageData) { pxd = ((ImageData) image).getDefaultPixels(); imageID = image.getId(); } else { pxd = (PixelsData) image; if (pxd != null) imageID = pxd.getImage().getId(); } description = "Loading thumbnail"; final PixelsData index = pxd; final boolean last = size == k; k++; final long iid = imageID; add(new BatchCall(description) { public void doCall() { loadThumbail(index, userID, value, last, iid); } }); } } catch (RuntimeException r) { // If we fail to pass control to loadThumbnail // then we need to clean up the service. if (store != null) { try { store.close(); } catch (ServerError e) { context.getLogger().warn(this, "Failed to close " + store); } } } } } /** * Returns the lastly retrieved thumbnail. * This will be packed by the framework into a feedback event and * sent to the provided call observer, if any. * * @return A {@link ThumbnailData} containing the thumbnail pixels. */ protected Object getPartialResult() { return currentThumbnail; } /** * Returns the last loaded thumbnail (important for the BirdsEyeLoader to * work correctly). But in fact, thumbnails are progressively delivered with * feedback events. * @see BatchCallTree#getResult() */ protected Object getResult() { return currentThumbnail; } /** * Creates a new instance. * If bad arguments are passed, we throw a runtime exception so to fail * early and in the caller's thread. * * @param ctx The security context. * @param imgs Contains {@link DataObject}s, one * for each thumbnail to retrieve. * @param maxWidth The maximum acceptable width of the thumbnails. * @param maxHeight The maximum acceptable height of the thumbnails. * @param userIDs The users the thumbnail are for. */ public ThumbnailLoader(SecurityContext ctx, Set<DataObject> imgs, int maxWidth, int maxHeight, Set<Long> userIDs) { if (imgs == null) throw new NullPointerException("No images."); if (maxWidth <= 0) throw new IllegalArgumentException( "Non-positive width: "+maxWidth+"."); if (maxHeight <= 0) throw new IllegalArgumentException( "Non-positive height: "+maxHeight+"."); this.ctx = ctx; this.maxWidth = maxWidth; this.maxHeight = maxHeight; images = imgs; this.userIDs = userIDs; asImage = false; service = context.getImageService(); } /** * Creates a new instance. * If bad arguments are passed, we throw a runtime exception so to fail * early and in the caller's thread. * * @param ctx The security context. * @param imgs Contains {@link DataObject}s, one for each thumbnail to * retrieve. * @param userID The user the thumbnail are for. */ public ThumbnailLoader(SecurityContext ctx, Collection<DataObject> imgs, long userID) { if (imgs == null) throw new NullPointerException("No images."); this.ctx = ctx; asImage = true; images = imgs; userIDs = new HashSet<Long>(1); userIDs.add(userID); service = context.getImageService(); } /** * Creates a new instance. * If bad arguments are passed, we throw a runtime exception so to fail * early and in the caller's thread. * * @param ctx The security context. * @param imgs Contains {@link DataObject}s, one for each thumbnail to * retrieve. * @param maxWidth The maximum acceptable width of the thumbnails. * @param maxHeight The maximum acceptable height of the thumbnails. * @param userID The user the thumbnail are for. */ public ThumbnailLoader(SecurityContext ctx, Collection<DataObject> imgs, int maxWidth, int maxHeight, long userID) { if (imgs == null) throw new NullPointerException("No images."); if (maxWidth <= 0) throw new IllegalArgumentException( "Non-positive width: "+maxWidth+"."); if (maxHeight <= 0) throw new IllegalArgumentException( "Non-positive height: "+maxHeight+"."); this.ctx = ctx; this.maxWidth = maxWidth; this.maxHeight = maxHeight; images = imgs; userIDs = new HashSet<Long>(1); userIDs.add(userID); asImage = false; service = context.getImageService(); } /** * Creates a new instance. * If bad arguments are passed, we throw a runtime exception so to fail * early and in the caller's thread. * * @param ctx The security context. * @param image The {@link ImageData}, the thumbnail * @param maxWidth The maximum acceptable width of the thumbnails. * @param maxHeight The maximum acceptable height of the thumbnails. * @param userID The user the thumbnails are for. */ public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, long userID) { if (image == null) throw new IllegalArgumentException("No image."); if (maxWidth <= 0) throw new IllegalArgumentException( "Non-positive width: "+maxWidth+"."); if (maxHeight <= 0) throw new IllegalArgumentException( "Non-positive height: "+maxHeight+"."); this.ctx = ctx; userIDs = new HashSet<Long>(1); userIDs.add(userID); images = new HashSet<DataObject>(1); images.add(image); this.maxWidth = maxWidth; this.maxHeight = maxHeight; asImage = false; service = context.getImageService(); } /** * Creates a new instance. * If bad arguments are passed, we throw a runtime exception so to fail * early and in the caller's thread. * * @param ctx The security context. * @param pixelsID The id of the pixel set. * @param maxWidth The m aximum acceptable width of the thumbnails. * @param maxHeight The maximum acceptable height of the thumbnails. * @param userID The user the thumbnail are for. */ public ThumbnailLoader(SecurityContext ctx, long pixelsID, int maxWidth, int maxHeight, long userID) { if (maxWidth <= 0) throw new IllegalArgumentException( "Non-positive id: "+pixelsID+"."); if (maxWidth <= 0) throw new IllegalArgumentException( "Non-positive width: "+maxWidth+"."); if (maxHeight <= 0) throw new IllegalArgumentException( "Non-positive height: "+maxHeight+"."); this.ctx = ctx; pixelsCall = true; this.maxWidth = maxWidth; this.maxHeight = maxHeight; this.pixelsID = pixelsID; userIDs = new HashSet<Long>(1); userIDs.add(userID); service = context.getImageService(); } /** * Creates a new instance. * If bad arguments are passed, we throw a runtime exception so to fail * early and in the caller's thread. * * @param ctx The security context. * @param image The {@link ImageData}, the thumbnail * @param maxWidth The maximum acceptable width of the thumbnails. * @param maxHeight The maximum acceptable height of the thumbnails. * @param userIDs The users the thumbnail are for. */ public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, Set<Long> userIDs) { if (image == null) throw new IllegalArgumentException("No image."); if (maxWidth <= 0) throw new IllegalArgumentException( "Non-positive width: "+maxWidth+"."); if (maxHeight <= 0) throw new IllegalArgumentException( "Non-positive height: "+maxHeight+"."); this.ctx = ctx; images = new HashSet<DataObject>(1); images.add(image); this.maxWidth = maxWidth; this.maxHeight = maxHeight; this.userIDs = userIDs; asImage = false; service = context.getImageService(); } }