All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaitools.media.jai.kernel.KernelFactory Maven / Gradle / Ivy

Go to download

Provides a single jar containing all JAI-tools modules which you can use instead of including individual modules in your project. Note: It does not include the Jiffle scripting language or Jiffle image operator.

The newest version!
/*
 * Copyright 2009-2011 Michael Bedward
 *
 * This file is part of jai-tools.
 *
 * jai-tools is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * jai-tools 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with jai-tools.  If not, see .
 *
 */
package jaitools.media.jai.kernel;

import java.awt.Point;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.List;

import javax.media.jai.KernelJAI;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.MultiPoint;

import jaitools.CollectionFactory;
import jaitools.numeric.CompareOp;

/**
 * Provides static methods to create a variety of raster kernels 
 * ({@code KernelJAI} objects).
 *
 * @author Michael Bedward
 * @since 1.0
 * @version $Id: KernelFactory.java 1613 2011-03-31 12:47:52Z michael.bedward $
 */
public class KernelFactory {
    
    private static final double C_GAUSS = 1.0 / Math.sqrt(2 * Math.PI);
    private static final double C_QUARTIC = 15.0 / 16.0;
    private static final double C_TRIWEIGHT = 35.0 / 32.0;
    private static final double PI_ON_2 = Math.PI / 2;
    private static final double PI_ON_4 = Math.PI / 4;


    /**
     * Constants specifying how kernel element values are calculated.
     */
    public static enum ValueType {

        /**
         * Inside elements have value 1.0; outside 0.0
         */
        BINARY,
        
        /**
         * Value is {@code PI/4 * cos(uPI/2)} where {@code u} is proportional
         * distance to the key element.
         */
        COSINE,
        
        /**
         * Value is the distance to the kernel's key element.
         */
        DISTANCE,
        
        /**
         * Value is {@code 3(1 - u^2)/4} where {@code u} is proportional
         * distance to the key element.
         */
        EPANECHNIKOV,
        
        /**
         * Value is {@code 1/sqrt(2PI) e^(-u^2 / 2)} where {@code u} is proportional
         * distance to the key element.
         */
        GAUSSIAN,
        
        /**
         * Value is the inverse distance to the kernel's key element.
         */
        INVERSE_DISTANCE,
        
        /**
         * Also known as biweight.
         * Value is {@code 15/16 (1 - u^2)^2} where {@code u} is proportional
         * distance to the key element.
         */
        QUARTIC,
        
        /**
         * Value is {@code 1 - u}  where {@code u} is proportional
         * distance to the key element.
         */
        TRIANGULAR,
        
        /**
         * Also known as tricubic.
         * Value is {@code 35/32 (1 - u^2)^3} where {@code u} is proportional
         * distance to the key element.
         */
        TRIWEIGHT;
        
    };

