001    /*
002    Copyright (c) 1996-2011, Damon Hart-Davis
003    All rights reserved.
004    
005    Redistribution and use in source and binary forms, with or without
006    modification, are permitted provided that the following conditions are
007    met:
008    
009      * Redistributions of source code must retain the above copyright
010        notice, this list of conditions and the following disclaimer.
011    
012      * Redistributions in binary form must reproduce the above copyright
013        notice, this list of conditions and the following disclaimer in the
014        documentation and/or other materials provided with the
015        distribution.
016    
017    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028    */
029    
030    package org.hd.d.pg2k.ai.scorer.parameterised;
031    
032    import java.io.IOException;
033    import java.util.ArrayList;
034    import java.util.Arrays;
035    import java.util.Collections;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.concurrent.Callable;
039    
040    import org.hd.d.pg2k.ai.scorer.AbstractImgSampleScorer;
041    import org.hd.d.pg2k.ai.scorer.ScoreAndConf;
042    import org.hd.d.pg2k.ai.scorer.ScorerIF;
043    import org.hd.d.pg2k.ai.scorer.ScorerParam;
044    import org.hd.d.pg2k.ai.scorer.ScorerParamInteger;
045    import org.hd.d.pg2k.svrCore.ROIntArray;
046    import org.hd.d.pg2k.svrCore.Tuple.Pair;
047    
048    // FIXME: NOT FINISHED
049    
050    /**Simple score/measure of presence of various key 'hues' in an image; never null.
051     * This scores an image highly if a suitable fraction (in a specified range) of sampled pixels
052     * is within one of a number of acceptable (wrapped) hue ranges (centre hue and tolerance)
053     * in an HSB (Hue, Saturation, Brightness) colour model.
054     * <p>
055     * The aim is to be able (for example) to give an image a good score if it contains
056     * a reasonable fraction of sky (blue) or skin (tone) or green (grass and trees).
057     * <p>
058     * The set of sample points is a grid spaced in a regular (though not a simple grid)
059     * pattern across the image.  The layout of the sample points depends on the image
060     * size and aspect ratio, and is generated from a pseudo-random generator,
061     * to help avoid poor sampling of images with regular patterns in them.
062     * <p>
063     * Very small images will have all their pixels sampled,
064     * and so can in principle still achieve a MAX confidence.
065     * <p>
066     * Parameters include:
067     * <ul>
068     * <li>Pixel sample-set size.
069     * <li>Vector of target hues (from pixel HSB values).
070     * <li>Vector of hue tolerances (max 50% either side, wrapped through zero).
071     * <li>Vector of target percentages of pixels with given hue (0--100%).
072     * <li>Vector of percentage tolerances (max 50% either side).
073     * </ul>
074     * <p>
075     * This scoring method should run in constant time regardless of image size
076     * since it samples at most a fixed ceiling number of image points.
077     * <p>
078     * We may reject samples that are:
079     * <ul>
080     * <li>Mainly transparent.
081     * <li>Barely coloured, eg zero saturation or zero brightness in HSB colour space.
082     * </ul>
083     *
084     * @author dhd
085     */
086    public final class SimpleHue extends AbstractImgSampleScorer
087        {
088        /**Create simple non-parameterised instance. */
089        public SimpleHue()
090            {
091            // All parameters get default values.
092            sampleSizeParam = sampleSizeParamBounds;
093    //        brightnessParam = brightnessParamBounds;
094    //        outlierParam = outlierParamBounds;
095            }
096    
097        /**Create parameterised version. */
098        public SimpleHue(final String nameAndParameters)
099            {
100            super(nameAndParameters);
101    
102            final Pair<String, Map<String, String>> nap = parseNameAndParameters(nameAndParameters);
103            final Map<String, String> paramValueMap = nap.second; // Capture the parameters.
104    
105            // Assign parameters from the captured map where possible, using default values otherwise.
106            sampleSizeParam = (ScorerParamInteger) sampleSizeParamBounds.parse(paramValueMap.get(sampleSizeParamBounds.name));
107    //        brightnessParam = (ScorerParamInteger) brightnessParamBounds.parse(paramValueMap.get(brightnessParamBounds.name));
108    //        outlierParam = (ScorerParamInteger) outlierParamBounds.parse(paramValueMap.get(outlierParamBounds.name));
109            }
110    
111        /**Create parameterised version. */
112        public SimpleHue(final String baseName, final List<ScorerParam> parameters)
113            {
114            super(baseName, parameters);
115    
116            final Map<String, ScorerParam> paramValueMap = paramListAsMap(parameters);
117    
118            // Assign parameters from the captured map where possible, using default values otherwise.
119            sampleSizeParam = (ScorerParamInteger) sampleSizeParamBounds.extract(paramValueMap.get(sampleSizeParamBounds.name));
120    //        brightnessParam = (ScorerParamInteger) brightnessParamBounds.extract(paramValueMap.get(brightnessParamBounds.name));
121    //        outlierParam = (ScorerParamInteger) outlierParamBounds.extract(paramValueMap.get(outlierParamBounds.name));
122            }
123    
124        /**Simple non-static factory for the parameterised case. */
125        public ScorerIF createVariant(final String nameAndParameters) throws IllegalArgumentException
126            { return(new SimpleHue(nameAndParameters)); }
127    
128        /* (non-Javadoc)
129         * @see org.hd.d.pg2k.ai.scorer.ScorerIF#createVariant(java.lang.String, java.util.List)
130         */
131        public ScorerIF createVariant(final String baseName, final List<ScorerParam> parameters) throws IllegalArgumentException
132            { return(new SimpleHue(baseName, parameters)); }
133    
134        /**Score the image for "exposure"; never null nor negative in any component.
135         * @throws IllegalArgumentException for a null image factory
136         * @throws IllegalStateException for some problem retrieving the image
137         * @return assessment of the image, else (0,0) for a null image
138         *
139         * @throws IllegalArgumentException for a null image factory
140         * @throws IllegalStateException for some problem retrieving the image
141         * @return assessment of the image, else (0,0) for a null image
142         */
143        @Override public ScoreAndConf computeScoreAndConfidenceOnStillImagePixelSamples(final Object key, final Callable<ROIntArray> stillImagePixelSamplesFactory)
144            throws IOException
145            {
146            final ROIntArray stillImagePixelSamples = callStillImagePixelSamplesFactory(stillImagePixelSamplesFactory);
147            if(stillImagePixelSamples == null) { return(ScoreAndConf.NO_OPINION); }
148    
149            // Compute the target number of sample points (strictly positive).
150            final int targetSamplePoints = (1 << sampleSizeParam.value);
151            assert(targetSamplePoints > 0);
152    
153            // Set of brightness values sampled from the image.
154            // Assumed to be sortable, ie all values finite.
155            final int availableSamples = stillImagePixelSamples.length();
156            Math.min(availableSamples, targetSamplePoints);
157    
158    
159            throw new Error("NOT IMPLEMENTED");
160    
161    //        return(new ScoreAndConf(score, confidence));
162            }
163    
164        /**Min, default and max shift (power-of-two) for the target sample size; never null.
165         * This has a bias to lower values since this results in less work done.
166         */
167        private static final ScorerParamInteger sampleSizeParamBounds =
168            ScorerParamInteger.createScorerParamInteger(2, 7, MAX_SAMPLE_SIZE_POWER, 1, true, "sampleSizePower");
169    
170        /**Target sample size for this instance; never null.
171         * Target number of sample points; strictly positive.
172         * If fewer sample points than this are available,
173         * then the result's confidence will drop
174         * in accordance with expected sample-variance noise reduction.
175         * <p>
176         * Reducing the sample size by a factor of x is assumed to increase the noise,
177         * and thus reduce the confidence, by a factor of sqrt(x).
178         * <p>
179         * Experience suggests that upwards of a thousand samples gives good consistency.
180         */
181        private final ScorerParamInteger sampleSizeParam;
182    
183        /**Get parameter definitions and values (immutable) for this Scorer; never null. */
184        @Override
185        public List<ScorerParam> getParameterDefsAndValues()
186            {
187            final ArrayList<ScorerParam> result = new ArrayList<ScorerParam>();
188            result.addAll(Arrays.asList(new ScorerParam[] {
189                    sampleSizeParam, // brightnessParam, outlierParam
190                }));
191            //result.addAll(scoreMappingParams);
192            //result.addAll(confidenceMappingParams);
193            result.trimToSize();
194            return(Collections.unmodifiableList(result));
195            }
196        }