├── Procfile ├── .DS_Store ├── deploy.sh ├── static ├── .DS_Store ├── css │ ├── Hero.otf │ ├── BlurBold.otf │ ├── BlurMedium.otf │ ├── HeroLight.otf │ ├── blur.xml │ ├── transPulse.svg │ ├── rickshaw.min.css │ └── main.css ├── js │ ├── .DS_Store │ ├── main.js │ ├── package.json │ ├── websocket.js │ ├── animate.js │ ├── mathmatical.js │ ├── matrixmath.js │ ├── underscore-min.js │ ├── camera.js │ ├── jadeICA.js │ └── rickshaw.min.js └── images │ ├── .DS_Store │ └── transparentPulse.png ├── resources ├── screenshot1.jpg ├── screenshot_allow.png ├── screenshot_info1.png ├── screenshot_info2.png ├── screenshot_info3.png ├── screenshot_info4.png ├── screenshot_info5.png ├── screenshot_max1.png ├── screenshot_min1.png └── screenshot_splash.png ├── requirements.txt ├── .gitignore ├── app.py ├── templates ├── master.html ├── index.html └── splash.html ├── LICENSE ├── model.py ├── README.md └── jade.py /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn -k flask_sockets.worker app:app 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/.DS_Store -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | . env/bin/activate 2 | gunicorn -k flask_sockets.worker app:app -------------------------------------------------------------------------------- /static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/.DS_Store -------------------------------------------------------------------------------- /static/css/Hero.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/css/Hero.otf -------------------------------------------------------------------------------- /static/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/js/.DS_Store -------------------------------------------------------------------------------- /resources/screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot1.jpg -------------------------------------------------------------------------------- /static/css/BlurBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/css/BlurBold.otf -------------------------------------------------------------------------------- /static/css/BlurMedium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/css/BlurMedium.otf -------------------------------------------------------------------------------- /static/css/HeroLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/css/HeroLight.otf -------------------------------------------------------------------------------- /static/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/images/.DS_Store -------------------------------------------------------------------------------- /resources/screenshot_allow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_allow.png -------------------------------------------------------------------------------- /resources/screenshot_info1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_info1.png -------------------------------------------------------------------------------- /resources/screenshot_info2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_info2.png -------------------------------------------------------------------------------- /resources/screenshot_info3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_info3.png -------------------------------------------------------------------------------- /resources/screenshot_info4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_info4.png -------------------------------------------------------------------------------- /resources/screenshot_info5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_info5.png -------------------------------------------------------------------------------- /resources/screenshot_max1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_max1.png -------------------------------------------------------------------------------- /resources/screenshot_min1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_min1.png -------------------------------------------------------------------------------- /resources/screenshot_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/resources/screenshot_splash.png -------------------------------------------------------------------------------- /static/images/transparentPulse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camilleanne/pulse/HEAD/static/images/transparentPulse.png -------------------------------------------------------------------------------- /static/css/blur.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Flask-Sockets==0.1 3 | Jinja2==2.7.1 4 | MarkupSafe==0.18 5 | Werkzeug==0.9.4 6 | gevent==0.13.8 7 | gevent-websocket==0.3.6 8 | greenlet==0.4.1 9 | gunicorn==18.0 10 | itsdangerous==0.23 11 | numpy==1.8.0 12 | wsgiref==0.1.2 13 | 14 | -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | camera.init(); 2 | var paused = false; 3 | 4 | document.getElementById("end_camera").addEventListener("click", function() { 5 | if (!paused){ 6 | camera.pause(); 7 | paused = true; 8 | } else { 9 | camera.start(); 10 | paused = false; 11 | } 12 | }); -------------------------------------------------------------------------------- /static/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "headtrackr", 3 | "description": "headtracking via webcam and WebRTC/getUserMedia", 4 | "keywords": ["webcam", "headtracking", "computer vision", "facetracking"], 5 | "version": "1.0.2", 6 | "author": "Audun Mathias Øygard ", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/auduno/headtrackr.git" 10 | }, 11 | "bugs": "https://github.com/auduno/headtrackr/issues", 12 | "main": "headtrackr.js" 13 | } 14 | -------------------------------------------------------------------------------- /static/js/websocket.js: -------------------------------------------------------------------------------- 1 | var dataSocket = new WebSocket("ws://"+ location.host + "/echo"); 2 | 3 | dataSocket.onopen = function(){ 4 | console.log("websocket open!"); 5 | } 6 | 7 | dataSocket.onmessage = function(e){ 8 | var data = JSON.parse(e.data); 9 | 10 | if (data.id === "ICA"){ 11 | camera.cardiac(data.array, data.bufferWindow); 12 | } 13 | 14 | } 15 | 16 | function sendData(data){ 17 | dataSocket.send(data); 18 | } 19 | 20 | dataSocket.onclose = function(){ 21 | console.log('closed'); 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | 66243.pdf 39 | test 40 | env 41 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request 2 | from flask_sockets import Sockets 3 | import model 4 | import json 5 | 6 | app = Flask(__name__) 7 | sockets = Sockets(app) 8 | 9 | @app.route("/") 10 | def index(): 11 | return render_template("splash.html") 12 | 13 | @app.route("/begin") 14 | def get_heartrate(): 15 | return render_template("index.html") 16 | 17 | 18 | @sockets.route('/echo') 19 | def echo_socket(ws): 20 | while True: 21 | message = json.loads(ws.receive()) 22 | signals = model.parse_RGB(message) 23 | 24 | ws.send(signals) 25 | 26 | if __name__ == "__main__": 27 | app.run(debug = True) -------------------------------------------------------------------------------- /templates/master.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pulse 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% block body %}{% endblock %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Camille Teicheira 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name "pulse," in reference to this work, nor the names of its contributors 13 | may be used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% block body %} 3 |
4 |
5 |
6 |
7 |

allow webcam

8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 |
26 | 27 |
28 |
29 |
30 |

Change in brightness (raw video data)

