package it.geosolutions.imageio.plugins.jhdf; import it.geosolutions.hdf.object.h4.H4DatatypeUtilities; import it.geosolutions.hdf.object.h4.H4File; import it.geosolutions.hdf.object.h4.H4SDS; import it.geosolutions.hdf.object.h4.H4SDSCollection; import it.geosolutions.imageio.plugins.slices2D.SliceImageReader; import it.geosolutions.imageio.stream.input.FileImageInputStreamExtImpl; import java.awt.Rectangle; import java.awt.image.BandedSampleModel; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferDouble; import java.awt.image.DataBufferFloat; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferShort; import java.awt.image.Raster; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; import javax.imageio.spi.ImageReaderSpi; public abstract class AbstractHDFImageReader extends SliceImageReader { /** * A LinkedHashMap having all the Datasets * contained within the source */ protected LinkedHashMap subDatasetsMap; /** A mutex used to synchronize operations */ protected final int[] mutex = new int[] { 0 }; protected H4SDSCollection h4SDSCollection = null; /** * Returns a subDataset given a subDatasetIndex * * @param subDatasetIndex * The index of the required subDataset * @return the required subDataset */ protected H4SDS retrieveDataset(int subDatasetIndex) { synchronized (mutex) { Set set = subDatasetsMap.keySet(); Iterator it = set.iterator(); for (int j = 0; j < subDatasetIndex; j++) it.next(); return (H4SDS) subDatasetsMap.get((String) it.next()); } } private void checkImageIndex(int imageIndex) { // TODO: implements this to handle supported indexes } // TODO: should be moved in the aboveLayer? /** * Class used to store basic source structure properties such as number of * subdatasets and basic properties of each sub dataset, such as, rank, * dimensions size, chunk size. */ protected class SourceStructure { /** the number of subdatasets contained within the data source */ protected int nSubdatasets; /** a list of {@link SubDatasetInfo} instances */ protected ArrayList subDatasetInfo; /** * Redundant field. Allows index management without scanning all * SubDatasetInfo */ protected ArrayList subDatasetSizes; /** * A-priori Constructor. Use this form when you know the exact * subdataset number prior to instantiate the * SourceStructure * * @param subdatasetsNum */ public SourceStructure(int subdatasetsNum) { nSubdatasets = subdatasetsNum; subDatasetInfo = new ArrayList(subdatasetsNum); // new SubDatasetInfo[subdatasetsNum]; subDatasetSizes = new ArrayList(subdatasetsNum); } /** * Constructor which need to be used when you dont know the exact * subdataset number a priori. After all SubDatasetInfo's * has been added, you need to call the init method. * */ public SourceStructure() { nSubdatasets = 0; subDatasetInfo = new ArrayList(10); subDatasetSizes = new ArrayList(10); } public int getSubDatasetSize(int index) { return ((Integer) subDatasetSizes.get(index)).intValue(); // return subDatasetSize[0]; } public void setSubDatasetSize(int index, int size) { subDatasetSizes.add(index, new Integer(size)); } public int getNSubdatasets() { return nSubdatasets; } public void setNSubdatasets(int subdatasets) { nSubdatasets = subdatasets; } public int[] getSubDatasetSizes() { final Object[] objArray = subDatasetSizes.toArray(); final int length = objArray.length; final int[] array = new int[length]; for (int i = 0; i < length; i++) array[i] = ((Integer) (objArray[i])).intValue(); return array; } public void setSubDatasetInfo(int subDatasetIndex, SubDatasetInfo dsInfo) { // subDatasetInfo[j] = dsInfo; subDatasetInfo.add(subDatasetIndex, dsInfo); } public void addSubDatasetProperties(SubDatasetInfo dsInfo, int subDatasetSize) { // subDatasetInfo[j] = dsInfo; subDatasetInfo.add(nSubdatasets, dsInfo); subDatasetSizes.add(nSubdatasets++, new Integer(subDatasetSize)); } public SubDatasetInfo getSubDatasetInfo(int subDatasetIndex) { if (subDatasetIndex <= nSubdatasets) // return subDatasetInfo[subDatasetIndex]; return (SubDatasetInfo) subDatasetInfo.get(subDatasetIndex); else return null; } public void dispose() { nSubdatasets = 0; subDatasetInfo.clear(); subDatasetSizes.clear(); } } /** * * @param itemName * The name of the product/subDataset to be checked * @return true if the product/subdataset needs to be taken * on account. */ protected abstract boolean isAcceptedItem(final String itemName); /** * Additional initialization for a specific HDF "Profile". Depending on the * HDF data producer, the originating file has a proper data/metadata * structure. For this reason, a specific initialization should be * implemented for each different HDF "Profile". As an instance, the * Automated Processing System (APS) produces HDF files having a structure * which differes from the HDF structure of a file produced by TIROS * Operational Vertical Sounder (TOVS). * * @throws Exception */ protected abstract void initializeProfile() throws Exception; protected abstract int getBandNumberFromProduct(final String productName); /** The originating H4File */ protected H4File h4file = null; protected ImageTypeSpecifier imageType = null; /** * a SourceStructure's instance needed to get main * SubDatasets info and properties. */ protected SourceStructure sourceStructure; protected AbstractHDFImageReader(ImageReaderSpi originatingProvider) { super(originatingProvider); } /** * Returns the width in pixels of the given image within the input source. * * @param imageIndex * the index of the image to be queried. * * @return the width of the image, as an int. */ public int getWidth(final int imageIndex) throws IOException { if (!isInitialized) initialize(); return sourceStructure.getSubDatasetInfo( retrieveSubDatasetIndex(imageIndex)).getWidth(); } /** * Returns the height in pixels of the given image within the input source. * * @param imageIndex * the index of the image to be queried. * * @return the height of the image, as an int. */ public int getHeight(final int imageIndex) throws IOException { if (!isInitialized) initialize(); return sourceStructure.getSubDatasetInfo( retrieveSubDatasetIndex(imageIndex)).getHeight(); } /** * Reads the image indexed by imageIndex and returns it as a * BufferedImage, using a supplied * ImageReadParam * * @param imageIndex * the index of the image to be retrieved. * @param param * an ImageReadParam used to control the reading * process, or null. * * @return the desired portion of the image as a BufferedImage. */ public BufferedImage read(final int imageIndex, ImageReadParam param) throws IOException { // //////////////////////////////////////////////////////////////////// // // INITIALIZATIONS // // //////////////////////////////////////////////////////////////////// if (!isInitialized) initialize(); // Getting indexing information final int[] slice2DindexCoordinates = getSlice2DIndexCoordinates(imageIndex); final int subDatasetIndex = slice2DindexCoordinates[0]; final H4SDS dataset = retrieveDataset(subDatasetIndex); BufferedImage bimage = null; SubDatasetInfo sdInfo = sourceStructure .getSubDatasetInfo(subDatasetIndex); final int rank = sdInfo.getRank(); final int width = sdInfo.getWidth(); final int height = sdInfo.getHeight(); final int datatype = sdInfo.getDatatype(); if (param == null) param = getDefaultReadParam(); int dstWidth = -1; int dstHeight = -1; int srcRegionWidth = -1; int srcRegionHeight = -1; int srcRegionXOffset = -1; int srcRegionYOffset = -1; int xSubsamplingFactor = -1; int ySubsamplingFactor = -1; // // // // Retrieving Information about Source Region and doing // additional intialization operations. // // // Rectangle srcRegion = param.getSourceRegion(); if (srcRegion != null) { srcRegionWidth = (int) srcRegion.getWidth(); srcRegionHeight = (int) srcRegion.getHeight(); srcRegionXOffset = (int) srcRegion.getX(); srcRegionYOffset = (int) srcRegion.getY(); // // // // Minimum correction for wrong source regions // // When you do subsampling or source subsetting it might // happen that the given source region in the read param is // uncorrect, which means it can be or a bit larger than the // original file or can begin a bit before original limits. // // We got to be prepared to handle such case in order to avoid // generating ArrayIndexOutOfBoundsException later in the code. // // // if (srcRegionXOffset < 0) srcRegionXOffset = 0; if (srcRegionYOffset < 0) srcRegionYOffset = 0; if ((srcRegionXOffset + srcRegionWidth) > width) { srcRegionWidth = width - srcRegionXOffset; } // initializing destWidth dstWidth = srcRegionWidth; if ((srcRegionYOffset + srcRegionHeight) > height) { srcRegionHeight = height - srcRegionYOffset; } // initializing dstHeight dstHeight = srcRegionHeight; } else { // Source Region not specified. // Assuming Source Region Dimension equal to Source Image // Dimension dstWidth = width; dstHeight = height; srcRegionXOffset = srcRegionYOffset = 0; srcRegionWidth = width; srcRegionHeight = height; } // SubSampling variables initialization xSubsamplingFactor = param.getSourceXSubsampling(); ySubsamplingFactor = param.getSourceYSubsampling(); // //// // // Updating the destination size in compliance with // the subSampling parameters // // //// dstWidth = ((dstWidth - 1) / xSubsamplingFactor) + 1; dstHeight = ((dstHeight - 1) / ySubsamplingFactor) + 1; // getting dataset properties. final int[] start = new int[rank]; final int[] stride = new int[rank]; final int[] sizes = new int[rank]; // final int[] start = dataset.getStartDims(); // final int[] stride = dataset.getStride(); // final int[] sizes = dataset.getSelectedDims(); // Setting variables needed to execute read operation. start[rank - 2] = srcRegionYOffset; start[rank - 1] = srcRegionXOffset; sizes[rank - 2] = dstHeight; sizes[rank - 1] = dstWidth; stride[rank - 2] = ySubsamplingFactor; stride[rank - 1] = xSubsamplingFactor; if (rank > 2) { // Setting indexes of dimensions > 2. for (int i = 0; i < rank - 2; i++) { // TODO: Need to change indexing logic? start[i] = slice2DindexCoordinates[i + 1]; sizes[i] = 1; stride[i] = 1; } } final int nBands = getBandNumberFromProduct(sdInfo.getName()); // bands variables final int[] banks = new int[nBands]; final int[] offsets = new int[nBands]; for (int band = 0; band < nBands; band++) { banks[band] = band; offsets[band] = 0; } // Setting SampleModel and ColorModel final int bufferType = H4DatatypeUtilities .getBufferTypeFromDataType(datatype); SampleModel sm = new BandedSampleModel(bufferType, dstWidth, dstHeight, dstWidth, banks, offsets); ColorModel cm = retrieveColorModel(sm); // //////////////////////////////////////////////////////////////////// // // DATA READ // // //////////////////////////////////////////////////////////////////// WritableRaster wr = null; final Object data; try { data = dataset.read(start, stride, sizes); final int size = dstWidth * dstHeight; DataBuffer dataBuffer = null; switch (bufferType) { case DataBuffer.TYPE_BYTE: dataBuffer = new DataBufferByte((byte[]) data, size); break; case DataBuffer.TYPE_SHORT: case DataBuffer.TYPE_USHORT: dataBuffer = new DataBufferShort((short[]) data, size); break; case DataBuffer.TYPE_INT: dataBuffer = new DataBufferInt((int[]) data, size); break; case DataBuffer.TYPE_FLOAT: dataBuffer = new DataBufferFloat((float[]) data, size); break; case DataBuffer.TYPE_DOUBLE: dataBuffer = new DataBufferDouble((double[]) data, size); break; } wr = Raster.createWritableRaster(sm, dataBuffer, null); bimage = new BufferedImage(cm, wr, false, null); } catch (Exception e) { RuntimeException rte = new RuntimeException( "Exception occurred while data Reading" + e); rte.initCause(e); throw rte; } return bimage; } public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { this.setInput(input); } public void setInput(Object input, boolean seekForwardOnly) { this.setInput(input, seekForwardOnly); } public void setInput(Object input) { // //////////////////////////////////////////////////////////////////// // // Reset the state of this reader // // Prior to set a new input, I need to do a pre-emptive reset in order // to clear any value-object related to the previous input. // //////////////////////////////////////////////////////////////////// // TODO: Add URL & String support. if (originatingFile != null) reset(); // TODO: Need to reset also sourceStructure if (input instanceof File) { originatingFile = (File) input; } if (input instanceof FileImageInputStreamExtImpl) { // retrieving File originatingFile = ((FileImageInputStreamExtImpl) input).getFile(); } try { initialize(); } catch (IOException e) { new RuntimeException("Not a Valid Input", e); } } /** * Simple initialization method */ protected void initialize() throws IOException { synchronized (mutex) { if (originatingFile == null) throw new IOException( "Unable to Initialize data. Provided Input is not valid"); final String fileName = originatingFile.getAbsolutePath(); try { h4file = new H4File(fileName); h4SDSCollection = h4file.getH4SdsCollection(); if (h4SDSCollection != null) initializeProfile(); } catch (Exception e) { IOException ioe = new IOException( "Unable to Initialize data. Provided Input is not valid" + e); ioe.initCause(e); throw ioe; } isInitialized = true; } } /** * Returns an Iterator containing possible image types to * which the given image may be decoded, in the form of * ImageTypeSpecifierss. * * @param imageIndex * the index of the image to be retrieved. * * @return an Iterator containing at least one * ImageTypeSpecifier representing suggested image * types for decoding the current given image. * * @exception IllegalStateException * if the input source has not been set. * @exception IndexOutOfBoundsException * if the supplied index is out of bounds. * @exception IOException * if an error occurs reading the format information from the * input source. */ public Iterator getImageTypes(final int imageIndex) throws IOException { final List l = new java.util.ArrayList(1); if (!isInitialized) initialize(); final SubDatasetInfo sdInfo = sourceStructure .getSubDatasetInfo(retrieveSubDatasetIndex(imageIndex)); final int datatype = sdInfo.getDatatype(); final int width = sdInfo.getWidth(); final int height = sdInfo.getHeight(); final int nBands = getBandNumberFromProduct(sdInfo.getName()); // bands variables final int[] banks = new int[nBands]; final int[] offsets = new int[nBands]; for (int band = 0; band < nBands; band++) { banks[band] = band; offsets[band] = 0; } // Variable used to specify the data type for the storing samples // of the SampleModel final int bufferType = H4DatatypeUtilities .getBufferTypeFromDataType(datatype); final SampleModel sm = new BandedSampleModel(bufferType, width, height, width, banks, offsets); final ColorModel cm = retrieveColorModel(sm); imageType = new ImageTypeSpecifier(cm, sm); l.add(imageType); return l.iterator(); } /** * Returns the height of a tile in the given image. * * @param imageIndex * the index of the image to be queried. * * @return the height of a tile. * * @exception IOException * if an error occurs during reading. */ public int getTileHeight(final int imageIndex) throws IOException { if (!isInitialized) initialize(); final SubDatasetInfo sdInfo = sourceStructure .getSubDatasetInfo(retrieveSubDatasetIndex(imageIndex)); final int[] chunkSize = sdInfo.getChunkSize(); // TODO: Change this behavior if (chunkSize != null) { final int rank = sdInfo.getRank(); return (int) chunkSize[rank - 1]; } else return Math.min(512, sdInfo.getHeight()); } /** * Returns the width of a tile in the given image. * * @param imageIndex * the index of the image to be queried. * * @return the width of a tile. * * @exception IOException * if an error occurs during reading. */ public int getTileWidth(final int imageIndex) throws IOException { if (!isInitialized) initialize(); final SubDatasetInfo sdInfo = sourceStructure .getSubDatasetInfo(retrieveSubDatasetIndex(imageIndex)); final int[] chunkSize = sdInfo.getChunkSize(); // TODO: Change this behavior if (chunkSize != null) { final int rank = sdInfo.getRank(); return (int) chunkSize[rank - 2]; } else return Math.min(512, sdInfo.getWidth()); } public void dispose() { super.dispose(); try { h4file.close(); sourceStructure.dispose(); sourceStructure = null; } catch (Exception e) { // TODO Nothing to do. } } public void reset() { super.setInput(null, false, false); originatingFile = null; } /** * returns a proper subindex needed to access a specific 2D slice of a * specified coverage/subdataset. * * @param imageIndex * the specified coverage/subDataset * @param selectedIndexOfEachDim * the required index of each dimension * * TODO: Should I use a single int[] input parameter containing also the * subdataset index? */ public int retrieveSlice2DIndex(int imageIndex, int[] selectedIndexOfEachDim) { int subIndexOffset = 0; final SubDatasetInfo sdInfo = sourceStructure .getSubDatasetInfo(imageIndex); for (int i = 0; i < imageIndex; i++) subIndexOffset += (sourceStructure.getSubDatasetSize(i)); if (selectedIndexOfEachDim != null) { // X and Y dims are not taken in account final int selectedDimsLenght = selectedIndexOfEachDim.length; final int[] subDatasetDims = sdInfo.getDims(); final int rank = sdInfo.getRank(); // supposing specifying all required subDimensions. // as an instance, if rank=5, I need to specify 3 dimensions-index // TODO: maybe I can assume some default behavior. // as an instance, using 0 as dimension-index when not specified. if (selectedDimsLenght != (rank - 2)) { throw new IndexOutOfBoundsException( "The selected dims array can't be" + "greater than the rank of the subDataset"); } for (int i = 0; i < selectedDimsLenght; i++) { if (selectedIndexOfEachDim[i] > subDatasetDims[i]) { final StringBuffer sb = new StringBuffer(); sb .append( "At least one of the specified indexes is greater than the max allowed index in that dimension\n") .append("dimension=") .append(i) .append(" index=") .append(selectedIndexOfEachDim[i]) .append( " while the maximum index available for this dimension is ") .append(subDatasetDims[i]); throw new IndexOutOfBoundsException(sb.toString()); } } int displacement = 0; if (rank > 2) { // The least significant dimension is used as offset int finalOffset = selectedIndexOfEachDim[rank - 3]; if (rank > 3) { // TODO: review and test this logic with a 4D dataset. final int[] multipliers = new int[rank - 3]; for (int i = 0; i < rank - 3; i++) multipliers[i] = subDatasetDims[i + 2]; for (int i = 0; i < rank - 3; i++) { int factor = 1; for (int j = 0; j < rank - 3 - i; j++) factor *= multipliers[j]; displacement += (factor * selectedIndexOfEachDim[i]); } } displacement += finalOffset; } subIndexOffset += displacement; } return (int) subIndexOffset; } /** * Given a specifiedIndex as an input, returns a int[] * having the subDataset/coverage index at the first position of the array. * Then, the indexes (of the other dimensions) needed to retrieve a proper * 2D Slice. * * As an instance, suppose a HDF source contains a 4D SubDataset with the * form (X,Y,Z,T). if returnedIndex[]={2,3,1}, the required Slice2D is * available at the subDataset with index=2, timeIndex=3, zIndex=1. * * TODO: Now, we are supposing order is 5thDim -> T -> Z -> (X,Y) * */ public int[] getSlice2DIndexCoordinates(int requiredSlice2DIndex) { final int nTotalDataset = sourceStructure.getNSubdatasets(); final int[] subDatasetSizes = sourceStructure.getSubDatasetSizes(); int iSubdataset = 0; for (; iSubdataset < nTotalDataset; iSubdataset++) { int subDatasetSize = (int) subDatasetSizes[iSubdataset]; if (requiredSlice2DIndex >= subDatasetSize) requiredSlice2DIndex -= subDatasetSize; else break; } // Getting the SubDatasetInfo related to the specified subDataset. final SubDatasetInfo sdInfo = sourceStructure .getSubDatasetInfo(iSubdataset); final int rank = sdInfo.getRank(); // index initialization final int[] slice2DIndexCoordinates = new int[rank - 1];// subDatasetIndex+(rank-2) for (int i = 0; i < rank - 1; i++) slice2DIndexCoordinates[i] = 0; slice2DIndexCoordinates[0] = iSubdataset; if (rank > 2) { if (rank > 3) { // TODO: review and test this logic with a 4D dataset. final int[] subDatasetDims = sdInfo.getDims(); final int[] multipliers = new int[rank - 3]; for (int i = 0; i < rank - 3; i++) multipliers[i] = subDatasetDims[i]; for (int i = 0; i < rank - 3; i++) { int factor = 1; for (int j = 0; j < rank - 3 - i; j++) factor *= multipliers[j]; while (requiredSlice2DIndex >= factor) { requiredSlice2DIndex -= factor; slice2DIndexCoordinates[i + 1]++; } } } slice2DIndexCoordinates[rank - 2] = requiredSlice2DIndex; } return slice2DIndexCoordinates; } // /** // * Given a specifiedIndex as an input, returns a long[] // * having the subDataset/coverage index at the first position of the // array. // * Then, the indexes (of the other dimensions) needed to retrieve a proper // * 2D Slice. // * // * As an instance, suppose a HDF source contains a 4D SubDataset with the // * form (X,Y,Z,T). if returnedIndex[]={2,3,1}, the required Slice2D is // * available at the subDataset with index=2, timeIndex=3, zIndex=1. // * // * TODO: Now, we are supposing order is 5thDim -> T -> Z -> (X,Y) // * // */ // public int[] getSlice2DIndexCoordinates(int requiredSlice2DIndex) { // final int nTotalDataset = sourceStructure.getNSubdatasets(); // final long[] subDatasetSizes = sourceStructure.getSubDatasetSizes(); // int iSubdataset = 0; // for (; iSubdataset < nTotalDataset; iSubdataset++) { // int subDatasetSize = (int) subDatasetSizes[iSubdataset]; // if (requiredSlice2DIndex >= subDatasetSize) // requiredSlice2DIndex -= subDatasetSize; // else // break; // } // // // Getting the SubDatasetInfo related to the specified subDataset. // final SubDatasetInfo sdInfo = sourceStructure // .getSubDatasetInfo(iSubdataset); // final int rank = sdInfo.getRank(); // // // index initialization // final int[] slice2DIndexCoordinates = new int[rank - 1];// // subDatasetIndex+(rank-2) // for (int i = 0; i < rank - 1; i++) // slice2DIndexCoordinates[i] = 0; // slice2DIndexCoordinates[0] = iSubdataset; // // if (rank > 2) { // final long[] subDatasetDims = sdInfo.getDims(); // if (rank > 3) { // final long[] multipliers = new long[rank - 3]; // for (int i = 0; i < rank - 3; i++) // multipliers[i] = subDatasetDims[i + 2]; // // for (int i = 0; i < rank - 3; i++) { // int factor = 1; // for (int j = 0; j < rank - 3 - i; j++) // factor *= multipliers[j]; // while (requiredSlice2DIndex >= factor) { // requiredSlice2DIndex -= factor; // slice2DIndexCoordinates[i + 1]++; // } // } // } // slice2DIndexCoordinates[rank - 2] = requiredSlice2DIndex; // } // return slice2DIndexCoordinates; // } /** * Given the index of a 2D image, retrieve the index of the subDataset * containing that image. * * @param imageIndex * the index of a 2D image * @return the index of the subDataset containing that image. */ protected int retrieveSubDatasetIndex(int imageIndex) { checkImageIndex(imageIndex); final int nTotalDataset = sourceStructure.getNSubdatasets(); final int[] subDatasetSizes = sourceStructure.getSubDatasetSizes(); int iSubdataset = 0; if (imageIndex==31) { System.out.print("V"); } for (; iSubdataset < nTotalDataset; iSubdataset++) { int subDatasetSize = (int) subDatasetSizes[iSubdataset]; if (imageIndex >= subDatasetSize) imageIndex -= subDatasetSize; else break; } return iSubdataset; } }