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.awt.Color;
033    import java.io.IOException;
034    import java.util.ArrayList;
035    import java.util.Arrays;
036    import java.util.Collections;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.concurrent.Callable;
040    
041    import org.hd.d.pg2k.ai.scorer.AbstractImgSampleScorer;
042    import org.hd.d.pg2k.ai.scorer.ScoreAndConf;
043    import org.hd.d.pg2k.ai.scorer.ScorerIF;
044    import org.hd.d.pg2k.ai.scorer.ScorerParam;
045    import org.hd.d.pg2k.ai.scorer.ScorerParamEnum;
046    import org.hd.d.pg2k.ai.scorer.ScorerParamInteger;
047    import org.hd.d.pg2k.svrCore.ROIntArray;
048    import org.hd.d.pg2k.svrCore.Tuple.Pair;
049    
050    /**Simple but configurable Scoring based on local pixel sampling; never null.
051     * Since this does not involve any global image operations,
052     * running time is nominally independent of image dimensions,
053     * ie in all case local image properties are sampled.
054     * <p>
055     * This can sample various pixel parameters such as R, G and B components,
056     * and can use the absolute values or available ranges.
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     * The confidence depends on the number of selected sample points that are available.
064     * <p>
065     * Very small images will have all their pixels sampled,
066     * and so can in principle still achieve a MAX confidence.
067     * <p>
068     * Parameters include:
069     * <ul>
070     * <li>Pixel sample-set size.
071     * <li>Pixel component to sample: A (alpha), R, G, B, Y (luminance), H, S, B.
072     * <li>Pixel sampling method: IGNORED FOR NOW (always as-if value): value or median range (on grid centred on specified pixel).
073     * <li>Pixel sample combination method: mean or median range.
074     * <li>Fraction of outliers to discard for sample-combination median range computations.
075     * <li>Mapping interpolation vector from sample combination value to score.
076     * <li>Mapping interpolation vector from sample combination value to confidence.
077     * </ul>
078     * <p>
079     * This may be most useful for continuous-tone true-colour/greyscale (eg JPEG) images.
080     *
081     * @author dhd
082     */
083    public final class LocalSampler extends AbstractImgSampleScorer
084        {
085        /**Create simple non-parameterised instance. */
086        public LocalSampler()
087            {
088            // All parameters get default values.
089            sampleSizeParam = sampleSizeParamBounds;
090            outlierParam = outlierParamBounds;
091            componentParam = componentParamBounds;
092            sampleCombinationParam = sampleCombinationParamBounds;
093            pixelSampleMethodParam = pixelSampleMethodParamBounds;
094            scoreMappingParams = scoreMappingParamsBounds;
095            confidenceMappingParams = confidenceMappingParamsBounds;
096            }
097    
098        /**Create parameterised version. */
099        public LocalSampler(final String nameAndParameters)
100            {
101            super(nameAndParameters);
102    
103            final Pair<String, Map<String, String>> nap = parseNameAndParameters(nameAndParameters);
104            final Map<String, String> paramValueMap = nap.second; // Capture the parameters.
105    
106            // Assign parameters from the captured map where possible, using default values otherwise.
107            sampleSizeParam = (ScorerParamInteger) sampleSizeParamBounds.parse(paramValueMap.get(sampleSizeParamBounds.name));
108            outlierParam = (ScorerParamInteger) outlierParamBounds.parse(paramValueMap.get(outlierParamBounds.name));
109            componentParam = (ScorerParamEnum<PixelComponent>) componentParamBounds.parse(paramValueMap.get(componentParamBounds.name));
110            sampleCombinationParam = (ScorerParamEnum<SampleType>) sampleCombinationParamBounds.parse(paramValueMap.get(sampleCombinationParamBounds.name));
111            pixelSampleMethodParam = (ScorerParamEnum<SampleType>) pixelSampleMethodParamBounds.parse(paramValueMap.get(pixelSampleMethodParamBounds.name));
112            final ArrayList<ScorerParamInteger> s = new ArrayList<ScorerParamInteger>(MAPPING_INTERVALS+1);
113            for(final ScorerParamInteger p : scoreMappingParamsBounds)
114                { s.add((ScorerParamInteger) p.parse(paramValueMap.get(p.name))); }
115            scoreMappingParams = Collections.unmodifiableList(s);
116            final ArrayList<ScorerParamInteger> c = new ArrayList<ScorerParamInteger>(MAPPING_INTERVALS+1);
117            for(final ScorerParamInteger p : confidenceMappingParamsBounds)
118                { c.add((ScorerParamInteger) p.parse(paramValueMap.get(p.name))); }
119            confidenceMappingParams = Collections.unmodifiableList(c);
120            }
121    
122        /**Create parameterised version. */
123        public LocalSampler(final String baseName, final List<ScorerParam> parameters)
124            {
125            super(baseName, parameters);
126    
127            final Map<String, ScorerParam> paramValueMap = paramListAsMap(parameters);
128    
129            // Assign parameters from the captured map where possible, using default values otherwise.
130            sampleSizeParam = (ScorerParamInteger) sampleSizeParamBounds.extract(paramValueMap.get(sampleSizeParamBounds.name));
131            outlierParam = (ScorerParamInteger) outlierParamBounds.extract(paramValueMap.get(outlierParamBounds.name));
132            componentParam = (ScorerParamEnum<PixelComponent>) componentParamBounds.extract(paramValueMap.get(componentParamBounds.name));
133            sampleCombinationParam = (ScorerParamEnum<SampleType>) sampleCombinationParamBounds.extract(paramValueMap.get(sampleCombinationParamBounds.name));
134            pixelSampleMethodParam = (ScorerParamEnum<SampleType>) pixelSampleMethodParamBounds.extract(paramValueMap.get(pixelSampleMethodParamBounds.name));
135            final ArrayList<ScorerParamInteger> s = new ArrayList<ScorerParamInteger>(MAPPING_INTERVALS+1);
136            for(final ScorerParamInteger p : scoreMappingParamsBounds)
137                { s.add((ScorerParamInteger) p.extract(paramValueMap.get(p.name))); }
138            scoreMappingParams = Collections.unmodifiableList(s);
139            final ArrayList<ScorerParamInteger> c = new ArrayList<ScorerParamInteger>(MAPPING_INTERVALS+1);
140            for(final ScorerParamInteger p : confidenceMappingParamsBounds)
141                { c.add((ScorerParamInteger) p.extract(paramValueMap.get(p.name))); }
142            confidenceMappingParams = Collections.unmodifiableList(c);
143            }
144    
145        /**Simple non-static factory for the parameterised case. */
146        public ScorerIF createVariant(final String nameAndParameters) throws IllegalArgumentException
147            { return(new LocalSampler(nameAndParameters)); }
148    
149        /* (non-Javadoc)
150         * @see org.hd.d.pg2k.ai.scorer.ScorerIF#createVariant(java.lang.String, java.util.List)
151         */
152        public ScorerIF createVariant(final String baseName, final List<ScorerParam> parameters) throws IllegalArgumentException
153            { return(new LocalSampler(baseName, parameters)); }
154    
155        /**Score the image for "exposure"; never null nor negative in any component.
156         * @throws IllegalArgumentException for a null image factory
157         * @throws IllegalStateException for some problem retrieving the image
158         * @return assessment of the image, else (0,0) for a null image
159         *
160         * @throws IllegalArgumentException for a null image factory
161         * @throws IllegalStateException for some problem retrieving the image
162         * @return assessment of the image, else (0,0) for a null image
163         */
164        @Override public ScoreAndConf computeScoreAndConfidenceOnStillImagePixelSamples(final Object key, final Callable<ROIntArray> stillImagePixelSamplesFactory)
165            throws IOException
166            {
167            final ROIntArray stillImagePixelSamples = callStillImagePixelSamplesFactory(stillImagePixelSamplesFactory);
168            if(stillImagePixelSamples == null) { return(ScoreAndConf.NO_OPINION); }
169    
170    //        // True if we will be sampling local pixel "spread".
171    //        final boolean pixelSpread = (pixelSampleMethodParam.value == SampleType.RANGE);
172    //        // We need slightly larger minimal image size to sample pixel spread.
173    //        if(pixelSpread && ((h < 3) || (w < 3))) { return(ScoreAndConf.NO_OPINION); }
174    
175            // Compute the target number of sample points (strictly positive).
176            final int targetSamplePoints = (1 << sampleSizeParam.value);
177            assert(targetSamplePoints > 0);
178    
179            // Set of brightness values sampled from the image.
180            // Assumed to be sortable, ie all values finite.
181            final int availableSamples = stillImagePixelSamples.length();
182            // If an image has fewer pixels than our putative sample size then cap our target to match.
183            final int adjustedTargetSamplePoints = Math.min(availableSamples, targetSamplePoints);
184    
185            // True if we will be applying a RANGE to combine the individual samples.
186            final boolean sampleCombRANGE = (sampleCombinationParam.value == SampleType.RANGE);
187    
188            // Count of all values sampled from the image.
189            final int sampleCounts[] = new int[MAX_COMPONENT_VALUE+1];
190    
191            int nSamples = 0;
192            // Attempt to collect the target number of samples,
193            // but scan at most twice the required sample points to exact suitably opaque ones
194            // to avoid getting a wildly skewed set of samples from mainly-transparent images.
195            for(int i = Math.min(availableSamples, 2*targetSamplePoints); (--i >= 0) && (nSamples < adjustedTargetSamplePoints); )
196                {
197                final int argb = stillImagePixelSamples.get(i);
198                final int b = samplePoint(argb);
199                if(b == -1) { continue; }
200                ++sampleCounts[b];
201                ++nSamples;
202                }
203    
204            // After discarding outliers, and with at >=2 points remaining to compute the range with
205            // then we must have collected at least 4 samples.
206            // If we don't have at least this many, then return a "no confidence" result.
207            // We do this even for VALUE (rather than RANGE) results.
208            if(nSamples < 4) { return(ScoreAndConf.NO_OPINION); }
209            // Compute number of outliers to discard from either extreme.
210            final int nOutliers = Math.max(1, nSamples / (1 << outlierParam.value));
211            final int medianSamples = nSamples - 2*nOutliers;
212            assert(medianSamples >= 2);
213    
214            trimOutliers(sampleCounts, nOutliers);
215            int lowest = -1;
216            for(int i = 0; i <= MAX_COMPONENT_VALUE; ++i)
217                { if(sampleCounts[i] != 0) { lowest = i; break; } }
218            int highest = -1;
219            for(int i = MAX_COMPONENT_VALUE; i >= 0; --i)
220                { if(sampleCounts[i] != 0) { highest = i; break; } }
221            assert((lowest != -1) && (highest != -1) && (lowest <= highest));
222    
223            // Compute the raw value [0,MAX_COMPONENT_VALUE] before mapping, eg mean or range.
224            final int value;
225            if(sampleCombRANGE)
226                {
227                // Find the lowest and highest sample values with non-zero counts.
228    //    System.out.println("SimpleExposure: low/high = "+lowest+"/"+highest + "; lowest/highest = "+pointsSampled.get(0)+"/"+pointsSampled.get(nSamples-1));
229                value = highest - lowest;
230                }
231            else
232                {
233                int sum = 0;
234                // We expect this array to be dense, ie most values non-zero in the median range.
235                for(int i = lowest; i <= highest; ++i)
236                    { sum += (i * sampleCounts[i]); }
237                value = sum / medianSamples;
238                }
239            assert((value >= 0) && (value <= MAX_COMPONENT_VALUE));
240    
241            // Generate final score.
242            // Note that value is in range [0,MAX_COMPONENT_VALUE] so needs normalisation.
243            final int score = mapRawValue(value, scoreMappingParams);
244    
245            // The confidence value depends on the number of samples that we were able to take
246            // as well as the mapping from raw value.
247            final int confidence = Math.max(0, Math.min(ScoreAndConf.MAX,
248                (int) Math.round(mapRawValue(value, confidenceMappingParams) * Math.sqrt(nSamples / (double) adjustedTargetSamplePoints))));
249    
250            return(new ScoreAndConf(score, confidence));
251            }
252    
253        /**Trim the n outliers of each end of the supplied array of sample counts.
254         * @param sampleCounts  array of non-negative sample count
255         *     for each value x at index x; never null
256         * @param nOutliers  strictly positive outlier count; less than half the total sum
257         */
258        private static void trimOutliers(final int[] sampleCounts, final int nOutliers)
259            {
260            assert((sampleCounts != null) && (sampleCounts.length == MAX_COMPONENT_VALUE+1));
261            assert(nOutliers > 0);
262    
263            // Trim from the bottom.
264            int bottomOutliersRemaining = nOutliers;
265            for(int i = 0; (i <= MAX_COMPONENT_VALUE) && (bottomOutliersRemaining > 0); ++i)
266                {
267                final int c = sampleCounts[i];
268                final int toZap = Math.min(c, bottomOutliersRemaining);
269                sampleCounts[i] -= toZap;
270                bottomOutliersRemaining -= toZap;
271                }
272            assert(bottomOutliersRemaining == 0); // Should have been able to clear them all.
273    
274            // Trim from the top.
275            int topOutliersRemaining = nOutliers;
276            for(int i = MAX_COMPONENT_VALUE; (i >= 0) && (topOutliersRemaining > 0); --i)
277                {
278                final int c = sampleCounts[i];
279                final int toZap = Math.min(c, topOutliersRemaining);
280                sampleCounts[i] -= toZap;
281                topOutliersRemaining -= toZap;
282                }
283            assert(topOutliersRemaining == 0); // Should have been able to clear them all.
284            }
285    
286        /**Max brightness returned by samplePoint (minimum is 0); strictly positive. */
287        private static final byte MAX_COMPONENT_VALUE = 127;
288    
289        /**Compute (interpolated) mapping from raw value to score/confidence.
290         * The result is in the range permitted by the input mapping's min/max bounds.
291         */
292        private static final int mapRawValue(final int rawVal, final java.util.List<ScorerParamInteger> mapping)
293            {
294            assert((rawVal >= 0) && (rawVal <= MAX_COMPONENT_VALUE));
295            assert((mapping != null) && (mapping.size() == MAPPING_INTERVALS+1));
296    
297            // Deal with both extremes with a special fast path.
298            if(rawVal == 0) { return(mapping.get(0).value); }
299            if(rawVal == MAX_COMPONENT_VALUE) { return(mapping.get(MAPPING_INTERVALS).value); }
300    
301            // Compute the interval number n,
302            // so we can interpolate between mapping value n and n+1.
303            final int intervalNumber = (rawVal * MAPPING_INTERVALS) / MAX_COMPONENT_VALUE;
304            assert((intervalNumber >= 0) && (intervalNumber < MAPPING_INTERVALS));
305            final int low = mapping.get(intervalNumber).value;
306            final int high = mapping.get(intervalNumber+1).value;
307    
308            final int maxResidual = (MAX_COMPONENT_VALUE+1) / MAPPING_INTERVALS;
309            final int residual = rawVal % maxResidual;
310    
311            final int interpolatedValue = low + (((high-low) * residual) / maxResidual);
312            assert(interpolatedValue >= mapping.get(intervalNumber).min);
313            assert(interpolatedValue <= mapping.get(intervalNumber).max);
314    
315            return(interpolatedValue);
316            }
317    
318    //    /**Take sample of area centred on specified point in image; null means point cannot be sampled, else [0,127].
319    //     * We compute the median range of the appropriate component of the specified pixel
320    //     * and the 8 around it, skipping any that would be outside the image boundary
321    //     * (the specified point is assumed to be within the image)
322    //     * or that are returned as unsamplable by the the samplePoint() method.
323    //     * <p>
324    //     * We discard the highest and lowest values (as possible noise)
325    //     * and return the span of the remaining values.
326    //     * <p>
327    //     * We must be able to find at least 4 (four) samplable values
328    //     * else we must return null.
329    //     *
330    //     * @return -1 for an unsamplable point, else value in range [0,127] inclusive
331    //     */
332    //    private int samplePointRange(final BufferedImage stillImage, final int x, final int y)
333    //        {
334    //        final int h = stillImage.getHeight();
335    //        final int w = stillImage.getWidth();
336    //        final int minX = stillImage.getMinX();
337    //        final int minY = stillImage.getMinY();
338    //        assert((x >= minX) && (x < minX+w));
339    //        assert((y >= minY) && (y < minY+h));
340    //
341    //        final ArrayList<Byte> values = new ArrayList<Byte>(9);
342    //
343    //        final int xl = Math.max(minX, x-1);
344    //        final int xh = Math.min(minX+w-1, x+1);
345    //        final int yl = Math.max(minY, y-1);
346    //        final int yh = Math.min(minY+h-1, y+1);
347    //        for(int xi = xl; xi <= xh; ++xi)
348    //            {
349    //            for(int yi = yl; yi <= yh; ++yi)
350    //                {
351    //                final int value = samplePoint(stillImage, xi, yi);
352    //                if(value != -1) { values.add(Byte.valueOf((byte) value)); }
353    //                }
354    //            }
355    //
356    //        // Must have enough samples to extract median range,
357    //        // ie 4 if we are to discard highest and lowest values.
358    //        // Note that 4 is few enough to be able to sample at an image corner.
359    //        final int sampleCount = values.size();
360    //        if(sampleCount < 4) { return(-1); }
361    //
362    //        // Sort the values into order,
363    //        // and ignore the top and bottom points,
364    //        // taking the result as the range of the remainder.
365    //        Collections.sort(values);
366    //        final int low = values.get(1);
367    //        final int high = values.get(sampleCount-1);
368    //        assert(low <= high);
369    //        return(high - low);
370    //        }
371    
372        /**Take sample at given point in image; null means point cannot be sampled, else [0,127].
373         * We regard points that are less than 50% opaque as being unsamplable
374         * except where we are sampling the alpha value itself.
375         *
376         * @return -1 for an unsamplable point, else value in range [0,127] inclusive
377         */
378        private int samplePoint(final int argb)
379            {
380            final int alpha = (argb >>> 24);
381            if(componentParam.value == PixelComponent.ALPHA) { return(Byte.valueOf((byte) (alpha >>> 1))); }
382    
383            // Reject mainly-transparent pixels.
384            // alpha == 0xff ==> opaque (we can use it), alpha == 0 ==> transparent/unusable.
385            if(alpha < 0x80) { return(-1); }
386    
387            // Extract r g b components in the range [0,255].
388            final int r = (argb >>> 16) & 0xff;
389            if(componentParam.value == PixelComponent.R) { return(Byte.valueOf((byte) (r >>> 1))); }
390            final int g = (argb >>>  8) & 0xff;
391            if(componentParam.value == PixelComponent.G) { return(Byte.valueOf((byte) (g >>> 1))); }
392            final int b =  argb         & 0xff;
393            if(componentParam.value == PixelComponent.B) { return(Byte.valueOf((byte) (b >>> 1))); }
394    
395            // Compute B (brightness) of HSB model efficiently.
396            if(componentParam.value == PixelComponent.BRI)
397                {
398                final int cmax = Math.max(r, Math.max(g, b));
399                final int brByte = cmax >>> 1;
400                assert((brByte >= 0) && (brByte <= MAX_COMPONENT_VALUE));
401                return(brByte);
402                }
403    
404            // Compute Y (luminance) value efficiently.
405            if(componentParam.value == PixelComponent.Y)
406                {
407                // Compute Y of YUV model (as used in PAL TV encodings).
408                // Approx .299r + .587g + .114b, but mapping from [0-255] inputs to [0,127] result.
409                final int Y = (37 * r + 74 * g + 14 * b + 128) >> 8;
410                assert((Y >= 0) && (Y <= MAX_COMPONENT_VALUE));
411                return(Y);
412                }
413    
414            // Hue or saturation; make it default to hue for expansion robustness.
415            // Slow computation involving floating-point.
416            final boolean hueRequired = (componentParam.value != PixelComponent.SAT);
417            return(Math.max(0, Math.min(MAX_COMPONENT_VALUE, Math.round(
418                Color.RGBtoHSB(r, g, b, null)[hueRequired ? 0 : 1] * MAX_COMPONENT_VALUE))));
419            }
420    
421        /**Number of intervals in which to map score and confidence; strictly positive.
422         * Note that there will be one more parameters than this for each of
423         * the score and confidence mappings
424         * since the parameters mark the end-points of each interval,
425         * with linear interpolation between them.
426         * <p>
427         * Too small a value hinders a good fit to multi-modal distributions,
428         * too large a value makes the state space to explore unnecessarily large.
429         * <p>
430         * There is no point in having more intervals than distinct component values
431         * (from which this is driven), indeed there should be many fewer.
432         * <p>
433         * A power of two may help speed up some computations.
434         */
435        private static final int MAPPING_INTERVALS = (1 << 4);
436    
437        /**Default base score mapping output value; never null.
438         * Because there are many parameters based on this, with this name as a prefix,
439         * we keep the name as short as possible.
440         * <p>
441         * We ensure that this prefix uniquely distinguishes this from all other parameters
442         * to give us the maximum possible choice of suffixes.
443         * <p>
444         * The perturbation value allows a reasonable state-space exploration speed.
445         * <p>
446         * There is no bias: there is no preferred/cheap perturbation direction.
447         */
448        private static final ScorerParamInteger scoreMappingParamBoundsBase =
449            ScorerParamInteger.createScorerParamInteger(-ScoreAndConf.MAX, 0, +ScoreAndConf.MAX, ScoreAndConf.MAX/64, false, "S");
450    
451        /**Immutable List of default mapping factors for the score from least to highest input values; never null.
452         * Always MAPPING_INTERVALS+1 entries.
453         */
454        private static final java.util.List<ScorerParamInteger> scoreMappingParamsBounds;
455        /**Initialise scoreMappingParamsBounds. */
456        static
457            {
458            final ArrayList<ScorerParamInteger> l = new ArrayList<ScorerParamInteger>(MAPPING_INTERVALS+1);
459            for(int i = 0; i <= MAPPING_INTERVALS; ++i)
460                {
461                // Set the values to run from 0 to MAX.
462                final int value = (scoreMappingParamBoundsBase.max * i) / MAPPING_INTERVALS;
463                // Name the encoding of the parameter number as short as possible.
464                final String n = Integer.toString(i, Character.MAX_RADIX);
465                l.add(ScorerParamInteger.createScorerParamInteger(scoreMappingParamBoundsBase.min, scoreMappingParamBoundsBase.def, scoreMappingParamBoundsBase.max, scoreMappingParamBoundsBase.delta, scoreMappingParamBoundsBase.biasedLow, scoreMappingParamBoundsBase.name + n, value));
466                }
467            // Make result immutable for safety/sanity!
468            scoreMappingParamsBounds = Collections.<ScorerParamInteger>unmodifiableList(l);
469            }
470    
471        /**Mapping factors for the score from least to highest input values; never null.
472         * Always MAPPING_INTERVALS+1 entries.
473         */
474        private final java.util.List<ScorerParamInteger> scoreMappingParams;
475    
476        /**Default base confidence mapping output value; never null.
477         * Because there are many parameters based on this, with this name as a prefix,
478         * we keep the name as short as possible.
479         * <p>
480         * We ensure that this prefix uniquely distinguishes this from all other parameters
481         * to give us the maximum possible choice of suffixes.
482         * <p>
483         * The perturbation value allows a reasonable state-space exploration speed.
484         * <p>
485         * There is no bias: there is no preferred/cheap perturbation direction.
486         */
487        private static final ScorerParamInteger confidenceMappingParamBoundsBase =
488            ScorerParamInteger.createScorerParamInteger(0, 0, +ScoreAndConf.MAX, ScoreAndConf.MAX/128, false, "C");
489    
490        /**Immutable List of default mapping factors for the confidence from least to highest input values; never null.
491         * Always MAPPING_INTERVALS+1 entries.
492         */
493        private static final java.util.List<ScorerParamInteger> confidenceMappingParamsBounds;
494        /**Initialise confidenceMappingParamsBounds. */
495        static
496            {
497            final ArrayList<ScorerParamInteger> l = new ArrayList<ScorerParamInteger>(MAPPING_INTERVALS+1);
498            for(int i = 0; i <= MAPPING_INTERVALS; ++i)
499                {
500                // Set the values to run from 0 to MAX.
501                final int value = (confidenceMappingParamBoundsBase.max * i) / MAPPING_INTERVALS;
502                // Name the encoding of the parameter number as short as possible.
503                final String n = Integer.toString(i, Character.MAX_RADIX);
504                l.add(ScorerParamInteger.createScorerParamInteger(confidenceMappingParamBoundsBase.min, confidenceMappingParamBoundsBase.def, confidenceMappingParamBoundsBase.max, confidenceMappingParamBoundsBase.delta, confidenceMappingParamBoundsBase.biasedLow, confidenceMappingParamBoundsBase.name + n, value));
505                }
506            // Make result immutable for safety/sanity!
507            confidenceMappingParamsBounds = Collections.<ScorerParamInteger>unmodifiableList(l);
508            }
509    
510        /**Mapping factors for the score from least to highest input values; never null.
511         * Always MAPPING_INTERVALS+1 entries.
512         */
513        private final java.util.List<ScorerParamInteger> confidenceMappingParams;
514    
515        /**Default pixel sample method; never null. */
516        private static final ScorerParamEnum<SampleType> pixelSampleMethodParamBounds =
517            ScorerParamEnum.createScorerParamEnum(SampleType.VALUE, "pixelSampleMethod");
518    
519        /**Pixel sample method; never null. */
520        private final ScorerParamEnum<SampleType> pixelSampleMethodParam;
521    
522        /**Default sample combination method; never null. */
523        private static final ScorerParamEnum<SampleType> sampleCombinationParamBounds =
524            ScorerParamEnum.createScorerParamEnum(SampleType.RANGE, "sampleCombination");
525    
526        /**Pixel-value constituent to sample; never null. */
527        private final ScorerParamEnum<SampleType> sampleCombinationParam;
528    
529        /**Default pixel-value constituent to sample; never null. */
530        private static final ScorerParamEnum<PixelComponent> componentParamBounds =
531            ScorerParamEnum.createScorerParamEnum(PixelComponent.Y, "component");
532    
533        /**Pixel-value constituent to sample; never null. */
534        private final ScorerParamEnum<PixelComponent> componentParam;
535    
536        /**Min, default and max shift (power-of-two) for the target sample size; never null.
537         * This has a bias to lower values since this results in less work done.
538         */
539        private static final ScorerParamInteger sampleSizeParamBounds =
540            ScorerParamInteger.createScorerParamInteger(2, 7, 12, 1, true, "sampleSizePower");
541    
542        /**Target sample size for this instance; never null.
543         * Target number of sample points; strictly positive.
544         * If fewer sample points than this are available,
545         * then the result's confidence will drop
546         * in accordance with expected sample-variance noise reduction.
547         * <p>
548         * Reducing the sample size by a factor of x is assumed to increase the noise,
549         * and thus reduce the confidence, by a factor of sqrt(x).
550         * <p>
551         * Experience suggests that upwards of a thousand samples gives good consistency.
552         */
553        private final ScorerParamInteger sampleSizeParam;
554    
555        /**Min, default and max shift for the outlier fraction to remove; never null.
556         * Shift to get fraction/portion of the samples that are discarded from either end as potential noise; 3 or greater.
557         * A larger value discards fewer samples/values,
558         * but makes the measurement more sensitive to noise/grain and small highlights (etc).
559         * We always trim at least one outlier at each end.
560         * <p>
561         * A good shift value may line in the region of 3 to 7
562         * thus discarding ~13% to ~1% of the outliers from either extreme.
563         */
564        private static final ScorerParamInteger outlierParamBounds =
565            ScorerParamInteger.createScorerParamInteger(2, 6, 10, "outlierFractionPower");
566    
567        /**Outlier parameter value for this instance; never null. */
568        private final ScorerParamInteger outlierParam;
569    
570        /**Get ordered parameter definitions and values (immutable) for this Scorer; never null. */
571        @Override
572        public List<ScorerParam> getParameterDefsAndValues()
573            {
574            final ArrayList<ScorerParam> result = new ArrayList<ScorerParam>(5 + scoreMappingParams.size() + confidenceMappingParams.size());
575            result.addAll(Arrays.asList(new ScorerParam[] {
576                    sampleSizeParam, outlierParam, componentParam, sampleCombinationParam, pixelSampleMethodParam,
577                }));
578            result.addAll(scoreMappingParams);
579            result.addAll(confidenceMappingParams);
580            result.trimToSize();
581            return(Collections.unmodifiableList(result));
582            }
583        }