question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Exception when multiple decodeSingle is requested

See original GitHub issue

First, allow me to thank you for this high quality barcode library.

I tried to use quaggaJS in an express nodejs application. I noticed an exception could be encountered randomly when multiple request being send. I wrote the following test code snippet and am able to reproduce the problem, on another computer. (Both windows 10)

var Quagga = require('quagga').default;

for(var i=0;i<10;i++){
    setTimeout(()=>{
        Quagga.decodeSingle({
            src: "image.jpg",// The image doesn't seem matter, I used a sample image from the repo
            numOfWorkers: 0,  // Needs to be 0 when used within node
            inputStream: {
                size: 800  // restrict input-size to be 800px in width (long-side)
            },
            decoder: {
                readers: ["code_128_reader"] // List of active readers
            },
        }, function(result) {
            if(result.codeResult) {
                console.log("result", result.codeResult.code);
            } else {
                console.log("not detected");
            }
        });
    },0);    
}

I run the this snippet multiple times, sometimes an exception would happen. The following is the result from two consecutive run

C:\Development\quaggaJSTest>node index.js
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
[ 640, 480, 4 ]
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
result FANAVF14617104
result FANAVF14617104
result FANAVF14617104
result FANAVF14617104
result FANAVF14617104
result FANAVF14617104
result FANAVF14617104
result FANAVF14617104
FrameGrabber {"videoSize":[640,480],"canvasSize":[800,600],"stepSize":[0.8,0.8],"size":[800,600],"topRight":{"x":0,"y":0}}
result FANAVF14617104
result FANAVF14617104

C:\Development\quaggaJSTest>node index.js
[ 640, 480, 4 ]
[ 640, 480, 4 ]
C:\Development\quaggaJSTest\node_modules\quagga\lib\quagga.js:6620
                inputStream.setWidth(Math.floor(Math.floor(size.x / patchSize.x) * (1 / halfSample) * patchSize.x));
                                                                             ^

TypeError: Cannot read property 'x' of null
    at Object.checkImageConstraints (C:\Development\quaggaJSTest\node_modules\quagga\lib\quagga.js:6620:71)
    at canRecord (C:\Development\quaggaJSTest\node_modules\quagga\lib\quagga.js:4158:32)
    at publishEvent (C:\Development\quaggaJSTest\node_modules\quagga\lib\quagga.js:4766:30)
    at Timeout._onTimeout (C:\Development\quaggaJSTest\node_modules\quagga\lib\quagga.js:4755:18)
    at tryOnTimeout (timers.js:232:11)
    at Timer.listOnTimeout (timers.js:202:5)

C:\Development\quaggaJSTest>

Is this an issue?

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:6
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
OzzyTheGiantcommented, Dec 28, 2017

I had the same issue when running the code multiple times without the first instance finishing. For those looking for a quick fix, you may want to create some sort of queue to process barcodes one at a time. I did this by creating an array that would act as a queue and pushing image file paths to it upon requesting for a barcode scan. If the scanning process hasn’t started yet on one of the barcodes, use a for loop to scan each barcode and do what you need to do with it one at a time; otherwise, do nothing. This would be controlled by a boolean flag stating if this loop is already running in the background. Observe:

async function somefunction() {
    fileQueue.push(filePath); // fileQueue is an array
    if (!isProcessing) { // isProcessing is a boolean value
        isProcessing = true;
            while (fileQueue.length != 0) {
                await processFile(fileQueue[0]).catch(error => log.error(error)); // processFile is an async function
                fileQueue.shift();
             }
        isProcessing = false;
    }
}
1reaction
beisecommented, Nov 27, 2016

I think the error occurs, because the Quagga module override its ‘module global variables’ (I call them so, because I don’t know the right term) like _inputStream, _framegrabber, … and so on with every call to .decodeSingle(…)

