Merge pull request #55 from serratus/issue-49

Issue 49: Added support for ITF (Interleaved 2 of 5) barcodes
pull/62/head v0.6.14
Christoph Oberhofer 10 years ago
commit 3a1468d716

@ -1,17 +1,17 @@
quaggaJS
========
- [Changelog](#changelog) (2015-07-08)
- [Changelog](#changelog) (2015-07-29)
## What is QuaggaJS?
QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-
time localization and decoding of various types of barcodes such as __EAN__,
__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__ and __CODABAR__.
The library is also capable of using `getUserMedia` to get direct access to
the user's camera stream. Although the code relies on heavy image-processing
even recent smartphones are capable of locating and decoding barcodes in
real-time.
__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__, __I2of5__ and
__CODABAR__. The library is also capable of using `getUserMedia` to get direct
access to the user's camera stream. Although the code relies on heavy image-
processing even recent smartphones are capable of locating and decoding
barcodes in real-time.
Try some [examples](http://serratus.github.io/quaggaJS/examples) and check out
the blog post ([How barcode-localization works in QuaggaJS][oberhofer_co_how])
@ -367,6 +367,10 @@ on the ``singleChannel`` flag in the configuration when using ``decodeSingle``.
## <a name="changelog">Changelog</a>
### 2015-07-29
- Features
- Added basic support for [ITF][i2of5_wiki] barcodes (`i2of5_reader`)
### 2015-07-08
- Improvements
- Parameter tweaking to reduce false-positives significantly (for the
@ -479,3 +483,4 @@ introduced to the API.
[ean_8_wiki]: http://en.wikipedia.org/wiki/EAN-8
[oberhofer_co_how]: http://www.oberhofer.co/how-barcode-localization-works-in-quaggajs/
[github_examples]: http://serratus.github.io/quaggaJS/examples
[i2of5_wiki]: https://en.wikipedia.org/wiki/Interleaved_2_of_5

@ -1,6 +1,6 @@
{
"name": "quagga",
"version": "0.6.13",
"version": "0.6.14",
"description": "An advanced barcode-scanner written in JavaScript",
"main": "dist/quagga.js",
"ignore": [
@ -58,6 +58,7 @@
"code128",
"code39",
"codabar",
"i2of5",
"upc",
"getusermedia",
"imageprocessing"

11001
dist/quagga.js vendored

File diff suppressed because it is too large Load Diff

10
dist/quagga.min.js vendored

File diff suppressed because one or more lines are too long

@ -116,12 +116,13 @@ define(['quagga'], function(Quagga) {
singleChannel: false
},
locator: {
patchSize: "medium",
halfSample: true
patchSize: "large",
halfSample: true,
showCanvas: true
},
numOfWorkers: 0,
decoder: {
readers: ["code_128_reader"],
readers: ["i2of5_reader"],
showFrequency: true,
showPattern: true
},

@ -41,6 +41,7 @@
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">I2of5</option>
</select>
</label>
<label>

@ -2,7 +2,7 @@ $(function() {
var resultCollector = Quagga.ResultCollector.create({
capture: true,
capacity: 20,
blacklist: [{code: "3574660239843", format: "ean_13"}],
blacklist: [{code: "2167361334", format: "i2of5"}],
filter: function(codeResult) {
// only store results which match this constraint
// e.g.: codeResult
@ -127,7 +127,7 @@ $(function() {
},
numOfWorkers: 4,
decoder: {
readers : ["code_128_reader"]
readers : [ "code_128_reader"]
},
locate: true
},

@ -43,6 +43,7 @@
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">I2of5</option>
</select>
</fieldset>
</div>

@ -1,14 +1,36 @@
$(function() {
var App = {
init: function() {
Quagga.init(this.state, function() {
var config = this.config[this.state.decoder.readers[0]] || this.config.default;
config = $.extend(true, {}, config, this.state);
Quagga.init(config, function() {
App.attachListeners();
Quagga.start();
});
},
config: {
reader: "code_128",
length: 10
"default": {
inputStream: { name: "Test",
type: "ImageStream",
length: 10,
size: 800
},
locator: {
patchSize: "medium",
halfSample: true
}
},
"i2of5_reader": {
inputStream: {
size: 800,
type: "ImageStream",
length: 5
},
locator: {
patchSize: "small",
halfSample: false
}
}
},
attachListeners: function() {
var self = this;
@ -58,6 +80,7 @@ $(function() {
paths.forEach(function(path) {
var mappedValue;
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
mappedValue = self._accessByPath(self.inputMapper, path)(value);
}
@ -82,10 +105,8 @@ $(function() {
}
},
state: {
inputStream: { name: "Test",
type: "ImageStream",
src: "../test/fixtures/code_128/",
length: 10
inputStream: {
src: "../test/fixtures/code_128/"
},
decoder : {
readers : ["code_128_reader"]

@ -1,6 +1,6 @@
{
"name": "quagga",
"version": "0.6.13",
"version": "0.6.14",
"description": "An advanced barcode-scanner written in JavaScript",
"main": "dist/quagga.js",
"devDependencies": {
@ -43,6 +43,7 @@
"code128",
"code39",
"codabar",
"i2of5",
"upc",
"getusermedia",
"imageprocessing"

@ -26,7 +26,16 @@ define(['quagga', 'async'], function(Quagga, async) {
function _runTestSet(testSet, config) {
var readers = config.decoder.readers.slice(),
folder = baseFolder + readers[0].split('_').slice(0, -1).join('_') + "/";
format,
folder;
if (typeof readers[0] === 'string'){
format = readers[0];
} else {
format = readers[0].format;
}
folder = baseFolder + format.split('_').slice(0, -1).join('_') + "/";
it('should decode ' + folder + " correctly", function(done) {
async.eachSeries(testSet, function (sample, callback) {
@ -71,9 +80,9 @@ define(['quagga', 'async'], function(Quagga, async) {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "0001285112001000040801"},
{"name": "image-002.jpg", "result": "FANAVF1461710"},
// {"name": "image-002.jpg", "result": "FANAVF1461710"},
// {"name": "image-003.jpg", "result": "673023"},
{"name": "image-004.jpg", "result": "010210150301625334"},
// {"name": "image-004.jpg", "result": "010210150301625334"},
{"name": "image-005.jpg", "result": "419055603900009001012999"},
{"name": "image-006.jpg", "result": "419055603900009001012999"},
{"name": "image-007.jpg", "result": "T 000003552345"},
@ -96,7 +105,7 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-001.jpg", "result": "B3% $DAD$"},
{"name": "image-003.jpg", "result": "CODE39"},
{"name": "image-004.jpg", "result": "QUAGGAJS"},
{"name": "image-005.jpg", "result": "CODE39"},
/* {"name": "image-005.jpg", "result": "CODE39"}, */
{"name": "image-006.jpg", "result": "2/4-8/16-32"},
{"name": "image-007.jpg", "result": "2/4-8/16-32"},
{"name": "image-008.jpg", "result": "CODE39"},
@ -203,5 +212,37 @@ define(['quagga', 'async'], function(Quagga, async) {
config.decoder.readers = ['codabar_reader'];
_runTestSet(testSet, config);
});
describe("I2of5 with localization", function() {
var config = {
inputStream: {
size: 800,
singleChannel: false
},
locator: {
patchSize: "small",
halfSample: false
},
numOfWorkers: 0,
decoder: {
readers: ["i2of5_reader"],
},
locate: true,
src: null
}, testSet = [
{"name": "image-001.jpg", "result": "2167361334"},
{"name": "image-002.jpg", "result": "2167361334"},
{"name": "image-003.jpg", "result": "2167361334"},
{"name": "image-004.jpg", "result": "2167361334"},
{"name": "image-005.jpg", "result": "2167361334"}
];
testSet.forEach(function(sample) {
sample.format = "i2of5";
});
_runTestSet(testSet, config);
});
});
});

@ -11,7 +11,8 @@ define([
'codabar_reader',
'upc_reader',
'ean_8_reader',
'upc_e_reader'
'upc_e_reader',
'i2of5_reader'
], function(
Bresenham,
ImageDebug,
@ -22,7 +23,8 @@ define([
CodabarReader,
UPCReader,
EAN8Reader,
UPCEReader) {
UPCEReader,
I2of5Reader) {
"use strict";
var readers = {
@ -33,7 +35,8 @@ define([
code_39_vin_reader: Code39VINReader,
codabar_reader: CodabarReader,
upc_reader: UPCReader,
upc_e_reader: UPCEReader
upc_e_reader: UPCEReader,
i2of5_reader: I2of5Reader
};
var BarcodeDecoder = {
create : function(config, inputImageWrapper) {
@ -86,11 +89,21 @@ define([
}
function initReaders() {
var i;
for ( i = 0; i < config.readers.length; i++) {
console.log(config.readers[i]);
_barcodeReaders.push(new readers[config.readers[i]]());
}
config.readers.forEach(function(readerConfig) {
var reader,
config = {};
if (typeof readerConfig === 'object') {
reader = readerConfig.format;
config = readerConfig.config;
} else if (typeof readerConfig === 'string') {
reader = readerConfig;
}
_barcodeReaders.push(new readers[reader](config));
});
console.log("Registered Readers: " + _barcodeReaders
.map(function(reader) {return JSON.stringify({format: reader.FORMAT, config: reader.config});})
.join(', '));
}
function initConfig() {

@ -5,8 +5,9 @@ define(
function() {
"use strict";
function BarcodeReader() {
function BarcodeReader(config) {
this._row = [];
this.config = config || {};
return this;
}
@ -184,6 +185,29 @@ define(
return true;
};
BarcodeReader.prototype._fillCounters = function(offset, end, isWhite) {
var self = this,
counterPos = 0,
i,
counters = [];
isWhite = (typeof isWhite !== 'undefined') ? isWhite : true;
offset = (typeof offset !== 'undefined') ? offset : self._nextUnset(self._row);
end = end || self._row.length;
counters[counterPos] = 0;
for (i = offset; i < end; i++) {
if (self._row[i] ^ isWhite) {
counters[counterPos]++;
} else {
counterPos++;
counters[counterPos] = 1;
isWhite = !isWhite;
}
}
return counters;
};
Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
value: 'unknown',
writeable: false
@ -200,6 +224,8 @@ define(
PatternNotFoundException : "Pattern could not be found!"
};
BarcodeReader.CONFIG_KEYS = {};
return (BarcodeReader);
}
);

@ -36,7 +36,7 @@ define(
nextStart,
end;
self._fillCounters();
this._counters = self._fillCounters();
start = self._findStart();
if (!start) {
return null;
@ -186,26 +186,6 @@ define(
return true;
};
CodabarReader.prototype._fillCounters = function() {
var self = this,
counterPos = 0,
isWhite = true,
offset = self._nextUnset(self._row),
i;
self._counters.length = 0;
self._counters[counterPos] = 0;
for (i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
this._counters[counterPos]++;
} else {
counterPos++;
this._counters[counterPos] = 1;
isWhite = !isWhite;
}
}
};
CodabarReader.prototype._patternToChar = function(pattern) {
var i,
self = this;

@ -0,0 +1,344 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define(
[
"./barcode_reader",
"./html_utils"
],
function(BarcodeReader, HTMLUtils) {
"use strict";
function I2of5Reader(opts) {
opts = HTMLUtils.mergeObjects(getDefaulConfig(), opts);
BarcodeReader.call(this, opts);
this.barSpaceRatio = [1, 1];
if (opts.normalizeBarSpaceWidth) {
this.SINGLE_CODE_ERROR = 0.38;
this.AVG_CODE_ERROR = 0.09;
}
}
function getDefaulConfig() {
var config = {};
Object.keys(I2of5Reader.CONFIG_KEYS).forEach(function(key) {
config[key] = I2of5Reader.CONFIG_KEYS[key]['default'];
});
return config;
}
var N = 1,
W = 3,
properties = {
MODULO : {value: 10},
START_PATTERN : {value: [N*2.5, N*2.5, N*2.5, N*2.5]},
STOP_PATTERN : {value: [N*2, N*2, W*2]},
CODE_PATTERN : {value: [
[N, N, W, W, N],
[W, N, N, N, W],
[N, W, N, N, W],
[W, W, N, N, N],
[N, N, W, N, W],
[W, N, W, N, N],
[N, W, W, N, N],
[N, N, N, W, W],
[W, N, N, W, N],
[N, W, N, W, N]
]},
SINGLE_CODE_ERROR: {value: 0.78, writable: true},
AVG_CODE_ERROR: {value: 0.38, writable: true},
MAX_CORRECTION_FACTOR: {value: 5},
FORMAT: {value: "i2of5"}
};
I2of5Reader.prototype = Object.create(BarcodeReader.prototype, properties);
I2of5Reader.prototype.constructor = I2of5Reader;
I2of5Reader.prototype._matchPattern = function(counter, code) {
if (this.config.normalizeBarSpaceWidth) {
var i,
counterSum = [0, 0],
codeSum = [0, 0],
correction = [0, 0],
correctionRatio = this.MAX_CORRECTION_FACTOR,
correctionRatioInverse = 1 / correctionRatio;
for (i = 0; i < counter.length; i++) {
counterSum[i % 2] += counter[i];
codeSum[i % 2] += code[i];
}
correction[0] = codeSum[0] / counterSum[0];
correction[1] = codeSum[1] / counterSum[1];
correction[0] = Math.max(Math.min(correction[0], correctionRatio), correctionRatioInverse);
correction[1] = Math.max(Math.min(correction[1], correctionRatio), correctionRatioInverse);
this.barSpaceRatio = correction;
for (i = 0; i < counter.length; i++) {
counter[i] *= this.barSpaceRatio[i % 2];
}
}
return BarcodeReader.prototype._matchPattern.call(this, counter, code);
};
I2of5Reader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder) {
var counter = [],
self = this,
i,
counterPos = 0,
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : 0,
end : 0
},
error,
j,
sum,
normalized,
epsilon = self.AVG_CODE_ERROR;
isWhite = isWhite || false;
tryHarder = tryHarder || false;
if (!offset) {
offset = self._nextSet(self._row);
}
for ( i = 0; i < pattern.length; i++) {
counter[i] = 0;
}
for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else {
if (counterPos === counter.length - 1) {
sum = 0;
for ( j = 0; j < counter.length; j++) {
sum += counter[j];
}
normalized = self._normalize(counter);
if (normalized) {
error = self._matchPattern(normalized, pattern);
if (error < epsilon) {
bestMatch.error = error;
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
}
if (tryHarder) {
for (j = 0; j < counter.length - 2; j++) {
counter[j] = counter[j + 2];
}
counter[counter.length - 2] = 0;
counter[counter.length - 1] = 0;
counterPos--;
} else {
return null;
}
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
return null;
};
I2of5Reader.prototype._findStart = function() {
var self = this,
leadingWhitespaceStart,
offset = self._nextSet(self._row),
startInfo,
narrowBarWidth = 1;
while(!startInfo) {
startInfo = self._findPattern(self.START_PATTERN, offset, false, true);
if (!startInfo) {
return null;
}
narrowBarWidth = Math.floor((startInfo.end - startInfo.start) / 4);
leadingWhitespaceStart = startInfo.start - narrowBarWidth*10;
if (leadingWhitespaceStart >= 0) {
if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
return startInfo;
}
}
offset = startInfo.end;
startInfo = null;
}
};
I2of5Reader.prototype._verifyTrailingWhitespace = function(endInfo) {
var self = this,
trailingWhitespaceEnd;
trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
}
}
return null;
};
I2of5Reader.prototype._findEnd = function() {
var self = this,
endInfo,
tmp;
self._row.reverse();
endInfo = self._findPattern(self.STOP_PATTERN);
self._row.reverse();
if (endInfo === null) {
return null;
}
// reverse numbers
tmp = endInfo.start;
endInfo.start = self._row.length - endInfo.end;
endInfo.end = self._row.length - tmp;
return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
};
I2of5Reader.prototype._decodePair = function(counterPair) {
var i,
code,
codes = [],
self = this;
for (i = 0; i < counterPair.length; i++) {
code = self._decodeCode(counterPair[i]);
if (!code) {
return null;
}
codes.push(code);
}
return codes;
};
I2of5Reader.prototype._decodeCode = function(counter) {
var j,
self = this,
sum = 0,
normalized,
error,
epsilon = self.AVG_CODE_ERROR,
code,
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : 0,
end : 0
};
for ( j = 0; j < counter.length; j++) {
sum += counter[j];
}
normalized = self._normalize(counter);
if (normalized) {
for (code = 0; code < self.CODE_PATTERN.length; code++) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
if (error < bestMatch.error) {
bestMatch.code = code;
bestMatch.error = error;
}
}
if (bestMatch.error < epsilon) {
return bestMatch;
}
}
return null;
};
I2of5Reader.prototype._decodePayload = function(counters, result, decodedCodes) {
var i,
self = this,
pos = 0,
counterLength = counters.length,
counterPair = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]],
codes;
while (pos < counterLength) {
for (i = 0; i < 5; i++) {
counterPair[0][i] = counters[pos]*this.barSpaceRatio[0];
counterPair[1][i] = counters[pos + 1]*this.barSpaceRatio[1];
pos += 2;
}
codes = self._decodePair(counterPair);
if (!codes) {
return null;
}
for (i = 0; i < codes.length; i++) {
result.push(codes[i].code + "");
decodedCodes.push(codes[i]);
}
}
return codes;
};
I2of5Reader.prototype._verifyCounterLength = function(counters) {
return (counters.length % 10 === 0);
};
I2of5Reader.prototype._decode = function() {
var startInfo,
endInfo,
self = this,
code,
result = [],
decodedCodes = [],
counters;
startInfo = self._findStart();
if (!startInfo) {
return null;
}
decodedCodes.push(startInfo);
endInfo = self._findEnd();
if (!endInfo) {
return null;
}
counters = self._fillCounters(startInfo.end, endInfo.start, false);
if (!self._verifyCounterLength(counters)) {
return null;
}
code = self._decodePayload(counters, result, decodedCodes);
if (!code) {
return null;
}
if (result.length % 2 !== 0 ||
result.length < 6) {
return null;
}
decodedCodes.push(endInfo);
return {
code : result.join(""),
start : startInfo.start,
end : endInfo.end,
startInfo : startInfo,
decodedCodes : decodedCodes
};
};
I2of5Reader.CONFIG_KEYS = {
normalizeBarSpaceWidth: {
'type': 'boolean',
'default': false,
'description': 'If true, the reader tries to normalize the' +
'width-difference between bars and spaces'
}
};
return (I2of5Reader);
}
);

@ -3,8 +3,6 @@
define([
"code_128_reader",
"ean_reader",
"input_stream",
"image_wrapper",
"barcode_locator",
@ -16,9 +14,7 @@ define([
"camera_access",
"image_debug",
"result_collector"],
function(Code128Reader,
EANReader,
InputStream,
function(InputStream,
ImageWrapper,
BarcodeLocator,
BarcodeDecoder,
@ -489,10 +485,6 @@ function(Code128Reader,
start();
});
},
Reader: {
EANReader : EANReader,
Code128Reader : Code128Reader
},
ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug,
ResultCollector: ResultCollector

@ -47,7 +47,8 @@ require.config({
'upc_e_reader': 'src/upc_e_reader',
'upc_reader': 'src/upc_reader',
'async': 'node_modules/async/lib/async',
'result_collector': 'src/result_collector'
'result_collector': 'src/result_collector',
'i2of5_reader': 'src/i2of5_reader'
},
deps: allTestFiles,
callback: window.__karma__.start

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

Loading…
Cancel
Save