1 """
2 Build selection lists.
3
4 Copyright 2008-2013 Research Foundation State University of New York
5 This file is part of QUB Express.
6
7 QUB Express is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 QUB Express is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License,
18 named LICENSE.txt, in the QUB Express program directory. If not, see
19 <http://www.gnu.org/licenses/>.
20
21 """
22
23 import gobject
24 import gtk
25 from gtk import gdk
26 import numpy
27 import os
28
29 import qubx.data_types
30 import qubx.faces
31 import qubx.GTK
32 import qubx.pyenv
33 import qubx.settings
34 import qubx.settingsGTK
35 import qubx.table
36 import qubx.tree
37 from qubx.accept import *
38 from qubx.GTK import pack_item, pack_space, pack_label, pack_button, pack_check, pack_radio
39 from qubx.settings import Property, Propertied
40 from qubx.util_types import *
41
42 CHOP_NONE, CHOP_TIME, CHOP_IDL = (0, 1, 2)
43 BREAK_CLASS, BREAK_COUNT = (0, 1)
44
45 RADIO_SPACE = 16
46 CHOPTIME_SPACE = 36
47 CHOPIDL_SPACE = 36
48
49 @Propertied(Property('chop', CHOP_TIME, "choice of algorithm",
50 value_names = {CHOP_NONE : 'CHOP_NONE', CHOP_TIME : 'CHOP_TIME', CHOP_IDL : 'CHOP_IDL'}),
51 Property('chop_skip', 0.0, "msec to skip before each selection"),
52 Property('chop_get', 100.0, "msec duration of each selection"),
53 Property('chop_skip_after', 0.0, "msec to skip after each selection"),
54 Property('chop_discard_initial', 0.0, "msec to skip each segment before chopping"),
55 Property('chop_discard_final', 0.0, "msec to skip each segment after chopping"),
56 Property('chop_once', False, "True to stop after first selection each segment"),
57 Property('discard_partial_sels', True, "False to allow incomplete final selection"),
58 Property('break_at', BREAK_CLASS, "burst termination criterion",
59 value_names = {BREAK_CLASS : 'BREAK_CLASS', BREAK_COUNT : 'BREAK_COUNT'}),
60 Property("break_class", 0, "BREAK_CLASS: class index of burst terminator"),
61 Property("break_time", 500.0, "BREAK_CLASS: min/max msec of burst terminator"),
62 Property('break_time_lt', False, 'BREAK_CLASS: True to list events longer than break_time'),
63 Property("break_count", 10, "BREAK_COUNT: events per selection"),
64 Property("discard_on_class", True, "True to discard selections containing discard_class"),
65 Property("discard_class", 2, "see discard_on_class"),
66 Property("discard_on_length", False, "True to discard too-short selections"),
67 Property("discard_length", 10.0, "Minimum selection duration in msec, if discard_on_length"),
68 Property("discard_on_count", True, "True to discard selections with fewer than discard_count events"),
69 Property('discard_count', 10, "see discard_on_count"),
70 Property('drop_final', True, "True to balance the number of open and closed events"),
71 Property('list_name', 'Chopped', "name of output selection list"),
72 Property('replace_list', True, 'True to clear the output list before chopping'),
73 Property('switch_to_output', True, 'False to leave the current list in front.')
74 )
77 QubX = qubx.pyenv.env.globals['QubX']
78 gtk.Dialog.__init__(self, '%s - Chop' % QubX.appname, qubx.GTK.get_active_window(), gtk.DIALOG_MODAL,
79 buttons=('Cancel', gtk.RESPONSE_REJECT, 'OK', gtk.RESPONSE_ACCEPT))
80 self.__ref = Reffer()
81 self.__running = False
82
83 self.propertied_connect_settings('Chop')
84
85 qubx.notebook.Notebook.register_auto('ChopIdl.Segments', 'Segments table, on Chop by idealization', True)
86 qubx.notebook.Notebook.register_auto('ChopIdl.Bursts', 'Bursts table, on Chop by idealization', True)
87
88 panel = pack_item(gtk.HBox(), self.vbox)
89 pack_label('Data source:', panel)
90 self.chkDSFile = pack_radio('Whole file', panel, active=True, on_toggle=self.__onToggleDS)
91 self.chkDSScreen = pack_radio('Screen', panel, group=self.chkDSFile, on_toggle=self.__onToggleDS)
92 self.chkDSList = pack_radio('List', panel, group=self.chkDSFile, on_toggle=self.__onToggleDS)
93 self.mnuPresets = pack_item(qubx.settingsGTK.PresetsMenu('Chop', 'QubX'), panel, at_end=True)
94
95 panel = pack_item(gtk.HBox(), self.vbox)
96 v = pack_item(gtk.VBox(), panel, expand=True)
97 self.lblChop = pack_label('Chop by:', v)
98 v = pack_item(gtk.VBox(), panel, expand=True)
99 pack_space(panel, x=6)
100
101 vh = pack_item(gtk.HBox(), v)
102
103 self.chkChopNone = pack_radio('None', vh, show=False)
104 pack_space(vh, x=RADIO_SPACE)
105 self.chkChopTime = pack_radio('Time...', vh, group=self.chkChopNone)
106 pack_space(vh, x=RADIO_SPACE)
107 self.chkChopIdl = pack_radio('Idealization...', vh, group=self.chkChopNone)
108 pack_space(vh, x=RADIO_SPACE)
109 self.propertied_connect_radios('chop', [(CHOP_NONE, self.chkChopNone),
110 (CHOP_TIME, self.chkChopTime),
111 (CHOP_IDL, self.chkChopIdl)])
112
113 self.panChopTime = pack_item(gtk.VBox(), self.vbox, expand=True, show=(self.chop == CHOP_TIME))
114 h = pack_item(gtk.HBox(), self.panChopTime)
115 pack_space(h, x=CHOPTIME_SPACE)
116 lbl = pack_label('Skip:', h)
117 self.txtChopSkip = pack_item(qubx.GTK.NumEntry(self.chop_skip, acceptFloatGreaterThanOrEqualTo(0.0), '%.3g', width_chars=6), h)
118 self.propertied_connect_NumEntry('chop_skip', self.txtChopSkip)
119 for widget in (lbl, self.txtChopSkip):
120 widget.set_tooltip_text('number of msec to omit before of each selection')
121 pack_space(h, x=CHOPTIME_SPACE)
122 lbl = pack_label('Get:', h)
123 self.txtChopGet = pack_item(qubx.GTK.NumEntry(self.chop_get, acceptFloatGreaterThanOrEqualTo(0.0), '%.3g', width_chars=6), h)
124 self.propertied_connect_NumEntry('chop_get', self.txtChopGet)
125 for widget in (lbl, self.txtChopGet):
126 widget.set_tooltip_text('length of output selections in msec')
127 pack_space(h, x=CHOPTIME_SPACE)
128 lbl = pack_label('Skip:', h)
129 self.txtChopSkipAfter = pack_item(qubx.GTK.NumEntry(self.chop_skip_after, acceptFloatGreaterThanOrEqualTo(0.0), '%.3g', width_chars=6), h)
130 self.propertied_connect_NumEntry('chop_skip_after', self.txtChopSkipAfter)
131 for widget in (lbl, self.txtChopSkipAfter):
132 widget.set_tooltip_text('number of msec to omit after each selection')
133 h = pack_item(gtk.HBox(), self.panChopTime)
134 pack_space(h, x=CHOPTIME_SPACE)
135 lbl = pack_label('Discard initial:', h)
136 self.txtChopDiscardInitial = pack_item(qubx.GTK.NumEntry(self.chop_discard_initial, acceptFloatGreaterThanOrEqualTo(0.0), '%.3g', width_chars=6), h)
137 self.propertied_connect_NumEntry('chop_discard_initial', self.txtChopDiscardInitial)
138 for widget in (lbl, self.txtChopDiscardInitial):
139 widget.set_tooltip_text('number of msec to omit each segment before chopping any selections')
140 pack_space(h, x=CHOPTIME_SPACE)
141 lbl = pack_label('Discard final:', h)
142 self.txtChopDiscardFinal = pack_item(qubx.GTK.NumEntry(self.chop_discard_final, acceptFloatGreaterThanOrEqualTo(0.0), '%.3g', width_chars=6), h)
143 self.propertied_connect_NumEntry('chop_discard_final', self.txtChopDiscardFinal)
144 for widget in (lbl, self.txtChopDiscardFinal):
145 widget.set_tooltip_text('number of msec to omit from chopping, at end of each segment')
146 h = pack_item(gtk.HBox(), self.panChopTime)
147 pack_space(h, x=CHOPTIME_SPACE)
148 pack_label('(all times in ms)', h, at_end=True, expand=True)
149 self.chkChopOnce = pack_check('once per segment', h)
150 self.propertied_connect_check('chop_once', self.chkChopOnce)
151 h = pack_item(gtk.HBox(), self.panChopTime)
152 pack_space(h, x=CHOPTIME_SPACE)
153 self.chkDiscardPartialSels = pack_check('discard partial sels', h)
154 self.propertied_connect_check('discard_partial_sels', self.chkDiscardPartialSels)
155 self.chkDiscardPartialSels.set_tooltip_text("omits the final selection if it ends early")
156
157 self.panChopIdl = pack_item(gtk.VBox(), self.vbox, show=(self.chop == CHOP_IDL))
158 ph = pack_item(gtk.HBox(), self.panChopIdl)
159 pack_space(ph, x=CHOPIDL_SPACE)
160 phv = pack_item(gtk.VBox(), ph)
161 phvh = pack_item(gtk.HBox(), phv)
162 pack_label('Break at:', phv)
163 pack_space(ph, x=6)
164 phv = pack_item(gtk.VBox(), ph)
165 phvh = pack_item(gtk.HBox(), phv)
166 self.chkBreakClass = pack_radio('class ', phvh)
167 self.txtBreakClass = pack_item(qubx.GTK.NumEntry(self.break_class, acceptIntGreaterThanOrEqualTo(0), width_chars=3), phvh)
168 self.propertied_connect_NumEntry('break_class', self.txtBreakClass)
169 pack_label(' event', phvh)
170 self.mnuBreakLT = pack_item(qubx.GTK.StaticComboList(['longer', 'shorter']), phvh)
171 self.mnuBreakLT.active_i = int(self.break_time_lt)
172 self.mnuBreakLT.OnChange += self.__ref(self.__onChangeBreakLT)
173 pack_label('than ', phvh)
174 self.txtBreakTime = pack_item(qubx.GTK.NumEntry(self.break_time, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), phvh)
175 self.propertied_connect_NumEntry('break_time', self.txtBreakTime)
176 pack_label(' ms', phvh)
177 phvh = pack_item(gtk.HBox(), phv)
178 self.chkBreakCount = pack_radio('every ', phvh, group=self.chkBreakClass)
179 self.txtBreakCount = pack_item(qubx.GTK.NumEntry(self.break_count, acceptIntGreaterThan(0), width_chars=5), phvh)
180 self.propertied_connect_NumEntry('break_count', self.txtBreakCount)
181 self.propertied_connect_radios('break_at', [(BREAK_CLASS, self.chkBreakClass),
182 (BREAK_COUNT, self.chkBreakCount)])
183 pack_label(' events', phvh)
184
185 ph = pack_item(gtk.HBox(), self.panChopIdl)
186 pack_space(ph, x=CHOPIDL_SPACE)
187 phv = pack_item(gtk.VBox(), ph)
188 pack_label('Discard if:', phv)
189 pack_space(ph, x=6)
190 phv = pack_item(gtk.VBox(), ph)
191 phvh = pack_item(gtk.HBox(), phv)
192 self.chkDiscardClass = pack_check('dwells(s) in class >= ', phvh)
193 self.propertied_connect_check('discard_on_class', self.chkDiscardClass)
194 self.txtDiscardClass = pack_item(qubx.GTK.NumEntry(self.discard_class, acceptIntGreaterThanOrEqualTo(0), width_chars=3), phvh)
195 self.propertied_connect_NumEntry('discard_class', self.txtDiscardClass)
196 phvh = pack_item(gtk.HBox(), phv)
197 self.chkDiscardLength = pack_check('length < ', phvh)
198 self.propertied_connect_check('discard_on_length', self.chkDiscardLength)
199 self.txtDiscardLength = pack_item(qubx.GTK.NumEntry(self.discard_length, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), phvh)
200 self.propertied_connect_NumEntry('discard_length', self.txtDiscardLength)
201 pack_label(' ms', phvh)
202 phvh = pack_item(gtk.HBox(), phv)
203 self.chkDiscardCount = pack_check('dwell count < ', phvh)
204 self.propertied_connect_check('discard_on_count', self.chkDiscardCount)
205 self.txtDiscardCount = pack_item(qubx.GTK.NumEntry(self.discard_count, acceptIntGreaterThan(0), '%.3g', width_chars=5), phvh)
206 self.propertied_connect_NumEntry('discard_count', self.txtDiscardCount)
207 ph = pack_item(gtk.HBox(), self.panChopIdl)
208 pack_space(ph, x=CHOPIDL_SPACE)
209 self.chkDropFinal = pack_check('drop final dwell to balance counts', ph)
210 self.propertied_connect_check('drop_final', self.chkDropFinal)
211
212 h = pack_item(gtk.HBox(), self.vbox)
213 pack_label('Output list name:', h)
214 self.txtListName = pack_item(qubx.GTK.NumEntry(self.list_name), h, expand=True)
215 self.propertied_connect_NumEntry('list_name', self.txtListName)
216 h = pack_item(gtk.HBox(), self.vbox)
217 self.chkReplaceList = pack_check('clear list first', h)
218 self.propertied_connect_check('replace_list', self.chkReplaceList)
219 self.chkSwitchToList = pack_check('show output list', h)
220 self.propertied_connect_check('switch_to_output', self.chkSwitchToList)
247 - def run(self, silent=False):
287
288
289 if self.chop == CHOP_TIME:
290 settings_pairs = settings.as_pairs(keys=set(['chop', 'chop_skip', 'chop_get', 'chop_skip_after',
291 'chop_discard_initial', 'chop_discard_final',
292 'chop_once', 'discard_partial_sels', 'list_name', 'replace_list', 'switch_to_output']),
293 enums={'chop' : ['CHOP_NONE', 'CHOP_TIME', 'CHOP_IDL']})
294 qubx.pyenv.env.OnScriptable('Chop(%s, silent=True)' % ', '.join(['%s=%s'%(k,v) for k,v in settings_pairs]))
295
296 for seg in segs:
297 chop_seg(seg, lst, self.chop_skip, self.chop_get, self.chop_skip_after,
298 self.chop_discard_initial, self.chop_discard_final,
299 self.chop_once, self.discard_partial_sels, update_seg)
300 update_iseg[0] += 1
301
302
303 if self.chop == CHOP_IDL:
304 settings_pairs = settings.as_pairs(keys=set(['chop', 'break_at', 'break_class', 'break_time', 'break_time_lt', 'break_count',
305 'discard_on_class', 'discard_class', 'discard_on_length', 'discard_length',
306 'discard_on_count', 'discard_count', 'drop_final',
307 'list_name', 'replace_list']),
308 enums={'chop' : ['CHOP_NONE', 'CHOP_TIME', 'CHOP_IDL'],
309 'break_at' : ['BREAK_CLASS', 'BREAK_COUNT']})
310 qubx.pyenv.env.OnScriptable('Chop(%s, silent=True)' % ', '.join(['%s=%s'%(k,v) for k,v in settings_pairs]))
311
312 for seg in segs:
313 chop_idl(seg, segments, lst,
314 self.break_at, self.break_class, self.break_time, self.break_time_lt, self.break_count,
315 self.discard_on_class, self.discard_class, self.discard_on_length,
316 self.discard_length, self.discard_on_count, self.discard_count,
317 self.drop_final, update_seg)
318 update_iseg[0] += 1
319 view.measure_list(lst, 'Idealized.py', wait=True)
320
321
322 if self.chop == CHOP_IDL:
323 qubx.notebook.Notebook.send(qubx.tableGTK.TableView(segments).nbTable, auto_id='ChopIdl.Segments')
324 qubx.notebook.Notebook.send(qubx.tableGTK.TableView(lst).nbTable, auto_id='ChopIdl.Bursts')
325
326
327
328 UPDATE_PROG_SKIP = 2048
329
330
331 -def chop_seg(seg, lst, skip, get, skip_after, discard_initial, discard_final, once, discard_partial_sels, update):
332
333 n_discard_initial = int(round(discard_initial * 1e-3 / seg.sampling))
334 n_discard_final = int(round(discard_final * 1e-3 / seg.sampling))
335 if (n_discard_initial + n_discard_final) >= seg.n:
336 return
337 ifirst = int(round(skip * 1e-3 / seg.sampling))
338 ilast = int(round((skip+get)*1e-3 / seg.sampling)) - 1
339 ipast = int(round((skip+get+skip_after)*1e-3 / seg.sampling))
340 for ibegin in xrange(n_discard_initial, seg.n-n_discard_final, ipast):
341 if not (ibegin % UPDATE_PROG_SKIP):
342 if not update((ibegin - n_discard_initial) * 1.0 / (seg.n - n_discard_initial - n_discard_final)):
343 return
344 f_off = ibegin+ifirst
345 l_off = ibegin+ilast
346 if l_off >= seg.n:
347 if discard_partial_sels:
348 break
349 else:
350 l_off = seg.n - 1
351 if f_off <= l_off:
352 lst.insert_selection(seg.f+f_off, seg.f+l_off, Start=seg.start + f_off*1e3*seg.sampling,
353 Duration=(l_off - f_off + 1)*1e3*seg.sampling)
354 if once:
355 break
356
357 -def chop_idl(seg, segstats, bursts,
358 break_at, break_class, break_time, break_time_lt, break_count, discard_on_class, discard_class,
359 discard_on_length, discard_length, discard_on_count, discard_count, drop_final, update):
360 burst_count = [0]
361 durs = []
362 interbursts = []
363 last_end = [seg.start]
364 def add_seg(ff, ll, cc, dd):
365 n = len(ff)
366 if drop_final and (len(ff) > 2) and (cc[0] == cc[-1]):
367 n -= 1
368 if n < 1:
369 return
370 if discard_on_count and (n < discard_count):
371 return
372 dur = 0.0
373 for i in xrange(n):
374 if discard_on_class and (cc[i] >= discard_class):
375 return
376 dur += dd[i]
377 if discard_on_length and (dur < discard_length):
378 return
379 start = seg.start + seg.sampling*1e3*(ff[0] - seg.f)
380 dur = (ll[-1]-ff[0]+1)*1e3*seg.sampling
381 bursts.insert_selection(ff[0], ll[-1], Start=start,
382 Duration=dur,
383 Source_segment=seg.index,
384 Burst_index=burst_count[0])
385 if segstats.size:
386 stats_i = max(0, min(segstats.size-1, seg.index))
387 if burst_count[0] == 0:
388 segstats.set(stats_i, 'First_burst_start', start - seg.start)
389 else:
390 interbursts.append(start - last_end[0])
391 durs.append(dur)
392 last_end[0] = start + dur
393 burst_count[0] += 1
394
395 if break_at == BREAK_CLASS:
396 should_break_on_time = lambda cls, dur: (cls == break_class) and ((break_time_lt and (dur < break_time)) or ((not break_time_lt) and (dur > break_time)))
397 else:
398 should_break_on_time = lambda cls, dur: False
399
400 ff, ll, cc, dd = seg.get_idealization(mark_excluded=False, get_fragments=True, get_durations=True)
401 at = 0
402 while at < len(ff):
403 if not (at % UPDATE_PROG_SKIP):
404 if not update(at * 1.0 / len(ff)):
405 return
406 if should_break_on_time(cc[at], dd[at]):
407 at += 1
408 continue
409 past = at + 1
410 while past < len(ff):
411 if break_at == BREAK_CLASS:
412 if should_break_on_time(cc[past], dd[past]):
413 break
414 elif break_at == BREAK_COUNT:
415 if (past - at) == break_count:
416 break
417 past += 1
418 add_seg(ff[at:past], ll[at:past], cc[at:past], dd[at:past])
419 at = past
420
421 if segstats.size and bursts:
422 stats_i = max(0, min(segstats.size-1, seg.index))
423 if durs:
424 segstats.set(stats_i, 'Burst_count', len(durs))
425 segstats.set(stats_i, 'Burst_dur_mean', numpy.mean(durs))
426 segstats.set(stats_i, 'Burst_dur_median', numpy.median(durs))
427 if interbursts:
428 segstats.set(stats_i, 'Interburst_mean', numpy.mean(interbursts))
429 segstats.set(stats_i, 'Interburst_median', numpy.median(interbursts))
430
431 Chopper = None
432 -def Chop(silent=True, **kw):
433 """Creates a List of selections.
434
435 Keyword options:
436
437 * silent (=True): doesn't prompt graphically for options
438 * chop - CHOP_NONE, CHOP_TIME, or CHOP_IDL
439 * chop_skip
440 * chop_get
441 * chop_skip_after
442 * chop_once
443 * discard_partial_sels
444 * break_at - BREAK_CLASS or BREAK_COUNT
445 * break_class
446 * break_time
447 * break_time_lt
448 * break_count
449 * discard_on_class
450 * discard_class
451 * discard_on_length
452 * discard_length
453 * discard_on_count
454 * discard_count
455 * drop_final
456 * list_name
457 * replace_list
458 * switch_to_output
459 """
460 global Chopper
461 if not Chopper:
462 Chopper = ChopDialog()
463 if kw:
464 settings = qubx.settings.SettingsMgr['Chop']
465 settings.setPropertiesDict(**kw)
466 return Chopper.run(silent=silent)
467