-
Notifications
You must be signed in to change notification settings - Fork 5
/
tac-plots.html
409 lines (358 loc) · 14.3 KB
/
tac-plots.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="worker-builder.html">
<link rel="import" href="utils.html">
<link rel="import" href="managed-canvas.html">
<dom-module id="tac-plots">
<script is='worker-builder' id="worker" title="tac-plots" type='javascript/worker'>
"use strict";
var render_options = {
max_delta_t: 500, // miliseconds
n_bins: 100,
height: 50 //px, width matches n_bins exactly
};
var spike_times;
var timebase;
var pkey_to_array = new Map(); // Note that in the worker, key's are used only for arrays of spike-indices.
var pkey_to_rendered_options = new Map();
var pkeys_pending_render = [];
var timer;
var pkey_generation = 0;
var show;
var set_show = function(v){
show = v;
// note that we don't meddle with the pkeys_pending_render list here
// instead we use the timer to decide whether or not to bother doing any rendering
touch_timer();
}
var set_options = function(opts){
for(var k in render_options){
render_options[k] = opts[k] === undefined ? render_options[k] : opts[k];
}
pkeys_pending_render = [];
if(spike_times){
for(let pkey of pkey_to_array.keys()){
pkeys_pending_render.push(pkey);
}
}
touch_timer();
}
var set_spike_times = function(timebase_, arrays){
timebase = timebase_;
spike_times = arrays && arrays.data;
pkeys_pending_render = [];
if(spike_times){
for(let pkey of pkey_to_array.keys()){
pkeys_pending_render.push(pkey);
}
}
touch_timer();
}
var update_pkeys = function(changes, arrays){
if(changes.generation_remove !== pkey_generation){
changes.remove = [];
pkey_to_array.clear();
pkey_to_rendered_options.clear();
pkeys_pending_render = [];
console.log("tac-plots worker: key generation mismatch");
}
// note we don't both to remove keys from the pending list
// unless we change key generation, in which case we drop
// everything that's pending. This is ensures that when we
// attempt a render we can be sure that if we do find the
// array for the given key we know it's for the relevant
// generation and that it's still wanted.
var remove_pkeys = changes.remove || [];
for(let pkey of remove_pkeys){
pkey_to_array.delete(pkey);
pkey_to_rendered_options.delete(pkey);
}
if(changes.generation_add !== changes.generation_remove){
pkeys_pending_render = [];
pkey_generation = changes.generation_add;
}
var add_pkeys = changes.add || [];
for(let pkey of add_pkeys){
pkey_to_array.set(pkey, arrays[pkey]);
pkeys_pending_render.push(pkey);
}
touch_timer();
}
var touch_timer = function(){
// turns timer on if there's work to be done, otherwise stops it (if it was running)
// note it's not actually a timer, because the interval is zero!
// the point of using this is that it enables messages from main thread to interupt
// between ticks, we can thus cancel work as an when needed.
if(spike_times && pkeys_pending_render.length && show === 'y'){
timer = timer || setImmediate(timer_tick);
} else{
clearImmediate(timer);
timer = 0;
}
}
var timer_tick = function(){
timer = 0;
let batch_limit = 5; // a bit arbitrary
let arrays={};
let msgs = [];
// find batch_limit things to render...
while(pkeys_pending_render.length > 0 && msgs.length < batch_limit){
let pkey = pkeys_pending_render.shift();
if(pkey && pkey_to_array.get(pkey) && !is_equal_simple(pkey_to_rendered_options.get(pkey), render_options)){
msgs.push(get_group_hist(pkey, arrays));
}
}
if(msgs.length){
exec_main_b('plot_hist', msgs, arrays);
}
touch_timer();
}
var get_group_hist = function(pkey, arrays){
// note desired_max_delta_t and spike_times should both be in the same units
let group_times = pick(spike_times, pkey_to_array.get(pkey));
// build pairwise-diff histogram
let hist = new Uint32Array(render_options.n_bins+1);
get_group_hist_sub(hist, group_times, render_options.max_delta_t/1000*timebase,
render_options.max_delta_t/1000*timebase/render_options.n_bins);
hist = plot_histogram(hist, render_options.height);
let opts = Object.assign({}, render_options);
pkey_to_rendered_options.set(pkey, opts);
arrays[pkey + ' hist'] = hist;
return {
pkey: pkey,
generation: pkey_generation,
hist: pkey + ' hist',
dims: [opts.n_bins, opts.height],
render_options: opts
}
}
var get_group_hist_sub = function(ret, group_times, D, b){
// For every pair of spikes separated in time by no more than time D, bin
// up the time separation, with bin size b, and record it in hist, ret.
for(var later_idx=1, earlier_idx=0; later_idx<group_times.length; later_idx++){
let later_time = group_times[later_idx];
while (group_times[earlier_idx] < later_time - D){
earlier_idx++;
}
for(let i=earlier_idx; i<later_idx; i++){
ret[0 | ((later_time - group_times[i])/b)]++;
}
}
}
// TODO: come with a better method than copy-pasting utility functions ....
var max = function(vals, w){
let max = 0;
for(let ii=0; ii<w; ii++){
(vals[ii] > max) && (max = vals[ii]);
}
return max;
}
var plot_histogram = function(vals, h){
let w = vals.length-1;
let factor = h/max(vals, w);
let color_a = 0xff0000ff;
let im = new Uint32Array(w*h);
for(let x=0, y=vals[0]; x<w; x++){
let target_y = 0 | (vals[x]*factor);
for(; y<target_y; y++){
im[w*(h-y) + x] = color_a;
}
for(; y>target_y; y--){
im[w*(h-y) + x] = color_a;
}
im[w*(h-y) + x] = color_a;
}
return im;
}
var pick = function(from, indices){
// Take elements specified by indicies from the 1d array "from".
var result = new from.constructor(indices.length); //make an array of the same type as the from array
for(let i=0;i<indices.length;i++)
result[i] = from[indices[i]];
return result;
}
var is_equal_simple = function(a, b){
// very simple....
if((a && !b) || (!a && b)){
return false;
}
for(var aa in a){
if(b[aa] !== a[aa]){
return false;
}
}
for(let bb in b){
if(b[bb] !== a[bb]){
return false;
}
}
return true;
}
</script>
<template></template>
<script>
"use strict";
Polymer({
is:'tac-plots',
behaviors: [
Polymer.ShortcutNotifyerBehavior
], properties: {
max_delta_t: {
type: Number, // in miliseconds
value: 2,
notify: true
}, spike_times: {
type: Object, // array akey "data" and timebase Number in Hz (ie per second not per milisecond)
value: function(){ return {};},
notify: true
}, show: {
type: String, // "y" or "n" (to match other modules which have multiple toggle-able things)
value: "y",
notify: true
}, groups: {
type: Array,
value: function(){return [];},
notify: true,
observer: '_groups_set'
}, want_spike_times: {
type: Array,
notify: true,
observer: 'want_spike_times_set'
}
}, observers: [
'_groups_spliced(groups.splices)',
'_options_modified(max_delta_t)',
'_times_modified(spike_times)',
'_show_changed(show)'
], created: function(){
// copy-paste defaults from worker code above (could implement
// something more complicated, but hey this works and is easy).
this._options = {
max_delta_t: 500, // miliseconds
n_bins: 100,
height: 50 //px, width matches n_bins exactly
};
var worker_builder = Polymer.DomModule.import('tac-plots','#worker');
this._worker = worker_builder.create_for(this);
this._pkey_generation = 0;
this._times_is_on_worker = false;
}, want_spike_times_set: function(){
this._show_changed(this.show);
}, _update_okeys: function(okeys_to_remove, okeys_to_add, generation_remove, generation_add){
var am = Utils.typed_array_manager;
var added_arrays = {};
var pkeys_to_add = [];
if (this.show === 'y'){
// get clones of new arrays to send to worker
for(let okey of okeys_to_add){
if(!okey) continue;
pkeys_to_add.push(okey._pkey);
added_arrays[okey._pkey] = am.get_array_clone(okey.akey);
okey._tac_is_on_worker = true;
}
}
// free canvases, it's our responsiblity to do this, whereas
// freeing the inds arrays was the responsibility of cut-obj.
var pkeys_to_remove = [];
for(let okey of okeys_to_remove){
if(!okey) continue;
pkeys_to_remove.push(okey._pkey);
okey.tac = null;
okey._tac_other = null;
okey._tac_other_options = {};
okey._tac_is_on_worker = false;
}
// inform worker of the new/deleted arrays
this._worker.exec_b('update_pkeys', {
add: pkeys_to_add,
remove: pkeys_to_remove,
generation_remove: generation_remove,
generation_add: generation_add
}, added_arrays);
}, _groups_set: function(new_val, old_val){
this._groups_collection = Array.isArray(new_val) && Polymer.Collection.get(new_val);
this._update_okeys(old_val || [], new_val || [], this._pkey_generation, ++this._pkey_generation); // added keys are for a new generation
}, _groups_spliced: function(splices){
let parsed = Utils.parse_splices(splices);
this._update_okeys(parsed.removed, parsed.added, this._pkey_generation, this._pkey_generation);
}, _options_modified: function(){
this._options.max_delta_t = this.max_delta_t;
this._use_other_if_possible(this.groups || []);
this._worker.exec('set_options', this._options);
}, _times_modified: function(){
if(this.show === 'y'){
if(this.spike_times){
var arr = Utils.typed_array_manager.get_array_clone(this.spike_times.times);
this._worker.exec_b('set_spike_times', this.spike_times.timebase, {data: arr});
} else {
if(!this._worker) return;
this._worker.exec('set_spike_times', {});
}
this._times_is_on_worker = true;
} else {
if(!this.spike_times && this._times_is_on_worker){
this._worker.exec('set_spike_times', {});
}
this._times_is_on_worker = false;
}
}, _use_other_if_possible: function(okeys){
// The okey._tac_other stores the last canvas from the worker,
// with okey._tac_other_options containing the render settings used.
// Here we check whether the rendered options match what we currently
// want, and if so we set the visible okey.tac property from the
// okey._tac_other value. We also deal with hiding canvases on show='n'.
for(let okey of okeys) if(okey){
// are the okey.rm_x and okey._rm_x_other the same?
let same = okey.tac === okey._tac_other;
//0 : no-op, +1 : use _other values, -1: set to null
let op = this.show === 'n' ?
( okey.tac ? -1 : 0 )
: ( !same && Utils.is_equal_simple(okey._tac_other_options, this._options) ? 1 : 0 );
if(op){
okey.tac = op === 1 ? okey._tac_other : null;
this.shortcutNotify('tac', okey.tac, okey); // actual path: 'groups.' + okey._pkey + '.tac'
}
}
}, _show_changed: function(show){
if(!this.want_spike_times){
return;
}
this._use_other_if_possible(this.groups || []);
if (show === 'n'){
let idx = this.want_spike_times.indexOf("tac-plots");
(idx > -1) && this.splice('want_spike_times', idx, 1);
} else if(show === 'y'){
let idx = this.want_spike_times.indexOf("tac-plots");
(idx === -1) && this.push('want_spike_times', "tac-plots");
// while show was false we avoided sending times to worker...
if(!this._times_is_on_worker){
this._times_modified();
}
// and we avoided sending group inds to worker...
let added = [];
for(let okey of (this.groups || []))if(okey && !okey._tac_is_on_worker){
added.push(okey);
}
this._update_okeys([], added, this._pkey_generation, this._pkey_generation);
} else {
console.log("invalid value for tac show");
this.set('show', 'y');
return;
}
this._worker.exec('set_show', this.show); // worker needs to know even if it's all 'n', because then it won't bother doing anything.
}, plot_hist: function(msgs, arrays) {
for(let msg of msgs){
if(msg.generation !== this._pkey_generation)
continue; // pft! get with the times, dude.
let okey = this._groups_collection.getItem(msg.pkey);
if(!okey){
continue;
}
let cm = Utils.canvas_manager;
okey._tac_other = {buffer: arrays[msg.hist].buffer, dims: msg.dims};
okey._tac_other_options = msg.render_options;
this._use_other_if_possible([okey]);
}
}
});
</script>
</dom-module>