use of org.geotoolkit.image.io.DimensionSlice in project geotoolkit by Geomatys.
the class ImageCoverageReader method read.
/**
* Converts geodetic parameters to image parameters, reads the image and wraps it in a
* grid coverage. First, this method creates an initially empty block of image parameters
* by invoking the {@link #createImageReadParam(int)} method. The image parameter
* {@linkplain ImageReadParam#setSourceRegion source region},
* {@linkplain ImageReadParam#setSourceSubsampling source subsampling} and
* {@linkplain ImageReadParam#setSourceBands source bands} are computed from the
* parameter given to this {@code read} method. Then, the following image parameters
* are set (if the image parameter class allows such settings):
* <p>
* <ul>
* <li><code>{@linkplain SpatialImageReadParam#setSampleConversionAllowed
* setSampleConversionAllowed}({@linkplain SampleConversionType#REPLACE_FILL_VALUES
* REPLACE_FILL_VALUES}, true)</code> in order to allow the replacement of
* fill values by {@link Float#NaN NaN}.</li>
*
* <li><code>{@linkplain SpatialImageReadParam#setSampleConversionAllowed
* setSampleConversionAllowed}({@linkplain SampleConversionType#SHIFT_SIGNED_INTEGERS
* SHIFT_SIGNED_INTEGERS}, true)</code> if the sample dimensions declare an unsigned
* range of sample values.</li>
*
* <li><code>{@linkplain MosaicImageReadParam#setSubsamplingChangeAllowed
* setSubsamplingChangeAllowed}(true)</code> in order to allow {@link MosaicImageReader}
* to use a different resolution than the requested one. This is crucial from a
* performance point of view. Since the {@code GridCoverageReader} contract does not
* guarantee that the grid geometry of the returned coverage is the requested geometry,
* we are allowed to do that.</li>
* </ul>
* <p>
* Finally, the image is read and wrapped in a {@link GridCoverage2D} using the
* information provided by {@link #getGridGeometry(int)} and {@link #getSampleDimensions(int)}.
*
* /!\ If {@link org.geotoolkit.coverage.io.GridCoverageReadParam#setDeferred(boolean)} parameter is set to true, the
* returned coverage will rely on the current reader to cache it's data on the fly, so you CANNOT dispose of the current
* reader while your using the resulting coverage.
*
* @param param Optional parameters used to control the reading process, or {@code null}.
* @return The {@link GridCoverage} at the specified index.
* @throws IllegalStateException if the input source has not been set.
* @throws IndexOutOfBoundsException if the supplied index is out of bounds.
* @throws CoverageStoreException If an error occurs while reading the information from the input source.
* @throws CancellationException If {@link #abort()} has been invoked in an other thread during
* the execution of this method.
*
* @see ImageReader#read(int)
*/
public GridCoverage2D read(final GridCoverageReadParam param) throws DataStoreException, CancellationException {
final int index = 0;
final boolean loggingEnabled = isLoggable();
long fullTime = (loggingEnabled) ? System.nanoTime() : 0;
ignoreGridTransforms = !loggingEnabled;
/*
* Parameters check.
*/
abortRequested = false;
// Protect from changes.
final ImageReader imageReader = this.imageReader;
if (imageReader == null) {
throw new IllegalStateException(formatErrorMessage(Errors.Keys.NoImageInput));
}
GridGeometry gridGeometry = getGridGeometry();
final int gridDimensionX, gridDimensionY;
if (gridGeometry.getDimension() == 2) {
gridDimensionX = 0;
gridDimensionY = 1;
} else if (gridGeometry.isDefined(GridGeometry.EXTENT)) {
final int[] subSpace = gridGeometry.getExtent().getSubspaceDimensions(2);
gridDimensionX = subSpace[0];
gridDimensionY = subSpace[1];
} else
throw new DataStoreException("Cannot determine X and Y axis of the coverage");
checkAbortState();
final ImageReadParam imageParam;
try {
imageParam = createImageReadParam(0);
} catch (IOException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
}
final int[] srcBands;
final int[] dstBands;
MathTransform2D destToExtractedGrid = null;
boolean supportBandSelection = true;
if (param != null) {
srcBands = param.getSourceBands();
dstBands = param.getDestinationBands();
if (srcBands != null && dstBands != null && srcBands.length != dstBands.length) {
throw new IllegalArgumentException(Errors.getResources(locale).getString(Errors.Keys.MismatchedArrayLength_2, "sourceBands", "destinationBands"));
}
/*
* Convert geodetic envelope and resolution to pixel coordinates.
* Store the result of the above conversions in the ImageReadParam object.
*/
destToExtractedGrid = geodeticToPixelCoordinates(gridGeometry, param, imageParam, false);
/*
* Conceptually we could compute right now:
*
* AffineTransform change = new AffineTransform();
* change.translate(sourceRegion.x, sourceRegion.y);
* change.scale(xSubsampling, ySubsampling);
*
* However this implementation will scale only after the image has been read,
* because the MosaicImageReader may have changed the subsampling to more
* efficient values if it was authorized to make such change.
*/
if (srcBands != null) {
// -- particulaity case for tiff image reader which does not support setbands indexes.
try {
imageParam.setSourceBands(srcBands);
} catch (UnsupportedOperationException ex) {
LOGGER.log(Level.WARNING, ex.getMessage() + "Read coverage without set any source bands.");
supportBandSelection = false;
}
}
if (dstBands != null) {
try {
imageParam.setDestinationBands(dstBands);
} catch (UnsupportedOperationException ex) {
LOGGER.log(Level.WARNING, ex.getMessage() + "Read coverage without set any destination bands.");
}
}
} else {
srcBands = null;
dstBands = null;
}
/*
* Next, check if we should allow the image reader to add an offset to signed intergers
* in order to make them unsigned. We will allow such offset if the SampleDimensions
* declare unsigned range of sample values.
*/
boolean usePaletteFactory = false;
final SampleDimension[] bands = getSampleDimensions(index, supportBandSelection ? srcBands : null, supportBandSelection ? dstBands : null);
if (imageParam instanceof SpatialImageReadParam) {
final SpatialImageReadParam sp = (SpatialImageReadParam) imageParam;
if (!isRangeSigned(bands)) {
sp.setSampleConversionAllowed(SampleConversionType.SHIFT_SIGNED_INTEGERS, true);
}
sp.setSampleConversionAllowed(SampleConversionType.REPLACE_FILL_VALUES, true);
/*
* If the image does not have its own color palette, provides a palette factory
* which will create the IndexColorModel (if needed) from the SampleDimension.
*/
if (bands != null && imageReader instanceof SpatialImageReader)
try {
usePaletteFactory = !((SpatialImageReader) imageReader).hasColors(index);
} catch (IOException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
}
/*
* If there is supplemental dimensions (over the usual 2 dimensions) and the subclass
* implementation did not defined explicitely some dimension slices, then convert the
* envelope bounds in those supplemental dimensions to slices index.
*
* TODO: there is some duplication between this code and the work done in the parent
* class. We need to refactor geodeticToPixelCoordinates(…) in a new helper
* class for making easier to divide the work in smaller parts.
*/
if (param != null && !sp.hasDimensionSlices()) {
final int gridDim = gridGeometry.getDimension();
if (gridDim > 2) {
// max(X_DIMENSION, Y_DIMENSION) + 1
final CoordinateReferenceSystem crs = gridGeometry.getCoordinateReferenceSystem();
final int geodeticDim = crs.getCoordinateSystem().getDimension();
if (geodeticDim > 2) {
Envelope envelope = param.getEnvelope();
if (envelope != null && envelope.getDimension() > 2)
try {
if (crs instanceof CompoundCRS) {
envelope = CRSUtilities.appendMissingDimensions(envelope, (CompoundCRS) crs);
}
envelope = Envelopes.transform(envelope, crs);
final double[] median = new double[geodeticDim];
for (int i = 0; i < geodeticDim; i++) {
median[i] = envelope.getMedian(i);
}
final double[] indices = new double[gridDim];
gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER).inverse().transform(median, 0, indices, 0, 1);
final GridExtent gridExtent;
if (crs instanceof GridGeometry) {
gridExtent = ((GridGeometry) crs).getExtent();
} else {
// We can not fallback on gridGeometry.getExtent(), because
// GridGeometry2D contract forces all extra dimensions to have
// a span of 1.
gridExtent = null;
}
for (int i = 0; i < gridDim; i++) {
if (i != gridDimensionX && i != gridDimensionY) {
final double sliceIndex = indices[i];
if (!Double.isNaN(sliceIndex)) {
final DimensionSlice slice = sp.newDimensionSlice();
slice.addDimensionId(i);
slice.setSliceIndex((int) Math.round(Math.max(gridExtent != null ? gridExtent.getLow(i) : Integer.MIN_VALUE, Math.min(gridExtent != null ? gridExtent.getHigh(i) : Integer.MAX_VALUE, sliceIndex))));
}
}
}
} catch (TransformException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
}
}
}
}
}
checkAbortState();
/*
* Read the image using the ImageReader.read(...) method. We could have used
* ImageReader.readAsRenderedImage(...) instead in order to give the reader a
* chance to return a tiled image, but experience with some formats suggests
* that it requires to keep the ImageReader with its input stream open.
*/
final String name;
RenderedImage image;
try {
if (usePaletteFactory) {
SampleDimensionPalette.BANDS.set(bands);
((SpatialImageReadParam) imageParam).setPaletteFactory(SampleDimensionPalette.FACTORY);
}
if (param != null && param.isDeferred()) {
image = new LargeRenderedImage(imageReader.getOriginatingProvider(), imageParam, imageReader.getInput(), index, null);
} else {
image = imageReader.read(index, imageParam);
}
} catch (IOException | IllegalArgumentException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
} finally {
if (usePaletteFactory) {
SampleDimensionPalette.BANDS.remove();
}
}
/*
* If the grid geometry changed as a result of subsampling or reading a smaller region,
* update the grid geometry. The (xmin, ymin) values are usually (0,0), but we take
* them in account anyway as a paranoiac safety (a previous version of this code used
* the 'readAsRenderedImage(...)' method, which could have shifted the image).
*/
if (param != null) {
final Rectangle sourceRegion = imageParam.getSourceRegion();
final AffineTransform change = AffineTransform.getTranslateInstance(sourceRegion.x, sourceRegion.y);
change.scale(imageParam.getSourceXSubsampling(), imageParam.getSourceYSubsampling());
final int xmin = image.getMinX();
final int ymin = image.getMinY();
final int xi = gridDimensionX;
final int yi = gridDimensionY;
final MathTransform gridToCRS = gridGeometry.getGridToCRS(PixelInCell.CELL_CORNER);
MathTransform newGridToCRS = gridToCRS;
if (!change.isIdentity()) {
final int gridDimension = gridToCRS.getSourceDimensions();
final Matrix matrix = Matrices.createIdentity(gridDimension + 1);
matrix.setElement(xi, xi, change.getScaleX());
matrix.setElement(yi, yi, change.getScaleY());
matrix.setElement(xi, gridDimension, change.getTranslateX() - xmin);
matrix.setElement(yi, gridDimension, change.getTranslateY() - ymin);
newGridToCRS = MathTransforms.concatenate(MathTransforms.linear(matrix), gridToCRS);
}
final GridExtent gridExtent = gridGeometry.getExtent();
final long[] low = GridGeometryIterator.getLow(gridExtent);
final long[] high = GridGeometryIterator.getHigh(gridExtent);
low[xi] = xmin;
high[xi] = xmin + image.getWidth() - 1;
low[yi] = ymin;
high[yi] = ymin + image.getHeight() - 1;
if (imageParam instanceof SpatialImageReadParam) {
for (final DimensionSlice slice : ((SpatialImageReadParam) imageParam).getDimensionSlices()) {
for (final Object id : slice.getDimensionIds()) {
if (id instanceof Integer) {
final int dim = (Integer) id;
low[dim] = high[dim] = slice.getSliceIndex();
}
}
}
}
final GridExtent newGridRange = new GridExtent(null, low, high, true);
if (newGridToCRS != gridToCRS || !newGridRange.equals(gridExtent)) {
gridGeometry = new GridGeometry(newGridRange, PixelInCell.CELL_CORNER, newGridToCRS, gridGeometry.getCoordinateReferenceSystem());
}
}
final GridCoverage2D coverage = new org.apache.sis.coverage.grid.GridCoverage2D(gridGeometry, Arrays.asList(bands), image);
if (loggingEnabled) {
fullTime = System.nanoTime() - fullTime;
final Level level = getLogLevel(fullTime);
if (LOGGER.isLoggable(level)) {
ImageCoverageStore.logOperation(level, locale, ImageCoverageReader.class, false, input, index, coverage, null, null, destToExtractedGrid, fullTime);
}
}
return coverage;
}
Aggregations