use of org.apache.sis.coverage.Category in project sis by apache.
the class CoverageStyling method createCategoryTable.
/**
* Creates a table showing the color of a qualitative or quantitative coverage categories.
* The color can be modified by selecting the table row, then clicking on the color.
*
* @param vocabulary localized resources, given because already known by the caller
* (this argument would be removed if this method was public API).
*/
final TableView<Category> createCategoryTable(final Resources resources, final Vocabulary vocabulary) {
final TableColumn<Category, String> name = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Name));
name.setCellValueFactory(CoverageStyling::getCategoryName);
name.setCellFactory(CoverageStyling::createNameCell);
name.setEditable(false);
name.setId("name");
/*
* Create the table with above "category name" column (read-only),
* and add an editable column for color(s).
*/
final TableView<Category> table = new TableView<>();
table.getColumns().add(name);
addColumnTo(table, vocabulary.getString(Vocabulary.Keys.Colors));
/*
* Add contextual menu items.
*/
final MenuItem reset = new MenuItem(resources.getString(Resources.Keys.ClearAll));
reset.setOnAction((e) -> clear(table.getItems()));
table.setContextMenu(new ContextMenu(reset));
return table;
}
use of org.apache.sis.coverage.Category in project sis by apache.
the class RecoloredImage method stretchColorRamp.
/**
* Returns an image with the same sample values than the given image, but with its color ramp stretched
* between specified or inferred bounds. The mapping applied by this method is conceptually a linear
* transform applied on sample values before they are mapped to their colors.
*
* <p>Current implementation can stretch gray scale and {@linkplain IndexColorModel index color models}).
* If this method can not stretch the color ramp, for example because the given image is an RGB image,
* then the image is returned unchanged.</p>
*
* @param processor the processor to use for computing statistics if needed.
* @param source the image to recolor (can be {@code null}).
* @param modifiers modifiers for narrowing the range of values, or {@code null} if none.
* @return the image with color ramp stretched between the automatic bounds,
* or {@code image} unchanged if the operation can not be applied on the given image.
*
* @see ImageProcessor#stretchColorRamp(RenderedImage, Map)
*/
static RenderedImage stretchColorRamp(final ImageProcessor processor, final RenderedImage source, final Map<String, ?> modifiers) {
/*
* Images having more than one band (without any band marked as the single band to show) are probably
* RGB images. It would be possible to stretch the Red, Green and Blue bands separately, but current
* implementation don't do that because we do not have yet a clear use case.
*/
final int visibleBand = ImageUtilities.getVisibleBand(source);
if (visibleBand < 0) {
return source;
}
/*
* Main use case: color model is (probably) an IndexColorModel or ScaledColorModel instance,
* or something we can handle in the same way.
*/
RenderedImage statsSource = source;
Statistics[] statsAllBands = null;
Statistics statistics = null;
Shape areaOfInterest = null;
Number[] nodataValues = null;
SampleDimension range = null;
double minimum = Double.NaN;
double maximum = Double.NaN;
double deviations = Double.POSITIVE_INFINITY;
/*
* Extract and validate parameter values.
* No calculation started at this stage.
*/
if (modifiers != null) {
final Number minValue = Containers.property(modifiers, "minimum", Number.class);
final Number maxValue = Containers.property(modifiers, "maximum", Number.class);
if (minValue != null)
minimum = minValue.doubleValue();
if (maxValue != null)
maximum = maxValue.doubleValue();
if (minimum >= maximum) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalRange_2, minValue, maxValue));
}
{
// For keeping `value` in local scope.
final Number value = Containers.property(modifiers, "multStdDev", Number.class);
if (value != null) {
deviations = value.doubleValue();
ArgumentChecks.ensureStrictlyPositive("multStdDev", deviations);
}
}
areaOfInterest = Containers.property(modifiers, "areaOfInterest", Shape.class);
Object value = modifiers.get("nodataValues");
if (value != null) {
if (value instanceof Number) {
nodataValues = new Number[] { (Number) value };
} else if (value instanceof Number[]) {
nodataValues = (Number[]) value;
} else {
throw illegalPropertyType(modifiers, "nodataValues", value);
}
}
value = modifiers.get("statistics");
if (value != null) {
if (value instanceof RenderedImage) {
statsSource = (RenderedImage) value;
} else if (value instanceof Statistics) {
statistics = (Statistics) value;
} else if (value instanceof Statistics[]) {
statsAllBands = (Statistics[]) value;
} else {
throw illegalPropertyType(modifiers, "statistics", value);
}
}
value = modifiers.get("sampleDimensions");
if (value != null) {
if (value instanceof List<?>) {
final List<?> ranges = (List<?>) value;
if (visibleBand < ranges.size()) {
value = ranges.get(visibleBand);
}
}
if (value != null) {
if (value instanceof SampleDimension) {
range = (SampleDimension) value;
} else {
throw illegalPropertyType(modifiers, "sampleDimensions", value);
}
}
}
}
/*
* If minimum and maximum values were not explicitly specified, compute them from statistics.
* If the range is not valid, then the image will be silently returned as-is.
*/
if (Double.isNaN(minimum) || Double.isNaN(maximum)) {
if (statistics == null) {
if (statsAllBands == null) {
final DoubleUnaryOperator[] sampleFilters = new DoubleUnaryOperator[visibleBand + 1];
sampleFilters[visibleBand] = processor.filterNodataValues(nodataValues);
statsAllBands = processor.valueOfStatistics(statsSource, areaOfInterest, sampleFilters);
}
if (statsAllBands != null && visibleBand < statsAllBands.length) {
statistics = statsAllBands[visibleBand];
}
}
if (statistics != null) {
deviations *= statistics.standardDeviation(true);
final double mean = statistics.mean();
if (Double.isNaN(minimum))
minimum = Math.max(statistics.minimum(), mean - deviations);
if (Double.isNaN(maximum))
maximum = Math.min(statistics.maximum(), mean + deviations);
}
}
if (!(minimum < maximum)) {
// Use ! for catching NaN.
return source;
}
/*
* Finished to collect information. Derive a new color model from the existing one.
*/
final ColorModel cm;
if (source.getColorModel() instanceof IndexColorModel) {
/*
* Get the range of indices of RGB values than can be used for interpolations.
* We want to exclude qualitative categories (no data, clouds, forests, etc.).
* In the vast majority of cases, we have at most one quantitative category.
* But if there is 2 or more, then we select the one having largest intersection
* with the [minimum … maximum] range.
*/
final IndexColorModel icm = (IndexColorModel) source.getColorModel();
final int size = icm.getMapSize();
int validMin = 0;
// Inclusive.
int validMax = size - 1;
if (range != null) {
double span = 0;
for (final Category category : range.getCategories()) {
if (category.isQuantitative()) {
final NumberRange<?> r = category.getSampleRange();
final double min = Math.max(r.getMinDouble(true), 0);
final double max = Math.min(r.getMaxDouble(true), size - 1);
// Intersection.
final double s = Math.min(max, maximum) - Math.max(min, minimum);
if (s > span) {
validMin = (int) min;
validMax = (int) max;
span = s;
}
}
}
}
/*
* Create a copy of RGB codes and replace values in the range of the quantitative category.
* Values for other categories (qualitative) are left unmodified.
*/
final int start = Math.max((int) minimum, validMin);
// Inclusive.
final int end = Math.min((int) maximum, validMax);
final int[] ARGB = new int[size];
// Initialize to a copy of current colors.
icm.getRGBs(ARGB);
// Part of quantitative category outside the new range.
Arrays.fill(ARGB, validMin, start, icm.getRGB(validMin));
Arrays.fill(ARGB, end + 1, validMax + 1, icm.getRGB(validMax));
final float scale = (float) ((validMax - validMin) / (maximum - minimum));
for (int i = start; i <= end; i++) {
final float s = (i - start) * scale + validMin;
ARGB[i] = icm.getRGB(Math.round(s));
}
final SampleModel sm = source.getSampleModel();
cm = ColorModelFactory.createIndexColorModel(sm.getNumBands(), visibleBand, ARGB, icm.hasAlpha(), icm.getTransparentPixel());
} else {
/*
* Wraps the given image with its colors ramp scaled between the given bounds. If the given image is
* already using a color ramp for the given range of values, then that image is returned unchanged.
*/
final SampleModel sm = source.getSampleModel();
cm = ColorModelFactory.createGrayScale(sm.getDataType(), sm.getNumBands(), visibleBand, minimum, maximum);
}
return create(source, cm);
}
use of org.apache.sis.coverage.Category in project sis by apache.
the class Colorizer method initialize.
/**
* Uses the given sample dimension for mapping range of values to colors. For each category in
* the sample dimension, colors will be determined by a call to {@code colors.apply(category)}
* where {@code colors} is the function specified at construction time.
*
* @param source description of range of values in the source image, or {@code null}.
* @return {@code true} on success, or {@code false} if no range of values has been found.
* @throws IllegalStateException if a sample dimension is already defined on this colorizer.
*/
public boolean initialize(final SampleDimension source) {
checkInitializationStatus(false);
if (source != null) {
this.source = source;
final List<Category> categories = source.getCategories();
if (!categories.isEmpty()) {
final ColorsForRange[] entries = new ColorsForRange[categories.size()];
for (int i = 0; i < entries.length; i++) {
final Category category = categories.get(i);
entries[i] = new ColorsForRange(category, category.getSampleRange(), colors.apply(category));
}
// Leave `target` to null. It will be computed by `compact()` if needed.
this.entries = entries;
return true;
}
}
return false;
}
use of org.apache.sis.coverage.Category in project sis by apache.
the class Colorizer method initialize.
/*
* Do not provide methods taking Raster or RenderedImage argument.
* See class javadoc for rational.
*/
/**
* Applies colors on the given range of values. The 0 index will be reserved for NaN value,
* and indices in the [1 … 255] will be mapped to the given range.
*
* <p>This method is typically used as a last resort fallback when other {@code initialize(…)}
* methods failed or can not be applied.</p>
*
* @param minimum minimum value, inclusive.
* @param maximum maximum value, inclusive.
* @throws IllegalStateException if a sample dimension is already defined on this colorizer.
*/
public void initialize(final double minimum, final double maximum) {
checkInitializationStatus(false);
ArgumentChecks.ensureFinite("minimum", minimum);
ArgumentChecks.ensureFinite("maximum", maximum);
defaultRange = NumberRange.create(minimum, true, maximum, true);
target = new SampleDimension.Builder().setBackground(null, 0).addQuantitative(Vocabulary.formatInternational(Vocabulary.Keys.Data), NumberRange.create(1, true, MAX_VALUE, true), defaultRange).build();
source = target.forConvertedValues(true);
final List<Category> categories = source.getCategories();
final ColorsForRange[] entries = new ColorsForRange[categories.size()];
for (int i = 0; i < entries.length; i++) {
final Category category = categories.get(i);
entries[i] = new ColorsForRange(category, category.forConvertedValues(false).getSampleRange(), colors.apply(category));
}
this.entries = entries;
}
use of org.apache.sis.coverage.Category in project sis by apache.
the class CoverageStylingApp method createCategoryTable.
/**
* Creates a table with arbitrary categories to show.
*/
private static TableView<Category> createCategoryTable() {
final SampleDimension band = new SampleDimension.Builder().addQualitative("Background", 0).addQualitative("Cloud", 1).addQualitative("Land", 2).addQuantitative("Temperature", 5, 255, 0.15, -5, Units.CELSIUS).setName("Sea Surface Temperature").build();
final CoverageStyling styling = new CoverageStyling(null);
styling.setARGB(band.getCategories().get(1), new int[] { 0xFF607080 });
final TableView<Category> table = styling.createCategoryTable(Resources.forLocale(null), Vocabulary.getResources((Locale) null));
table.getItems().setAll(band.getCategories());
return table;
}
Aggregations