From 78727d90058f53afbb49506b32fcdc30417e702a Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Tue, 14 Aug 2018 11:34:30 +0100 Subject: [PATCH 01/13] Updates to ThumbnailLoader and ThumbnailService ThumbnailLoader calls new API, checks if image has pyramids and can determine what state a thumbnail is in if it is missing ThumbnailBean has additional simple load method that only returns a thumbnail if is available, otherwise returns an empty array --- .../resources/omero/api/ThumbnailStore.ice | 32 +- .../services/blitz/impl/ThumbnailStoreI.java | 7 + .../common/src/ome/api/ThumbnailStore.java | 29 + .../shoola/env/data/model/ThumbnailData.java | 127 ++-- .../env/data/views/DataManagerViewImpl.java | 3 +- .../data/views/HierarchyBrowsingViewImpl.java | 12 +- .../env/data/views/calls/ThumbnailLoader.java | 561 +++++++----------- .../src/ome/services/ThumbnailBean.java | 97 ++- .../server/src/ome/services/ThumbnailCtx.java | 19 + 9 files changed, 469 insertions(+), 418 deletions(-) diff --git a/components/blitz/resources/omero/api/ThumbnailStore.ice b/components/blitz/resources/omero/api/ThumbnailStore.ice index 9933f641836..47ba7d0262e 100644 --- a/components/blitz/resources/omero/api/ThumbnailStore.ice +++ b/components/blitz/resources/omero/api/ThumbnailStore.ice @@ -90,7 +90,8 @@ module omero { * in the on-disk cache it will be returned directly, * otherwise it will be created as in * {@link #getThumbnailDirect}, placed in the on-disk - * cache and returned. + * cache and returned. If the thumbnail is missing, a clock will + * be returned to signify that the thumbnail is yet to be generated. * * @param sizeX the X-axis width of the thumbnail. * null specifies the default size @@ -112,6 +113,35 @@ module omero { */ idempotent Ice::ByteSeq getThumbnail(omero::RInt sizeX, omero::RInt sizeY) throws ServerError; + /** + * Retrieves a thumbnail for a pixels set using a given set of + * rendering settings (RenderingDef). If the thumbnail exists + * in the on-disk cache it will be returned directly, + * otherwise it will be created as in + * {@link #getThumbnailDirect}, placed in the on-disk + * cache and returned. If the thumbnail is still to be generated, an empty array will + * be returned. + * + * @param sizeX the X-axis width of the thumbnail. + * null specifies the default size + * of 48. + * @param sizeY the Y-axis width of the thumbnail. + * null specifies the default size + * of 48. + * @throws ApiUsageException + * if: + * + * @return a JPEG thumbnail byte buffer + * @see #getThumbnailDirect + */ + idempotent Ice::ByteSeq getThumbnailWithoutDefault(omero::RInt sizeX, omero::RInt sizeY) throws ServerError; + /** * Retrieves a number of thumbnails for pixels sets using * given sets of rendering settings (RenderingDef). If the diff --git a/components/blitz/src/ome/services/blitz/impl/ThumbnailStoreI.java b/components/blitz/src/ome/services/blitz/impl/ThumbnailStoreI.java index ee645fb8fb1..be568535cff 100644 --- a/components/blitz/src/ome/services/blitz/impl/ThumbnailStoreI.java +++ b/components/blitz/src/ome/services/blitz/impl/ThumbnailStoreI.java @@ -16,6 +16,7 @@ import omero.api.AMD_ThumbnailStore_createThumbnailsByLongestSideSet; import omero.api.AMD_ThumbnailStore_getRenderingDefId; import omero.api.AMD_ThumbnailStore_getThumbnail; +import omero.api.AMD_ThumbnailStore_getThumbnailWithoutDefault; import omero.api.AMD_ThumbnailStore_getThumbnailByLongestSide; import omero.api.AMD_ThumbnailStore_getThumbnailByLongestSideDirect; import omero.api.AMD_ThumbnailStore_getThumbnailByLongestSideSet; @@ -126,6 +127,12 @@ public void getThumbnail_async(AMD_ThumbnailStore_getThumbnail __cb, } + public void getThumbnailWithoutDefault_async(AMD_ThumbnailStore_getThumbnailWithoutDefault __cb, + RInt sizeX, RInt sizeY, Current __current) throws ServerError { + callInvokerOnRawArgs(__cb, __current, sizeX, sizeY); + + } + public void resetDefaults_async(AMD_ThumbnailStore_resetDefaults __cb, Current __current) throws ServerError { callInvokerOnRawArgs(__cb, __current); diff --git a/components/common/src/ome/api/ThumbnailStore.java b/components/common/src/ome/api/ThumbnailStore.java index f2048a36f28..3811c447457 100644 --- a/components/common/src/ome/api/ThumbnailStore.java +++ b/components/common/src/ome/api/ThumbnailStore.java @@ -105,6 +105,35 @@ public interface ThumbnailStore extends StatefulServiceInterface { * @see #getThumbnailDirect(Integer, Integer) */ public byte[] getThumbnail(Integer sizeX, Integer sizeY); + + /** + * Retrieves a thumbnail for a pixels set using a given set of + * rendering settings (RenderingDef). If the thumbnail exists + * in the on-disk cache it will be returned directly, + * otherwise it will be created as in + * {@link #getThumbnailDirect}, placed in the on-disk + * cache and returned. If the thumbnail is still to be generated, an empty array will + * be returned. + * + * @param sizeX the X-axis width of the thumbnail. + * null specifies the default size + * of 48. + * @param sizeY the Y-axis width of the thumbnail. + * null specifies the default size + * of 48. + * @throws ApiUsageException + * if: + * + * @return a JPEG thumbnail byte buffer + * @see #getThumbnailDirect + */ + public byte[] getThumbnailWithoutDefault(Integer sizeX, Integer sizeY); /** * Retrieves a number of thumbnails for pixels sets using given sets of diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/model/ThumbnailData.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/model/ThumbnailData.java index 73149962abb..e226f9e3004 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/model/ThumbnailData.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/model/ThumbnailData.java @@ -11,7 +11,7 @@ * 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. @@ -22,12 +22,14 @@ package org.openmicroscopy.shoola.env.data.model; import java.awt.Graphics2D; +import java.awt.Image; import java.awt.image.BufferedImage; import omero.gateway.model.ImageData; +import org.openmicroscopy.shoola.util.image.geom.Factory; -/** +/** * Holds a {@link BufferedImage} serving as a thumbnail for a given OME * image. * @@ -45,79 +47,79 @@ public class ThumbnailData /** The id of the image to which the thumbnail belong. */ private long userID; - + /** The id of the image to which the thumbnail belong. */ private long imageID; - + /** The thumbnail pixels. */ private BufferedImage thumbnail; /** Flag indicating if the image is a default image or not. */ private boolean validImage; - + /** Used to store the image. */ private ImageData image; - + /** Flag indicating that the image required a pyramid to be build.*/ private Boolean requirePyramid; - + /** The object of reference. */ private omero.gateway.model.DataObject refObject; - - /** + + /** * Exception if not possible to create the object. This should * be used when imported. */ private Exception error; - + /** * Creates a new instance. - * + * * @param imageID The id of the image to which the thumbnail belong. * Must be positive. * @param thumbnail The thumbnail pixels. Mustn't be null. * @param userID The id of the user the thumbnail is for. * Must be positive. - * @param validImage Pass true if the image is a real image, + * @param validImage Pass true if the image is a real image, * false otherwise. */ - public ThumbnailData(long imageID, BufferedImage thumbnail, long userID, - boolean validImage) + public ThumbnailData(long imageID, Image thumbnail, long userID, + boolean validImage) { - if (imageID <= 0) + if (imageID <= 0) throw new IllegalArgumentException("Non-positive image id: "+ imageID+"."); this.imageID = imageID; - this.thumbnail = thumbnail; + this.thumbnail = toBufferedImage(thumbnail); this.userID = userID; this.validImage = validImage; requirePyramid = null; } - + /** * Creates a new instance. - * + * * @param imageID The id of the image to which the thumbnail belong. * Must be positive. * @param thumbnail The thumbnail pixels. Mustn't be null. - * @param validImage Pass true if the image is a real image, + * @param validImage Pass true if the image is a real image, * false otherwise. */ - public ThumbnailData(long imageID, BufferedImage thumbnail, + public ThumbnailData(long imageID, Image thumbnail, boolean validImage) { this(imageID, thumbnail, -1, validImage); } - + /** * Creates a new instance. - * + * * @param refOjbect The object of reference. Mustn't be null. * @param thumbnail The thumbnail pixels. Mustn't be null. - * @param validImage Passed true if it is a valid image, + * @param validImage Passed true if it is a valid image, * false otherwise. */ - public ThumbnailData(omero.gateway.model.DataObject refOjbect, BufferedImage thumbnail, + public ThumbnailData(omero.gateway.model.DataObject refOjbect, Image thumbnail, boolean validImage) { if (refOjbect == null) @@ -126,13 +128,13 @@ public ThumbnailData(omero.gateway.model.DataObject refOjbect, BufferedImage thu throw new IllegalArgumentException("Type not valid."); this.refObject = refOjbect; this.validImage = validImage; - this.thumbnail = thumbnail; + this.thumbnail = toBufferedImage(thumbnail); requirePyramid = null; } - + /** * Creates a new instance. - * + * * @param refOjbect The object of reference. Mustn't be null. * @param requirePyramid Pass true if a pyramid is required, * false otherwise. @@ -142,10 +144,10 @@ public ThumbnailData(omero.gateway.model.DataObject refOjbect, Boolean requirePy this(refOjbect, null, false); this.requirePyramid = requirePyramid; } - + /** * Creates a new instance. - * + * * @param refOjbect The object of reference. Mustn't be null. * @param thumbnail The thumbnail pixels. Mustn't be null. */ @@ -153,43 +155,43 @@ public ThumbnailData(omero.gateway.model.DataObject refOjbect, BufferedImage thu { this(refOjbect, thumbnail, true); } - + /** * Sets the time the flag indicating if the image requires a pyramid to be * built. - * + * * @param requirePyramid The value to set. */ public void setBackOffForPyramid(Boolean requirePyramid) { this.requirePyramid = requirePyramid; } - + /** * Sets the exception thrown when trying to create a thumbnail. - * + * * @param error The exception to set. */ public void setError(Exception error) { this.error = error; } - + /** * Returns the exception. - * + * * @return See above. */ public Exception getError() { return error; } - - /** + + /** * Sets the image. - * + * * @param image The image to set. */ public void setImage(ImageData image) { this.image = image; } - + /** * Clones this object. * This is a deep-copy, the thumbnail pixels are cloned too. - * + * * @see org.openmicroscopy.shoola.env.data.model.DataObject#makeNew() */ public DataObject makeNew() @@ -197,7 +199,7 @@ public DataObject makeNew() BufferedImage pixClone = null; if (thumbnail != null) { pixClone = new BufferedImage( thumbnail.getWidth(), - thumbnail.getHeight(), + thumbnail.getHeight(), thumbnail.getType()); Graphics2D g2D = pixClone.createGraphics(); g2D.drawImage(thumbnail, null, 0, 0); @@ -216,58 +218,71 @@ public DataObject makeNew() } /** - * Returns true if the image is a real image, + * Returns true if the image is a real image, * false otherwise. - * + * * @return See above. */ public boolean isValidImage() { return validImage; } - + /** * Returns the id of the user. - * + * * @return See above. */ public long getUserID() { return userID; } - + /** * Returns the id of the image to which the thumbnail belong. - * + * * @return See above. */ public long getImageID() { return imageID; } - + /** * Returns the thumbnail pixels. - * + * * @return See above. */ public BufferedImage getThumbnail() { return thumbnail; } - + /** * Returns the image. - * + * * @return See above. */ public ImageData getImage() { return image; } - + /** * Returns the object of reference. - * + * * @return See above. */ public omero.gateway.model.DataObject getRefObject() { return refObject; } - + /** * Returns true if a pyramid is required, false * otherwise. - * + * * @return See above. */ public Boolean requirePyramid() - { + { if (requirePyramid != null) return requirePyramid.booleanValue(); return null; } + /** + * Converts a given Image into a BufferedImage + * + * @param img The Image to be converted + * @return The converted BufferedImage + */ + public static BufferedImage toBufferedImage(Image img) { + if (img instanceof BufferedImage) { + return (BufferedImage) img; + } + return Factory.createImage(img); + } + } diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/DataManagerViewImpl.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/DataManagerViewImpl.java index b0b332f6f45..901d1dfddae 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/DataManagerViewImpl.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/DataManagerViewImpl.java @@ -141,8 +141,7 @@ public CallHandle countContainerItems(SecurityContext ctx, Set rootIDs, public CallHandle loadThumbnail(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, long userID, AgentEventListener observer) { - BatchCallTree cmd = new ThumbnailLoader(ctx, image, maxWidth, maxHeight, - userID); + BatchCallTree cmd = new ThumbnailLoader(ctx, image, maxWidth, maxHeight, userID); return cmd.exec(observer); } diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/HierarchyBrowsingViewImpl.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/HierarchyBrowsingViewImpl.java index 9b232bf4919..93394ddc637 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/HierarchyBrowsingViewImpl.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/HierarchyBrowsingViewImpl.java @@ -65,18 +65,18 @@ public CallHandle loadHierarchy(SecurityContext ctx, Class rootNodeType, /** * Implemented as specified by the view interface. - * @see HierarchyBrowsingView#loadThumbnails(Collection, int, int, long, - * AgentEventListener) + * @see HierarchyBrowsingView#loadThumbnails(SecurityContext, Collection, int, int, long, int, AgentEventListener) */ public CallHandle loadThumbnails(SecurityContext ctx, Collection images, int maxWidth, int maxHeight, long userID, int type, AgentEventListener observer) { BatchCallTree cmd; - if (type == EXPERIMENTER) - cmd = new ThumbnailSetLoader(ctx, images, maxHeight, type); - else cmd = new ThumbnailLoader(ctx, images, maxWidth, - maxHeight, userID); + if (type == EXPERIMENTER) { + cmd = new ThumbnailSetLoader(ctx, images, maxHeight, type); + } else { + cmd = new ThumbnailLoader(ctx, images, maxWidth, maxHeight, userID); + } return cmd.exec(observer); } diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index 6d4708dba24..b91f304fb27 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -11,7 +11,7 @@ * 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. @@ -21,38 +21,34 @@ 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 ome.conditions.ResourceError; import omero.ServerError; - +import omero.api.IConfigPrx; +import omero.api.RawPixelsStorePrx; import omero.api.ThumbnailStorePrx; - +import omero.gateway.SecurityContext; +import omero.gateway.exception.DSAccessException; +import omero.gateway.exception.DSOutOfServiceException; +import omero.gateway.model.DataObject; +import omero.gateway.model.ImageData; +import omero.gateway.model.PixelsData; +import omero.log.LogMessage; 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; +import java.awt.Dimension; +import java.awt.Image; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; -/** +/** * Command to load a given set of thumbnails. - *

As thumbnails are retrieved from OMERO, they're posted back to the + *

As thumbnails are retrieved from OMERO, they're posted back to the * caller through DSCallFeedbackEvents. Each thumbnail will be * posted in a single event; the caller can then invoke the * getPartialResult method to retrieve a ThumbnailData @@ -62,195 +58,206 @@ * original image and so that their area doesn't exceed maxWidth* * maxHeight, which is specified to the constructor.

* - * @author Jean-Marie Burel      - * j.burel@dundee.ac.uk - * @author
Andrea Falconi      - * - * a.falconi@dundee.ac.uk + * @author Jean-Marie Burel      + * j.burel@dundee.ac.uk + * @author
Andrea Falconi      + * + * a.falconi@dundee.ac.uk * @version 2.2 * @since OME2.2 */ public class ThumbnailLoader - extends BatchCallTree -{ + extends BatchCallTree { - /** The images for which we need thumbnails. */ + /** + * The images for which we need thumbnails. + */ private Collection images; - /** The maximum acceptable width of the thumbnails. */ + /** + * The maximum acceptable width of the thumbnails. + */ private int maxWidth; - /** The maximum acceptable height of the thumbnails. */ + /** + * The maximum acceptable height of the thumbnails. + */ private int maxHeight; - /** The lastly retrieved thumbnail. */ + /** + * 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 userIDs; + /** + * Collection of user IDs. + */ + private Collection userIDs; - /** Helper reference to the image service. */ + /** + * Helper reference to the image service. + */ private OmeroImageService service; - /** Load the thumbnail as an full size image. */ - private boolean asImage; - - /** The security context.*/ + /** + * The security context. + */ private SecurityContext ctx; /** - * Loads the thumbnail for {@link #images}[index]. - * - * @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. + * Use getConfigService() instead of this directly */ - private void loadThumbail(PixelsData pxd, long userID, - ThumbnailStorePrx store, boolean last, long imageID) - { - BufferedImage thumbPix = null; - boolean valid = true; - int sizeX = maxWidth, sizeY = maxHeight; + private IConfigPrx configService; + + /** + * Load the thumbnail as an full size image. + */ + private boolean asImage = false; + + + private IConfigPrx getConfigService() throws DSOutOfServiceException { + if (configService == null) { + configService = context.getGateway() + .getConfigService(ctx); + } + return configService; + } + + private void handleBatchCall(ThumbnailStorePrx store, PixelsData pxd, long userId) { + // If image has pyramids, check to see if image is ready for loading as a thumbnail. try { - if (asImage) { - sizeX = pxd.getSizeX(); - sizeY = pxd.getSizeY(); + Image thumbnail; + byte[] thumbnailData = loadThumbnail(store, pxd, userId); + if (thumbnailData == null || thumbnailData.length == 0) { + // Find out why the thumbnail is not ready on the server + if (requiresPixelsPyramid(pxd)) { + thumbnail = determineThumbnailState(pxd); + } else { + thumbnail = Factory.createDefaultThumbnail("Loading"); + } } else { - Dimension d = Factory.computeThumbnailSize(sizeX, sizeY, - pxd.getSizeX(), pxd.getSizeY()); - sizeX = d.width; - sizeY = d.height; + thumbnail = WriterImage.bytesToImage(thumbnailData); } + // Convert thumbnail to whatever + currentThumbnail = new ThumbnailData(pxd.getImage().getId(), + thumbnail, userId, true); + } catch (Exception e) { + context.getLogger().error(this, e.getMessage()); + } + } - if (!store.setPixelsId(pxd.getId())) { - store.resetDefaults(); - store.setPixelsId(pxd.getId()); - } - if (userID >= 0) { - long rndDefId = service.getRenderingDef(ctx, - pxd.getId(), userID); - // the user might not have own rendering settings - // for this image - if (rndDefId >= 0) - store.setRenderingDefId(rndDefId); - } - 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); - } + private PixelsData dataObjectToPixelsData(DataObject image) { + return image instanceof ImageData ? + ((ImageData) image).getDefaultPixels() : + (PixelsData) image; + } + + private Image determineThumbnailState(PixelsData pxd) + throws DSOutOfServiceException, ServerError { + RawPixelsStorePrx rawPixelStore = context.getGateway() + .getPixelsStore(ctx); + try { + // This method will throw if there is an issue with the pyramid + // generation (i.e. it's not finished, corrupt) + rawPixelStore.setPixelsId(pxd.getId(), false); + } catch (omero.MissingPyramidException e) { + // Thrown if pyramid file is missing + // create and show a loading .symbol + return Factory.createDefaultThumbnail("Loading"); + } catch (ResourceError e) { + context.getLogger().error(this, new LogMessage("Error getting pyramid from server," + + " it might be corrupt", e)); + } + return Factory.createDefaultThumbnail("Error"); + } + + /** + * Loads the thumbnail for {@link #images}[index]. + * + * @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. + */ + private byte[] loadThumbnail(ThumbnailStorePrx store, PixelsData pxd, long userId) + throws ServerError, DSAccessException, DSOutOfServiceException { + int sizeX = maxWidth, sizeY = maxHeight; + 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 (thumbPix == null) { - valid = false; - thumbPix = Factory.createDefaultImageThumbnail(sizeX, sizeY); + + if (!store.setPixelsId(pxd.getId())) { + store.resetDefaults(); + store.setPixelsId(pxd.getId()); } - currentThumbnail = new ThumbnailData(imageID, thumbPix, userID, valid); + + if (userId >= 0) { + long rndDefId = service.getRenderingDef(ctx, + pxd.getId(), userId); + // the user might not have own rendering settings + // for this image + if (rndDefId >= 0) + store.setRenderingDefId(rndDefId); + } + + return store.getThumbnailWithoutDefault(omero.rtypes.rint(sizeX), + omero.rtypes.rint(sizeY)); } /** - * Creates a {@link BatchCall} to retrieve rendering control. - * - * @return The {@link BatchCall}. + * Returns whether a pyramid should be used for the given {@link PixelsData}. + * This usually implies that this is a "Big image" and therefore will + * need tiling. + * + * @param pxd + * @return */ - 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; - } - }; - } + private boolean requiresPixelsPyramid(PixelsData pxd) throws DSOutOfServiceException, ServerError { + int maxWidth = Integer.parseInt(getConfigService() + .getConfigValue("omero.pixeldata.max_plane_width")); + int maxHeight = Integer.parseInt(getConfigService() + .getConfigValue("omero.pixeldata.max_plane_height")); + return pxd.getSizeX() * pxd.getSizeY() > maxWidth * maxHeight; + } /** * 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 j = userIDs.iterator(); - Long id; - Iterator 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); + @Override + protected void buildTree() { + final int lastIndex = images.size() - 1; + for (final long userId : userIDs) { + int k = 0; + for (DataObject image : images) { + // Cast our image to pixels object + final PixelsData pxd = dataObjectToPixelsData(image); + + // Flag to check if we've iterated to the last image + final boolean last = lastIndex == k++; + + // Add a new load thumbnail task to tree + add(new BatchCall("Loading thumbnails") { + @Override + public void doCall() throws Exception { + super.doCall(); + ThumbnailStorePrx store = service.createThumbnailStore(ctx); + try { + handleBatchCall(store, pxd, userId); + } finally { + if (last) { + context.getDataService() + .closeService(ctx, store); + } } - }); - } - } 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); } - } + }); } } } @@ -259,198 +266,78 @@ public void doCall() { * 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; } + 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. + * work correctly). But in fact, thumbnails are progressively delivered with + * feedback events. + * * @see BatchCallTree#getResult() */ - protected Object getResult() { return currentThumbnail; } + @Override + 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 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. + * @param userIDs The users the thumbnail are for. */ - public ThumbnailLoader(SecurityContext ctx, Set imgs, - int maxWidth, int maxHeight, Set userIDs) - { - if (imgs == null) throw new NullPointerException("No images."); - if (maxWidth <= 0) + public ThumbnailLoader(SecurityContext ctx, Collection imgs, + int maxWidth, int maxHeight, Collection userIDs) { + if (images == null) { + throw new NullPointerException("No images."); + } + + if (maxWidth <= 0) { throw new IllegalArgumentException( - "Non-positive width: "+maxWidth+"."); - if (maxHeight <= 0) + "Non-positive width: " + maxWidth + "."); + } + + if (maxHeight <= 0) { throw new IllegalArgumentException( - "Non-positive height: "+maxHeight+"."); - this.ctx = ctx; + "Non-positive height: " + maxHeight + "."); + } + + this.images = imgs; 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 imgs, - long userID) - { - if (imgs == null) throw new NullPointerException("No images."); this.ctx = ctx; - asImage = true; - images = imgs; - userIDs = new HashSet(1); - userIDs.add(userID); - service = context.getImageService(); + this.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 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(1); - userIDs.add(userID); - asImage = false; - service = context.getImageService(); + public ThumbnailLoader(SecurityContext ctx, Collection imgs, long userID) { + this(ctx, imgs, 0, 0, Collections.singleton(userID)); + this.asImage = true; } - /** - * 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(1); - userIDs.add(userID); - images = new HashSet(1); - images.add(image); - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - asImage = false; - service = context.getImageService(); + public ThumbnailLoader(SecurityContext ctx, Collection imgs, int maxWidth, int maxHeight, long userID) { + this(ctx, imgs, maxWidth, maxHeight, Collections.singleton(userID)); } - /** - * 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(1); - userIDs.add(userID); - service = context.getImageService(); + public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, long userID) { + this(ctx, new HashSet(), maxWidth, maxHeight, Collections.singleton(userID)); + images.add(image); } - /** - * 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 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(1); + public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, Collection userIDs) { + this(ctx, new HashSet(), maxWidth, maxHeight, userIDs); images.add(image); - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - this.userIDs = userIDs; - asImage = false; - service = context.getImageService(); } } diff --git a/components/server/src/ome/services/ThumbnailBean.java b/components/server/src/ome/services/ThumbnailBean.java index 3cd73336431..4df22294813 100644 --- a/components/server/src/ome/services/ThumbnailBean.java +++ b/components/server/src/ome/services/ThumbnailBean.java @@ -481,9 +481,7 @@ public void setSettingsService(IRenderingSettings settingsService) { * @throws IOException * if there is a problem writing to disk. */ - private void compressThumbnailToDisk(Thumbnail thumb, BufferedImage image) - throws IOException { - + private void compressThumbnailToDisk(Thumbnail thumb, BufferedImage image) throws IOException { if (diskSpaceChecking) { iRepositoryInfo.sanityCheckRepository(); } @@ -819,10 +817,18 @@ public void createThumbnail(Integer sizeX, Integer sizeY) } } - /** Actually does the work specified by {@link #createThumbnail(Integer, Integer)}. */ + /** Helper function for using local thumbnailMetadata */ private Thumbnail _createThumbnail() { + // For old times sake + return _createThumbnail(thumbnailMetadata); + } + + /* + * Actually does the work specified by {@link #createThumbnail(Integer, Integer)}. + */ + private Thumbnail _createThumbnail(Thumbnail thumbMetaData) { StopWatch s1 = new Slf4JStopWatch("omero._createThumbnail"); - if (thumbnailMetadata == null) { + if (thumbMetaData == null) { throw new ValidationException("Missing thumbnail metadata."); } else if (ctx.dirtyMetadata(pixels.getId())) { // Increment the version of the thumbnail so that its @@ -831,7 +837,7 @@ private Thumbnail _createThumbnail() { // implemented using IUpdate.touch() or similar once that // functionality exists. //Check first if the thumbnail is the one of the settings owner - Long ownerId = thumbnailMetadata.getDetails().getOwner().getId(); + Long ownerId = thumbMetaData.getDetails().getOwner().getId(); Long rndOwnerId = settings.getDetails().getOwner().getId(); final Long rndGroupId = settings.getDetails().getGroup().getId(); final Map groupContext = new HashMap<>(); @@ -846,16 +852,16 @@ private Thumbnail _createThumbnail() { } if (rndOwnerId.equals(ownerId)) { final Pixels unloadedPixels = new Pixels(pixels.getId(), false); - thumbnailMetadata.setPixels(unloadedPixels); - _setMetadataVersion(thumbnailMetadata, inProgress); + thumbMetaData.setPixels(unloadedPixels); + _setMetadataVersion(thumbMetaData, inProgress); dirtyMetadata = true; } else { //new one for owner of the settings. - final Dimension d = new Dimension(thumbnailMetadata.getSizeX(), - thumbnailMetadata.getSizeY()); - thumbnailMetadata = ctx.createThumbnailMetadata(pixels, d); - _setMetadataVersion(thumbnailMetadata, inProgress); - thumbnailMetadata = iUpdate.saveAndReturnObject(thumbnailMetadata); + final Dimension d = new Dimension(thumbMetaData.getSizeX(), + thumbMetaData.getSizeY()); + thumbMetaData = ctx.createThumbnailMetadata(pixels, d); + _setMetadataVersion(thumbMetaData, inProgress); + thumbMetaData = iUpdate.saveAndReturnObject(thumbMetaData); dirtyMetadata = false; } } finally { @@ -874,9 +880,9 @@ private Thumbnail _createThumbnail() { BufferedImage image = createScaledImage(null, null); try { - compressThumbnailToDisk(thumbnailMetadata, image); + compressThumbnailToDisk(thumbMetaData, image); s1.stop(); - return thumbnailMetadata; + return thumbMetaData; } catch (IOException e) { log.error("Thumbnail could not be compressed.", e); throw new ResourceError(e.getMessage()); @@ -1070,6 +1076,38 @@ public byte[] getThumbnail(Integer sizeX, Integer sizeY) { return value; } + /* + * (non-Javadoc) + * + * @see ome.api.ThumbnailStore#getThumbnail(ome.model.core.Pixels, + * ome.model.displayRenderingDef, java.lang.Integer, + * java.lang.Integer) + */ + @RolesAllowed("user") + @Transactional(readOnly = false) + public byte[] getThumbnailWithoutDefault(Integer sizeX, Integer sizeY) { + errorIfNullPixelsAndRenderingDef(); + Dimension dimensions = sanityCheckThumbnailSizes(sizeX, sizeY); + Set pixelsIds = Collections.singleton(pixelsId); + ctx.loadAndPrepareMetadata(pixelsIds, dimensions); + + thumbnailMetadata = ctx.getMetadataSimple(pixelsId); + if (thumbnailMetadata == null) { + // If this comes back null, don't have a thumbnail yet + thumbnailMetadata = ctx.createThumbnailMetadata(pixels, dimensions); + + // Trigger a thumbnail creation + // ToDo: make this async + _createThumbnail(thumbnailMetadata); + } + + byte[] value = retrieveThumbnail(); + + // I don't really know why this is here, no iquery calls being that I can see... + iQuery.clear();//see #11072 + return value; + } + /** * Creates the thumbnail or retrieves it from cache and updates the * thumbnail metadata. @@ -1159,6 +1197,33 @@ private byte[] retrieveThumbnail(boolean rewriteMetadata) } } + /** + * A simple way to creates the thumbnail or retrieves it from cache. + * + * @return Thumbnail bytes. + */ + private byte[] retrieveThumbnail() throws ResourceError { + try { + boolean cached = ctx.isThumbnailCached(pixels.getId()); + if (cached) { + if (log.isDebugEnabled()) { + log.debug("Cache hit."); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Cache miss, thumbnail missing or out of date."); + } + + // Return empty array for the time being + return new byte[0]; + } + return ioService.getThumbnail(thumbnailMetadata); + } catch (IOException e) { + log.error("Could not obtain thumbnail", e); + throw new ResourceError(e.getMessage()); + } + } + /* * (non-Javadoc) * @@ -1423,4 +1488,4 @@ private byte[] handleNoThumbnail(Throwable t, Dimension dimensions) { } } -} +} \ No newline at end of file diff --git a/components/server/src/ome/services/ThumbnailCtx.java b/components/server/src/ome/services/ThumbnailCtx.java index 328295e8c11..dfa8fa4ccfc 100644 --- a/components/server/src/ome/services/ThumbnailCtx.java +++ b/components/server/src/ome/services/ThumbnailCtx.java @@ -494,6 +494,25 @@ else if (thumbnail == null) return thumbnail; } + /** + * Retrieves the Thumbnail object for a given Pixels ID. + * + * @param pixelsId Pixels ID to retrieve the Thumbnail object for. + * @return returns null if the thumbnail metadata can't be found + */ + public Thumbnail getMetadataSimple(long pixelsId) throws ResourceError { + Thumbnail thumbnail = pixelsIdMetadataMap.get(pixelsId); + if (thumbnail == null && securitySystem.isGraphCritical(null)) { + Pixels pixels = pixelsIdPixelsMap.get(pixelsId); + long ownerId = pixels.getDetails().getOwner().getId(); + throw new ResourceError(String.format( + "The user id:%s may not be the owner id:%d. The owner " + + "has not viewed the Pixels set id:%d and thumbnail " + + "metadata is missing.", userId, ownerId, pixelsId)); + } + return thumbnail; + } + /** * Whether or not the thumbnail metadata for a given Pixels ID is dirty * (the RenderingDef has been updated since the Thumbnail was). From 2627ed7dd607051820e3a841f1fa63d412f07113 Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Wed, 22 Aug 2018 10:19:10 +0100 Subject: [PATCH 02/13] Add empty implementation to Destroyable --- .../services/blitz/test/utests/IceMethodInvokerUnitTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/blitz/test/ome/services/blitz/test/utests/IceMethodInvokerUnitTest.java b/components/blitz/test/ome/services/blitz/test/utests/IceMethodInvokerUnitTest.java index 00444a58612..c4a22162c57 100644 --- a/components/blitz/test/ome/services/blitz/test/utests/IceMethodInvokerUnitTest.java +++ b/components/blitz/test/ome/services/blitz/test/utests/IceMethodInvokerUnitTest.java @@ -233,6 +233,11 @@ public long getRenderingDefId() { return -1; } + @Override + public byte[] getThumbnailWithoutDefault(Integer arg0, Integer arg1) { + return null; + } + } // From 194a65b7dc1ba51f6b36afc908ca44fea075b512 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Wed, 22 Aug 2018 15:35:03 +0100 Subject: [PATCH 03/13] Fixed null images error --- .../shoola/env/data/views/calls/ThumbnailLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index b91f304fb27..fc79c8a8d21 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -299,7 +299,7 @@ protected Object getResult() { */ public ThumbnailLoader(SecurityContext ctx, Collection imgs, int maxWidth, int maxHeight, Collection userIDs) { - if (images == null) { + if (imgs == null) { throw new NullPointerException("No images."); } From 28f3d0af7d9dd80392b226733636921424687eb7 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Wed, 22 Aug 2018 17:05:08 +0100 Subject: [PATCH 04/13] Fixed thumbnail loading bug --- .../src/ome/services/ThumbnailBean.java | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/components/server/src/ome/services/ThumbnailBean.java b/components/server/src/ome/services/ThumbnailBean.java index 4df22294813..24b2c210625 100644 --- a/components/server/src/ome/services/ThumbnailBean.java +++ b/components/server/src/ome/services/ThumbnailBean.java @@ -823,8 +823,11 @@ private Thumbnail _createThumbnail() { return _createThumbnail(thumbnailMetadata); } - /* + /** * Actually does the work specified by {@link #createThumbnail(Integer, Integer)}. + * + * @param thumbMetaData Thumbnail meta data object + * @return */ private Thumbnail _createThumbnail(Thumbnail thumbMetaData) { StopWatch s1 = new Slf4JStopWatch("omero._createThumbnail"); @@ -1090,19 +1093,17 @@ public byte[] getThumbnailWithoutDefault(Integer sizeX, Integer sizeY) { Dimension dimensions = sanityCheckThumbnailSizes(sizeX, sizeY); Set pixelsIds = Collections.singleton(pixelsId); ctx.loadAndPrepareMetadata(pixelsIds, dimensions); - - thumbnailMetadata = ctx.getMetadataSimple(pixelsId); - if (thumbnailMetadata == null) { + Thumbnail thumbMetaData = ctx.getMetadataSimple(pixelsId); + if (thumbMetaData == null) { // If this comes back null, don't have a thumbnail yet - thumbnailMetadata = ctx.createThumbnailMetadata(pixels, dimensions); + thumbMetaData = ctx.createThumbnailMetadata(pixels, dimensions); // Trigger a thumbnail creation // ToDo: make this async - _createThumbnail(thumbnailMetadata); + _createThumbnail(thumbMetaData); } - byte[] value = retrieveThumbnail(); - + byte[] value = retrieveThumbnail(thumbMetaData); // I don't really know why this is here, no iquery calls being that I can see... iQuery.clear();//see #11072 return value; @@ -1202,29 +1203,29 @@ private byte[] retrieveThumbnail(boolean rewriteMetadata) * * @return Thumbnail bytes. */ - private byte[] retrieveThumbnail() throws ResourceError { - try { - boolean cached = ctx.isThumbnailCached(pixels.getId()); - if (cached) { - if (log.isDebugEnabled()) { - log.debug("Cache hit."); - } - } else { - if (log.isDebugEnabled()) { - log.debug("Cache miss, thumbnail missing or out of date."); - } - - // Return empty array for the time being - return new byte[0]; + private byte[] retrieveThumbnail(Thumbnail thumbMetaData) throws ResourceError { + if (!ctx.isThumbnailCached(pixels.getId())) { + if (log.isDebugEnabled()) { + log.debug("Cache miss, thumbnail missing or out of date."); } - return ioService.getThumbnail(thumbnailMetadata); + + // Return empty array for the time being + return new byte[0]; + } + + if (log.isDebugEnabled()) { + log.debug("Cache hit."); + } + + try { + return ioService.getThumbnail(thumbMetaData); } catch (IOException e) { log.error("Could not obtain thumbnail", e); throw new ResourceError(e.getMessage()); } } - /* + /** * (non-Javadoc) * * @see ome.api.ThumbnailStore#getThumbnailByLongestSide(ome.model.core.Pixels, From e12a6a92ceea0714ab02a243b22e8ee53a3462de Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Thu, 23 Aug 2018 00:06:01 +0100 Subject: [PATCH 05/13] Not sure what's going, thumbnails load, but not in all cases --- .../env/data/views/calls/ThumbnailLoader.java | 20 +++--- .../src/ome/services/ThumbnailBean.java | 65 ++++++++++++------- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index fc79c8a8d21..cb775b13834 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -126,15 +126,16 @@ private IConfigPrx getConfigService() throws DSOutOfServiceException { private void handleBatchCall(ThumbnailStorePrx store, PixelsData pxd, long userId) { // If image has pyramids, check to see if image is ready for loading as a thumbnail. try { - Image thumbnail; + Image thumbnail; byte[] thumbnailData = loadThumbnail(store, pxd, userId); if (thumbnailData == null || thumbnailData.length == 0) { // Find out why the thumbnail is not ready on the server - if (requiresPixelsPyramid(pxd)) { - thumbnail = determineThumbnailState(pxd); - } else { - thumbnail = Factory.createDefaultThumbnail("Loading"); - } + thumbnail = Factory.createDefaultThumbnail("Loading"); +// if (requiresPixelsPyramid(pxd)) { +// thumbnail = determineThumbnailState(pxd); +// } else { +// thumbnail = Factory.createDefaultThumbnail("Loading"); +// } } else { thumbnail = WriterImage.bytesToImage(thumbnailData); } @@ -243,10 +244,9 @@ protected void buildTree() { final boolean last = lastIndex == k++; // Add a new load thumbnail task to tree - add(new BatchCall("Loading thumbnails") { + BatchCall call = new BatchCall("Loading thumbnails") { @Override public void doCall() throws Exception { - super.doCall(); ThumbnailStorePrx store = service.createThumbnailStore(ctx); try { handleBatchCall(store, pxd, userId); @@ -257,7 +257,9 @@ public void doCall() throws Exception { } } } - }); + }; + + add(call); } } } diff --git a/components/server/src/ome/services/ThumbnailBean.java b/components/server/src/ome/services/ThumbnailBean.java index 24b2c210625..506959c05b8 100644 --- a/components/server/src/ome/services/ThumbnailBean.java +++ b/components/server/src/ome/services/ThumbnailBean.java @@ -1093,20 +1093,42 @@ public byte[] getThumbnailWithoutDefault(Integer sizeX, Integer sizeY) { Dimension dimensions = sanityCheckThumbnailSizes(sizeX, sizeY); Set pixelsIds = Collections.singleton(pixelsId); ctx.loadAndPrepareMetadata(pixelsIds, dimensions); - Thumbnail thumbMetaData = ctx.getMetadataSimple(pixelsId); - if (thumbMetaData == null) { - // If this comes back null, don't have a thumbnail yet - thumbMetaData = ctx.createThumbnailMetadata(pixels, dimensions); - // Trigger a thumbnail creation - // ToDo: make this async - _createThumbnail(thumbMetaData); + // If this comes back null, don't have a thumbnail yet + thumbnailMetadata = ctx.getMetadataSimple(pixelsId); + if (thumbnailMetadata == null) { + // We don't have a thumbnail to load, lets try to create it + // and then return it + thumbnailMetadata = ctx.createThumbnailMetadata(pixels, dimensions); } - byte[] value = retrieveThumbnail(thumbMetaData); // I don't really know why this is here, no iquery calls being that I can see... iQuery.clear();//see #11072 - return value; + + return retrieveThumbnail(thumbnailMetadata); + +// return retrieveThumbnailDirect((int) dimensions.getWidth(), +// (int) dimensions.getHeight(), null, null, true); + + /*byte[] value = retrieveThumbnail(thumbMetaData); + if (value.length == 0) { + try { + pixelDataService.getPixelBuffer(ctx.getPixels(pixelsId), false); + } catch (ConcurrencyException e) { + log.debug("ConcurrencyException on retrieveThumbnailSet.ctx.hasSettings: pyramid in progress"); + return value; + } + + // Trigger a thumbnail creation + // ToDo: make this async + // _createThumbnail(thumbMetaData); + + // Now try to load thumbnail + // value = retrieveThumbnail(thumbMetaData); + }*/ + + + // return value; } /** @@ -1204,24 +1226,23 @@ private byte[] retrieveThumbnail(boolean rewriteMetadata) * @return Thumbnail bytes. */ private byte[] retrieveThumbnail(Thumbnail thumbMetaData) throws ResourceError { - if (!ctx.isThumbnailCached(pixels.getId())) { + try { + return ioService.getThumbnail(thumbMetaData); + } catch (IOException e) { if (log.isDebugEnabled()) { log.debug("Cache miss, thumbnail missing or out of date."); } - // Return empty array for the time being - return new byte[0]; - } - - if (log.isDebugEnabled()) { - log.debug("Cache hit."); - } + // If we get here, then we can assume the thumbnail just needs created + // and saved to disk + _createThumbnail(thumbMetaData); - try { - return ioService.getThumbnail(thumbMetaData); - } catch (IOException e) { - log.error("Could not obtain thumbnail", e); - throw new ResourceError(e.getMessage()); + try { + return ioService.getThumbnail(thumbMetaData); + } catch (IOException e1) { + log.error("Could not obtain thumbnail", e1); + throw new ResourceError(e.getMessage()); + } } } From 14529411d63d6f665e8ae131de475a2fe1e81e3e Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Thu, 23 Aug 2018 14:59:01 +0100 Subject: [PATCH 06/13] Fixed thumbnail loading issue and cleaned up thumbnail loading code in DataBrowserModel --- .../dataBrowser/view/DataBrowserModel.java | 19 +- .../env/data/views/calls/ThumbnailLoader.java | 251 +++++++++--------- .../src/ome/services/ThumbnailBean.java | 36 +-- 3 files changed, 138 insertions(+), 168 deletions(-) diff --git a/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java b/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java index b057644bda8..b627a7ab2c9 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java @@ -1022,24 +1022,7 @@ List createThumbnailsLoader(List images) { if (images == null) return null; List loaders = new ArrayList(); - int n = images.size(); - int diff = n/MAX_LOADER; - List l; - int j; - int step = 0; - if (n < MAX_LOADER) diff = 1; - for (int k = 0; k < MAX_LOADER; k++) { - l = new ArrayList(); - j = step+diff; - if (k == (MAX_LOADER-1)) j += (n-j); - if (j <= n) { - l = images.subList(step, j); - step += l.size(); - } - if (l.size() > 0) { - loaders.add(new ThumbnailLoader(component, ctx, l, n)); - } - } + loaders.add(new ThumbnailLoader(component, ctx, images, images.size())); return loaders; } diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index cb775b13834..e1a315c4354 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -115,6 +115,124 @@ public class ThumbnailLoader private boolean asImage = false; + /** + * 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, Collection imgs, + int maxWidth, int maxHeight, Collection 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.images = imgs; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this.userIDs = userIDs; + this.ctx = ctx; + this.service = context.getImageService(); + } + + public ThumbnailLoader(SecurityContext ctx, Collection imgs, long userID) { + this(ctx, imgs, 0, 0, Collections.singleton(userID)); + this.asImage = true; + } + + public ThumbnailLoader(SecurityContext ctx, Collection imgs, int maxWidth, int maxHeight, long userID) { + this(ctx, imgs, maxWidth, maxHeight, Collections.singleton(userID)); + } + + public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, long userID) { + this(ctx, new HashSet(), maxWidth, maxHeight, Collections.singleton(userID)); + images.add(image); + } + + public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, Collection userIDs) { + this(ctx, new HashSet(), maxWidth, maxHeight, userIDs); + images.add(image); + } + + /** + * 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() + */ + @Override + protected Object getResult() { + return currentThumbnail; + } + + /** + * 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. + */ + @Override + protected Object getPartialResult() { + return currentThumbnail; + } + + /** + * Adds a {@link BatchCall} to the tree for each thumbnail to retrieve. + * + * @see BatchCallTree#buildTree() + */ + @Override + protected void buildTree() { + final int lastIndex = images.size() - 1; + for (final long userId : userIDs) { + int k = 0; + for (DataObject image : images) { + // Cast our image to pixels object + final PixelsData pxd = dataObjectToPixelsData(image); + + // Flag to check if we've iterated to the last image + final boolean last = lastIndex == k++; + + // Add a new load thumbnail task to tree + BatchCall call = new BatchCall("Loading thumbnails") { + @Override + public void doCall() throws Exception { + // System.out.println(image.getId()); + ThumbnailStorePrx store = service.createThumbnailStore(ctx); + try { + handleBatchCall(store, pxd, userId); + } finally { + if (last) { + context.getDataService() + .closeService(ctx, store); + } + } + } + }; + + add(call); + } + } + } + private IConfigPrx getConfigService() throws DSOutOfServiceException { if (configService == null) { configService = context.getGateway() @@ -126,16 +244,15 @@ private IConfigPrx getConfigService() throws DSOutOfServiceException { private void handleBatchCall(ThumbnailStorePrx store, PixelsData pxd, long userId) { // If image has pyramids, check to see if image is ready for loading as a thumbnail. try { - Image thumbnail; + Image thumbnail; byte[] thumbnailData = loadThumbnail(store, pxd, userId); if (thumbnailData == null || thumbnailData.length == 0) { // Find out why the thumbnail is not ready on the server - thumbnail = Factory.createDefaultThumbnail("Loading"); -// if (requiresPixelsPyramid(pxd)) { -// thumbnail = determineThumbnailState(pxd); -// } else { -// thumbnail = Factory.createDefaultThumbnail("Loading"); -// } + if (requiresPixelsPyramid(pxd)) { + thumbnail = determineThumbnailState(pxd); + } else { + thumbnail = Factory.createDefaultThumbnail("Loading"); + } } else { thumbnail = WriterImage.bytesToImage(thumbnailData); } @@ -162,8 +279,8 @@ private Image determineThumbnailState(PixelsData pxd) // generation (i.e. it's not finished, corrupt) rawPixelStore.setPixelsId(pxd.getId(), false); } catch (omero.MissingPyramidException e) { - // Thrown if pyramid file is missing - // create and show a loading .symbol + // Thrown if pyramid file is missing, then we know the thumbnail still has + // to be generated in a short time return Factory.createDefaultThumbnail("Loading"); } catch (ResourceError e) { context.getLogger().error(this, new LogMessage("Error getting pyramid from server," + @@ -226,120 +343,4 @@ private boolean requiresPixelsPyramid(PixelsData pxd) throws DSOutOfServiceExcep return pxd.getSizeX() * pxd.getSizeY() > maxWidth * maxHeight; } - /** - * Adds a {@link BatchCall} to the tree for each thumbnail to retrieve. - * - * @see BatchCallTree#buildTree() - */ - @Override - protected void buildTree() { - final int lastIndex = images.size() - 1; - for (final long userId : userIDs) { - int k = 0; - for (DataObject image : images) { - // Cast our image to pixels object - final PixelsData pxd = dataObjectToPixelsData(image); - - // Flag to check if we've iterated to the last image - final boolean last = lastIndex == k++; - - // Add a new load thumbnail task to tree - BatchCall call = new BatchCall("Loading thumbnails") { - @Override - public void doCall() throws Exception { - ThumbnailStorePrx store = service.createThumbnailStore(ctx); - try { - handleBatchCall(store, pxd, userId); - } finally { - if (last) { - context.getDataService() - .closeService(ctx, store); - } - } - } - }; - - add(call); - } - } - } - - /** - * 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() - */ - @Override - 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, Collection imgs, - int maxWidth, int maxHeight, Collection 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.images = imgs; - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - this.userIDs = userIDs; - this.ctx = ctx; - this.service = context.getImageService(); - } - - public ThumbnailLoader(SecurityContext ctx, Collection imgs, long userID) { - this(ctx, imgs, 0, 0, Collections.singleton(userID)); - this.asImage = true; - } - - public ThumbnailLoader(SecurityContext ctx, Collection imgs, int maxWidth, int maxHeight, long userID) { - this(ctx, imgs, maxWidth, maxHeight, Collections.singleton(userID)); - } - - public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, long userID) { - this(ctx, new HashSet(), maxWidth, maxHeight, Collections.singleton(userID)); - images.add(image); - } - - public ThumbnailLoader(SecurityContext ctx, ImageData image, int maxWidth, int maxHeight, Collection userIDs) { - this(ctx, new HashSet(), maxWidth, maxHeight, userIDs); - images.add(image); - } - } diff --git a/components/server/src/ome/services/ThumbnailBean.java b/components/server/src/ome/services/ThumbnailBean.java index 506959c05b8..8266fa25d9e 100644 --- a/components/server/src/ome/services/ThumbnailBean.java +++ b/components/server/src/ome/services/ThumbnailBean.java @@ -1102,33 +1102,10 @@ public byte[] getThumbnailWithoutDefault(Integer sizeX, Integer sizeY) { thumbnailMetadata = ctx.createThumbnailMetadata(pixels, dimensions); } + byte[] value = retrieveThumbnail(thumbnailMetadata); // I don't really know why this is here, no iquery calls being that I can see... iQuery.clear();//see #11072 - - return retrieveThumbnail(thumbnailMetadata); - -// return retrieveThumbnailDirect((int) dimensions.getWidth(), -// (int) dimensions.getHeight(), null, null, true); - - /*byte[] value = retrieveThumbnail(thumbMetaData); - if (value.length == 0) { - try { - pixelDataService.getPixelBuffer(ctx.getPixels(pixelsId), false); - } catch (ConcurrencyException e) { - log.debug("ConcurrencyException on retrieveThumbnailSet.ctx.hasSettings: pyramid in progress"); - return value; - } - - // Trigger a thumbnail creation - // ToDo: make this async - // _createThumbnail(thumbMetaData); - - // Now try to load thumbnail - // value = retrieveThumbnail(thumbMetaData); - }*/ - - - // return value; + return value; } /** @@ -1233,6 +1210,15 @@ private byte[] retrieveThumbnail(Thumbnail thumbMetaData) throws ResourceError { log.debug("Cache miss, thumbnail missing or out of date."); } + final long pixelsId = thumbMetaData.getPixels().getId(); + if (!ctx.hasSettings(pixelsId)) { + try { + pixelDataService.getPixelBuffer(ctx.getPixels(pixelsId), false); + } catch (ConcurrencyException ce) { + return new byte[0]; + } + } + // If we get here, then we can assume the thumbnail just needs created // and saved to disk _createThumbnail(thumbMetaData); From fe5598e8d3940cf4cfc9fad1c588f80afdb79b3d Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Tue, 28 Aug 2018 17:36:10 +0100 Subject: [PATCH 07/13] Cleaned up some code in insight, however discovered that default clock still is returned in some cases --- .../env/data/views/calls/ThumbnailLoader.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index e1a315c4354..4dba457ee36 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -216,7 +216,7 @@ protected void buildTree() { @Override public void doCall() throws Exception { // System.out.println(image.getId()); - ThumbnailStorePrx store = service.createThumbnailStore(ctx); + ThumbnailStorePrx store = getThumbnailStore(pxd); try { handleBatchCall(store, pxd, userId); } finally { @@ -289,6 +289,17 @@ private Image determineThumbnailState(PixelsData pxd) return Factory.createDefaultThumbnail("Error"); } + private ThumbnailStorePrx getThumbnailStore(PixelsData pxd) throws DSAccessException, + DSOutOfServiceException, ServerError { + // System.out.println(image.getId()); + ThumbnailStorePrx store = service.createThumbnailStore(ctx); + if (!store.setPixelsId(pxd.getId())) { + store.resetDefaults(); + store.setPixelsId(pxd.getId()); + } + return store; + } + /** * Loads the thumbnail for {@link #images}[index]. * @@ -309,11 +320,6 @@ private byte[] loadThumbnail(ThumbnailStorePrx store, PixelsData pxd, long userI sizeY = d.height; } - if (!store.setPixelsId(pxd.getId())) { - store.resetDefaults(); - store.setPixelsId(pxd.getId()); - } - if (userId >= 0) { long rndDefId = service.getRenderingDef(ctx, pxd.getId(), userId); From 41171853804dae2e84fcb75c22b73422f7d41273 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Wed, 29 Aug 2018 15:39:00 +0100 Subject: [PATCH 08/13] Added local inpogress boolean to method --- .../src/ome/services/ThumbnailBean.java | 128 ++++++++++-------- 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/components/server/src/ome/services/ThumbnailBean.java b/components/server/src/ome/services/ThumbnailBean.java index 8266fa25d9e..23df403cec1 100644 --- a/components/server/src/ome/services/ThumbnailBean.java +++ b/components/server/src/ome/services/ThumbnailBean.java @@ -481,7 +481,8 @@ public void setSettingsService(IRenderingSettings settingsService) { * @throws IOException * if there is a problem writing to disk. */ - private void compressThumbnailToDisk(Thumbnail thumb, BufferedImage image) throws IOException { + private void compressThumbnailToDisk(Thumbnail thumb, BufferedImage image, boolean inProgress) + throws IOException { if (diskSpaceChecking) { iRepositoryInfo.sanityCheckRepository(); } @@ -489,7 +490,7 @@ private void compressThumbnailToDisk(Thumbnail thumb, BufferedImage image) throw FileOutputStream stream = ioService.getThumbnailOutputStream(thumb); try { if (inProgress) { - compressInProgressImageToStream(thumb, stream); + compressInProgressImageToStream(thumb, stream, inProgressImageResource); } else { compressionService.compressToStream(image, stream); } @@ -500,45 +501,48 @@ private void compressThumbnailToDisk(Thumbnail thumb, BufferedImage image) throw /** * Compresses the in progress image to a stream. - * @param thumb The thumbnail metadata. + * + * @param thumb Thumbnail meta data * @param outputStream Stream to compress the data to. + * @param inProgressImageResource The image file (located in resources) to write to disk + * @throws IOException */ - private void compressInProgressImageToStream( - Thumbnail thumb, OutputStream outputStream) { - int x = thumb.getSizeX(); - int y = thumb.getSizeY(); - StopWatch s1 = new Slf4JStopWatch("omero.transcodeSVG"); - try - { - SVGRasterizer rasterizer = new SVGRasterizer( - inProgressImageResource.getInputStream()); - // Batik will automatically maintain the aspect ratio of the - // resulting image if we only specify the width or height. - if (x > y) - { - rasterizer.setImageWidth(x); - } - else - { - rasterizer.setImageHeight(y); - } - rasterizer.setQuality(compressionService.getCompressionLevel()); - rasterizer.createJPEG(outputStream); - s1.stop(); - } - catch (IOException e1) - { - String s = "Error loading in-progress image from Spring resource."; - log.error(s, e1); - throw new ResourceError(s); - } - catch (TranscoderException e2) - { - String s = "Error transcoding in progress SVG."; - log.error(s, e2); - throw new ResourceError(s); - } - } + private void compressInProgressImageToStream(Thumbnail thumb, OutputStream outputStream, + Resource inProgressImageResource) { + int x = thumb.getSizeX(); + int y = thumb.getSizeY(); + StopWatch s1 = new Slf4JStopWatch("omero.transcodeSVG"); + try + { + SVGRasterizer rasterizer = new SVGRasterizer( + inProgressImageResource.getInputStream()); + // Batik will automatically maintain the aspect ratio of the + // resulting image if we only specify the width or height. + if (x > y) + { + rasterizer.setImageWidth(x); + } + else + { + rasterizer.setImageHeight(y); + } + rasterizer.setQuality(compressionService.getCompressionLevel()); + rasterizer.createJPEG(outputStream); + s1.stop(); + } + catch (IOException e1) + { + String s = "Error loading in-progress image from Spring resource."; + log.error(s, e1); + throw new ResourceError(s); + } + catch (TranscoderException e2) + { + String s = "Error transcoding in progress SVG."; + log.error(s, e2); + throw new ResourceError(s); + } + } /** * Checks that sizeX and sizeY are not out of range for the active pixels @@ -883,7 +887,7 @@ private Thumbnail _createThumbnail(Thumbnail thumbMetaData) { BufferedImage image = createScaledImage(null, null); try { - compressThumbnailToDisk(thumbMetaData, image); + compressThumbnailToDisk(thumbMetaData, image, inProgress); s1.stop(); return thumbMetaData; } catch (IOException e) { @@ -1209,26 +1213,34 @@ private byte[] retrieveThumbnail(Thumbnail thumbMetaData) throws ResourceError { if (log.isDebugEnabled()) { log.debug("Cache miss, thumbnail missing or out of date."); } + } - final long pixelsId = thumbMetaData.getPixels().getId(); - if (!ctx.hasSettings(pixelsId)) { - try { - pixelDataService.getPixelBuffer(ctx.getPixels(pixelsId), false); - } catch (ConcurrencyException ce) { - return new byte[0]; - } + final long pixelsId = thumbMetaData.getPixels().getId(); + if (!ctx.hasSettings(pixelsId)) { + try { + pixelDataService.getPixelBuffer(ctx.getPixels(pixelsId), false); + } catch (ConcurrencyException ce) { + return new byte[0]; } + } - // If we get here, then we can assume the thumbnail just needs created - // and saved to disk - _createThumbnail(thumbMetaData); - - try { - return ioService.getThumbnail(thumbMetaData); - } catch (IOException e1) { - log.error("Could not obtain thumbnail", e1); - throw new ResourceError(e.getMessage()); + // If we get here, then we can assume the thumbnail just needs created + // and saved to disk + try { + BufferedImage image = createScaledImage(null, null); + if (image != null) { + compressThumbnailToDisk(thumbMetaData, image, false); } + } catch (Exception e) { + log.error("Thumbnail could not be compressed.", e); + throw new ResourceError(e.getMessage()); + } + + try { + return ioService.getThumbnail(thumbMetaData); + } catch (IOException e) { + log.error("Could not obtain thumbnail", e); + throw new ResourceError(e.getMessage()); } } @@ -1300,11 +1312,11 @@ private byte[] retrieveThumbnailDirect(Integer sizeX, Integer sizeY, thumbnailMetadata = local; } - BufferedImage image = createScaledImage(theZ, theT); + BufferedImage image = inProgress? null : createScaledImage(theZ, theT); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); try { if (inProgress) { - compressInProgressImageToStream(local, byteStream); + compressInProgressImageToStream(local, byteStream, inProgressImageResource); } else { compressionService.compressToStream(image, byteStream); } From 9f57aa64f4bfff85353b13c66c1762afebe3fbb0 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Wed, 29 Aug 2018 16:04:31 +0100 Subject: [PATCH 09/13] Fixed a javadoc comment on _createThumbnail method --- components/server/src/ome/services/ThumbnailBean.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/server/src/ome/services/ThumbnailBean.java b/components/server/src/ome/services/ThumbnailBean.java index 23df403cec1..be1db4f6fac 100644 --- a/components/server/src/ome/services/ThumbnailBean.java +++ b/components/server/src/ome/services/ThumbnailBean.java @@ -821,7 +821,12 @@ public void createThumbnail(Integer sizeX, Integer sizeY) } } - /** Helper function for using local thumbnailMetadata */ + /** + * Calls {@code _createThumbnail(Thumbnail thumbMetaData)} with the local + * variable {@code thumbnailMetadata} as the input parameter. + * + * @return thumbnail object + */ private Thumbnail _createThumbnail() { // For old times sake return _createThumbnail(thumbnailMetadata); From fbc04e5a6fe5e8c93a2e6eb2a46266b9dfec2710 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Thu, 30 Aug 2018 14:11:07 +0100 Subject: [PATCH 10/13] Restored DataBrowserModel.createThumbnailsLoader due to better thread allocation --- .../dataBrowser/view/DataBrowserModel.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java b/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java index b627a7ab2c9..b057644bda8 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/agents/dataBrowser/view/DataBrowserModel.java @@ -1022,7 +1022,24 @@ List createThumbnailsLoader(List images) { if (images == null) return null; List loaders = new ArrayList(); - loaders.add(new ThumbnailLoader(component, ctx, images, images.size())); + int n = images.size(); + int diff = n/MAX_LOADER; + List l; + int j; + int step = 0; + if (n < MAX_LOADER) diff = 1; + for (int k = 0; k < MAX_LOADER; k++) { + l = new ArrayList(); + j = step+diff; + if (k == (MAX_LOADER-1)) j += (n-j); + if (j <= n) { + l = images.subList(step, j); + step += l.size(); + } + if (l.size() > 0) { + loaders.add(new ThumbnailLoader(component, ctx, l, n)); + } + } return loaders; } From 93e5e19203cc45639390a32a9f67b143bf13c552 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Fri, 31 Aug 2018 14:31:38 +0100 Subject: [PATCH 11/13] Removed commented out code --- .../shoola/env/data/views/calls/ThumbnailLoader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index 4dba457ee36..1a92674823c 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -215,7 +215,6 @@ protected void buildTree() { BatchCall call = new BatchCall("Loading thumbnails") { @Override public void doCall() throws Exception { - // System.out.println(image.getId()); ThumbnailStorePrx store = getThumbnailStore(pxd); try { handleBatchCall(store, pxd, userId); From 2e576bc1860f0a468cfcd208277723ee901a2303 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Fri, 31 Aug 2018 16:10:08 +0100 Subject: [PATCH 12/13] Added ability to support older versions of omero.server less than 5.4.8 --- .../env/data/views/calls/ThumbnailLoader.java | 37 +++++++++++++------ .../shoola/util/VersionCompare.java | 36 ++++++++++++++++++ 2 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index 1a92674823c..d78ca281b1f 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -37,6 +37,7 @@ import org.openmicroscopy.shoola.env.data.model.ThumbnailData; import org.openmicroscopy.shoola.env.data.views.BatchCall; import org.openmicroscopy.shoola.env.data.views.BatchCallTree; +import org.openmicroscopy.shoola.util.VersionCompare; import org.openmicroscopy.shoola.util.image.geom.Factory; import org.openmicroscopy.shoola.util.image.io.WriterImage; @@ -66,8 +67,12 @@ * @version 2.2 * @since OME2.2 */ -public class ThumbnailLoader - extends BatchCallTree { +public class ThumbnailLoader extends BatchCallTree { + + /** + * Version of OMERO.server with {@code getThumbnailWithoutDefault} + */ + private static final String VERSION_THUMBNAIL_NO_DEFAULT = "5.4.8"; /** * The images for which we need thumbnails. @@ -216,8 +221,9 @@ protected void buildTree() { @Override public void doCall() throws Exception { ThumbnailStorePrx store = getThumbnailStore(pxd); + IConfigPrx configService = getConfigService(); try { - handleBatchCall(store, pxd, userId); + handleBatchCall(store, configService, pxd, userId); } finally { if (last) { context.getDataService() @@ -240,14 +246,14 @@ private IConfigPrx getConfigService() throws DSOutOfServiceException { return configService; } - private void handleBatchCall(ThumbnailStorePrx store, PixelsData pxd, long userId) { + private void handleBatchCall(ThumbnailStorePrx store, IConfigPrx configService, PixelsData pxd, long userId) { // If image has pyramids, check to see if image is ready for loading as a thumbnail. try { Image thumbnail; - byte[] thumbnailData = loadThumbnail(store, pxd, userId); + byte[] thumbnailData = loadThumbnail(store, configService, pxd, userId); if (thumbnailData == null || thumbnailData.length == 0) { // Find out why the thumbnail is not ready on the server - if (requiresPixelsPyramid(pxd)) { + if (requiresPixelsPyramid(configService, pxd)) { thumbnail = determineThumbnailState(pxd); } else { thumbnail = Factory.createDefaultThumbnail("Loading"); @@ -306,7 +312,7 @@ private ThumbnailStorePrx getThumbnailStore(PixelsData pxd) throws DSAccessExcep * @param userId The id of the user the thumbnail is for. * @param store The thumbnail store to use. */ - private byte[] loadThumbnail(ThumbnailStorePrx store, PixelsData pxd, long userId) + private byte[] loadThumbnail(ThumbnailStorePrx store, IConfigPrx configService, PixelsData pxd, long userId) throws ServerError, DSAccessException, DSOutOfServiceException { int sizeX = maxWidth, sizeY = maxHeight; if (asImage) { @@ -328,8 +334,15 @@ private byte[] loadThumbnail(ThumbnailStorePrx store, PixelsData pxd, long userI store.setRenderingDefId(rndDefId); } - return store.getThumbnailWithoutDefault(omero.rtypes.rint(sizeX), - omero.rtypes.rint(sizeY)); + if (VersionCompare.compare(configService.getVersion(), VERSION_THUMBNAIL_NO_DEFAULT) >= 0) { + // If the client is connecting a server with version 5.4.8 or greater, use the thumbnail + // loading function that doesn't return a clock. + return store.getThumbnailWithoutDefault(omero.rtypes.rint(sizeX), + omero.rtypes.rint(sizeY)); + } else { + return store.getThumbnail(omero.rtypes.rint(sizeX), + omero.rtypes.rint(sizeY)); + } } /** @@ -340,10 +353,10 @@ private byte[] loadThumbnail(ThumbnailStorePrx store, PixelsData pxd, long userI * @param pxd * @return */ - private boolean requiresPixelsPyramid(PixelsData pxd) throws DSOutOfServiceException, ServerError { - int maxWidth = Integer.parseInt(getConfigService() + private boolean requiresPixelsPyramid(IConfigPrx configService, PixelsData pxd) throws ServerError { + int maxWidth = Integer.parseInt(configService .getConfigValue("omero.pixeldata.max_plane_width")); - int maxHeight = Integer.parseInt(getConfigService() + int maxHeight = Integer.parseInt(configService .getConfigValue("omero.pixeldata.max_plane_height")); return pxd.getSizeX() * pxd.getSizeY() > maxWidth * maxHeight; } diff --git a/components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java b/components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java new file mode 100644 index 00000000000..8ef154f392b --- /dev/null +++ b/components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java @@ -0,0 +1,36 @@ +package org.openmicroscopy.shoola.util; + +public class VersionCompare { + + /** + * Compares two version strings. https://stackoverflow.com/a/6702029/272446 + *

+ * Use this instead of String.compareTo() for a non-lexicographical + * comparison that works for version strings. e.g. "1.10".compareTo("1.6"). + * + * @param str1 a string of ordinal numbers separated by decimal points. + * @param str2 a string of ordinal numbers separated by decimal points. + * @return The result is a negative integer if str1 is _numerically_ less than str2. + * The result is a positive integer if str1 is _numerically_ greater than str2. + * The result is zero if the strings are _numerically_ equal. + * @note It does not work if "1.10" is supposed to be equal to "1.10.0". + */ + public static int compare(String str1, String str2) { + String[] vals1 = str1.split("\\."); + String[] vals2 = str2.split("\\."); + int i = 0; + // set index to first non-equal ordinal or length of shortest version string + while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) { + i++; + } + // compare first non-equal ordinal number + if (i < vals1.length && i < vals2.length) { + int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); + return Integer.signum(diff); + } + // the strings are equal or one string is a substring of the other + // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" + return Integer.signum(vals1.length - vals2.length); + } + +} From 9c4b34e4091d00f866691b86a1754ec73e9ccc75 Mon Sep 17 00:00:00 2001 From: Riad Gozim Date: Fri, 31 Aug 2018 17:16:22 +0100 Subject: [PATCH 13/13] Switched to using Gateway.getServerVersion function for obtaining version of server connected to. Cleaned up javadoc --- .../env/data/views/calls/ThumbnailLoader.java | 21 +++++++++---------- .../shoola/util/VersionCompare.java | 5 +++-- .../src/ome/services/ThumbnailBean.java | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java index d78ca281b1f..c46800a8bc6 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/env/data/views/calls/ThumbnailLoader.java @@ -221,9 +221,8 @@ protected void buildTree() { @Override public void doCall() throws Exception { ThumbnailStorePrx store = getThumbnailStore(pxd); - IConfigPrx configService = getConfigService(); try { - handleBatchCall(store, configService, pxd, userId); + handleBatchCall(store, pxd, userId); } finally { if (last) { context.getDataService() @@ -246,14 +245,14 @@ private IConfigPrx getConfigService() throws DSOutOfServiceException { return configService; } - private void handleBatchCall(ThumbnailStorePrx store, IConfigPrx configService, PixelsData pxd, long userId) { + private void handleBatchCall(ThumbnailStorePrx store, PixelsData pxd, long userId) { // If image has pyramids, check to see if image is ready for loading as a thumbnail. try { Image thumbnail; - byte[] thumbnailData = loadThumbnail(store, configService, pxd, userId); + byte[] thumbnailData = loadThumbnail(store, pxd, userId); if (thumbnailData == null || thumbnailData.length == 0) { // Find out why the thumbnail is not ready on the server - if (requiresPixelsPyramid(configService, pxd)) { + if (requiresPixelsPyramid(pxd)) { thumbnail = determineThumbnailState(pxd); } else { thumbnail = Factory.createDefaultThumbnail("Loading"); @@ -312,7 +311,7 @@ private ThumbnailStorePrx getThumbnailStore(PixelsData pxd) throws DSAccessExcep * @param userId The id of the user the thumbnail is for. * @param store The thumbnail store to use. */ - private byte[] loadThumbnail(ThumbnailStorePrx store, IConfigPrx configService, PixelsData pxd, long userId) + private byte[] loadThumbnail(ThumbnailStorePrx store, PixelsData pxd, long userId) throws ServerError, DSAccessException, DSOutOfServiceException { int sizeX = maxWidth, sizeY = maxHeight; if (asImage) { @@ -334,8 +333,8 @@ private byte[] loadThumbnail(ThumbnailStorePrx store, IConfigPrx configService, store.setRenderingDefId(rndDefId); } - if (VersionCompare.compare(configService.getVersion(), VERSION_THUMBNAIL_NO_DEFAULT) >= 0) { - // If the client is connecting a server with version 5.4.8 or greater, use the thumbnail + if (VersionCompare.compare(context.getGateway().getServerVersion(), VERSION_THUMBNAIL_NO_DEFAULT) >= 0) { + // If the client is connecting to a server with version 5.4.8 or greater, use the thumbnail // loading function that doesn't return a clock. return store.getThumbnailWithoutDefault(omero.rtypes.rint(sizeX), omero.rtypes.rint(sizeY)); @@ -353,10 +352,10 @@ private byte[] loadThumbnail(ThumbnailStorePrx store, IConfigPrx configService, * @param pxd * @return */ - private boolean requiresPixelsPyramid(IConfigPrx configService, PixelsData pxd) throws ServerError { - int maxWidth = Integer.parseInt(configService + private boolean requiresPixelsPyramid(PixelsData pxd) throws ServerError, DSOutOfServiceException { + int maxWidth = Integer.parseInt(getConfigService() .getConfigValue("omero.pixeldata.max_plane_width")); - int maxHeight = Integer.parseInt(configService + int maxHeight = Integer.parseInt(getConfigService() .getConfigValue("omero.pixeldata.max_plane_height")); return pxd.getSizeX() * pxd.getSizeY() > maxWidth * maxHeight; } diff --git a/components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java b/components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java index 8ef154f392b..ccbc5f970ef 100644 --- a/components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java +++ b/components/insight/SRC/org/openmicroscopy/shoola/util/VersionCompare.java @@ -3,17 +3,18 @@ public class VersionCompare { /** - * Compares two version strings. https://stackoverflow.com/a/6702029/272446 + * Compares two version strings. Source *

* Use this instead of String.compareTo() for a non-lexicographical * comparison that works for version strings. e.g. "1.10".compareTo("1.6"). + *

+ * Note: It does not work if "1.10" is supposed to be equal to "1.10.0". * * @param str1 a string of ordinal numbers separated by decimal points. * @param str2 a string of ordinal numbers separated by decimal points. * @return The result is a negative integer if str1 is _numerically_ less than str2. * The result is a positive integer if str1 is _numerically_ greater than str2. * The result is zero if the strings are _numerically_ equal. - * @note It does not work if "1.10" is supposed to be equal to "1.10.0". */ public static int compare(String str1, String str2) { String[] vals1 = str1.split("\\."); diff --git a/components/server/src/ome/services/ThumbnailBean.java b/components/server/src/ome/services/ThumbnailBean.java index be1db4f6fac..8007fc05f90 100644 --- a/components/server/src/ome/services/ThumbnailBean.java +++ b/components/server/src/ome/services/ThumbnailBean.java @@ -1249,7 +1249,7 @@ private byte[] retrieveThumbnail(Thumbnail thumbMetaData) throws ResourceError { } } - /** + /* * (non-Javadoc) * * @see ome.api.ThumbnailStore#getThumbnailByLongestSide(ome.model.core.Pixels,