use of org.geotoolkit.storage.coverage.ImageStatistics in project geotoolkit by Geomatys.
the class Statistics method analyse.
/**
* Run Statistics process with a RenderedImage and return ImageStatistics
* @param image RenderedImage to analyse
* @param excludeNoData exclude no-data flag (NaN values)
* @return ImageStatistics
* @throws ProcessException
*/
public static ImageStatistics analyse(RenderedImage image, boolean excludeNoData) throws ProcessException {
org.geotoolkit.process.Process process = new Statistics(image, excludeNoData);
Parameters out = Parameters.castOrWrap(process.call());
return out.getValue(OUTCOVERAGE);
}
use of org.geotoolkit.storage.coverage.ImageStatistics in project geotoolkit by Geomatys.
the class GO2Utilities method buildRanges.
/**
* Check given image statistics to find extremums to use for color interpretation.
*
* @implNote : Current code uses statistics min and max values directly only
* if no histogram with more than 3 values is available. Otherwise, we try to
* restrain extremums by ignoring 2% of extremum values. Note that we don't
* use standard deviation to build extremums, because in practice, it's very
* difficult to obtain coherent extremums using this information.
*
* @param stats Ready-to-use image statistics.
* @param numBands In case we don't want the same number of bands as described
* in the statistics (Example : we want an rgba image from rgb one).
* @return A 2D array, whose first dimension represents band indices, and
* second dimension has 2 values : chosen minimum at index 0 and maximum at
* index 1. Never null. Empty only if input statistics has no band defined.
*/
private static double[][] buildRanges(final ImageStatistics stats, final int numBands) {
final ImageStatistics.Band[] bands = stats.getBands();
final double[][] ranges = new double[numBands][2];
for (int bandIdx = 0; bandIdx < bands.length && bandIdx < numBands; bandIdx++) {
ranges[bandIdx][0] = Double.NEGATIVE_INFINITY;
ranges[bandIdx][1] = Double.POSITIVE_INFINITY;
final ImageStatistics.Band b = bands[bandIdx];
final long[] histogram = b.getHistogram();
// We remove extremums only if we've got a coherent histogram (contains more than just min/mean/max)
if (histogram != null && histogram.length > 3) {
final long valueCount = Arrays.stream(histogram).sum();
final long twoPercent = Math.round(valueCount * 0.02);
final double histogramStep = (b.getMax() - b.getMin()) / histogram.length;
long currentSum = 0;
for (int i = 0; i < histogram.length; i++) {
currentSum += histogram[i];
if (currentSum > twoPercent) {
ranges[bandIdx][0] = b.getMin() + histogramStep * i;
break;
}
}
currentSum = 0;
for (int i = histogram.length - 1; i > 0; i--) {
currentSum += histogram[i];
if (currentSum > twoPercent) {
ranges[bandIdx][1] = b.getMax() - histogramStep * (histogram.length - i - 1);
break;
}
}
}
if (!Double.isFinite(ranges[bandIdx][0])) {
ranges[bandIdx][0] = b.getMin();
}
if (!Double.isFinite(ranges[bandIdx][1])) {
ranges[bandIdx][1] = b.getMax();
}
}
return ranges;
}
use of org.geotoolkit.storage.coverage.ImageStatistics in project geotoolkit by Geomatys.
the class StatisticsTest method executionTestRepartition.
@Test
public void executionTestRepartition() throws NoSuchIdentifierException, ProcessException {
final ProcessDescriptor desc = ProcessFinder.getProcessDescriptor(GeotkProcessingRegistry.NAME, StatisticsDescriptor.NAME);
final ParameterValueGroup procparams = desc.getInputDescriptor().createValue();
procparams.parameter("inCoverage").setValue(coverage);
final org.geotoolkit.process.Process process = desc.createProcess(procparams);
final ParameterValueGroup result = process.call();
ImageStatistics statistics = (ImageStatistics) result.parameter("outStatistic").getValue();
ImageStatistics.Band band0 = statistics.getBands()[0];
// test min / max values
Assert.assertEquals(100d, band0.getMin(), 0d);
Assert.assertEquals(200d, band0.getMax(), 0d);
// test distribution
Assert.assertTrue(1l == band0.getDistribution().get(200d));
Assert.assertTrue(8l == band0.getDistribution().get(100d));
}
use of org.geotoolkit.storage.coverage.ImageStatistics in project geotoolkit by Geomatys.
the class GO2Utilities method inferStyle.
/**
* Try to create a simple style from given statistics. For now, two cases exists:
* <ol>
* <li>
* Less than 3 bands are detected. In this case, we simply create a color map over first band. The
* color map make use of standard deviation to compute interpolation boundaries.
* </li>
* <li>
* For 3 bands or more, we create a dynamic range RGB rendering based on first bands in the statistics. We
* define value boundary for each band using {@link #buildRanges(ImageStatistics, int) a custom empiric
* method removing extremums}.
* </li>
* </ol>
* @param stats The statistics to use for style creation. Can be null or empty, in which case nothing is returned.
* @param forceAlpha A flag used only if given statistics contains more than 3 bands. Indicates that for RGB styling,
* we must interpret 4th band as alpha component.
* @return A style inferred from input statistics, or an empty shell if given argument does not contains enough
* information.
* @throws IOException It can happen when trying to access a color map definition on disk, but it fails.
*/
public static Optional<MutableStyle> inferStyle(final ImageStatistics stats, final boolean forceAlpha) throws IOException {
if (stats == null)
return Optional.empty();
final ImageStatistics.Band[] bands = stats.getBands();
if (bands == null || bands.length < 1)
return Optional.empty();
if (bands.length < 3) {
LOGGER.log(Level.FINE, "applyColorMapStyle : fallBack way is choosen." + "GrayScale interpretation of the first coverage image band.");
final ImageStatistics.Band band0 = bands[0];
final Double bmin = band0.getMin();
final Double bmax = band0.getMax();
if (bmin == null || bmax == null || !Double.isFinite(bmin) || !Double.isFinite(bmax)) {
LOGGER.log(Level.WARNING, "Image statistics object is present but contains null or non-finite extremas. Ignore it");
return Optional.empty();
}
double palMin = bmin;
double palMax = bmax;
final Double mean = band0.getMean();
final Double std = band0.getStd();
if (mean != null && std != null && Double.isFinite(mean) && Double.isFinite(std)) {
palMin = Math.max(bmin, mean - 2 * std);
palMax = Math.min(bmax, mean + 2 * std);
}
if (!Double.isFinite(palMin) || !Double.isFinite(palMax)) {
LOGGER.finest("Adapting rendering distribution failed. Fallback on input min/max");
palMin = bmin;
palMax = bmax;
}
assert Double.isFinite(palMin) : "Raster Style fallback : minimum value should be finite. min = " + palMin;
assert Double.isFinite(palMax) : "Raster Style fallback : maximum value should be finite. max = " + palMax;
assert palMin >= bmin;
assert palMax <= bmax;
final List<InterpolationPoint> values = new ArrayList<>();
final double[] nodatas = band0.getNoData();
if (nodatas != null)
for (double nodata : nodatas) {
values.add(STYLE_FACTORY.interpolationPoint(nodata, STYLE_FACTORY.literal(new Color(0, 0, 0, 0))));
}
values.add(STYLE_FACTORY.interpolationPoint(Float.NaN, STYLE_FACTORY.literal(new Color(0, 0, 0, 0))));
// -- Color palette
// Color[] colorsPal = PaletteFactory.getDefault().getColors("rainbow-t");
Color[] colorsPal = PaletteFactory.getDefault().getColors("grayscale");
assert colorsPal.length >= 2;
if (colorsPal.length < 4) {
final double percent_5 = (colorsPal.length == 3) ? 0.1 : 0.05;
final Color[] colorsPalTemp = colorsPal;
colorsPal = Arrays.copyOf(colorsPal, colorsPal.length + 2);
System.arraycopy(colorsPalTemp, 2, colorsPal, 2, colorsPalTemp.length - 2);
colorsPal[colorsPal.length - 1] = colorsPalTemp[colorsPalTemp.length - 1];
colorsPal[1] = DefaultInterpolate.interpolate(colorsPalTemp[0], colorsPalTemp[1], percent_5);
colorsPal[colorsPal.length - 2] = DefaultInterpolate.interpolate(colorsPalTemp[colorsPalTemp.length - 2], colorsPalTemp[colorsPalTemp.length - 1], 1 - percent_5);
}
// -- if difference between band minimum statistic and palette minimum,
// -- define values between them as transparency
values.add(STYLE_FACTORY.interpolationPoint(bmin, STYLE_FACTORY.literal(colorsPal[0])));
assert colorsPal.length >= 4;
// -- min and max transparency
final double step = (palMax - palMin) / (colorsPal.length - 3);
double currentVal = palMin;
for (int c = 1; c <= colorsPal.length - 2; c++) {
values.add(STYLE_FACTORY.interpolationPoint(currentVal, STYLE_FACTORY.literal(colorsPal[c])));
currentVal += step;
}
assert StrictMath.abs(currentVal - step - palMax) < 1E-9;
values.add(STYLE_FACTORY.interpolationPoint(bmax, STYLE_FACTORY.literal(colorsPal[colorsPal.length - 1])));
final Expression function = STYLE_FACTORY.interpolateFunction(DEFAULT_CATEGORIZE_LOOKUP, values, Method.COLOR, Mode.LINEAR, DEFAULT_FALLBACK);
final ColorMap colorMap = STYLE_FACTORY.colorMap(function);
final RasterSymbolizer symbol = STYLE_FACTORY.rasterSymbolizer(band0.getName(), null, null, null, colorMap, null, null, null);
return Optional.of(STYLE_FACTORY.style(symbol));
} else {
LOGGER.log(Level.FINE, "RGBStyle : fallBack way is choosen." + "RGB interpretation of the three first coverage image bands.");
final int rgbNumBand = forceAlpha && bands.length > 3 ? 4 : 3;
assert rgbNumBand <= bands.length;
final double[][] ranges = buildRanges(stats, 4);
final List<DRChannel> channels = new ArrayList<>();
for (int i = 0; i < rgbNumBand; i++) {
final DRChannel channel = new DRChannel();
final String bandName = bands[i].getName();
channel.setBand(bandName == null || bandName.isEmpty() ? Integer.toString(i) : bandName);
channel.setColorSpaceComponent(DynamicRangeSymbolizer.DRChannel.RGBA_COMPONENTS[i]);
DynamicRangeSymbolizer.DRBound drBound = new DynamicRangeSymbolizer.DRBound();
drBound.setValue(FILTER_FACTORY.literal(ranges[i][0]));
channel.setLower(drBound);
drBound = new DynamicRangeSymbolizer.DRBound();
drBound.setValue(FILTER_FACTORY.literal(ranges[i][1]));
channel.setUpper(drBound);
channels.add(channel);
}
final DynamicRangeSymbolizer drgb = new DynamicRangeSymbolizer();
drgb.setChannels(channels);
return Optional.of(STYLE_FACTORY.style(drgb));
}
}
use of org.geotoolkit.storage.coverage.ImageStatistics in project geotoolkit by Geomatys.
the class RasterSymbolizerRenderer method applyColorMapStyle.
/**
* Apply {@linkplain RasterSymbolizer#getColorMap() color map style properties} on current coverage if need.<br><br>
*
* In case where no {@linkplain ColorMap#getFunction() sample to geophysic}
* transformation function is available and coverage is define as {@link ViewType#GEOPHYSICS}
* a way is find to avoid empty result, like follow : <br>
* The first band from {@linkplain GridCoverage2D#getRenderedImage() coverage image} is selected
* and a grayscale color model is apply from {@linkplain ImageStatistics computed image statistic}.
*
* @param ref needed to compute statistics from internal metadata in case where missing informations.
* @param coverage color map style apply on this object.
* @param styleElement the {@link RasterSymbolizer} which contain color map properties.
* @return image which is the coverage exprimate into {@link ViewType#PHOTOGRAPHIC}.
* @throws ProcessException if problem during statistic problem.
*/
private static RenderedImage applyColorMapStyle(final GridCoverageResource ref, GridCoverage coverage, final RasterSymbolizer styleElement) throws ProcessException, IOException, PortrayalException {
ensureNonNull("CoverageReference", ref);
ensureNonNull("coverage", coverage);
ensureNonNull("styleElement", styleElement);
RenderedImage resultImage;
// Recolor coverage -----------------------------------------------------
final ColorMap recolor = styleElement.getColorMap();
recolorCase: if (recolor == null || recolor.getFunction() == null) {
resultImage = coverage.forConvertedValues(false).render(null);
final SampleModel sampleMod = resultImage.getSampleModel();
final ColorModel riColorModel = resultImage.getColorModel();
/**
* Break computing statistic if indexcolormodel is already adapted for java 2d interpretation
* (which mean index color model with positive colormap array index -> DataBuffer.TYPE_BYTE || DataBuffer.TYPE_USHORT)
* or if image has already 3 or 4 bands Byte typed.
*/
if (riColorModel != null && !defaultStyleIsNeeded(sampleMod, riColorModel)) {
break recolorCase;
}
// if there is no geophysic, the same coverage is returned
coverage = coverage.forConvertedValues(true);
CoverageDescription covRefMetadata = null;
if (covRefMetadata == null) {
final Metadata metadata;
try {
metadata = ref.getMetadata();
} catch (DataStoreException ex) {
throw new IOException("Cannot fetch metadata from input resource.", ex);
}
covRefMetadata = MetadataUtilities.extractCoverageDescription(metadata).findFirst().orElse(null);
}
ImageStatistics analyse = null;
if (covRefMetadata != null) {
analyse = ImageStatistics.transform(covRefMetadata);
if (analyse != null) {
// Ensure band statistics are valid.
for (ImageStatistics.Band b : analyse.getBands()) {
if (b.getMax() == null || b.getMin() == null || b.getMax() - b.getMin() < 1e-11) {
analyse = null;
break;
}
}
}
}
// ensure consistency over tiled rendering (cf. OpenLayer/WMS).
if (analyse == null) {
analyse = Statistics.analyse(coverage.render(null), true);
}
final Optional<MutableStyle> styleFromStats = GO2Utilities.inferStyle(analyse, (riColorModel == null) ? true : riColorModel.hasAlpha());
if (styleFromStats.isPresent()) {
/* WARNING: That's neither optimal nor stable. However, do not know any other way to override style on
* the fly.
*
* !!! IMPORTANT !!!
* The canvas here is created with the geometry of input coverage, because otherwise, we would apply
* two times the affine transform to display system.
*/
final MapLayers subCtx = MapBuilder.createContext();
subCtx.getComponents().add(MapBuilder.createCoverageLayer(coverage, styleFromStats.get()));
resultImage = DefaultPortrayalService.portray(new CanvasDef(coverage.getGridGeometry()), new SceneDef(subCtx));
}
} else {
// color map is applied on geophysics view
// if there is no geophysic, the same coverage is returned
coverage = coverage.forConvertedValues(true);
resultImage = (RenderedImage) recolor.getFunction().apply(coverage.render(null));
}
assert resultImage != null : "applyColorMapStyle : image can't be null.";
return resultImage;
}
Aggregations