<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: penguinprogramer</title>
    <description>The latest articles on DEV Community by penguinprogramer (@penguinprogramer).</description>
    <link>https://dev.to/penguinprogramer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F676590%2F5851618f-0c99-4976-ab72-09a3e5d59406.png</url>
      <title>DEV Community: penguinprogramer</title>
      <link>https://dev.to/penguinprogramer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/penguinprogramer"/>
    <language>en</language>
    <item>
      <title>Creating a workout tracker using Teachable Machine and SashiDo</title>
      <dc:creator>penguinprogramer</dc:creator>
      <pubDate>Fri, 06 Aug 2021 08:13:22 +0000</pubDate>
      <link>https://dev.to/penguinprogramer/creating-a-workout-tracker-using-teachable-machine-and-sashido-j3e</link>
      <guid>https://dev.to/penguinprogramer/creating-a-workout-tracker-using-teachable-machine-and-sashido-j3e</guid>
      <description>&lt;p&gt;I always used to think that in order to use machine learning models you had to be a genius and have PhD in mathematics, but now using transfer learning it has become easier than ever!&lt;/p&gt;

&lt;p&gt;In this tutorial, I will show you how you can get started with machine learning by creating a simple web app to count your push-ups and squats.&lt;/p&gt;

&lt;p&gt;This article is aimed at people with some experience of javascript but no experience of Machine Learning or AI. &lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt;What we will be making&lt;br&gt;
Part 1: Model Training&lt;br&gt;
Part 2: Creating the API&lt;br&gt;
A word of caution…&lt;br&gt;
Part 3: Creating the front End&lt;br&gt;
Part 4 Deployment&lt;br&gt;
Closing Thoughts&lt;/p&gt;
&lt;h2&gt;
  
  
  What we will be making
&lt;/h2&gt;

&lt;p&gt;This tutorial is based on a small workout tracker I created to count push-ups and squats. The web app is hosted on &lt;a href="https://www.sashido.io/en/" rel="noopener noreferrer"&gt;SashiDo&lt;/a&gt; and uses SashiDo’s cloud code for the Express API too. The Machine Learning model was trained using a &lt;a href="https://teachablemachine.withgoogle.com/" rel="noopener noreferrer"&gt;Teachable Machine&lt;/a&gt; model. The front end uses the svelte framework but the code should translate easily to your framework of choice -so if you are more familiar with React or Vue feel free to follow along with that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 1: Model Training
&lt;/h2&gt;

&lt;p&gt;The first step when creating a machine learning model is to decide on an architecture. This is the really challenging part which people spend years creating and refining. Luckily, using the Teachable Machine we can leverage an existing model (namely PoseNet). &lt;/p&gt;

&lt;p&gt;If you are interested in how PoseNet works under the hood I recommend &lt;a href="https://blog.tensorflow.org/2018/05/real-time-human-pose-estimation-in.html" rel="noopener noreferrer"&gt;this article&lt;/a&gt; from the TensorFlow blog.&lt;/p&gt;

&lt;p&gt;The next step is model training. This is where we teach our model to perform our task.&lt;/p&gt;

&lt;p&gt;The pose model from Teachable Machine is already pre-trained on thousands of hours of training data, which makes it ideal for general purpose pose estimation. &lt;/p&gt;

&lt;p&gt;But we don’t just want to do general purpose pose estimation, we want to count push-ups and squats. &lt;/p&gt;

&lt;p&gt;To solve this problem we can leverage something called transfer learning. The solution involves tweaking the pre-trained model so that it works for our task. We do this by giving the model a smaller amount of training examples.&lt;/p&gt;

&lt;p&gt;Although I say “smaller” don’t be fooled, if you have only a handful of training examples, you will have very lackluster performance. My model used around 1000 images per class which may seem like a lot but is really a drop in the ocean compared to the millions that PoseNet was trained on originally.&lt;/p&gt;

&lt;p&gt;Getting the training data for this task was quite easy, I simply went to YouTube and recorded videos of people doing workouts. You can also use your webcam directly, the most important thing is that you get a range of samples with as much variation in lighting and position as possible.&lt;/p&gt;

&lt;p&gt;After that I extracted the video frames using ffmpeg.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-r&lt;/span&gt; 5 putput_%04d.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line of code extracts frames from the video &lt;code&gt;input.mp4&lt;/code&gt; and saves the frames as numbered png images. I specified a frame rate of 5 frames per second as this strikes a balance between having enough data and not having frames which are too similar to each other.&lt;/p&gt;

&lt;p&gt;After that the data needs to be categorized. I split up the data into three categories: up, middle and down. Strictly speaking only up and down categories are needed but using a third category in the middle is useful to distinguish the difference between up and down more clearly.&lt;/p&gt;

