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 }