/* * ome.util.PixelData * * Copyright 2007-2014 Glencoe Software Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.util; import java.lang.reflect.Method; import java.nio.ByteOrder; import java.nio.ByteBuffer; import loci.formats.FormatTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a block of pixel data. * * @author Chris Allan      <a * href="mailto:chris@glencoesoftware.com">chris@glencoesoftware.com</a> * @author m.t.b.carroll@dundee.ac.uk * @version $Revision$ * @since 3.0 * @see "ome.io.nio.PixelBuffer" */ public class PixelData { public static final String CONFIG_KEY = "omero.pixeldata.dispose"; private static final Logger LOG = LoggerFactory.getLogger(PixelData.class); /* set to sun.nio.ch.DirectBuffer.cleaner() only if it is both available and to be invoked, otherwise null */ private static final Method DIRECT_BUFFER_CLEANER; /* the clean() method of DIRECT_BUFFER_CLEANER.cleaner() */ private static final Method DIRECT_BUFFER_CLEANER_CLEAN; static { final String configValue = System.getProperties().getProperty(CONFIG_KEY); final Boolean dispose; /* parse config value and log accordingly */ if ("true".equalsIgnoreCase(configValue)) { dispose = Boolean.TRUE; } else if ("false".equalsIgnoreCase(configValue)) { dispose = Boolean.FALSE; } else { dispose = null; } if (dispose == null) { LOG.warn("{} cannot be set to invalid value {}, must be true or false", CONFIG_KEY, configValue); } else { if (LOG.isDebugEnabled()) { LOG.debug("{} set to {}", CONFIG_KEY, dispose.toString()); } } if (Boolean.TRUE.equals(dispose)) { Method cleanerGetter = null; Method cleaner = null; try { /* find cleaner() on DirectBuffer, if the class can be loaded at all */ final Class<?> directBufferClass = Class.forName("sun.nio.ch.DirectBuffer"); FIND_CLEANER_CLEAN: for (final Method directBufferMethod : directBufferClass.getMethods()) { if (!directBufferMethod.isBridge() && !directBufferMethod.isSynthetic() && "cleaner".equals(directBufferMethod.getName()) && directBufferMethod.getParameterTypes().length == 0) { /* find void clean() on cleaner() */ final Class<?> cleanerClass = directBufferMethod.getReturnType(); for (final Method cleanerMethod : cleanerClass.getMethods()) { if (!cleanerMethod.isBridge() && !cleanerMethod.isSynthetic() && "clean".equals(cleanerMethod.getName()) && cleanerMethod.getReturnType() == void.class && cleanerMethod.getParameterTypes().length == 0) { cleanerGetter = directBufferMethod; cleaner = cleanerMethod; break FIND_CLEANER_CLEAN; } } } } } catch (Exception e) { /* logged below */ } if (cleanerGetter == null) { LOG.warn("{} set to true, but cannot be actioned in the JVM", CONFIG_KEY); DIRECT_BUFFER_CLEANER = null; DIRECT_BUFFER_CLEANER_CLEAN = null; } else { DIRECT_BUFFER_CLEANER = cleanerGetter; DIRECT_BUFFER_CLEANER_CLEAN = cleaner; } } else { DIRECT_BUFFER_CLEANER = null; DIRECT_BUFFER_CLEANER_CLEAN = null; } } /** Identifies the type used to store pixel values. */ public static final int BYTE = 0; /** Identifies the type used to store pixel values. */ public static final int SHORT = 1; /** Identifies the type used to store pixel values. */ public static final int INT = 2; /** Identifies the type used to store pixel values. */ public static final int LONG = 3; /** Identifies the type used to store pixel values. */ public static final int FLOAT = 4; /** Identifies the type used to store pixel values. */ public static final int DOUBLE = 5; /** Identifies the type used to store pixel values. */ public static final int BIT = 6; /** Type of the pixel data. */ protected String pixelsType; /** The pixels data backing buffer. */ protected ByteBuffer data; /** If the data is signed. */ protected boolean isSigned; /** If the data is floating point. */ protected boolean isFloat; /** The pixels type as it would be represented in Java. */ protected int javaType; /** The number of bytes per pixel. */ protected int bytesPerPixel; /** The minimum pixel value for the pixels type of the pixel data. */ protected double minimum; /** The maximum pixel value for the pixels type of the pixel data. */ protected double maximum; /** * Default constructor. * * @param pixelsType The OME pixels type. * @param data The raw pixel data. */ public PixelData(String pixelsType, ByteBuffer data) { this.data = data; this.pixelsType = pixelsType; bytesPerPixel = bytesPerPixel(); int type = FormatTools.pixelTypeFromString(pixelsType); long[] values = FormatTools.defaultMinMax(type); isSigned = FormatTools.isSigned(type); isFloat = FormatTools.isFloatingPoint(type); minimum = values[0]; maximum = values[1]; switch(type) { case FormatTools.INT8: case FormatTools.UINT8: javaType = BYTE; break; case FormatTools.INT16: case FormatTools.UINT16: javaType = SHORT; break; case FormatTools.INT32: case FormatTools.UINT32: javaType = INT; break; case FormatTools.FLOAT: javaType = FLOAT; break; case FormatTools.DOUBLE: javaType = DOUBLE; break; case FormatTools.BIT: javaType = BIT; } } /** * Returns whether or not the pixel data type is is one of the elements in * an array. * * @param strings The strings for which you want to check against. * @return See above. */ public boolean in(String[] strings) { for (int i = 0; i < strings.length; i++) { if (pixelsType.equals(strings[i])) { return true; } } return false; } /** * Returns the number of byte per pixel for the pixel data. * * @return See above. */ public int bytesPerPixel() { int type = FormatTools.pixelTypeFromString(pixelsType); return FormatTools.getBytesPerPixel(type); } /** * Returns whether or not the data is signed. * * @return See above. */ public boolean isSigned() { return isSigned; } /** * Returns whether or not the data is floating point. * * @return See above. */ public boolean isFloat() { return isFloat; } /** * Returns the Java type that has the same byte width of the pixel data. * * @return See above. */ public int javaType() { return javaType; } /** * Returns the minimum pixel value this pixel data supports. * * @return See above. */ public double getMinimum() { return minimum; } /** * Returns the minimum pixel value this pixel data supports. * * @return See above. */ public double getMaximum() { return maximum; } /** * Sets the pixel intensity value of the pixel at a given offset within * the backing buffer. This method takes into account bytes per pixel. * * @param offset The relative offset (taking into account the number of * bytes per pixel) within the backing buffer. * @param value Pixel value to set. */ public void setPixelValue(int offset, double value) { setPixelValueDirect(offset * bytesPerPixel, value); } /** * Sets the pixel intensity value of the pixel at a given offset within * the backing buffer. This method does not take into account bytes per * pixel. * * @param offset The absolute offset within the backing buffer. * @param value Pixel value to set. */ public void setPixelValueDirect(int offset, double value) { switch (javaType) { case BIT: int byteOffset = offset / 8; byte x = data.get(byteOffset); if (value == 0) { data.put(byteOffset, (byte) (x & ~(1 << (7 - (offset % 8))))); break; } data.put(byteOffset, (byte) (x | 1 << (7 - (offset % 8)))); break; case BYTE: data.put(offset, (byte) value); break; case SHORT: data.putShort(offset, (short) value); break; case INT: data.putInt(offset, (int) value); break; case FLOAT: data.putFloat(offset, (float) value); break; case DOUBLE: data.putDouble(offset, value); break; } } /** * Returns the pixel intensity value of the pixel at a given offset within * the backing buffer. This method takes into account bytes per pixel. * * @param offset The relative offset (taking into account the number of * bytes per pixel) within the backing buffer. * @return The intensity value. */ public double getPixelValue(int offset) { return getPixelValueDirect(offset * bytesPerPixel); } /** * Returns the pixel intensity value of the pixel at a given offset within * the backing buffer. This method does not take into account bytes per * pixel. * * @param offset The absolute offset within the backing buffer. * @return The intensity value. */ public double getPixelValueDirect(int offset) { if (isSigned()) { switch (javaType) { case BYTE: return data.get(offset); case SHORT: return data.getShort(offset); case INT: return data.getInt(offset); case FLOAT: return data.getFloat(offset); case DOUBLE: return data.getDouble(offset); } } else { switch (javaType) { case BIT: return data.get(offset / 8) >> (7 - (offset % 8)) & 1; case BYTE: return (short) (data.get(offset) & 0xFF); case SHORT: return data.getShort(offset) & 0xFFFF; case INT: return data.getInt(offset) & 0xFFFFFFFFL; case FLOAT: return data.getFloat(offset); case DOUBLE: return data.getDouble(offset); } } throw new RuntimeException("Unknown pixel type."); } /** * Returns the backing buffer for the pixel data. * * @return See above. */ public ByteBuffer getData() { return data; } /** * Returns the byte order of the backing buffer. * * @return See above. */ public ByteOrder getOrder() { return data.order(); } /** * Set the byte order of the backing buffer. * * @param order The byte order. */ public void setOrder(ByteOrder order) { data.order(order); } /** * Returns the pixel count of this block of pixel data. * * @return See above. */ public int size() { return data.capacity() / bytesPerPixel; } /** * Retrieves the bit width of a particular <code>PixelsType</code>. * * @param type * a pixel type. * @return width of a single pixel value in bits. */ public static int getBitDepth(String type) { int value = FormatTools.pixelTypeFromString(type); switch(value) { case FormatTools.INT8: case FormatTools.UINT8: return 8; case FormatTools.INT16: case FormatTools.UINT16: return 16; case FormatTools.INT32: case FormatTools.UINT32: case FormatTools.FLOAT: return 32; case FormatTools.DOUBLE: return 64; case FormatTools.BIT: return 1; } throw new RuntimeException("Pixels type '" + type + "' unsupported by nio."); } /** * Attempt to free up any native memory resources associated with the data buffer. * This is a temporary workaround hoped to ameliorate trac ticket #11250. * This {@link PixelData} instance <em>must not</em> be accessed by any thread after this method is called. * If not called, the resources should eventually be freed anyway by garbage collection and finalization. */ public void dispose() { if (DIRECT_BUFFER_CLEANER != null && this.data != null && DIRECT_BUFFER_CLEANER.getDeclaringClass().isAssignableFrom(this.data.getClass())) { try { final Object cleaner = DIRECT_BUFFER_CLEANER.invoke(this.data); if (cleaner != null) { DIRECT_BUFFER_CLEANER_CLEAN.invoke(cleaner); } } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug("DirectBuffer disposal failed", e); } } this.data = null; } } }