&lt;p&gt;Be warned this is the most tedious part of the whole process but it is crucial if you want your model to perform well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jp8h8r7nkk24gvlbcv0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jp8h8r7nkk24gvlbcv0.png" alt="Folders containing images"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once that’s done we’re ready to train our model, head over to the &lt;a href="https://teachablemachine.withgoogle.com/train/pose" rel="noopener noreferrer"&gt;Teachable Machine website&lt;/a&gt;, create the categories, and upload your frames.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8afkzo3o51smtb91r6uz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8afkzo3o51smtb91r6uz.png" alt="Teachable Machine"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the Train Model button to commence the transfer learning using the samples you’ve added. Depending on the number of samples you’ve added and your computer’s resources this may take a while since model training happens in your browser.&lt;/p&gt;

&lt;p&gt;Once that's done click Export Model and copy the model url that’s created - we’ll need that for the next stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Creating the API
&lt;/h2&gt;

&lt;p&gt;The training of the model made use of client side processing - all of the computation was performed in your browser. Inference could be performed on the client side too however this has the disadvantage of giving users on low powered devices such as smartphones a greatly diminished experience. As a result, for this tutorial we will stick to performing inference on a server and delivering those predictions using a REST API. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/SashiDo/teachablemachine-node#readme" rel="noopener noreferrer"&gt;Teachable Machine node package&lt;/a&gt; does most of the heavy lifting here so the entire express api is only 30 lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TeachableMachine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sashido/teachablemachine-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TeachableMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="nx"&gt;modelUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://teachablemachine.withgoogle.com/models/aH894BqRS/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;
&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/image/classify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;
&lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;
&lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="mi"&gt;20&lt;/span&gt;  &lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;21&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;predictions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt;  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;predictions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;24&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;predictions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;28&lt;/span&gt;  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something went wrong!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;29&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;31&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example should get you started but if you are looking to deploy this in a production environment, I recommend looking into adding additional features such as rate limiting and validation on the incoming data. &lt;/p&gt;

&lt;h3&gt;
  
  
  A word of caution…
&lt;/h3&gt;

&lt;p&gt;You may be tempted to dynamically create a new instance of a model on each request but this is a bad idea. Instantiating a new instance of the Teachable Machine class is highly computationally intensive so will usually take several seconds to complete. Consequently creating a new instance on each request would harm performance drastically.&lt;/p&gt;

&lt;p&gt;If you do expect to change your model I recommend storing the model url in  environment variables which can be accessed when the node app is launched.&lt;/p&gt;

&lt;p&gt;SashiDo makes this particularly easy - &lt;a href="https://blog.sashido.io/announcing-environment-variables/" rel="noopener noreferrer"&gt;environment variables&lt;/a&gt; can be set within the runtime section of the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1xa33gk8z0umq90h2os.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1xa33gk8z0umq90h2os.png" alt="SashiDo console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Creating the Front End
&lt;/h2&gt;

&lt;p&gt;Using a JS framework for a project as small as this could be considered overkill, but I used Svelte in this case as I think it makes the code slightly more readable and modular, plus it gave me an opportunity to use a framework I have not used before. &lt;/p&gt;

&lt;p&gt;There are three main requirements for the front end code. It must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request Access and setup the user’s camera&lt;/li&gt;
&lt;li&gt;Make requests to the API&lt;/li&gt;
&lt;li&gt;Display a counter showing the number of repetitions of the current exercise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting access to the user’s webcam can easily be achieved using the &lt;code&gt;mediaDevices.getUserMedia&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;We store the stream object returned in the srcObject of a video element so that it can be accessed later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;          &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt; &lt;span class="nf"&gt;getUserMedia&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;          &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;          &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;  &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;srcObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error setting up video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch clause is most likely triggered if the user rejects access to their webcam. In this case I have just logged the result to the console. I will leave it as an exercise for the reader to modify the code to deliver an informative error message to the user. &lt;/p&gt;

&lt;p&gt;Before we can send a request to our REST API we must first capture an image, we do this by drawing a frame from the hidden video element onto a canvas. Lines 2-12 scale the crop so that it is a 217px by 217px square as the pose net can only perform inference on square images that size.&lt;/p&gt;

&lt;p&gt;It would be possible to perform this scaling on our server but that would mean sending a large image across the internet - something which would unnecessarily slow things down for users with slow internet connections.&lt;/p&gt;

