/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2015 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.env.rnd; import java.awt.image.BufferedImage; import java.lang.management.ManagementFactory; import java.lang.management.MemoryUsage; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import omero.api.RenderingEnginePrx; import omero.model.ChannelBinding; import omero.model.Pixels; import omero.model.QuantumDef; import omero.model.RenderingDef; import omero.romio.PlaneDef; import org.openmicroscopy.shoola.env.Container; import org.openmicroscopy.shoola.env.LookupNames; import org.openmicroscopy.shoola.env.config.Registry; import omero.gateway.Gateway; import omero.gateway.SecurityContext; import omero.gateway.exception.DSOutOfServiceException; import omero.gateway.exception.RenderingServiceException; import omero.gateway.rnd.DataSink; import omero.log.Logger; import omero.gateway.model.ChannelData; import omero.gateway.model.PixelsData; /** * Factory to create the {@link RenderingControl} proxies. * This class keeps track of all {@link RenderingControl} instances * that have been created and are not yet closed. A new * component is only created if none of the <i>tracked</i> ones is already * active. Otherwise, the existing proxy is recycled. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk">a.falconi@dundee.ac.uk</a> * @since OME2.2 */ public class PixelsServicesFactory { /** The percentage of memory used for caching. */ private static final double RATIO = 0.10; /** Values used to determine the size of a cache. */ private static final int FACTOR = RenderingControl.MAX_SIZE*RenderingControl.MAX_SIZE; /** The sole instance. */ private static PixelsServicesFactory singleton; /** Reference to the container Registry. */ private static Registry registry; /** The maximum amount of memory in bytes used for caching. */ private static int maxSize; /** * Converts the {@link RenderingDef} into a {@link RndProxyDef}. * * @param rndDef The object to convert. * @return See above. */ public static RndProxyDef convert(RenderingDef rndDef) { if (rndDef == null) return null; RndProxyDef proxy = new RndProxyDef(rndDef); try { long v = rndDef.getDetails().getUpdateEvent().getTime().getValue(); proxy.setLastModified(new Timestamp(v)); } catch (Exception e) { //ignore; } if (rndDef.getName() != null) proxy.setName(rndDef.getName().getValue()); proxy.setDefaultZ(rndDef.getDefaultZ().getValue()); proxy.setDefaultT(rndDef.getDefaultT().getValue()); proxy.setColorModel(rndDef.getModel().getValue().getValue()); QuantumDef def = rndDef.getQuantization(); proxy.setCodomain(def.getCdStart().getValue(), def.getCdEnd().getValue()); proxy.setBitResolution(def.getBitResolution().getValue()); ChannelBinding c; Collection<ChannelBinding> bindings = rndDef.copyWaveRendering(); Iterator<ChannelBinding> k = bindings.iterator(); int i = 0; int[] rgba; ChannelBindingsProxy cb; while (k.hasNext()) { c = k.next(); cb = proxy.getChannel(i); if (cb == null) { cb = new ChannelBindingsProxy(); proxy.setChannel(i, cb); } if (c != null) { rgba = new int[4]; rgba[0] = c.getRed().getValue(); rgba[1] = c.getGreen().getValue(); rgba[2] = c.getBlue().getValue(); rgba[3] = c.getAlpha().getValue(); cb.setActive(c.getActive().getValue()); cb.setInterval(c.getInputStart().getValue(), c.getInputEnd().getValue()); cb.setRGBA(rgba); cb.setQuantization(c.getFamily().getValue().getValue(), c.getCoefficient().getValue(), c.getNoiseReduction().getValue()); } i++; } return proxy; } /** * Creates a new instance. This can't be called outside of container b/c * agents have no refs to the singleton container. So we can be sure this * method is going to create services just once. * * @param c Reference to the container. * @return The sole instance. * @throws NullPointerException If the reference to the {@link Container} * is <code>null</code>. */ public static PixelsServicesFactory getInstance(Container c) { if (c == null) throw new NullPointerException(); //An agent called this method? if (singleton == null) { registry = c.getRegistry(); singleton = new PixelsServicesFactory(); //Retrieve the maximum heap size. MemoryUsage usage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); String message = "Heap memory usage: max "+usage.getMax(); registry.getLogger().info(singleton, message); //percentage of memory used for caching. maxSize = (int) (RATIO*usage.getMax())/FACTOR; } return singleton; } /** * Creates a new {@link RenderingControl}. We pass a reference to the * the registry to ensure that agents don't call the method. * * @param context Reference to the registry. To ensure that agents * cannot call the method. * It must be a reference to the * container's registry. * @param ctx The security context. * @param reList The rendering proxies. * @param pixels The pixels set. * @param metadata The channel metadata. * @param compression Pass <code>0</code> if no compression otherwise * pass the compression used. * @param defs The rendering defs linked to the rendering engine. * This is passed to speed up the initialization * sequence. * @return See above. * @throws IllegalArgumentException If an Agent try to access the method. */ public static RenderingControl createRenderingControl(Registry context, SecurityContext ctx, List<RenderingEnginePrx> reList, Pixels pixels, List<ChannelData> metadata, int compression, List<RndProxyDef> defs) { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); return singleton.makeNew(ctx, reList, pixels, metadata, compression, defs); } /** * Reloads the rendering engine. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The ID of the pixels set. * @param reList The rendering proxies * @return See above. * @throws RenderingServiceException If an error occurred while setting * the value. * @throws DSOutOfServiceException If the connection is broken. */ public static RenderingControlProxy reloadRenderingControl(Registry context, long pixelsID, List<RenderingEnginePrx> reList) throws RenderingServiceException, DSOutOfServiceException { if (!(registry.equals(context))) throw new IllegalArgumentException("Not allow to access method."); if (reList == null || reList.size() == 0) throw new IllegalArgumentException("No RE specified."); RenderingControlProxy proxy = (RenderingControlProxy) singleton.rndSvcProxies.get(pixelsID); if (proxy != null) { proxy.shutDown(); proxy.setRenderingEngine(reList.get(0)); reList.remove(0); List<RenderingControl> slaves = proxy.getSlaves(); if (slaves.size() == reList.size()) { Iterator<RenderingControl> i = slaves.iterator(); int index = 0; while (i.hasNext()) { proxy = (RenderingControlProxy) i.next(); proxy.shutDown(); proxy.setRenderingEngine(reList.get(index)); } index++; } } return proxy; } /** * Resets the rendering engine. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The ID of the pixels set. * @param reList The rendering proxies * @param def The rendering def linked to the rendering engine. * This is passed to speed up the initialization sequence. * @return See above. * @throws RenderingServiceException If an error occurred while setting * the value. * @throws DSOutOfServiceException If the connection is broken. */ public static RenderingControlProxy resetRenderingControl(Registry context, long pixelsID, List<RenderingEnginePrx> reList, RenderingDef def) throws RenderingServiceException, DSOutOfServiceException { if (!(registry.equals(context))) throw new IllegalArgumentException("Not allow to access method."); if (reList == null || reList.size() == 0) throw new IllegalArgumentException("No RE specified."); RenderingControlProxy proxy = (RenderingControlProxy) singleton.rndSvcProxies.get(pixelsID); if (proxy != null) { RndProxyDef converted = convert(def); proxy.resetRenderingEngine(reList.get(0), converted); reList.remove(0); List<RenderingControl> slaves = proxy.getSlaves(); if (slaves.size() == reList.size()) { Iterator<RenderingControl> i = slaves.iterator(); int index = 0; while (i.hasNext()) { proxy = (RenderingControlProxy) i.next(); proxy.resetRenderingEngine(reList.get(index), converted); } index++; } } return proxy; } /** * Shuts downs the rendering service attached to the specified * pixels set. Returns <code>true</code> if the rendering control is shared, * <code>false</code> otherwise. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The ID of the pixels set. * @return See above. */ public static boolean shutDownRenderingControl(Registry context, long pixelsID) { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); RenderingControlProxy proxy = (RenderingControlProxy) singleton.rndSvcProxies.get(pixelsID); Integer count = singleton.rndSvcProxiesCount.get(pixelsID); if (proxy != null) { if (count == 1) { proxy.shutDown(); singleton.rndSvcProxies.remove(pixelsID); singleton.rndSvcProxiesCount.remove(pixelsID); getCacheSize(); } else { count--; singleton.rndSvcProxiesCount.put(pixelsID, count); } return (singleton.rndSvcProxiesCount.containsKey(pixelsID)); } return false; } /** * Shuts downs all running rendering services. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * */ public static void shutDownRenderingControls(Registry context) { //Note that the class should be deleted. singleton.rndSvcProxies.clear(); singleton.rndSvcProxiesCount.clear(); } /** * Checks if the rendering controls are still active. Shuts the inactive * ones. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. */ public static void checkRenderingControls(Registry context) { // TODO Auto-generated method stub if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); RenderingControlProxy proxy; Entry<Long, RenderingControl> e; Iterator<Entry<Long, RenderingControl>> i = singleton.rndSvcProxies.entrySet().iterator(); Long value = (Long) context.lookup(LookupNames.RE_TIMEOUT); long timeout = 60000; //1min if (value != null && value.longValue() > timeout) timeout = value.longValue(); Logger logger = context.getLogger(); while (i.hasNext()) { e = i.next(); proxy = (RenderingControlProxy) e.getValue(); if (!proxy.isProxyActive(timeout)) { if (!proxy.shutDown(true)) logger.info(singleton, "Rendering Engine shut down: PixelsID "+e.getKey()); } } } /** * Returns the {@link RenderingControl} linked to the passed set of pixels, * returns <code>null</code> if no proxy associated. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The id of the pixels set. * @param init Pass <code>true</code> if the rendering control * is going to be initialized, <code>false</code> * otherwise. * @return See above. */ public static RenderingControl getRenderingControl(Registry context, Long pixelsID, boolean init) { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); if (init && singleton.rndSvcProxiesCount.containsKey(pixelsID)) { Integer count = singleton.rndSvcProxiesCount.get(pixelsID); count++; singleton.rndSvcProxiesCount.put(pixelsID, count); } return singleton.rndSvcProxies.get(pixelsID); } /** * Returns <code>true</code> if the proxy is used elsewhere. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The id of the pixels set. * @return See above. */ public static boolean isRenderingControlShared(Registry context, Long pixelsID) { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); if (!singleton.rndSvcProxiesCount.containsKey(pixelsID)) return false; Integer count = singleton.rndSvcProxiesCount.get(pixelsID); return (count > 1); } /** * Creates a new data sink for the specified set of pixels. * * @param pixels The pixels set the data sink is for. * @return See above. */ public static DataSink createDataSink(PixelsData pixels, Gateway gw) { if (pixels == null) throw new IllegalArgumentException("Pixels cannot be null."); if (singleton.pixelsSource != null && singleton.pixelsSource.isSame(pixels.getId())) return singleton.pixelsSource; registry.getCacheService().clearAllCaches(); int size = getCacheSize(); if (size <= 0) size = 0; singleton.pixelsSource = DataSink.makeNew(pixels, registry.getGateway(), size); return singleton.pixelsSource; } /** * Shuts downs the data sink attached to the specified pixels set. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The ID of the pixels set. */ public static void shutDownDataSink(Registry context, long pixelsID) { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); if (singleton.pixelsSource != null && singleton.pixelsSource.isSame(pixelsID)) { int size = getCacheSize(); boolean cacheInMemory = true; if (size <= 0) cacheInMemory = false; singleton.pixelsSource.clearCache(); singleton.pixelsSource.setCacheInMemory(cacheInMemory); } } /** * Renders the specified {@link PlaneDef 2D-plane}. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The id of the pixels set. * @param pDef The plane to render. * @param largeImage Indicates to set the resolution to <code>0</code>. * @param compression The compression level. * @return The image representing the plane. * @return See above. * * @throws RenderingServiceException If an error occurred while setting * the value. * @throws DSOutOfServiceException If the connection is broken. */ public static Object render(Registry context, SecurityContext ctx, Long pixelsID, PlaneDef pDef, boolean largeImage, int compression) throws RenderingServiceException, DSOutOfServiceException { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); RenderingControlProxy proxy = (RenderingControlProxy) singleton.rndSvcProxies.get(pixelsID); if (proxy == null) throw new RuntimeException("No rendering service " + "initialized for the specified pixels set."); int level = proxy.getSelectedResolutionLevel(); if (compression < RenderingControl.UNCOMPRESSED) compression = RenderingControl.UNCOMPRESSED; if (compression > RenderingControl.LOW) compression = RenderingControl.LOW; proxy.setCompression(compression); if (largeImage) { proxy.setSelectedResolutionLevel(0); } Object o = proxy.render(pDef); if (largeImage) proxy.setSelectedResolutionLevel(level); return o; } /** * Renders the specified {@link PlaneDef 2D-plane}. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The id of the pixels set. * @param pd The plane to render. * @param tableID The id of the table hosting the mask. * @param overlays The overlays to render or <code>null</code>. * @return See above. * * @throws RenderingServiceException If an error occurred while setting * the value. * @throws DSOutOfServiceException If the connection is broken. */ public static Object renderOverlays(Registry context, long pixelsID, PlaneDef pd, long tableID, Map<Long, Integer> overlays) throws RenderingServiceException, DSOutOfServiceException { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); RenderingControlProxy proxy = (RenderingControlProxy) singleton.rndSvcProxies.get(pixelsID); if (proxy == null) throw new RuntimeException("No rendering service " + "initialized for the specified pixels set."); proxy.setOverlays(tableID, overlays); return proxy.render(pd); } /** * Renders the projected images. * * @param context Reference to the registry. To ensure that agents cannot * call the method. It must be a reference to the * container's registry. * @param pixelsID The id of the pixels set. * @param startZ The first optical section. * @param endZ The last optical section. * @param stepping Stepping value to use while calculating the projection. * @param type One of the projection type defined by this class. * @param channels The collection of channels to project. * @return See above. * @throws RenderingServiceException If an error occurred while setting * the value. * @throws DSOutOfServiceException If the connection is broken. */ public static BufferedImage renderProjected(Registry context, Long pixelsID, int startZ, int endZ, int type, int stepping, List<Integer> channels) throws RenderingServiceException, DSOutOfServiceException { if (!(context.equals(registry))) throw new IllegalArgumentException("Not allow to access method."); RenderingControlProxy proxy = (RenderingControlProxy) singleton.rndSvcProxies.get(pixelsID); if (proxy == null) throw new RuntimeException("No rendering service " + "initialized for the specified pixels set."); return proxy.renderProjected(startZ, endZ, stepping, type, channels); } /** * Returns the compression quality related to the passed level. * * @param compressionLevel The level to handle. * @return See above. */ static final float getCompressionQuality(int compressionLevel) { Float value; switch (compressionLevel) { default: case RenderingControl.UNCOMPRESSED: case RenderingControl.MEDIUM: value = (Float) registry.lookup( LookupNames.COMPRESSIOM_MEDIUM_QUALITY); return value.floatValue(); case RenderingControl.LOW: value = (Float) registry.lookup( LookupNames.COMPRESSIOM_LOW_QUALITY); return value.floatValue(); } } /** Keep track of all the rendering service already initialized. */ private Map<Long, RenderingControl> rndSvcProxies; /** Access to the raw data. */ private DataSink pixelsSource; /** * Keep track of the number of times the rendering proxy has been requested * to be initialized. */ private Map<Long, Integer> rndSvcProxiesCount; /** Creates the sole instance. */ private PixelsServicesFactory() { rndSvcProxies = new HashMap<Long, RenderingControl>(); rndSvcProxiesCount = new HashMap<Long, Integer>(); } /** * Makes a new {@link RenderingControl}. * * @param reList The rendering engines. * @param pixels The pixels set. * @param metadata The related metadata. * @param compression Pass <code>0</code> if no compression otherwise * pass the compression used. * @param defs The rendering definitions linked to the * rendering engine.This is passed to speed up the initialization sequence. * @return See above. */ private RenderingControl makeNew(SecurityContext ctx, List<RenderingEnginePrx> reList, Pixels pixels, List<ChannelData> metadata, int compression, List<RndProxyDef> defs) { if (singleton == null) throw new NullPointerException(); Long id = pixels.getId().getValue(); RenderingControl rnd = (RenderingControl) singleton.rndSvcProxies.get(id); if (rnd != null) return rnd; singleton.rndSvcProxiesCount.put(id, 1); RenderingEnginePrx master = reList.get(0); int size = getCacheSize(); reList.remove(0); rnd = new RenderingControlProxy(registry, ctx, master, pixels, metadata, compression, defs, size); Iterator<RenderingEnginePrx> i = reList.iterator(); if (reList.size() > 0) { List<RenderingControl> slaves = new ArrayList<RenderingControl>(reList.size()); while (i.hasNext()) { slaves.add(new RenderingControlProxy(registry, ctx, i.next(), pixels, metadata, compression, defs, size)); } ((RenderingControlProxy) rnd).setSlaves(slaves); } singleton.rndSvcProxies.put(id, rnd); return rnd; } /** * Returns the size of the cache. * * @return See above. */ private static int getCacheSize() { MemoryUsage usage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); //percentage of memory used for caching. maxSize = (int) (RATIO*(usage.getMax()-usage.getUsed()))/FACTOR; int m = singleton.rndSvcProxies.size(); int n = 0; int sizeCache = 0; RenderingControlProxy proxy; Entry<Long, RenderingControl> entry; Iterator<Entry<Long, RenderingControl>> i; if (singleton.pixelsSource != null) n = 1; if (n == 0 && m == 0) return maxSize*FACTOR; else if (n == 0 && m > 0) { sizeCache = (maxSize/(m+1))*FACTOR; //reset all the image caches. i = singleton.rndSvcProxies.entrySet().iterator(); while (i.hasNext()) { entry = i.next(); proxy = (RenderingControlProxy) entry.getValue(); proxy.setCacheSize(sizeCache); } return sizeCache; } else if (m == 0 && n > 0) { sizeCache = (maxSize/(n+1))*FACTOR; return sizeCache; } sizeCache = (maxSize/(m+n+1))*FACTOR; //reset all the image caches. i = singleton.rndSvcProxies.entrySet().iterator(); while (i.hasNext()) { entry = i.next(); proxy = (RenderingControlProxy) entry.getValue(); proxy.setCacheSize(sizeCache); } return sizeCache; } }