/* * Copyright 2009 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.io.bioformats; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import loci.common.DataTools; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.IFormatReader; import ome.io.nio.RomioPixelBuffer; import ome.io.nio.DimensionsOutOfBoundsException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @since Beta4.3 */ public class BfPixelsWrapper { private final static Logger log = LoggerFactory.getLogger(BfPixelsWrapper.class); private final IFormatReader reader; private final String path; /** * We may want a constructor that takes the id of an imported file * or that takes a File object? * There should ultimately be some sort of check here that the * file is in a/the repository. */ public BfPixelsWrapper(String path, IFormatReader reader) throws IOException, FormatException { this.path = path; this.reader = reader; // don't re-memoize reader.setFlattenedResolutions(false); reader.setId(path); } public byte[] getMessageDigest() throws IOException { MessageDigest md; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException( "Required SHA-1 message digest algorithm unavailable."); } int sizeT = getSizeT(); for (int t = 0; t < sizeT; t++) { try { int size = RomioPixelBuffer.safeLongToInteger(getTimepointSize()); byte[] buffer = new byte[size]; getTimepoint(t,buffer); md.update(ByteBuffer.wrap(buffer)); } catch (DimensionsOutOfBoundsException e) { throw new RuntimeException(e); } } return md.digest(); } public void checkBounds(Integer x, Integer y, Integer z, Integer c, Integer t) throws DimensionsOutOfBoundsException { int sizeX = getSizeX(); int sizeY = getSizeY(); int sizeZ = getSizeZ(); int sizeC = getSizeC(); int sizeT = getSizeT(); if (x != null && (x > sizeX - 1 || x < 0)) { throw new DimensionsOutOfBoundsException("X '" + x + "' greater than sizeX '" + sizeX + "'."); } if (y != null && (y > sizeY - 1 || y < 0)) { throw new DimensionsOutOfBoundsException("Y '" + y + "' greater than sizeY '" + sizeY + "'."); } if (z != null && (z > sizeZ - 1 || z < 0)) { throw new DimensionsOutOfBoundsException("Z '" + z + "' greater than sizeZ '" + sizeZ + "'."); } if (c != null && (c > sizeC - 1 || c < 0)) { throw new DimensionsOutOfBoundsException("C '" + c + "' greater than sizeC '" + sizeC + "'."); } if (t != null && (t > sizeT - 1 || t < 0)) { throw new DimensionsOutOfBoundsException("T '" + t + "' greater than sizeT '" + sizeT + "'."); } } private void checkCubeBounds(List<Integer> offset, List<Integer> size, List<Integer> step) throws DimensionsOutOfBoundsException { // At the moment the array must contain 5 values if(offset.size()!=5 || size.size()!=5 || step.size()!=5) { throw new DimensionsOutOfBoundsException( "Invalid List length: each list must contain 5 elements XYZCT"); } checkBounds(offset.get(0),offset.get(1),offset.get(2),offset.get(3),offset.get(4)); checkBounds(offset.get(0)+size.get(0)-1,offset.get(1)+size.get(1)-1, offset.get(2)+size.get(2)-1,offset.get(3)+size.get(3)-1,offset.get(4)+size.get(4)-1); if(step.get(0) < 1 || step.get(1) < 1 || step.get(2) < 1 || step.get(3) < 1 || step.get(4) < 1) { throw new DimensionsOutOfBoundsException( "Invalid step size: steps sizes must be 1 or greater"); } } public void close() throws IOException { reader.close(); } public long getId() { // id may have no meaning when reading a file directly return 0; } public String getPath() { return path; } /* * Get dimension sizes */ public int getSizeC() { return reader.getSizeC(); } public int getSizeT() { return reader.getSizeT(); } public int getSizeX() { return reader.getSizeX(); } public int getSizeY() { return reader.getSizeY(); } public int getSizeZ() { return reader.getSizeZ(); } /* * Get data sizes */ public int getByteWidth() { return FormatTools.getBytesPerPixel(reader.getPixelType()); } public Integer getRowSize() { return getSizeX() * getByteWidth(); } public Integer getColSize() { return getSizeY() * getByteWidth(); } public Long getPlaneSize() { return (long) getSizeY() * (long) getRowSize(); } public Long getStackSize() { return getSizeZ() * getPlaneSize(); } public Long getTimepointSize() { return getSizeC() * getStackSize(); } public Long getTotalSize() { return getSizeT() * getTimepointSize(); } public Long getHypercubeSize(List<Integer> offset, List<Integer> size, List<Integer> step) throws DimensionsOutOfBoundsException { // only works for 5d at present checkCubeBounds(offset, size, step); int tStripes = (size.get(4) + step.get(4) - 1) / step.get(4); int cStripes = (size.get(3) + step.get(3) - 1) / step.get(3); int zStripes = (size.get(2) + step.get(2) - 1) / step.get(2); int yStripes = (size.get(1) + step.get(1) - 1) / step.get(1); int xStripes = (size.get(0) + step.get(0) - 1) / step.get(0); long tileRowSize = (long) getByteWidth() * xStripes; long cubeSize = tileRowSize * yStripes * zStripes * cStripes * tStripes; return cubeSize; } /* * Get data offsets */ public Long getRowOffset(Integer y, Integer z, Integer c, Integer t) throws DimensionsOutOfBoundsException { checkBounds(null, y, z, c, t); return (long) getPlaneOffset(z,c,t) + y * getRowSize(); } public Long getPlaneOffset(Integer z, Integer c, Integer t) throws DimensionsOutOfBoundsException { checkBounds(null, null, z, c, t); return (long) getStackOffset(c,t) + z * getPlaneSize(); } public Long getStackOffset(Integer c, Integer t) throws DimensionsOutOfBoundsException { checkBounds(null, null, null, c, t); return (long) getTimepointOffset(t) + c * getStackSize(); } public Long getTimepointOffset(Integer t) throws DimensionsOutOfBoundsException { checkBounds(null, null, null, null, t); return (long) t * getTimepointSize(); } public byte[] getCol(Integer x, Integer z, Integer c, Integer t, byte[] buffer) throws IOException, DimensionsOutOfBoundsException { checkBounds(x, null, z, c, t); try { if (buffer.length != getColSize()) throw new RuntimeException("Buffer size incorrect."); int size = RomioPixelBuffer.safeLongToInteger(getPlaneSize()); byte[] plane = new byte[size]; getWholePlane(z,c,t,plane); for(int y = 0; y < reader.getSizeY(); y++) { System.arraycopy(plane, (y*getRowSize())+(x*getByteWidth()), buffer, y*getByteWidth(), getByteWidth()); } } catch (FormatException e) { throw new RuntimeException(e); } return buffer; } public byte[] getPlane(Integer z, Integer c, Integer t, byte[] buffer) throws IOException, DimensionsOutOfBoundsException { checkBounds(null, null, z, c, t); try { if (buffer.length != getPlaneSize()) throw new RuntimeException("Buffer size incorrect."); getWholePlane(z,c,t,buffer); } catch (FormatException e) { throw new RuntimeException(e); } return buffer; } public byte[] getPlaneRegion(Integer z, Integer c, Integer t, Integer count, Integer offset, byte[] buffer) throws IOException, DimensionsOutOfBoundsException { return null; } public byte[] getRegion(Integer size, Long offset, byte[] buffer) throws IOException { return null; } public byte[] getRow(Integer y, Integer z, Integer c, Integer t, byte[] buffer) throws IOException, DimensionsOutOfBoundsException { checkBounds(null, y, z, c, t); try { if (buffer.length != getRowSize()) throw new RuntimeException("Buffer size incorrect."); int size = RomioPixelBuffer.safeLongToInteger(getPlaneSize()); byte[] plane = new byte[size]; getWholePlane(z,c,t,plane); System.arraycopy(plane, y*getRowSize(), buffer, 0, getRowSize()); } catch (FormatException e) { throw new RuntimeException(e); } return buffer; } public byte[] getStack(Integer c, Integer t, byte[] buffer) throws IOException, DimensionsOutOfBoundsException { checkBounds(null, null, null, c, t); try { if (buffer.length != getStackSize()) throw new RuntimeException("Buffer size incorrect."); int size = RomioPixelBuffer.safeLongToInteger(getPlaneSize()); byte[] plane = new byte[size]; int sizeZ = getSizeZ(); for(int z = 0; z < sizeZ; z++) { getWholePlane(z,c,t,plane); System.arraycopy(plane, 0, buffer, z*size, size); } } catch (FormatException e) { throw new RuntimeException(e); } return buffer; } public byte[] getTimepoint(Integer t, byte[] buffer) throws IOException, DimensionsOutOfBoundsException { checkBounds(null, null, null, null, t); if (buffer.length != getTimepointSize()) throw new RuntimeException("Buffer size incorrect."); int size = RomioPixelBuffer.safeLongToInteger(getStackSize()); byte[] stack = new byte[size]; int sizeC = getSizeC(); for(int c = 0; c < sizeC; c++) { getStack(c, t, stack); System.arraycopy(stack, 0, buffer, c*size, size); } return buffer; } public byte[] getHypercube(List<Integer> offset, List<Integer> size, List<Integer> step, byte[] buffer) throws IOException, DimensionsOutOfBoundsException { if (buffer.length != getHypercubeSize(offset, size, step)) throw new RuntimeException("Buffer size incorrect."); try { getWholeHypercube(offset,size,step,buffer); } catch (FormatException e) { throw new RuntimeException(e); } return buffer; } public byte[] getTile(int z, int c, int t, int x, int y, int w, int h, byte[] buffer) throws FormatException, IOException { return reader.openBytes(reader.getIndex(z, c, t), buffer, x, y, w, h); } /* * Helper methods */ /* * Get a plane dealing with rgb/interleaving if necessary */ private byte[] getWholePlane(int z, int c, int t, byte[] plane) throws IOException, FormatException { int planeNumber; if (reader.getRGBChannelCount() == 1) { planeNumber = reader.getIndex(z, c, t); reader.openBytes(planeNumber, plane); } else { int size = RomioPixelBuffer.safeLongToInteger(getPlaneSize()); byte[] fullPlane = new byte[size*reader.getRGBChannelCount()]; planeNumber = reader.getIndex(z, 0, t); reader.openBytes(planeNumber, fullPlane); if(reader.isInterleaved()) { for(int p = 0; p < size; p += getByteWidth()) { System.arraycopy(fullPlane, c*getByteWidth() + p*reader.getRGBChannelCount(), plane, p, getByteWidth()); } } else { System.arraycopy(fullPlane, c*size, plane, 0, size); } } return plane; } /* * Get a plane dealing with rgb/interleaving if necessary * using openPlane2d doesn't seem to work */ /* private byte[] getWholePlane(int z, int c, int t, byte[] plane) throws IOException, FormatException { if(rgbChannels == 1) { int planeNumber = reader.getIndex(z, c, t); Plane2D plane2d = reader.openPlane2D(path, planeNumber, plane); plane = plane2d.getData().array(); } else { // Separate channels as openPlane2D doesn't do this byte[] fullPlane = new byte[getPlaneSize()*rgbChannels]; int planeNumber = reader.getIndex(z, 0, t); Plane2D plane2d = reader.openPlane2D(path, planeNumber, plane); fullPlane = plane2d.getData().array(); System.arraycopy(fullPlane, c*getPlaneSize(), plane, 0, getPlaneSize()); } return plane; } */ private byte[] getWholeHypercube(List<Integer> offset, List<Integer> size, List<Integer> step, byte[] cube) throws IOException, FormatException { int cubeOffset = 0; int xStripes = (size.get(0) + step.get(0) - 1) / step.get(0); int tileRowSize = getByteWidth() * xStripes; int planeSize = RomioPixelBuffer.safeLongToInteger(getPlaneSize()); byte[] plane = new byte[planeSize]; for(int t = offset.get(4); t < size.get(4)+offset.get(4); t += step.get(4)) { for(int c = offset.get(3); c < size.get(3)+offset.get(3); c += step.get(3)) { for(int z = offset.get(2); z < size.get(2)+offset.get(2); z += step.get(2)) { getWholePlane(z,c,t,plane); int rowOffset = offset.get(1)*getRowSize(); if(step.get(0)==1) { int byteOffset = rowOffset + offset.get(0)*getByteWidth(); for(int y = offset.get(1); y < size.get(1)+offset.get(1); y += step.get(1)) { System.arraycopy(plane, byteOffset, cube, cubeOffset, tileRowSize); cubeOffset += tileRowSize; byteOffset += getRowSize()*step.get(1); } } else { for(int y = offset.get(1); y < size.get(1)+offset.get(1); y += step.get(1)) { int byteOffset = offset.get(0)*getByteWidth(); for(int x = offset.get(0); x < size.get(0)+offset.get(0); x += step.get(0)) { System.arraycopy(plane, rowOffset+byteOffset, cube, cubeOffset, getByteWidth()); cubeOffset += getByteWidth(); byteOffset += step.get(0)*getByteWidth(); } rowOffset += getRowSize()*step.get(1); } } } } } return cube; } /** * cgb - created from the methods below? * * Retrieves how many bytes per pixel the current plane or section has. * @return the number of bytes per pixel. */ public String getPixelsType() { return FormatTools.getPixelTypeString(reader.getPixelType()); } public boolean isFloat() { return FormatTools.isFloatingPoint(reader.getPixelType()); } public boolean isSigned() { return FormatTools.isSigned(reader.getPixelType()); } /** * cgb - stolen from ImportLibrary - slightly modified * * Examines a byte array to see if it needs to be byte swapped and modifies * the byte array directly. * @param bytes The byte array to check and modify if required. * @return the <i>byteArray</i> either swapped or not for convenience. * @throws IOException if there is an error read from the file. * @throws FormatException if there is an error during metadata parsing. */ public byte[] swapIfRequired(byte[] bytes) throws FormatException, IOException { // We've got nothing to do if the samples are only 8-bits wide. if (getByteWidth() == 1) return bytes; boolean isLittleEndian = reader.isLittleEndian(); ByteBuffer buffer = ByteBuffer.wrap(bytes); int length; if (isLittleEndian) { if (getByteWidth() == 2) { // short/ushort ShortBuffer buf = buffer.asShortBuffer(); length = buffer.limit() / 2; for (int i = 0; i < length; i++) { buf.put(i, DataTools.swap(buf.get(i))); } } else if (getByteWidth() == 4) { // int/uint/float IntBuffer buf = buffer.asIntBuffer(); length = buffer.limit() / 4; for (int i = 0; i < length; i++) { buf.put(i, DataTools.swap(buf.get(i))); } } else if (getByteWidth() == 8) // long/double { LongBuffer buf = buffer.asLongBuffer(); length = buffer.limit() / 8; for (int i = 0; i < length ; i++) { buf.put(i, DataTools.swap(buf.get(i))); } } else { throw new FormatException(String.format( "Unsupported sample bit width: %d", getByteWidth())); } } // We've got a big-endian file with a big-endian byte array. bytes = buffer.array(); return bytes; } }