/*
* Data HUb Service (DHuS) - For Space data distribution.
* Copyright (C) 2013,2014,2015,2016 European Space Agency (ESA)
* Copyright (C) 2013,2014,2015,2016 GAEL Systems
* Copyright (C) 2013,2014,2015,2016 Serco Spa
*
* This file is part of DHuS software sources.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.gael.drb.cortex.topic.sentinel3.jai.operator;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.media.jai.ColorSpaceJAI;
import javax.media.jai.FloatDoubleColorModel;
import javax.media.jai.ImageLayout;
import javax.media.jai.PixelAccessor;
import javax.media.jai.PointOpImage;
import javax.media.jai.UnpackedImageData;
import com.sun.media.jai.codecimpl.util.RasterFactory;
import com.sun.media.jai.util.JDKWorkarounds;
import fr.gael.drb.cortex.topic.sentinel3.jai.operator.Common.PixelCorrection;
@SuppressWarnings ({"unchecked","rawtypes"})
public class QuicklookOlciOpImage extends PointOpImage
{
private short[][] detectors;
private double[][] sza;
private boolean hasSza;
private float[][] solarFlux;
// Bands and corrections
private int bands[]; // band[0]=Red, band[1]=Green,band[2]=Blue
private int bandsCoefficients[];
private PixelCorrection[]pixelsCorrection;
private boolean hasPixelCorrection;
/** List of ColorModels required for IndexColorModel support */
private ColorModel[] colorModels;
/**
* Constructs a <code>QuicklookOlciOpImage</code>.
*
* <p>
* The <code>layout</code> parameter may optionally contain the tile grid
* layout, sample model, and/or color model. The image dimension is
* determined by the intersection of the bounding boxes of the source images.
*
* For OLCI dataset, all the sources has the same dimension, and color model.
*
* <p>
* The image layout of the first source image, <code>source1</code>, is
* used as the fallback for the image layout of the destination image. The
* destination number of bands is the sum of all source image bands.
*
* @param sources <code>List</code> of sources [Red, Green, Blue].
* @param config Configurable attributes of the image including configuration
* variables indexed by <code>RenderingHints.Key</code>s and image
* properties indexed by <code>String</code>s or
* <code>CaselessStringKey</code>s. This is simply forwarded to the
* superclass constructor.
* @param detectors Array of detectors indexes.
* @param sza Array of Sun azimuth angle.
* @param solar_flux solar flux.
* @param layout The destination image layout.
*/
public QuicklookOlciOpImage(List sources, Map config,
short[][] detectors, double[][] sza, float[][] solar_flux,
PixelCorrection[]pixels_correction, int[]bands, int[]bands_coefficients,
ImageLayout layout)
{
super(vectorize(sources), layoutHelper(sources, layout, false), config,
true);
// Set flag to permit in-place operation.
permitInPlaceOperation();
this.detectors = detectors;
this.sza = sza;
this.hasSza = sza!=null;
this.solarFlux = solar_flux;
this.bands = bands;
this.bandsCoefficients = bands_coefficients;
this.pixelsCorrection = pixels_correction;
this.hasPixelCorrection = pixels_correction!=null;
if (this.hasPixelCorrection)
for (PixelCorrection pc: pixels_correction)
this.hasPixelCorrection &= pc!=null;
// get ColorModels for IndexColorModel support
int numSrcs = sources.size();
colorModels = new ColorModel[numSrcs];
for (int i = 0; i < numSrcs; i++)
colorModels[i] = ((RenderedImage) sources.get(i)).getColorModel();
}
/**
* This computeRect method only implements USHORT data type.
*/
@Override
protected void computeRect(Raster[] sources, WritableRaster dest,
Rectangle destRect)
{
// Source number
int nSrcs = sources.length;
// Bands associated with each sources
int[] snbands = new int[nSrcs];
// PixelAccessor array for each source
PixelAccessor[] pas = new PixelAccessor[nSrcs];
for (int i = 0; i < nSrcs; i++)
{
pas[i] = new PixelAccessor(sources[i].getSampleModel(),colorModels[i]);
if (colorModels[i] instanceof IndexColorModel)
snbands[i]=colorModels[i].getNumComponents();
else
snbands[i] = sources[i].getNumBands();
}
// Destination bands
int dnbands = dest.getNumBands();
// Destination data type
int destType = dest.getTransferType();
// PixelAccessor associated with the destination raster
PixelAccessor d = new PixelAccessor(dest.getSampleModel(), null);
UnpackedImageData dimd = d.getPixels(dest, destRect, destType, true);
// Destination data values
short[][] dstdata = (short[][]) dimd.data;
// Cycle on all the sources
for (int sindex = 0, db = 0; sindex < nSrcs; sindex++)
{
UnpackedImageData simd =
colorModels[sindex] instanceof IndexColorModel ?
pas[sindex].getComponents(sources[sindex], destRect,
sources[sindex].getSampleModel().getTransferType())
: pas[sindex].getPixels(sources[sindex],
destRect, sources[sindex].getSampleModel().getTransferType(),
false);
int srcPixelStride = simd.pixelStride;
int srcLineStride = simd.lineStride;
int dstPixelStride = dimd.pixelStride;
int dstLineStride = dimd.lineStride;
int dRectWidth = destRect.width;
// Cycle on each source bands
for (int sb = 0; sb < snbands[sindex]; sb++, db++)
{
if (db >= dnbands)
{
// exceeding destNumBands; should not have happened
break;
}
short[] dstdatabandb = dstdata[db];
short[][] srcdata = (short[][]) simd.data;
short[] srcdatabandsb = srcdata[sb];
int srcstart = simd.bandOffsets[sb];
int dststart = dimd.bandOffsets[db];
int srcbandindex = sb+sindex;
// Cycle on the y-axis
for (int y=destRect.y; y<(destRect.height+destRect.y);
y++, srcstart+=srcLineStride, dststart+=dstLineStride)
{
// Cycle on the x-axis
for (int i=0, srcpos=srcstart, dstpos=dststart;
i<dRectWidth;
i++, srcpos+=srcPixelStride, dstpos+=dstPixelStride)
{
double radiance=srcdatabandsb[srcpos];
// As srcdatabandsb is a table of short values (not unsigned),
// overflows shall be managed.
if (radiance<0) radiance+=(2*(Short.MAX_VALUE+1));
if (hasPixelCorrection)
radiance = pixelsCorrection[srcbandindex].scale*radiance +
pixelsCorrection[srcbandindex].offset;
int detector = ((int)detectors[y][srcpos]) ;
// As detectors is a table of short values (not unsigned),
// overflows shall be managed.
if (detector<0) detector+=(2*(Short.MAX_VALUE+1));
double angle = 0;
if (this.hasSza) angle = sza[y][srcpos/64];
if (detector != 65535)
{
float solFlux =
this.solarFlux[bands[srcbandindex]-1][detector];
double ln = radiance / solFlux;
double reflectance = Math.PI * ln /
Math.cos(Math.toRadians(angle));
// weight
reflectance = reflectance*bandsCoefficients[srcbandindex];
if (reflectance > 1)
reflectance = 1.; // sometimes saturates over clouds.
short pixel_value =
(short)(reflectance*(double)Common.colorRange);
dstdatabandb[dstpos] = pixel_value;
}
}
}
}
}
d.setPixels(dimd);
}
/**
* Create a colormodel without an alpha band in the case that no alpha band
* is present. Otherwise JAI set an alpha band by default for an image with 2
* or 4 bands.
*
* @param sm
* @param setAlpha
* @return
*/
public static ColorModel getDefaultColorModel(SampleModel sm,
boolean setAlpha)
{
// Check on the data type
int dataType = sm.getDataType();
int numBands = sm.getNumBands();
if (dataType<DataBuffer.TYPE_BYTE || dataType==DataBuffer.TYPE_SHORT ||
dataType>DataBuffer.TYPE_DOUBLE || numBands<1 || numBands>4)
{
return null;
}
// Creation of the colorspace
ColorSpace cs = null;
switch(numBands)
{
case 0:
throw new IllegalArgumentException("No input bands defined");
case 1:
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
break;
case 2:
case 4:
if (setAlpha)
{
cs = (numBands==2)?ColorSpace.getInstance(ColorSpaceJAI.CS_GRAY)
: ColorSpace.getInstance(ColorSpaceJAI.CS_sRGB);
}
else
{
// For 2 and 4 bands a custom colorspace is created
cs = new ColorSpace(dataType, numBands)
{
private static final long serialVersionUID = 1L;
@Override
public float[] toRGB(float[] colorvalue)
{
return null;
}
@Override
public float[] toCIEXYZ(float[] colorvalue)
{
return null;
}
@Override
public float[] fromRGB(float[] rgbvalue)
{
return null;
}
@Override
public float[] fromCIEXYZ(float[] colorvalue)
{
return null;
}
};
}
break;
case 3:
cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
break;
default:
return null;
}
// Definition of the colormodel
int dataTypeSize = DataBuffer.getDataTypeSize(dataType);
int[] bits = new int[numBands];
for (int i = 0; i < numBands; i++)
{
bits[i] = dataTypeSize;
}
boolean useAlpha = false, premultiplied = false;
int transparency = Transparency.OPAQUE;
switch(dataType)
{
case DataBuffer.TYPE_BYTE:
return new ComponentColorModel(cs, bits, useAlpha, premultiplied,
transparency, dataType);
case DataBuffer.TYPE_USHORT:
return new ComponentColorModel(cs, bits, useAlpha, premultiplied,
transparency, dataType);
case DataBuffer.TYPE_INT:
return new ComponentColorModel(cs, bits, useAlpha, premultiplied,
transparency, dataType);
case DataBuffer.TYPE_FLOAT:
return new FloatDoubleColorModel(cs, useAlpha, premultiplied,
transparency, dataType);
case DataBuffer.TYPE_DOUBLE:
return new FloatDoubleColorModel(cs, useAlpha, premultiplied,
transparency, dataType);
default:
throw new IllegalArgumentException("Wrong data type used");
}
}
/**
* This method takes in input the list of all the sources and calculates the
* total number of bands of the destination image.
*
* @param sources List of the source images
* @return the total number of the destination bands
*/
private static int totalNumBands(List sources)
{
// Initialization
int total = 0;
// Cycle on all the sources
for (int i = 0; i < sources.size(); i++)
{
RenderedImage image = (RenderedImage) sources.get(i);
// If the source ColorModel is IndexColorModel, then the bands are
// defined by its components
if (image.getColorModel() instanceof IndexColorModel)
{
total += image.getColorModel().getNumComponents();
// Else the bands are defined from the SampleModel
}
else
{
total += image.getSampleModel().getNumBands();
}
}
// Total bands number
return total;
}
private static ImageLayout layoutHelper(List sources, ImageLayout il,
boolean setAlpha)
{
// If the layout is not defined, a new one is created, else is cloned
ImageLayout layout =
(il == null) ? new ImageLayout() : (ImageLayout) il.clone();
// Number of input sources
int numSources = sources.size();
// dest data type is the maximum of transfertype of source image
// utilizing the monotonicity of data types.
// dest number of bands = sum of source bands
int destNumBands = totalNumBands(sources);
int destDataType = DataBuffer.TYPE_BYTE; // initialize
RenderedImage srci = (RenderedImage) sources.get(0);
// Destination Bounds are taken from the first image
Rectangle destBounds = new Rectangle(srci.getMinX(), srci.getMinY(),
srci.getWidth(), srci.getHeight());
// Cycle on all the images
for (int i = 0; i < numSources; i++)
{
// Selection of a source
srci = (RenderedImage) sources.get(i);
// Intersection of the initial bounds with the source bounds
destBounds = destBounds.intersection(new Rectangle(srci.getMinX(),
srci.getMinY(), srci.getWidth(), srci.getHeight()));
// Selection of the source TransferType
int typei = srci.getSampleModel().getTransferType();
// NOTE: this depends on JDK ordering
destDataType = typei > destDataType ? typei : destDataType;
}
// Definition of the Layout
layout.setMinX(destBounds.x);
layout.setMinY(destBounds.y);
layout.setWidth(destBounds.width);
layout.setHeight(destBounds.height);
// First image sampleModel
SampleModel sm = layout.getSampleModel((RenderedImage) sources.get(0));
// Creation of a new SampleModel with the new settings
if (sm.getNumBands() < destNumBands)
{
int[] destOffsets = new int[destNumBands];
for (int i = 0; i < destNumBands; i++)
{
destOffsets[i] = i;
}
// determine the proper width and height to use
int destTileWidth = sm.getWidth();
int destTileHeight = sm.getHeight();
if (layout.isValid(ImageLayout.TILE_WIDTH_MASK))
{
destTileWidth = layout.getTileWidth((RenderedImage) sources.get(0));
}
if (layout.isValid(ImageLayout.TILE_HEIGHT_MASK))
{
destTileHeight =
layout.getTileHeight((RenderedImage) sources.get(0));
}
sm = RasterFactory.createComponentSampleModel(sm, destDataType,
destTileWidth, destTileHeight,destNumBands);
layout.setSampleModel(sm);
}
// Selection of a colormodel associated with the layout
ColorModel cm = layout.getColorModel(null);
if (cm != null && !JDKWorkarounds.areCompatibleDataModels(sm, cm))
{
// Clear the mask bit if incompatible.
layout.unsetValid(ImageLayout.COLOR_MODEL_MASK);
}
if ( (cm == null || !cm.hasAlpha()) && sm instanceof ComponentSampleModel)
{
cm = getDefaultColorModel(sm, setAlpha);
layout.setColorModel(cm);
}
return layout;
}
/**
* This method takes in input a List of Objects and creates a vector from its
* elements
*
* @param sources list of the input sources
* @return a vector of all the input list
*/
private static Vector vectorize(List sources)
{
if (sources instanceof Vector)
{
return (Vector) sources;
}
else
{
Vector vector = new Vector(sources.size());
for (Object element : sources)
{
vector.add(element);
}
return vector;
}
}
}