&lt;p&gt;After that we POST a request to our API using javascript's &lt;code&gt;fetch&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPrediction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="nx"&gt;videoScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="mi"&gt;3&lt;/span&gt;  &lt;span class="nx"&gt;xOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="nx"&gt;videoScale&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;DIMENSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;videoScale&lt;/span&gt;
 &lt;span class="mi"&gt;4&lt;/span&gt;  &lt;span class="nx"&gt;yOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="nx"&gt;videoScale&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;DIMENSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;videoScale&lt;/span&gt;
 &lt;span class="mi"&gt;5&lt;/span&gt;
 &lt;span class="mi"&gt;6&lt;/span&gt;  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="mi"&gt;7&lt;/span&gt;   &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;8&lt;/span&gt;   &lt;span class="nx"&gt;xOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;9&lt;/span&gt;   &lt;span class="nx"&gt;yOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;   &lt;span class="nx"&gt;DIMENSION&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;xOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;   &lt;span class="nx"&gt;DIMENSION&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;yOffset&lt;/span&gt;
&lt;span class="mi"&gt;12&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;   &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;16&lt;/span&gt;   &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;     &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;18&lt;/span&gt;   &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="mi"&gt;19&lt;/span&gt;   &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="mi"&gt;20&lt;/span&gt;     &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;21&lt;/span&gt;   &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt;   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="mi"&gt;24&lt;/span&gt;   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interpret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;26&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Making one request is nice but we really need to do it several times per second&lt;/p&gt;

&lt;p&gt;You could be tempted to write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTimeOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getPrediction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it falls short in two areas: For users with a fast internet connection we are artificially limiting them to 5 frames per second. But worse still for users with a slow internet connection after 200 milliseconds the request may still not have been completed. &lt;/p&gt;

&lt;p&gt;HTTP 1.1 can only cope with a handful of concurrent connections at once, so a solution like this one will likely starve the most recent requests as they wait for the previous ones to complete. If the user is lucky they may just have a very laggy experience and, if they’re not, their entire browser may crash.&lt;/p&gt;

&lt;p&gt;To solve this issue we can use a recursive function which waits for the previous request to finish before making a new one. The boolean variable &lt;code&gt;isTracking&lt;/code&gt; is used here to allow us to stop making requests when the user ends their session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sequentialTrack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getPrediction&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;isTracking&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sequentialTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To finish off we need to update the counter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;interpret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;predictions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;probability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;likeliest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;predictions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;probability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="mi"&gt;4&lt;/span&gt;  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="mi"&gt;5&lt;/span&gt;   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;6&lt;/span&gt;   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;7&lt;/span&gt;    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="mi"&gt;8&lt;/span&gt;     &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="mi"&gt;9&lt;/span&gt;     &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;12&lt;/span&gt;   &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although there are 3 classes (“up”, “middle”, and “down”) we only change the counter when the current likeliest prediction is “up” or “down”.&lt;/p&gt;

&lt;p&gt;The if statement (line 7) is used to ensure there is a state transition - a transition from down to up should increment the counter, whereas multiple sequential down predictions should not change it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4 Deployment
&lt;/h2&gt;

&lt;p&gt;Using SashiDo deployment is easier than ever. All projects are given a private github account so to deploy your version to the web simply push your changes to the github repo. &lt;/p&gt;

&lt;p&gt;Since Svelte requires a build step, I used two repositories and set the output of the build from rollup, to be the public directory of the SashiDo repo. There are other ways to structure this so feel free to structure it in a way which works for your project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Creating this tutorial was a fun learning experience. Although I have experience using javascript, I was new to Teachable Machine and Svelte, not to mention SashiDo.&lt;/p&gt;

&lt;p&gt;I’ve tried to keep this tutorial as accessible as possible by sticking to only the essential features of the web app. &lt;/p&gt;

&lt;p&gt;As a result I have only just scratched the surface of what SashiDo can do. Using Parse Server for instance you could allow users to login to save and track progress over time.&lt;/p&gt;

&lt;p&gt;I hope you found this interesting and if you’re interested in having a go at the live version it can be found here &lt;a href="//www.workout-tracker.ml/"&gt;www.workout-tracker.ml&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Links:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/penguinprogramer/workout-tracker" rel="noopener noreferrer"&gt;Github Repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.parseplatform.org/js/guide/" rel="noopener noreferrer"&gt;Official Parse Guide for Javascript&lt;/a&gt;&lt;br&gt;
&lt;a href="https://blog.sashido.io/sashidos-getting-started-guide/" rel="noopener noreferrer"&gt;SashiDo’s Getting Started Guide&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/SashiDo/teachablemachine-node#readme" rel="noopener noreferrer"&gt;Teachable Machine on NodeJS&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/googlecreativelab/teachablemachine-community/" rel="noopener noreferrer"&gt;Teachable Machine Community repo on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
