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: {
...