0

Hi see a few similar questions out there for this but I can't seem to find the bug here. I am experiencing a reference error in my javascript file which cannot find my init function (which is an async function) even though I can see it's there. The script has no trouble accessing the onResults() function so I know it can find the js file, but I cannot for the life of me figure out why my html file cannot find the init function from the js file. Here is the exact error message I'm receiving:

Uncaught ReferenceError: init is not defined
    at HTMLButtonElement.onclick (media-pipe-test:25:44)

Line 25 is this one (full code in the next block):

<button type="button" onclick="init()">Start</button>

Here is my media-pipe-test.html script:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.3.1/dist/tf.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@teachablemachine/pose@0.8/dist/teachablemachine-pose.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js" crossorigin="anonymous"></script>

  <script type="module" src="static/js/reactive-facemesh.js"></script>
</head>

<body>
    <div>Teachable Machine Pose Model</div>
    <button type="button" onclick="init()">Start</button>
    <div><canvas id="canvas"></canvas></div>
    <div id="label-container"></div>

    <!-- TEST -->
    <p id="test">Nothing</p>
    <!-- END TEST -->
    
  <div class="container">
    <video class="input_video"></video>
    <canvas class="output_canvas" width="1280px" height="720px"></canvas>
  </div>
</body>
</html>

here is the static/js/reactive-facemesh.js file:

// the link to your model provided by Teachable Machine export panel
const URL = "https://teachablemachine.withgoogle.com/models/CvlC5wCam/"; //"./my_model/";
let model, webcam, ctx, labelContainer, maxPredictions;

const videoElement = document.getElementsByClassName('input_video')[0];
const canvasElement = document.getElementsByClassName('output_canvas')[0];
const canvasCtx = canvasElement.getContext('2d');

function onResults(results) {
  canvasCtx.save();
  canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
  canvasCtx.drawImage(
      results.image, 0, 0, canvasElement.width, canvasElement.height);
  if (results.multiFaceLandmarks) {
    for (const landmarks of results.multiFaceLandmarks) {
      drawConnectors(canvasCtx, landmarks, FACEMESH_TESSELATION,
                     {color: '#C0C0C070', lineWidth: 1});
      drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_EYE, {color: '#FF3030'});
      drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_EYEBROW, {color: '#FF3030'});
      drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_IRIS, {color: '#FF3030'});
      drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_EYE, {color: '#30FF30'});
      drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_EYEBROW, {color: '#30FF30'});
      drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_IRIS, {color: '#30FF30'});
      drawConnectors(canvasCtx, landmarks, FACEMESH_FACE_OVAL, {color: '#E0E0E0'});
      drawConnectors(canvasCtx, landmarks, FACEMESH_LIPS, {color: '#E0E0E0'});
    }
  }
  canvasCtx.restore();
}

const faceMesh = new FaceMesh({locateFile: (file) => {
  return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`;
}});
faceMesh.setOptions({
  maxNumFaces: 1,
  refineLandmarks: true,
  minDetectionConfidence: 0.5,
  minTrackingConfidence: 0.5
});
faceMesh.onResults(onResults);

const camera = new Camera(videoElement, {
  onFrame: async () => {
    await faceMesh.send({image: videoElement});
  },
  width: 1280,
  height: 720
});
camera.start();



// //  Teachable Machine
async function init() {
    const modelURL = URL + "model.json";
    const metadataURL = URL + "metadata.json";

    // load the model and metadata
    // Refer to tmImage.loadFromFiles() in the API to support files from a file picker
    // Note: the pose library adds a tmPose object to your window (window.tmPose)
    model = await tmPose.load(modelURL, metadataURL);
    maxPredictions = model.getTotalClasses();

    // Convenience function to setup a webcam
    // const size = 8000;
    // const width = window.innerWidth; // set window size automatically to users full width and height
    // const height = window.innerHeight;
    const width = 1280;
    const height = 720;
    const flip = true; // whether to flip the webcam
    // webcam = new tmPose.Webcam(size, size, flip); // width, height, flip
    webcam = new tmPose.Webcam(width, height, flip); // width, height, flip
    await webcam.setup(); // request access to the webcam
    await webcam.play();
    window.requestAnimationFrame(loop);

    // append/get elements to the DOM
    const canvas = document.getElementById("canvas");
    // const canvas = document.getElementById("videoElement1"); // Change by EC to get the video element instead of canvas element
    // canvas.width = size; 
    // canvas.height = size;
    canvas.width = width; 
    canvas.height = height;
    ctx = canvas.getContext("2d");
    labelContainer = document.getElementById("label-container");

    for (let i = 0; i < maxPredictions; i++) { // and class labels
        labelContainer.appendChild(document.createElement("div"));
    }
}

async function loop(timestamp) {
    webcam.update(); // update the webcam frame
    await predict();
    window.requestAnimationFrame(loop);
}

async function predict() {
    // Prediction #1: run input through posenet
    // estimatePose can take in an image, video or canvas html element
    const { pose, posenetOutput } = await model.estimatePose(webcam.canvas);
    // Prediction 2: run input through teachable machine classification model
    const prediction = await model.predict(posenetOutput);

    for (let i = 0; i < maxPredictions; i++) {
        const classPrediction =
            prediction[i].className + ": " + prediction[i].probability.toFixed(2);
        labelContainer.childNodes[i].innerHTML = classPrediction;

        // Test to see if I can produce something on front if certian value is true
        if  (prediction[i].className.includes("arms") && prediction[i].probability > .98) {
            document.getElementById("test").innerHTML = "It's Arms in a V!";
        }
            // document.getElementById("test").innerHTML = classPrediction2;
                // Test to see if I can produce something on front if certian value is true
        if  (prediction[i].className.includes("base") && prediction[i].probability > .98) {
            document.getElementById("test").innerHTML = "Base Class!!!!";
        }
    }

    // finally draw the poses
    drawPose(pose);
}

function drawPose(pose) {
    if (webcam.canvas) {
        ctx.drawImage(webcam.canvas, 0, 0);
        // draw the keypoints and skeleton
        if (pose) {
            const minPartConfidence = 0.5;
            tmPose.drawKeypoints(pose.keypoints, minPartConfidence, ctx);
            tmPose.drawSkeleton(pose.keypoints, minPartConfidence, ctx);
        }
    }
}

I'm using Flask to render the website but not sure if that's relevant here. Thank you for the help!

Statmonger
  • 415
  • 6
  • 14
  • See [here](https://stackoverflow.com/a/59539045). Variables in modules don't become global, and inline handlers may only reference global variables. `init` is not a global variable, so it can't be referenced from an inline handler. Best approach: ditch inline handlers entirely and attach a listener properly using JavaScript instead – CertainPerformance Oct 05 '22 at 14:31
  • Thanks @CertainPerformance. But can you recommend an alternative solution to allow me to maintain the current structure of the script please? – Statmonger Oct 17 '22 at 14:12
  • All you need to do is remove the (very bad practice) inline handler and attach the event listener with `.addEventListener` - it shouldn't require any restructuring of anything else, just a couple lines to change – CertainPerformance Oct 17 '22 at 20:02

0 Answers0