31 |
32 |
33 | 34 |
35 | 36 | 37 | 73 | {% endblock %} -------------------------------------------------------------------------------- /static/js/animate.js: -------------------------------------------------------------------------------- 1 | // ** USER INTERFACE ** 2 | // jQuery provides functionality for animation 3 | // and basic UI 4 | var delay = 1000; 5 | 6 | function minimizeVideo(){ 7 | var delay = 1000; 8 | $( ".video" ).animate({ 9 | width: 240, 10 | height: 180, 11 | // width: 120, 12 | // height: 90, 13 | left: "-5px", 14 | position: "absolute", 15 | margin: "10px" 16 | }, delay ); 17 | $( "#border" ).animate({ 18 | width: 225, 19 | height: 170, 20 | // width: 113, 21 | // height: 85, 22 | left: "-25px", 23 | top: "-25px", 24 | border: "22px solid #E1F1F1" 25 | }, delay ); 26 | $("#buttonBar").animate({ 27 | left: "20px", 28 | width: 160, 29 | height: 220, 30 | margin: "20px 0 0 10px" 31 | }, delay, function(){ 32 | }); 33 | $("button").animate({ 34 | margin: 2, 35 | }, delay); 36 | $("#heartbeat").animate({ 37 | width: 600, 38 | height: 600, 39 | top: "-500px", 40 | left: "30%" 41 | }, delay); 42 | $("#graphs").animate({ 43 | top: "-500px" 44 | }, delay); 45 | $("#minToggle").html("Maximize Video"); 46 | 47 | } 48 | 49 | function maximizeVideo(){ 50 | var center = ($(window).width()/2) - (480/2); 51 | $( ".video" ).animate({ 52 | // width: 480, 53 | // height: 360, 54 | width: 380, 55 | height: 285, 56 | margin: "0 0 0 " + center + "px", 57 | }, delay, function(){ 58 | $("#vid").css("position", "relative"); 59 | $("#canvas").css("position", "relative"); 60 | $(".video").css("margin", "0"); 61 | $(".video").css("left", "0"); 62 | $("#vid").css("margin", "auto"); 63 | }); 64 | $( "#border" ).animate({ 65 | // width: 460, 66 | // height: 340, 67 | width: 360, 68 | height: 270, 69 | left: "-30px", 70 | top: "-30px", 71 | }, delay ); 72 | $("#buttonBar").animate({ 73 | left: "20px", 74 | width: 385, 75 | height: 60, 76 | margin: "0 0 0 " + center + "px", 77 | }, delay, function(){ 78 | $("#buttonBar").css("margin", "auto"); 79 | }); 80 | $("button").animate({ 81 | margin: "3.5px", 82 | }, delay); 83 | $("#heartbeat").animate({ 84 | width: 400, 85 | height: 400, 86 | top: "-400px", 87 | left: "0%" 88 | }, delay); 89 | $("#graphs").animate({ 90 | top: "-200px" 91 | }, delay); 92 | $("#minToggle").html("Minimize Video"); 93 | 94 | } -------------------------------------------------------------------------------- /static/css/transPulse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 24 | 25 | -------------------------------------------------------------------------------- /static/js/mathmatical.js: -------------------------------------------------------------------------------- 1 | function mean(array) { 2 | var sum = array.reduce(function(a, b){ return a + b }); 3 | return sum/array.length; 4 | }; 5 | 6 | function normalize(array){ 7 | // normalizes an array of data to a mean of zero (does not normalize between -1 and 1) 8 | var square = []; 9 | var normalized = []; 10 | var averageOfArray = mean(array); 11 | 12 | //standard deviation 13 | for (var i = 0; i < array.length; i++){ 14 | square.push(Math.pow((array[i] - averageOfArray), 2)); 15 | }; 16 | 17 | var squaredAverage = mean(square); 18 | var stdDev = Math.sqrt(squaredAverage); 19 | 20 | //normalize 21 | for (var i = 0; i < array.length; i++){ 22 | normalized.push((array[i] - averageOfArray)/stdDev) 23 | }; 24 | 25 | return normalized; 26 | 27 | }; 28 | 29 | function frequencyExtract(fftArray, framerate){ 30 | // Return the Discrete Fourier Transform sample frequencies. 31 | // returns the center of the FFT bins 32 | // blantantly ripped from numpy.fft.fftfreq(), but ported by JS by yours truly 33 | 34 | var val, n, d, N; 35 | var p1 = []; 36 | var p2 = []; 37 | var results = []; 38 | var freqs = []; 39 | 40 | //from numpy.fft.fftfreq doc: "Sample spacing (inverse of the sampling rate). Defaults to 1. For instance, if the sample spacing is in seconds, then the frequency unit is cycles/second." 41 | // if my frequency is 15 fps, or 15 Hz, or every 66ms, then the sample spacing is .066 seconds (1/15) 42 | 43 | n = fftArray.length; 44 | d = 1.0/framerate; 45 | 46 | val = 1.0/(n * d); 47 | N = ((n - 1)/2 + 1) >> 0; 48 | for (var i = 0; i < N; i++){ p1.push(i) } 49 | for (var i = (-(n/2) >> 0); i < 0; i++) { p2.push(i) } 50 | results = p1.concat(p2); 51 | freqs = results.map( function(i){ return i * val }); 52 | 53 | return filterFreq(fftArray, freqs, framerate); 54 | } 55 | 56 | function filterFreq(fftArray, freqs, framerate){ 57 | // calculates the power spectra and returns 58 | // the frequency, in Hz, of the most prominent 59 | // frequency between 0.75 Hz and 3.3333 Hz (45 - 200 bpm) 60 | var filteredFFT = []; 61 | var filteredFreqBin = []; 62 | 63 | var freqObj = _.object(freqs, fftArray); 64 | for (freq in freqObj){ 65 | if ((freq > 0.80) && (freq < 3)){ 66 | filteredFFT.push(freqObj[freq]); 67 | filteredFreqBin.push((freq)/1); 68 | } 69 | } 70 | var normalizedFreqs = filteredFFT.map(function(i) {return Math.pow(Math.abs(i), 2)}); 71 | var idx = _.indexOf(normalizedFreqs, _.max(normalizedFreqs)); 72 | var freq_in_hertz = filteredFreqBin[idx]; 73 | 74 | freqs = {normalizedFreqs: normalizedFreqs, filteredFreqBin: filteredFreqBin, freq_in_hertz: freq_in_hertz} 75 | return freqs; 76 | } -------------------------------------------------------------------------------- /static/js/matrixmath.js: -------------------------------------------------------------------------------- 1 | // some naive solutions for some basic matrix math funcions using TypedArrays 2 | // open to much memory optimization, I'm sure. 3 | 4 | // example arrays 5 | var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 6 | 7 | var x = new Float64Array(a.length); 8 | 9 | x.set(a); 10 | 11 | var X = new Matrix(x, 3, 4); 12 | 13 | //create Matrix obj, keeps dimension metadata handy 14 | function Matrix(array, r, c){ 15 | this.array = array; 16 | this.r = r; 17 | this.c = c; 18 | 19 | } 20 | 21 | function transpose(matrix){ 22 | var ai = 0; 23 | var arrayT = new Float64Array(matrix.array.length); 24 | for (var i = 0; i < matrix.c; i++){ 25 | for (var i2 = 0; i2 < matrix.array.length; i2+=matrix.c){ 26 | arrayT[ai] = matrix.array[i2+i]; 27 | ai++; 28 | } 29 | } 30 | return new Matrix(arrayT, matrix.c, matrix.r); 31 | } 32 | 33 | function getSection(matrix, r, c){ 34 | if (typeof r === "undefined" || r === null){ r = -1; } 35 | if (typeof c === "undefined" || c === null){ c = -1; } 36 | 37 | if (c >= 0 && r < 0){ 38 | //return a column 39 | var ci = c; 40 | var ce = matrix.r; 41 | var col = []; 42 | for (var i = ci; i < matrix.array.length; i += matrix.c){ 43 | col.push(i) 44 | } 45 | return col 46 | } else if (c < 0 && r >= 0){ 47 | //return a row 48 | var ri = r * matrix.c; 49 | var re = ri + matrix.c; 50 | var row = []; 51 | for (var i = ri; i < re; i++){ row.push(i) } 52 | return row 53 | } else if (c >= 0 && r >= 0){ 54 | //return a cell based on row + column 55 | return ((r * matrix.c) + (c)) 56 | } else { 57 | return "error" 58 | } 59 | } 60 | 61 | function matrixMultiply(matrix1, matrix2){ 62 | //multiply two matrices 63 | if (matrix1.c != matrix2.r){ 64 | return "error: inner dimensions are not the same" 65 | } else { 66 | var multArray = []; 67 | var matrixArray = new Float64Array(matrix1.r * matrix2.c) 68 | var ai = 0; 69 | for (var i = 0; i < matrix.r; i ++){ 70 | var workingRow = getSection(matrix1, i, null) 71 | for (var i2 = 0; i2 < matrix2.c; i2 ++){ 72 | var workingCol = getSection(matrix2, null, i2) 73 | var temp = 0; 74 | for (var i3 = 0; i3 < matrix1.c; i3++){ 75 | temp = temp + (matrix1.array[workingRow[i3]] * matrix2.array[workingCol[i3]]) 76 | } 77 | multArray.push(temp) 78 | } 79 | } 80 | matrixArray.set(multArray) 81 | return new Matrix(matrixArray, matrix1.r, matrix2.c); 82 | } 83 | } -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | import jade 2 | import numpy as np 3 | import json 4 | 5 | def parse_RGB(message): 6 | buffer_window = message["bufferWindow"] 7 | 8 | # # ** FOR GREEN CHANNEL ONLY ** 9 | # X = np.array(message["array"]) 10 | # X = normalize_array(X) 11 | # return json.dumps(parse_ICA_results(X, buffer_window)) 12 | 13 | # ** FOR RGB CHANNELS & ICA ** 14 | X = np.ndarray(shape = (3, buffer_window), buffer= np.array(message["array"])) 15 | X = normalize_matrix(X) 16 | ICA = jade.main(X) 17 | return json.dumps(parse_ICA_results(ICA, buffer_window)) #message["time"] 18 | 19 | 20 | 21 | 22 | def parse_ICA_results(ICA, buffer_window): #time 23 | signals = {} 24 | signals["id"] = "ICA" 25 | signals["bufferWindow"] = buffer_window 26 | 27 | # ** FOR RGB CHANNELS & ICA ** 28 | one = np.squeeze(np.asarray(ICA[:, 0])).tolist() 29 | two = np.squeeze(np.asarray(ICA[:, 1])).tolist() 30 | three = np.squeeze(np.asarray(ICA[:, 2])).tolist() 31 | 32 | one = (np.hamming(len(one)) * one) 33 | two = (np.hamming(len(two)) * two) 34 | three = (np.hamming(len(three)) * three) 35 | 36 | # print "one: ", one.astype(float).tolist() 37 | # print "two: ", two.astype(float).tolist() 38 | # print "three: ", three.astype(float).tolist() 39 | 40 | one = np.absolute(np.square(np.fft.irfft(one))).astype(float).tolist() 41 | two = np.absolute(np.square(np.fft.irfft(two))).astype(float).tolist() 42 | three = np.absolute(np.square(np.fft.irfft(three))).astype(float).tolist() 43 | 44 | power_ratio = [0, 0, 0] 45 | power_ratio[0] = np.sum(one)/np.amax(one) 46 | power_ratio[1] = np.sum(two)/np.amax(two) 47 | power_ratio[2] = np.sum(three)/np.amax(three) 48 | 49 | if np.argmax(power_ratio) == 0: 50 | signals["array"] = one 51 | elif np.argmax(power_ratio) == 1: 52 | signals["array"] = two 53 | else: 54 | signals["array"] = three 55 | 56 | # print power_ratio 57 | # print signals 58 | return signals 59 | 60 | # # ** FOR GREEN CHANNEL ONLY ** 61 | # hamming = (np.hamming(len(ICA)) * ICA) 62 | # fft = np.fft.rfft(hamming) 63 | # fft = np.absolute(np.square(fft)) 64 | # signals["array"] = fft.astype(float).tolist() 65 | 66 | # return signals 67 | 68 | 69 | # ** experiments ** 70 | # ** for interpolation and hamming ** 71 | # even_times = np.linspace(time[0], time[-1], len(time)) 72 | # interpolated_two = np.interp(even_times, time, np.squeeze(np.asarray(ICA[:, 1])).tolist()) 73 | # interpolated_two = np.hamming(len(time)) * interpolated_two 74 | # interpolated_two = interpolated_two - np.mean(interpolated_two) 75 | 76 | # signals["two"] = interpolated_two.tolist() 77 | 78 | # #fft = np.fft.rfft(np.squeeze(np.asarray(ICA[:, 1]))) 79 | # #signals["two"] = fft.astype(float).tolist() 80 | 81 | 82 | def normalize_matrix(matrix): 83 | # ** for matrix 84 | for array in matrix: 85 | average_of_array = np.mean(array) 86 | std_dev = np.std(array) 87 | 88 | for i in range(len(array)): 89 | array[i] = ((array[i] - average_of_array)/std_dev) 90 | return matrix 91 | 92 | def normalize_array(array): 93 | #** for array 94 | average_of_array = np.mean(array) 95 | std_dev = np.std(array) 96 | 97 | for i in range(len(array)): 98 | array[i] = ((array[i] - average_of_array)/std_dev) 99 | return array -------------------------------------------------------------------------------- /static/css/rickshaw.min.css: -------------------------------------------------------------------------------- 1 | rickshaw_graph .detail{pointer-events:none;position:absolute;top:0;z-index:2;background:rgba(0,0,0,.1);bottom:0;width:1px;transition:opacity .25s linear;-moz-transition:opacity .25s linear;-o-transition:opacity .25s linear;-webkit-transition:opacity .25s linear}.rickshaw_graph .detail.inactive{opacity:0}.rickshaw_graph .detail .item.active{opacity:1}.rickshaw_graph .detail .x_label{font-family:Arial,sans-serif;border-radius:3px;padding:6px;opacity:.5;border:1px solid #e0e0e0;font-size:12px;position:absolute;background:#fff;white-space:nowrap}.rickshaw_graph .detail .x_label.left{left:0}.rickshaw_graph .detail .x_label.right{right:0}.rickshaw_graph .detail .item{position:absolute;z-index:2;border-radius:3px;padding:.25em;font-size:12px;font-family:Arial,sans-serif;opacity:0;background:rgba(0,0,0,.4);color:#fff;border:1px solid rgba(0,0,0,.4);margin-left:1em;margin-right:1em;margin-top:-1em;white-space:nowrap}.rickshaw_graph .detail .item.left{left:0}.rickshaw_graph .detail .item.right{right:0}.rickshaw_graph .detail .item.active{opacity:1;background:rgba(0,0,0,.8)}.rickshaw_graph .detail .item:after{position:absolute;display:block;width:0;height:0;content:"";border:5px solid transparent}.rickshaw_graph .detail .item.left:after{top:1em;left:-5px;margin-top:-5px;border-right-color:rgba(0,0,0,.8);border-left-width:0}.rickshaw_graph .detail .item.right:after{top:1em;right:-5px;margin-top:-5px;border-left-color:rgba(0,0,0,.8);border-right-width:0}.rickshaw_graph .detail .dot{width:4px;height:4px;margin-left:-2px;margin-top:-2px;border-radius:5px;position:absolute;box-shadow:0 0 2px rgba(0,0,0,.6);background:#fff;border-width:2px;border-style:solid;display:none;background-clip:padding-box}.rickshaw_graph .detail .dot.active{display:block}.rickshaw_graph{position:relative}.rickshaw_graph svg{display:block;overflow:hidden}.rickshaw_graph .x_tick{position:absolute;top:0;bottom:0;width:0;border-left:1px dotted rgba(0,0,0,.2);pointer-events:none}.rickshaw_graph .x_tick .title{position:absolute;font-size:12px;font-family:Arial,sans-serif;opacity:.5;white-space:nowrap;margin-left:3px;bottom:1px}.rickshaw_annotation_timeline{height:1px;border-top:1px solid #e0e0e0;margin-top:10px;position:relative}.rickshaw_annotation_timeline .annotation{position:absolute;height:6px;width:6px;margin-left:-2px;top:-3px;border-radius:5px;background-color:rgba(0,0,0,.25)}.rickshaw_graph .annotation_line{position:absolute;top:0;bottom:-6px;width:0;border-left:2px solid rgba(0,0,0,.3);display:none}.rickshaw_graph .annotation_line.active{display:block}.rickshaw_graph .annotation_range{background:rgba(0,0,0,.1);display:none;position:absolute;top:0;bottom:-6px}.rickshaw_graph .annotation_range.active{display:block}.rickshaw_graph .annotation_range.active.offscreen{display:none}.rickshaw_annotation_timeline .annotation .content{background:#fff;color:#000;opacity:.9;padding:5px;box-shadow:0 0 2px rgba(0,0,0,.8);border-radius:3px;position:relative;z-index:20;font-size:12px;padding:6px 8px 8px;top:18px;left:-11px;width:160px;display:none;cursor:pointer}.rickshaw_annotation_timeline .annotation .content:before{content:"\25b2";position:absolute;top:-11px;color:#fff;text-shadow:0 -1px 1px rgba(0,0,0,.8)}.rickshaw_annotation_timeline .annotation.active,.rickshaw_annotation_timeline .annotation:hover{background-color:rgba(0,0,0,.8);cursor:none}.rickshaw_annotation_timeline .annotation .content:hover{z-index:50}.rickshaw_annotation_timeline .annotation.active .content{display:block}.rickshaw_annotation_timeline .annotation:hover .content{display:block;z-index:50}.rickshaw_graph .y_axis,.rickshaw_graph .x_axis_d3{fill:none}.rickshaw_graph .y_ticks .tick,.rickshaw_graph .x_ticks_d3 .tick{stroke:rgba(0,0,0,.16);stroke-width:2px;shape-rendering:crisp-edges;pointer-events:none}.rickshaw_graph .y_grid .tick,.rickshaw_graph .x_grid_d3 .tick{z-index:-1;stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:1 1}.rickshaw_graph .y_grid path,.rickshaw_graph .x_grid_d3 path{fill:none;stroke:none}.rickshaw_graph .y_ticks path,.rickshaw_graph .x_ticks_d3 path{fill:none;stroke:gray}.rickshaw_graph .y_ticks text,.rickshaw_graph .x_ticks_d3 text{opacity:.5;font-size:12px;pointer-events:none}.rickshaw_graph .x_tick.glow .title,.rickshaw_graph .y_ticks.glow text{fill:#000;color:#000;text-shadow:-1px 1px 0 rgba(255,255,255,.1),1px -1px 0 rgba(255,255,255,.1),1px 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1),0 -1px 0 rgba(255,255,255,.1),1px 0 0 rgba(255,255,255,.1),-1px 0 0 rgba(255,255,255,.1),-1px -1px 0 rgba(255,255,255,.1)}.rickshaw_graph .x_tick.inverse .title,.rickshaw_graph .y_ticks.inverse text{fill:#fff;color:#fff;text-shadow:-1px 1px 0 rgba(0,0,0,.8),1px -1px 0 rgba(0,0,0,.8),1px 1px 0 rgba(0,0,0,.8),0 1px 0 rgba(0,0,0,.8),0 -1px 0 rgba(0,0,0,.8),1px 0 0 rgba(0,0,0,.8),-1px 0 0 rgba(0,0,0,.8),-1px -1px 0 rgba(0,0,0,.8)}.rickshaw_legend{font-family:Arial;font-size:12px;color:#fff;background:#404040;display:inline-block;padding:12px 5px;border-radius:2px;position:relative}.rickshaw_legend:hover{z-index:10}.rickshaw_legend .swatch{width:10px;height:10px;border:1px solid rgba(0,0,0,.2)}.rickshaw_legend .line{clear:both;line-height:140%;padding-right:15px}.rickshaw_legend .line .swatch{display:inline-block;margin-right:3px;border-radius:2px}.rickshaw_legend .label{margin:0;white-space:nowrap;display:inline;font-size:inherit;background-color:transparent;color:inherit;font-weight:400;line-height:normal;padding:0;text-shadow:none}.rickshaw_legend .action:hover{opacity:.6}.rickshaw_legend .action{margin-right:.2em;font-size:10px;opacity:.2;cursor:pointer;font-size:14px}.rickshaw_legend .line.disabled{opacity:.4}.rickshaw_legend ul{list-style-type:none;margin:0;padding:0;margin:2px;cursor:pointer}.rickshaw_legend li{padding:0 0 0 2px;min-width:80px;white-space:nowrap}.rickshaw_legend li:hover{background:rgba(255,255,255,.08);border-radius:3px}.rickshaw_legend li:active{background:rgba(255,255,255,.2);border-radius:3px} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### NOTE: This project is no longer maintained -- it was a code school project from several years ago. As of 7/2017 I am on a one year travel haitus and am slowly (while on trains planes and in automobiles) dragging this project into a frontend and node backend module that can be applied to any project your heart desires. I will ignore any tickets opened on *this* repo, but feel free to fork (keep the license in mind, please) and have a good time with this for now. 2 | 3 | 4 | Pulse 5 | =========== 6 | 7 | Pulse is a browser-based, non-contact heartrate detection application. It can derive a heartrate in thirty seconds or less, requiring only a browser and a webcam. Based on recent research in photoplethysmography and signal processing, the heartbeat is derived from minuscule changes in pixels over time. 8 | 9 | [Play with Pulse online!](http://pulsation.herokuapp.com) (only in Chrome) 10 | 11 | Pulse is based on techniques outlined in ["Non-contact, automated cardiac pulse measurements using video imaging and blind source separation"](http://www.opticsinfobase.org/oe/abstract.cfm?uri=oe-18-10-10762) by Poh, et al (2010) and work by @thearn with OpenCV and Python ([webcam-pulse-detector](https://github.com/thearn/webcam-pulse-detector)). 12 | 13 | Pulse works because changes in blood volume in the face during the cardiac cycle modify the amount of ambient light reflected by the blood vessels. This change in brightness can be picked up as a periodic signal by a webcam. It is most prominent in the green channel, but using independent component analysis (ICA), an even more prominent signal can be extracted from a combination of the red, blue, and green channels. 14 | 15 | The process blog for this project is here: [Camille Codes](http://camillecodes.tumblr.com) 16 | 17 | Click through for more detailed explanations of the process and technology. 18 | 19 | ![Pulse](https://raw.github.com/camilleanne/biofeedback/master/resources/screenshot_splash.png) 20 | 21 | ### Usage 22 | 23 | #### Installation 24 | recommended to use a virtual environment. If that's not your style-- the dependencies are in `requirements.txt` 25 | 26 | ``` 27 | virtualenv env 28 | source ./env/bin/activate 29 | pip install -r requirements.txt 30 | ``` 31 | 32 | I had some issues with NumPy installing most recently on OSX 10.9, suppressing clang errors helped: 33 | 34 | ``` 35 | export CFLAGS=-Qunused-arguments 36 | export CPPFLAGS=-Qunused-arguments 37 | ``` 38 | 39 | #### Run 40 | ``` 41 | ./deploy.sh 42 | ``` 43 | 44 | Pulse will be running at `http://localhost:8000` 45 | 46 | ### Technology 47 | 48 | ##### Short Roundup 49 | 50 | Facial recognition, pixel manipulation, and some frequency extraction are done in Javascript, and there is a Python backend for Independent Component Analysis (JADE algorithm) and Fast Fourier Transform. This project runs on Javascript, HTML5 Canvas, and WebRTC, Python ( & NumPy), D3js, Rickshaw, with a splash of Flask, web sockets, and Jinja. 51 | 52 | ![Pulse](https://raw.github.com/camilleanne/biofeedback/master/resources/screenshot_min1.png) 53 | 54 | 55 | ##### Detailed Explanation 56 | 57 | Pulse brings in the webcam from the user with getUserMedia and draws it on the canvas at 15 frames a second. It then looks for a face and begins tracking the head with headtrackr.js (uses the Viola-Jones algorithm). A region of interest (ROI) is selected from the forehead hased on the tracked head area. For each frame, the red, blue, and green values for the ROI are extracted from the canvas and averaged for the ROI. This average value for each channel is put into a time series array and sent via websocket (flask_sockets) to a python server. 58 | 59 | On the server the data is normalized and run though independent component analysis (JADE algorithm) to isolate the heartbeat from the three signals. Since the order of the independent components is arbitrary, using NumPy a Fast Fourier Transform is applied to each one and the resulting spectrum is filtered by the power density ratio. The component with the highest ratio is selected as the most likely to contain a heartbeat and passed back to the browser via the websocket. 60 | 61 | This happens fifteen times a second, but back on the browser, the heartrate is calculated from this information once a second. 62 | 63 | The data is filtered between 0.8 - 3.0 Hz (48 - 180 BPM), and the frequency bins calculated to determine which frequency in the FFT spectrum has the greatest power. This frequency at the greatest power is assumed to be the frequency of the heartrate and is multiplied by 60. This is the number that is printed to the screen within the pulsing circle. 64 | 65 | An average of the previous five calculated heartbeats (over five seconds) is taken and it is at that frequency that the circle pulses. 66 | 67 | The graph on the left of the screen (built with Rickshaw) is the raw feed of data from the forehead and represents changes in brightness in the green channel (where the photoplethysmographic signal is strongest) over time. 68 | 69 | The graph on the right (build with D3) titled "Confidence in frequency in BPM" is the result of the FFT function (confidence) graphed against the frequency bins represented in that result (frequency). The higher the peak, the more likely the heartbeat is at that frequency. In the graph you can see periodic noise, but also a very satisfying "settling" as the program finds a heartrate and stays there. 70 | 71 | ### Structure 72 | ####(camera.js) 73 | The meat of the program is here. It intitializes camera, canvas, and headtracking and extracts the RGB channels from the ROI. It averages them and sends them to the Python server, and when the data is returned to the browser, it uses functions from (mathmatical.js) to filter and extract frequencies. It also initializes and times the graphing of the final data. 74 | 75 | ####(mathmatical.js) 76 | Some functions for signal processing and math used by (camera.js). 77 | 78 | ####(model.py) 79 | Data is normalized, run through ICA (jade.py) and FFT and filtered by the power density ratio. 80 | 81 | 82 | ![Pulse](https://raw.github.com/camilleanne/biofeedback/master/resources/screenshot_info3.png) 83 | 84 | 85 | ####Pulse is supported by: 86 | 87 | [Headtrackr.js](https://github.com/auduno/headtrackr/) is currently doing the heavy lifting for the facetracking. 88 | 89 | [underscore.js](https://github.com/jashkenas/underscore) is helping with some of the utilities needed for computation. 90 | 91 | [D3.js](https://d3js.org) 92 | 93 | [Rickshaw](https://github.com/shutterstock/rickshaw) 94 | 95 | [flask_sockets](https://github.com/kennethreitz/flask-sockets) 96 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: "helvetica neue", helvetica, sans-serif; 3 | font-size: 12px; 4 | background-color: #E1F1F1; 5 | } 6 | 7 | @font-face { 8 | font-family: "BlurBold"; 9 | src: local("BlurBold"), 10 | local("Blur Bold"), 11 | url(BlurBold.otf); 12 | /*font-weight: bold;*/ 13 | } 14 | 15 | @font-face { 16 | font-family: "BlurMedium"; 17 | src: local("BlurMedium"), 18 | local("Blur Medium"), 19 | url(BlurMedium.otf); 20 | } 21 | 22 | @font-face { 23 | font-family: "Hero Light"; 24 | src: local("Hero Light"), 25 | local("HeroLight"), 26 | url(HeroLight.otf); 27 | } 28 | @font-face { 29 | font-family: "Hero"; 30 | src: local("Hero"), 31 | local("Hero"), 32 | url(Hero.otf); 33 | } 34 | 35 | /* ------- index.html ------- */ 36 | /* ## VIDEO ## */ 37 | 38 | #vid { 39 | clear: both; 40 | display:block; 41 | margin: auto; 42 | position: relative; 43 | width: 380px; 44 | height: 285px; 45 | padding: 0 0 35px 0; 46 | } 47 | 48 | #canvas { 49 | position: relative; 50 | display: inline-block; 51 | width: 380px; 52 | height: 285px; 53 | } 54 | 55 | .center { 56 | margin: auto; 57 | position: relative; 58 | } 59 | 60 | .clear { 61 | margin: 0; 62 | } 63 | 64 | #canvasOverlay { 65 | position: absolute; 66 | top: 0px; 67 | z-index: 100001; 68 | display: block; 69 | } 70 | 71 | .blur { 72 | filter: blur(10px); 73 | -webkit-filter: blur(10px); 74 | -o-filter: blur(10px); 75 | -ms-filter: blur(10px); 76 | -moz-filter: blur(10px); 77 | filter: url(blur.xml#blurred); 78 | } 79 | 80 | .border{ 81 | /* width: 460px; 82 | height: 340px; 83 | border: 42px solid #E1F1F1; 84 | z-index: 1001; 85 | right: -30px; 86 | top: -30px; 87 | position: absolute;*/ 88 | width: 360px; 89 | height: 270px; 90 | border: 37px solid #E1F1F1; 91 | z-index: 1; 92 | right: -30px; 93 | top: -30px; 94 | position: absolute; 95 | } 96 | 97 | #border{ 98 | position: absolute; 99 | } 100 | 101 | /* ## BUTTON ## */ 102 | 103 | button { 104 | width: 120px; 105 | display: inline-block; 106 | height: 50px; 107 | text-align: center; 108 | background-color: #2B8A81; 109 | border: none; 110 | font-size: 1em; 111 | float: left; 112 | margin: 2px; 113 | color: white; 114 | } 115 | 116 | button:hover { 117 | background-color: #DA755C; 118 | } 119 | button:visited, button:active{ 120 | background-color: #44A39D; 121 | } 122 | 123 | .active { 124 | background-color: #DA755C; 125 | } 126 | 127 | #buttonBar{ 128 | width: 385px; 129 | margin: auto; 130 | z-index: 10002; 131 | position: relative; 132 | right: -9px; 133 | 134 | } 135 | 136 | /* ## GRAPHS ## */ 137 | 138 | #graphs { 139 | position: relative; 140 | top: -200px; 141 | z-index: -1; 142 | /*width: 1300px;*/ 143 | width: 95%; 144 | margin: auto; 145 | height: 200px; 146 | } 147 | #confidenceGraph { 148 | width: 600px; 149 | height: 160px; 150 | margin: 20px; 151 | } 152 | 153 | .graph { 154 | float: left; 155 | margin: 20px; 156 | } 157 | 158 | #confidenceGraph { 159 | float: left; 160 | } 161 | 162 | #heartbeat { 163 | height: 400px; 164 | width: 400px; 165 | position: relative; 166 | top: -400px; 167 | left: 8%; 168 | z-index: -1; 169 | } 170 | 171 | path { 172 | stroke: steelblue; 173 | stroke-width: 2; 174 | fill: none; 175 | } 176 | 177 | #rawDataLabel { 178 | font-size: 12pt; 179 | color: steelblue; 180 | position: relative; 181 | /* top: -75px; 182 | float: left; 183 | right: -20px;*/ 184 | display: none; 185 | right: 625px; 186 | top: 175px; 187 | } 188 | 189 | .axis { 190 | shape-rendering: crispEdges; 191 | } 192 | 193 | .x.axis .minor { 194 | stroke-opacity: .5; 195 | } 196 | 197 | .x.axis path { 198 | display: none; 199 | } 200 | 201 | /* ------- splash.html ------- */ 202 | /* ## SPLASH PAGE ## */ 203 | 204 | h1 { 205 | font-family: "BlurBold", "Chango", sans-serif; 206 | font-weight: normal; 207 | font-size: 13em; 208 | color: #8FA1A3; 209 | letter-spacing: .05em; 210 | } 211 | 212 | h2 { 213 | font-family: "Hero Light", Helvetica, sans-serif; 214 | font-size: 2.3em; 215 | color: #0E0C09; 216 | letter-spacing: .05em; 217 | } 218 | 219 | a { 220 | text-decoration: none; 221 | color: inherit; 222 | } 223 | 224 | a:hover { 225 | background-color: #2B8A81; 226 | color: #E1F1F1; 227 | } 228 | 229 | #container { 230 | width: 600px; 231 | margin: 0 auto; 232 | display:block; 233 | 234 | } 235 | 236 | #transparentPulse{ 237 | z-index: 10001; 238 | 239 | } 240 | 241 | #overlay { 242 | position: absolute; 243 | top: 150px; 244 | width: 600px; 245 | height: 300px; 246 | z-index: 101; 247 | 248 | } 249 | 250 | .begin { 251 | position: relative; 252 | margin: 0 auto; 253 | top: 100px; 254 | width: 150px; 255 | padding: 20px; 256 | z-index: 1001; 257 | 258 | } 259 | 260 | #hidden { 261 | float: right; 262 | margin: 0 60px 0 0; 263 | /*display: none;*/ 264 | 265 | } 266 | 267 | .hide { 268 | display: none; 269 | } 270 | 271 | #arrow { 272 | margin: 50px 0; 273 | width: 0; 274 | height: 0; 275 | border-top: 200px solid #66A18B; 276 | border-left: 200px solid transparent; 277 | /*-webkit-transform: rotate(-45deg); 278 | -moz-transform: rotate(-45deg); 279 | -ms-transform: rotate(-45deg); 280 | -o-transform: rotate(-45deg);*/ 281 | position: relative; 282 | display: block; 283 | } 284 | 285 | #arrow:after { 286 | display: block; 287 | width: 180px; 288 | height: 180px; 289 | background: #66A18B; 290 | position: absolute; 291 | content: ""; 292 | top: -130px; 293 | left: -250px; 294 | -webkit-transform: rotate(-45deg); 295 | -moz-transform: rotate(-45deg); 296 | -ms-transform: rotate(-45deg); 297 | -o-transform: rotate(-45deg); 298 | } 299 | 300 | #allowWebcam { 301 | top: -40px; 302 | right: 100px; 303 | position: relative; 304 | /*display: none;*/ 305 | } 306 | 307 | #pulse { 308 | position: relative; 309 | top: 100px; 310 | } 311 | 312 | /* ## SPLASH PAGE GRAPH ## */ 313 | #graph { 314 | width: 600px; 315 | height: 210px; 316 | position: relative; 317 | top: 180px; 318 | /*top: -260px;*/ 319 | z-index: -1; 320 | background-color: #DA755C; 321 | } 322 | .waveform { 323 | fill: none; 324 | stroke: white; 325 | stroke-width: 1.5px; 326 | } 327 | 328 | /* ## CSS Animations ## 329 | borrowed with love from: http://www.justinaguilar.com/animations/ 330 | */ 331 | 332 | .pulse { 333 | animation-name: pulse; 334 | -webkit-animation-name: pulse; 335 | 336 | animation-duration: 1.5s; 337 | -webkit-animation-duration: 1.5s; 338 | 339 | animation-iteration-count: infinite; 340 | -webkit-animation-iteration-count: infinite; 341 | } 342 | 343 | @keyframes pulse { 344 | 0% { 345 | transform: scale(0.9); 346 | opacity: 0.7; 347 | } 348 | 50% { 349 | transform: scale(1); 350 | opacity: 1; 351 | } 352 | 100% { 353 | transform: scale(0.9); 354 | opacity: 0.7; 355 | } 356 | } 357 | 358 | @-webkit-keyframes pulse { 359 | 0% { 360 | -webkit-transform: scale(0.95); 361 | opacity: 0.7; 362 | } 363 | 50% { 364 | -webkit-transform: scale(1); 365 | opacity: 1; 366 | } 367 | 100% { 368 | -webkit-transform: scale(0.95); 369 | opacity: 0.7; 370 | } 371 | } 372 | 373 | 374 | /* ## INSTRUCTIONS ## */ 375 | 376 | #introduction { 377 | display: none; 378 | background-color: rgba( 225, 241, 241, 0.75); 379 | width: 550px; 380 | height: 400px; 381 | z-index: 1002; 382 | position: relative; 383 | margin: auto; 384 | top: -150px; 385 | padding: 25px; 386 | } 387 | 388 | h3 { 389 | font-family: "Hero Light", Helvetica, sans-serif; 390 | font-size: 2.3em; 391 | /*color: #0E0C09;*/ 392 | color: #3E3D45; 393 | line-height: 1.12em; 394 | } 395 | 396 | .num { 397 | font-family: "BlurMedium", "Chango", sans-serif; 398 | border-radius: 50%; 399 | padding: 10px 20px 10px 20px; 400 | background-color: #44A39D; 401 | text-align: center; 402 | color: #E1F1F1; 403 | width: 10px; 404 | height: 10px; 405 | } 406 | 407 | #next, #skip { 408 | font-family: "Hero", Helvetica, sans-serif; 409 | font-size: 1.5em; 410 | color: #3E3D45; 411 | padding: 10px; 412 | position: absolute; 413 | bottom: 0; 414 | } 415 | 416 | #next:hover, #skip:hover { 417 | background-color: #2B8A81; 418 | color: #E1F1F1; 419 | } 420 | 421 | #next { 422 | left: 525px; 423 | } 424 | 425 | .bold { 426 | font-family: "Hero", Helvetica, sans-serif; 427 | } 428 | 429 | .blurFont { 430 | font-family: "BlurMedium", "Chango", sans-serif; 431 | } 432 | -------------------------------------------------------------------------------- /templates/splash.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% block body %} 3 |
4 | 5 | 6 |
7 |

