4

I am using ml5 to classify images in a machine learning project. The program takes images path and type from a json file and then it adds each image to the ml5 classifier, which is then used to train machine learning model.

When I try to load only one image into the classifier, it works normally, (however it won't create a model because minimum of 2 are required for it to train). But when I try to add these images to the classifier in a for loop, it gives me errors.

The files are loaded by the load_data() function which is triggered by a button. load_data() calls load_imgs(label, nimgs) where label is the folder and nimgs is the number of images in it. load_imgs(label, nimgs) then loops over nimgs and takes the path of the image specified in data(json file), makes an image element out of it and then adds it to the classifier.

These functions are defined as:

    async function load_imgs(label, nimgs) {

        const forLoop = async _ => {
            for (let i = 0; i < nimgs; i++) {
                const imageData = await data.children[label].children[i]
                const image = document.createElement("img")
                image.src = imageData.path
                const type = await imageData.type;
                await classifier.addImage(image, type, (res) => {
                    console.log("image added to classifier", image.height);
                    console.log(image);

                })

            }
        }
        await forLoop()
    }

    function load_data() {
        (async() => {
            try {
                await load_imgs(0, googleImages);
                await load_imgs(1, amazonImages);
                await load_imgs(2, paypalImages);
                await load_imgs(3, facebookImages);
                await load_imgs(4, dropboxImages);
                console.log("images added");

            } catch (error) {
                console.log(error);
            }
        })();

    }

Log generated for this code is:

    index-train.html:106 Live reload enabled.
    train.js:84 json loaded
    train.js:69 Model loaded
    train.js:38 image added to classifier 0
    train.js:39 <img src=​"all_data/​google/​google_85.png">​
    train.js:107 Error: Requested texture size [0x0] is invalid.
        at re (tf-core.esm.js:17)
        at Wi (tf-core.esm.js:17)
        at Gi (tf-core.esm.js:17)
        at t.createUnsignedBytesMatrixTexture (tf-core.esm.js:17)
        at t.acquireTexture (tf-core.esm.js:17)
        at t.acquireTexture (tf-core.esm.js:17)
        at t.uploadToGPU (tf-core.esm.js:17)
        at t.getTexture (tf-core.esm.js:17)
        at t.fromPixels (tf-core.esm.js:17)
        at t.fromPixels (tf-core.esm.js:17)

I expect this to print the actual size of the image in the function callback of classifier.addImagebut instead, it is getting a 0x0 texture

  • `await data.children[label].children[i]` <- what's this? – zerkms Jul 17 '19 at 09:20
  • data is a json which is loaded into an object. the `label` indicates index of the folder in which order, it is defined in the json. `i` represents the string that contains the local path of the image file – Akhilesh Sharma Jul 17 '19 at 09:22
  • Add an `image.onload` listener.`image.src = imageData.path` does not mean your image has been loaded. – lx1412 Jul 17 '19 at 09:29
  • [this question](https://codereview.stackexchange.com/questions/128587/check-if-images-are-loaded-es6-promises) might help – A. M. Jul 17 '19 at 09:50
  • @lx1412 Thank you all this was like a splinter in my finger. Thank you so much – Akhilesh Sharma Jul 17 '19 at 09:56

3 Answers3

2

What you suggest is a declared bad practice.

Is not a good practice to "put awaits inside loops".

Theorics are that you must not to await async operations in every iteration of the loop. Instead you must declare all async operations, then await.

This example from ES Lint I think fills your scenario.

Examples of correct code for this rule:

async function foo(things) {
  const results = [];
  for (const thing of things) {
    // Good: all asynchronous operations are immediately started.
    results.push(bar(thing));
  }
  // Now that all the asynchronous operations are running, here we wait until they all complete.
  return baz(await Promise.all(results));
}

Examples of incorrect code for this rule:

async function foo(things) {
  const results = [];
  for (const thing of things) {
    // Bad: each loop iteration is delayed until the entire asynchronous operation completes
    results.push(await bar(thing));
  }
  return baz(results);
}
Sam
  • 1,459
  • 1
  • 18
  • 32
1

Reading image.height immediately after setting image.src results in getting 0 as height. After setting src you should wait for the image to load. There's no promise to await for loading the image. Instead it does have a callback called onload:

let img = new Image();
img.onload = function() { console.log("Height: " + this.height); }
img.src = "...";
frogatto
  • 28,539
  • 11
  • 83
  • 129
1

Try to use the map function instead of for loop, convert nimgs into array and use map function like this:

async function load_imgs(label, nimgs) {

    await Promise.all(nimgs.map(async (i) => {
        const imageData = await data.children[label].children[i]
        const image = document.createElement("img")
        image.src = imageData.path
        const type = await imageData.type;
        await classifier.addImage(image, type, (res) => {
                console.log("image added to classifier", image.height);
                console.log(image);

        })
    }))
}

See this explanation: Using async/await with a forEach loop

See this article fro more infos: https://lavrton.com/javascript-loops-how-to-handle-async-await-6252dd3c795/