Emotion balancer - POC
by morphcast
HTML
<script src="https://ai-sdk.morphcast.com/stage/ai-sdk.js"></script>
<div id="app">
Emotion balancer - POC v3.2
<br />
<button type="button" :disabled="!!started" v-on:click="started=true;init();">StartSDK!</button>
<button type="button" v-on:click="toggleRaw();">Show {{ raw ? 'balanced' : 'raw' }} output</button>
<button type="button" v-on:click="reset();">Reset</button>
<br />
Output is {{ raw ? 'raw' : 'balanced (operating at ' + Math.round(getWindowUsage*100) + ' %)' }}. Used last {{ 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% - 150px);
}
.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
///// POC
class RunningAvgBalance {
constructor(maxWindowSize = 100) {
this._avgEmotions = {
Angry: 0,
Disgust: 0,
Fear: 0,
Happy: 0,
Sad: 0,
Surprise: 0
};
this._window = {
Angry: [],
Disgust: [],
Fear: [],
Happy: [],
Sad: [],
Surprise: []
};
this._hasReachedMaxSize = () => this.getWindowSize() >= maxWindowSize;
this._windowUsage = () => this.getWindowSize() / maxWindowSize;
}
getWindowUsage() {
return this._windowUsage();
}
getWindowSize() {
return this._window.Angry.length;
}
canTrain() {
return true;
}
_pushAvg(newEmotions) {
Object.keys(this._avgEmotions).forEach((key) => {
const n = this.getWindowSize();
const new_value = newEmotions[key];
const old_avg = this._avgEmotions[key];
if ( this._hasReachedMaxSize() ) {
const removed_value = this._window[key].shift();
this._avgEmotions[key] = (old_avg*n-removed_value+new_value)/n;
} else {
this._avgEmotions[key] = (n * old_avg + new_value) / (n+1);
}
this._window[key].push(new_value);
});
}
next(emo) {
if (this.canTrain()) {
this._pushAvg(emo);
console.log("training...", this._avgEmotions);
}
let avg = this._avgEmotions;
const subtractOffset = (x, offset) => (x > offset) ? x - offset : 0;
const gain = (offset) => 1/(Math.max(1-offset,0.001));
let ret = {};
Object.keys(avg).forEach((k) => {
const dynamic_offset = avg[k] * this._windowUsage();
ret[k] = subtractOffset(emo[k], dynamic_offset)*gain(dynamic_offset);
});
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)
...