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

org.jaitools.media.jai.regionalize.RegionalizeOpImage Maven / Gradle / Ivy

Go to download

Identifies (sufficiently) uniform regions in the source image, allocates each a unique integer ID, and generates an output image with these IDs as pixel values

The newest version!
/* 
 *  Copyright (c) 2009-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.media.jai.regionalize;

import java.awt.Rectangle;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.media.jai.AreaOpImage;
import javax.media.jai.ImageLayout;
import javax.media.jai.PointOpImage;
import javax.media.jai.TileCache;

import org.jaitools.CollectionFactory;
import org.jaitools.imageutils.FillResult;
import org.jaitools.imageutils.FloodFiller;
import org.jaitools.tilecache.DiskMemTileCache;
import org.jaitools.tiledimage.DiskMemImage;

/**
 * An operator to identify regions of uniform value, within
 * a user-specified tolerance, in the source image. Produces a
 * destination image of these regions where pixel values are equal
 * to region ID.
 * 

* To avoid region numbering artefacts on image tile boundaries this * operator imposes an order on tile computation (by column within row). * If an arbitrary tile is requested by the caller, the operator first * checks that all of the preceding tiles have been computed and cached, * processing any that have not. The operator creates its own * {@link ExecutorService} for sequential tile computations. *

* Each computed tile is cached using an instance of {@link DiskMemTileCache}. * The caller can provide this to the operator via {@code RenderingHints}, or set * it as the default {@code TileCache} using {@code JAI.getDefaultInstance().setTileCache()}. * Otherwise the operator will create a {@code DiskMemTileCache} object for itself. * * * @see RegionalizeDescriptor * @see Region * * @author Michael Bedward * @since 1.0 * @version $Id$ */ public class RegionalizeOpImage extends PointOpImage { /** * Destination value indicating that a pixel does not * belong to a region */ public static final int NO_REGION = 0; private boolean singleBand; private boolean diagonal; private int band; private double tolerance; private FloodFiller filler; private Map regions; private int currentID; private final DiskMemImage regionImage; private final ExecutorService executor; private final Object getTileLock = new Object(); private final Object computeTileLock = new Object(); private boolean[] tileComputed; private class ComputeTileTask implements Callable { int tileX, tileY; ComputeTileTask(int tileX, int tileY) { this.tileX = tileX; this.tileY = tileY; } public Raster call() throws Exception { return computeTile(tileX, tileY); } } /** * Creates a new instance. * * @param source the source image * * @param config configurable attributes of the image (see {@link AreaOpImage}) * * @param layout an optional {@code ImageLayout} or {@code null} * * @param band the source image band to process * * @param tolerance the maximum absolute difference in value between the starting * pixel for a region and other subsequent pixels added to it * * @param diagonal if {@code true} include sub-regions with only diagonal connectedness; * if {@code false} require orthogonal connectedness * * @see RegionalizeDescriptor */ public RegionalizeOpImage(RenderedImage source, Map config, ImageLayout layout, int band, double tolerance, boolean diagonal) { super(source, layout, config, false); if (getSampleModel().getDataType() != DataBuffer.TYPE_INT) { throw new IllegalStateException("destination sample model must be TYPE_INT"); } this.band = band; this.tolerance = tolerance; /* * @TODO remove later if we expand the operator to * deal with multiple bands */ this.singleBand = true; this.diagonal = diagonal; /* * Any tile cache provided by the caller is ignored */ regionImage = new DiskMemImage(getWidth(), getHeight(), getSampleModel()); setTileCache( regionImage.getTileCache() ); filler = new FloodFiller(source, band, regionImage, 0, tolerance, diagonal); regions = CollectionFactory.sortedMap(); this.executor = Executors.newSingleThreadExecutor(); tileComputed = new boolean[getNumXTiles() * getNumYTiles()]; Arrays.fill(tileComputed, false); // paranoia this.currentID = 1; } /** * Gets a property associated with this operator. Use this * to retrieve the {@linkplain Region} object with the * property name {@linkplain RegionalizeDescriptor#REGION_DATA_PROPERTY} * * @param name property name * @return the matching object or null if there was no match */ @Override public Object getProperty(String name) { if (RegionalizeDescriptor.REGION_DATA_PROPERTY.equalsIgnoreCase(name)) { List regionData = CollectionFactory.list(); regionData.addAll(regions.values()); return regionData; } else { return super.getProperty(name); } } /** * Gets the properties for this operator. These will * include the {@linkplain Region} object. * * @return the properties */ @Override public Hashtable getProperties() { Hashtable props = super.getProperties(); if (props == null) { props = new Hashtable(); } props.put(RegionalizeDescriptor.REGION_DATA_PROPERTY, "dynamic"); return props; } /** * {@inheritDoc} */ @Override public Class getPropertyClass(String name) { if (RegionalizeDescriptor.REGION_DATA_PROPERTY.equalsIgnoreCase(name)) { return List.class; } else { return super.getPropertyClass(name); } } /** * {@inheritDoc} */ @Override public String[] getPropertyNames() { String[] superNames = super.getPropertyNames(); int len = superNames != null ? superNames.length + 1 : 1; String[] names = new String[len]; int k = 0; if (len > 1) { for (String name : superNames) { names[k++] = name; } } names[k] = RegionalizeDescriptor.REGION_DATA_PROPERTY; return names; } /** * Returns a tile of this image as a {@code Raster}. If the * requested tile is completely outside of this image's bounds, * this method returns {@code null}. *

* The nature of the regionalizing algorithm means that to compute * any tile other than the first (top left) we must compute * all tiles to avoid region numbering artefacts across * tile boundaries. * * @param tileX The X index of the tile. * @param tileY The Y index of the tile. */ @Override public Raster getTile(int tileX, int tileY) { Raster tile = null; if (tileX >= getMinTileX() && tileX <= getMaxTileX() && tileY >= getMinTileY() && tileY <= getMaxTileY()) { if (tileComputed[getTileIndex(tileX, tileY)]) { tile = regionImage.getTile(tileX, tileY); } else { synchronized (getTileLock) { try { tile = executor.submit(new ComputeTileTask(tileX, tileY)).get(); } catch (ExecutionException execEx) { throw new IllegalStateException(execEx); } catch (InterruptedException intEx) { // @todo is this safe ? return null; } } } } return tile; } /** * {@inheritDoc} */ @Override public Raster computeTile(int tileX, int tileY) { Rectangle destRect = getTileRect(tileX, tileY); synchronized (computeTileLock) { for (int destY = destRect.y, row = 0; row < destRect.height; destY++, row++) { for (int destX = destRect.x, col = 0; col < destRect.width; destX++, col++) { if (getRegionForPixel(destX, destY) == NO_REGION) { FillResult fill = filler.fill(destX, destY, currentID); regions.put(currentID, new Region(fill)); currentID++; } } } tileComputed[getTileIndex(tileX, tileY)] = true; } return regionImage.getTile(tileX, tileY); } /** * Calculates a single value tile coordinate. * * @param tileX tile X coordinate * @param tileY tile Y coordinate * * @return single integer coordinate used to index fields in this class */ private int getTileIndex(int tileX, int tileY) { return (tileY - getMinTileY()) * getNumXTiles() + (tileX - getMinTileX()); } /** * This method is overridden to prevent it being used by clients. * * @param tileX tile X ordinate * @param tileY tile Y ordinate * @param tile the tile * * @throws UnsupportedOperationException if called */ @Override protected void addTileToCache(int tileX, int tileY, Raster tile) { throw new UnsupportedOperationException("this method should not be called !"); } /** * This method is overridden to ensure that the cache is always addressed * through the {@code DiskMemImage} being used by this operator, otherwise * tile IDs calculated by the cache will vary with the perceived owner * (the image or the operator) of the tile. * * @param tileX tile X coordinate * @param tileY tile Y coordinate * * @return the requested tile */ @Override protected Raster getTileFromCache(int tileX, int tileY) { return regionImage.getTile(tileX, tileY); } /** * Set the tile cache. The supplied cache must be an instance of * {@code DiskMemTileCache}. * * @param cache an instance of DiskMemTileCache * @throws IllegalArgumentException if cache is null or not an instance * of {@code DiskMemTileCache} */ @Override public void setTileCache(TileCache cache) { if (cache != null && cache instanceof DiskMemTileCache) { super.setTileCache(cache); } else { throw new IllegalArgumentException("cache must be an instance of DiskMemTileCache"); } } /** * Get the ID of the region that contains the given pixel * position * * @return the id of the region that contains this pixel OR * NO_REGION if the pixel hasn't been processed */ private int getRegionForPixel(int x, int y) { int tileX = XToTileX(x); int tileY = YToTileY(y); Raster tile = regionImage.getTile(tileX, tileY); assert(tile != null); return tile.getSample(x, y, 0); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy