diff --git a/examples/Bodypose-single-image/data/runner.jpg b/examples/Bodypose-single-image/data/runner.jpg new file mode 100644 index 00000000..25df5133 Binary files /dev/null and b/examples/Bodypose-single-image/data/runner.jpg differ diff --git a/examples/Bodypose-single-image/index.html b/examples/Bodypose-single-image/index.html new file mode 100644 index 00000000..9ea888c4 --- /dev/null +++ b/examples/Bodypose-single-image/index.html @@ -0,0 +1,42 @@ + + + +
+ +Loading model...
+ + + +image by Funk Dooby via Wikimedia
+ + + diff --git a/examples/Bodypose-single-image/sketch.js b/examples/Bodypose-single-image/sketch.js new file mode 100644 index 00000000..05e830c8 --- /dev/null +++ b/examples/Bodypose-single-image/sketch.js @@ -0,0 +1,111 @@ +// declare variables img, bodyPose, and poses in the global scope +// so that we can access them inside the `draw()` function + +/** @type {HTMLImageElement} - the input image */ +let img; + +/** @type {Object} the loaded ml5 model */ +let bodyPose; + +/** @type {Array} - the poses detected by the model */ +let poses; + + +// Preload assets - setup() will not run until the loading is complete. +function preload() { + // Load an image for pose detection + img = loadImage('data/runner.jpg'); + // Load the ml5 model + bodyPose = ml5.bodyPose(); +} + +function setup() { + // Draw the image to the canvas + createCanvas(img.width, img.height, document.querySelector('canvas')); + image(img, 0, 0); + // Do not need to draw on every frame + noLoop(); + // Update the status + select('#status').html('Model Loaded'); + // Detect poses in the image + bodyPose.detect(img, onPose); + // Draw again when changing checkboxes + // TODO: can use p5 function once this fix is published - https://github.com/processing/p5.js/pull/6838 + // select("form").changed(redraw); + document.querySelector('form').addEventListener('change', redraw); +} + +// Function to run when the model detects poses. +function onPose(result) { + // Update the status + select("#status").html('Pose Detected'); + // Store the poses + poses = result; + // Initiate the drawing + redraw(); +} + +// p5 draw function +function draw() { + // Need to reset the canvas by drawing the image again. + // In order to "undraw" when deselecting checkboxes. + image(img, 0, 0); + + // If there are no poses, we are done. + if (!poses) { + return; + } + + // Draw the correct layers based on the current checkboxes. + const showSkeleton = select("#skeleton").checked(); + const showKeypoints = select("#keypoints").checked(); + const showLabels = select("#labels").checked(); + const showBox = select("#box").checked(); + + // Loop through all the poses detected + poses.forEach(pose => { + // For each pose detected, loop through all body connections on the skeleton + if (showSkeleton) { + pose.skeleton?.forEach(connection => { + // Each connection is an array of two parts + const [partA, partB] = connection; + // Draw a line between the two parts + stroke(255); + strokeWeight(2); + line(partA.x, partA.y, partB.x, partB.y); + }); + } + // For each pose detected, draw the bounding box rectangle + if (showBox) { + console.log('drawing box', pose.box); + const boundingBox = pose.box; + stroke(255); + noFill(); + strokeWeight(2); + rect(boundingBox.xMin * width, boundingBox.yMin * height, boundingBox.width * width, boundingBox.height * height); + } + // For each pose detected, loop through all the keypoints + // A keypoint is an object describing a body part (like rightArm or leftShoulder) + pose.keypoints.forEach(keypoint => { + // Only draw an ellipse is the pose probability is bigger than 0.2 + if (keypoint.score > 0.2) { + if (showLabels) { + // Line from part to label + stroke(60); + strokeWeight(1); + line(keypoint.x, keypoint.y, keypoint.x + 10, keypoint.y); + // Write the name of the part + textAlign(LEFT, CENTER); + text(keypoint.name, keypoint.x + 10, keypoint.y); + } + if (showKeypoints) { + // Draw ellipse over part + fill(255); + stroke(20); + strokeWeight(4); + ellipse(round(keypoint.x), round(keypoint.y), 8, 8); + } + } + }); + }); +} diff --git a/src/BodyPose/index.js b/src/BodyPose/index.js index f8130834..92041cb6 100644 --- a/src/BodyPose/index.js +++ b/src/BodyPose/index.js @@ -48,9 +48,6 @@ class BodyPose { * @private */ constructor(modelName = "MoveNet", options, callback) { - // for compatibility with p5's preload() - if (this.p5PreLoadExists()) window._incrementPreload(); - this.modelName = modelName; this.model = null; this.config = options; @@ -128,9 +125,6 @@ class BodyPose { await tf.ready(); this.model = await poseDetection.createDetector(pipeline, modelConfig); - // for compatibility with p5's preload() - if (this.p5PreLoadExists) window._decrementPreload(); - return this; } @@ -250,20 +244,6 @@ class BodyPose { }); return result; } - - /** - * Checks if p5.js' preload() function is present. - * @returns {boolean} true if preload() exists. - * @private - */ - p5PreLoadExists() { - if (typeof window === "undefined") return false; - if (typeof window.p5 === "undefined") return false; - if (typeof window.p5.prototype === "undefined") return false; - if (typeof window.p5.prototype.registerPreloadMethod === "undefined") - return false; - return true; - } } /** diff --git a/src/BodySegmentation/index.js b/src/BodySegmentation/index.js index 43754850..ff144772 100644 --- a/src/BodySegmentation/index.js +++ b/src/BodySegmentation/index.js @@ -23,9 +23,6 @@ class BodySegmentation { * @param {function} [callback] - A callback to be called when the model is ready. */ constructor(modelName, options, callback) { - // for compatibility with p5's preload() - if (this.p5PreLoadExists()) window._incrementPreload(); - this.modelName = modelName; this.video = video; this.model = null; @@ -116,9 +113,6 @@ class BodySegmentation { modelConfig ); - // for compatibility with p5's preload() - if (this.p5PreLoadExists) window._decrementPreload(); - return this; } /** @@ -269,21 +263,6 @@ class BodySegmentation { return imageData; } } - - /** - * Check if p5.js' preload() function is present - * @returns {boolean} true if preload() exists - * - * @private - */ - p5PreLoadExists() { - if (typeof window === "undefined") return false; - if (typeof window.p5 === "undefined") return false; - if (typeof window.p5.prototype === "undefined") return false; - if (typeof window.p5.prototype.registerPreloadMethod === "undefined") - return false; - return true; - } } /** diff --git a/src/FaceMesh/index.js b/src/FaceMesh/index.js index 467fabcf..3b6e517a 100644 --- a/src/FaceMesh/index.js +++ b/src/FaceMesh/index.js @@ -35,9 +35,6 @@ class FaceMesh { * @private */ constructor(options, callback) { - // for compatibility with p5's preload() - if (this.p5PreLoadExists()) window._incrementPreload(); - this.model = null; this.config = options; this.runtimeConfig = {}; @@ -77,9 +74,6 @@ class FaceMesh { modelConfig ); - // for compatibility with p5's preload() - if (this.p5PreLoadExists) window._decrementPreload(); - return this; } @@ -246,21 +240,6 @@ class FaceMesh { } return faces; } - - /** - * Check if p5.js' preload() function is present - * @returns {boolean} true if preload() exists - * - * @private - */ - p5PreLoadExists() { - if (typeof window === "undefined") return false; - if (typeof window.p5 === "undefined") return false; - if (typeof window.p5.prototype === "undefined") return false; - if (typeof window.p5.prototype.registerPreloadMethod === "undefined") - return false; - return true; - } } /** diff --git a/src/HandPose/index.js b/src/HandPose/index.js index 388d98bf..1ce3f04b 100644 --- a/src/HandPose/index.js +++ b/src/HandPose/index.js @@ -36,9 +36,6 @@ class HandPose { * @private */ constructor(options, callback) { - // for compatibility with p5's preload() - if (this.p5PreLoadExists()) window._incrementPreload(); - this.model = null; this.config = options; this.runtimeConfig = {}; @@ -78,9 +75,6 @@ class HandPose { await tf.ready(); this.model = await handPoseDetection.createDetector(pipeline, modelConfig); - // for compatibility with p5's preload() - if (this.p5PreLoadExists) window._decrementPreload(); - return this; } @@ -203,20 +197,6 @@ class HandPose { }); return result; } - - /** - * Check if p5.js' preload() function is present in the current environment. - * @returns {boolean} True if preload() exists. False otherwise. - * @private - */ - p5PreLoadExists() { - if (typeof window === "undefined") return false; - if (typeof window.p5 === "undefined") return false; - if (typeof window.p5.prototype === "undefined") return false; - if (typeof window.p5.prototype.registerPreloadMethod === "undefined") - return false; - return true; - } } /** diff --git a/src/NeuralNetwork/index.js b/src/NeuralNetwork/index.js index 00dfd420..5b004cfb 100644 --- a/src/NeuralNetwork/index.js +++ b/src/NeuralNetwork/index.js @@ -124,7 +124,7 @@ class DiyNeuralNetwork { // will take a URL to model.json, an object, or files array this.ready = this.load(this.options.modelUrl, callback); } else { - this.ready = true; + this.ready = Promise.resolve(this); } } diff --git a/src/Sentiment/index.js b/src/Sentiment/index.js index 0f67c5bf..ee3aa922 100644 --- a/src/Sentiment/index.js +++ b/src/Sentiment/index.js @@ -51,8 +51,8 @@ class Sentiment { */ constructor(modelName, callback) { /** - * Boolean value that specifies if the model has loaded. - * @type {boolean} + * Promise that resolves when the model has loaded. + * @type {Promise