/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2014 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 omero.gateway.rnd; import omero.api.RawPixelsStorePrx; import omero.gateway.Gateway; import omero.gateway.SecurityContext; import omero.gateway.cache.CacheService; import omero.gateway.exception.DataSourceException; import omero.util.ReadOnlyByteArray; import omero.gateway.model.PixelsData; /** * Encapsulates access to the image raw data. * Contains the logic to interpret a linear byte array as a 5D array. * Knows how to extract a 2D-plane from the 5D array, but delegates to the * specified 2D-Plane the retrieval of pixel values. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * @since OME3.0 */ public class DataSink { /** Identifies the type used to store pixel values. */ public static final String INT_8 = PixelsData.INT8_TYPE; /** Identifies the type used to store pixel values. */ public static final String UINT_8 = PixelsData.UINT8_TYPE; /** Identifies the type used to store pixel values. */ public static final String INT_16 = PixelsData.INT16_TYPE; /** Identifies the type used to store pixel values. */ public static final String UINT_16 = PixelsData.UINT16_TYPE; /** Identifies the type used to store pixel values. */ public static final String INT_32 = PixelsData.INT32_TYPE; /** Identifies the type used to store pixel values. */ public static final String UINT_32 = PixelsData.UINT32_TYPE; /** Identifies the type used to store pixel values. */ public static final String FLOAT = PixelsData.FLOAT_TYPE; /** Identifies the type used to store pixel values. */ public static final String DOUBLE = PixelsData.DOUBLE_TYPE; /** * Factory method to create a new <code>DataSink</code> to handle * access to the metadata associated with the specified pixels set. * * @param source The pixels set. Mustn't be <code>null</code>. * @param gw Reference to the {@link Gateway} Mustn't be <code>null</code>. * @return See above. */ public static DataSink makeNew(PixelsData source, Gateway gw) { if (source == null) throw new NullPointerException("No pixels."); if (gw == null) throw new NullPointerException("No Gateway."); return new DataSink(source, gw); } /** * Factory method to create a new <code>DataSink</code> to handle access to * the metadata associated with the specified pixels set. * * @param source * The pixels set. Mustn't be <code>null</code>. * @param gw * Reference to the {@link Gateway} Mustn't be <code>null</code>. * @param cacheSize * The size of the cache to use. (Make sure the {@link Gateway} provides * a {@link CacheService}) * @return See above. */ public static DataSink makeNew(PixelsData source, Gateway gw, int cacheSize) { if (source == null) throw new NullPointerException("No pixels."); if (gw == null) throw new NullPointerException("No Gateway."); return new DataSink(source, gw, cacheSize); } /** The data source. */ private PixelsData source; /** The number of bytes per pixel. */ private int bytesPerPixels; /** Strategy used to transform the raw data. */ private BytesConverter strategy; /** The id of the cache. */ private int cacheID = -1; /** The pixels store for that pixels set.*/ private RawPixelsStorePrx store; /**Reference to the gateway.*/ private Gateway gw; /** * Creates a new instance without using a cache. * * @param source The pixels set. * @param context The container's registry. */ private DataSink(PixelsData source, Gateway gw) { this(source, gw, 0); } /** * Creates a new instance. * * @param source The pixels set. * @param context The container's registry. * @param cacheSize The size of the cache. */ private DataSink(PixelsData source, Gateway gw, int cacheSize) { this.gw = gw; this.source = source; String type = source.getPixelType(); bytesPerPixels = getBytesPerPixels(type); if (cacheSize > 0) { if (gw.getCacheService() == null) throw new IllegalArgumentException("No cache provided!"); int maxEntries = cacheSize/(source.getSizeX()*source.getSizeY()*bytesPerPixels); cacheID = gw.getCacheService().createCache( CacheService.IN_MEMORY, maxEntries); } strategy = BytesConverter.getConverter(type); } /** * Returns the number of bytes per pixel depending on the pixel type. * * @param v The pixels Type. * @return See above. */ private int getBytesPerPixels(String v) { if (INT_8.equals(v) || UINT_8.equals(v)) return 1; if (INT_16.equals(v) || UINT_16.equals(v)) return 2; if (INT_32.equals(v) || UINT_32.equals(v) || FLOAT.equals(v)) return 4; if (DOUBLE.equals(v)) return 8; return -1; } /** * Transforms 3D coords into linear coords. * The returned value <code>L</code> is calculated as follows: * <nobr><code>L = sizeZ*sizeC*t + sizeZ*w + z</code></nobr>. * * @param z The z coord. Must be in the range <code>[0, sizeZ)</code>. * @param c The c coord. Must be in the range <code>[0, sizeC)</code>. * @param t The t coord. Must be in the range <code>[0, sizeT)</code>. * @return The linearized value corresponding to <code>(z, c, t)</code>. */ private Integer linearize(int z, int c, int t) { int sizeZ = source.getSizeZ(); int sizeC = source.getSizeC(); if (z < 0 || sizeZ <= z) throw new IllegalArgumentException( "z out of range [0, "+sizeZ+"): "+z+"."); if (c < 0 || sizeC <= c) throw new IllegalArgumentException( "c out of range [0, "+sizeC+"): "+c+"."); if (t < 0 || source.getSizeT() <= t) throw new IllegalArgumentException( "t out of range [0, "+source.getSizeT()+"): "+t+"."); return Integer.valueOf(sizeZ*sizeC*t + sizeZ*c + z); } /** * Factory method to fetch plane data and create an object to access it. * * @param ctx The security context. * @param z The z-section at which data is to be fetched. * @param t The timepoint at which data is to be fetched. * @param c The channel at which data is to be fetched. * @param strategy To transform bytes into pixels values. * @param close Indicates to close the service if <code>true</code>. * @return A plane 2D object that encapsulates the actual plane pixels. * @throws DataSourceException If an error occurs while retrieving the * plane data from the pixels source. */ private Plane2D createPlane(SecurityContext ctx, int z, int t, int c, BytesConverter strategy, boolean close) throws DataSourceException { //Retrieve data Integer planeIndex = linearize(z, c, t); Plane2D plane = null; if (cacheID >= 0) { CacheService cache = gw.getCacheService(); plane = (Plane2D) cache.getElement(cacheID, planeIndex); if (plane != null) return plane; } byte[] data = null; try { //initializes if null. if (store == null) { store = gw.createPixelsStore(ctx); store.setPixelsId(source.getId(), false); } data = store.getPlane(z, c, t); } catch (Exception e) { String p = "("+z+", "+c+", "+t+")"; throw new DataSourceException("Cannot retrieve the plane "+p, e); } finally { if (close) { gw.closeService(ctx, store); store = null; } } ReadOnlyByteArray array = new ReadOnlyByteArray(data, 0, data.length); plane = new Plane2D(array, source.getSizeX(), source.getSizeY(), bytesPerPixels, strategy); if (cacheID >= 0) gw.getCacheService().addElement(cacheID, planeIndex, plane); return plane; } /** * Extracts a 2D tile from the pixels set this object is working for. * * @param ctx * The security context. * @param z * The z-section at which data is to be fetched. * @param t * The timepoint at which data is to be fetched. * @param c * The channel at which data is to be fetched. * @param x * The x coordinate * @param y * The y coordinate * @param w * The width of the tile * @param h * The height of the tile * @param close * Indicates to close the service if <code>true</code>. * @return A plane 2D object that encapsulates the actual plane pixels. * @throws DataSourceException * If an error occurs while retrieving the plane data from the * pixels source. */ public Plane2D getTile(SecurityContext ctx, int z, int t, int c, int x, int y, int w, int h, boolean close) throws DataSourceException { byte[] data = null; try { // initializes if null. if (store == null) { store = gw.createPixelsStore(ctx); store.setPixelsId(source.getId(), false); } data = store.getTile(z, c, t, x, y, w, h); } catch (Exception e) { String p = "(" + z + ", " + c + ", " + t + ", " + x + ", " + y + ", " + w + ", " + h + ")"; throw new DataSourceException("Cannot retrieve the plane " + p, e); } finally { if (close) { gw.closeService(ctx, store); store = null; } } ReadOnlyByteArray array = new ReadOnlyByteArray(data, 0, data.length); return new Plane2D(array, w, h, bytesPerPixels, strategy); } /** * Extracts a 2D plane from the pixels set this object is working for. * * @param ctx The security context. * @param z The z-section at which data is to be fetched. * @param t The timepoint at which data is to be fetched. * @param c The channel at which data is to be fetched. * @return A plane 2D object that encapsulates the actual plane pixels. * @throws DataSourceException If an error occurs while retrieving the * plane data from the pixels source. */ public Plane2D getPlane(SecurityContext ctx, int z, int t, int c) throws DataSourceException { return createPlane(ctx, z, t, c, strategy, true); } /** * Extracts a 2D plane from the pixels set this object is working for. * * @param ctx The security context. * @param z The z-section at which data is to be fetched. * @param t The timepoint at which data is to be fetched. * @param c The channel at which data is to be fetched. * @param close Indicate to close or not the service. * @return A plane 2D object that encapsulates the actual plane pixels. * @throws DataSourceException If an error occurs while retrieving the * plane data from the pixels source. */ public Plane2D getPlane(SecurityContext ctx, int z, int t, int c, boolean close) throws DataSourceException { return createPlane(ctx, z, t, c, strategy, close); } /** * Returns <code>true</code> if a data source has already been created * for the specified pixels set, <code>false</code> otherwise. * * @param pixelsID The id of the pixels set. * @return See above. */ public boolean isSame(long pixelsID) { return pixelsID == source.getId(); } /** Erases the cache. */ public void clearCache() { if (cacheID == -1) return; gw.getCacheService().clearCache(cacheID); } /** * Sets the size either to 1 or 0 depending on the passed value. * * @param cacheInMemory Passed <code>true</code> to set the size to 1, * <code>false</code> to set to 0. */ public void setCacheInMemory(boolean cacheInMemory) { if (cacheID == -1) return; clearCache(); if (cacheInMemory) gw.getCacheService().setCacheEntries(cacheID, 1); else gw.getCacheService().setCacheEntries(cacheID, 0); } }