I changed the code in quagga.js for testing purposes in two places (Change #1, Change#2). Now exports.default exports a function, which encapsulate the whole code including the variables and returns the object with .decodeSingle(…) in it.

//Change #1.
exports.default = function () {
    var _inputStream,
        _framegrabber,
        _stopped,
        _canvasContainer = {
        ctx: {
            image: null,
            overlay: null
        },
        dom: {
            image: null,
            overlay: null
        }
    },
        _inputImageWrapper,
        _boxSize,
        _decoder,
        _workerPool = [],
        _onUIThread = true,
        _resultCollector,
        _config = {};

    function initializeData(imageWrapper) {
        initBuffers(imageWrapper);
        _decoder = _barcode_decoder2.default.create(_config.decoder, _inputImageWrapper);
    }

    function initInputStream(cb) {
        var video;
        if (_config.inputStream.type === "VideoStream") {
            video = document.createElement("video");
            _inputStream = _input_stream2.default.createVideoStream(video);
        } else if (_config.inputStream.type === "ImageStream") {
            _inputStream = _input_stream2.default.createImageStream();
        } else if (_config.inputStream.type === "LiveStream") {
            var $viewport = getViewPort();
            if ($viewport) {
                video = $viewport.querySelector("video");
                if (!video) {
                    video = document.createElement("video");
                    $viewport.appendChild(video);
                }
            }
            _inputStream = _input_stream2.default.createLiveStream(video);
            _camera_access2.default.request(video, _config.inputStream.constraints).then(function () {
                _inputStream.trigger("canrecord");
            }).catch(function (err) {
                return cb(err);
            });
        }

        _inputStream.setAttribute("preload", "auto");
        _inputStream.setInputStream(_config.inputStream);
        _inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
    }

    function getViewPort() {
        var target = _config.inputStream.target;
        // Check if target is already a DOM element
        if (target && target.nodeName && target.nodeType === 1) {
            return target;
        } else {
            // Use '#interactive.viewport' as a fallback selector (backwards compatibility)
            var selector = typeof target === 'string' ? target : '#interactive.viewport';
            return document.querySelector(selector);
        }
    }

    function canRecord(cb) {
        _barcode_locator2.default.checkImageConstraints(_inputStream, _config.locator);
        initCanvas(_config);
        _framegrabber = _frame_grabber2.default.create(_inputStream, _canvasContainer.dom.image);

        adjustWorkerPool(_config.numOfWorkers, function () {
            if (_config.numOfWorkers === 0) {
                initializeData();
            }
            ready(cb);
        });
    }

    function ready(cb) {
        _inputStream.play();
        cb();
    }

    function initCanvas() {
        if (typeof document !== "undefined") {
            var $viewport = getViewPort();
            _canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
            if (!_canvasContainer.dom.image) {
                _canvasContainer.dom.image = document.createElement("canvas");
                _canvasContainer.dom.image.className = "imgBuffer";
                if ($viewport && _config.inputStream.type === "ImageStream") {
                    $viewport.appendChild(_canvasContainer.dom.image);
                }
            }
            _canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
            _canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
            _canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;

            _canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
            if (!_canvasContainer.dom.overlay) {
                _canvasContainer.dom.overlay = document.createElement("canvas");
                _canvasContainer.dom.overlay.className = "drawingBuffer";
                if ($viewport) {
                    $viewport.appendChild(_canvasContainer.dom.overlay);
                }
                var clearFix = document.createElement("br");
                clearFix.setAttribute("clear", "all");
                if ($viewport) {
                    $viewport.appendChild(clearFix);
                }
            }
            _canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
            _canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x;
            _canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y;
        }
    }

    function initBuffers(imageWrapper) {
        if (imageWrapper) {
            _inputImageWrapper = imageWrapper;
        } else {
            _inputImageWrapper = new _image_wrapper2.default({
                x: _inputStream.getWidth(),
                y: _inputStream.getHeight()
            });
        }

        if (false) {
            console.log(_inputImageWrapper.size);
        }
        _boxSize = [vec2.clone([0, 0]), vec2.clone([0, _inputImageWrapper.size.y]), vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]), vec2.clone([_inputImageWrapper.size.x, 0])];
        _barcode_locator2.default.init(_inputImageWrapper, _config.locator);
    }

    function getBoundingBoxes() {
        if (_config.locate) {
            return _barcode_locator2.default.locate();
        } else {
            return [[vec2.clone(_boxSize[0]), vec2.clone(_boxSize[1]), vec2.clone(_boxSize[2]), vec2.clone(_boxSize[3])]];
        }
    }

    function transformResult(result) {
        var topRight = _inputStream.getTopRight(),
            xOffset = topRight.x,
            yOffset = topRight.y,
            i;

        if (xOffset === 0 && yOffset === 0) {
            return;
        }

        if (result.barcodes) {
            for (i = 0; i < result.barcodes.length; i++) {
                transformResult(result.barcodes[i]);
            }
        }

        if (result.line && result.line.length === 2) {
            moveLine(result.line);
        }

        if (result.box) {
            moveBox(result.box);
        }

        if (result.boxes && result.boxes.length > 0) {
            for (i = 0; i < result.boxes.length; i++) {
                moveBox(result.boxes[i]);
            }
        }

        function moveBox(box) {
            var corner = box.length;

            while (corner--) {
                box[corner][0] += xOffset;
                box[corner][1] += yOffset;
            }
        }

        function moveLine(line) {
            line[0].x += xOffset;
            line[0].y += yOffset;
            line[1].x += xOffset;
            line[1].y += yOffset;
        }
    }

    function addResult(result, imageData) {
        if (!imageData || !_resultCollector) {
            return;
        }

        if (result.barcodes) {
            result.barcodes.filter(function (barcode) {
                return barcode.codeResult;
            }).forEach(function (barcode) {
                return addResult(barcode, imageData);
            });
        } else if (result.codeResult) {
            _resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
        }
    }

    function hasCodeResult(result) {
        return result && (result.barcodes ? result.barcodes.some(function (barcode) {
            return barcode.codeResult;
        }) : result.codeResult);
    }

    function publishResult(result, imageData) {
        var resultToPublish = result;

        if (result && _onUIThread) {
            transformResult(result);
            addResult(result, imageData);
            resultToPublish = result.barcodes || result;
        }

        _events2.default.publish("processed", resultToPublish);
        if (hasCodeResult(result)) {
            _events2.default.publish("detected", resultToPublish);
        }
    }

    function locateAndDecode() {
        var result, boxes;

        boxes = getBoundingBoxes();
        if (boxes) {
            result = _decoder.decodeFromBoundingBoxes(boxes);
            result = result || {};
            result.boxes = boxes;
            publishResult(result, _inputImageWrapper.data);
        } else {
            publishResult();
        }
    }

    function update() {
        var availableWorker;

        if (_onUIThread) {
            if (_workerPool.length > 0) {
                availableWorker = _workerPool.filter(function (workerThread) {
                    return !workerThread.busy;
                })[0];
                if (availableWorker) {
                    _framegrabber.attachData(availableWorker.imageData);
                } else {
                    return; // all workers are busy
                }
            } else {
                _framegrabber.attachData(_inputImageWrapper.data);
            }
            if (_framegrabber.grab()) {
                if (availableWorker) {
                    availableWorker.busy = true;
                    availableWorker.worker.postMessage({
                        cmd: 'process',
                        imageData: availableWorker.imageData
                    }, [availableWorker.imageData.buffer]);
                } else {
                    locateAndDecode();
                }
            }
        } else {
            locateAndDecode();
        }
    }

    function startContinuousUpdate() {
        var next = null,
            delay = 1000 / (_config.frequency || 60);

        _stopped = false;
        (function frame(timestamp) {
            next = next || timestamp;
            if (!_stopped) {
                if (timestamp >= next) {
                    next += delay;
                    update();
                }
                window.requestAnimFrame(frame);
            }
        })(performance.now());
    }

    function _start() {
        if (_onUIThread && _config.inputStream.type === "LiveStream") {
            startContinuousUpdate();
        } else {
            update();
        }
    }

    function initWorker(cb) {
        var blobURL,
            workerThread = {
            worker: undefined,
            imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
            busy: true
        };

        blobURL = generateWorkerBlob();
        workerThread.worker = new Worker(blobURL);

        workerThread.worker.onmessage = function (e) {
            if (e.data.event === 'initialized') {
                URL.revokeObjectURL(blobURL);
                workerThread.busy = false;
                workerThread.imageData = new Uint8Array(e.data.imageData);
                if (false) {
                    console.log("Worker initialized");
                }
                return cb(workerThread);
            } else if (e.data.event === 'processed') {
                workerThread.imageData = new Uint8Array(e.data.imageData);
                workerThread.busy = false;
                publishResult(e.data.result, workerThread.imageData);
            } else if (e.data.event === 'error') {
                if (false) {
                    console.log("Worker error: " + e.data.message);
                }
            }
        };

        workerThread.worker.postMessage({
            cmd: 'init',
            size: { x: _inputStream.getWidth(), y: _inputStream.getHeight() },
            imageData: workerThread.imageData,
            config: configForWorker(_config)
        }, [workerThread.imageData.buffer]);
    }

    function configForWorker(config) {
        return _extends({}, config, {
            inputStream: _extends({}, config.inputStream, {
                target: null
            })
        });
    }

    function workerInterface(factory) {
        /* eslint-disable no-undef*/
        if (factory) {
            var Quagga = factory().default;
            if (!Quagga) {
                self.postMessage({ 'event': 'error', message: 'Quagga could not be created' });
                return;
            }
        }
        var imageWrapper;

        self.onmessage = function (e) {
            if (e.data.cmd === 'init') {
                var config = e.data.config;
                config.numOfWorkers = 0;
                imageWrapper = new Quagga.ImageWrapper({
                    x: e.data.size.x,
                    y: e.data.size.y
                }, new Uint8Array(e.data.imageData));
                Quagga.init(config, ready, imageWrapper);
                Quagga.onProcessed(onProcessed);
            } else if (e.data.cmd === 'process') {
                imageWrapper.data = new Uint8Array(e.data.imageData);
                Quagga.start();
            } else if (e.data.cmd === 'setReaders') {
                Quagga.setReaders(e.data.readers);
            }
        };

        function onProcessed(result) {
            self.postMessage({
                'event': 'processed',
                imageData: imageWrapper.data,
                result: result
            }, [imageWrapper.data.buffer]);
        }

        function ready() {
            // eslint-disable-line
            self.postMessage({ 'event': 'initialized', imageData: imageWrapper.data }, [imageWrapper.data.buffer]);
        }

        /* eslint-enable */
    }

    function generateWorkerBlob() {
        var blob, factorySource;

        /* jshint ignore:start */
        if (typeof __factorySource__ !== 'undefined') {
            factorySource = __factorySource__; // eslint-disable-line no-undef
        }
        /* jshint ignore:end */

        blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'], { type: 'text/javascript' });

        return window.URL.createObjectURL(blob);
    }

    function _setReaders(readers) {
        if (_decoder) {
            _decoder.setReaders(readers);
        } else if (_onUIThread && _workerPool.length > 0) {
            _workerPool.forEach(function (workerThread) {
                workerThread.worker.postMessage({ cmd: 'setReaders', readers: readers });
            });
        }
    }

    function adjustWorkerPool(capacity, cb) {
        var increaseBy = capacity - _workerPool.length;
        if (increaseBy === 0) {
            return cb && cb();
        }
        if (increaseBy < 0) {
            var workersToTerminate = _workerPool.slice(increaseBy);
            workersToTerminate.forEach(function (workerThread) {
                workerThread.worker.terminate();
                if (false) {
                    console.log("Worker terminated!");
                }
            });
            _workerPool = _workerPool.slice(0, increaseBy);
            return cb && cb();
        } else {
            var workerInitialized = function workerInitialized(workerThread) {
                _workerPool.push(workerThread);
                if (_workerPool.length >= capacity) {
                    cb && cb();
                }
            };

            for (var i = 0; i < increaseBy; i++) {
                initWorker(workerInitialized);
            }
        }
    }
    //Change #2
    return {
        init: function init(config, cb, imageWrapper) {
            _config = (0, _merge3.default)({}, _config3.default, config);
            if (imageWrapper) {
                _onUIThread = false;
                initializeData(imageWrapper);
                return cb();
            } else {
                initInputStream(cb);
            }
        },
        start: function start() {
            _start();
        },
        stop: function stop() {
            _stopped = true;
            adjustWorkerPool(0);
            if (_config.inputStream.type === "LiveStream") {
                _camera_access2.default.release();
                _inputStream.clearEventHandlers();
            }
        },
        pause: function pause() {
            _stopped = true;
        },
        onDetected: function onDetected(callback) {
            _events2.default.subscribe("detected", callback);
        },
        offDetected: function offDetected(callback) {
            _events2.default.unsubscribe("detected", callback);
        },
        onProcessed: function onProcessed(callback) {
            _events2.default.subscribe("processed", callback);
        },
        offProcessed: function offProcessed(callback) {
            _events2.default.unsubscribe("processed", callback);
        },
        setReaders: function setReaders(readers) {
            _setReaders(readers);
        },
        registerResultCollector: function registerResultCollector(resultCollector) {
            if (resultCollector && typeof resultCollector.addResult === 'function') {
                _resultCollector = resultCollector;
            }
        },
        canvas: _canvasContainer,
        decodeSingle: function decodeSingle(config, resultCallback) {
            var _this = this;

            config = (0, _merge3.default)({
                inputStream: {
                    type: "ImageStream",
                    sequence: false,
                    size: 800,
                    src: config.src
                },
                numOfWorkers:  false ? 0 : 1,
                locator: {
                    halfSample: false
                }
            }, config);
            this.init(config, function () {
                _events2.default.once("processed", function (result) {
                    _this.stop();
                    resultCallback.call(null, result);
                }, true);
                _start();
            });
        },
        ImageWrapper: _image_wrapper2.default,
        ImageDebug: _image_debug2.default,
        ResultCollector: _result_collector2.default
    };
};

