Search in sources :

Example 1 with Category

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;
}
Also used : Category(org.apache.sis.coverage.Category) MenuItem(javafx.scene.control.MenuItem) ContextMenu(javafx.scene.control.ContextMenu) InternationalString(org.opengis.util.InternationalString) TableColumn(javafx.scene.control.TableColumn) TableView(javafx.scene.control.TableView)

Example 2 with Category

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);
}
Also used : Shape(java.awt.Shape) Category(org.apache.sis.coverage.Category) Statistics(org.apache.sis.math.Statistics) SampleDimension(org.apache.sis.coverage.SampleDimension) SampleModel(java.awt.image.SampleModel) ColorModel(java.awt.image.ColorModel) IndexColorModel(java.awt.image.IndexColorModel) List(java.util.List) RenderedImage(java.awt.image.RenderedImage) DoubleUnaryOperator(java.util.function.DoubleUnaryOperator) IndexColorModel(java.awt.image.IndexColorModel)

Example 3 with Category

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;
}
Also used : Category(org.apache.sis.coverage.Category)

Example 4 with Category

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;
}
Also used : Category(org.apache.sis.coverage.Category)

Example 5 with Category

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;
}
Also used : Locale(java.util.Locale) Category(org.apache.sis.coverage.Category) SampleDimension(org.apache.sis.coverage.SampleDimension)

Aggregations

Category (org.apache.sis.coverage.Category)7 SampleDimension (org.apache.sis.coverage.SampleDimension)4 DoubleUnaryOperator (java.util.function.DoubleUnaryOperator)2 Color (java.awt.Color)1 Shape (java.awt.Shape)1 ColorModel (java.awt.image.ColorModel)1 IndexColorModel (java.awt.image.IndexColorModel)1 RenderedImage (java.awt.image.RenderedImage)1 SampleModel (java.awt.image.SampleModel)1 HashMap (java.util.HashMap)1 List (java.util.List)1 Locale (java.util.Locale)1 Map (java.util.Map)1 ContextMenu (javafx.scene.control.ContextMenu)1 MenuItem (javafx.scene.control.MenuItem)1 TableColumn (javafx.scene.control.TableColumn)1 TableView (javafx.scene.control.TableView)1 Statistics (org.apache.sis.math.Statistics)1 NumberRange (org.apache.sis.measure.NumberRange)1 InternationalString (org.opengis.util.InternationalString)1