001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.processing.morphology;
031
032import java.util.HashSet;
033import java.util.Set;
034
035import org.openimaj.image.FImage;
036import org.openimaj.image.pixel.ConnectedComponent;
037import org.openimaj.image.pixel.Pixel;
038import org.openimaj.image.processor.KernelProcessor;
039import org.openimaj.image.processor.connectedcomponent.ConnectedComponentProcessor;
040import org.openimaj.math.geometry.shape.Rectangle;
041
042/**
043 * Morphological hit-and-miss transform of connected 
044 * components and (assumed binary) FImages.
045 * 
046 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
047 */
048public class HitAndMiss implements ConnectedComponentProcessor, KernelProcessor<Float, FImage> {
049        /**
050         * Hit and miss operator for detecting convex corners
051         */
052        public final static HitAndMiss CONVEX_CORNERS;
053        static {
054                CONVEX_CORNERS = new HitAndMiss(
055                                new StructuringElement(
056                                                new Pixel[] {new Pixel(0,-1), new Pixel(0,0), new Pixel(1,0)}, 
057                                                new Pixel[] {new Pixel(-1,0), new Pixel(-1,1), new Pixel(0,1)}),
058                                                new StructuringElement(
059                                                                new Pixel[] {new Pixel(0,-1), new Pixel(0,0), new Pixel(-1,0)}, 
060                                                                new Pixel[] {new Pixel(1,0), new Pixel(1,1), new Pixel(0,1)}),
061                                                                new StructuringElement(
062                                                                                new Pixel[] {new Pixel(-1,0), new Pixel(0,0), new Pixel(0,1)}, 
063                                                                                new Pixel[] {new Pixel(0,-1), new Pixel(1,-1), new Pixel(1,0)}),
064                                                                                new StructuringElement(
065                                                                                                new Pixel[] {new Pixel(0,1), new Pixel(0,0), new Pixel(1,0)}, 
066                                                                                                new Pixel[] {new Pixel(-1,-1), new Pixel(0,-1), new Pixel(-1,0)})
067                );
068        }
069
070        protected Set<Pixel> outputPixels = new HashSet<Pixel>();
071        protected StructuringElement [] elements;
072        protected int cx;
073        protected int cy;
074        protected int sw=0;
075        protected int sh=0;
076
077        /**
078         * Construct a hit and miss operator with the given
079         * structuring elements.
080         * @param ses the structuring elements
081         */
082        public HitAndMiss(StructuringElement... ses) {
083                this.elements = ses;
084
085                for (StructuringElement se : ses) {
086                        int [] sz = se.size();
087
088                        if (sw < sz[0]) sw = sz[0];
089                        if (sh < sz[1]) sh = sz[1];
090                }
091                cx = sw/2;
092                cy = sw/2;
093        }
094
095        @Override
096        public void process(ConnectedComponent cc) {
097                //hit and miss transform; doesn't affect original pixels.
098                outputPixels.clear();
099
100                for (StructuringElement element : elements) {
101                        Set<Pixel> pixels = cc.getPixels();
102                        int [] se_size = element.size();
103                        Rectangle cc_bb = cc.calculateRegularBoundingBox();
104                        for (int j=(int) (cc_bb.y-se_size[1]); j<=cc_bb.y+se_size[1]+cc_bb.height; j++) {
105                                for (int i=(int) (cc_bb.x-se_size[0]); i<=cc_bb.x+se_size[0]+cc_bb.width; i++) {
106                                        Pixel p = new Pixel(i, j);
107
108                                        if (element.matches(p, pixels)) {
109                                                outputPixels.add(p);
110                                        }
111                                }
112                        }
113                }
114        }
115
116        /**
117         * Get the pixels selected by the hit and miss operator
118         * @return a list of selected pixels
119         */
120        public Set<Pixel> getPixels() {
121                return outputPixels;
122        }
123
124        @Override
125        public int getKernelHeight() {
126                return sh;
127        }
128
129        @Override
130        public int getKernelWidth() {
131                return sw;
132        }
133
134        @Override
135        public Float processKernel(FImage patch) {
136
137                for (StructuringElement element : elements) {
138                        int count = 0;
139                        for (Pixel p : element.positive) {
140                                int px = cx - p.x;
141                                int py = cy - p.y;
142                                if (px>=0 && py>=0 && px<sw && py<sh && patch.pixels[py][px] == 1)
143                                        count++;
144                        }
145
146                        for (Pixel p : element.negative) {
147                                int px = cx - p.x;
148                                int py = cy - p.y;
149                                if (px>=0 && py>=0 && px<sw && py<sh && patch.pixels[py][px] == 0)
150                                        count++;
151                        }
152                        
153                        if (count == element.positive.size()+element.negative.size()) return 1f;
154                }
155
156                return 0f;
157        }
158}