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

org.jaitools.imageutils.ROIGeometry Maven / Gradle / Ivy

Go to download

Support and utility classes used by other JAITools components and available for general use.

There is a newer version: 1.6.0
Show newest version
/* 
 *  Copyright (c) 2010-2011, Michael Bedward. All rights reserved. 
 *   
 *  Redistribution and use in source and binary forms, with or without modification, 
 *  are permitted provided that the following conditions are met: 
 *   
 *  - Redistributions of source code must retain the above copyright notice, this  
 *    list of conditions and the following disclaimer. 
 *   
 *  - Redistributions in binary form must reproduce the above copyright notice, this 
 *    list of conditions and the following disclaimer in the documentation and/or 
 *    other materials provided with the distribution.   
 *   
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 
 *  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 *  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */   

package org.jaitools.imageutils;

import org.jaitools.imageutils.shape.LiteShape;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderedImageFactory;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.ROIShape;

import org.jaitools.jts.CoordinateSequence2D;

import com.vividsolutions.jts.awt.ShapeReader;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryComponentFilter;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
import com.vividsolutions.jts.geom.util.AffineTransformation;


/**
 * An ROI class backed by a vector object providing precision and the ability 
 * to handle massive regions. It has a minimal memory footprint allowing it to 
 * be used with massive images.
 * 

* JAI operations often involve converting ROI objects to images. This class * implements its {@link #getAsImage()} method using the JAITools "VectorBinarize" * operator to avoid exhausting available memory when dealing with ROIs that * cover massive image areas. *

* Note that this class can be used to honour floating precision pixel coordinates * by setting the {@code useFixedPrecision} constructor argument to {@code false}. * The effect of the default fixed coordinate precision is to provide equivalent * behaviour to that of the standard {@code ROIShape}, where pixel coordinates * are treated as referring to the upper-left pixel corner. * * @author Michael Bedward * @author Andrea Aime * @since 1.1 * @version $Id$ */ public class ROIGeometry extends ROI { private static final Logger LOGGER = Logger.getLogger(ROIGeometry.class.getName()); /** * Default setting for use of anti-aliasing when drawing the reference * {@code Geometry} during a {@link #getAsImage()} request. * The default value is {@code true} which provides behaviour corresponding * to that of the standard JAI {@code ROIShape} class. */ public static final boolean DEFAULT_ROIGEOMETRY_ANTIALISING = true; /** * Default setting for use of fixed precision ({@code true}). */ public static final boolean DEFAULT_ROIGEOMETRY_USEFIXEDPRECISION = false; private boolean useAntialiasing = DEFAULT_ROIGEOMETRY_ANTIALISING; private boolean useFixedPrecision = DEFAULT_ROIGEOMETRY_USEFIXEDPRECISION; private static final long serialVersionUID = 1L; private static final AffineTransformation Y_INVERSION = new AffineTransformation(1, 0, 0, 0, -1, 0); private static final String UNSUPPORTED_ROI_TYPE = "The argument be either an ROIGeometry or an ROIShape"; /** The {@code Geometry} that defines the area of inclusion */ private final PreparedGeometry theGeom; /** Thread safe cache for the roi image */ private volatile PlanarImage roiImage; private final GeometryFactory geomFactory; private final static double tolerance = 1d; private final static PrecisionModel PRECISION = new PrecisionModel(tolerance); // read, remove excess ordinates, force precision and collect private final static GeometryFactory PRECISE_FACTORY = new GeometryFactory(PRECISION); private final static PrecisionModel FLOAT_PRECISION = new PrecisionModel(PrecisionModel.FLOATING_SINGLE); private final static GeometryFactory FLOAT_PRECISION_FACTORY = new GeometryFactory(FLOAT_PRECISION); private final CoordinateSequence2D testPointCS; private final com.vividsolutions.jts.geom.Point testPoint; private final CoordinateSequence2D testRectCS; private final Polygon testRect; private RenderingHints hints; /** * Constructor which takes a {@code Geometry} object to be used * as the reference against which to test inclusion of image coordinates. * The argument {@code geom} must be either a {@code Polygon} or * {@code MultiPolygon}. * The input geometry is copied so subsequent changes to it will * not be reflected in the {@code ROIGeometry} object. * * @param geom either a {@code Polygon} or {@code MultiPolygon} object * defining the area(s) of inclusion. * * @throws IllegalArgumentException if {@code geom} is {@code null} or * not an instance of either {@code Polygon} or {@code MultiPolygon} */ public ROIGeometry(Geometry geom) { this(geom, DEFAULT_ROIGEOMETRY_ANTIALISING, DEFAULT_ROIGEOMETRY_USEFIXEDPRECISION); } /** * Constructor which takes a {@code Geometry} object and a {@code boolean} * value for whether to use fixed coordinate precision (equivalent to * working with integer pixel coordinates). * The argument {@code geom} must be either a {@code Polygon} or * {@code MultiPolygon}. * The input geometry is copied so subsequent changes to it will * not be reflected in the {@code ROIGeometry} object. * * @param geom either a {@code Polygon} or {@code MultiPolygon} object * defining the area(s) of inclusion. * * @param useFixedPrecision whether to use fixed precision when comparing * pixel coordinates to the reference geometry * * @throws IllegalArgumentException if {@code geom} is {@code null} or * not an instance of either {@code Polygon} or {@code MultiPolygon} */ public ROIGeometry(Geometry geom, final boolean useFixedPrecision) { this(geom, DEFAULT_ROIGEOMETRY_ANTIALISING, useFixedPrecision); } /** * Constructors a new ROIGeometry. * The argument {@code geom} must be either a {@code Polygon} or * {@code MultiPolygon}. * The input geometry is copied so subsequent changes to it will * not be reflected in the {@code ROIGeometry} object. * * @param geom either a {@code Polygon} or {@code MultiPolygon} object * defining the area(s) of inclusion. * * @param antiAliasing whether to use anti-aliasing when converting this * ROI to an image * * @param useFixedPrecision whether to use fixed precision when comparing * pixel coordinates to the reference geometry * * @throws IllegalArgumentException if {@code geom} is {@code null} or * not an instance of either {@code Polygon} or {@code MultiPolygon} */ public ROIGeometry(Geometry geom, final boolean antiAliasing, final boolean useFixedPrecision) { this(geom, DEFAULT_ROIGEOMETRY_ANTIALISING, useFixedPrecision, null); } /** * Builds a new ROIGeometry. * The argument {@code geom} must be either a {@code Polygon} or * {@code MultiPolygon}. * The input geometry is copied so subsequent changes to it will * not be reflected in the {@code ROIGeometry} object. * * @param geom either a {@code Polygon} or {@code MultiPolygon} object * defining the area(s) of inclusion. * * @param hints The JAI hints to be used when generating the raster equivalent of this ROI * * @throws IllegalArgumentException if {@code geom} is {@code null} or * not an instance of either {@code Polygon} or {@code MultiPolygon} */ public ROIGeometry(Geometry geom, final RenderingHints hints) { this(geom, DEFAULT_ROIGEOMETRY_ANTIALISING, DEFAULT_ROIGEOMETRY_USEFIXEDPRECISION, hints); } /** * Fully-specified constructor. * The argument {@code geom} must be either a {@code Polygon} or * {@code MultiPolygon}. * The input geometry is copied so subsequent changes to it will * not be reflected in the {@code ROIGeometry} object. * * @param geom either a {@code Polygon} or {@code MultiPolygon} object * defining the area(s) of inclusion. * * @param antiAliasing whether to use anti-aliasing when converting this * ROI to an image * * @param useFixedPrecision whether to use fixed precision when comparing * pixel coordinates to the reference geometry * * @param hints The JAI hints to be used when generating the raster equivalent of this ROI * * @throws IllegalArgumentException if {@code geom} is {@code null} or * not an instance of either {@code Polygon} or {@code MultiPolygon} */ public ROIGeometry(Geometry geom, final boolean antiAliasing, final boolean useFixedPrecision, final RenderingHints hints) { if (geom == null) { throw new IllegalArgumentException("geom must not be null"); } if (!(geom instanceof Polygon || geom instanceof MultiPolygon)) { throw new IllegalArgumentException("geom must be a Polygon, MultiPolygon"); } this.useFixedPrecision = useFixedPrecision; this.hints = hints; Geometry cloned = null; if (useFixedPrecision){ geomFactory = PRECISE_FACTORY; cloned = geomFactory.createGeometry(geom); Coordinate[] coords = cloned.getCoordinates(); for (Coordinate coord : coords) { Coordinate cc1 = coord; PRECISION.makePrecise(cc1); } cloned.normalize(); } else { geomFactory = FLOAT_PRECISION_FACTORY; cloned = geomFactory.createGeometry(geom); Coordinate[] coords = cloned.getCoordinates(); for (Coordinate coord : coords) { Coordinate cc1 = coord; FLOAT_PRECISION.makePrecise(cc1); } cloned.normalize(); } theGeom = PreparedGeometryFactory.prepare(cloned); testPointCS = new CoordinateSequence2D(1); testPoint = geomFactory.createPoint(testPointCS); testRectCS = new CoordinateSequence2D(5); testRect = geomFactory.createPolygon(geomFactory.createLinearRing(testRectCS), null); } /** * Returns a new instance which is the union of this ROI and {@code roi}. * This is only possible if {@code roi} is an instance of ROIGeometry * or {@link ROIShape}. * * @param roi the ROI to add * @return the union as a new instance * @throws UnsupportedOperationException if {@code roi} is not an instance * of ROIGeometry or {@link ROIShape} */ @Override public ROI add(ROI roi) { final Geometry geom = getGeometry(roi); if (geom != null) { Geometry union = geom.union(theGeom.getGeometry()); return buildROIGeometry(union); } throw new UnsupportedOperationException(UNSUPPORTED_ROI_TYPE); } /** * Tests if this ROI contains the given point. * * @param p the point * * @return {@code true} if the point is within this ROI; * {@code false} otherwise */ @Override public boolean contains(Point p) { return contains(p.getX(), p.getY()); } /** * Tests if this ROI contains the given point. * * @param p the point * * @return {@code true} if the point is within this ROI; * {@code false} otherwise */ @Override public boolean contains(Point2D p) { return contains(p.getX(), p.getY()); } /** * Tests if this ROI contains the given image location. * * @param x location X ordinate * @param y location Y ordinate * * @return {@code true} if the location is within this ROI; * {@code false} otherwise */ @Override public boolean contains(int x, int y) { return contains((double)x, (double)y); } /** * Tests if this ROI contains the given image location. * * @param x location X ordinate * @param y location Y ordinate * * @return {@code true} if the location is within this ROI; * {@code false} otherwise */ @Override public boolean contains(double x, double y) { testPointCS.setX(0, x); testPointCS.setY(0, y); testPoint.geometryChanged(); return theGeom.contains(testPoint); } /** * Tests if this ROI contains the given rectangle. * * @param rect the rectangle * @return {@code true} if the rectangle is within this ROI; * {@code false} otherwise */ @Override public boolean contains(Rectangle rect) { return contains(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight()); } /** * Tests if this ROI contains the given rectangle. * * @param rect the rectangle * @return {@code true} if the rectangle is within this ROI; * {@code false} otherwise */ @Override public boolean contains(Rectangle2D rect) { return contains(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight()); } /** * Tests if this ROI contains the given rectangle. * * @param x rectangle origin X ordinate * @param y rectangle origin Y ordinate * @param w rectangle width * @param h rectangle height * * @return {@code true} if the rectangle is within this ROI; * {@code false} otherwise */ @Override public boolean contains(int x, int y, int w, int h) { return contains((double)x, (double)y, (double)w, (double)h); } /** * Tests if this ROI contains the given rectangle. * * @param x rectangle origin X ordinate * @param y rectangle origin Y ordinate * @param w rectangle width * @param h rectangle height * * @return {@code true} if the rectangle is within this ROI; * {@code false} otherwise */ @Override public boolean contains(double x, double y, double w, double h) { setTestRect(x, y, w, h); return theGeom.contains(testRect); } /** * Returns a new instance which is the exclusive OR of this ROI and {@code roi}. * This is only possible if {@code roi} is an instance of ROIGeometry * or {@link ROIShape}. * * @param roi the ROI to add * @return the union as a new instance * @throws UnsupportedOperationException if {@code roi} is not an instance * of ROIGeometry or {@link ROIShape} */ @Override public ROI exclusiveOr(ROI roi) { final Geometry geom = getGeometry(roi); if (geom != null) { return buildROIGeometry(theGeom.getGeometry().symDifference(geom)); } throw new UnsupportedOperationException(UNSUPPORTED_ROI_TYPE); } /** * This method is not supported. * @throws UnsupportedOperationException if called */ @Override public int[][] getAsBitmask(int x, int y, int width, int height, int[][] mask) { throw new UnsupportedOperationException("Not implemented"); } /** * Gets an image representation of this ROI using the {@code VectorBinarize} * operation. For an ROI with very large bounds but simple shape(s) the * resulting image has a small memory footprint. * * @return a new image representing this ROI * @see org.jaitools.media.jai.vectorbinarize.VectorBinarizeDescriptor */ @Override public PlanarImage getAsImage() { if (roiImage == null) { synchronized (this) { // this synch idiom works only if roiImage is volatile, keep it as such if (roiImage == null) { Envelope env = theGeom.getGeometry().getEnvelopeInternal(); int x = (int) Math.floor(env.getMinX()); int y = (int) Math.floor(env.getMinY()); int w = (int) Math.ceil(env.getMaxX()) - x; int h = (int) Math.ceil(env.getMaxY()) - y; ParameterBlockJAI pb = new ParameterBlockJAI("VectorBinarize"); pb.setParameter("minx", x); pb.setParameter("miny", y); pb.setParameter("width", w); pb.setParameter("height", h); pb.setParameter("geometry", theGeom); pb.setParameter("antiAliasing", useAntialiasing); roiImage = JAI.create("VectorBinarize", pb, hints); } } } return roiImage; } /** * This method is not supported. * @throws UnsupportedOperationException if called */ @Override public LinkedList getAsRectangleList(int x, int y, int width, int height) { throw new UnsupportedOperationException("Not implemented"); } /** * This method is not supported. * @throws UnsupportedOperationException if called */ @Override protected LinkedList getAsRectangleList(int x, int y, int width, int height, boolean mergeRectangles) { throw new UnsupportedOperationException("Not implemented"); } /** * Gets a new {@link Shape} representing this ROI. * * @return the shape */ @Override public Shape getAsShape() { return new LiteShape(theGeom.getGeometry()); } /** * Returns the ROI as a JTS {@code Geometry}. * * @return the geometry */ public Geometry getAsGeometry() { return theGeom.getGeometry(); } /** * Gets the enclosing rectangle of this ROI. * * @return a new rectangle */ @Override public Rectangle getBounds() { Envelope env = theGeom.getGeometry().getEnvelopeInternal(); return new Rectangle((int)env.getMinX(), (int)env.getMinY(), (int)env.getWidth(), (int)env.getHeight()); } /** * Gets the enclosing double-precision rectangle of this ROI. * * @return a new rectangle */ @Override public Rectangle2D getBounds2D() { Envelope env = theGeom.getGeometry().getEnvelopeInternal(); return new Rectangle2D.Double(env.getMinX(), env.getMinY(), env.getWidth(), env.getHeight()); } /** * This method is not supported. * @throws UnsupportedOperationException if called */ @Override public int getThreshold() { throw new UnsupportedOperationException("Not implemented"); } /** * Returns a new instance which is the intersection of this ROI and {@code roi}. * This is only possible if {@code roi} is an instance of ROIGeometry * or {@link ROIShape}. * * @param roi the ROI to intersect with * @return the intersection as a new instance * @throws UnsupportedOperationException if {@code roi} is not an instance * of ROIGeometry or {@link ROIShape} */ @Override public ROI intersect(ROI roi) { final Geometry geom = getGeometry(roi); if (geom != null) { Geometry intersect = geom.intersection(theGeom.getGeometry()); return buildROIGeometry(intersect); } throw new UnsupportedOperationException(UNSUPPORTED_ROI_TYPE); } /** * Gets a {@link Geometry} from an input {@link ROI}. * * @param roi the ROI * @return a {@link Geometry} instance from the provided input; * null in case the input roi is neither a geometry, nor a shape. */ private Geometry getGeometry(ROI roi){ if (roi instanceof ROIGeometry){ return ((ROIGeometry) roi).getAsGeometry(); } else if (roi instanceof ROIShape){ final Shape shape = ((ROIShape) roi).getAsShape(); final Geometry geom = ShapeReader.read(shape, 0, geomFactory); geom.apply(Y_INVERSION); return geom; } return null; } /** * Tests if the given rectangle intersects with this ROI. * * @param rect the rectangle * @return {@code true} if there is an intersection; {@code false} otherwise */ @Override public boolean intersects(Rectangle rect) { setTestRect(rect.x, rect.y, rect.width, rect.height); return theGeom.intersects(testRect); } /** * Tests if the given rectangle intersects with this ROI. * * @param rect the rectangle * @return {@code true} if there is an intersection; {@code false} otherwise */ @Override public boolean intersects(Rectangle2D rect) { setTestRect(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight()); return theGeom.intersects(testRect); } /** * Tests if the given rectangle intersects with this ROI. * * @param x rectangle origin X ordinate * @param y rectangle origin Y ordinate * @param w rectangle width * @param h rectangle height * @return {@code true} if there is an intersection; {@code false} otherwise */ @Override public boolean intersects(int x, int y, int w, int h) { setTestRect(x, y, w, h); return theGeom.intersects(testRect); } /** * Tests if the given rectangle intersects with this ROI. * * @param x rectangle origin X ordinate * @param y rectangle origin Y ordinate * @param w rectangle width * @param h rectangle height * @return {@code true} if there is an intersection; {@code false} otherwise */ @Override public boolean intersects(double x, double y, double w, double h) { setTestRect(x, y, w, h); return theGeom.intersects(testRect); } /** * This method is not supported. * @throws UnsupportedOperationException if called */ @Override public ROI performImageOp(RenderedImageFactory RIF, ParameterBlock paramBlock, int sourceIndex, RenderingHints renderHints) { throw new UnsupportedOperationException("Not implemented"); } /** * This method is not supported. * @throws UnsupportedOperationException if called */ @Override public ROI performImageOp(String name, ParameterBlock paramBlock, int sourceIndex, RenderingHints renderHints) { throw new UnsupportedOperationException("Not implemented"); } /** * This method is not supported. * @throws UnsupportedOperationException if called */ @Override public void setThreshold(int threshold) { throw new UnsupportedOperationException("Not implemented"); } /** * Returns a new instance which is the difference of this ROI and {@code roi}. * This is only possible if {@code roi} is an instance of ROIGeometry * or {@link ROIShape}. * * @param roi the ROI to add * @return the union as a new instance * @throws UnsupportedOperationException if {@code roi} is not an instance * of ROIGeometry or {@link ROIShape} */ @Override public ROI subtract(ROI roi) { final Geometry geom = getGeometry(roi); if (geom != null) { Geometry difference = theGeom.getGeometry().difference(geom); return buildROIGeometry(difference); } throw new UnsupportedOperationException(UNSUPPORTED_ROI_TYPE); } /** * Returns a new ROI created by applying the given transform to * this ROI. * * @param at the transform * @param interp ignored * * @return the new ROI */ @Override public ROI transform(AffineTransform at, Interpolation interp) { return transform(at); } /** * Returns a new ROI created by applying the given transform to * this ROI. * * @param at the transform * * @return the new ROI */ @Override public ROI transform(AffineTransform at) { Geometry cloned = (Geometry) theGeom.getGeometry().clone(); cloned.apply(new AffineTransformation(at.getScaleX(), at.getShearX(), at.getTranslateX(), at.getShearY(), at.getScaleY(), at.getTranslateY())); if (useFixedPrecision){ Geometry fixed = PRECISE_FACTORY.createGeometry(cloned); Coordinate[] coords = fixed.getCoordinates(); for (Coordinate coord : coords) { Coordinate precise = coord; PRECISION.makePrecise(precise); } cloned = fixed; } return buildROIGeometry(cloned); } /** * Helper function for contains and intersects methods. * * @param x rectangle origin X ordinate * @param y rectangle origin Y ordinate * @param w rectangle width * @param h rectangle height */ private void setTestRect(double x, double y, double w, double h) { testRectCS.setXY(0, x, y); testRectCS.setXY(1, x, y + h); testRectCS.setXY(2, x + w, y + h); testRectCS.setXY(3, x + w, y); testRectCS.setXY(4, x, y); testRect.geometryChanged(); } /** * Setup a ROIGeometry on top of a geometry. * It takes care of removing invalid polygon, opened line strings and so on * @param geometry the input geometry to be used as reference to create a new {@link ROIGeometry} * @return a ROI from the input geometry. */ private ROI buildROIGeometry(Geometry geometry) { // cleanup the geometry, extract only the polygons, set oriented operations might // have returned a mix of points and lines in the resulting geometries final List polygons = new ArrayList(); geometry.apply(new GeometryComponentFilter() { public void filter(Geometry geom) { if (geom instanceof Polygon) { polygons.add((Polygon) geom); } } }); // build a polygon or a multipolygon Geometry geom = null; if (polygons.size() == 0) { geom = geomFactory.createMultiPolygon(new Polygon[0]); } else if (polygons.size() == 1) { geom = polygons.get(0); } else { Polygon[] polygonArray = (Polygon[]) polygons.toArray(new Polygon[polygons.size()]); geom = geomFactory.createMultiPolygon(polygonArray); } return new ROIGeometry(geom, this.useAntialiasing, this.useFixedPrecision, this.hints); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy