/* * ome.io.nio.PixelsService * * Copyright 2006-2013 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.io.nio; import java.awt.Dimension; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteOrder; import java.util.Iterator; import java.util.List; import loci.formats.ChannelFiller; import loci.formats.ChannelSeparator; import loci.formats.FormatException; import loci.formats.IFormatReader; import loci.formats.ImageReader; import loci.formats.Memoizer; import loci.formats.MinMaxCalculator; import loci.formats.meta.IMinMaxStore; import ome.api.IQuery; import ome.conditions.LockTimeout; import ome.conditions.MissingPyramidException; import ome.conditions.ResourceError; import ome.io.bioformats.BfPixelBuffer; import ome.io.bioformats.BfPyramidPixelBuffer; import ome.io.messages.MissingPyramidMessage; import ome.io.messages.MissingStatsInfoMessage; import ome.parameters.Parameters; import ome.system.metrics.Metrics; import ome.system.metrics.Timer; import ome.model.core.Pixels; import ome.model.stats.StatsInfo; import ome.util.PixelData; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.FatalBeanException; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; /** * @author <br> * Chris Allan     <a * href="mailto:callan@blackcat.ca">callan@blackcat.ca</a> * @version 3.0 <small> (<b>Internal version:</b> $Revision$ $Date$) </small> * @since OMERO-Beta1.0 */ public class PixelsService extends AbstractFileSystemService implements ApplicationEventPublisherAware { /** The logger for this class. */ private transient static Logger log = LoggerFactory.getLogger(PixelsService.class); /** Publisher interface used to publish messages concerning missing * data and similar. */ private transient ApplicationEventPublisher pub; /** Suffix for an the image pyramid of a given pixels set. */ public static final String PYRAMID_SUFFIX = "_pyramid"; /** Null plane size constant. */ public static final int NULL_PLANE_SIZE = 64; /** Default of 100 ms for {@link #memoizerWait} */ public static final long MEMOIZER_WAIT = 100; /** Resolver of archived original file paths for pixels sets. */ protected FilePathResolver resolver; /** BackOff implementation for calculating MissingPyramidExceptions */ protected final BackOff backOff; /** TileSizes implementation for default values */ protected final TileSizes sizes; /** * Location where cached data from the {@link Memoizer} should be stored. */ protected final File memoizerDirectory; /** * Time in ms. which setId must take before a file is memoized */ protected final long memoizerWait; private Timer tileTimes; private Timer minmaxTimes; private IQuery iQuery; /** Null plane byte array. */ public static final byte[] nullPlane = new byte[] { -128, 127, -128, 127, -128, 127, -128, 127, -128, 127, // 10 -128, 127, -128, 127, -128, 127, -128, 127, -128, 127, // 20 -128, 127, -128, 127, -128, 127, -128, 127, -128, 127, // 30 -128, 127, -128, 127, -128, 127, -128, 127, -128, 127, // 40 -128, 127, -128, 127, -128, 127, -128, 127, -128, 127, // 50 -128, 127, -128, 127, -128, 127, -128, 127, -128, 127, // 60 -128, 127, -128, 127 }; // 64 /** * Constructor. * @param path The root of the ROMIO proprietary pixels store. (usually * <code>/OMERO/Pixels</code>). */ public PixelsService(String path) { this(path, null, new SimpleBackOff(), new ConfiguredTileSizes(), null); } /** * Constructor. * @param path The root of the ROMIO proprietary pixels store. (usually * <code>/OMERO/Pixels</code>). */ public PixelsService(String path, FilePathResolver resolver) { this(path, resolver, new SimpleBackOff(), new ConfiguredTileSizes(), null); } /** * Constructor. * @param path The root of the ROMIO proprietary pixels store. (usually * <code>/OMERO/Pixels</code>). * @param resolver Original file path resolver for pixels sets. */ public PixelsService(String path, FilePathResolver resolver, BackOff backOff, TileSizes sizes, IQuery iQuery) { this(path, new File(new File(path), "BioFormatsCache"), resolver, backOff, sizes, iQuery); } /** * Call {@link #PixelsService(String, File, long, FilePathResolver, BackOff, TileSizes, IQuery)} * with {@link #MEMOIZER_WAIT}. */ public PixelsService(String path, long memoizerWait, FilePathResolver resolver, BackOff backOff, TileSizes sizes, IQuery iQuery) { this(path, new File(new File(path), "BioFormatsCache"), memoizerWait, resolver, backOff, sizes, iQuery); } /** * Call {@link #PixelsService(String, File, long, FilePathResolver, BackOff, TileSizes, IQuery)} * with {@link #MEMOIZER_WAIT}. */ public PixelsService(String path, File memoizerDirectory, FilePathResolver resolver, BackOff backOff, TileSizes sizes, IQuery iQuery) { this(path, memoizerDirectory, MEMOIZER_WAIT, resolver, backOff, sizes, iQuery); } public PixelsService(String path, File memoizerDirectory, long memoizerWait, FilePathResolver resolver, BackOff backOff, TileSizes sizes, IQuery iQuery) { super(path); this.resolver = resolver; this.backOff = backOff; this.sizes = sizes; this.memoizerDirectory = memoizerDirectory; this.memoizerWait = memoizerWait; if (!this.memoizerDirectory.exists()) { log.info("Creating Bio-Formats Cache: {}", memoizerDirectory); this.memoizerDirectory.mkdirs(); } else { log.info("Using Bio-Formats Cache: {}", memoizerDirectory); } if (log.isInfoEnabled()) { log.info("PixelsService(path=" + path + ", resolver=" + resolver + ", backoff=" + backOff + ", sizes=" + sizes + ")"); } this.iQuery = iQuery; } public void setMetrics(Metrics metrics) { this.tileTimes = metrics.timer(this, "tileTimes"); this.minmaxTimes = metrics.timer(this, "minmaxTimes"); } public long getMemoizerWait() { return memoizerWait; } public File getMemoizerDirectory() { return memoizerDirectory; } public void setApplicationEventPublisher(ApplicationEventPublisher pub) { if (this.pub != null) { throw new FatalBeanException("Publisher already set."); } this.pub = pub; } public void setFilePathResolver(FilePathResolver resolver) { this.resolver = resolver; } /** * Creates a PixelBuffer for a given pixels set. * * @param pixels Pixels set to create a pixel buffer for. * @return Allocated pixel buffer ready to be used. * @throws IOException If there is an I/O error creating the pixel buffer * backing file. */ public PixelBuffer createPixelBuffer(Pixels pixels) throws IOException { RomioPixelBuffer pixbuf = new RomioPixelBuffer(getPixelsPath(pixels .getId()), pixels, true); initPixelBuffer(pixbuf); return pixbuf; } /** * Creates a pixels pyramid for a given set of pixels. If the pyramid file * already exists, then a DEBUG message is logged and this method returns. * * @param pixels Pixels set to retrieve a pixel buffer for. * @since OMERO-Beta4.3 */ public StatsInfo[] makePyramid(Pixels pixels) { final String pixelsFilePath = getPixelsPath(pixels.getId()); final File pixelsFile = new File(pixelsFilePath); final String pixelsPyramidFilePath = pixelsFilePath + PYRAMID_SUFFIX; final File pixelsPyramidFile = new File(pixelsPyramidFilePath); final String originalFilePath = getOriginalFilePath(pixels); final boolean requirePyramid = requiresPixelsPyramid(pixels); // This was called perhaps while a pyramid was // being generated, and is no longer needed. if (pixelsPyramidFile.exists()) { log.debug("Pyramid already exists: " + pixelsPyramidFilePath); return null; // EARLY EXIT! } if (!requirePyramid) { log.debug("Creating only StatsInfo."); int series = getSeries(pixels); final PixelsPyramidMinMaxStore minMaxStore = new PixelsPyramidMinMaxStore(pixels.getSizeC()); BfPixelBuffer bfPixelBuffer = createMinMaxBfPixelBuffer( originalFilePath, series, minMaxStore); try { for (int t = 0; t < pixels.getSizeT(); t++) { for (int c = 0; c < pixels.getSizeC(); c++) { for (int z = 0; z < pixels.getSizeZ(); z++) { Timer.Context ctx = minmaxTimes == null ? null : minmaxTimes.time(); try { bfPixelBuffer.getPlane(z, c, t); } finally { if (ctx != null) { ctx.stop(); } } } } } return minMaxStore.createStatsInfo(); } catch (IOException e) { log.error("I/O exception while calculating min/max.", e); return null; } } final BfPyramidPixelBuffer pixelsPyramid = createPyramidPixelBuffer( pixels, pixelsPyramidFilePath, true); try { // If we don't have any data to properly generate the pyramid // we close the instance which will save an empty (and therefore // corrupt) pyramid. This is intentional since further calls will // get an exception rather than being told to try indefinitely. // (see ticket:5189) if (!pixelsFile.exists() && originalFilePath == null) { log.error("FAIL -- Original pixels file does not exist: " + pixelsFile.getAbsolutePath()); return null; // EARLY EXIT! closed in finally block! } PixelsPyramidMinMaxStore minMaxStore = performWrite( pixels, pixelsPyramidFile, pixelsPyramid, pixelsFile, pixelsFilePath, originalFilePath); if (minMaxStore != null) { return minMaxStore.createStatsInfo(); } return null; } finally { if (pixelsPyramid != null) { try { pixelsPyramid.close(); } catch (IOException e) { log.error("Error closing pixel pyramid.", e); } } } } private PixelsPyramidMinMaxStore performWrite( final Pixels pixels,final File pixelsPyramidFile, final BfPyramidPixelBuffer pixelsPyramid, final File pixelsFile, final String pixelsFilePath, final String originalFilePath) { final PixelBuffer source; final Dimension tileSize; final PixelsPyramidMinMaxStore minMaxStore; if (pixelsFile.exists()) { minMaxStore = null; source = createRomioPixelBuffer(pixelsFilePath, pixels, false); // FIXME: This should be configuration or service driven // FIXME: Also implemented in RenderingBean.getTileSize() tileSize = new Dimension(Math.min(pixels.getSizeX(), sizes.getTileWidth()), Math.min(pixels.getSizeY(), sizes.getTileHeight())); } else { minMaxStore = new PixelsPyramidMinMaxStore(pixels.getSizeC()); int series = getSeries(pixels); BfPixelBuffer bfPixelBuffer = createMinMaxBfPixelBuffer( originalFilePath, series, minMaxStore); pixelsPyramid.setByteOrder( bfPixelBuffer.isLittleEndian()? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); source = bfPixelBuffer; // If the tile sizes we've been given are completely ridiculous // then reset them to WIDTHxHEIGHT. Currently these conditions are: // * TileWidth == ImageWidth // * TileHeight == ImageHeight // * Smallest tile dimension divided by the largest resolution // level factor is < 1. // -- Chris Allan (ome:#5224). final Dimension sourceTileSize = source.getTileSize(); final double tileWidth = sourceTileSize.getWidth(); final double tileHeight = sourceTileSize.getHeight(); final boolean tileDimensionTooSmall; double factor = Math.pow(2, 5); if (((tileWidth / factor) < 1.0) || ((tileHeight / factor) < 1.0)) { tileDimensionTooSmall = true; } else { tileDimensionTooSmall = false; } if (tileWidth == source.getSizeX() || tileHeight == source.getSizeY() || tileDimensionTooSmall) { tileSize = new Dimension(Math.min(pixels.getSizeX(), sizes.getTileWidth()), Math.min(pixels.getSizeY(), sizes.getTileHeight())); } else { tileSize = sourceTileSize; } } log.info("Destination pyramid tile size: " + tileSize); try { final double totalTiles = source.getSizeZ() * source.getSizeC() * source.getSizeT() * (Math.ceil(source.getSizeX() / tileSize.getWidth())) * (Math.ceil(source.getSizeY() / tileSize.getHeight())); final int tenPercent = Math.max((int) totalTiles / 10, 1); Utils.forEachTile(new TileLoopIteration() { public void run(int z, int c, int t, int x, int y, int w, int h, int tileCount) { if (log.isInfoEnabled() && tileCount % tenPercent == 0) { log.info(String.format( "Pyramid creation for Pixels:%d %d/%d (%d%%).", pixels.getId(), tileCount + 1, (int) totalTiles, (int) (tileCount / totalTiles * 100))); } try { Timer.Context ctx = tileTimes == null ? null : tileTimes.time(); try { PixelData tile = source.getTile(z, c, t, x, y, w, h); pixelsPyramid.setTile( tile.getData().array(), z, c, t, x, y, w, h); tile.dispose(); } finally { if (ctx != null) { ctx.stop(); } } } catch (IOException e1) { log.error("FAIL -- Error during tile population", e1); try { pixelsPyramidFile.delete(); FileUtils.touch(pixelsPyramidFile); // ticket:5189 } catch (Exception e2) { log.warn("Error clearing empty or incomplete pixel " + "buffer.", e2); } return; } } }, source, (int) tileSize.getWidth(), (int) tileSize.getHeight()); log.info("SUCCESS -- Pyramid created for pixels id:" + pixels.getId()); } finally { if (source != null) { try { source.close(); } catch (IOException e) { log.error("Error closing pixel pyramid.", e); } } } return minMaxStore; } /** * Returns a pixel buffer for a given set of pixels. Either a proprietary * ROMIO pixel buffer or a specific pixel buffer implementation. * @param pixels Pixels set to retrieve a pixel buffer for. * @return A pixel buffer instance. <b>NOTE:</b> The pixel buffer is * initialized as <b>read-write</b>. * @deprecated In the future callers should use the more descriptive * {@link #getPixelBuffer(Pixels, boolean)}. * @since OMERO-Beta4.3 * @see #getPixelBuffer(Pixels, boolean) */ @Deprecated public PixelBuffer getPixelBuffer(Pixels pixels) { return getPixelBuffer(pixels, true); } /** * Returns a pixel buffer for a given set of pixels. Either a proprietary * ROMIO pixel buffer or a specific pixel buffer implementation. * @param pixels Pixels set to retrieve a pixel buffer for. * @param write Whether or not to open the pixel buffer as read-write. * <code>true</code> opens as read-write, <code>false</code> opens as * read-only. * @return A pixel buffer instance. * @since OMERO-Beta4.3 */ public PixelBuffer getPixelBuffer(Pixels pixels, boolean write) { PixelBuffer pb = _getPixelBuffer(pixels, write); if (log.isDebugEnabled()) { log.debug(pb +" for " + pixels); } return pb; } public PixelBuffer _getPixelBuffer(Pixels pixels, boolean write) { final String originalFilePath = getOriginalFilePath(pixels); final boolean requirePyramid = requiresPixelsPyramid(pixels); final String pixelsFilePath = getPixelsPath(pixels.getId()); final File pixelsFile = new File(pixelsFilePath); final String pixelsPyramidFilePath = pixelsFilePath + PYRAMID_SUFFIX; final File pixelsPyramidFile = new File(pixelsPyramidFilePath); final boolean pixelsFileExists = pixelsFile.exists(); // // 1. If the pixels file exists, then we know that this isn't // an attempt to write a new ROMIO Pixels file, therefore if // a pyramid is required but does not exist, then we raise a // message allowing other beans the chance to create the pyramid. // If none signal "retry", then all we can do is throw. // // Note: since big pixels stored with 4.3+ will generate only // a pyramid, the existence of the ROMIO Pixels file implies // that this is legacy data. // if ((pixelsFileExists || originalFilePath != null) && requirePyramid) { while (!pixelsPyramidFile.exists()) { // If we are in OMERO.fs mode and the source original file // is already a pyramid don't try and create one. if (originalFilePath != null) { int series = getSeries(pixels); PixelBuffer bfPixelBuffer = createBfPixelBuffer( originalFilePath, series); if (bfPixelBuffer.getResolutionLevels() > 1) { return bfPixelBuffer; } } // throws if loop should exit! handleMissingPyramid(pixels, pixelsPyramidFilePath); } } // Note: since the OMERO.fs work, a pixels pyramid is only required // when the pixels set meets big image criteria. if (!pixelsFileExists && requirePyramid && originalFilePath != null) { handleMissingStatsInfo(pixels); } // // 2. If the pyramid exists, regardless of whether or not it // is required, we should use it. // if (pixelsPyramidFile.exists()) { log.info("Using Pyramid BfPixelBuffer: " + pixelsPyramidFilePath); return createPyramidPixelBuffer(pixels, pixelsPyramidFilePath, write); } // // 3. Finally, this must be a ROMIO, direct writing to the // BfPyramidPixelBuffer or OMERO.fs "light" (where the original data // has been archived). If the relevant OriginalFile path can be // resolved use it with BfPixelBuffer, otherwise create a new // RomioPixelBuffer and return. if (!pixelsFileExists) { if (requirePyramid) { if (!write) { throw new LockTimeout( "Pixels pyramid missing, being created or " + "import in progress.", 15*1000, 0); } log.info("Creating Pyramid BfPixelBuffer: " + pixelsPyramidFilePath); return createPyramidPixelBuffer(pixels, pixelsPyramidFilePath, write); } else { if (originalFilePath != null) { int series = getSeries(pixels); return createBfPixelBuffer(originalFilePath, series); } if (!write) { throw new LockTimeout("Import in progress.", 15*1000, 0); } log.info("Creating ROMIO Pixel buffer."); createSubpath(pixelsFilePath); return createRomioPixelBuffer(pixelsFilePath, pixels, write); } } log.info("Pixel buffer file exists returning read-only " + "ROMIO pixel buffer."); return createRomioPixelBuffer(pixelsFilePath, pixels, false); } /** * Returns whether a pyramid should be used for the given {@link Pixels}. * This usually implies that this is a "Big image" and therefore will * need tiling. * * @param pixels * @return {@code true} if a pyramid should be used, {@code false} * otherwise */ public boolean requiresPixelsPyramid(Pixels pixels) { String type = pixels.getPixelsType().getValue(); if ("float".equals(type) || "double".equals(type)) return false; final long sizeX = pixels.getSizeX(); final long sizeY = pixels.getSizeY(); final boolean requirePyramid = (sizeX * sizeY) > (sizes.getMaxPlaneWidth()*sizes.getMaxPlaneHeight()); return requirePyramid; } /** * Retrieves the original file path for a given set of pixels. * @param pixels Set of pixels to return an orignal file path for. * @return The original file path or <code>null</code> if the original file * path could not be located or the <code>resolver</code> has not been set. */ protected String getOriginalFilePath(Pixels pixels) { if (resolver == null) { return null; } return resolver.getOriginalFilePath(this, pixels); } /** * Retrieves the series for a given set of pixels. * @param pixels Set of pixels to return the series for. * @return The series as specified by the pixels parameters or * <code>0</code> (the first series). */ protected int getSeries(Pixels pixels) { try { final String query = "SELECT image.series FROM Pixels WHERE id = :id"; final List<Object[]> results = iQuery.projection(query, new Parameters().addId(pixels.getId())); return (Integer) results.get(0)[0]; } catch (Exception e) // NumberFormatException, NullPointerException { return 0; } } /** * Initializes each plane of a PixelBuffer using a null plane byte array. * * @param pixbuf Pixel buffer to initialize. * @throws IOException If there is an I/O error during initialization. */ private void initPixelBuffer(RomioPixelBuffer pixbuf) throws IOException { String path = getPixelsPath(pixbuf.getId()); createSubpath(path); Integer size = RomioPixelBuffer.safeLongToInteger(pixbuf.getPlaneSize()); byte[] padding = new byte[size - NULL_PLANE_SIZE]; FileOutputStream stream = new FileOutputStream(path); try { for (int z = 0; z < pixbuf.getSizeZ(); z++) { for (int c = 0; c < pixbuf.getSizeC(); c++) { for (int t = 0; t < pixbuf.getSizeT(); t++) { stream.write(nullPlane); stream.write(padding); } } } } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { log.error("Error closing stream.", e); } } } } /** * If the outer loop should continue, this method returns successfully; * otherwise it throws a MissingPyramidException. * @param pixels */ protected void handleMissingStatsInfo(Pixels pixels) { for (int channel = 0; channel < pixels.sizeOfChannels(); channel++) { if (pixels.getChannel(channel).getStatsInfo() != null) { return; } } long pixelsId = pixels.getId(); MissingStatsInfoMessage m = new MissingStatsInfoMessage(this, pixelsId); pub.publishEvent(m); if (m.isRetry()) { log.debug("Retrying stats info for Pixels:" + pixelsId); return; } String msg = "Missing stats info for Pixels:" + pixelsId; log.info(msg); backOff.throwMissingPyramidException(msg, pixels); } /** * If the outer loop should continue, this method returns successfully; * otherwise it throws a MissingPyramidException. * * @param pixels * @param pixelsPyramidFilePath * @throws MissingPyramidException */ protected void handleMissingPyramid(Pixels pixels, final String pixelsPyramidFilePath) { MissingPyramidMessage mpm = new MissingPyramidMessage(this, pixels.getId()); pub.publishEvent(mpm); if (mpm.isRetry()) { log.debug("Retrying pyramid:" + pixelsPyramidFilePath); return; } String msg = "Missing pyramid:" + pixelsPyramidFilePath; log.info(msg); backOff.throwMissingPyramidException(msg, pixels); } /** * Helper method to properly log any exceptions raised by Bio-Formats and * add a min/max calculator wrapper to the reader stack. * @param filePath Non-null. * @param series series to use * @param store Min/max store to use with the min/max calculator. */ protected BfPixelBuffer createMinMaxBfPixelBuffer(final String filePath, final int series, final IMinMaxStore store) { try { IFormatReader reader = createBfReader(); MinMaxCalculator calculator = new MinMaxCalculator(reader); calculator.setMinMaxStore(store); BfPixelBuffer pixelBuffer = new BfPixelBuffer(filePath, calculator); pixelBuffer.setSeries(series); log.info(String.format("Creating BfPixelBuffer: %s Series: %d", filePath, series)); return pixelBuffer; } catch (Exception e) { String msg = "Error instantiating pixel buffer: " + filePath; log.error(msg, e); throw new ResourceError(msg); } } /** * Short-cut in the FS case where we know that we are dealing with a FS-lite * file, and want to retrieve the actual file as opposed to a pyramid or anything * else. This may be used to access the original metadata. * @throws FormatException * @throws IOException */ public IFormatReader getBfReader(Pixels pixels) throws FormatException, IOException { // from getPixelBuffer final String originalFilePath = getOriginalFilePath(pixels); final int series = getSeries(pixels); final IFormatReader reader = createBfReader(); reader.setId(originalFilePath); // Called by BfPixelsBuffer elsewhere. reader.setSeries(series); return reader; } /** * Create an {@link IFormatReader} with the appropriate {@link loci.formats.ReaderWrapper} * instances and {@link IFormatReader#setFlattenedResolutions(boolean)} set to false. */ protected IFormatReader createBfReader() { IFormatReader reader = new ImageReader(); reader = new ChannelFiller(reader); reader = new ChannelSeparator(reader); reader = new Memoizer(reader, getMemoizerWait(), getMemoizerDirectory()); reader.setFlattenedResolutions(false); reader.setMetadataFiltered(true); return reader; } /** * Helper method to properly log any exceptions raised by Bio-Formats. * @param filePath Non-null. * @param series series to use * @return the initialized {@link BfPixelBuffer} */ protected BfPixelBuffer createBfPixelBuffer(final String filePath, final int series) { try { IFormatReader reader = createBfReader(); BfPixelBuffer pixelBuffer = new BfPixelBuffer(filePath, reader); pixelBuffer.setSeries(series); log.info(String.format("Creating BfPixelBuffer: %s Series: %d", filePath, series)); return pixelBuffer; } catch (Exception e) { String msg = "Error instantiating pixel buffer: " + filePath; log.error(msg, e); throw new ResourceError(msg); } } /** * Helper method to properly log any exceptions raised by Bio-Formats. * @param pixels passed to {@link BfPixelBuffer} * @param filePath Non-null. * @return the initialized {@link BfPyramidPixelBuffer} */ protected BfPyramidPixelBuffer createPyramidPixelBuffer(final Pixels pixels, final String filePath, boolean write) { try { if (write) { // #5159. Creating the path if we need to write. createSubpath(filePath); } return new BfPyramidPixelBuffer(pixels, filePath, write); } catch (Exception e) { if (e instanceof LockTimeout) { throw (LockTimeout) e; } String msg = "Error instantiating pixel buffer: " + filePath; log.error(msg, e); throw new ResourceError(msg); } } /** * Helper method to properlty create a RomioPixelBuffer. * * @param pixelsFilePath * @param pixels * @param allowModification */ protected PixelBuffer createRomioPixelBuffer(String pixelsFilePath, Pixels pixels, boolean allowModification) { return new RomioPixelBuffer(pixelsFilePath, pixels, allowModification); } /** * Removes files from data repository based on a parameterized List of Long * pixels ids * * @param pixelIds Long file keys to be deleted * @throws ResourceError If deletion fails. */ public void removePixels(List<Long> pixelIds) { File file; String fileName; boolean success = false; for (Iterator<Long> iter = pixelIds.iterator(); iter.hasNext();) { Long id = iter.next(); String pixelPath = getPixelsPath(id); file = new File(pixelPath); fileName = file.getName(); if (file.exists()) { success = file.delete(); if (!success) { throw new ResourceError( "Pixels " + fileName + " deletion failed"); } else { if (log.isInfoEnabled()) { log.info("INFO: Pixels " + fileName + " deleted."); } } } } } class PixelsPyramidMinMaxStore implements IMinMaxStore { final double[][] channelGlobalMinMax; final int sizeC; public PixelsPyramidMinMaxStore(int sizeC) { this.sizeC = sizeC; channelGlobalMinMax = new double[sizeC][2]; } /* (non-Javadoc) * @see loci.formats.meta.IMinMaxStore#setChannelGlobalMinMax(int, double, double, int) */ public void setChannelGlobalMinMax(int channel, double minimum, double maximum, int series) { channelGlobalMinMax[channel][0] = minimum; channelGlobalMinMax[channel][1] = maximum; } public StatsInfo[] createStatsInfo() { StatsInfo[] statsInfo = new StatsInfo[sizeC]; for (int c = 0; c < sizeC; c++) { statsInfo[c] = new StatsInfo(); statsInfo[c].setGlobalMin(channelGlobalMinMax[c][0]); statsInfo[c].setGlobalMax(channelGlobalMinMax[c][1]); } return statsInfo; } } }