    /**
     * Create a circular kernel where all elements within the circle
     * have value 1.0 while those outside have value 0.0.
     * Kernel width is {@code 2*radius + 1}.
     * The key element is at position {@code x=radius, y=radius}.
     * 

* This is equivalent to: *


     *     createCircle(radius, Kernel.ValueType.BINARY, 1.0f)
     * 
* * @param radius radius of the circle * * @return a new {@code KernelJAI} object */ public static KernelJAI createCircle(int radius) { return createConstantCircle(radius, 1.0f); } public static KernelJAI createConstantCircle(int radius, float value) { if (radius <= 0) { throw new IllegalArgumentException( "Invalid radius (" + radius + "); must be > 0"); } KernelFactoryHelper helper = new KernelFactoryHelper(); float[] weights = helper.makeCircle(radius); int w = 2*radius + 1; helper.rowFill(weights, w, w, value); return new KernelJAI(w, w, weights); } /** * Creates a circular kernel with width {@code 2*radius + 1}. * The key element is at position {@code x=radius, y=radius}. * * @param radius the radius of the circle expressed in pixels * @param type a {@link ValueType} constant * @param centreValue the value to assign to the kernel centre (key element) * * @return a new {@code KernelJAI} object * * @deprecated Please use {@link #createCircle(int, ValueType)} instead and * set the centre element value on the returned kernel if that is * required. */ public static KernelJAI createCircle(int radius, ValueType type, float centreValue) { KernelJAI kernel = createCircle(radius, type); return KernelUtil.setElement(kernel, radius, radius, centreValue); } /** * Creates a circular kernel with width {@code 2*radius + 1}. * The key element is at position {@code x=radius, y=radius}. *

* If {@code type} is {@link ValueType#INVERSE_DISTANCE} the kernel's * key element will be set to 1.0f. * * @param radius the radius of the circle expressed in pixels * @param type a {@link ValueType} constant * * @return a new {@code KernelJAI} object */ public static KernelJAI createCircle(int radius, ValueType type) { if (radius <= 0) { throw new IllegalArgumentException( "Invalid radius (" + radius + "); must be > 0"); } KernelFactoryHelper kh = new KernelFactoryHelper(); int width = 2 * radius + 1; float[] weights = new float[width * width]; float r2 = radius * radius; int k0 = 0; int k1 = weights.length - 1; double dist2 = 0; float value = 0f; for (int y = radius; y > 0; y--) { int y2 = y * y; for (int x = -radius; x <= radius; x++, k0++, k1--) { dist2 = x * x + y2; if (CompareOp.acompare(r2, dist2) >= 0) { switch (type) { case BINARY: value = 1.0f; break; case COSINE: value = (float) (PI_ON_4 * Math.cos(PI_ON_2 * Math.sqrt(dist2) / radius)); break; case DISTANCE: value = (float) Math.sqrt(dist2); break; case EPANECHNIKOV: value = (float) (3.0 * (1.0 - dist2 / r2) / 4.0); break; case GAUSSIAN: value = (float) (C_GAUSS * Math.exp(-0.5 * dist2 / r2)); break; case INVERSE_DISTANCE: value = (float) (1.0 / Math.sqrt(dist2)); break; case QUARTIC: double termq = 1.0 - dist2 / r2; value = (float) (C_QUARTIC * termq * termq); break; case TRIANGULAR: value = (float) (1.0 - Math.sqrt(dist2) / radius); break; case TRIWEIGHT: double termt = 1.0 - dist2 / r2; value = (float) (C_TRIWEIGHT * termt * termt * termt); break; } weights[k0] = weights[k1] = value; } } } for (int x = -radius; x <= radius; x++, k0++) { if (x == 0 && type == ValueType.INVERSE_DISTANCE) { value = 1.0f; } else { switch (type) { case BINARY: value = 1.0f; break; case COSINE: value = (float) (PI_ON_4 * Math.cos(PI_ON_2 * x / radius)); break; case DISTANCE: value = (float) Math.abs(x); break; case EPANECHNIKOV: value = (float) (3.0 * (1.0 - (double)x * x / r2) / 4.0); break; case GAUSSIAN: value = (float) (C_GAUSS * Math.exp(-0.5 * x * x / r2)); break; case INVERSE_DISTANCE: value = (float) (1.0 / Math.abs(x)); break; case QUARTIC: double termq = 1.0 - (double) x * x / r2; value = (float) (C_QUARTIC * termq * termq); break; case TRIANGULAR: value = (float) (1.0 - (double) Math.abs(x) / radius); break; case TRIWEIGHT: double termt = 1.0 - (double) x * x / r2; value = (float) (C_TRIWEIGHT * termt * termt * termt); break; } } weights[k0] = value; } return new KernelJAI(width, width, weights); } /** * Creates an annular kernel (a doughnut). * The kernel width is {@code 2*outerRadius + 1}. * The kernel's key element is at position {@code x=outerRadius, y=outerRadius}. *

* Calling this method with {@code innerRadius == 0} is equivalent to * calling {@link #createCircle } * * @param outerRadius the radius of the annulus * @param innerRadius the radius of the 'hole' * @param type a {@link ValueType} constant * @param centreValue the value to assign to the kernel centre (key element) * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if {@code outerRadius <= 0} or * {@code innerRadius >= outerRadius} * * @deprecated Please use {@link #createAnnulua(int, int, ValueType)} instead and * set the centre element value on the returned kernel if that is * required. */ public static KernelJAI createAnnulus(int outerRadius, int innerRadius, ValueType type, float centreValue) { KernelJAI kernel = createAnnulus(outerRadius, innerRadius, type); return KernelUtil.setElement(kernel, outerRadius, outerRadius, centreValue); } /** * Creates an annular kernel (a doughnut) where elements inside the annulus * have a constant value while those outside are set to 0. *

* The kernel width is {@code 2*outerRadius + 1}. * The kernel's key element is at position {@code x=outerRadius, y=outerRadius}. * * @param outerRadius the outer radius of the annulus * @param innerRadius the radius of the 'hole' * @param value element value * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if {@code outerRadius <= 0} or * {@code innerRadius >= outerRadius} */ public static KernelJAI createConstantAnnulus(int outerRadius, int innerRadius, float value) { if (outerRadius <= 0) { throw new IllegalArgumentException("outerRadius must be > 0"); } if (innerRadius >= outerRadius) { throw new IllegalArgumentException("innerRadius must be less than outerRadius"); } final int w = 2*outerRadius + 1; float[] data = new float[w * w]; double outer2 = outerRadius * outerRadius; double inner2 = innerRadius * innerRadius; double d2; int k = 0; for (int y = 0; y < w; y++) { double y2 = y * y; for (int x = 0; x < w; x++) { d2 = y2 + x * x; if (CompareOp.acompare(d2, outer2) <= 0 && CompareOp.acompare(d2, inner2) > 0) { data[k] = value; } else { data[k] = 0; } } } return new KernelJAI(w, w, data); } /** * Creates an annular kernel (a doughnut). If {@code innerRadius} is 0 the * returned kernel will be identical to that from {@code createCircle(outerRadius, type)}. *

* The kernel width is {@code 2*outerRadius + 1}. * The kernel's key element is at position {@code x=outerRadius, y=outerRadius}. *

* * @param outerRadius the outer radius of the annulus * @param innerRadius the radius of the 'hole' * @param type a {@link ValueType} constant * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if {@code outerRadius <= 0} or * {@code innerRadius >= outerRadius} */ public static KernelJAI createAnnulus(int outerRadius, int innerRadius, ValueType type) { if (innerRadius < 0) { throw new IllegalArgumentException( "Invalid innerRadius (" + innerRadius + "); must be >= 0"); } if (outerRadius <= innerRadius) { throw new IllegalArgumentException("outerRadius must be greater than innerRadius"); } if (innerRadius == 0) { return createCircle(outerRadius, type); } int width = 2 * outerRadius + 1; float[] weights = new float[width * width]; double outer2 = outerRadius * outerRadius; double inner2 = innerRadius * innerRadius; double dist2 = 0; float value = 0f; int k0 = 0; int k1 = weights.length - 1; for (int y = outerRadius; y > 0; y--) { int y2 = y * y; for (int x = -outerRadius; x <= outerRadius; x++, k0++, k1--) { dist2 = x * x + y2; if (CompareOp.acompare(dist2, outer2) <= 0 && CompareOp.acompare(dist2, inner2) > 0) { switch (type) { case BINARY: value = 1.0f; break; case COSINE: value = (float) (PI_ON_4 * Math.cos(PI_ON_2 * Math.sqrt(dist2) / outerRadius)); break; case DISTANCE: value = (float) Math.sqrt(dist2); break; case EPANECHNIKOV: value = (float) (3.0 * (1.0 - dist2 / outer2) / 4.0); break; case GAUSSIAN: value = (float) (C_GAUSS * Math.exp(-0.5 * dist2 / outer2)); break; case INVERSE_DISTANCE: value = (float) (1.0 / Math.sqrt(dist2)); break; case QUARTIC: double termq = 1.0 - dist2 / outer2; value = (float) (C_QUARTIC * termq * termq); break; case TRIANGULAR: value = (float) (1.0 - Math.sqrt(dist2) / outerRadius); break; case TRIWEIGHT: double termt = 1.0 - dist2 / outer2; value = (float) (C_TRIWEIGHT * termt * termt * termt); break; } weights[k0] = weights[k1] = value; } } } for (int x = -outerRadius; x <= outerRadius; x++, k0++) { if (x < -innerRadius || x > innerRadius) { switch (type) { case BINARY: value = 1.0f; break; case COSINE: value = (float) (PI_ON_4 * Math.cos(PI_ON_2 * x / outerRadius)); break; case DISTANCE: value = (float) Math.abs(x); break; case EPANECHNIKOV: value = (float) (3.0 * (1.0 - (double) x * x / outer2) / 4.0); break; case GAUSSIAN: value = (float) (C_GAUSS * Math.exp(-0.5 * x * x / outer2)); break; case INVERSE_DISTANCE: value = (float) (1.0 / Math.abs(x)); break; case QUARTIC: double termq = 1.0 - (double) x * x / outer2; value = (float) (C_QUARTIC * termq * termq); break; case TRIANGULAR: value = (float) (1.0 - (double) Math.abs(x) / outerRadius); break; case TRIWEIGHT: double termt = 1.0 - (double) x * x / outer2; value = (float) (C_TRIWEIGHT * termt * termt * termt); break; } weights[k0] = value; } } return new KernelJAI(width, width, weights); } /** * Creates a rectangular kernel where all elements have value 1.0. * The key element will be at {@code (width/2, height/2)}. * * @param width rectangle width * @param height rectangle height * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if either {@code width} or {@code height} * are less than 1 */ public static KernelJAI createRectangle(int width, int height) { return createConstantRectangle(width, height, 1.0f); } /** * Creates a rectangular kernel where all elements have the same value. * The key element will be at {@code (width/2, height/2)}. * * @param width rectangle width * @param height rectangle height * @param value element value * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if either {@code width} or {@code height} * are less than 1 */ public static KernelJAI createConstantRectangle(int width, int height, float value) { if (width < 1 || height < 1) { throw new IllegalArgumentException("width and height must both be >= 1"); } float [] weights = (new KernelFactoryHelper()).makeRect(width, height, value); return new KernelJAI(width, height, weights); } /** * Creates a rectangular kernel where all elements have the same value. * * @param width rectangle width * @param height rectangle height * @param value element value * @param keyX key element X ordinate * @param keyY key element Y ordinate * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if either {@code width} or {@code height} * are less than 1 or if the key element location is outside the * rectangle */ public static KernelJAI createConstantRectangle(int width, int height, int keyX, int keyY, float value) { if (width < 1 || height < 1) { throw new IllegalArgumentException("width and height must both be >= 1"); } if (keyX < 0 || keyX >= width || keyY < 0 || keyY >= height) { throw new IllegalArgumentException("key element must be within the rectangle"); } float [] weights = (new KernelFactoryHelper()).makeRect(width, height, value); return new KernelJAI(width, height, keyX, keyY, weights); } /** * Creates a rectangular kernel. If the element value type is one that involves * proportional distance, such as {@link ValueType#COSINE} or * {@link ValueType#EPANECHNIKOV}, this is calculated as the proportion of the * maximum distance from the key element to a kernel edge element. * * @param width rectangle width * @param height rectangle height * @param type a {@link ValueType} constant * @param keyX X ordinate of the key element * @param keyY Y ordinate of the key element (0 is top) * @param keyValue value of the key element * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if either {@code width} or {@code height} * are less than 1; or if {@code keyX} is not in the interval {@code [0,width)}; * or if {@code keyY} is not in the interval {@code [0,height)}; * * @deprecated Please use {@link #createRectangle(int, int, ValueType, int, int)} * instead and set the centre element value on the returned kernel if that is * required. */ public static KernelJAI createRectangle( int width, int height, ValueType type, int keyX, int keyY, float keyValue) { KernelJAI kernel = createRectangle(width, height, type, keyX, keyY); return KernelUtil.setElement(kernel, keyX, keyY, keyValue); } /** * Creates a rectangular kernel. If the element value type is one that involves * proportional distance, such as {@link ValueType#COSINE} or * {@link ValueType#EPANECHNIKOV}, this is calculated as the proportion of the * maximum distance from the key element to a kernel edge element. * * @param width rectangle width * @param height rectangle height * @param type a {@link ValueType} constant * @param keyX X ordinate of the key element * @param keyY Y ordinate of the key element (0 is top) * * @return a new {@code KernelJAI} object * * @throws IllegalArgumentException if either {@code width} or {@code height} * are less than 1; or if {@code keyX} is not in the interval {@code [0,width)}; * or if {@code keyY} is not in the interval {@code [0,height)}; */ public static KernelJAI createRectangle( int width, int height, ValueType type, int keyX, int keyY) { if (width < 1) { throw new IllegalArgumentException("width must be >= 1"); } if (height < 1) { throw new IllegalArgumentException("height must be >= 1"); } if (!(keyX >= 0 && keyX < width) || !(keyY >= 0 && keyY < height)) { throw new IllegalArgumentException("key element position " + keyX + "," + keyY + " is outside rectangle bounds"); } KernelFactoryHelper kh = new KernelFactoryHelper(); float weights[]; if (type == ValueType.BINARY) { weights = kh.makeRect(width, height, 1.0f); return new KernelJAI(width, height, keyX, keyY, weights); } weights = new float[width*height]; int k = 0; // find distance from key element to most distance edge element int dx = Math.max(keyX, width - 1 - keyX); int dy = Math.max(keyY, height - 1 - keyY); double dmax2 = dx*dx + dy*dy; double dmax = Math.sqrt(dmax2); Point2D p = new Point(keyX, keyY); double dist2 = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++, k++) { dist2 = (float) p.distanceSq(x, y); switch (type) { case COSINE: weights[k] = (float) (PI_ON_4 * Math.cos(PI_ON_2 * Math.sqrt(dist2) / dmax)); break; case DISTANCE: weights[k] = (float) Math.sqrt(dist2); break; case EPANECHNIKOV: weights[k] = (float) (3.0 * (1.0 - dist2 / dmax2) / 4.0); break; case GAUSSIAN: weights[k] = (float) (C_GAUSS * Math.exp(-0.5 * dist2 / dmax2)); break; case INVERSE_DISTANCE: if (dist2 < 1.0) { weights[k] = 1.0f; } else { weights[k] = (float) (1.0 / Math.sqrt(dist2)); } break; case QUARTIC: double termq = 1.0 - dist2 / dmax2; weights[k] = (float) (C_QUARTIC * termq * termq); break; case TRIANGULAR: weights[k] = (float) (1.0 - Math.sqrt(dist2) / dmax); break; case TRIWEIGHT: double termt = 1.0 - dist2 / dmax2; weights[k] = (float) (C_TRIWEIGHT * termt * termt * termt); break; } } } return new KernelJAI(width, height, keyX, keyY, weights); } /** * Create a kernel by rasterizing a shape. The shape must be a closed * polygon. Kernel element centre coordinates are used to test whether * elements are inside the shape. *

* This method can cope with arbitrary shape bounds, ie. there is no need to * set the bounding rectangle to have origin x=0, y=0. The values of keyX and keyY, * which specify the position of the kernel's key element, must be within the * bounds of the shape as passed to this method, but do not need to be inside the * shape itself. * * @param shape an object representing a closed polygon * @param transform an optional AffineTransform to relate shape coordinates to * kernel element coordinates. May be null. This is useful to scale and/or rotate * the shape. * * @param type a {@link ValueType} constant * * @param keyX X ordinate of the key element * @param keyY Y ordinate of the key element * @param keyValue the value of the key element * * @return a new instance of KernelJAI */ public static KernelJAI createFromShape(Shape shape, AffineTransform transform, ValueType type, int keyX, int keyY, float keyValue) { /* * First we transform the shape to a JTS Polygon object * so we can take advantage of the JTS intersects method * and avoid rasterizing artefacts which can arise when * using Shape.contains */ PathIterator iter = shape.getPathIterator(transform, 0.05); float[] buf = new float[6]; List coords = CollectionFactory.list(); while (!iter.isDone()) { iter.currentSegment(buf); coords.add(new Coordinate(buf[0], buf[1])); iter.next(); } GeometryFactory gf = new GeometryFactory(); Coordinate[] coordsAr = coords.toArray(new Coordinate[coords.size()]); Geometry poly = gf.createPolygon(gf.createLinearRing(coordsAr), null); Envelope env = poly.getEnvelopeInternal(); int left = (int) Math.floor(env.getMinX()); int right = (int) Math.ceil(env.getMaxX()); int top = (int) Math.ceil(env.getMaxY()); int bottom = (int) Math.floor(env.getMinY()); int width = right - left + 1; int height = top - bottom + 1; float[] weights = new float[width * height]; int[] offset = new int[height]; for (int i = 0, o=0; i < offset.length; i++, o+=width) offset[i] = o; coords.clear(); double y = top; for (int iy = 0; iy < height; y--, iy++) { double x = left; for (int ix = 0; ix < width; x++, ix++) { coords.add(new Coordinate(x, y)); } } /* * Now we buffer the polygon by a small amount to avoid * rejecting points that lie exactly on the boundary */ MultiPoint mp = gf.createMultiPoint(coords.toArray(new Coordinate[coords.size()])); Geometry inside = mp.intersection(poly.buffer(0.05, Math.max(width/2, 10))); final int n = inside.getNumGeometries(); /* * If required, find the maximum distance between the key element lcoation * and a kernel edge element. */ double dmax = 0, dmax2 = 0; switch (type) { case COSINE: case EPANECHNIKOV: case GAUSSIAN: case QUARTIC: case TRIANGULAR: case TRIWEIGHT: for (int i = 0; i < n; i++) { Geometry g = inside.getGeometryN(i); Coordinate c = g.getCoordinate(); double d2 = Point2D.distanceSq(keyX, keyY, (int)c.x, (int)c.y); if (d2 > dmax2) { dmax2 = d2; } } dmax = Math.sqrt(dmax2); } /* * For each intersecting point we set a kernel element */ double dist2 = 0; for (int i = 0; i < n; i++) { Geometry g = inside.getGeometryN(i); Coordinate c = g.getCoordinate(); int index = (int) c.x - left + offset[(int) c.y - bottom]; if (type != ValueType.BINARY) { dist2 = Point2D.distanceSq(keyX, keyY, (int)c.x, (int)c.y); } switch (type) { case BINARY: weights[index] = 1.0f; break; case COSINE: weights[index] = (float) (PI_ON_4 * Math.cos(PI_ON_2 * Math.sqrt(dist2) / dmax)); break; case DISTANCE: weights[index] = (float) Math.sqrt(dist2); break; case EPANECHNIKOV: weights[index] = (float) (3.0 * (1.0 - dist2 / dmax2) / 4.0); break; case GAUSSIAN: weights[index] = (float) (C_GAUSS * Math.exp(-0.5 * dist2 / dmax2)); break; case INVERSE_DISTANCE: weights[index] = (float) (1.0 / Math.sqrt(dist2)); break; case QUARTIC: double termq = 1.0 - dist2 / dmax2; weights[index] = (float) (C_QUARTIC * termq * termq); break; case TRIANGULAR: weights[index] = (float) (1.0 - Math.sqrt(dist2) / dmax); break; case TRIWEIGHT: double termt = 1.0 - dist2 / dmax2; weights[index] = (float) (C_TRIWEIGHT * termt * termt * termt); break; } } // set the key element to the requested value weights[keyX + offset[keyY]] = keyValue; return new KernelJAI(width, height, keyX, keyY, weights); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy