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.convolution;
031
032import org.openimaj.image.FImage;
033import org.openimaj.image.processor.SinglebandImageProcessor;
034import org.openimaj.math.matrix.MatrixUtils;
035
036import Jama.SingularValueDecomposition;
037
038/**
039 * Base class for implementation of classes that perform convolution operations
040 * on {@link FImage}s as a @link{SinglebandImageProcessor}, with the kernel
041 * itself formed from and @link{FImage}.
042 *
043 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
044 */
045public class FConvolution implements SinglebandImageProcessor<Float, FImage> {
046        /** The kernel */
047        public FImage kernel;
048
049        private ConvolveMode mode;
050
051        interface ConvolveMode {
052                public void convolve(FImage f);
053
054                class OneD implements ConvolveMode {
055                        private float[] kernel;
056                        private boolean rowMode;
057
058                        OneD(FImage image) {
059                                if (image.height == 1) {
060                                        this.rowMode = true;
061                                        this.kernel = image.pixels[0];
062
063                                } else {
064                                        this.rowMode = false;
065                                        this.kernel = new float[image.height];
066                                        for (int i = 0; i < image.height; i++)
067                                                this.kernel[i] = image.pixels[i][0];
068                                }
069                        }
070
071                        @Override
072                        public void convolve(FImage f) {
073                                if (this.rowMode)
074                                        FImageConvolveSeparable.convolveHorizontal(f, kernel);
075                                else
076                                        FImageConvolveSeparable.convolveVertical(f, kernel);
077                        }
078
079                }
080
081                class Separable implements ConvolveMode {
082                        private float[] row;
083                        private float[] col;
084
085                        Separable(SingularValueDecomposition svd) {
086
087                                final int nrows = svd.getU().getRowDimension();
088
089                                this.row = new float[nrows];
090                                this.col = new float[nrows];
091
092                                final float factor = (float) Math.sqrt(svd.getS().get(0, 0));
093                                for (int i = 0; i < nrows; i++) {
094                                        this.row[i] = (float) svd.getU().get(i, 0) * factor;
095                                        this.col[i] = (float) svd.getV().get(i, 0) * factor;
096                                }
097                        }
098
099                        @Override
100                        public void convolve(FImage f) {
101                                FImageConvolveSeparable.convolveHorizontal(f, col);
102                                FImageConvolveSeparable.convolveVertical(f, row);
103                        }
104                }
105
106                class BruteForce implements ConvolveMode {
107                        protected FImage kernel;
108
109                        BruteForce(FImage kernel) {
110                                this.kernel = kernel;
111                        }
112
113                        @Override
114                        public void convolve(FImage image) {
115                                final int kh = kernel.height;
116                                final int kw = kernel.width;
117                                final int hh = kh / 2;
118                                final int hw = kw / 2;
119                                final FImage clone = image.newInstance(image.width, image.height);
120                                for (int y = hh; y < image.height - (kh - hh); y++) {
121                                        for (int x = hw; x < image.width - (kw - hw); x++) {
122                                                float sum = 0;
123                                                for (int j = 0, jj = kh - 1; j < kh; j++, jj--) {
124                                                        for (int i = 0, ii = kw - 1; i < kw; i++, ii--) {
125                                                                final int rx = x + i - hw;
126                                                                final int ry = y + j - hh;
127
128                                                                sum += image.pixels[ry][rx] * kernel.pixels[jj][ii];
129                                                        }
130                                                }
131                                                clone.pixels[y][x] = sum;
132                                        }
133                                }
134                                image.internalAssign(clone);
135                        }
136                }
137        }
138
139        /**
140         * Construct the convolution operator with the given kernel
141         *
142         * @param kernel
143         *            the kernel
144         */
145        public FConvolution(FImage kernel) {
146                this.kernel = kernel;
147                setup(false);
148        }
149
150        /**
151         * Construct the convolution operator with the given kernel
152         *
153         * @param kernel
154         *            the kernel
155         */
156        public FConvolution(float[][] kernel) {
157                this.kernel = new FImage(kernel);
158                setup(false);
159        }
160
161        /**
162         * Set brute-force convolution; disables kernel separation and other
163         * optimisations.
164         *
165         * @param brute
166         */
167        public void setBruteForce(boolean brute) {
168                setup(brute);
169        }
170
171        private void setup(boolean brute) {
172                if (brute) {
173                        this.mode = new ConvolveMode.BruteForce(this.kernel);
174                        return;
175                }
176                if (this.kernel.width == 1 || this.kernel.height == 1) {
177                        this.mode = new ConvolveMode.OneD(kernel);
178                } else {
179                        final SingularValueDecomposition svd = new SingularValueDecomposition(
180                                        MatrixUtils.matrixFromFloat(this.kernel.pixels));
181                        if (svd.rank() == 1)
182                                this.mode = new ConvolveMode.Separable(svd);
183                        else
184                                this.mode = new ConvolveMode.BruteForce(this.kernel);
185                }
186        }
187
188        /*
189         * (non-Javadoc)
190         *
191         * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj
192         * .image.Image)
193         */
194        @Override
195        public void processImage(FImage image) {
196                mode.convolve(image);
197        }
198
199        /**
200         * Return the kernel response at the x,y in the given image.
201         *
202         * This method will throw an array index out of bounds if x,y requests pixels
203         * outside the image bounds
204         *
205         * @param x
206         * @param y
207         * @param image
208         * @return the kernel response at the given coordinates
209         */
210        public float responseAt(int x, int y, FImage image) {
211                float sum = 0;
212                final int kh = kernel.height;
213                final int kw = kernel.width;
214                final int hh = kh / 2;
215                final int hw = kw / 2;
216
217                for (int j = 0, jj = kh - 1; j < kh; j++, jj--) {
218                        for (int i = 0, ii = kw - 1; i < kw; i++, ii--) {
219                                final int rx = x + i - hw;
220                                final int ry = y + j - hh;
221
222                                sum += image.pixels[ry][rx] * kernel.pixels[jj][ii];
223                        }
224                }
225                return sum;
226        }
227}