and use it like that:

    for(var i=0;i<10;i++){
        setTimeout(()=>{
           //Always call default() when using it
            require('quagga').default().decodeSingle({
                src: "image.jpg",// The image doesn't seem matter, I used a sample image from the repo
                numOfWorkers: 0,  // Needs to be 0 when used within node
                inputStream: {
                    size: 800  // restrict input-size to be 800px in width (long-side)
                },
                decoder: {
                    readers: ["ean_reader"] // List of active readers
                },
            }, function(result) {
                if(result.codeResult) {
                    console.log("result", result.codeResult.code);
                } else {
                    console.log("not detected");
                }
            },i--);
        },0);    
    }

Maybe some experienced javascript developer can review this. Could this be a valid solution for this problem?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Exception when multiple decodeSingle is requested
I tried to use quaggaJS in an express nodejs application. I noticed an exception could be encountered randomly when multiple request being send....
Read more >
Swift 5 Default Decododable implementation with only one ...
Is there a way to keep Swift's default implementation for a Decodable class with only Decodable objects but one exception. Unfortunately no.
Read more >
Resolved Problems for Service Pack 1 - Oracle Help Center
Exceptions were not correctly handled when viewing the JNDI tree for a Managed Server. ... When multiple JMS Servers were configured and new...
Read more >
Pipelining Review -- Mark Smotherman - Clemson University
operand forwarding paths, full network, clustered subnets, multi-cycle ... entry, multiple exits). interrupt/faults/exceptions complicate pipeline design.
Read more >
Symbol DS3408 - Product Reference Guide - Barcode Datalink
lists of required steps that are not necessarily sequential. • Sequential lists (e.g., those that ... Other parameters require scanning several bar codes....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found