package com.google.zxing.oned;\r
\r
import com.google.zxing.BarcodeFormat;\r
-import com.google.zxing.ReaderException;\r
+import com.google.zxing.DecodeHintType;\r
+import com.google.zxing.FormatException;\r
+import com.google.zxing.NotFoundException;\r
import com.google.zxing.Result;\r
import com.google.zxing.ResultPoint;\r
import com.google.zxing.common.BitArray;\r
-import com.google.zxing.common.GenericResultPoint;\r
\r
import java.util.Hashtable;\r
\r
/**\r
- *<p>\r
- * Implements decoding of the ITF format.\r
- * </p>\r
- *<p>\r
- * "ITF" stands for Interleaved Two of Five. This Reader will scan ITF barcode with 6, 10 or 14 digits. \r
- * The checksum is optional and is not applied by this Reader. The consumer of the decoded value will have to apply a checksum if\r
- * required.\r
- * </p>\r
- * \r
- *<p>\r
- * <a\r
- * href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.\r
- * org/wiki/Interleaved_2_of_5</a> is a great reference for Interleaved 2 of 5\r
- * information.\r
- * </p>\r
- * \r
+ * <p>Implements decoding of the ITF format.</p>\r
+ *\r
+ * <p>"ITF" stands for Interleaved Two of Five. This Reader will scan ITF barcode with 6, 10 or 14\r
+ * digits. The checksum is optional and is not applied by this Reader. The consumer of the decoded\r
+ * value will have to apply a checksum if required.</p>\r
+ *\r
+ * <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>\r
+ * is a great reference for Interleaved 2 of 5 information.</p>\r
+ *\r
* @author kevin.osullivan@sita.aero, SITA Lab.\r
*/\r
-public class ITFReader extends AbstractOneDReader {\r
-\r
- private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
- private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
-\r
- private static final int W = 3; // Pixel width of a wide line\r
- private static final int N = 1; // Pixed width of a narrow line\r
-\r
- // Stores the actual narrow line width of the image being decoded.\r
- private int narrowLineWidth = -1;\r
-\r
- /**\r
- * Start/end guard pattern.\r
- * \r
- * Note: The end pattern is reversed because the row is reversed before\r
- * searching for the END_PATTERN\r
- */\r
- private static final int[] START_PATTERN = { N, N, N, N };\r
- private static final int[] END_PATTERN_REVERSED = { N, N, W };\r
-\r
- /**\r
- * Patterns of Wide / Narrow lines to indicate each digit\r
- */\r
- static final int[][] PATTERNS = { { N, N, W, W, N }, // 0\r
- { W, N, N, N, W }, // 1\r
- { N, W, N, N, W }, // 2\r
- { W, W, N, N, N }, // 3\r
- { N, N, W, N, W }, // 4\r
- { W, N, W, N, N }, // 5\r
- { N, W, W, N, N }, // 6\r
- { N, N, N, W, W }, // 7\r
- { W, N, N, W, N }, // 8\r
- { N, W, N, W, N } // 9\r
- };\r
-\r
- public final Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {\r
-\r
- StringBuffer result = new StringBuffer(20);\r
-\r
- /**\r
- * Find out where the Middle section (payload) starts & ends\r
- */\r
- int[] startRange = decodeStart(row);\r
- int[] endRange = decodeEnd(row);\r
-\r
- decodeMiddle(row, startRange[1], endRange[0], result);\r
-\r
- String resultString = result.toString();\r
- /**\r
- * To avoid false positives with 2D barcodes (and other patterns), make\r
- * an assumption that the decoded string must be 6, 10 or 14 digits.\r
- */\r
- if ((resultString.length() != 6 && resultString.length() != 10 && resultString.length() != 14) || \r
- resultString.length() % 2 == 1)\r
- throw ReaderException.getInstance();\r
-\r
- return new Result(resultString,\r
- null, // no natural byte representation for these barcodes\r
- new ResultPoint[] { new GenericResultPoint(startRange[1], (float) rowNumber), new GenericResultPoint(startRange[0], (float) rowNumber) },\r
- BarcodeFormat.ITF);\r
- }\r
-\r
- /**\r
- * @param row\r
- * row of black/white values to search\r
- * @param payloadStart\r
- * offset of start pattern\r
- * @param resultString\r
- * {@link StringBuffer} to append decoded chars to\r
- * @throws ReaderException\r
- * if decoding could not complete successfully\r
- */\r
- protected void decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuffer resultString) throws ReaderException {\r
-\r
- // Digits are interleaved in pairs - 5 black lines for one digit, and the\r
- // 5\r
- // interleaved white lines for the second digit.\r
- // Therefore, need to scan 10 lines and then\r
- // split these into two arrays\r
- int[] counterDigitPair = new int[10];\r
- int[] counterBlack = new int[5];\r
- int[] counterWhite = new int[5];\r
-\r
- for (int x = 0; payloadStart < payloadEnd; x++) {\r
-\r
- // Get 10 runs of black/white.\r
- recordPattern(row, payloadStart, counterDigitPair);\r
- // Split them into each array\r
- for (int k = 0; k < 5; k++) {\r
- counterBlack[k] = counterDigitPair[k * 2];\r
- counterWhite[k] = counterDigitPair[(k * 2) + 1];\r
- }\r
-\r
- int bestMatch = decodeDigit(counterBlack);\r
- resultString.append((char) ('0' + bestMatch % 10));\r
- bestMatch = decodeDigit(counterWhite);\r
- resultString.append((char) ('0' + bestMatch % 10));\r
-\r
- for (int i = 0; i < counterDigitPair.length; i++) {\r
- payloadStart += counterDigitPair[i];\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * Identify where the start of the middle / payload section starts.\r
- * \r
- * @param row\r
- * row of black/white values to search\r
- * @return Array, containing index of start of 'start block' and end of\r
- * 'start block'\r
- * @throws ReaderException\r
- */\r
- int[] decodeStart(BitArray row) throws ReaderException {\r
- int endStart = skipWhiteSpace(row);\r
- int startPattern[] = findGuardPattern(row, endStart, START_PATTERN);\r
-\r
- /**\r
- * Determine the width of a narrow line in pixels. We can do this by\r
- * getting the width of the start pattern and dividing by 4 because its\r
- * made up of 4 narrow lines.\r
- */\r
- this.narrowLineWidth = (startPattern[1] - startPattern[0]) / 4;\r
-\r
- validateQuietZone(row, startPattern[0]);\r
-\r
- return startPattern;\r
- }\r
-\r
- /**\r
- * \r
- * The start & end patterns must be pre/post fixed by a quiet zone. This\r
- * zone must be at least 10 times the width of a narrow line. Scan back until\r
- * we either get to the start of the barcode or match the necessary number of \r
- * quiet zone pixels.\r
- * \r
- * Note: Its assumed the row is reversed when using this method to find\r
- * quiet zone after the end pattern.\r
- * \r
- * ref: http://www.barcode-1.net/i25code.html\r
- * \r
- * @param row - The bit array representing the scanned barcode. \r
- * @param startPattern - The index into row of the start or end pattern.\r
- * @throws ReaderException - If the quiet zone cannot be found, a ReaderException is thrown.\r
- */\r
- private void validateQuietZone(BitArray row, int startPattern) throws ReaderException {\r
-\r
- int quietCount=this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone\r
- \r
- int i=0;\r
- for (i=startPattern-1; quietCount>0 && i>=0; i--)\r
- {\r
- if (row.get(i)==true)\r
- break;\r
- quietCount--;\r
- }\r
- if (quietCount!=0)\r
- {\r
- // Unable to find the necessary number of quiet zone pixels.\r
- throw ReaderException.getInstance();\r
- }\r
- }\r
-\r
- /**\r
- * Skip all whitespace until we get to the first black line.\r
- * \r
- * @param row\r
- * row of black/white values to search\r
- * @return index of the first black line.\r
- * @throws ReaderException\r
- * Throws exception if no black lines are found in the row\r
- */\r
- private int skipWhiteSpace(BitArray row) throws ReaderException {\r
- int width = row.getSize();\r
- int endStart = 0;\r
- while (endStart < width) {\r
- if (row.get(endStart)) {\r
- break;\r
- }\r
- endStart++;\r
- }\r
- if (endStart == width)\r
- throw ReaderException.getInstance();\r
-\r
- return endStart;\r
- }\r
-\r
- /**\r
- * Identify where the end of the middle / payload section ends.\r
- * \r
- * @param row\r
- * row of black/white values to search\r
- * @return Array, containing index of start of 'end block' and end of 'end\r
- * block'\r
- * @throws ReaderException\r
- */\r
-\r
- int[] decodeEnd(BitArray row) throws ReaderException {\r
-\r
- // For convenience, reverse the row and then\r
- // search from 'the start' for the end block\r
- row.reverse();\r
-\r
- int endStart = skipWhiteSpace(row);\r
- int endPattern[] = null;\r
- try {\r
- endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);\r
- } catch (ReaderException e) {\r
- // Put our row of data back the right way before throwing\r
- row.reverse();\r
- throw e;\r
- }\r
-\r
- /**\r
- * The start & end patterns must be pre/post fixed by a quiet zone. This\r
- * zone must be at least 10 times the width of a narrow line.\r
- * \r
- * ref: http://www.barcode-1.net/i25code.html\r
- */\r
- \r
- validateQuietZone(row, endPattern[0]);\r
-\r
- // Now recalc the indicies of where the 'endblock' starts & stops to\r
- // accomodate\r
- // the reversed nature of the search\r
- int temp = endPattern[0];\r
- endPattern[0] = row.getSize() - endPattern[1];\r
- endPattern[1] = row.getSize() - temp;\r
-\r
- // Put the row back the righ way.\r
- row.reverse();\r
- return endPattern;\r
- }\r
-\r
- /**\r
- * @param row\r
- * row of black/white values to search\r
- * @param rowOffset\r
- * position to start search\r
- * @param pattern\r
- * pattern of counts of number of black and white pixels that are\r
- * being searched for as a pattern\r
- * @return start/end horizontal offset of guard pattern, as an array of two\r
- * ints\r
- * @throws ReaderException\r
- * if pattern is not found\r
- * \r
- * TODO: This is very similar to implementation in AbstractUPCEANReader. Consider if they can be merged to\r
- * a single method.\r
- */\r
- int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) throws ReaderException {\r
- int patternLength = pattern.length;\r
- int[] counters = new int[patternLength];\r
- int width = row.getSize();\r
- boolean isWhite = false;\r
-\r
- int counterPosition = 0;\r
- int patternStart = rowOffset;\r
- for (int x = rowOffset; x < width; x++) {\r
- boolean pixel = row.get(x);\r
- if ((!pixel && isWhite) || (pixel && !isWhite)) {\r
- counters[counterPosition]++;\r
- } else {\r
- if (counterPosition == patternLength - 1) {\r
- if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {\r
- return new int[] { patternStart, x };\r
- }\r
- patternStart += counters[0] + counters[1];\r
- for (int y = 2; y < patternLength; y++) {\r
- counters[y - 2] = counters[y];\r
- }\r
- counters[patternLength - 2] = 0;\r
- counters[patternLength - 1] = 0;\r
- counterPosition--;\r
- } else {\r
- counterPosition++;\r
- }\r
- counters[counterPosition] = 1;\r
- isWhite = !isWhite;\r
- }\r
- }\r
- throw ReaderException.getInstance();\r
- }\r
-\r
- /**\r
- * Attempts to decode a sequence of ITF black/white lines into single\r
- * digit.\r
- * \r
- * @param counters\r
- * the counts of runs of observed black/white/black/... values\r
- * \r
- * @return The decoded digit\r
- * \r
- * @throws ReaderException\r
- * if digit cannot be decoded\r
- */\r
- static int decodeDigit(int[] counters) throws ReaderException {\r
-\r
- int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
- int bestMatch = -1;\r
- int max = PATTERNS.length;\r
- for (int i = 0; i < max; i++) {\r
- int[] pattern = PATTERNS[i];\r
- int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
- if (variance < bestVariance) {\r
- bestVariance = variance;\r
- bestMatch = i;\r
- }\r
- }\r
- if (bestMatch >= 0) {\r
- return bestMatch;\r
- } else {\r
- throw ReaderException.getInstance();\r
- }\r
- }\r
-\r
-}
\ No newline at end of file
+public final class ITFReader extends OneDReader {\r
+\r
+ private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
+\r
+ private static final int W = 3; // Pixel width of a wide line\r
+ private static final int N = 1; // Pixed width of a narrow line\r
+\r
+ private static final int[] DEFAULT_ALLOWED_LENGTHS = { 6, 10, 12, 14, 44 };\r
+\r
+ // Stores the actual narrow line width of the image being decoded.\r
+ private int narrowLineWidth = -1;\r
+\r
+ /**\r
+ * Start/end guard pattern.\r
+ *\r
+ * Note: The end pattern is reversed because the row is reversed before\r
+ * searching for the END_PATTERN\r
+ */\r
+ private static final int[] START_PATTERN = {N, N, N, N};\r
+ private static final int[] END_PATTERN_REVERSED = {N, N, W};\r
+\r
+ /**\r
+ * Patterns of Wide / Narrow lines to indicate each digit\r
+ */\r
+ static final int[][] PATTERNS = {\r
+ {N, N, W, W, N}, // 0\r
+ {W, N, N, N, W}, // 1\r
+ {N, W, N, N, W}, // 2\r
+ {W, W, N, N, N}, // 3\r
+ {N, N, W, N, W}, // 4\r
+ {W, N, W, N, N}, // 5\r
+ {N, W, W, N, N}, // 6\r
+ {N, N, N, W, W}, // 7\r
+ {W, N, N, W, N}, // 8\r
+ {N, W, N, W, N} // 9\r
+ };\r
+\r
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws FormatException, NotFoundException {\r
+\r
+ // Find out where the Middle section (payload) starts & ends\r
+ int[] startRange = decodeStart(row);\r
+ int[] endRange = decodeEnd(row);\r
+\r
+ StringBuffer result = new StringBuffer(20);\r
+ decodeMiddle(row, startRange[1], endRange[0], result);\r
+ String resultString = result.toString();\r
+\r
+ int[] allowedLengths = null;\r
+ if (hints != null) {\r
+ allowedLengths = (int[]) hints.get(DecodeHintType.ALLOWED_LENGTHS);\r
+\r
+ }\r
+ if (allowedLengths == null) {\r
+ allowedLengths = DEFAULT_ALLOWED_LENGTHS;\r
+ }\r
+\r
+ // To avoid false positives with 2D barcodes (and other patterns), make\r
+ // an assumption that the decoded string must be 6, 10 or 14 digits.\r
+ int length = resultString.length();\r
+ boolean lengthOK = false;\r
+ for (int i = 0; i < allowedLengths.length; i++) {\r
+ if (length == allowedLengths[i]) {\r
+ lengthOK = true;\r
+ break;\r
+ }\r
+\r
+ }\r
+ if (!lengthOK) {\r
+ throw FormatException.getFormatInstance();\r
+ }\r
+\r
+ return new Result(\r
+ resultString,\r
+ null, // no natural byte representation for these barcodes\r
+ new ResultPoint[] { new ResultPoint(startRange[1], (float) rowNumber),\r
+ new ResultPoint(endRange[0], (float) rowNumber)},\r
+ BarcodeFormat.ITF);\r
+ }\r
+\r
+ /**\r
+ * @param row row of black/white values to search\r
+ * @param payloadStart offset of start pattern\r
+ * @param resultString {@link StringBuffer} to append decoded chars to\r
+ * @throws NotFoundException if decoding could not complete successfully\r
+ */\r
+ private static void decodeMiddle(BitArray row, int payloadStart, int payloadEnd,\r
+ StringBuffer resultString) throws NotFoundException {\r
+\r
+ // Digits are interleaved in pairs - 5 black lines for one digit, and the\r
+ // 5\r
+ // interleaved white lines for the second digit.\r
+ // Therefore, need to scan 10 lines and then\r
+ // split these into two arrays\r
+ int[] counterDigitPair = new int[10];\r
+ int[] counterBlack = new int[5];\r
+ int[] counterWhite = new int[5];\r
+\r
+ while (payloadStart < payloadEnd) {\r
+\r
+ // Get 10 runs of black/white.\r
+ recordPattern(row, payloadStart, counterDigitPair);\r
+ // Split them into each array\r
+ for (int k = 0; k < 5; k++) {\r
+ int twoK = k << 1;\r
+ counterBlack[k] = counterDigitPair[twoK];\r
+ counterWhite[k] = counterDigitPair[twoK + 1];\r
+ }\r
+\r
+ int bestMatch = decodeDigit(counterBlack);\r
+ resultString.append((char) ('0' + bestMatch));\r
+ bestMatch = decodeDigit(counterWhite);\r
+ resultString.append((char) ('0' + bestMatch));\r
+\r
+ for (int i = 0; i < counterDigitPair.length; i++) {\r
+ payloadStart += counterDigitPair[i];\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Identify where the start of the middle / payload section starts.\r
+ *\r
+ * @param row row of black/white values to search\r
+ * @return Array, containing index of start of 'start block' and end of\r
+ * 'start block'\r
+ * @throws NotFoundException\r
+ */\r
+ int[] decodeStart(BitArray row) throws NotFoundException {\r
+ int endStart = skipWhiteSpace(row);\r
+ int[] startPattern = findGuardPattern(row, endStart, START_PATTERN);\r
+\r
+ // Determine the width of a narrow line in pixels. We can do this by\r
+ // getting the width of the start pattern and dividing by 4 because its\r
+ // made up of 4 narrow lines.\r
+ this.narrowLineWidth = (startPattern[1] - startPattern[0]) >> 2;\r
+\r
+ validateQuietZone(row, startPattern[0]);\r
+\r
+ return startPattern;\r
+ }\r
+\r
+ /**\r
+ * The start & end patterns must be pre/post fixed by a quiet zone. This\r
+ * zone must be at least 10 times the width of a narrow line. Scan back until\r
+ * we either get to the start of the barcode or match the necessary number of\r
+ * quiet zone pixels.\r
+ *\r
+ * Note: Its assumed the row is reversed when using this method to find\r
+ * quiet zone after the end pattern.\r
+ *\r
+ * ref: http://www.barcode-1.net/i25code.html\r
+ *\r
+ * @param row bit array representing the scanned barcode.\r
+ * @param startPattern index into row of the start or end pattern.\r
+ * @throws NotFoundException if the quiet zone cannot be found, a ReaderException is thrown.\r
+ */\r
+ private void validateQuietZone(BitArray row, int startPattern) throws NotFoundException {\r
+\r
+ int quietCount = this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone\r
+\r
+ for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) {\r
+ if (row.get(i)) {\r
+ break;\r
+ }\r
+ quietCount--;\r
+ }\r
+ if (quietCount != 0) {\r
+ // Unable to find the necessary number of quiet zone pixels.\r
+ throw NotFoundException.getNotFoundInstance();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Skip all whitespace until we get to the first black line.\r
+ *\r
+ * @param row row of black/white values to search\r
+ * @return index of the first black line.\r
+ * @throws NotFoundException Throws exception if no black lines are found in the row\r
+ */\r
+ private static int skipWhiteSpace(BitArray row) throws NotFoundException {\r
+ int width = row.getSize();\r
+ int endStart = 0;\r
+ while (endStart < width) {\r
+ if (row.get(endStart)) {\r
+ break;\r
+ }\r
+ endStart++;\r
+ }\r
+ if (endStart == width) {\r
+ throw NotFoundException.getNotFoundInstance();\r
+ }\r
+\r
+ return endStart;\r
+ }\r
+\r
+ /**\r
+ * Identify where the end of the middle / payload section ends.\r
+ *\r
+ * @param row row of black/white values to search\r
+ * @return Array, containing index of start of 'end block' and end of 'end\r
+ * block'\r
+ * @throws NotFoundException\r
+ */\r
+\r
+ int[] decodeEnd(BitArray row) throws NotFoundException {\r
+\r
+ // For convenience, reverse the row and then\r
+ // search from 'the start' for the end block\r
+ row.reverse();\r
+ try {\r
+ int endStart = skipWhiteSpace(row);\r
+ int[] endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);\r
+\r
+ // The start & end patterns must be pre/post fixed by a quiet zone. This\r
+ // zone must be at least 10 times the width of a narrow line.\r
+ // ref: http://www.barcode-1.net/i25code.html\r
+ validateQuietZone(row, endPattern[0]);\r
+\r
+ // Now recalculate the indices of where the 'endblock' starts & stops to\r
+ // accommodate\r
+ // the reversed nature of the search\r
+ int temp = endPattern[0];\r
+ endPattern[0] = row.getSize() - endPattern[1];\r
+ endPattern[1] = row.getSize() - temp;\r
+\r
+ return endPattern;\r
+ } finally {\r
+ // Put the row back the right way.\r
+ row.reverse();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @param row row of black/white values to search\r
+ * @param rowOffset position to start search\r
+ * @param pattern pattern of counts of number of black and white pixels that are\r
+ * being searched for as a pattern\r
+ * @return start/end horizontal offset of guard pattern, as an array of two\r
+ * ints\r
+ * @throws NotFoundException if pattern is not found\r
+ */\r
+ private static int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) throws NotFoundException {\r
+\r
+ // TODO: This is very similar to implementation in UPCEANReader. Consider if they can be\r
+ // merged to a single method.\r
+ int patternLength = pattern.length;\r
+ int[] counters = new int[patternLength];\r
+ int width = row.getSize();\r
+ boolean isWhite = false;\r
+\r
+ int counterPosition = 0;\r
+ int patternStart = rowOffset;\r
+ for (int x = rowOffset; x < width; x++) {\r
+ boolean pixel = row.get(x);\r
+ if (pixel ^ isWhite) {\r
+ counters[counterPosition]++;\r
+ } else {\r
+ if (counterPosition == patternLength - 1) {\r
+ if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {\r
+ return new int[]{patternStart, x};\r
+ }\r
+ patternStart += counters[0] + counters[1];\r
+ for (int y = 2; y < patternLength; y++) {\r
+ counters[y - 2] = counters[y];\r
+ }\r
+ counters[patternLength - 2] = 0;\r
+ counters[patternLength - 1] = 0;\r
+ counterPosition--;\r
+ } else {\r
+ counterPosition++;\r
+ }\r
+ counters[counterPosition] = 1;\r
+ isWhite = !isWhite;\r
+ }\r
+ }\r
+ throw NotFoundException.getNotFoundInstance();\r
+ }\r
+\r
+ /**\r
+ * Attempts to decode a sequence of ITF black/white lines into single\r
+ * digit.\r
+ *\r
+ * @param counters the counts of runs of observed black/white/black/... values\r
+ * @return The decoded digit\r
+ * @throws NotFoundException if digit cannot be decoded\r
+ */\r
+ private static int decodeDigit(int[] counters) throws NotFoundException {\r
+\r
+ int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
+ int bestMatch = -1;\r
+ int max = PATTERNS.length;\r
+ for (int i = 0; i < max; i++) {\r
+ int[] pattern = PATTERNS[i];\r
+ int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
+ if (variance < bestVariance) {\r
+ bestVariance = variance;\r
+ bestMatch = i;\r
+ }\r
+ }\r
+ if (bestMatch >= 0) {\r
+ return bestMatch;\r
+ } else {\r
+ throw NotFoundException.getNotFoundInstance();\r
+ }\r
+ }\r
+\r
+}\r