Issue 1169 PDF417 error correction ground-work. Now actually computes the syndrome to detect errors but doesn't correct. This fixes misreads but takes out 1-2 images that accidentally passed as the erasure was inconsequential.

git-svn-id: https://zxing.googlecode.com/svn/trunk@2190 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2012-02-16 13:25:28 +00:00
parent 8dce85e03c
commit 3dbad7baf6
6 changed files with 441 additions and 32 deletions

View file

@ -18,6 +18,7 @@ package com.google.zxing.pdf417;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
@ -51,13 +52,13 @@ public final class PDF417Reader implements Reader {
* @throws FormatException if a PDF417 cannot be decoded
*/
@Override
public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
public Result decode(BinaryBitmap image) throws NotFoundException, FormatException, ChecksumException {
return decode(image, null);
}
@Override
public Result decode(BinaryBitmap image,
Map<DecodeHintType,?> hints) throws NotFoundException, FormatException {
public Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, FormatException, ChecksumException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {

View file

@ -18,9 +18,9 @@ package com.google.zxing.pdf417.decoder;
import com.google.zxing.ChecksumException;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.pdf417.decoder.ec.ErrorCorrection;
/**
* <p>The main class which implements PDF417 Code decoding -- as
@ -32,11 +32,10 @@ public final class Decoder {
private static final int MAX_ERRORS = 3;
private static final int MAX_EC_CODEWORDS = 512;
//private final ReedSolomonDecoder rsDecoder;
private final ErrorCorrection errorCorrection;
public Decoder() {
// TODO MGMG
//rsDecoder = new ReedSolomonDecoder();
errorCorrection = new ErrorCorrection();
}
/**
@ -45,9 +44,8 @@ public final class Decoder {
*
* @param image booleans representing white/black PDF417 modules
* @return text and bytes encoded within the PDF417 Code
* @throws NotFoundException if the PDF417 Code cannot be decoded
*/
public DecoderResult decode(boolean[][] image) throws FormatException {
public DecoderResult decode(boolean[][] image) throws FormatException, ChecksumException {
int dimension = image.length;
BitMatrix bits = new BitMatrix(dimension);
for (int i = 0; i < dimension; i++) {
@ -68,7 +66,7 @@ public final class Decoder {
* @return text and bytes encoded within the PDF417 Code
* @throws FormatException if the PDF417 Code cannot be decoded
*/
public DecoderResult decode(BitMatrix bits) throws FormatException {
public DecoderResult decode(BitMatrix bits) throws FormatException, ChecksumException {
// Construct a parser to read the data codewords and error-correction level
BitMatrixParser parser = new BitMatrixParser(bits);
int[] codewords = parser.readCodewords();
@ -80,7 +78,7 @@ public final class Decoder {
int numECCodewords = 1 << (ecLevel + 1);
int[] erasures = parser.getErasures();
correctErrors(codewords, erasures, numECCodewords);
correctErrors(codewords, erasures.length, numECCodewords);
verifyCodewordCount(codewords, numECCodewords);
// Decode the codewords
@ -92,7 +90,6 @@ public final class Decoder {
*
* @param codewords
* @return an index to the first data codeword.
* @throws FormatException
*/
private static void verifyCodewordCount(int[] codewords, int numECCodewords) throws FormatException {
if (codewords.length < 4) {
@ -119,31 +116,20 @@ public final class Decoder {
/**
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
* correct the errors in-place using Reed-Solomon error correction.</p>
* correct the errors in-place.</p>
*
* @param codewords data and error correction codewords
* @throws ChecksumException if error correction fails
*/
private static int correctErrors(int[] codewords,
int[] erasures,
int numECCodewords) throws FormatException {
if (erasures.length > numECCodewords / 2 + MAX_ERRORS ||
private void correctErrors(int[] codewords,
int numErasures,
int numECCodewords) throws ChecksumException {
if (numErasures > numECCodewords / 2 + MAX_ERRORS ||
numECCodewords < 0 || numECCodewords > MAX_EC_CODEWORDS) {
// Too many errors or EC Codewords is corrupted
throw FormatException.getFormatInstance();
throw ChecksumException.getChecksumInstance();
}
// Try to correct the errors
// TODO enable error correction
int result = 0; // rsDecoder.correctErrors(codewords, numECCodewords);
int numErasures = erasures.length;
if (result > 0) {
numErasures -= result;
}
if (numErasures > MAX_ERRORS) {
// Still too many errors
throw FormatException.getFormatInstance();
}
return result;
errorCorrection.decode(codewords, numECCodewords);
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright 2012 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.pdf417.decoder.ec;
import com.google.zxing.ChecksumException;
/**
* <p>Incomplete implementation of PDF417 error correction. For now, only detects errors.</p>
*
* @author Sean Owen
* @see com.google.zxing.common.reedsolomon.ReedSolomonDecoder
*/
public final class ErrorCorrection {
private final ModulusGF field;
public ErrorCorrection() {
this.field = ModulusGF.PDF417_GF;
}
public void decode(int[] received, int numECCodewords) throws ChecksumException {
ModulusPoly poly = new ModulusPoly(field, received);
int[] syndromeCoefficients = new int[numECCodewords];
boolean noError = true;
for (int i = 0; i < numECCodewords; i++) {
int eval = poly.evaluateAt(field.exp(i + 1));
syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
if (eval != 0) {
noError = false;
}
}
if (!noError) {
throw ChecksumException.getChecksumInstance();
}
// TODO actually correct errors!
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright 2012 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.pdf417.decoder.ec;
/**
* <p>A field based on powers of a generator integer, modulo some modulus.</p>
*
* @author Sean Owen
* @see com.google.zxing.common.reedsolomon.GenericGF
*/
public final class ModulusGF {
public static final ModulusGF PDF417_GF = new ModulusGF(929, 3);
private final int[] expTable;
private final int[] logTable;
private final ModulusPoly zero;
private final ModulusPoly one;
private final int modulus;
public ModulusGF(int modulus, int generator) {
this.modulus = modulus;
expTable = new int[modulus];
logTable = new int[modulus];
int x = 1;
for (int i = 0; i < modulus; i++) {
expTable[i] = x;
x = (x * generator) % modulus;
}
for (int i = 0; i < modulus-1; i++) {
logTable[expTable[i]] = i;
}
// logTable[0] == 0 but this should never be used
zero = new ModulusPoly(this, new int[]{0});
one = new ModulusPoly(this, new int[]{1});
}
ModulusPoly getZero() {
return zero;
}
ModulusPoly getOne() {
return one;
}
ModulusPoly buildMonomial(int degree, int coefficient) {
if (degree < 0) {
throw new IllegalArgumentException();
}
if (coefficient == 0) {
return zero;
}
int[] coefficients = new int[degree + 1];
coefficients[0] = coefficient;
return new ModulusPoly(this, coefficients);
}
int add(int a, int b) {
return (a + b) % modulus;
}
int subtract(int a, int b) {
return (modulus + a - b) % modulus;
}
int exp(int a) {
return expTable[a];
}
int log(int a) {
if (a == 0) {
throw new IllegalArgumentException();
}
return logTable[a];
}
int inverse(int a) {
if (a == 0) {
throw new ArithmeticException();
}
return expTable[modulus - logTable[a] - 1];
}
int multiply(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
return expTable[(logTable[a] + logTable[b]) % (modulus - 1)];
}
int getSize() {
return modulus;
}
}

View file

@ -0,0 +1,260 @@
/*
* Copyright 2012 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.pdf417.decoder.ec;
/**
* @author Sean Owen
* @see com.google.zxing.common.reedsolomon.GenericGFPoly
*/
final class ModulusPoly {
private final ModulusGF field;
private final int[] coefficients;
ModulusPoly(ModulusGF field, int[] coefficients) {
if (coefficients.length == 0) {
throw new IllegalArgumentException();
}
this.field = field;
int coefficientsLength = coefficients.length;
if (coefficientsLength > 1 && coefficients[0] == 0) {
// Leading term must be non-zero for anything except the constant polynomial "0"
int firstNonZero = 1;
while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) {
firstNonZero++;
}
if (firstNonZero == coefficientsLength) {
this.coefficients = field.getZero().coefficients;
} else {
this.coefficients = new int[coefficientsLength - firstNonZero];
System.arraycopy(coefficients,
firstNonZero,
this.coefficients,
0,
this.coefficients.length);
}
} else {
this.coefficients = coefficients;
}
}
int[] getCoefficients() {
return coefficients;
}
/**
* @return degree of this polynomial
*/
int getDegree() {
return coefficients.length - 1;
}
/**
* @return true iff this polynomial is the monomial "0"
*/
boolean isZero() {
return coefficients[0] == 0;
}
/**
* @return coefficient of x^degree term in this polynomial
*/
int getCoefficient(int degree) {
return coefficients[coefficients.length - 1 - degree];
}
/**
* @return evaluation of this polynomial at a given point
*/
int evaluateAt(int a) {
if (a == 0) {
// Just return the x^0 coefficient
return getCoefficient(0);
}
int size = coefficients.length;
if (a == 1) {
// Just the sum of the coefficients
int result = 0;
for (int coefficient : coefficients) {
result = field.add(result, coefficient);
}
return result;
}
int result = coefficients[0];
for (int i = 1; i < size; i++) {
result = field.add(field.multiply(a, result), coefficients[i]);
}
return result;
}
ModulusPoly add(ModulusPoly other) {
if (!field.equals(other.field)) {
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (isZero()) {
return other;
}
if (other.isZero()) {
return this;
}
int[] smallerCoefficients = this.coefficients;
int[] largerCoefficients = other.coefficients;
if (smallerCoefficients.length > largerCoefficients.length) {
int[] temp = smallerCoefficients;
smallerCoefficients = largerCoefficients;
largerCoefficients = temp;
}
int[] sumDiff = new int[largerCoefficients.length];
int lengthDiff = largerCoefficients.length - smallerCoefficients.length;
// Copy high-order terms only found in higher-degree polynomial's coefficients
System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
for (int i = lengthDiff; i < largerCoefficients.length; i++) {
sumDiff[i] = field.add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
}
return new ModulusPoly(field, sumDiff);
}
ModulusPoly subtract(ModulusPoly other) {
if (!field.equals(other.field)) {
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (other.isZero()) {
return this;
}
return add(other.negative());
}
ModulusPoly multiply(ModulusPoly other) {
if (!field.equals(other.field)) {
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (isZero() || other.isZero()) {
return field.getZero();
}
int[] aCoefficients = this.coefficients;
int aLength = aCoefficients.length;
int[] bCoefficients = other.coefficients;
int bLength = bCoefficients.length;
int[] product = new int[aLength + bLength - 1];
for (int i = 0; i < aLength; i++) {
int aCoeff = aCoefficients[i];
for (int j = 0; j < bLength; j++) {
product[i + j] = field.add(product[i + j], field.multiply(aCoeff, bCoefficients[j]));
}
}
return new ModulusPoly(field, product);
}
ModulusPoly negative() {
int size = coefficients.length;
int[] negativeCoefficients = new int[size];
for (int i = 0; i < size; i++) {
negativeCoefficients[i] = field.subtract(0, coefficients[i]);
}
return new ModulusPoly(field, negativeCoefficients);
}
ModulusPoly multiply(int scalar) {
if (scalar == 0) {
return field.getZero();
}
if (scalar == 1) {
return this;
}
int size = coefficients.length;
int[] product = new int[size];
for (int i = 0; i < size; i++) {
product[i] = field.multiply(coefficients[i], scalar);
}
return new ModulusPoly(field, product);
}
ModulusPoly multiplyByMonomial(int degree, int coefficient) {
if (degree < 0) {
throw new IllegalArgumentException();
}
if (coefficient == 0) {
return field.getZero();
}
int size = coefficients.length;
int[] product = new int[size + degree];
for (int i = 0; i < size; i++) {
product[i] = field.multiply(coefficients[i], coefficient);
}
return new ModulusPoly(field, product);
}
ModulusPoly[] divide(ModulusPoly other) {
if (!field.equals(other.field)) {
throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (other.isZero()) {
throw new IllegalArgumentException("Divide by 0");
}
ModulusPoly quotient = field.getZero();
ModulusPoly remainder = this;
int denominatorLeadingTerm = other.getCoefficient(other.getDegree());
int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) {
int degreeDifference = remainder.getDegree() - other.getDegree();
int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm);
ModulusPoly term = other.multiplyByMonomial(degreeDifference, scale);
ModulusPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
quotient = quotient.add(iterationQuotient);
remainder = remainder.subtract(term);
}
return new ModulusPoly[] { quotient, remainder };
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(8 * getDegree());
for (int degree = getDegree(); degree >= 0; degree--) {
int coefficient = getCoefficient(degree);
if (coefficient != 0) {
if (coefficient < 0) {
result.append(" - ");
coefficient = -coefficient;
} else {
if (result.length() > 0) {
result.append(" + ");
}
}
if (degree == 0 || coefficient != 1) {
result.append(coefficient);
}
if (degree != 0) {
if (degree == 1) {
result.append('x');
} else {
result.append("x^");
result.append(degree);
}
}
}
}
return result.toString();
}
}

View file

@ -29,8 +29,8 @@ public final class PDF417BlackBox2TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox2TestCase() {
super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417);
addTest(15, 15, 3, 3, 0.0f);
addTest(14, 14, 1, 1, 180.0f);
addTest(11, 11, 0, 0, 0.0f);
addTest(13, 13, 0, 0, 180.0f);
}
}