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.text.extraction.swt;
031
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.Comparator;
035import java.util.List;
036
037import org.openimaj.image.processing.threshold.OtsuThreshold;
038import org.openimaj.util.pair.FloatFloatPair;
039
040/**
041 * This class models a candidate word (a collection of letter candidates with a
042 * consistent inter-character spacing) from the {@link SWTTextDetector}.
043 * 
044 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
045 * 
046 */
047public class WordCandidate extends Candidate {
048        /**
049         * The line to which this word belongs
050         */
051        protected LineCandidate line;
052
053        /**
054         * The letters in this word
055         */
056        protected List<LetterCandidate> letters = new ArrayList<LetterCandidate>();
057
058        protected static List<WordCandidate> extractWords(LineCandidate line, SWTTextDetector.Options options) {
059                final List<WordCandidate> words = new ArrayList<WordCandidate>();
060
061                // sort the letters
062                Collections.sort(line.letters, new Comparator<LetterCandidate>() {
063                        @Override
064                        public int compare(LetterCandidate o1, LetterCandidate o2) {
065                                return o1.centroid.x - o2.centroid.x;
066                        }
067                });
068
069                // Collect inter-word spacings
070                final float[] spacings = new float[line.letters.size() - 1];
071
072                float mean = 0;
073                int rng = 0;
074                for (int i = 1; i < line.letters.size(); i++) {
075                        final LetterCandidate left = line.letters.get(i - 1);
076                        final LetterCandidate right = line.letters.get(i);
077
078                        spacings[i - 1] = Math.max(0,
079                                        right.getRegularBoundingBox().x
080                                                        - (left.getRegularBoundingBox().x + left.getRegularBoundingBox().width));
081                        mean += spacings[i - 1];
082
083                        if (spacings[i - 1] >= rng)
084                                rng = (int) (spacings[i - 1] + 1);
085                }
086                mean /= spacings.length;
087
088                // use Otsu's method to find the optimal threshold
089                final FloatFloatPair threshVar = OtsuThreshold.calculateThresholdAndVariance(spacings, rng);
090                final float threshold = threshVar.first;
091                final float variance = threshVar.second;
092
093                // System.out.println(Math.sqrt(variance) / mean + " " + variance + " "
094                // + threshold);
095                // if the variance is sufficiently high to suggest multiple words
096                if (Math.sqrt(variance) > mean * options.wordBreakdownRatio)
097                {
098                        WordCandidate word = new WordCandidate();
099                        word.line = line;
100                        word.letters.add(line.letters.get(0));
101                        words.add(word);
102                        for (int i = 0; i < spacings.length; i++) {
103                                if (spacings[i] > threshold) {
104                                        word = new WordCandidate();
105                                        words.add(word);
106                                }
107                                word.letters.add(line.letters.get(i + 1));
108                        }
109                } else {
110                        final WordCandidate word = new WordCandidate();
111                        word.line = line;
112                        word.letters = line.letters;
113                        words.add(word);
114                }
115
116                for (final WordCandidate w : words) {
117                        w.regularBoundingBox = LetterCandidate.computeBounds(w.letters);
118
119                        for (final LetterCandidate letter : w.letters)
120                                letter.word = w;
121                }
122
123                return words;
124        }
125
126        /**
127         * Get the letters within this word.
128         * 
129         * @return the letters.
130         */
131        public List<LetterCandidate> getLetters() {
132                return letters;
133        }
134
135        /**
136         * Get the line containing this word.
137         * 
138         * @return the enclosing line.
139         */
140        public LineCandidate getLine() {
141                return line;
142        }
143}