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.feature.dense.binarypattern;
031
032import gnu.trove.list.array.TIntArrayList;
033import gnu.trove.map.hash.TIntObjectHashMap;
034
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.List;
038
039import org.openimaj.citation.annotation.Reference;
040import org.openimaj.citation.annotation.ReferenceType;
041import org.openimaj.image.FImage;
042import org.openimaj.image.pixel.Pixel;
043
044/**
045 * Class for determining whether specific binary patterns are "uniform". Uniform
046 * patterns have less than one 01 transition and one 01 transition when viewed
047 * as a circular buffer.
048 * <p>
049 * The class caches lookup tables of uniform patterns on demand, with the
050 * exception of the commonly used 8-bit patterns which are cached on
051 * initialization.
052 * 
053 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
054 * 
055 */
056@Reference(
057                type = ReferenceType.Article,
058                author = { "Ojala, T.", "Pietikainen, M.", "Maenpaa, T." },
059                title = "Multiresolution gray-scale and rotation invariant texture classification with local binary patterns",
060                year = "2002",
061                journal = "Pattern Analysis and Machine Intelligence, IEEE Transactions on",
062                pages = { "971 ", "987" },
063                month = "jul",
064                number = "7",
065                volume = "24",
066                customData = {
067                                "doi", "10.1109/TPAMI.2002.1017623",
068                                "ISSN", "0162-8828"
069                })
070public class UniformBinaryPattern {
071        protected static TIntObjectHashMap<TIntArrayList> lut = new TIntObjectHashMap<TIntArrayList>();
072
073        static {
074                // pre-cache the table for 8-bit patterns as it is common
075                lut.put(8, calculateUniformPatterns(8));
076                // other patterns will be cached on demand
077        }
078
079        protected static TIntArrayList calculateUniformPatterns(int nbits) {
080                final TIntArrayList result = new TIntArrayList();
081
082                final boolean[] bits = new boolean[nbits];
083
084                for (int i = 0; i < Math.pow(2, nbits); i++) {
085                        Arrays.fill(bits, false);
086
087                        for (int temp = i, j = 1; j <= nbits; j++) {
088                                final int pow = (int) Math.pow(2, (nbits - j));
089
090                                if (temp / pow > 0) {
091                                        bits[j - 1] = true;
092                                }
093                                temp = temp % pow;
094                        }
095
096                        if (isUniform(bits)) {
097                                result.add(i);
098                        }
099                }
100
101                return result;
102        }
103
104        protected static boolean isUniform(boolean[] pattern) {
105                int count = 0;
106
107                for (int i = 0; i < pattern.length - 1; i++) {
108                        if (pattern[i] != pattern[i + 1]) {
109                                count++;
110                        }
111                }
112
113                return count <= 2;
114        }
115
116        /**
117         * Get a list of all the binary patterns of a given length that are
118         * "uniform". Uniform patterns have less than one 01 transition and one 01
119         * transition when viewed as a circular buffer.
120         * 
121         * The length must be between 1 and 32 bits.
122         * 
123         * @param nbits
124         *            pattern length
125         * @return set of patterns encoded as integers
126         */
127        public static TIntArrayList getUniformPatterns(int nbits) {
128                if (nbits < 1 || nbits > 32)
129                        throw new IllegalArgumentException("Only patterns with lengths between 1 and 32 bits are supported");
130
131                TIntArrayList patterns = lut.get(nbits);
132
133                if (patterns == null) {
134                        patterns = calculateUniformPatterns(nbits);
135                        lut.put(nbits, patterns);
136                }
137
138                return patterns;
139        }
140
141        /**
142         * Check whether the given nbits pattern is uniform.
143         * 
144         * @param pattern
145         *            the pattern
146         * @param nbits
147         *            the pattern length
148         * @return true if uniform; false otherwise.
149         */
150        public static boolean isPatternUniform(int pattern, int nbits) {
151                return getUniformPatterns(nbits).contains(pattern);
152        }
153
154        /**
155         * Compute a binary map showing the locations of the specified pattern code.
156         * 
157         * @param patternImage
158         *            the pattern data
159         * @param code
160         *            the code to extract
161         * @return a binary {@link FImage} depicting the locations of the given code
162         */
163        public static FImage extractPatternImage(int[][] patternImage, int code) {
164                final FImage image = new FImage(patternImage[0].length, patternImage.length);
165
166                for (int y = 0; y < image.height; y++) {
167                        for (int x = 0; x < image.width; x++) {
168                                if (patternImage[y][x] == code) {
169                                        image.pixels[y][x] = 1;
170                                }
171                        }
172                }
173
174                return image;
175        }
176
177        /**
178         * Compute all binary maps for each possible pattern code given the number
179         * of bits used to encode patterns.
180         * 
181         * @param patternImage
182         *            the pattern data
183         * @param nbits
184         *            the number of bits for the patterns
185         * @return an array of binary maps corresponding to each pattern
186         */
187        public static FImage[] extractPatternImages(int[][] patternImage, int nbits) {
188                final TIntArrayList uniformPatterns = getUniformPatterns(nbits);
189
190                final FImage[] images = new FImage[uniformPatterns.size() + 1];
191                final int width = patternImage[0].length;
192                final int height = patternImage.length;
193
194                for (int y = 0; y < height; y++) {
195                        for (int x = 0; x < width; x++) {
196                                final int idx = uniformPatterns.indexOf(patternImage[y][x]);
197
198                                if (images[idx + 1] == null) {
199                                        images[idx + 1] = new FImage(width, height);
200                                }
201
202                                images[idx + 1].pixels[y][x] = 1;
203                        }
204                }
205
206                return images;
207        }
208
209        /**
210         * Compute all binary maps for each possible pattern code given the number
211         * of bits used to encode patterns.
212         * 
213         * @param patternImage
214         *            the pattern data
215         * @param nbits
216         *            the number of bits for the patterns
217         * @return an array of binary maps corresponding to each pattern
218         */
219        public static boolean[][][] extractPatternMaps(int[][] patternImage, int nbits) {
220                final TIntArrayList uniformPatterns = getUniformPatterns(nbits);
221
222                final int width = patternImage[0].length;
223                final int height = patternImage.length;
224                final boolean[][][] maps = new boolean[uniformPatterns.size() + 1][height][width];
225
226                for (int y = 0; y < height; y++) {
227                        for (int x = 0; x < width; x++) {
228                                final int idx = uniformPatterns.indexOf(patternImage[y][x]);
229
230                                maps[idx + 1][y][x] = true;
231                        }
232                }
233
234                return maps;
235        }
236
237        /**
238         * Compute all pixels matching each possible pattern code given the number
239         * of bits used to encode patterns.
240         * 
241         * @param patternImage
242         *            the pattern data
243         * @param nbits
244         *            the number of bits for the patterns
245         * @return an array of binary maps corresponding to each pattern
246         */
247        public static List<List<Pixel>> extractPatternPixels(int[][] patternImage, int nbits) {
248                final TIntArrayList uniformPatterns = getUniformPatterns(nbits);
249
250                final List<List<Pixel>> images = new ArrayList<List<Pixel>>(uniformPatterns.size() + 1);
251                final int width = patternImage[0].length;
252                final int height = patternImage.length;
253
254                for (int i = 0; i < uniformPatterns.size() + 1; i++)
255                        images.add(null);
256
257                for (int y = 0; y < height; y++) {
258                        for (int x = 0; x < width; x++) {
259                                final int idx = uniformPatterns.indexOf(patternImage[y][x]);
260
261                                if (images.get(idx + 1) == null) {
262                                        images.set(idx + 1, new ArrayList<Pixel>());
263                                }
264
265                                images.get(idx + 1).add(new Pixel(x, y));
266                        }
267                }
268
269                return images;
270        }
271}