/* * MaskedGray32SubImgGen.java * * Given a target image size and a horizontal and vertical offset * generates a series of subimages within the input image, * each subimage offset by an integral multiple of the * offset with size equal to the target size and lying * entirely within the original image. The offset of the * subimage in the input image is given in the subimage class. * In this masked version only subimages whose center is not a masked * point (mask image value = 0) will be generated. * * Created on July 1, 2007, 1:51 PM * * To change this template, choose Tools | Template Manager * and open the template in the editor. * * Copyright 2007 by Jon A. Webb * This program 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. * * This program 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 Lesser GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package jjil.algorithm; import jjil.core.Error; import jjil.core.Gray32MaskedImage; import jjil.core.Gray32OffsetImage; import jjil.core.Gray8OffsetImage; import jjil.core.Image; import jjil.core.PipelineStage; /** * Generates subimages from a source Gray32Image, using a mask. In a normal * subimage generator subimages are generated evenly spaced across the input * image. Here, the subimage is generated only if is center point is not masked. * This can increase processing speed in hierarchical detection operations since features * detected at coarser resolution don't have to be redetected at finer resolution. * @author webb */ public class MaskedGray32SubImgGen extends PipelineStage { Gray32MaskedImage imageInput; // input image boolean oSubImageReady; // true if sub image position is OK int nHeight; // target height int nHorizLimit = 0; // number of subimages generated horizontally int nVertLimit = 0; // number of subimages generated vertically int nHorizIndex = 0; // current subimiage index, horizontal int nVertIndex = 0; // current subimage index, vertical int nWidth; // target width int nXOffset; // x offset multiple for subimages int nYOffset; // y offset multiple for subimages /** * Creates a new instance of MaskedGray32SubImgGen. * @param nWidth the width of the generated subimage. * @param nHeight the height of the generated subimage. * @param nXOffset the horizontal offset between subimages. * @param nYOffset the vertical offset between subimages. */ public MaskedGray32SubImgGen(int nWidth, int nHeight, int nXOffset, int nYOffset) { this.nWidth = nWidth; this.nHeight = nHeight; this.nXOffset = nXOffset; this.nYOffset = nYOffset; this.oSubImageReady = false; // create an output image. We'll reuse this // image, changing the contents and offset, // for every Gray8OffsetImage we output. super.imageOutput = new Gray8OffsetImage( this.nWidth, this.nHeight, 0, 0); } /** advanceToNextSubImage advances to the next position for generating * a subimage. It returns true iff there is a non-masked position within * the image where a subimage can be generated. */ private boolean advanceToNextSubImage() { if (oSubImageReady) { return this.nVertIndex <= this.nVertLimit; } // advance to next subimage position this.nHorizIndex ++; if (this.nHorizIndex > this.nHorizLimit) { this.nVertIndex ++; this.nHorizIndex = 0; } // nPos is the byte address that we will test in the mask image // to see if it is OK to generate a subimage. It is the midpoint // of the subimage. int nPos = (this.nHeight / 2 + this.nVertIndex * this.nYOffset) * this.imageInput.getWidth() + this.nWidth / 2 + this.nHorizIndex * this.nXOffset; // starting at the current position, search forward for a position // that is not masked. while (this.nVertIndex <= this.nVertLimit) { while (this.nHorizIndex <= this.nHorizLimit) { if (this.imageInput.getMaskData()[nPos] == Byte.MIN_VALUE) { // found it oSubImageReady = true; return true; } this.nHorizIndex ++; nPos += this.nWidth; } this.nHorizIndex = 0; this.nVertIndex ++; // reset nPos for next row of subimages nPos = (this.nHeight / 2 + this.nVertIndex * this.nYOffset) * this.imageInput.getWidth() + this.nWidth / 2; } this.oSubImageReady = true; return false; } // We are done producing images when the advance returns no more images /** * Returns true iff there is another image available from getFront(). * Note that the existence of another image from * MaskedGray32SubImgGen depends on the mask image so there's no way to guarantee * there will be even one subimage generated for a particular input. You must * always call isEmpty(). * @return true iff there is another image available from getFront(). */ public boolean isEmpty() { return !advanceToNextSubImage(); } // Return the next subimage and increment the indices /** * Returns the next subimage generated. The subimage will have its offset * set to indicate where it was generated in the input image. * @return a MaskedGray8SubImage that is the next subimage in the input Gray8Image to * be processed. * @throws jjil.core.Error if no subimage is available (you have to call isEmpty() to determine if * a subimage is available. As few as 0 subimage can be generated for a * given input if the entire image is masked.) Also throws if the output * image (stored in the superclass) has been changed in type. */ public Image getFront() throws jjil.core.Error { // reuse output image // check to make sure nobody damaged it somehow if (!(super.imageOutput instanceof Gray32OffsetImage)) { throw new Error( Error.PACKAGE.ALGORITHM, ErrorCodes.OBJECT_NOT_EXPECTED_TYPE, super.imageOutput.toString(), "Gray32SubImage", null); } if (!advanceToNextSubImage()) { throw new Error( Error.PACKAGE.ALGORITHM, ErrorCodes.SUBIMAGE_NO_IMAGE_AVAILABLE, this.toString(), null, null); } int[] dataIn = this.imageInput.getData(); // offset of first pixel of the subimage within the // larget image. int nHOffset = this.nXOffset * this.nHorizIndex; int nVOffset = this.nYOffset * this.nVertIndex; Gray32OffsetImage imageResult = (Gray32OffsetImage) super.imageOutput; imageResult.setXOffset(nHOffset); imageResult.setYOffset(nVOffset); int[] dataOut = imageResult.getData(); // don't access outside the image int nLimitY = Math.min(this.imageInput.getHeight() - nVOffset, this.nHeight); int nLimitX = Math.min(this.imageInput.getWidth() - nHOffset, this.nWidth); for (int i=0; i<nLimitY; i++) { int nVInLoc = i + nVOffset; System.arraycopy( dataIn, nVInLoc*this.imageInput.getWidth() + nHOffset, dataOut, i*this.nWidth, nLimitX); } this.oSubImageReady = false; return imageResult; } /** * Accepts a new MaskedGray8Image and initializes all the generator indices. * @param image The input MaskedGray8Image. * @throws jjil.core.Error if the input is not of type MaskedGray8Image or is smaller than the * subimages to be generated. */ public void push(Image image) throws jjil.core.Error { if (!(image instanceof Gray32MaskedImage)) { throw new Error( Error.PACKAGE.ALGORITHM, ErrorCodes.OBJECT_NOT_EXPECTED_TYPE, image.toString(), "Gray32MaskedImage", null); } if (image.getWidth() < this.nWidth || image.getHeight() < this.nHeight) { throw new Error( Error.PACKAGE.ALGORITHM, ErrorCodes.IMAGE_TOO_SMALL, image.toString(), new Integer(this.nWidth).toString(), new Integer(this.nHeight).toString()); } this.imageInput = (Gray32MaskedImage) image; // we want to find the largest integer l such that // (l-1) * w + w <= iw // where l = computed limit on index // w = subimage width or height // iw = image width or height // or l = (iw - w) / w + 1 (truncated) // Java division truncates this.nHorizLimit = (image.getWidth() - this.nWidth) / this.nXOffset; this.nVertLimit = (image.getHeight() - this.nHeight) / this.nYOffset; this.nHorizIndex = -1; // first time through increments this.nVertIndex = 0; this.oSubImageReady = false; } }