Emotion balancer - POC v1

by morphcast

HTML

<script src="https://ai-sdk.morphcast.com/stage/ai-sdk.js"></script>
<div id="app">
Emotion balancer - POC v2
<br />
<button type="button" :disabled="!!started" v-on:click="started=true;init();">StartSDK!</button>
<button type="button" :disabled="!!canTrain" v-on:click="trainAuto();">Train with 100 frames</button>
<button type="button" :disabled="!!canTrain" v-on:click="trainStart();">Manual train</button>
<button type="button" :disabled="!canTrain" v-on:click="trainStop();">Stop train</button>
<button type="button" v-on:click="reset();">Reset</button>
<br />
Used {{ getIterations }} frames for training.
<div class="wrapper" :class="{black:black}">
  <div class="centered">
    <div class="emo angry">
      <span>Angry</span>
      <div class="level">
        <div class="lvl" :style="{height : level.angry+'%'}"></div>
      </div>
    </div>
    <div class="emo disgust">
      <span>Disgust</span>
      <div class="level">
        <div class="lvl" :style="{height : level.disgust+'%'}"></div>
      </div>
    </div>
    <div class="emo fear">
      <span>Fear</span>
      <div class="level">
        <div class="lvl" :style="{height : level.fear+'%'}"></div>
      </div>
    </div>
    <div class="emo happy">
      <span>Happy</span>
      <div class="level">
        <div class="lvl" :style="{height : level.happy+'%'}"></div>
      </div>
    </div>
    <div class="emo sad">
      <span>Sad</span>
      <div class="level">
        <div class="lvl" :style="{height : level.sad+'%'}"></div>
      </div>
    </div>
    <div class="emo surprise">
      <span>Surprise</span>
      <div class="level">
        <div class="lvl" :style="{height : level.surprise+'%'}"></div>
      </div>
    </div>
    <div class="emo neutral">
      <span>Neutral</span>
      <div class="level">
        <div class="lvl" :style="{height : level.neutral+'%'}"></div>
      </div>
    </div>
  </div>
</div>
</div>

CSS

#app {
      height: 100%;
      width: 100%;
    }

    .centered {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      margin: auto;
      max-width: 400px;
      display: flex;
      justify-content: space-evenly;
      height: calc(100% - 30px);
    }

    .emo {
      width: 40px;
      height: 100%;
      flex: 0 0 40px;
    }

    .emo span {
      display: block;
      width: 100%;
      text-align: center;
      color: black;
      font-size: 13px;
      font-weight: 300;
    }

    .emo .level {
      width: 6px;
      margin: 10px 17px;
      background-color: darkgray;
      position: relative;
      height: 80%
    }

    .emo .level .lvl {
      position: absolute;
      width: 100%;
      bottom: 0;

      transition: height 200ms;
    }

    .emo.angry .lvl {
      background-color: #e60f28;
    }

    .emo.disgust .lvl {
      background-color: #27d534;
    }

    .emo.fear .lvl {
      background-color: #8b4f92;
    }

    .emo.happy .lvl {
      background-color: #ffea00;
    }

    .emo.sad .lvl {
      background-color: #64b5e6;
    }

    .emo.surprise .lvl {
      background-color: #ffbbc7;
    }

    .emo.neutral .lvl {
      background-color: #3e3e3e;
    }

    .emo img {
      width: 28px;
      margin: 0 6px;
    }

    .black .emo span {
      color: white;
    }

    @media screen and (max-width: 768px) {
      .centered {
        height: calc(100% - 150px);
      }

      .emo .level {
        margin: 5px 17px;
      }
    }

JavaScript

///// FIRST POC

class InitialAvgBalance {
  constructor() {
    this._avgEmotions = {
      Angry: 0,
      Disgust: 0,
      Fear: 0,
      Happy: 0,
      Sad: 0,
      Surprise: 0
    };
    this._iterations = 0;
    this._canCollectCondition = () => false;
  }
  
  getIterations() {
  	return this._iterations;
  }
  
  autoStart({maxTrainSamples = 100}) {
    this._canCollectCondition = () => this._iterations < maxTrainSamples;
  }
  
  manualStart() {
    this._canCollectCondition = () => true;
  }
  
  manualStop() {
    this._canCollectCondition = () => false;
  }
  
  canTrain() {
  	return this._canCollectCondition();
  }

  _pushAvg(newEmotions) {
    const n = ++this._iterations;
    Object.keys(this._avgEmotions).forEach((key) => {
      const old_avg = this._avgEmotions[key];
      this._avgEmotions[key] = ((n - 1) * old_avg + newEmotions[key]) / n;
    });
  }

  next(emo) {
    if (this.canTrain()) {
      this._pushAvg(emo);
      console.log("training...", this._avgEmotions);
    }

    let avg = this._avgEmotions;
    let sum = 0;
    let getPositive = (a, b) => {
      const diff = a - b;
      if (diff <= 0) {
        sum += -diff;
        return 0;
      } else {
        return diff;
      }
    };

    let ret = {};
    Object.keys(avg).forEach((k) => {
    	const gain = 100/(Math.max(100-avg[k],0.1));
      ret[k] = getPositive(emo[k], avg[k])*gain;
    });
    ret.Neutral = emo.Neutral;// + sum;
    let newSum = Object.values(ret).reduce((a,b)=>a+b, 0);
    Object.keys(ret).forEach((k) => {
      ret[k] *= 1/newSum;
    });
    return ret;
  }


}

/////////////// script

let startSDK;
CY.loader().addModule(CY.modules().FACE_EMOTION.name, {smoothness: 0.5})
          .delayMs(142)
          .load().then(({start})=>{
          	startSDK = start;
          });
          

          
let vue = new Vue({
  el: "#app",
  props:["black", "emotions"],
  data: {
  	started : false,
		emotion_balancer : new InitialAvgBalance(),
    level: {
     ...