begin

8 | 9 |
10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | 77 | 271 | {% endblock %} -------------------------------------------------------------------------------- /static/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.5.2 2 | // http://underscorejs.org 3 | // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.2";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={},i=null==r?j.identity:k(r);return A(t,function(r,a){var o=i.call(e,r,a,t);n(u,o,r)}),u}};j.groupBy=F(function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o;return function(){i=this,u=arguments,a=new Date;var c=function(){var l=new Date-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u)))},l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u)),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=w||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /static/js/camera.js: -------------------------------------------------------------------------------- 1 | var camera = (function(){ 2 | var htracker; 3 | var video, canvas, context, videoPause, canvasOverlay, overlayContext; 4 | var countdownCanvas, countdownContext, rawDataGraph; 5 | var renderTimer, dataSend, workingBuffer, heartbeatTimer; 6 | var width = 380; 7 | var height = 285; 8 | var fps = 15; 9 | var heartrate = 60; 10 | var bufferWindow = 512; 11 | var sendingData = false; 12 | var red = []; 13 | var green = []; 14 | var blue = []; 15 | var pause = false; 16 | var spectrum; 17 | var confidenceGraph, x, y, line, xAxis; 18 | var heartrateAverage = []; 19 | var circle, circleSVG, r; 20 | var toggle = 1; 21 | var hrAv = 65; 22 | var blur = false; 23 | var graphing = false; 24 | 25 | function initVideoStream(){ 26 | video = document.createElement("video"); 27 | video.setAttribute("width", width); 28 | video.setAttribute("height", height); 29 | 30 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; 31 | 32 | window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; 33 | 34 | // ** for showing/hiding arrow ** 35 | var hidden = document.getElementById("arrow"); 36 | var buttonBar = document.getElementById("buttonBar"); 37 | var allowWebcam = document.getElementById("allowWebcam"); 38 | 39 | if (navigator.getUserMedia){ 40 | navigator.getUserMedia({ 41 | video: true, 42 | audio: false 43 | }, function(stream){ 44 | if (video.mozSrcObject !== undefined) { // for Firefox 45 | video.mozSrcObject = stream; 46 | } else { 47 | video.src = window.URL.createObjectURL(stream); 48 | } 49 | hidden.style.display = "none"; 50 | hidden.className = ""; 51 | allowWebcam.style.display = "none"; 52 | 53 | buttonBar.className = "button"; 54 | 55 | initCanvas(); 56 | }, errorCallback); 57 | }; 58 | }; 59 | 60 | function initCanvas(){ 61 | canvas = document.getElementById("canvas"); 62 | canvas.setAttribute("width", width); 63 | canvas.setAttribute("height", height); 64 | context = canvas.getContext("2d"); 65 | 66 | canvasOverlay = document.getElementById("canvasOverlay"); 67 | canvasOverlay.setAttribute("width", width); 68 | canvasOverlay.setAttribute("height", height); 69 | overlayContext = canvasOverlay.getContext("2d"); 70 | overlayContext.clearRect(0,0,width,height); 71 | 72 | var button = document.getElementById("end_camera"); 73 | button.style.display = "block"; 74 | 75 | // ** displays raw data (difference in pixel averages over time sampled 15 times a second) ** 76 | rawDataGraph = new Rickshaw.Graph( { 77 | element: document.getElementById("rawDataGraph"), 78 | width: 600, 79 | height: 120, 80 | renderer: "line", 81 | min: -2, 82 | interpolation: "basis", 83 | series: new Rickshaw.Series.FixedDuration([{ name: "one" }], undefined, { 84 | timeInterval: 1000/fps, 85 | maxDataPoints: 300, 86 | timeBase: new Date().getTime() / 1000 87 | }) 88 | }); 89 | 90 | 91 | 92 | startCapture(); 93 | }; 94 | 95 | function headtrack (){ 96 | htracker = new headtrackr.Tracker({detectionInterval: 1000/fps}); 97 | htracker.init(video, canvas, context); 98 | htracker.start(); 99 | 100 | // ** for each facetracking event received draw rectangle around tracked face on canvas ** 101 | document.addEventListener("facetrackingEvent", greenRect) 102 | }; 103 | 104 | function greenRect(event) { 105 | // ** clear canvas ** 106 | overlayContext.clearRect(0,0,width,height); 107 | 108 | var sx, sy, sw, sh, forehead, inpos, outpos; 109 | var greenSum = 0; 110 | var redSum = 0; 111 | var blueSum = 0; 112 | 113 | // ** approximating forehead based on facetracking ** 114 | sx = event.x + (-(event.width/5)) + 20 >> 0; 115 | sy = event.y + (-(event.height/3)) + 10 >> 0; 116 | sw = (event.width/5) >> 0; 117 | sh = (event.height/10) >> 0; 118 | 119 | // ** CS == camshift (in headtrackr.js) ** 120 | // ** once we have stable tracking, draw rectangle ** 121 | if (event.detection == "CS") /**/ { 122 | overlayContext.rotate(event.angle-(Math.PI/2)); 123 | overlayContext.strokeStyle = "#00CC00"; 124 | overlayContext.strokeRect(event.x + (-(event.width/2)) >> 0, event.y + (-(event.height/2)) >> 0, event.width, event.height); 125 | 126 | // ** for debugging: blue forehead box ** 127 | overlayContext.strokeStyle = "#33CCFF"; 128 | overlayContext.strokeRect(sx, sy, sw, sh); 129 | 130 | forehead = context.getImageData(sx, sy, sw, sh); 131 | 132 | // ** turn green ** 133 | for (i = 0; i < forehead.data.length; i+=4){ 134 | // ** for reference: ** 135 | // var red = forehead.data[i]; 136 | // var green = forehead.data[i+1]; 137 | // var blue = forehead.data[i+2]; 138 | // var alpha = forehead.data[i+3]; 139 | 140 | // ** for debugging: puts a green video image on screen ** 141 | // forehead.data[i] = 0; 142 | // forehead.data[i + 1] = forehead.data[i] 143 | // forehead.data[i + 2] = 0; 144 | 145 | // ** get sum of green area for each frame ** 146 | // ** FOR RGB CHANNELS & ICA ** 147 | redSum = forehead.data[i] + redSum; 148 | greenSum = forehead.data[i+1] + greenSum; 149 | blueSum = forehead.data[i+2] + blueSum; 150 | 151 | // ** blurs video after head tracking ** 152 | if (blur == false){ 153 | var border = document.getElementById("border"); 154 | canvas.className = "video blur"; 155 | border.className = "border"; 156 | blur = true; 157 | minimizeVideo(); 158 | } 159 | 160 | // // ** TOGGLE FOR GREEN CHANNEL ONLY ** 161 | // greenSum = forehead.data[i+1] + greenSum; 162 | }; 163 | 164 | // ** get average of green area for each frame ** 165 | 166 | // ** FOR RGB CHANNELS & ICA ** 167 | var redAverage = redSum/(forehead.data.length/4); 168 | var greenAverage = greenSum/(forehead.data.length/4); 169 | var blueAverage = blueSum/(forehead.data.length/4); 170 | 171 | // // ** TOGGLE FOR GREEN CHANNEL ONLY ** 172 | // var greenAverage = greenSum/(forehead.data.length/4); 173 | 174 | // // ** TOGGLE FOR GREEN CHANNEL ONLY ** 175 | // if (green.length < bufferWindow){ 176 | // green.push(greenAverage); 177 | // if (green.length > bufferWindow/8){ 178 | // sendingData = true; 179 | // } 180 | // } else { 181 | // green.push(greenAverage); 182 | // green.shift(); 183 | // } 184 | 185 | // ** FOR RGB CHANNELS & ICA ** 186 | if (green.length < bufferWindow){ 187 | red.push(redAverage); 188 | green.push(greenAverage); 189 | blue.push(blueAverage); 190 | if (green.length > bufferWindow/8){ 191 | sendingData = true; 192 | } 193 | } else { 194 | red.push(redAverage); 195 | red.shift(); 196 | green.push(greenAverage); 197 | green.shift(); 198 | blue.push(blueAverage); 199 | blue.shift(); 200 | } 201 | 202 | graphData = {one: normalize(green)[green.length-1]} 203 | rawDataGraph.series.addData(graphData); 204 | rawDataGraph.update(); 205 | 206 | if (graphing === false){ 207 | var rickshawAxis = document.getElementById("rawDataLabel"); 208 | rickshawAxis.style.display = "block"; 209 | graphing = true; 210 | } 211 | 212 | // ** for debugging: puts green video image on screen ** 213 | // overlayContext.putImageData(forehead, sx, sy); 214 | 215 | overlayContext.rotate((Math.PI/2)-event.angle); 216 | } 217 | }; 218 | 219 | function drawCountdown(array){ 220 | countdownContext.font = "20pt Helvetica"; 221 | countdownContext.clearRect(0,0,200,100); 222 | countdownContext.save(); 223 | countdownContext.fillText(((bufferWindow - array.length)/fps) >> 0, 25, 25); 224 | countdownContext.restore(); 225 | }; 226 | 227 | 228 | function cardiac(array, bfwindow){ 229 | // ** if using Green Channel, you can normalize data in the browser: ** 230 | // var normalized = normalize(array); 231 | // var normalized = array; 232 | 233 | // // ** fast fourier transform from dsp.js ** 234 | // // ** if using green channel, you can run fft in the browser: ** 235 | // var fft = new RFFT(bfwindow, fps); 236 | // fft.forward(normalized); 237 | // spectrum = fft.spectrum; 238 | 239 | // ** if FFT is done on server, set spectrum to that array ** 240 | spectrum = array; 241 | 242 | var freqs = frequencyExtract(spectrum, fps); 243 | var freq = freqs.freq_in_hertz; 244 | heartrate = freq * 60; 245 | 246 | // //** TOGGLE FOR GREEN CHANNEL ONLY ** 247 | // graphData = {one: green[green.length-1]} 248 | // graph.series.addData(graphData); 249 | // graph.render(); 250 | 251 | showConfidenceGraph(freqs, 600, 100); 252 | heartbeatCircle(heartrate); 253 | 254 | // ** create an average of the last five heartrate 255 | // measurements for the pulsing circle ** 256 | if (heartrateAverage.length < 3){ 257 | heartrateAverage.push(heartrate); 258 | hrAV = heartrate; 259 | } else { 260 | heartrateAverage.push(heartrate); 261 | heartrateAverage.shift(); 262 | hrAv = mean(heartrateAverage); 263 | } 264 | }; 265 | 266 | function heartbeatCircle(heartrate){ 267 | var cx = $("#heartbeat").width() / 2; 268 | var cy = $("#heartbeat").width() / 2; 269 | r = $("#heartbeat").width() / 4; 270 | 271 | if (circle) { 272 | circleSVG.select("text").text(heartrate >> 0); 273 | 274 | } else { 275 | circleSVG = d3.select("#heartbeat") 276 | .append("svg") 277 | .attr("width", cx * 2) 278 | .attr("height", cy * 2); 279 | circle = circleSVG.append("circle") 280 | .attr("cx", cx) 281 | .attr("cy", cy) 282 | .attr("r", r) 283 | .attr("fill", "#DA755C"); 284 | circleSVG.append("text") 285 | .text(heartrate >> 0) 286 | .attr("text-anchor", "middle") 287 | .attr("x", cx ) 288 | .attr("y", cy + 10) 289 | .attr("font-size", "26pt") 290 | .attr("fill", "white"); 291 | } 292 | } 293 | 294 | function showConfidenceGraph(data, width, height){ 295 | // ** x == filteredFreqBin, y == normalizedFreqs ** 296 | var max = _.max(data.normalizedFreqs); 297 | data.filteredFreqBin = _.map(data.filteredFreqBin, function(num){return num * 60}); 298 | var data = _.zip(data.normalizedFreqs, data.filteredFreqBin); 299 | 300 | if (confidenceGraph){ 301 | y = d3.scale.linear().domain([ 0, max]).range([height, 0]); 302 | confidenceGraph.select("path").transition().attr("d", line(data)).attr("class", "line").ease("linear").duration(750); 303 | } else { 304 | x = d3.scale.linear().domain([48, 180]).range([0, width - 20]); 305 | y = d3.scale.linear().domain([0, max]).range([height, 0]); 306 | 307 | confidenceGraph = d3.select("#confidenceGraph").append("svg").attr("width", width).attr("height", 150); 308 | 309 | xAxis = d3.svg.axis().scale(x).tickSize(-height).tickSubdivide(true); 310 | 311 | line = d3.svg.line() 312 | .x(function(d) { return x(+d[1]); }) 313 | .y(function(d) { return y(+d[0]); }); 314 | 315 | confidenceGraph.append("svg:path").attr("d", line(data)).attr("class", "line"); 316 | confidenceGraph.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis); 317 | confidenceGraph.append("text").attr("x", 235).attr("y", height + 40).style("text-anchor", "end").text("Confidence in frequency in BPM").attr("font-size", "12pt").attr("fill", "steelblue"); 318 | } 319 | } 320 | 321 | function clearConfidenceGraph(){ 322 | var confidenceClear = document.getElementById("confidenceGraph"); 323 | while (confidenceClear.firstChild){ 324 | confidenceClear.removeChild(confidenceClear.firstChild); 325 | } 326 | } 327 | 328 | function startCapture(){ 329 | video.play(); 330 | 331 | // ** if the video is paused, reset everything so that the data collection process restarts ** 332 | if (pause == true){ 333 | pause = false; 334 | red = []; 335 | green = []; 336 | blue = []; 337 | confidenceGraph = null; 338 | clearConfidenceGraph(); 339 | } 340 | 341 | // ** set the framerate and draw the video the canvas at the desired fps ** 342 | renderTimer = setInterval(function(){ 343 | context.drawImage(video, 0, 0, width, height); 344 | }, Math.round(1000 / fps)); 345 | 346 | 347 | // ** send data via websocket to the python server ** 348 | dataSend = setInterval(function(){ 349 | // // ** TOGGLE FOR GREEN CHANNEL ONLY ** 350 | // if (sendingData){ 351 | // sendData(JSON.stringify({"array": green, "bufferWindow": green.length})); 352 | // } 353 | // ** FOR RGB CHANNELS & ICA ** 354 | if (sendingData){ 355 | sendData(JSON.stringify({"array": [red, green, blue], "bufferWindow": green.length})); 356 | } 357 | 358 | }, Math.round(1000)); 359 | 360 | // ** pulse the round cirle containing the heartrate 361 | // to an average of the last five heartrate measurements ** 362 | heartbeatTimer = setInterval(function(){ 363 | var duration = Math.round(((60/hrAv) * 1000)/4); 364 | if (confidenceGraph){ 365 | if (toggle % 2 == 0){ 366 | circleSVG.select("circle") 367 | .transition() 368 | .attr("r", r) 369 | .duration(duration); 370 | } else { 371 | circleSVG.select("circle") 372 | .transition() 373 | .attr("r", r + 15) 374 | .duration(duration); 375 | } 376 | if (toggle == 10){ 377 | toggle = 0; 378 | } 379 | toggle++; 380 | } 381 | }, Math.round(((60/hrAv) * 1000)/2)); 382 | 383 | 384 | // ** begin headtracking! ** 385 | headtrack(); 386 | }; 387 | 388 | function pauseCapture(){ 389 | // ** clears timers so they don't continue to fire ** 390 | if (renderTimer) clearInterval(renderTimer); 391 | if (dataSend) clearInterval(dataSend); 392 | if (heartbeatTimer) clearInterval(heartbeatTimer); 393 | 394 | pause = true; 395 | sendingData = false; 396 | video.pause(); 397 | 398 | // ** removes the event listener and stops headtracking ** 399 | document.removeEventListener("facetrackingEvent", greenRect); 400 | htracker.stop(); 401 | 402 | }; 403 | 404 | var errorCallback = function(error){ 405 | console.log("something is wrong with the webcam!", error); 406 | }; 407 | 408 | return{ 409 | init: function(){ 410 | initVideoStream(); 411 | }, 412 | start: startCapture, 413 | pause: pauseCapture, 414 | cardiac: cardiac, 415 | } 416 | 417 | })(); 418 | 419 | -------------------------------------------------------------------------------- /jade.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ####################################################################### 3 | # jadeR.py -- Blind source separation of real signals 4 | # 5 | # Version 1.8 6 | # 7 | # Copyright 2005, Jean-Francois Cardoso (Original MATLAB code) 8 | # Copyright 2007, Gabriel J.L. Beckers (NumPy translation) 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | ####################################################################### 23 | 24 | # This file can be either used from the command line (type 25 | # 'python jadeR.py --help' for usage, or see docstring of function main below) 26 | # or it can be imported as a module in a python shell or program (use 27 | # 'import jadeR'). 28 | 29 | # Comments in this source file are from the original MATLAB program, unless they 30 | # are preceded by 'GB'. 31 | 32 | 33 | """ 34 | jadeR 35 | 36 | This module contains only one function, jadeR, which does blind source 37 | separation of real signals. Hopefully more ICA algorithms will be added 38 | in the future. 39 | 40 | jadeR requires NumPy. 41 | """ 42 | 43 | from numpy import abs, append, arange, arctan2, argsort, array, concatenate, \ 44 | cos, diag, dot, eye, float32, float64, loadtxt, matrix, multiply, ndarray, \ 45 | newaxis, savetxt, sign, sin, sqrt, zeros 46 | from numpy.linalg import eig, pinv 47 | 48 | def jadeR(X): 49 | """ 50 | Blind separation of real signals with JADE. 51 | 52 | jadeR implements JADE, an Independent Component Analysis (ICA) algorithm 53 | developed by Jean-Francois Cardoso. More information about JADE can be 54 | found among others in: Cardoso, J. (1999) High-order contrasts for 55 | independent component analysis. Neural Computation, 11(1): 157-192. Or 56 | look at the website: http://www.tsi.enst.fr/~cardoso/guidesepsou.html 57 | 58 | More information about ICA can be found among others in Hyvarinen A., 59 | Karhunen J., Oja E. (2001). Independent Component Analysis, Wiley. Or at the 60 | website http://www.cis.hut.fi/aapo/papers/IJCNN99_tutorialweb/ 61 | 62 | Translated into NumPy from the original Matlab Version 1.8 (May 2005) by 63 | Gabriel Beckers, http://gbeckers.nl . 64 | 65 | Parameters: 66 | 67 | X -- an n x T data matrix (n sensors, T samples). Must be a NumPy array 68 | or matrix. 69 | 70 | m -- number of independent components to extract. Output matrix B will 71 | have size m x n so that only m sources are extracted. This is done 72 | by restricting the operation of jadeR to the m first principal 73 | components. Defaults to None, in which case m == n. 74 | 75 | verbose -- print info on progress. Default is False. 76 | 77 | Returns: 78 | 79 | An m*n matrix B (NumPy matrix type), such that Y = B * X are separated 80 | sources extracted from the n * T data matrix X. If m is omitted, B is a 81 | square n * n matrix (as many sources as sensors). The rows of B are 82 | ordered such that the columns of pinv(B) are in order of decreasing 83 | norm; this has the effect that the `most energetically significant` 84 | components appear first in the rows of Y = B * X. 85 | 86 | Quick notes (more at the end of this file): 87 | 88 | o This code is for REAL-valued signals. A MATLAB implementation of JADE 89 | for both real and complex signals is also available from 90 | http://sig.enst.fr/~cardoso/stuff.html 91 | 92 | o This algorithm differs from the first released implementations of 93 | JADE in that it has been optimized to deal more efficiently 94 | 1) with real signals (as opposed to complex) 95 | 2) with the case when the ICA model does not necessarily hold. 96 | 97 | o There is a practical limit to the number of independent 98 | components that can be extracted with this implementation. Note 99 | that the first step of JADE amounts to a PCA with dimensionality 100 | reduction from n to m (which defaults to n). In practice m 101 | cannot be `very large` (more than 40, 50, 60... depending on 102 | available memory) 103 | 104 | o See more notes, references and revision history at the end of 105 | this file and more stuff on the WEB 106 | http://sig.enst.fr/~cardoso/stuff.html 107 | 108 | o For more info on NumPy translation, see the end of this file. 109 | 110 | o This code is supposed to do a good job! Please report any 111 | problem relating to the NumPY code gabriel@gbeckers.nl 112 | 113 | Copyright original Matlab code: Jean-Francois Cardoso 114 | Copyright Numpy translation: Gabriel Beckers 115 | """ 116 | 117 | # GB: we do some checking of the input arguments and copy data to new 118 | # variables to avoid messing with the original input. We also require double 119 | # precision (float64) and a numpy matrix type for X. 120 | 121 | origtype = X.dtype #float64 122 | 123 | X = matrix(X.astype(float64)) #create a matrix from a copy of X created as a float 64 array 124 | 125 | [n,T] = X.shape 126 | 127 | m = n 128 | 129 | X -= X.mean(1) 130 | 131 | # whitening & projection onto signal subspace 132 | # ------------------------------------------- 133 | 134 | # An eigen basis for the sample covariance matrix 135 | [D,U] = eig((X * X.T) / float(T)) 136 | # Sort by increasing variances 137 | k = D.argsort() 138 | Ds = D[k] 139 | 140 | # The m most significant princip. comp. by decreasing variance 141 | PCs = arange(n-1, n-m-1, -1) 142 | 143 | 144 | #PCA 145 | # At this stage, B does the PCA on m components 146 | B = U[:,k[PCs]].T 147 | 148 | # --- Scaling --------------------------------- 149 | # The scales of the principal components 150 | scales = sqrt(Ds[PCs]) 151 | B = diag(1./scales) * B 152 | #Sphering 153 | X = B * X 154 | 155 | # We have done the easy part: B is a whitening matrix and X is white. 156 | 157 | del U, D, Ds, k, PCs, scales 158 | 159 | # NOTE: At this stage, X is a PCA analysis in m components of the real 160 | # data, except that all its entries now have unit variance. Any further 161 | # rotation of X will preserve the property that X is a vector of 162 | # uncorrelated components. It remains to find the rotation matrix such 163 | # that the entries of X are not only uncorrelated but also `as independent 164 | # as possible". This independence is measured by correlations of order 165 | # higher than 2. We have defined such a measure of independence which 1) 166 | # is a reasonable approximation of the mutual information 2) can be 167 | # optimized by a `fast algorithm" This measure of independence also 168 | # corresponds to the `diagonality" of a set of cumulant matrices. The code 169 | # below finds the `missing rotation " as the matrix which best 170 | # diagonalizes a particular set of cumulant matrices. 171 | 172 | #Estimation of Cumulant Matrices 173 | #------------------------------- 174 | 175 | # Reshaping of the data, hoping to speed up things a little bit... 176 | X = X.T #transpose data to (256, 3) 177 | # Dim. of the space of real symm matrices 178 | dimsymm = (m * (m + 1)) / 2 #6 179 | # number of cumulant matrices 180 | nbcm = dimsymm #6 181 | # Storage for cumulant matrices 182 | CM = matrix(zeros([m, m*nbcm], dtype = float64)) 183 | R = matrix(eye(m, dtype=float64)) #[[ 1. 0. 0.] [ 0. 1. 0.] [ 0. 0. 1.]] 184 | # Temp for a cum. matrix 185 | Qij = matrix(zeros([m, m], dtype = float64)) 186 | # Temp 187 | Xim = zeros(m, dtype=float64) 188 | # Temp 189 | Xijm = zeros(m, dtype=float64) 190 | 191 | # I am using a symmetry trick to save storage. I should write a short note 192 | # one of these days explaining what is going on here. 193 | # will index the columns of CM where to store the cum. mats. 194 | Range = arange(m) #[0 1 2] 195 | 196 | for im in range(m): 197 | Xim = X[:,im] 198 | Xijm = multiply(Xim, Xim) 199 | Qij = multiply(Xijm, X).T * X / float(T) - R - 2 * dot(R[:,im], R[:,im].T) 200 | CM[:,Range] = Qij 201 | Range = Range + m 202 | for jm in range(im): 203 | Xijm = multiply(Xim, X[:,jm]) 204 | Qij = sqrt(2) * multiply(Xijm, X).T * X / float(T) - R[:,im] * R[:,jm].T - R[:,jm] * R[:,im].T 205 | CM[:,Range] = Qij 206 | Range = Range + m 207 | 208 | # Now we have nbcm = m(m+1)/2 cumulants matrices stored in a big 209 | # m x m*nbcm array. 210 | 211 | 212 | # Joint diagonalization of the cumulant matrices 213 | # ============================================== 214 | 215 | V = matrix(eye(m, dtype=float64)) #[[ 1. 0. 0.] [ 0. 1. 0.] [ 0. 0. 1.]] 216 | 217 | Diag = zeros(m, dtype=float64) #[0. 0. 0.] 218 | On = 0.0 219 | Range = arange(m) #[0 1 2] 220 | for im in range(nbcm): #nbcm == 6 221 | Diag = diag(CM[:,Range]) 222 | On = On + (Diag * Diag).sum(axis = 0) 223 | Range = Range + m 224 | Off = (multiply(CM,CM).sum(axis=0)).sum(axis=0) - On 225 | # A statistically scaled threshold on `small" angles 226 | seuil = 1.0e-6 / sqrt(T) #6.25e-08 227 | # sweep number 228 | encore = True 229 | sweep = 0 230 | # Total number of rotations 231 | updates = 0 232 | # Number of rotations in a given seep 233 | upds = 0 234 | g = zeros([2,nbcm], dtype=float64) #[[ 0. 0. 0. 0. 0. 0.] [ 0. 0. 0. 0. 0. 0.]] 235 | gg = zeros([2,2], dtype=float64) #[[ 0. 0.] [ 0. 0.]] 236 | G = zeros([2,2], dtype=float64) 237 | c = 0 238 | s = 0 239 | ton = 0 240 | toff = 0 241 | theta = 0 242 | Gain = 0 243 | 244 | # Joint diagonalization proper 245 | 246 | while encore: 247 | encore = False 248 | sweep = sweep + 1 249 | upds = 0 250 | Vkeep = V 251 | 252 | for p in range(m-1): #m == 3 253 | for q in range(p+1, m): #p == 1 | range(p+1, m) == [2] 254 | 255 | Ip = arange(p, m*nbcm, m) #[ 0 3 6 9 12 15] [ 0 3 6 9 12 15] [ 1 4 7 10 13 16] 256 | Iq = arange(q, m*nbcm, m) #[ 1 4 7 10 13 16] [ 2 5 8 11 14 17] [ 2 5 8 11 14 17] 257 | 258 | #computation of Givens angle 259 | g = concatenate([CM[p, Ip] - CM[q, Iq], CM[p, Iq] + CM[q, Ip]]) 260 | gg = dot(g, g.T) 261 | ton = gg[0,0] - gg[1,1] # -6.54012319852 4.44880758012 -1.96674621935 262 | toff = gg[0, 1] + gg[1, 0] # -15.629032394 -4.3847687273 6.72969915184 263 | theta = 0.5 * arctan2(toff, ton + sqrt(ton * ton + toff * toff)) #-0.491778606993 -0.194537202087 0.463781701868 264 | Gain = (sqrt(ton * ton + toff * toff) - ton) / 4.0 #5.87059352069 0.449409565866 2.24448683877 265 | 266 | if abs(theta) > seuil: 267 | encore = True 268 | upds = upds + 1 269 | c = cos(theta) 270 | s = sin(theta) 271 | G = matrix([[c, -s] , [s, c] ]) # DON"T PRINT THIS! IT"LL BREAK THINGS! HELLA LONG 272 | pair = array([p, q]) #don't print this either 273 | V[:,pair] = V[:,pair] * G 274 | CM[pair,:] = G.T * CM[pair,:] 275 | CM[:, concatenate([Ip, Iq])] = append( c*CM[:,Ip]+s*CM[:,Iq], -s*CM[:,Ip]+c*CM[:,Iq], axis=1) 276 | On = On + Gain 277 | Off = Off - Gain 278 | updates = updates + upds #3 6 9 9 279 | 280 | # A separating matrix 281 | # ------------------- 282 | 283 | B = V.T * B #[[ 0.17242566 0.10485568 -0.7373937 ] [-0.41923305 -0.84589716 1.41050008] [ 1.12505903 -2.42824508 0.92226197]] 284 | 285 | # Permute the rows of the separating matrix B to get the most energetic 286 | # components first. Here the **signals** are normalized to unit variance. 287 | # Therefore, the sort is according to the norm of the columns of 288 | # A = pinv(B) 289 | 290 | A = pinv(B) #[[-3.35031851 -2.14563715 0.60277625] [-2.49989794 -1.25230985 -0.0835184 ] [-2.49501641 -0.67979249 0.12907178]] 291 | keys = array(argsort(multiply(A,A).sum(axis=0)[0]))[0] #[2 1 0] 292 | B = B[keys,:] #[[ 1.12505903 -2.42824508 0.92226197] [-0.41923305 -0.84589716 1.41050008] [ 0.17242566 0.10485568 -0.7373937 ]] 293 | B = B[::-1,:] #[[ 0.17242566 0.10485568 -0.7373937 ] [-0.41923305 -0.84589716 1.41050008] [ 1.12505903 -2.42824508 0.92226197]] 294 | # just a trick to deal with sign == 0 295 | b = B[:,0] #[[ 0.17242566] [-0.41923305] [ 1.12505903]] 296 | signs = array(sign(sign(b)+0.1).T)[0] #[1. -1. 1.] 297 | B = diag(signs) * B #[[ 0.17242566 0.10485568 -0.7373937 ] [ 0.41923305 0.84589716 -1.41050008] [ 1.12505903 -2.42824508 0.92226197]] 298 | return B 299 | 300 | 301 | # Revision history of MATLAB code: 302 | 303 | # - V1.8, December 2013 304 | # - modifications to main function for demo by Camille Teicheira 305 | # - also added inline comments of expected outputs for demo data 306 | # - demo here: http://github.com/camilleanne/pulsation 307 | # 308 | #- V1.8, May 2005 309 | # - Added some commented code to explain the cumulant computation tricks. 310 | # - Added reference to the Neural Comp. paper. 311 | # 312 | #- V1.7, Nov. 16, 2002 313 | # - Reverted the mean removal code to an earlier version (not using 314 | # repmat) to keep the code octave-compatible. Now less efficient, 315 | # but does not make any significant difference wrt the total 316 | # computing cost. 317 | # - Remove some cruft (some debugging figures were created. What 318 | # was this stuff doing there???) 319 | # 320 | # 321 | #- V1.6, Feb. 24, 1997 322 | # - Mean removal is better implemented. 323 | # - Transposing X before computing the cumulants: small speed-up 324 | # - Still more comments to emphasize the relationship to PCA 325 | # 326 | # V1.5, Dec. 24 1997 327 | # - The sign of each row of B is determined by letting the first element 328 | # be positive. 329 | # 330 | #- V1.4, Dec. 23 1997 331 | # - Minor clean up. 332 | # - Added a verbose switch 333 | # - Added the sorting of the rows of B in order to fix in some reasonable 334 | # way the permutation indetermination. See note 2) below. 335 | # 336 | #- V1.3, Nov. 2 1997 337 | # - Some clean up. Released in the public domain. 338 | # 339 | #- V1.2, Oct. 5 1997 340 | # - Changed random picking of the cumulant matrix used for initialization 341 | # to a deterministic choice. This is not because of a better rationale 342 | # but to make the ouput (almost surely) deterministic. 343 | # - Rewrote the joint diag. to take more advantage of Matlab"s tricks. 344 | # - Created more dummy variables to combat Matlab"s loose memory 345 | # management. 346 | # 347 | #- V1.1, Oct. 29 1997. 348 | # Made the estimation of the cumulant matrices more regular. This also 349 | # corrects a buglet... 350 | # 351 | #- V1.0, Sept. 9 1997. Created. 352 | # 353 | # Main references: 354 | # @article{CS-iee-94, 355 | # title = "Blind beamforming for non {G}aussian signals", 356 | # author = "Jean-Fran\c{c}ois Cardoso and Antoine Souloumiac", 357 | # HTML = "ftp://sig.enst.fr/pub/jfc/Papers/iee.ps.gz", 358 | # journal = "IEE Proceedings-F", 359 | # month = dec, number = 6, pages = {362-370}, volume = 140, year = 1993} 360 | # 361 | # 362 | #@article{JADE:NC, 363 | # author = "Jean-Fran\c{c}ois Cardoso", 364 | # journal = "Neural Computation", 365 | # title = "High-order contrasts for independent component analysis", 366 | # HTML = "http://www.tsi.enst.fr/~cardoso/Papers.PS/neuralcomp_2ppf.ps", 367 | # year = 1999, month = jan, volume = 11, number = 1, pages = "157-192"} 368 | # 369 | # 370 | # Notes: 371 | # ====== 372 | # 373 | # Note 1) The original Jade algorithm/code deals with complex signals in 374 | # Gaussian noise white and exploits an underlying assumption that the 375 | # model of independent components actually holds. This is a reasonable 376 | # assumption when dealing with some narrowband signals. In this context, 377 | # one may i) seriously consider dealing precisely with the noise in the 378 | # whitening process and ii) expect to use the small number of significant 379 | # eigenmatrices to efficiently summarize all the 4th-order information. 380 | # All this is done in the JADE algorithm. 381 | # 382 | # In *this* implementation, we deal with real-valued signals and we do 383 | # NOT expect the ICA model to hold exactly. Therefore, it is pointless to 384 | # try to deal precisely with the additive noise and it is very unlikely 385 | # that the cumulant tensor can be accurately summarized by its first n 386 | # eigen-matrices. Therefore, we consider the joint diagonalization of the 387 | # *whole* set of eigen-matrices. However, in such a case, it is not 388 | # necessary to compute the eigenmatrices at all because one may 389 | # equivalently use `parallel slices" of the cumulant tensor. This part 390 | # (computing the eigen-matrices) of the computation can be saved: it 391 | # suffices to jointly diagonalize a set of cumulant matrices. Also, since 392 | # we are dealing with reals signals, it becomes easier to exploit the 393 | # symmetries of the cumulants to further reduce the number of matrices to 394 | # be diagonalized. These considerations, together with other cheap tricks 395 | # lead to this version of JADE which is optimized (again) to deal with 396 | # real mixtures and to work `outside the model'. As the original JADE 397 | # algorithm, it works by minimizing a `good set' of cumulants. 398 | # 399 | # Note 2) The rows of the separating matrix B are resorted in such a way 400 | # that the columns of the corresponding mixing matrix A=pinv(B) are in 401 | # decreasing order of (Euclidian) norm. This is a simple, `almost 402 | # canonical" way of fixing the indetermination of permutation. It has the 403 | # effect that the first rows of the recovered signals (ie the first rows 404 | # of B*X) correspond to the most energetic *components*. Recall however 405 | # that the source signals in S=B*X have unit variance. Therefore, when we 406 | # say that the observations are unmixed in order of decreasing energy, 407 | # this energetic signature is to be found as the norm of the columns of 408 | # A=pinv(B) and not as the variances of the separated source signals. 409 | # 410 | # Note 3) In experiments where JADE is run as B=jadeR(X,m) with m varying 411 | # in range of values, it is nice to be able to test the stability of the 412 | # decomposition. In order to help in such a test, the rows of B can be 413 | # sorted as described above. We have also decided to fix the sign of each 414 | # row in some arbitrary but fixed way. The convention is that the first 415 | # element of each row of B is positive. 416 | # 417 | # Note 4) Contrary to many other ICA algorithms, JADE (or least this 418 | # version) does not operate on the data themselves but on a statistic 419 | # (the full set of 4th order cumulant). This is represented by the matrix 420 | # CM below, whose size grows as m^2 x m^2 where m is the number of 421 | # sources to be extracted (m could be much smaller than n). As a 422 | # consequence, (this version of) JADE will probably choke on a `large' 423 | # number of sources. Here `large' depends mainly on the available memory 424 | # and could be something like 40 or so. One of these days, I will prepare 425 | # a version of JADE taking the `data' option rather than the `statistic' 426 | # option. 427 | 428 | # Notes on translation (GB): 429 | # ========================= 430 | # 431 | # Note 1) The function jadeR is a relatively literal translation from the 432 | # original MATLAB code. I haven't really looked into optimizing it for 433 | # NumPy. If you have any time to look at this and good ideas, let me know. 434 | # 435 | # Note 2) A test module that compares NumPy output with Octave (MATLAB 436 | # clone) output of the original MATLAB script is available 437 | 438 | 439 | 440 | def main(X): 441 | B = jadeR(X) 442 | Y = B * matrix(X) 443 | return Y.T 444 | 445 | # B = B.astype(origtype) 446 | # savetxt("ct_jade_data.txt", Y.T) -------------------------------------------------------------------------------- /static/js/jadeICA.js: -------------------------------------------------------------------------------- 1 | // var rgbArray = [34.55564784053156, 34.488016136687236, 34.479473184622684, 34.47199810156621, 34.48813478879924, 34.495372567631705, 34.53986710963455, 34.53701945894637, 34.526459420977694, 34.50866160417655, 34.50261034646417, 34.51696725201709, 34.47686283815852, 34.471167536782154, 34.4858803986711, 34.493355481727576, 34.423469387755105, 34.536663502610345, 34.46511627906977, 34.501661129568106, 34.460844803037496, 34.426435690555294, 34.426435690555294, 34.466302800189844, 34.4093497864262, 34.4577598481253, 34.4718794494542, 34.51174655908875, 34.511390602752726, 34.56051257712387, 34.593023255813954, 34.57380161366872, 34.60548172757475, 34.60334598955861, 34.57320835310868, 34.36295681063123, 34.382771713336496, 34.35263407688657, 34.41658756525866, 34.34468438538206, 34.358092074038915, 34.34859990507831, 34.320242050308494, 34.45760658914729, 34.28766957364341, 34.444404069767444, 34.47250484496124, 34.39910368217054, 34.445251937984494, 34.43968023255814, 34.438226744186046, 34.423812984496124, 34.38687015503876, 34.423934108527135, 34.42647771317829, 34.41000484496124, 34.429748062015506, 34.41085271317829, 34.33842054263566, 34.40515988372093, 34.35562015503876, 34.4032218992248, 34.413638565891475, 34.426598837209305, 34.419331395348834, 34.354651162790695, 34.354651162790695, 34.34387112403101, 34.315406976744185, 33.96487403100775, 33.91388081395349, 34.02676841085271, 33.95893895348837, 33.95978682170543, 33.918968023255815, 33.83878391472868, 33.82158430232558, 33.753391472868216, 33.770348837209305, 33.74551841085271, 33.75593507751938, 33.689074612403104, 33.66108630952381, 34.29030257936508, 33.91939484126984, 33.93315972222222, 33.909598214285715, 33.91679067460318, 33.868923611111114, 33.826388888888886, 33.85987103174603, 33.76682194616977, 33.461309523809526, 33.41925465838509, 33.47683747412008, 33.4543731041456, 32.78409090909091, 32.85190217391305, 32.86123188405797, 32.401185770750985, 32.14869439071567, 32.67831237911025, 32.61116600790514, 33.01259881422925, 33.01259881422925, 32.713315217391305, 32.49835858585858, 33.04752066115702, 33.17389006342495, 33.05219344608879, 32.6401955602537, 32.344344608879496, 32.3073467230444, 32.23440803382664, 32.25673890063425, 32.25158562367865, 32.18565010570825, 32.19463530655391, 31.771405919661735, 31.807214587737842, 31.77669133192389, 31.739297040169134, 31.692653276955603, 31.6992600422833, 31.863768498942918, 31.701109936575055, 31.741411205073994, 32.00938160676533, 31.953021064301552, 31.68025568181818, 30.529545454545456, 30.174611973392462, 28.355785592739647, 27.370570321151718, 26.645232815964523, 26.913441145281016, 26.47961956521739, 26.9328125, 26.599479166666665, 26.648307291666665, 26.267660910518053, 26.29343275771847, 26.04552590266876, 26.088274572649574, 26.690494791666666, 26.571354166666666, 26.571354166666666, 27.12446977730647, 28.435710498409332, 27.916712860310422, 28.309035476718403, 27.187228260869563, 27.323777173913044, 26.646223958333334, 25.92908653846154, 25.715070643642072, 24.46310629514964, 23.901033386327505, 22.502403846153847, 22.080555555555556, 22.154805672268907, 22.09610423116615, 22.327170005136107, 22.43618387262455, 22.4133281972265, 22.934001027221367, 22.937878787878788, 23.045580808080807, 23.084343434343435, 23.83800505050505, 23.163383838383837, 22.890782828282827, 22.87651515151515, 22.955303030303032, 22.80088383838384, 22.706640625, 22.68515625, 22.914583333333333, 22.876302083333332, 22.876302083333332, 22.805338541666668, 23.018434343434343, 23.128282828282828, 22.885353535353534, 22.960479797979797, 22.85477657935285, 22.306625577812017, 21.053147574819402, 20.793343653250773, 22.770351890756302, 24.853137860082306, 26.049342105263158, 26.364127702362996, 27.773529411764706, 29.6231956197113, 30.294394841269842, 30.68955910852713, 30.82312252964427, 30.805335968379445, 31.106719367588934, 31.351408102766797, 31.48826581027668, 31.48719806763285, 31.029347826086955, 31.53409090909091, 31.500126262626264, 31.66373966942149, 31.885588842975206, 32.24035412262157, 32.522330866807614, 32.508985200845665, 32.487447145877375, 32.53514799154334, 32.48586152219873, 32.46022727272727, 32.44886363636363, 32.433007399577164, 32.502078713968956, 32.16130820399113, 32.1910569105691, 32.611805555555556, 32.611805555555556, 32.895833333333336, 32.56747159090909, 33.00085227272727, 32.95723684210526, 32.88262559808612, 33.36474116161616, 33.44775883838384, 33.47664141414141, 33.46543560606061, 33.532828282828284, 33.583175505050505, 33.592013888888886, 33.55366161616162, 33.37026515151515, 33.36916035353536, 33.38273358585859, 33.452651515151516, 33.37594696969697, 33.48453282828283, 33.380050505050505, 33.371212121212125, 33.36410984848485, 33.33128156565657, 33.41414141414141, 33.365530303030305, 33.328125, 33.308712121212125, 33.21243686868687, 33.058396464646464, 33.058396464646464, 33.08791035353536, 33.05539772727273, 33.13068181818182, 33.07339015151515], 2 | // [23.709302325581394, 23.790697674418606, 23.889416231608923, 23.926672994779306, 23.93343616516374, 23.972947318462268, 23.833412434741337, 23.816682486948267, 23.810275272899858, 23.756644518272424, 23.780374940673944, 23.820953962980543, 23.797698149027052, 23.792358803986712, 23.717726625533935, 23.807902230659707, 23.783222591362126, 23.81312292358804, 23.900806834361653, 23.909587090650213, 23.834480303749405, 23.82403891789274, 23.82403891789274, 23.798172757475083, 23.830683436165163, 23.808020882771714, 23.828547698149027, 23.82688656858092, 23.877432368296155, 23.87577123872805, 23.93391077361177, 24.008305647840533, 24.008661604176556, 24.08792121499763, 24.036782154722353, 23.954437588989084, 23.833412434741337, 23.825818699572853, 23.75996677740864, 23.774442335073566, 23.746677740863788, 23.737778832463217, 23.79734219269103, 23.843992248062015, 23.77749515503876, 23.89437984496124, 23.94985465116279, 24.039001937984494, 24.01889534883721, 24.021196705426355, 23.965358527131784, 23.97080910852713, 23.94731104651163, 23.870639534883722, 23.864825581395348, 23.855862403100776, 23.844597868217054, 23.893653100775193, 23.94234496124031, 23.903827519379846, 23.97202034883721, 23.949612403100776, 23.98655523255814, 24.04360465116279, 24.02749515503876, 23.98485949612403, 23.98485949612403, 23.939437984496124, 23.88032945736434, 23.699733527131784, 23.5656492248062, 23.617974806201552, 23.567102713178294, 23.477713178294575, 23.573764534883722, 23.546875, 23.597868217054263, 23.560804263565892, 23.600532945736433, 23.538396317829456, 23.483527131782946, 23.429626937984494, 23.31113591269841, 23.715401785714285, 23.468998015873016, 23.357762896825395, 23.46044146825397, 23.36359126984127, 23.394593253968253, 23.411954365079364, 23.359002976190474, 23.339415113871635, 23.11413043478261, 23.159679089026916, 23.183876811594203, 23.13511122345804, 22.51655138339921, 22.523591897233203, 22.42838164251208, 22.177248023715414, 21.925411025145067, 22.277562862669246, 22.232707509881422, 22.669342885375492, 22.669342885375492, 22.568552371541504, 22.412373737373738, 22.79816632231405, 23.27457716701903, 23.4257399577167, 23.28990486257928, 23.109275898520085, 23.039376321353064, 22.963927061310784, 22.875924947145876, 22.787526427061312, 22.813689217758984, 22.67534355179704, 22.821485200845665, 22.927589852008456, 22.94635306553911, 22.93208245243129, 22.908694503171247, 22.932346723044397, 22.83403805496829, 22.97291226215645, 22.865618393234673, 23.083641649048626, 23.05307649667406, 22.751704545454544, 22.164772727272727, 21.95842572062084, 20.905700510493478, 20.4390919158361, 20.018569844789358, 20.22733297985154, 19.96263586956522, 20.332682291666668, 20.128645833333334, 20.031510416666666, 19.609105180533753, 19.6641810570382, 19.54068550497122, 19.32946047008547, 19.671875, 19.448177083333334, 19.448177083333334, 20.01020678685048, 20.69976139978791, 20.32358647450111, 20.497366962305986, 19.566576086956523, 19.735054347826086, 19.272265625, 18.802083333333332, 18.650052328623758, 17.73297213622291, 17.286830948595654, 16.02777777777778, 15.544708994708994, 15.520483193277311, 15.31204850361197, 15.382639958911145, 15.445557267591166, 15.59643040575244, 15.889316897791474, 15.901262626262627, 15.971464646464646, 15.889267676767677, 16.310732323232322, 15.90050505050505, 15.81060606060606, 15.673358585858585, 15.736616161616162, 15.665530303030303, 15.590104166666666, 15.555729166666667, 15.768098958333333, 15.731640625, 15.731640625, 15.723046875, 15.977272727272727, 15.958838383838383, 15.76199494949495, 15.73939393939394, 15.7153312788906, 15.713662044170519, 14.932404540763674, 15.049922600619196, 16.47124474789916, 17.852880658436213, 18.826923076923077, 19.090120663650076, 20.14669117647059, 21.504604280736686, 22.102554563492063, 22.494307170542637, 22.65513833992095, 22.558053359683793, 22.871665019762847, 23.019145256916996, 23.163661067193676, 23.20205314009662, 22.820531400966182, 23.10669191919192, 23.030808080808082, 23.20932334710744, 23.226368801652892, 23.455470401691333, 23.53713002114165, 23.646009513742072, 23.62605708245243, 23.69635306553911, 23.725290697674417, 23.74127906976744, 23.683932346723044, 23.717098308668078, 23.784368070953438, 23.60379711751663, 23.58252032520325, 23.823333333333334, 23.823333333333334, 23.92972222222222, 23.651136363636365, 23.872585227272726, 23.854515550239235, 23.95529306220096, 24.359059343434343, 24.356218434343436, 24.33554292929293, 24.425978535353536, 24.468592171717173, 24.44239267676768, 24.451073232323232, 24.377051767676768, 24.259627525252526, 24.255997474747474, 24.279356060606062, 24.213383838383837, 24.2728851010101, 24.25347222222222, 24.34106691919192, 24.322443181818183, 24.290719696969695, 24.33301767676768, 24.376420454545453, 24.349747474747474, 24.257891414141415, 24.261679292929294, 24.0697601010101, 24.029829545454547, 24.029829545454547, 24.01120580808081, 24.055555555555557, 24.039141414141415, 24.086016414141415], 3 | // [20.46309919316564, 20.431656383483627, 20.473184622686283, 20.31988609397247, 20.41362126245847, 20.422757475083056, 20.50142382534409, 20.429876601803514, 20.376008542952064, 20.432842904603703, 20.441623160892263, 20.348243948742287, 20.460370194589462, 20.29295206454675, 20.49762695775985, 20.37921214997627, 20.544257237778833, 20.435809207403892, 20.483388704318937, 20.380280018984337, 20.542121499762697, 20.389416231608923, 20.389416231608923, 20.393806359753203, 20.371737066919792, 20.420740389178928, 20.390365448504983, 20.44126720455624, 20.42702895111533, 20.436165163739915, 20.425249169435215, 20.361770289511153, 20.454437588989084, 20.403061224489797, 20.41018035121025, 20.451945894636925, 20.43106312292359, 20.417892738490746, 20.38573801613669, 20.367702895111535, 20.362482202183198, 20.44874228761272, 20.33934504034172, 20.439437984496124, 20.343992248062015, 20.437621124031008, 20.430838178294575, 20.40734011627907, 20.517926356589147, 20.47238372093023, 20.431443798449614, 20.41860465116279, 20.42235949612403, 20.44331395348837, 20.375726744186046, 20.38687015503876, 20.406976744186046, 20.35731589147287, 20.336482558139537, 20.339268410852714, 20.420542635658915, 20.43531976744186, 20.41375968992248, 20.296632751937985, 20.346172480620154, 20.463662790697676, 20.463662790697676, 20.41654554263566, 20.36676356589147, 20.141472868217054, 20.1109496124031, 20.098231589147286, 20.180838178294575, 20.03766957364341, 20.03015988372093, 20.010174418604652, 19.89983042635659, 19.971172480620154, 19.799660852713178, 19.8000242248062, 19.854408914728683, 19.87718023255814, 19.918526785714285, 20.239955357142858, 20.08172123015873, 20.00359623015873, 19.88888888888889, 19.94357638888889, 19.966269841269842, 19.934275793650794, 19.899305555555557, 19.998317805383024, 19.858954451345756, 19.85830745341615, 19.808229813664596, 20.228640040444894, 19.9512104743083, 19.9649209486166, 19.94589371980676, 19.63759881422925, 19.52103481624758, 19.69838007736944, 19.745429841897234, 19.70009881422925, 19.70009881422925, 19.54211956521739, 19.318560606060608, 19.68904958677686, 19.975026427061312, 20.003567653276956, 19.839719873150106, 19.661601479915433, 19.5911733615222, 19.605840380549683, 19.601083509513742, 19.635438689217757, 19.449524312896404, 19.5759778012685, 19.63477801268499, 19.576770613107822, 19.66146934460888, 19.718287526427062, 19.733879492600423, 19.700449260042284, 19.668736786469346, 19.576902748414376, 19.539772727272727, 19.626585623678647, 19.545315964523283, 19.30241477272727, 18.842329545454547, 18.90909090909091, 18.74163357912649, 18.365863787375414, 18.256236141906875, 18.356839872746555, 18.075135869565216, 18.398958333333333, 18.175911458333335, 18.322265625, 17.967687074829932, 17.996336996336996, 17.774725274725274, 17.817574786324787, 17.869270833333335, 17.884635416666665, 17.884635416666665, 18.1550901378579, 18.720174973488866, 18.320260532150776, 18.54268292682927, 17.795380434782608, 17.792798913043477, 17.335677083333334, 16.87139423076923, 16.573914181057038, 15.637770897832818, 15.212109167991521, 13.948317307692308, 13.38042328042328, 12.805672268907562, 12.513931888544892, 12.565613764766308, 12.674114021571649, 12.515408320493066, 12.371982537236775, 12.393055555555556, 12.550252525252525, 12.491161616161616, 12.638383838383838, 12.255176767676767, 12.217171717171718, 12.307828282828282, 12.21111111111111, 12.2510101010101, 12.268489583333333, 12.311588541666667, 12.183333333333334, 12.262890625, 12.262890625, 12.271484375, 12.239646464646464, 12.242171717171717, 12.420454545454545, 12.337373737373737, 12.378145865434002, 12.249743194658448, 11.78328173374613, 11.959623323013416, 13.295824579831933, 14.47852366255144, 15.335146761133604, 15.719205630970336, 16.797794117647058, 18.285714285714285, 18.723710317460316, 19.013202519379846, 19.07534584980237, 19.30669466403162, 19.451581027667984, 19.6227766798419, 19.745429841897234, 19.831159420289854, 19.64891304347826, 19.93522727272727, 19.873737373737374, 19.94821797520661, 19.98024276859504, 20.167679704016912, 20.209434460887948, 20.157241014799155, 20.239825581395348, 20.196485200845665, 20.19423890063425, 20.117071881606766, 20.200184989429175, 20.220798097251585, 20.164079822616408, 20.03312084257206, 19.980352303523034, 20.126805555555556, 20.126805555555556, 20.14666666666667, 20.135795454545455, 20.22215909090909, 20.12141148325359, 20.167912679425836, 20.37957702020202, 20.1166351010101, 20.20249368686869, 20.10006313131313, 20.130839646464647, 20.172664141414142, 20.09090909090909, 20.001578282828284, 20.107323232323232, 20.077651515151516, 20.0083648989899, 20.039772727272727, 19.986426767676768, 20.05902777777778, 20.008838383838384, 20.071969696969695, 20.088383838383837, 20.16808712121212, 19.98879419191919, 20.005839646464647, 19.982007575757574, 19.96212121212121, 19.963699494949495, 20.008522727272727, 20.008522727272727, 19.919349747474747, 19.868213383838384, 19.85116792929293, 19.895675505050505] 4 | 5 | var rgbArray = [34.55564784053156, 23.709302325581394, 20.46309919316564, 34.488016136687236, 23.790697674418606, 20.431656383483627, 34.479473184622684, 23.889416231608923, 20.473184622686283, 34.47199810156621, 23.926672994779306, 20.31988609397247, 34.48813478879924, 23.93343616516374, 20.41362126245847, 34.495372567631705, 23.972947318462268, 20.422757475083056, 34.53986710963455, 23.833412434741337, 20.50142382534409, 34.53701945894637, 23.816682486948267, 20.429876601803514, 34.526459420977694, 23.810275272899858, 20.376008542952064, 34.50866160417655, 23.756644518272424, 20.432842904603703, 34.50261034646417, 23.780374940673944, 20.441623160892263, 34.51696725201709, 23.820953962980543, 20.348243948742287, 34.47686283815852, 23.797698149027052, 20.460370194589462, 34.471167536782154, 23.792358803986712, 20.29295206454675, 34.4858803986711, 23.717726625533935, 20.49762695775985, 34.493355481727576, 23.807902230659707, 20.37921214997627, 34.423469387755105, 23.783222591362126, 20.544257237778833, 34.536663502610345, 23.81312292358804, 20.435809207403892, 34.46511627906977, 23.900806834361653, 20.483388704318937, 34.501661129568106, 23.909587090650213, 20.380280018984337, 34.460844803037496, 23.834480303749405, 20.542121499762697, 34.426435690555294, 23.82403891789274, 20.389416231608923, 34.426435690555294, 23.82403891789274, 20.389416231608923, 34.466302800189844, 23.798172757475083, 20.393806359753203, 34.4093497864262, 23.830683436165163, 20.371737066919792, 34.4577598481253, 23.808020882771714, 20.420740389178928, 34.4718794494542, 23.828547698149027, 20.390365448504983, 34.51174655908875, 23.82688656858092, 20.44126720455624, 34.511390602752726, 23.877432368296155, 20.42702895111533, 34.56051257712387, 23.87577123872805, 20.436165163739915, 34.593023255813954, 23.93391077361177, 20.425249169435215, 34.57380161366872, 24.008305647840533, 20.361770289511153, 34.60548172757475, 24.008661604176556, 20.454437588989084, 34.60334598955861, 24.08792121499763, 20.403061224489797, 34.57320835310868, 24.036782154722353, 20.41018035121025, 34.36295681063123, 23.954437588989084, 20.451945894636925, 34.382771713336496, 23.833412434741337, 20.43106312292359, 34.35263407688657, 23.825818699572853, 20.417892738490746, 34.41658756525866, 23.75996677740864, 20.38573801613669, 34.34468438538206, 23.774442335073566, 20.367702895111535, 34.358092074038915, 23.746677740863788, 20.362482202183198, 34.34859990507831, 23.737778832463217, 20.44874228761272, 34.320242050308494, 23.79734219269103, 20.33934504034172, 34.45760658914729, 23.843992248062015, 20.439437984496124, 34.28766957364341, 23.77749515503876, 20.343992248062015, 34.444404069767444, 23.89437984496124, 20.437621124031008, 34.47250484496124, 23.94985465116279, 20.430838178294575, 34.39910368217054, 24.039001937984494, 20.40734011627907, 34.445251937984494, 24.01889534883721, 20.517926356589147, 34.43968023255814, 24.021196705426355, 20.47238372093023, 34.438226744186046, 23.965358527131784, 20.431443798449614, 34.423812984496124, 23.97080910852713, 20.41860465116279, 34.38687015503876, 23.94731104651163, 20.42235949612403, 34.423934108527135, 23.870639534883722, 20.44331395348837, 34.42647771317829, 23.864825581395348, 20.375726744186046, 34.41000484496124, 23.855862403100776, 20.38687015503876, 34.429748062015506, 23.844597868217054, 20.406976744186046, 34.41085271317829, 23.893653100775193, 20.35731589147287, 34.33842054263566, 23.94234496124031, 20.336482558139537, 34.40515988372093, 23.903827519379846, 20.339268410852714, 34.35562015503876, 23.97202034883721, 20.420542635658915, 34.4032218992248, 23.949612403100776, 20.43531976744186, 34.413638565891475, 23.98655523255814, 20.41375968992248, 34.426598837209305, 24.04360465116279, 20.296632751937985, 34.419331395348834, 24.02749515503876, 20.346172480620154, 34.354651162790695, 23.98485949612403, 20.463662790697676, 34.354651162790695, 23.98485949612403, 20.463662790697676, 34.34387112403101, 23.939437984496124, 20.41654554263566, 34.315406976744185, 23.88032945736434, 20.36676356589147, 33.96487403100775, 23.699733527131784, 20.141472868217054, 33.91388081395349, 23.5656492248062, 20.1109496124031, 34.02676841085271, 23.617974806201552, 20.098231589147286, 33.95893895348837, 23.567102713178294, 20.180838178294575, 33.95978682170543, 23.477713178294575, 20.03766957364341, 33.918968023255815, 23.573764534883722, 20.03015988372093, 33.83878391472868, 23.546875, 20.010174418604652, 33.82158430232558, 23.597868217054263, 19.89983042635659, 33.753391472868216, 23.560804263565892, 19.971172480620154, 33.770348837209305, 23.600532945736433, 19.799660852713178, 33.74551841085271, 23.538396317829456, 19.8000242248062, 33.75593507751938, 23.483527131782946, 19.854408914728683, 33.689074612403104, 23.429626937984494, 19.87718023255814, 33.66108630952381, 23.31113591269841, 19.918526785714285, 34.29030257936508, 23.715401785714285, 20.239955357142858, 33.91939484126984, 23.468998015873016, 20.08172123015873, 33.93315972222222, 23.357762896825395, 20.00359623015873, 33.909598214285715, 23.46044146825397, 19.88888888888889, 33.91679067460318, 23.36359126984127, 19.94357638888889, 33.868923611111114, 23.394593253968253, 19.966269841269842, 33.826388888888886, 23.411954365079364, 19.934275793650794, 33.85987103174603, 23.359002976190474, 19.899305555555557, 33.76682194616977, 23.339415113871635, 19.998317805383024, 33.461309523809526, 23.11413043478261, 19.858954451345756, 33.41925465838509, 23.159679089026916, 19.85830745341615, 33.47683747412008, 23.183876811594203, 19.808229813664596, 33.4543731041456, 23.13511122345804, 20.228640040444894, 32.78409090909091, 22.51655138339921, 19.9512104743083, 32.85190217391305, 22.523591897233203, 19.9649209486166, 32.86123188405797, 22.42838164251208, 19.94589371980676, 32.401185770750985, 22.177248023715414, 19.63759881422925, 32.14869439071567, 21.925411025145067, 19.52103481624758, 32.67831237911025, 22.277562862669246, 19.69838007736944, 32.61116600790514, 22.232707509881422, 19.745429841897234, 33.01259881422925, 22.669342885375492, 19.70009881422925, 33.01259881422925, 22.669342885375492, 19.70009881422925, 32.713315217391305, 22.568552371541504, 19.54211956521739, 32.49835858585858, 22.412373737373738, 19.318560606060608, 33.04752066115702, 22.79816632231405, 19.68904958677686, 33.17389006342495, 23.27457716701903, 19.975026427061312, 33.05219344608879, 23.4257399577167, 20.003567653276956, 32.6401955602537, 23.28990486257928, 19.839719873150106, 32.344344608879496, 23.109275898520085, 19.661601479915433, 32.3073467230444, 23.039376321353064, 19.5911733615222, 32.23440803382664, 22.963927061310784, 19.605840380549683, 32.25673890063425, 22.875924947145876, 19.601083509513742, 32.25158562367865, 22.787526427061312, 19.635438689217757, 32.18565010570825, 22.813689217758984, 19.449524312896404, 32.19463530655391, 22.67534355179704, 19.5759778012685, 31.771405919661735, 22.821485200845665, 19.63477801268499, 31.807214587737842, 22.927589852008456, 19.576770613107822, 31.77669133192389, 22.94635306553911, 19.66146934460888, 31.739297040169134, 22.93208245243129, 19.718287526427062, 31.692653276955603, 22.908694503171247, 19.733879492600423, 31.6992600422833, 22.932346723044397, 19.700449260042284, 31.863768498942918, 22.83403805496829, 19.668736786469346, 31.701109936575055, 22.97291226215645, 19.576902748414376, 31.741411205073994, 22.865618393234673, 19.539772727272727, 32.00938160676533, 23.083641649048626, 19.626585623678647, 31.953021064301552, 23.05307649667406, 19.545315964523283, 31.68025568181818, 22.751704545454544, 19.30241477272727, 30.529545454545456, 22.164772727272727, 18.842329545454547, 30.174611973392462, 21.95842572062084, 18.90909090909091, 28.355785592739647, 20.905700510493478, 18.74163357912649, 27.370570321151718, 20.4390919158361, 18.365863787375414, 26.645232815964523, 20.018569844789358, 18.256236141906875, 26.913441145281016, 20.22733297985154, 18.356839872746555, 26.47961956521739, 19.96263586956522, 18.075135869565216, 26.9328125, 20.332682291666668, 18.398958333333333, 26.599479166666665, 20.128645833333334, 18.175911458333335, 26.648307291666665, 20.031510416666666, 18.322265625, 26.267660910518053, 19.609105180533753, 17.967687074829932, 26.29343275771847, 19.6641810570382, 17.996336996336996, 26.04552590266876, 19.54068550497122, 17.774725274725274, 26.088274572649574, 19.32946047008547, 17.817574786324787, 26.690494791666666, 19.671875, 17.869270833333335, 26.571354166666666, 19.448177083333334, 17.884635416666665, 26.571354166666666, 19.448177083333334, 17.884635416666665, 27.12446977730647, 20.01020678685048, 18.1550901378579, 28.435710498409332, 20.69976139978791, 18.720174973488866, 27.916712860310422, 20.32358647450111, 18.320260532150776, 28.309035476718403, 20.497366962305986, 18.54268292682927, 27.187228260869563, 19.566576086956523, 17.795380434782608, 27.323777173913044, 19.735054347826086, 17.792798913043477, 26.646223958333334, 19.272265625, 17.335677083333334, 25.92908653846154, 18.802083333333332, 16.87139423076923, 25.715070643642072, 18.650052328623758, 16.573914181057038, 24.46310629514964, 17.73297213622291, 15.637770897832818, 23.901033386327505, 17.286830948595654, 15.212109167991521, 22.502403846153847, 16.02777777777778, 13.948317307692308, 22.080555555555556, 15.544708994708994, 13.38042328042328, 22.154805672268907, 15.520483193277311, 12.805672268907562, 22.09610423116615, 15.31204850361197, 12.513931888544892, 22.327170005136107, 15.382639958911145, 12.565613764766308, 22.43618387262455, 15.445557267591166, 12.674114021571649, 22.4133281972265, 15.59643040575244, 12.515408320493066, 22.934001027221367, 15.889316897791474, 12.371982537236775, 22.937878787878788, 15.901262626262627, 12.393055555555556, 23.045580808080807, 15.971464646464646, 12.550252525252525, 23.084343434343435, 15.889267676767677, 12.491161616161616, 23.83800505050505, 16.310732323232322, 12.638383838383838, 23.163383838383837, 15.90050505050505, 12.255176767676767, 22.890782828282827, 15.81060606060606, 12.217171717171718, 22.87651515151515, 15.673358585858585, 12.307828282828282, 22.955303030303032, 15.736616161616162, 12.21111111111111, 22.80088383838384, 15.665530303030303, 12.2510101010101, 22.706640625, 15.590104166666666, 12.268489583333333, 22.68515625, 15.555729166666667, 12.311588541666667, 22.914583333333333, 15.768098958333333, 12.183333333333334, 22.876302083333332, 15.731640625, 12.262890625, 22.876302083333332, 15.731640625, 12.262890625, 22.805338541666668, 15.723046875, 12.271484375, 23.018434343434343, 15.977272727272727, 12.239646464646464, 23.128282828282828, 15.958838383838383, 12.242171717171717, 22.885353535353534, 15.76199494949495, 12.420454545454545, 22.960479797979797, 15.73939393939394, 12.337373737373737, 22.85477657935285, 15.7153312788906, 12.378145865434002, 22.306625577812017, 15.713662044170519, 12.249743194658448, 21.053147574819402, 14.932404540763674, 11.78328173374613, 20.793343653250773, 15.049922600619196, 11.959623323013416, 22.770351890756302, 16.47124474789916, 13.295824579831933, 24.853137860082306, 17.852880658436213, 14.47852366255144, 26.049342105263158, 18.826923076923077, 15.335146761133604, 26.364127702362996, 19.090120663650076, 15.719205630970336, 27.773529411764706, 20.14669117647059, 16.797794117647058, 29.6231956197113, 21.504604280736686, 18.285714285714285, 30.294394841269842, 22.102554563492063, 18.723710317460316, 30.68955910852713, 22.494307170542637, 19.013202519379846, 30.82312252964427, 22.65513833992095, 19.07534584980237, 30.805335968379445, 22.558053359683793, 19.30669466403162, 31.106719367588934, 22.871665019762847, 19.451581027667984, 31.351408102766797, 23.019145256916996, 19.6227766798419, 31.48826581027668, 23.163661067193676, 19.745429841897234, 31.48719806763285, 23.20205314009662, 19.831159420289854, 31.029347826086955, 22.820531400966182, 19.64891304347826, 31.53409090909091, 23.10669191919192, 19.93522727272727, 31.500126262626264, 23.030808080808082, 19.873737373737374, 31.66373966942149, 23.20932334710744, 19.94821797520661, 31.885588842975206, 23.226368801652892, 19.98024276859504, 32.24035412262157, 23.455470401691333, 20.167679704016912, 32.522330866807614, 23.53713002114165, 20.209434460887948, 32.508985200845665, 23.646009513742072, 20.157241014799155, 32.487447145877375, 23.62605708245243, 20.239825581395348, 32.53514799154334, 23.69635306553911, 20.196485200845665, 32.48586152219873, 23.725290697674417, 20.19423890063425, 32.46022727272727, 23.74127906976744, 20.117071881606766, 32.44886363636363, 23.683932346723044, 20.200184989429175, 32.433007399577164, 23.717098308668078, 20.220798097251585, 32.502078713968956, 23.784368070953438, 20.164079822616408, 32.16130820399113, 23.60379711751663, 20.03312084257206, 32.1910569105691, 23.58252032520325, 19.980352303523034, 32.611805555555556, 23.823333333333334, 20.126805555555556, 32.611805555555556, 23.823333333333334, 20.126805555555556, 32.895833333333336, 23.92972222222222, 20.14666666666667, 32.56747159090909, 23.651136363636365, 20.135795454545455, 33.00085227272727, 23.872585227272726, 20.22215909090909, 32.95723684210526, 23.854515550239235, 20.12141148325359, 32.88262559808612, 23.95529306220096, 20.167912679425836, 33.36474116161616, 24.359059343434343, 20.37957702020202, 33.44775883838384, 24.356218434343436, 20.1166351010101, 33.47664141414141, 24.33554292929293, 20.20249368686869, 33.46543560606061, 24.425978535353536, 20.10006313131313, 33.532828282828284, 24.468592171717173, 20.130839646464647, 33.583175505050505, 24.44239267676768, 20.172664141414142, 33.592013888888886, 24.451073232323232, 20.09090909090909, 33.55366161616162, 24.377051767676768, 20.001578282828284, 33.37026515151515, 24.259627525252526, 20.107323232323232, 33.36916035353536, 24.255997474747474, 20.077651515151516, 33.38273358585859, 24.279356060606062, 20.0083648989899, 33.452651515151516, 24.213383838383837, 20.039772727272727, 33.37594696969697, 24.2728851010101, 19.986426767676768, 33.48453282828283, 24.25347222222222, 20.05902777777778, 33.380050505050505, 24.34106691919192, 20.008838383838384, 33.371212121212125, 24.322443181818183, 20.071969696969695, 33.36410984848485, 24.290719696969695, 20.088383838383837, 33.33128156565657, 24.33301767676768, 20.16808712121212, 33.41414141414141, 24.376420454545453, 19.98879419191919, 33.365530303030305, 24.349747474747474, 20.005839646464647, 33.328125, 24.257891414141415, 19.982007575757574, 33.308712121212125, 24.261679292929294, 19.96212121212121, 33.21243686868687, 24.0697601010101, 19.963699494949495, 33.058396464646464, 24.029829545454547, 20.008522727272727, 33.058396464646464, 24.029829545454547, 20.008522727272727, 33.08791035353536, 24.01120580808081, 19.919349747474747, 33.05539772727273, 24.055555555555557, 19.868213383838384, 33.13068181818182, 24.039141414141415, 19.85116792929293, 33.07339015151515, 24.086016414141415, 19.895675505050505] 6 | 7 | var bufferWindow = 256; 8 | function jadeICA(rgbArray, bufferWindow){ 9 | 10 | var x = new Float64Array(bufferWindow * 3); 11 | 12 | //assumes an array of [r,g,b,r,g,b,r,g,b], not [[r],[g],[b]] 13 | x.set(rgbArray); 14 | 15 | var X = new Matrix(X, 3, 256); 16 | 17 | // var n = 3; 18 | // var T = 256; 19 | 20 | var m = Matrix.r; 21 | 22 | var mean = new Float64Array(3) 23 | 24 | for (i = 0; i < X.array.length; i+=3){ 25 | mean[0] = mean[0] + X.array[i] 26 | mean[1] = mean[1] + X.array[i+1] 27 | mean[2] = mean[2] + X.array[i+2] 28 | }; 29 | for (i = 0; i < mean.length; i++){ 30 | mean[i] = mean[i]/mean.length 31 | }; 32 | for (i = 0; i < X.array.length; i+=3){ 33 | X[0] = X[i] - mean[0] 34 | X[1] = X[i+1] - mean[1] 35 | X[2] = X[i+2] - mean[2] 36 | }; 37 | //mean { '0': 31.22510846528421, '1': 22.12086108293013, '2': 18.807730334555153 } 38 | 39 | //whitening & projection onto signal subspace 40 | //------------------------------------------- 41 | 42 | var D; 43 | var U; 44 | 45 | } 46 | 47 | function Matrix(array, r, c){ 48 | this.array = matrix; 49 | this.r = r; 50 | this.c = c; 51 | 52 | } 53 | 54 | function transpose(matrix){ 55 | var ai = 0; 56 | var arrayT = new Float64Array(matrix.array.length); 57 | for (var i = 0; i < matrix.c; i++){ 58 | for (var i2 = 0; i2 < matrix.array.length; i2+=matrix.c){ 59 | arrayT[ai] = matrix.array[i2+i]; 60 | ai++; 61 | } 62 | } 63 | return new Matrix(arrayT, matrix.c, matrix.r); 64 | } 65 | 66 | function getSection(matrix, r, c){ 67 | if (typeof r === "undefined" || r === null){ r = -1; } 68 | if (typeof c === "undefined" || c === null){ c = -1; } 69 | 70 | if (c >= 0 && r < 0){ 71 | //return a column 72 | var ci = c; 73 | var ce = matrix.r; 74 | var col = []; 75 | for (var i = ci; i < matrix.array.length; i += matrix.c){ 76 | col.push(i) 77 | } 78 | return col 79 | } else if (c < 0 && r >= 0){ 80 | //return a row 81 | var ri = r * matrix.c; 82 | var re = ri + matrix.c; 83 | var row = []; 84 | for (var i = ri; i < re; i++){ row.push(i) } 85 | return row 86 | } else if (c >= 0 && r >= 0){ 87 | //return a cell based on row + column 88 | return ((r * matrix.c) + (c)) 89 | } else { 90 | return "error" 91 | } 92 | } 93 | 94 | function matrixMultiply(matrix1, matrix2){ 95 | //multiply two matrices 96 | if (matrix1.c != matrix2.r){ 97 | return "error: inner dimensions are not the same" 98 | } else { 99 | var multArray = []; 100 | var matrixArray = new Float64Array(matrix1.r * matrix2.c) 101 | var ai = 0; 102 | for (var i = 0; i < matrix.r; i ++){ 103 | var workingRow = getSection(matrix1, i, null) 104 | for (var i2 = 0; i2 < matrix2.c; i2 ++){ 105 | var workingCol = getSection(matrix2, null, i2) 106 | var temp = 0; 107 | for (var i3 = 0; i3 < matrix1.c; i3++){ 108 | temp = temp + (matrix1.array[workingRow[i3]] * matrix2.array[workingCol[i3]]) 109 | } 110 | multArray.push(temp) 111 | } 112 | } 113 | matrixArray.set(multArray) 114 | return new Matrix(matrixArray, matrix1.r, matrix2.c); 115 | } 116 | } -------------------------------------------------------------------------------- /static/js/rickshaw.min.js: -------------------------------------------------------------------------------- 1 | var Rickshaw={namespace:function(namespace,obj){var parts=namespace.split(".");var parent=Rickshaw;for(var i=1,length=parts.length;i=3){if(s.data[2].xthis.window.xMax)isInRange=false;return isInRange}return true};this.onUpdate=function(callback){this.updateCallbacks.push(callback)};this.registerRenderer=function(renderer){this._renderers=this._renderers||{};this._renderers[renderer.name]=renderer};this.configure=function(args){if(args.width||args.height){this.setSize(args)}Rickshaw.keys(this.defaults).forEach(function(k){this[k]=k in args?args[k]:k in this?this[k]:this.defaults[k]},this);this.setRenderer(args.renderer||this.renderer.name,args)};this.setRenderer=function(r,args){if(typeof r=="function"){this.renderer=new r({graph:self});this.registerRenderer(this.renderer)}else{if(!this._renderers[r]){throw"couldn't find renderer "+r}this.renderer=this._renderers[r]}if(typeof args=="object"){this.renderer.configure(args)}};this.setSize=function(args){args=args||{};if(typeof window!==undefined){var style=window.getComputedStyle(this.element,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);var elementHeight=parseInt(style.getPropertyValue("height"),10)}this.width=args.width||elementWidth||400;this.height=args.height||elementHeight||250;this.vis&&this.vis.attr("width",this.width).attr("height",this.height)};this.initialize(args)};Rickshaw.namespace("Rickshaw.Fixtures.Color");Rickshaw.Fixtures.Color=function(){this.schemes={};this.schemes.spectrum14=["#ecb796","#dc8f70","#b2a470","#92875a","#716c49","#d2ed82","#bbe468","#a1d05d","#e7cbe6","#d8aad6","#a888c2","#9dc2d3","#649eb9","#387aa3"].reverse();this.schemes.spectrum2000=["#57306f","#514c76","#646583","#738394","#6b9c7d","#84b665","#a7ca50","#bfe746","#e2f528","#fff726","#ecdd00","#d4b11d","#de8800","#de4800","#c91515","#9a0000","#7b0429","#580839","#31082b"];this.schemes.spectrum2001=["#2f243f","#3c2c55","#4a3768","#565270","#6b6b7c","#72957f","#86ad6e","#a1bc5e","#b8d954","#d3e04e","#ccad2a","#cc8412","#c1521d","#ad3821","#8a1010","#681717","#531e1e","#3d1818","#320a1b"];this.schemes.classic9=["#423d4f","#4a6860","#848f39","#a2b73c","#ddcb53","#c5a32f","#7d5836","#963b20","#7c2626","#491d37","#2f254a"].reverse();this.schemes.httpStatus={503:"#ea5029",502:"#d23f14",500:"#bf3613",410:"#efacea",409:"#e291dc",403:"#f457e8",408:"#e121d2",401:"#b92dae",405:"#f47ceb",404:"#a82a9f",400:"#b263c6",301:"#6fa024",302:"#87c32b",307:"#a0d84c",304:"#28b55c",200:"#1a4f74",206:"#27839f",201:"#52adc9",202:"#7c979f",203:"#a5b8bd",204:"#c1cdd1"};this.schemes.colorwheel=["#b5b6a9","#858772","#785f43","#96557e","#4682b4","#65b9ac","#73c03a","#cb513a"].reverse();this.schemes.cool=["#5e9d2f","#73c03a","#4682b4","#7bc3b8","#a9884e","#c1b266","#a47493","#c09fb5"];this.schemes.munin=["#00cc00","#0066b3","#ff8000","#ffcc00","#330099","#990099","#ccff00","#ff0000","#808080","#008f00","#00487d","#b35a00","#b38f00","#6b006b","#8fb300","#b30000","#bebebe","#80ff80","#80c9ff","#ffc080","#ffe680","#aa80ff","#ee00cc","#ff8080","#666600","#ffbfff","#00ffcc","#cc6699","#999900"]};Rickshaw.namespace("Rickshaw.Fixtures.RandomData");Rickshaw.Fixtures.RandomData=function(timeInterval){var addData;timeInterval=timeInterval||1;var lastRandomValue=200;var timeBase=Math.floor((new Date).getTime()/1e3);this.addData=function(data){var randomValue=Math.random()*100+15+lastRandomValue;var index=data[0].length;var counter=1;data.forEach(function(series){var randomVariance=Math.random()*20;var v=randomValue/25+counter++ +(Math.cos(index*counter*11/960)+2)*15+(Math.cos(index/7)+2)*7+(Math.cos(index/17)+2)*1;series.push({x:index*timeInterval+timeBase,y:v+randomVariance})});lastRandomValue=randomValue*.85};this.removeData=function(data){data.forEach(function(series){series.shift()});timeBase+=timeInterval}};Rickshaw.namespace("Rickshaw.Fixtures.Time");Rickshaw.Fixtures.Time=function(){var tzOffset=(new Date).getTimezoneOffset()*60;var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getUTCFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getUTCFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getUTCMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getUTCDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getUTCMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getUTCSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getUTCSeconds()+"s"}},{name:"decisecond",seconds:1/10,formatter:function(d){return d.getUTCMilliseconds()+"ms"}},{name:"centisecond",seconds:1/100,formatter:function(d){return d.getUTCMilliseconds()+"ms"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toUTCString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var nearFuture;var rounded;if(unit.name=="month"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setUTCFullYear(nearFuture.getUTCFullYear());rounded.setUTCMonth(nearFuture.getUTCMonth());rounded.setUTCDate(1);rounded.setUTCHours(0);rounded.setUTCMinutes(0);rounded.setUTCSeconds(0);rounded.setUTCMilliseconds(0);return rounded.getTime()/1e3}if(unit.name=="year"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setUTCFullYear(nearFuture.getUTCFullYear());rounded.setUTCMonth(0);rounded.setUTCDate(1);rounded.setUTCHours(0);rounded.setUTCMinutes(0);rounded.setUTCSeconds(0);rounded.setUTCMilliseconds(0);return rounded.getTime()/1e3}return Math.ceil(time/unit.seconds)*unit.seconds}};Rickshaw.namespace("Rickshaw.Fixtures.Time.Local");Rickshaw.Fixtures.Time.Local=function(){var tzOffset=(new Date).getTimezoneOffset()*60;var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getSeconds()+"s"}},{name:"decisecond",seconds:1/10,formatter:function(d){return d.getMilliseconds()+"ms"}},{name:"centisecond",seconds:1/100,formatter:function(d){return d.getMilliseconds()+"ms"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var nearFuture;var rounded;if(unit.name=="day"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);rounded.setDate(nearFuture.getDate());rounded.setMonth(nearFuture.getMonth());rounded.setFullYear(nearFuture.getFullYear());return rounded.getTime()/1e3}if(unit.name=="month"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);rounded.setDate(1);rounded.setMonth(nearFuture.getMonth());rounded.setFullYear(nearFuture.getFullYear());return rounded.getTime()/1e3}if(unit.name=="year"){nearFuture=new Date((time+unit.seconds-1)*1e3);rounded=new Date(0);rounded.setFullYear(nearFuture.getFullYear());rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);rounded.setDate(1);rounded.setMonth(0);return rounded.getTime()/1e3}return Math.ceil(time/unit.seconds)*unit.seconds}};Rickshaw.namespace("Rickshaw.Fixtures.Number");Rickshaw.Fixtures.Number.formatKMBT=function(y){var abs_y=Math.abs(y);if(abs_y>=1e12){return y/1e12+"T"}else if(abs_y>=1e9){return y/1e9+"B"}else if(abs_y>=1e6){return y/1e6+"M"}else if(abs_y>=1e3){return y/1e3+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.Fixtures.Number.formatBase1024KMGTP=function(y){var abs_y=Math.abs(y);if(abs_y>=0x4000000000000){return y/0x4000000000000+"P"}else if(abs_y>=1099511627776){return y/1099511627776+"T"}else if(abs_y>=1073741824){return y/1073741824+"G"}else if(abs_y>=1048576){return y/1048576+"M"}else if(abs_y>=1024){return y/1024+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.namespace("Rickshaw.Color.Palette");Rickshaw.Color.Palette=function(args){var color=new Rickshaw.Fixtures.Color;args=args||{};this.schemes={};this.scheme=color.schemes[args.scheme]||args.scheme||color.schemes.colorwheel;this.runningIndex=0;this.generatorIndex=0;if(args.interpolatedStopCount){var schemeCount=this.scheme.length-1;var i,j,scheme=[];for(i=0;iself.graph.x.range()[1]){if(annotation.element){annotation.line.classList.add("offscreen");annotation.element.style.display="none"}annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.add("offscreen")});return}if(!annotation.element){var element=annotation.element=document.createElement("div");element.classList.add("annotation");this.elements.timeline.appendChild(element);element.addEventListener("click",function(e){element.classList.toggle("active");annotation.line.classList.toggle("active");annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.toggle("active")})},false)}annotation.element.style.left=left+"px";annotation.element.style.display="block";annotation.boxes.forEach(function(box){var element=box.element;if(!element){element=box.element=document.createElement("div");element.classList.add("content");element.innerHTML=box.content;annotation.element.appendChild(element);annotation.line=document.createElement("div");annotation.line.classList.add("annotation_line");self.graph.element.appendChild(annotation.line);if(box.end){box.rangeElement=document.createElement("div");box.rangeElement.classList.add("annotation_range");self.graph.element.appendChild(box.rangeElement)}}if(box.end){var annotationRangeStart=left;var annotationRangeEnd=Math.min(self.graph.x(box.end),self.graph.x.range()[1]);if(annotationRangeStart>annotationRangeEnd){annotationRangeEnd=left;annotationRangeStart=Math.max(self.graph.x(box.end),self.graph.x.range()[0])}var annotationRangeWidth=annotationRangeEnd-annotationRangeStart;box.rangeElement.style.left=annotationRangeStart+"px";box.rangeElement.style.width=annotationRangeWidth+"px";box.rangeElement.classList.remove("offscreen")}annotation.line.classList.remove("offscreen");annotation.line.style.left=left+"px"})},this)};this.graph.onUpdate(function(){self.update()})};Rickshaw.namespace("Rickshaw.Graph.Axis.Time");Rickshaw.Graph.Axis.Time=function(args){var self=this;this.graph=args.graph;this.elements=[];this.ticksTreatment=args.ticksTreatment||"plain";this.fixedTimeUnit=args.timeUnit;var time=args.timeFixture||new Rickshaw.Fixtures.Time;this.appropriateTimeUnit=function(){var unit;var units=time.units;var domain=this.graph.x.domain();var rangeSeconds=domain[1]-domain[0];units.forEach(function(u){if(Math.floor(rangeSeconds/u.seconds)>=2){unit=unit||u}});return unit||time.units[time.units.length-1]};this.tickOffsets=function(){var domain=this.graph.x.domain();var unit=this.fixedTimeUnit||this.appropriateTimeUnit();var count=Math.ceil((domain[1]-domain[0])/unit.seconds);var runningTick=domain[0];var offsets=[];for(var i=0;iself.graph.x.range()[1])return;var element=document.createElement("div");element.style.left=self.graph.x(o.value)+"px";element.classList.add("x_tick");element.classList.add(self.ticksTreatment);var title=document.createElement("div");title.classList.add("title");title.innerHTML=o.unit.formatter(new Date(o.value*1e3));element.appendChild(title);self.graph.element.appendChild(element);self.elements.push(element)})};this.graph.onUpdate(function(){self.render()})};Rickshaw.namespace("Rickshaw.Graph.Axis.X");Rickshaw.Graph.Axis.X=function(args){var self=this;var berthRate=.1;this.initialize=function(args){this.graph=args.graph;this.orientation=args.orientation||"top";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";if(args.element){this.element=args.element;this._discoverSize(args.element,args);this.vis=d3.select(args.element).append("svg:svg").attr("height",this.height).attr("width",this.width).attr("class","rickshaw_graph x_axis_d3");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}this.graph.onUpdate(function(){self.render()})};this.setSize=function(args){args=args||{};if(!this.element)return;this._discoverSize(this.element.parentNode,args);this.vis.attr("height",this.height).attr("width",this.width*(1+berthRate));var berth=Math.floor(this.width*berthRate/2);this.element.style.left=-1*berth+"px"};this.render=function(){if(this._renderWidth!==undefined&&this.graph.width!==this._renderWidth)this.setSize({auto:true});var axis=d3.svg.axis().scale(this.graph.x).orient(this.orientation);axis.tickFormat(args.tickFormat||function(x){return x});if(this.tickValues)axis.tickValues(this.tickValues);this.ticks=this.staticTicks||Math.floor(this.graph.width/this.pixelsPerTick);var berth=Math.floor(this.width*berthRate/2)||0;var transform;if(this.orientation=="top"){var yOffset=this.height||this.graph.height;transform="translate("+berth+","+yOffset+")"}else{transform="translate("+berth+", 0)"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["x_ticks_d3",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));var gridSize=(this.orientation=="bottom"?1:-1)*this.graph.height;this.graph.vis.append("svg:g").attr("class","x_grid_d3").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize));this._renderHeight=this.graph.height};this._discoverSize=function(element,args){if(typeof window!=="undefined"){var style=window.getComputedStyle(element,null);var elementHeight=parseInt(style.getPropertyValue("height"),10);if(!args.auto){var elementWidth=parseInt(style.getPropertyValue("width"),10)}}this.width=(args.width||elementWidth||this.graph.width)*(1+berthRate);this.height=args.height||elementHeight||40};this.initialize(args)};Rickshaw.namespace("Rickshaw.Graph.Axis.Y");Rickshaw.Graph.Axis.Y=Rickshaw.Class.create({initialize:function(args){this.graph=args.graph;this.orientation=args.orientation||"right";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";this.tickFormat=args.tickFormat||function(y){return y};this.berthRate=.1;if(args.element){this.element=args.element;this.vis=d3.select(args.element).append("svg:svg").attr("class","rickshaw_graph y_axis");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}var self=this;this.graph.onUpdate(function(){self.render()})},setSize:function(args){args=args||{};if(!this.element)return;if(typeof window!=="undefined"){var style=window.getComputedStyle(this.element.parentNode,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);if(!args.auto){var elementHeight=parseInt(style.getPropertyValue("height"),10)}}this.width=args.width||elementWidth||this.graph.width*this.berthRate;this.height=args.height||elementHeight||this.graph.height;this.vis.attr("width",this.width).attr("height",this.height*(1+this.berthRate));var berth=this.height*this.berthRate;if(this.orientation=="left"){this.element.style.top=-1*berth+"px"}},render:function(){if(this._renderHeight!==undefined&&this.graph.height!==this._renderHeight)this.setSize({auto:true});this.ticks=this.staticTicks||Math.floor(this.graph.height/this.pixelsPerTick);var axis=this._drawAxis(this.graph.y);this._drawGrid(axis);this._renderHeight=this.graph.height},_drawAxis:function(scale){var axis=d3.svg.axis().scale(scale).orient(this.orientation);axis.tickFormat(this.tickFormat);if(this.tickValues)axis.tickValues(this.tickValues);if(this.orientation=="left"){var berth=this.height*this.berthRate;var transform="translate("+this.width+", "+berth+")"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["y_ticks",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));return axis},_drawGrid:function(axis){var gridSize=(this.orientation=="right"?1:-1)*this.graph.width;this.graph.vis.append("svg:g").attr("class","y_grid").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize))}});Rickshaw.namespace("Rickshaw.Graph.Axis.Y.Scaled");Rickshaw.Graph.Axis.Y.Scaled=Rickshaw.Class.create(Rickshaw.Graph.Axis.Y,{initialize:function($super,args){if(typeof args.scale==="undefined"){throw new Error("Scaled requires scale")}this.scale=args.scale;if(typeof args.grid==="undefined"){this.grid=true}else{this.grid=args.grid}$super(args)},_drawAxis:function($super,scale){var domain=this.scale.domain();var renderDomain=this.graph.renderer.domain().y;var extents=[Math.min.apply(Math,domain),Math.max.apply(Math,domain)];var extentMap=d3.scale.linear().domain([0,1]).range(extents);var adjExtents=[extentMap(renderDomain[0]),extentMap(renderDomain[1])]; 2 | var adjustment=d3.scale.linear().domain(extents).range(adjExtents);var adjustedScale=this.scale.copy().domain(domain.map(adjustment)).range(scale.range());return $super(adjustedScale)},_drawGrid:function($super,axis){if(this.grid){$super(axis)}}});Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight");Rickshaw.Graph.Behavior.Series.Highlight=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;var colorSafe={};var activeLine=null;var disabledColor=args.disabledColor||function(seriesColor){return d3.interpolateRgb(seriesColor,d3.rgb("#d8d8d8"))(.8).toString()};this.addHighlightEvents=function(l){l.element.addEventListener("mouseover",function(e){if(activeLine)return;else activeLine=l;self.legend.lines.forEach(function(line){if(l===line){if(self.graph.renderer.unstack&&(line.series.renderer?line.series.renderer.unstack:true)){var seriesIndex=self.graph.series.indexOf(line.series);line.originalIndex=seriesIndex;var series=self.graph.series.splice(seriesIndex,1)[0];self.graph.series.push(series)}return}colorSafe[line.series.name]=colorSafe[line.series.name]||line.series.color;line.series.color=disabledColor(line.series.color)});self.graph.update()},false);l.element.addEventListener("mouseout",function(e){if(!activeLine)return;else activeLine=null;self.legend.lines.forEach(function(line){if(l===line&&line.hasOwnProperty("originalIndex")){var series=self.graph.series.pop();self.graph.series.splice(line.originalIndex,0,series);delete line.originalIndex}if(colorSafe[line.series.name]){line.series.color=colorSafe[line.series.name]}});self.graph.update()},false)};if(this.legend){this.legend.lines.forEach(function(l){self.addHighlightEvents(l)})}};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order");Rickshaw.Graph.Behavior.Series.Order=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;if(typeof window.$=="undefined"){throw"couldn't find jQuery at window.$"}if(typeof window.$.ui=="undefined"){throw"couldn't find jQuery UI at window.$.ui"}$(function(){$(self.legend.list).sortable({containment:"parent",tolerance:"pointer",update:function(event,ui){var series=[];$(self.legend.list).find("li").each(function(index,item){if(!item.series)return;series.push(item.series)});for(var i=self.graph.series.length-1;i>=0;i--){self.graph.series[i]=series.shift()}self.graph.update()}});$(self.legend.list).disableSelection()});this.graph.onUpdate(function(){var h=window.getComputedStyle(self.legend.element).height;self.legend.element.style.height=h})};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle");Rickshaw.Graph.Behavior.Series.Toggle=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;this.addAnchor=function(line){var anchor=document.createElement("a");anchor.innerHTML="✔";anchor.classList.add("action");line.element.insertBefore(anchor,line.element.firstChild);anchor.onclick=function(e){if(line.series.disabled){line.series.enable();line.element.classList.remove("disabled")}else{if(this.graph.series.filter(function(s){return!s.disabled}).length<=1)return;line.series.disable();line.element.classList.add("disabled")}}.bind(this);var label=line.element.getElementsByTagName("span")[0];label.onclick=function(e){var disableAllOtherLines=line.series.disabled;if(!disableAllOtherLines){for(var i=0;idomainX){dataIndex=Math.abs(domainX-data[i].x)0){alignables.forEach(function(el){el.classList.remove("left");el.classList.add("right")});var rightAlignError=this._calcLayoutError(alignables);if(rightAlignError>leftAlignError){alignables.forEach(function(el){el.classList.remove("right");el.classList.add("left")})}}if(typeof this.onRender=="function"){this.onRender(args)}},_calcLayoutError:function(alignables){var parentRect=this.element.parentNode.getBoundingClientRect();var error=0;var alignRight=alignables.forEach(function(el){var rect=el.getBoundingClientRect();if(!rect.width){return}if(rect.right>parentRect.right){error+=rect.right-parentRect.right}if(rect.leftyMax)yMax=y});if(!series.length)return;if(series[0].xxMax)xMax=series[series.length-1].x});xMin-=(xMax-xMin)*this.padding.left;xMax+=(xMax-xMin)*this.padding.right;yMin=this.graph.min==="auto"?yMin:this.graph.min||0;yMax=this.graph.max===undefined?yMax:this.graph.max;if(this.graph.min==="auto"||yMin<0){yMin-=(yMax-yMin)*this.padding.bottom}if(this.graph.max===undefined){yMax+=(yMax-yMin)*this.padding.top}return{x:[xMin,xMax],y:[yMin,yMax]}},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var data=series.filter(function(s){return!s.disabled}).map(function(s){return s.stack});var nodes=vis.selectAll("path").data(data).enter().append("svg:path").attr("d",this.seriesPathFactory());var i=0;series.forEach(function(series){if(series.disabled)return;series.path=nodes[0][i++];this._styleSeries(series)},this)},_styleSeries:function(series){var fill=this.fill?series.color:"none";var stroke=this.stroke?series.color:"none";series.path.setAttribute("fill",fill);series.path.setAttribute("stroke",stroke);series.path.setAttribute("stroke-width",this.strokeWidth);series.path.setAttribute("class",series.className)},configure:function(args){args=args||{};Rickshaw.keys(this.defaults()).forEach(function(key){if(!args.hasOwnProperty(key)){this[key]=this[key]||this.graph[key]||this.defaults()[key];return}if(typeof this.defaults()[key]=="object"){Rickshaw.keys(this.defaults()[key]).forEach(function(k){this[key][k]=args[key][k]!==undefined?args[key][k]:this[key][k]!==undefined?this[key][k]:this.defaults()[key][k]},this)}else{this[key]=args[key]!==undefined?args[key]:this[key]!==undefined?this[key]:this.graph[key]!==undefined?this.graph[key]:this.defaults()[key]}},this)},setStrokeWidth:function(strokeWidth){if(strokeWidth!==undefined){this.strokeWidth=strokeWidth}},setTension:function(tension){if(tension!==undefined){this.tension=tension}}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Line");Rickshaw.Graph.Renderer.Line=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"line",defaults:function($super){return Rickshaw.extend($super(),{unstack:true,fill:false,stroke:true})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.line().x(function(d){return graph.x(d.x)}).y(function(d){return graph.y(d.y)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Stack");Rickshaw.Graph.Renderer.Stack=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"stack",defaults:function($super){return Rickshaw.extend($super(),{fill:true,stroke:false,unstack:false})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.area().x(function(d){return graph.x(d.x)}).y0(function(d){return graph.y(d.y0)}).y1(function(d){return graph.y(d.y+d.y0)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Bar");Rickshaw.Graph.Renderer.Bar=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"bar",defaults:function($super){var defaults=Rickshaw.extend($super(),{gapSize:.05,unstack:false});delete defaults.tension;return defaults},initialize:function($super,args){args=args||{};this.gapSize=args.gapSize||this.gapSize;$super(args)},domain:function($super){var domain=$super();var frequentInterval=this._frequentInterval(this.graph.stackedData.slice(-1).shift());domain.x[1]+=Number(frequentInterval.magnitude);return domain},barWidth:function(series){var frequentInterval=this._frequentInterval(series.stack);var barWidth=this.graph.x(series.stack[0].x+frequentInterval.magnitude*(1-this.gapSize));return barWidth},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var barWidth=this.barWidth(series.active()[0]);var barXOffset=0;var activeSeriesCount=series.filter(function(s){return!s.disabled}).length;var seriesBarWidth=this.unstack?barWidth/activeSeriesCount:barWidth;var transform=function(d){var matrix=[1,0,0,d.y<0?-1:1,0,d.y<0?graph.y.magnitude(Math.abs(d.y))*2:0];return"matrix("+matrix.join(",")+")"};series.forEach(function(series){if(series.disabled)return;var barWidth=this.barWidth(series);var nodes=vis.selectAll("path").data(series.stack.filter(function(d){return d.y!==null})).enter().append("svg:rect").attr("x",function(d){return graph.x(d.x)+barXOffset}).attr("y",function(d){return graph.y(d.y0+Math.abs(d.y))*(d.y<0?-1:1)}).attr("width",seriesBarWidth).attr("height",function(d){return graph.y.magnitude(Math.abs(d.y))}).attr("transform",transform);Array.prototype.forEach.call(nodes[0],function(n){n.setAttribute("fill",series.color)});if(this.unstack)barXOffset+=seriesBarWidth},this)},_frequentInterval:function(data){var intervalCounts={};for(var i=0;i0){this[0].data.forEach(function(plot){item.data.push({x:plot.x,y:0})})}else if(item.data.length===0){item.data.push({x:this.timeBase-(this.timeInterval||0),y:0})}this.push(item);if(this.legend){this.legend.addLine(this.itemByName(item.name))}},addData:function(data,x){var index=this.getIndex();Rickshaw.keys(data).forEach(function(name){if(!this.itemByName(name)){this.addItem({name:name})}},this);this.forEach(function(item){item.data.push({x:x||(index*this.timeInterval||1)+this.timeBase,y:data[item.name]||0})},this)},getIndex:function(){return this[0]&&this[0].data&&this[0].data.length?this[0].data.length:0},itemByName:function(name){for(var i=0;i1;i--){this.currentSize+=1;this.currentIndex+=1;this.forEach(function(item){item.data.unshift({x:((i-1)*this.timeInterval||1)+this.timeBase,y:0,i:i})},this)}}},addData:function($super,data,x){$super(data,x);this.currentSize+=1;this.currentIndex+=1;if(this.maxDataPoints!==undefined){while(this.currentSize>this.maxDataPoints){this.dropData()}}},dropData:function(){this.forEach(function(item){item.data.splice(0,1)});this.currentSize-=1},getIndex:function(){return this.currentIndex}}); --------------------------------------------------------------------------------