def create_displayed_values_toggle(self): """ Create the range sliders' visibility toggle button. Returns ------- Toggle A Toggle type button instance to control range sliders' visibility. """ displayed_values_toggle = Toggle(label="Displayed Values") displayed_values_toggle.on_click(self.toggle_range_sliders_visibility) return displayed_values_toggle
def create_index_sliders_toggle(self) -> Toggle: """ Create the index sliders' visibility toggle button. Returns ------- Toggle A Toggle type button instance to control index sliders' visibility. """ sliders_toggle = Toggle(label="Plane Indices", active=False) sliders_toggle.on_click(self.toggle_index_sliders_visibility) return sliders_toggle
class BasicOption: """ Basic option class. If you need to add further attributes to your options, create your class and inherit from this. """ number: int name: str gui_name: str selected: bool incompatibilities: List[str] def __init__(self, number: int, name: str, gui_name: str = "", selected: bool = False, incompatibilities: List[str] = []): self.number = number self.name = name self.gui_name = gui_name if gui_name else name self.selected = selected self.incompatibilities = incompatibilities self.incompatibility_count = 0 self.button = Toggle(label=self.gui_name, width=200, button_type="primary") self.button.on_click(self.button_function) def button_function(self, state): """ Disables incompatible options. """ if state: # styling self.button.button_type = "success" else: self.button.button_type = "primary" for incomp_opt in self.incompatibilities: # incompatibility logic if state: incomp_opt.incompatibility_count += 1 incomp_opt.button.disabled = True else: incomp_opt.incompatibility_count -= 1 if incomp_opt.incompatibility_count == 0: incomp_opt.button.disabled = False
def generate_gui(tsne, cut_extracellular_data, all_extra_spike_times, time_axis, cluster_info_file, use_existing_cluster, autocor_bin_number, sampling_freq, prb_file=None, k4=False, verbose=False): if k4: tsne_figure_size = [1000, 800] tsne_min_border_left = 50 spike_figure_size = [500, 500] hist_figure_size = [500, 500] heatmap_plot_size = [200, 800] clusters_table_size = [400, 300] layout_size = [1500, 1400] slider_size = [300, 100] user_info_size = [700, 80] else: tsne_figure_size = [850, 600] tsne_min_border_left = 10 spike_figure_size = [450, 300] hist_figure_size = [450, 300] heatmap_plot_size = [200, 800] clusters_table_size = [400, 400] layout_size = [1200, 800] slider_size = [270, 80] user_info_size = [450, 80] # Plots ------------------------------ # scatter plot global non_selected_points_alpha global selected_points_size global non_selected_points_size global update_old_selected_switch global previously_selected_spike_indices tsne_fig_tools = "pan,wheel_zoom,box_zoom,box_select,lasso_select,tap,resize,reset,save" tsne_figure = figure(tools=tsne_fig_tools, plot_width=tsne_figure_size[0], plot_height=tsne_figure_size[1], title='T-sne', min_border=10, min_border_left=tsne_min_border_left, webgl=True) tsne_source = ColumnDataSource({'tsne-x': tsne[0], 'tsne-y': tsne[1]}) tsne_selected_points_glyph = Circle(x='tsne-x', y='tsne-y', size=selected_points_size, line_alpha=0, fill_alpha=1, fill_color='red') tsne_nonselected_points_glyph = Circle(x='tsne-x', y='tsne-y', size=non_selected_points_size, line_alpha=0, fill_alpha=non_selected_points_alpha, fill_color='blue') tsne_invisible_points_glyph = Circle(x='tsne-x', y='tsne-y', size=selected_points_size, line_alpha=0, fill_alpha=0) tsne_nonselected_glyph_renderer = tsne_figure.add_glyph(tsne_source, tsne_nonselected_points_glyph, selection_glyph=tsne_invisible_points_glyph, nonselection_glyph=tsne_nonselected_points_glyph, name='tsne_nonselected_glyph_renderer') # note: the invisible glyph is required to be able to change the size of the selected points, since the # use of selection_glyph is usefull only for colors and alphas tsne_invinsible_glyph_renderer = tsne_figure.add_glyph(tsne_source, tsne_invisible_points_glyph, selection_glyph=tsne_selected_points_glyph, nonselection_glyph=tsne_invisible_points_glyph, name='tsne_invinsible_glyph_renderer') tsne_figure.select(BoxSelectTool).select_every_mousemove = False tsne_figure.select(LassoSelectTool).select_every_mousemove = False def on_tsne_data_update(attr, old, new): global previously_selected_spike_indices global currently_selected_spike_indices global non_selected_points_alpha global non_selected_points_size global selected_points_size global checkbox_find_clusters_of_selected_points previously_selected_spike_indices = np.array(old['1d']['indices']) currently_selected_spike_indices = np.array(new['1d']['indices']) num_of_selected_spikes = len(currently_selected_spike_indices) if num_of_selected_spikes > 0: if verbose: print('Num of selected spikes = ' + str(num_of_selected_spikes)) # update t-sne plot tsne_invisible_points_glyph.size = selected_points_size tsne_nonselected_points_glyph.size = non_selected_points_size tsne_nonselected_points_glyph.fill_alpha = non_selected_points_alpha # update spike plot avg_x = np.mean(cut_extracellular_data[:, :, currently_selected_spike_indices], axis=2) spike_mline_plot.data_source.data['ys'] = avg_x.tolist() print('Finished avg spike plot') # update autocorelogram diffs, norm = crosscorrelate_spike_trains(all_extra_spike_times[currently_selected_spike_indices].astype(np.int64), all_extra_spike_times[currently_selected_spike_indices].astype(np.int64), lag=1500) hist, edges = np.histogram(diffs, bins=autocor_bin_number) hist_plot.data_source.data["top"] = hist hist_plot.data_source.data["left"] = edges[:-1] / sampling_freq hist_plot.data_source.data["right"] = edges[1:] / sampling_freq print('finished autocorelogram') # update heatmap if prb_file is not None: print('Doing heatmap') data = cut_extracellular_data[:, :, currently_selected_spike_indices] final_image, (x_size, y_size) = spike_heatmap.create_heatmap(data, prb_file, rotate_90=True, flip_ud=True, flip_lr=False) new_image_data = dict(image=[final_image], x=[0], y=[0], dw=[x_size], dh=[y_size]) heatmap_data_source.data.update(new_image_data) print('Finished heatmap') tsne_source.on_change('selected', on_tsne_data_update) # spike plot spike_fig_tools = 'pan,wheel_zoom,box_zoom,reset,save' spike_figure = figure(toolbar_location='below', plot_width=spike_figure_size[0], plot_height=spike_figure_size[1], tools=spike_fig_tools, title='Spike average', min_border=10, webgl=True, toolbar_sticky=False) num_of_channels = cut_extracellular_data.shape[0] num_of_time_points = cut_extracellular_data.shape[1] xs = np.repeat(np.expand_dims(time_axis, axis=0), repeats=num_of_channels, axis=0).tolist() ys = np.ones((num_of_channels, num_of_time_points)).tolist() spike_mline_plot = spike_figure.multi_line(xs=xs, ys=ys) # autocorelogram plot hist, edges = np.histogram([], bins=autocor_bin_number) hist_fig_tools = 'pan,wheel_zoom,box_zoom,save,reset' hist_figure = figure(toolbar_location='below', plot_width=hist_figure_size[0], plot_height=hist_figure_size[1], tools=hist_fig_tools, title='Autocorrelogram', min_border=10, webgl=True, toolbar_sticky=False) hist_plot = hist_figure.quad(bottom=0, left=edges[:-1], right=edges[1:], top=hist, color="#3A5785", alpha=0.5, line_color="#3A5785") # heatmap plot heatmap_plot = figure(toolbar_location='right', plot_width=1, plot_height=heatmap_plot_size[1], x_range=(0, 1), y_range=(0, 1), title='Probe heatmap', toolbar_sticky=False) if prb_file is not None: data = np.zeros(cut_extracellular_data.shape) final_image, (x_size, y_size) = spike_heatmap.create_heatmap(data, prb_file, rotate_90=True, flip_ud=True, flip_lr=False) final_image[:, :, ] = 4294967295 # The int32 for the int8 255 (white) plot_width = max(heatmap_plot_size[0], int(heatmap_plot_size[1] * y_size / x_size)) heatmap_plot = figure(toolbar_location='right', plot_width=plot_width, plot_height=heatmap_plot_size[1], x_range=(0, x_size), y_range=(0, y_size), title='Probe heatmap', toolbar_sticky=False) heatmap_data_source = ColumnDataSource(data=dict(image=[final_image], x=[0], y=[0], dw=[x_size], dh=[y_size])) heatmap_renderer = heatmap_plot.image_rgba(source=heatmap_data_source, image='image', x='x', y='y', dw='dw', dh='dh', dilate=False) heatmap_plot.axis.visible = None heatmap_plot.xgrid.grid_line_color = None heatmap_plot.ygrid.grid_line_color = None # --------------------------------------- # --------------- CONTROLS -------------- # Texts and Tables # the clusters DataTable if use_existing_cluster: cluster_info = load_cluster_info(cluster_info_file) else: cluster_info = create_new_cluster_info_file(cluster_info_file, len(tsne)) cluster_info_data_source = ColumnDataSource(cluster_info) clusters_columns = [TableColumn(field='Cluster', title='Clusters'), TableColumn(field='Num_of_Spikes', title='Number of Spikes')] clusters_table = DataTable(source=cluster_info_data_source, columns=clusters_columns, selectable=True, editable=False, width=clusters_table_size[0], height=clusters_table_size[1], scroll_to_selection=True) def on_select_cluster_info_table(attr, old, new): global selected_cluster_names cluster_info = load_cluster_info(cluster_info_file) indices = list(chain.from_iterable(cluster_info.iloc[new['1d']['indices']].Spike_Indices.tolist())) selected_cluster_names = list(cluster_info.index[new['1d']['indices']]) old = new = tsne_source.selected tsne_source.selected['1d']['indices'] = indices tsne_source.trigger('selected', old, new) user_info_edit.value = 'Selected clusters = ' + ', '.join(selected_cluster_names) cluster_info_data_source.on_change('selected', on_select_cluster_info_table) def update_data_table(): cluster_info_data_source = ColumnDataSource(load_cluster_info(cluster_info_file)) cluster_info_data_source.on_change('selected', on_select_cluster_info_table) clusters_table.source = cluster_info_data_source options = list(cluster_info_data_source.data['Cluster']) options.insert(0, 'No cluster selected') select_cluster_to_move_points_to.options = options # cluster TextBox that adds cluster to the DataTable new_cluster_name_edit = TextInput(value='give the new cluster a name', title='Put selected points into a new cluster') def on_text_edit_new_cluster_name(attr, old, new): global currently_selected_spike_indices global clusters_of_all_spikes new_cluster_name = new_cluster_name_edit.value spike_indices_to_delete_from_existing_clusters = {} for spike_index in currently_selected_spike_indices: if clusters_of_all_spikes[spike_index] != -1: cluster_index = clusters_of_all_spikes[spike_index] if cluster_index not in spike_indices_to_delete_from_existing_clusters: spike_indices_to_delete_from_existing_clusters[cluster_index] = [spike_index] else: spike_indices_to_delete_from_existing_clusters[cluster_index].append(spike_index) cluster_info = load_cluster_info(cluster_info_file) for cluster_index in spike_indices_to_delete_from_existing_clusters.keys(): cluster_name = cluster_info.iloc[cluster_index].name remove_spikes_from_cluster(cluster_info_file, cluster_name, spike_indices_to_delete_from_existing_clusters[cluster_index], unassign=False) add_cluster_to_cluster_info(cluster_info_file, new_cluster_name, currently_selected_spike_indices) update_data_table() new_cluster_name_edit.on_change('value', on_text_edit_new_cluster_name) # user information Text user_info_edit = TextInput(value='', title='User information', width=user_info_size[0], height=user_info_size[1]) # Buttons ------------------------ # show all clusters Button button_show_all_clusters = Toggle(label='Show all clusters', button_type='primary') def on_button_show_all_clusters(state, *args): global tsne_clusters_scatter_plot if state: cluster_info = load_cluster_info(cluster_info_file) num_of_clusters = cluster_info.shape[0] indices_list_of_lists = cluster_info['Spike_Indices'].tolist() indices = [item for sublist in indices_list_of_lists for item in sublist] cluster_indices = np.arange(num_of_clusters) if verbose: print('Showing all clusters in colors... wait for it...') colors = [] for c in cluster_indices: r = np.random.random(size=1) * 255 g = np.random.random(size=1) * 255 for i in np.arange(len(indices_list_of_lists[c])): colors.append("#%02x%02x%02x" % (int(r), int(g), 50)) first_time = True for renderer in tsne_figure.renderers: if renderer.name == 'tsne_all_clusters_glyph_renderer': renderer.data_source.data['fill_color'] = renderer.data_source.data['line_color'] = colors renderer.glyph.fill_color = 'fill_color' renderer.glyph.line_color = 'line_color' first_time = False break if first_time: tsne_clusters_scatter_plot = tsne_figure.scatter(tsne[0][indices], tsne[1][indices], size=1, color=colors, alpha=1, name='tsne_all_clusters_glyph_renderer') tsne_clusters_scatter_plot.visible = True button_show_all_clusters.label = 'Hide all clusters' else: if verbose: print('Hiding clusters') button_show_all_clusters.update() tsne_clusters_scatter_plot.visible = False button_show_all_clusters.label = 'Show all clusters' button_show_all_clusters.on_click(on_button_show_all_clusters) # select the clusters that the selected points belong to Button # (that will then drive the selection of these spikes on t-sne through the update of the clusters_table source) button_show_clusters_of_selected_points = Button(label='Show clusters of selected points') def on_button_show_clusters_change(): print('Hello') global clusters_of_all_spikes currently_selected_spike_indices = tsne_source.selected['1d']['indices'] cluster_info = load_cluster_info(cluster_info_file) clusters_selected = [] new_indices_to_select = [] update_data_table() for spike_index in currently_selected_spike_indices: if clusters_of_all_spikes[spike_index] not in clusters_selected: clusters_selected.append(clusters_of_all_spikes[spike_index]) indices_in_cluster = cluster_info.iloc[clusters_of_all_spikes[spike_index]].Spike_Indices new_indices_to_select.append(indices_in_cluster) if len(new_indices_to_select) > 0: old = clusters_table.source.selected clusters_table.source.selected['1d']['indices'] = clusters_selected new = clusters_table.source.selected clusters_table.source.trigger('selected', old, new) for c in np.arange(len(clusters_selected)): clusters_selected[c] = cluster_info.index[clusters_selected[c]] button_show_clusters_of_selected_points.on_click(on_button_show_clusters_change) # merge clusters Button button_merge_clusters_of_selected_points = Button(label='Merge clusters of selected points') def on_button_merge_clusters_change(): global clusters_of_all_spikes currently_selected_spike_indices = tsne_source.selected['1d']['indices'] cluster_info = load_cluster_info(cluster_info_file) clusters_selected = [] for spike_index in currently_selected_spike_indices: if clusters_of_all_spikes[spike_index] not in clusters_selected: clusters_selected.append(clusters_of_all_spikes[spike_index]) if len(clusters_selected) > 0: clusters_selected = np.sort(clusters_selected) clusters_selected_names = [] for cluster_index in clusters_selected: clusters_selected_names.append(cluster_info.iloc[cluster_index].name) cluster_name = clusters_selected_names[0] add_cluster_to_cluster_info(cluster_info_file, cluster_name, currently_selected_spike_indices) i = 0 for c in np.arange(1, len(clusters_selected)): cluster_info = remove_cluster_from_cluster_info(cluster_info_file, cluster_info.iloc[clusters_selected[c] - i].name, unassign=False) i = i + 1 # Every time you remove a cluster the original index of the remaining clusters drops by one update_data_table() user_info_edit.value = 'Clusters '+ ', '.join(clusters_selected_names) + ' merged to cluster ' + cluster_name button_merge_clusters_of_selected_points.on_click(on_button_merge_clusters_change) # delete cluster Button button_delete_cluster = Button(label='Delete selected cluster(s)') def on_button_delete_cluster(): global selected_cluster_names for cluster_name in selected_cluster_names: remove_cluster_from_cluster_info(cluster_info_file, cluster_name) user_info_edit.value = 'Deleted clusters: ' + ', '.join(selected_cluster_names) update_data_table() button_delete_cluster.on_click(on_button_delete_cluster) # select cluster to move selected points to Select select_cluster_to_move_points_to = Select(title="Assign selected points to cluster:", value="No cluster selected") options = list(cluster_info_data_source.data['Cluster']) options.insert(0, 'No cluster selected') select_cluster_to_move_points_to.options = options def move_selected_points_to_cluster(attr, old, new): global currently_selected_spike_indices if len(currently_selected_spike_indices) > 0 and new is not 'No cluster selected': remove_spikes_from_all_clusters(cluster_info_file, currently_selected_spike_indices) add_spikes_to_cluster(cluster_info_file, new, currently_selected_spike_indices) update_data_table() select_cluster_to_move_points_to.value = 'No cluster selected' user_info_edit.value = 'Selected clusters = ' + new select_cluster_to_move_points_to.on_change('value', move_selected_points_to_cluster) # undo selection button undo_selected_points_button = Button(label='Undo last selection') def on_button_undo_selection(): global previously_selected_spike_indices tsne_source.selected['1d']['indices'] = previously_selected_spike_indices old = new = tsne_source.selected tsne_source.trigger('selected', old, new) undo_selected_points_button.on_click(on_button_undo_selection) # Sliders ------------------- # use the fake data trick to call the callback only when the mouse is released (mouseup only works for CustomJS) # change visibility of non selected points Slider slider_non_selected_visibility = Slider(start=0, end=1, value=0.2, step=.02, callback_policy='mouseup', title='Alpha of not selected points', width=slider_size[0], height=slider_size[1]) def on_slider_change_non_selected_visibility(attrname, old, new): global non_selected_points_alpha if len(source_fake_nsv.data['value']) > 0: non_selected_points_alpha = source_fake_nsv.data['value'][0] old = new = tsne_source.selected tsne_source.trigger('selected', old, new) source_fake_nsv = ColumnDataSource(data=dict(value=[])) source_fake_nsv.on_change('data', on_slider_change_non_selected_visibility) slider_non_selected_visibility.callback = CustomJS(args=dict(source=source_fake_nsv), code=""" source.data = { value: [cb_obj.value] } """) # change size of non selected points Slider slider_non_selected_size = Slider(start=0.5, end=10, value=2, step=0.5, callback_policy='mouseup', title='Size of not selected points', width=slider_size[0], height=slider_size[1]) def on_slider_change_non_selected_size(attrname, old, new): global non_selected_points_size if len(source_fake_nss.data['value']) > 0: non_selected_points_size = source_fake_nss.data['value'][0] old = new = tsne_source.selected tsne_source.trigger('selected', old, new) source_fake_nss = ColumnDataSource(data=dict(value=[])) source_fake_nss.on_change('data', on_slider_change_non_selected_size) slider_non_selected_size.callback = CustomJS(args=dict(source=source_fake_nss), code=""" source.data = { value: [cb_obj.value] } """) # change size of selected points Slider slider_selected_size = Slider(start=0.5, end=10, value=2, step=0.5, callback_policy='mouseup', title='Size of selected points', width=slider_size[0], height=slider_size[1]) def on_slider_change_selected_size(attrname, old, new): global selected_points_size if len(source_fake_ss.data['value']) > 0: selected_points_size = source_fake_ss.data['value'][0] old = new = tsne_source.selected tsne_source.trigger('selected', old, new) source_fake_ss = ColumnDataSource(data=dict(value=[])) source_fake_ss.on_change('data', on_slider_change_selected_size) slider_selected_size.callback = CustomJS(args=dict(source=source_fake_ss), code=""" source.data = { value: [cb_obj.value] } """) # ------------------------------------------- # Layout and session setup ------------------ # align and make layout spike_figure.min_border_top = 50 spike_figure.min_border_right = 10 hist_figure.min_border_top = 50 hist_figure.min_border_left = 10 tsne_figure.min_border_right = 50 if k4: lay = row(column(tsne_figure, row(slider_non_selected_visibility, slider_non_selected_size, slider_selected_size), row(spike_figure, hist_figure), user_info_edit), column(clusters_table, button_show_clusters_of_selected_points, button_merge_clusters_of_selected_points, button_delete_cluster, select_cluster_to_move_points_to, new_cluster_name_edit, button_show_all_clusters, undo_selected_points_button, heatmap_plot)) else: lay = row(column(tsne_figure, row(spike_figure, hist_figure)), column(row(heatmap_plot, column(slider_non_selected_visibility, slider_non_selected_size, slider_selected_size)), user_info_edit), column(clusters_table, button_show_clusters_of_selected_points, button_merge_clusters_of_selected_points, button_delete_cluster, select_cluster_to_move_points_to, new_cluster_name_edit, button_show_all_clusters, undo_selected_points_button)) session = push_session(curdoc()) session.show(lay) # open the document in a browser session.loop_until_closed() # run forever, requires stopping the interpreter in order to stop :)
def trigger_layout(text, run, ttype, source, edge, trigger, pretrigger): lay = column([ text, row([run, ttype, source, edge], sizing_mode=MODE, width=DEFAULT_WIDTH), trigger, pretrigger ], sizing_mode=MODE) return lay ###### CONNECT ########## but_connect = Toggle(label='Connect', active=True, sizing_mode='stretch_both') but_connect.on_click(update_but_connect) app.set_port_closed_callback(update_port_closed) devices = Dropdown(label="Device", menu=list_ttys(), disabled=False) devices.on_click(update_devices) but_refresh = Toggle(label='Refresh', active=True, sizing_mode='stretch_both') but_refresh.on_click(update_but_refresh) connect_layout = row([but_connect, devices, but_refresh], sizing_mode=MODE) ###### CHANNEL A ######## text_cha = Div(text='<hr><h2>Channel A</h2>') on_cha = Toggle(label='On/Off', active=True) on_cha.on_click(update_on_cha)
def checkbox_button_group_handler(active): print("checkbox_button_group_handler: %s" % active) def radio_button_group_handler(active): print("radio_button_group_handler: %s" % active) button = Button(label="Push button", icon=Icon(icon_name="check"), type="primary") button.on_click(button_handler) toggle = Toggle(label="Toggle button", type="success") toggle.on_click(toggle_handler) menu = [("Item 1", "item_1"), ("Item 2", "item_2"), None, ("Item 3", "item_3")] dropdown = Dropdown(label="Dropdown button", type="warning", menu=menu) dropdown.on_click(dropdown_handler) menu = [("Item 1", "foo"), ("Item 2", "bar"), None, ("Item 3", "baz")] split = Dropdown(label="Split button", type="danger", menu=menu, default_value="baz") split.on_click(split_handler) checkbox_group = CheckboxGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1]) checkbox_group.on_click(checkbox_group_handler)
print("radio_group_handler: %s" % active) session.store_document(document) def checkbox_button_group_handler(active): print("checkbox_button_group_handler: %s" % active) session.store_document(document) def radio_button_group_handler(active): print("radio_button_group_handler: %s" % active) session.store_document(document) button = Button(label="Push button", icon=Icon(name="check"), type="primary") button.on_click(button_handler) toggle = Toggle(label="Toggle button", type="success") toggle.on_click(toggle_handler) menu = [("Item 1", "item_1"), ("Item 2", "item_2"), None, ("Item 3", "item_3")] dropdown = Dropdown(label="Dropdown button", type="warning", menu=menu) dropdown.on_click(dropdown_handler) menu = [("Item 1", "foo"), ("Item 2", "bar"), None, ("Item 3", "baz")] split = Dropdown(label="Split button", type="danger", menu=menu, default_action="baz") split.on_click(split_handler) checkbox_group = CheckboxGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1]) checkbox_group.on_click(checkbox_group_handler) radio_group = RadioGroup(labels=["Option 1", "Option 2", "Option 3"], active=0) radio_group.on_click(radio_group_handler)
if active == False: filtered_data = dfincident[np.isin( dfincident["dim_incident_incident_type"], type_filter.value)] update_map(filtered_data) slider_active_toggle.label = "slider not active" slider_active_toggle.button_type = "default" # assign callbacks play_button = Toggle(label="PLAY", button_type="primary", width=100, callback=callback_play) time_slider.on_change('value', callback_time_slider) slider_active_toggle.on_click(callback_toggle_slider_activity) pattern_select.on_change('active', callback_pattern_selection) aggregate_select.on_change('active', callback_aggregation_selection) groupby_select.on_change('active', callback_groupby_selection) type_filter.on_change('value', callback_type_filter) geo_source.on_change('selected', callback_map_selection) select_all_types_button.on_click(callback_select_all_types) ## end callbacks # headers map_head = Div(text="<h2>Spatial distribution of incidents</h2>", css_classes=["plot-head"], width=LEFT_COLUMN_WIDTH) ts_head = Div(text="<h2>Time distribution of incidents</h2>", css_classes=["plot-head"], width=RIGHT_COLUMN_WIDTH)
value=c.EMPTY_STRING, options=[c.EMPTY_STRING] + FILTER_PROPERTIES[c.NC]) NG_HOST = Select(title='Neighbourhood Group:', value=c.EMPTY_STRING, options=[c.EMPTY_STRING] + NG_LIST) ROOM_TYPE_HOST = Select(title='Room Type:', value=c.EMPTY_STRING, options=[c.EMPTY_STRING] + RT_LIST) # Radio Button Widget USER_TYPE = RadioButtonGroup(labels=['Guest', 'Host'], active=0) USER_TYPE.on_change(c.ACTIVE, update_layout) # Button Toggle Widget PREDICT_VALUE = Toggle(label='Submit', button_type='success') PREDICT_VALUE.on_click(predict_price) # Text Widget HOST_PRICE = Paragraph(text="""Select all listing values and press submit to view your listings valued price.""", width=500, height=500) WIDGETS_BOTH_1 = [USER_TYPE, CITY_INPUT] WIDGETS_BOTH_2 = [ ACCOMMODATES_SLIDER, BEDROOM_SLIDER, BED_SLIDER, BATHROOM_SLIDER ] WIDGETS_BOTH_3 = [AMENITIES_SELECT] WIDGETS_GUEST = widgetbox( WIDGETS_BOTH_1 + [ROOM_TYPE_GROUP, PRICE_SLIDER] + WIDGETS_BOTH_2 +
trigger_level_input = TextInput(value="1", title="Trigger Level (V)") time_range_input = TextInput(value="1000", title="Time Range (us)") load_file_input = TextInput(value=home_dir, title="Load file:") save_filepath_input = TextInput(value=home_dir, title="Save to directory:") force_save_filename_input = TextInput(value=str(dt.now(PT).year) + "_" + str(dt.now(PT).month) + "_" + str(dt.now(PT).day) + ".h5", title="Save as filename:") save_filepath_PC_input = TextInput(value=home_dir, title="Save to directory:") save_filename_PC_input = TextInput(value="PC_" + str(dt.now(PT).year) + "_" + str(dt.now(PT).month) + "_" + str(dt.now(PT).day) + ".h5", title="Save as filename:") #setup event handlers all_off_button.on_click(lambda x: allOff_ButtonClick()) all_on_button.on_click(lambda x: allOn_ButtonClick()) reset_button.on_click(lambda x: reset_ButtonClick()) autoscale_button.on_click(lambda x: autoscale_ButtonClick()) channel_button[0].on_click(lambda x: channel_ButtonClick(1)) channel_button[1].on_click(lambda x: channel_ButtonClick(2)) channel_button[2].on_click(lambda x: channel_ButtonClick(3)) channel_button[3].on_click(lambda x: channel_ButtonClick(4)) pulse_capture_button.on_click(lambda x: pulseCapture_ButtonClick()) auto_save_button.on_click(lambda x: autoSave_ButtonClick()) load_button.on_click(lambda x: load_ButtonClick()) force_save_button.on_click(lambda x: forceSave_ButtonClick()) save_PC_button.on_click(lambda x: savePC_ButtonClick()) acq_period_select.on_change("value", acqPeriod_SelectChange) channel_name_select1.on_change("value", chanNameSelect1_SelectChange)
class MapWidgets: def __init__(self, tweet_data_controller, config): self.tweet_data_controller = tweet_data_controller self.config = config self.toggle_data = Toggle(label="Toggle Data", active=False, width=100) self.toggle_data.on_click(self.toggle_data_callback) self.button_clear = Button(label="Clear", width=100) self.button_clear.on_click(self.button_clear_callback) self.toggle_sde_ellipse = Toggle(label="Toggle Ellipse", active=False, width=150) self.toggle_sde_ellipse.on_click(self.toggle_sde_ellipse_callback) self.toggle_sibling_ellipses = Toggle(label="Toggle Siblings", active=False, width=150) self.toggle_sibling_ellipses.on_click( self.toggle_sibling_ellipses_callback) self.toggle_dissolve = Toggle(label="Toggle Dissolve", active=False, width=150) self.toggle_dissolve.on_click(self.toggle_dissolve_callback) self.text_selection_details = Div(text='Selected ID:') self.text_input = TextInput(value="-1", title="Enter ID:", width=150) self.button_find = Button(label="Find ID", width=100) self.button_find.on_click(self.button_find_callback) self.radio_button_data_type = RadioButtonGroup( labels=['all', 'working', 'non-working'], active=1, width=300) self.radio_button_data_type.on_change( 'active', self.radio_button_data_type_change) self.range_slider_count = RangeSlider(start=config.count[0], end=config.count[1], value=(config.count[0], config.count[1]), step=config.count[2], title="Sibling Count") self.range_slider_count.on_change('value', self.range_slider_count_change) self.button_count_start_minus = Button(label="-", width=15) self.button_count_start_minus.on_click( self.button_count_start_minus_callback) self.button_count_start_plus = Button(label="+", width=15) self.button_count_start_plus.on_click( self.button_count_start_plus_callback) self.button_count_end_minus = Button(label="-", width=15) self.button_count_end_minus.on_click( self.button_count_end_minus_callback) self.button_count_end_plus = Button(label="+", width=15) self.button_count_end_plus.on_click( self.button_count_end_plus_callback) self.tweet_data_controller.count_values = self.range_slider_count self.range_slider_area = RangeSlider(start=config.area[0], end=config.area[1], value=(config.area[0], config.area[1]), step=config.area[2], title="Area Size") self.range_slider_area.on_change('value', self.range_slider_area_change) self.button_area_start_minus = Button(label="-", width=15) self.button_area_start_minus.on_click( self.button_area_start_minus_callback) self.button_area_start_plus = Button(label="+", width=15) self.button_area_start_plus.on_click( self.button_area_start_plus_callback) self.button_area_end_minus = Button(label="-", width=15) self.button_area_end_minus.on_click( self.button_area_end_minus_callback) self.button_area_end_plus = Button(label="+", width=15) self.button_area_end_plus.on_click(self.button_area_end_plus_callback) self.tweet_data_controller.area_values = self.range_slider_area self.range_slider_distance = RangeSlider(start=config.distance[0], end=config.distance[1], value=(config.distance[0], config.distance[1]), step=config.distance[2], title="Distance") self.range_slider_distance.on_change('value', self.range_slider_distance_change) self.button_distance_start_minus = Button(label="-", width=15) self.button_distance_start_minus.on_click( self.button_distance_start_minus_callback) self.button_distance_start_plus = Button(label="+", width=15) self.button_distance_start_plus.on_click( self.button_distance_start_plus_callback) self.button_distance_end_minus = Button(label="-", width=15) self.button_distance_end_minus.on_click( self.button_distance_end_minus_callback) self.button_distance_end_plus = Button(label="+", width=15) self.button_distance_end_plus.on_click( self.button_distance_end_plus_callback) self.tweet_data_controller.distance_values = self.range_slider_distance self.range_slider_ratio = RangeSlider(start=config.ratio[0], end=config.ratio[1], value=(config.ratio[0], config.ratio[1]), step=config.ratio[2], title="Ratio") self.range_slider_ratio.on_change('value', self.range_slider_ratio_change) self.button_ratio_start_minus = Button(label="-", width=15) self.button_ratio_start_minus.on_click( self.button_ratio_start_minus_callback) self.button_ratio_start_plus = Button(label="+", width=15) self.button_ratio_start_plus.on_click( self.button_ratio_start_plus_callback) self.button_ratio_end_minus = Button(label="-", width=15) self.button_ratio_end_minus.on_click( self.button_ratio_end_minus_callback) self.button_ratio_end_plus = Button(label="+", width=15) self.button_ratio_end_plus.on_click( self.button_ratio_end_plus_callback) self.tweet_data_controller.ratio_values = self.range_slider_ratio self.range_slider_dissolve = RangeSlider(start=config.dissolve[0], end=config.dissolve[1], value=(config.dissolve[0], config.dissolve[1]), step=config.dissolve[2], title="Dissolve Area") self.range_slider_dissolve.on_change('value', self.range_slider_dissolve_change) self.button_dissolve_start_minus = Button(label="-", width=20) self.button_dissolve_start_minus.on_click( self.button_dissolve_start_minus_callback) self.button_dissolve_start_plus = Button(label="+", width=20) self.button_dissolve_start_plus.on_click( self.button_dissolve_start_plus_callback) self.button_dissolve_end_minus = Button(label="-", width=20) self.button_dissolve_end_minus.on_click( self.button_dissolve_end_minus_callback) self.button_dissolve_end_plus = Button(label="+", width=20) self.button_dissolve_end_plus.on_click( self.button_dissolve_end_plus_callback) self.tweet_data_controller.dissolve_values = self.range_slider_dissolve self.filters_active = CheckboxGroup(labels=[ "Filter by Sibling Count", "Filter by Area", "Filter by Distance", "Filter by Ratio", "Filter by Dissolve Area" ], active=[0, 1, 2, 3, 4]) self.filters_active.on_change('active', self.filters_active_change) self.toggle_blend = Toggle(label="Toggle Blend", active=False, width=150) self.toggle_blend.on_click(self.toggle_blend_callback) self.toggle_blend.disabled = False self.tweet_data_controller.blend_active = True self.slider_blend = Slider(start=0.0, end=1.0, value=0.5, step=0.025, title="Blend Ratio") self.slider_blend.on_change('value', self.slider_blend_change) self.slider_blend.disabled = True self.text_count = Paragraph(text="") self.tweet_data_controller.tc = self.text_count self.tweet_data_controller.update_text_count() self.toggle_legend = Toggle(label="Toggle Legend", active=False, width=100) self.toggle_legend.on_click(self.toggle_legend_callback) self.button_reset_filters = Button(label="Reset Filters", width=100) self.button_reset_filters.on_click(self.button_reset_filters_callback) def toggle_data_callback(self, arg): #print("Toggle Data: Callback:") if arg: self.tweet_data_controller.toggle_data(True) else: self.tweet_data_controller.toggle_data(False) def toggle_sde_ellipse_callback(self, arg): #print("Toggle Ellipse: Callback: " + str(self.tweet_data_controller.circle_id) + " : " + str(self.tweet_data_controller.circle_idx)) if arg: self.tweet_data_controller.update_sde_ellipse() self.tweet_data_controller.update_siblings() else: self.tweet_data_controller.clear_sde_ellipse() self.tweet_data_controller.clear_siblings() def toggle_sibling_ellipses_callback(self, arg): if arg: self.tweet_data_controller.update_sibling_ellipses() else: self.tweet_data_controller.clear_sibling_ellipses() def toggle_dissolve_callback(self, arg): #print("Toggle Dissolve: Callback: " + str(self.tweet_data_controller.circle_id) + " : " + str(self.tweet_data_controller.circle_idx)) if arg: self.tweet_data_controller.update_dissolve() else: self.tweet_data_controller.clear_dissolve() def radio_button_data_type_change(self, attrname, old, new): if new == 0: self.toggle_blend.disabled = True self.toggle_blend.active = False self.tweet_data_controller.blend_active = False else: self.toggle_blend.disabled = False self.toggle_blend.active = False self.tweet_data_controller.blend_active = False self.tweet_data_controller.switch_tweet_dataset(new) if self.toggle_sde_ellipse.active: self.tweet_data_controller.update_sde_ellipse() self.tweet_data_controller.update_siblings() else: self.tweet_data_controller.clear_sde_ellipse() self.tweet_data_controller.clear_siblings() if self.toggle_sibling_ellipses.active: self.tweet_data_controller.update_sibling_ellipses() else: self.tweet_data_controller.clear_sibling_ellipses() if self.toggle_dissolve.active: self.tweet_data_controller.update_dissolve() else: self.tweet_data_controller.clear_dissolve() self.tweet_data_controller.clear_find_circle() def button_find_callback(self): id_value = int(self.text_input.value) print("Button Find: Callback: " + str(id_value)) self.tweet_data_controller.find_id(id_value) def button_clear_callback(self): id_value = int(self.text_input.value) print("Button Clear: Callback: " + str(id_value)) self.tweet_data_controller.clear_all() self.toggle_sde_ellipse.active = False self.toggle_sibling_ellipses.active = False self.toggle_dissolve.active = False self.toggle_blend.active = False def range_slider_count_change(self, attrname, old, new): start_count = int(new[0]) end_count = int(new[1]) self.tweet_data_controller.filter_circles_by_count( start_count, end_count) self.tweet_data_controller.clear_all() def button_count_start_minus_callback(self): value_start = int(self.range_slider_count.value[0]) value_end = int(self.range_slider_count.value[1]) if value_start > self.range_slider_count.start: value_start -= 1 new_values = [value_start, value_end] self.range_slider_count.value = new_values self.tweet_data_controller.filter_circles_by_count( value_start, value_end) self.tweet_data_controller.clear_all() def button_count_start_plus_callback(self): value_start = int(self.range_slider_count.value[0]) value_end = int(self.range_slider_count.value[1]) if value_start < value_end: value_start += 1 new_values = [value_start, value_end] self.range_slider_count.value = new_values self.tweet_data_controller.filter_circles_by_count( value_start, value_end) self.tweet_data_controller.clear_all() def button_count_end_minus_callback(self): value_start = int(self.range_slider_count.value[0]) value_end = int(self.range_slider_count.value[1]) if value_end > value_start: value_end -= 1 new_values = [value_start, value_end] self.range_slider_count.value = new_values self.tweet_data_controller.filter_circles_by_count( value_start, value_end) self.tweet_data_controller.clear_all() def button_count_end_plus_callback(self): value_start = int(self.range_slider_count.value[0]) value_end = int(self.range_slider_count.value[1]) if value_end < self.range_slider_count.end: value_end += 1 new_values = [value_start, value_end] self.range_slider_count.value = new_values self.tweet_data_controller.filter_circles_by_count( value_start, value_end) self.tweet_data_controller.clear_all() def range_slider_area_change(self, attrname, old, new): start_area = int(new[0]) end_area = int(new[1]) self.tweet_data_controller.filter_circles_by_area(start_area, end_area) self.tweet_data_controller.clear_all() def button_area_start_minus_callback(self): value_start = int(self.range_slider_area.value[0]) value_end = int(self.range_slider_area.value[1]) if value_start > self.range_slider_area.start: value_start -= int(self.config.area[2]) new_values = [value_start, value_end] self.range_slider_area.value = new_values self.tweet_data_controller.filter_circles_by_area( value_start, value_end) self.tweet_data_controller.clear_all() def button_area_start_plus_callback(self): value_start = int(self.range_slider_area.value[0]) value_end = int(self.range_slider_area.value[1]) if value_start < value_end: value_start += int(self.config.area[2]) new_values = [value_start, value_end] self.range_slider_area.value = new_values self.tweet_data_controller.filter_circles_by_area( value_start, value_end) self.tweet_data_controller.clear_all() def button_area_end_minus_callback(self): value_start = int(self.range_slider_area.value[0]) value_end = int(self.range_slider_area.value[1]) if value_end > value_start: value_end -= int(self.config.area[2]) new_values = [value_start, value_end] self.range_slider_area.value = new_values self.tweet_data_controller.filter_circles_by_area( value_start, value_end) self.tweet_data_controller.clear_all() def button_area_end_plus_callback(self): value_start = int(self.range_slider_area.value[0]) value_end = int(self.range_slider_area.value[1]) if value_end < self.range_slider_area.end: value_end += int(self.config.area[2]) new_values = [value_start, value_end] self.range_slider_area.value = new_values self.tweet_data_controller.filter_circles_by_area( value_start, value_end) self.tweet_data_controller.clear_all() def range_slider_distance_change(self, attrname, old, new): start_distance = int(new[0]) end_distance = int(new[1]) self.tweet_data_controller.filter_circles_by_distance( start_distance, end_distance) self.tweet_data_controller.clear_all() def button_distance_start_minus_callback(self): value_start = int(self.range_slider_distance.value[0]) value_end = int(self.range_slider_distance.value[1]) if value_start > self.range_slider_distance.start: value_start -= 1 new_values = [value_start, value_end] self.range_slider_distance.value = new_values self.tweet_data_controller.filter_circles_by_distance( value_start, value_end) self.tweet_data_controller.clear_all() def button_distance_start_plus_callback(self): value_start = int(self.range_slider_distance.value[0]) value_end = int(self.range_slider_distance.value[1]) if value_start < value_end: value_start += 1 new_values = [value_start, value_end] self.range_slider_distance.value = new_values self.tweet_data_controller.filter_circles_by_distance( value_start, value_end) self.tweet_data_controller.clear_all() def button_distance_end_minus_callback(self): value_start = int(self.range_slider_distance.value[0]) value_end = int(self.range_slider_distance.value[1]) if value_end > value_start: value_end -= 1 new_values = [value_start, value_end] self.range_slider_distance.value = new_values self.tweet_data_controller.filter_circles_by_distance( value_start, value_end) self.tweet_data_controller.clear_all() def button_distance_end_plus_callback(self): value_start = int(self.range_slider_distance.value[0]) value_end = int(self.range_slider_distance.value[1]) if value_end < self.range_slider_distance.end: value_end += 1 new_values = [value_start, value_end] self.range_slider_distance.value = new_values self.tweet_data_controller.filter_circles_by_distance( value_start, value_end) self.tweet_data_controller.clear_all() def range_slider_ratio_change(self, attrname, old, new): start_ratio = new[0] end_ratio = new[1] self.tweet_data_controller.filter_circles_by_ratio( start_ratio, end_ratio) self.tweet_data_controller.clear_all() def button_ratio_start_minus_callback(self): value_start = self.range_slider_ratio.value[0] value_end = self.range_slider_ratio.value[1] if value_start > self.range_slider_ratio.start: value_start -= 0.1 # This is a hack to prevent the end value to reaching -1.1 if value_start < self.range_slider_ratio.start: value_start = self.range_slider_ratio.start new_values = [value_start, value_end] self.range_slider_ratio.value = new_values self.tweet_data_controller.filter_circles_by_ratio( value_start, value_end) self.tweet_data_controller.clear_all() def button_ratio_start_plus_callback(self): value_start = self.range_slider_ratio.value[0] value_end = self.range_slider_ratio.value[1] if value_start < value_end: value_start += 0.1 new_values = [value_start, value_end] self.range_slider_ratio.value = new_values self.tweet_data_controller.filter_circles_by_ratio( value_start, value_end) self.tweet_data_controller.clear_all() def button_ratio_end_minus_callback(self): value_start = self.range_slider_ratio.value[0] value_end = self.range_slider_ratio.value[1] if value_end > value_start: value_end -= 0.1 new_values = [value_start, value_end] self.range_slider_ratio.value = new_values self.tweet_data_controller.filter_circles_by_ratio( value_start, value_end) self.tweet_data_controller.clear_all() def button_ratio_end_plus_callback(self): value_start = self.range_slider_ratio.value[0] value_end = self.range_slider_ratio.value[1] if value_end < self.range_slider_ratio.end: value_end += 0.1 # This is a hack to prevent the end value to reaching 100.1 if value_end > self.range_slider_ratio.end: value_end = self.range_slider_ratio.end new_values = [value_start, value_end] self.range_slider_ratio.value = new_values self.tweet_data_controller.filter_circles_by_ratio( value_start, value_end) self.tweet_data_controller.clear_all() def range_slider_dissolve_change(self, attrname, old, new): start_dissolve = new[0] end_dissolve = new[1] self.tweet_data_controller.filter_circles_by_dissolve( start_dissolve, end_dissolve) self.tweet_data_controller.clear_all() def button_dissolve_start_minus_callback(self): value_start = self.range_slider_dissolve.value[0] value_end = self.range_slider_dissolve.value[1] if value_start > self.range_slider_dissolve.start: value_start -= 1 new_values = [value_start, value_end] self.range_slider_dissolve.value = new_values self.tweet_data_controller.filter_circles_by_dissolve( value_start, value_end) self.tweet_data_controller.clear_all() def button_dissolve_start_plus_callback(self): value_start = self.range_slider_dissolve.value[0] value_end = self.range_slider_dissolve.value[1] if value_start < value_end: value_start += 1 new_values = [value_start, value_end] self.range_slider_dissolve.value = new_values self.tweet_data_controller.filter_circles_by_dissolve( value_start, value_end) self.tweet_data_controller.clear_all() def button_dissolve_end_minus_callback(self): value_start = self.range_slider_dissolve.value[0] value_end = self.range_slider_dissolve.value[1] if value_end > value_start: value_end -= 1 new_values = [value_start, value_end] self.range_slider_dissolve.value = new_values self.tweet_data_controller.filter_circles_by_dissolve( value_start, value_end) self.tweet_data_controller.clear_all() def button_dissolve_end_plus_callback(self): value_start = self.range_slider_dissolve.value[0] value_end = self.range_slider_dissolve.value[1] if value_end < self.range_slider_dissolve.end: value_end += 1 new_values = [value_start, value_end] self.range_slider_dissolve.value = new_values self.tweet_data_controller.filter_circles_by_dissolve( value_start, value_end) self.tweet_data_controller.clear_all() def filters_active_change(self, attrname, old, new): self.tweet_data_controller.filters_active(new) def toggle_blend_callback(self, arg): #print("Toggle Blend: Callback: " + str(self.tweet_data_controller.circle_id) + " : " + str(self.tweet_data_controller.circle_idx)) if arg: self.tweet_data_controller.turn_blend_on( self.toggle_sde_ellipse.active, self.toggle_sibling_ellipses.active, self.toggle_dissolve.active) self.slider_blend.disabled = False else: self.tweet_data_controller.turn_blend_off() self.slider_blend.disabled = True def slider_blend_change(self, attrname, old, new): self.tweet_data_controller.blend(new, self.toggle_sde_ellipse.active, self.toggle_sibling_ellipses.active, self.toggle_dissolve.active) def toggle_legend_callback(self, arg): self.tweet_data_controller.toggle_legend(arg) def button_reset_filters_callback(self): print("Button Reset Filters:") values_reset = [self.config.count[0], self.config.count[1]] self.tweet_data_controller.filter_circles_by_count( self.config.count[0], self.config.count[1]) self.range_slider_count.value = values_reset values_reset = [self.config.area[0], self.config.area[1]] self.tweet_data_controller.filter_circles_by_area( self.config.area[0], self.config.area[1]) self.range_slider_area.value = values_reset values_reset = [self.config.distance[0], self.config.distance[1]] self.tweet_data_controller.filter_circles_by_distance( self.config.distance[0], self.config.distance[1]) self.range_slider_distance.value = values_reset values_reset = [self.config.ratio[0], self.config.ratio[1]] self.tweet_data_controller.filter_circles_by_ratio( self.config.ratio[0], self.config.ratio[1]) self.range_slider_ratio.value = values_reset values_reset = [self.config.dissolve[0], self.config.dissolve[1]] self.tweet_data_controller.filter_circles_by_dissolve( self.config.dissolve[0], self.config.dissolve[1]) self.range_slider_dissolve.value = values_reset self.tweet_data_controller.clear_all()
# Text inputs channel_name_input = TextInput(title="Name:") trigger_level_input = TextInput(value="1", title="Trigger Level (V)") time_range_input = TextInput(value="1000", title="Time Range (us)") load_file_input = TextInput(value=home_dir, title="Load file:") save_filepath_input = TextInput(value=home_dir, title="Save to directory:") force_save_filename_input = TextInput(value=str(dt.now(PT).year)+"_"+str(dt.now(PT).month)+"_"+str(dt.now(PT).day)+".h5", title="Save as filename:") save_filepath_PC_input = TextInput(value=home_dir, title="Save to directory:") save_filename_PC_input = TextInput(value="PC_"+str(dt.now(PT).year)+"_"+str(dt.now(PT).month)+"_"+str(dt.now(PT).day)+".h5", title="Save as filename:") #setup event handlers all_off_button.on_click(lambda x: allOff_ButtonClick()) all_on_button.on_click(lambda x: allOn_ButtonClick()) reset_button.on_click(lambda x: reset_ButtonClick()) autoscale_button.on_click(lambda x: autoscale_ButtonClick()) channel_button[0].on_click(lambda x: channel_ButtonClick(1)) channel_button[1].on_click(lambda x: channel_ButtonClick(2)) channel_button[2].on_click(lambda x: channel_ButtonClick(3)) channel_button[3].on_click(lambda x: channel_ButtonClick(4)) pulse_capture_button.on_click(lambda x: pulseCapture_ButtonClick()) auto_save_button.on_click(lambda x: autoSave_ButtonClick()) load_button.on_click(lambda x: load_ButtonClick()) force_save_button.on_click(lambda x: forceSave_ButtonClick()) save_PC_button.on_click(lambda x: savePC_ButtonClick()) acq_period_select.on_change("value", acqPeriod_SelectChange) channel_name_select1.on_change("value", chanNameSelect1_SelectChange)
class Dashboard: def __init__(self, default_dir, port): # path to source directory self.src_dir = os.path.dirname(os.path.abspath(__file__)) # MD directory and files selection self.md_dir = TextInput( title="Path to MD directory containing mdin and mdout files", value="", width=750) self.anim_button = Toggle(label="▶ Load", button_type="warning", width=80, height=50, active=False) self.port = port # container for the buttons that are created while the user types in the textinput self.autocomp_results = column(children=[]) # file used to display temperature, pressure...etc. plots self.mdout_sel = Select( title="MDout file", width=230, value=None, options=[], ) # button to load content self.mdout_button = Button(width=80, height=50, label="Plot", button_type="primary") self.mdout_files = [None] self.md_mdout_files = [] # mdinfo figures progressbar_tooltip = """ <span style="color:#428df5">@completed{0,0}</span> out of <span style="color:#428df5">@total{0,0}</span> steps (<span style="color:#428df5">@remaining{0,0}</span> remaining) """ self.progressbar = figure(title="Current progress", x_range=Range1d(0, 10), tooltips=progressbar_tooltip, height=70, width=350, tools="hover", toolbar_location=None) self.progressbar.xgrid.grid_line_color = None self.progressbar.ygrid.grid_line_color = None self.progressbar.axis.visible = False self.progressbar.outline_line_color = "#444444" self.steps_CDS = ColumnDataSource({ "total": [np.nan], "completed": [np.nan], "remaining": [np.nan], "color": ['#428df5'], }) self.progressbar.hbar( y=0, left=0, right="completed", source=self.steps_CDS, height=0.5, color="color", ) self.progressbar.hover[0].mode = "hline" self.calc_speed = Div(width=150, height=50, text="Calculation speed:", style={ "font-weight": "bold", "color": "#444444", "margin-top": "5px" }) self.eta = Div(width=280, height=50, text="Estimated time remaining:", style={ "font-weight": "bold", "color": "#444444", "margin-top": "5px" }) self.last_update = Div(width=280, height=50, text="Last update:", style={ "font-weight": "bold", "color": "#444444", "margin-top": "5px" }) # number of mdout files displayed on the dashboard at max self.slider = Slider(start=1, end=12, value=2, step=1, callback_policy="mouseup", title="Number of simulations displayed") self.dashboard_CDS = ColumnDataSource({ "y_coords": [0, 1], "mdout": ["heat.out", "prod.out"], "time": [42, 200], "angle": [1.09, 5.193], "color": [sim_palette[0], sim_palette[1]], }) dashboard_tooltip = """ <span style="color:@color">@mdout</span>: @time{0,0.00} ns """ # pie plot self.pie = figure(plot_height=300, width=500, title="Simulations length", toolbar_location=None, tools="hover", tooltips=dashboard_tooltip, x_range=Range1d(-0.5, 1.0)) self.rpie = self.pie.wedge(x=0, y=1, radius=0.4, source=self.dashboard_CDS, start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), line_color="white", fill_color='color', legend="mdout") self.pie.axis.axis_label = None self.pie.axis.visible = False self.pie.grid.grid_line_color = None self.pie.legend.label_text_font_size = '9pt' self.pie.legend.border_line_width = 0 self.pie.legend.border_line_alpha = 0 self.pie.legend.spacing = 0 self.pie.legend.margin = 0 # hbar plot self.bar = figure(width=850, plot_height=300, toolbar_location=None, tools="hover", tooltips=dashboard_tooltip) self.rbar = self.bar.hbar(y="y_coords", left=0, right="time", source=self.dashboard_CDS, height=0.8, color="color") self.bar.x_range.set_from_json("start", 0) self.bar.xaxis.axis_label = "Time (ns)" self.bar.yaxis.axis_label = None self.bar.yaxis.visible = False self.bar.hover[0].mode = "hline" ## Mdout figures self.mdinfo_CDS = ColumnDataSource(copy.deepcopy(empty_mddata_dic)) self.moving_avg_trans = CustomJSTransform(v_func=moving_avg_func) ticker = PrintfTickFormatter(format="%4.0e") # Temperature self.temperature_fig = figure( plot_height=size[1], plot_width=size[0], active_scroll="wheel_zoom", ) self.temperature_fig.toolbar.autohide = True self.temperature_fig.xaxis.axis_label = "Number of steps" self.temperature_fig.yaxis.axis_label = "Temperature (K)" self.temperature_fig.xaxis.formatter = ticker r = self.temperature_fig.line("Nsteps", "Temperature", color=palette[0], source=self.mdinfo_CDS, _width=1, alpha=0.15) self.temperature_fig.line(transform("Nsteps", self.moving_avg_trans), transform("Temperature", self.moving_avg_trans), color=colorscale(palette[0], 0.85), source=self.mdinfo_CDS, line_width=3) self.temperature_fig.add_tools(make_hover([r])) # Pressure self.pressure_fig = figure( plot_height=size[1], plot_width=size[0], active_scroll="wheel_zoom", ) self.pressure_fig.toolbar.autohide = True self.pressure_fig.xaxis.axis_label = "Number of steps" self.pressure_fig.yaxis.axis_label = "Pressure" self.pressure_fig.xaxis.formatter = ticker r = self.pressure_fig.line("Nsteps", "Pressure", color=palette[1], source=self.mdinfo_CDS, line_width=1, alpha=0.15) self.pressure_fig.line(transform("Nsteps", self.moving_avg_trans), transform("Pressure", self.moving_avg_trans), color=colorscale(palette[1], 0.85), source=self.mdinfo_CDS, line_width=3) self.pressure_fig.add_tools(make_hover([r])) # Energy self.energy_fig = figure( plot_height=size[1], plot_width=size[0], active_scroll="wheel_zoom", ) etot = self.energy_fig.line("Nsteps", "Etot", color=palette[2], source=self.mdinfo_CDS, line_width=1) ektot = self.energy_fig.line("Nsteps", "EKtot", color=palette[3], source=self.mdinfo_CDS, line_width=1) eptot = self.energy_fig.line("Nsteps", "EPtot", color=palette[4], source=self.mdinfo_CDS, line_width=1) legend = Legend(items=[ ("Total", [etot]), ("Kinetic", [ektot]), ("Potential", [eptot]), ], location="top_right") self.energy_fig.add_layout(legend, 'right') self.energy_fig.add_tools(make_hover([etot])) self.energy_fig.legend.location = "top_left" self.energy_fig.legend.click_policy = "hide" self.energy_fig.toolbar.autohide = True self.energy_fig.xaxis.axis_label = "Number of steps" self.energy_fig.yaxis.axis_label = "Energy" self.energy_fig.xaxis.formatter = ticker # Volume self.vol_fig = figure( plot_height=size[1], plot_width=size[0], active_scroll="wheel_zoom", ) self.vol_fig.toolbar.autohide = True self.vol_fig.xaxis.axis_label = "Number of steps" self.vol_fig.yaxis.axis_label = "Volume" self.vol_fig.xaxis.formatter = ticker r = self.vol_fig.line("Nsteps", "Volume", color=palette[6], source=self.mdinfo_CDS, line_width=1, alpha=0.15) self.vol_fig.line(transform("Nsteps", self.moving_avg_trans), transform("Volume", self.moving_avg_trans), color=colorscale(palette[6], 0.85), source=self.mdinfo_CDS, line_width=3) self.vol_fig.add_tools(make_hover([r])) # Density self.density_fig = figure( plot_height=size[1], plot_width=size[0], active_scroll="wheel_zoom", ) self.density_fig.toolbar.autohide = True self.density_fig.xaxis.axis_label = "Number of steps" self.density_fig.yaxis.axis_label = "Density" self.density_fig.xaxis.formatter = ticker r = self.density_fig.line("Nsteps", "Density", color=palette[7], source=self.mdinfo_CDS, line_width=1, alpha=0.15) self.density_fig.line(transform("Nsteps", self.moving_avg_trans), transform("Density", self.moving_avg_trans), color=colorscale(palette[7], 0.85), source=self.mdinfo_CDS, line_width=3) self.density_fig.add_tools(make_hover([r])) ## RMSD figure self.empty_rmsd_dic = {k: [] for k in ["Time", "RMSD"]} self.rmsd_CDS = ColumnDataSource(self.empty_rmsd_dic) self.rmsd_fig = figure( plot_height=size[1], plot_width=size[0], active_scroll="wheel_zoom", ) self.rmsd_fig.toolbar.autohide = True self.rmsd_fig.xaxis.axis_label = "Time (ps)" self.rmsd_fig.yaxis.axis_label = "RMSD (Å)" self.rmsd_fig.xaxis.formatter = ticker r = self.rmsd_fig.line("Time", "RMSD", color=palette[8], source=self.rmsd_CDS, line_width=2) self.rmsd_fig.add_tools( make_hover([r], tooltips=[("Time (ps)", "@Time{0,0}"), ("RMSD (Å)", "@RMSD")])) self.rmsd_button = Button(width=100, label="Calculate RMSD", button_type="primary") self.trajectory = MultiSelect( title="Trajectory file(s)", width=400, value=None, options=[], ) self.rst_traj = Select( title="Restart file", width=400, value=None, options=[], ) self.topology = Select( title="Topology file", width=200, value=None, options=[], ) self.mask = TextInput(title="Mask", value="protein@CA,C,O,N", width=200) # NGLview self.last_rst_update = 0 self.view_button = Button(width=80, label="Visualize", button_type="primary") self.view_canvas = Div(width=size[0], height=size[1] + 60, css_classes=["ngldiv"], text="") self.ngl_help_div = Div(width=0, height=0, text="") self.ngl_help_button = Toggle(width=80, label="Help", active=False) self.ngl_lig = TextInput(title="Ligand name", value="LIG", width=80) self.ngl_representations = CheckboxButtonGroup( labels=["Protein", "Ligand", "Water", "Lipids", "Ions"], active=[0, 1, 2, 3, 4], ) # info about simulation files (min, dt, rst and mdcrd files) self.mdout_info = {} # add callbacks self.add_callbacks() self.md_dir.value = default_dir def ngl_help(self, new_value): """Show help on NGL controls""" if self.ngl_help_button.active: self.ngl_help_div.width = 300 self.ngl_help_div.height = size[1] self.ngl_help_button.label = "Hide" self.ngl_help_div.text = NGL_HELP_TEXT else: self.ngl_help_div.width = 300 self.ngl_help_div.height = size[1] self.ngl_help_button.label = "Help" self.ngl_help_div.text = "" def autocomp_callback(self, attr, old, new): """List all directories for the current typed path, output as buttons to click""" path = os.path.join(new + "*", "") opts = [] if new == "" else glob.glob(path) opts = sorted(opts, key=lambda x: x.split("/")[-2].lower()) buttons = [Button(width=750, label=opt) for opt in opts] for b in buttons: cb = CustomJS(args=dict(md_dir=self.md_dir, button=b), code=""" md_dir.value_input = button.label; md_dir.value = button.label; """) b.js_on_click(cb) self.autocomp_results.children = buttons mdinfo_file = os.path.join(new, "mdinfo") if os.path.exists(mdinfo_file): self.anim_button.button_type = "success" else: self.anim_button.button_type = "warning" def traj_top_callback(self, attr, old, new): log.debug(f"Updating list of trajectory and topology files") try: # search netcdf files traj = [ f for f in os.listdir(self.md_dir.value) if re.search(r'.+\.n(et)?c(df)?$', f) ] traj.sort(key=lambda f: os.path.getmtime( os.path.join(self.md_dir.value, f)), reverse=True) self.trajectory.options = traj # search restart files restart = [ f for f in os.listdir(self.md_dir.value) if re.search(r'.+\.rst7?$', f) ] restart.sort(key=lambda f: os.path.getmtime( os.path.join(self.md_dir.value, f)), reverse=True) self.rst_traj.options = restart if self.rst_traj.options: self.rst_traj.value = self.rst_traj.options[0] # search for .top, .prmtop, .parm7 or .prm top = [ f for f in os.listdir(self.md_dir.value) if re.search(r'.+\.(prm)?top$', f) or re.search(r'.+\.pa?rm7?$', f) ] self.topology.options = top if self.topology.options: self.topology.value = self.topology.options[0] except FileNotFoundError: pass def compute_rmsd(self): """Compute RMSD during a trajectory""" self.rmsd_button.button_type = "default" mask = self.mask.value.replace( "protein", ":ALA,ARG,ASH,ASN,ASP,CYM,CYS,CYX,GLH,GLN,GLU,GLY,HID,HIE,HIP,HYP,HIS,ILE,LEU,LYN,LYS,MET,PHE,PRO,SER,THR,TRP,TYR,VAL" ) topology = os.path.join(self.md_dir.value, self.topology.value) trajectories = [ os.path.join(self.md_dir.value, f) for f in self.trajectory.value ] trajectories.sort(key=lambda f: os.path.getmtime(f), reverse=False) traj = pt.iterload(trajectories, topology) stepsize = get_stepsize(traj) frames = list( traj.iterframe(step=stepsize, autoimage=True, rmsfit=False, mask=mask)) log.debug( f"Computing RMSD for top {topology} and traj {trajectories} with a step of {stepsize}, using mask {mask}" ) ref = frames[0] results = {"Time": [], "RMSD": []} with ProcessPoolExecutor(max_workers=max_workers) as ex: for rmsd, frame in zip( ex.map(partial(compute_rmsd, ref=ref), frames), frames): results["Time"].append(frame.time) results["RMSD"].append(rmsd) del traj self.rmsd_CDS.data = results self.rmsd_button.button_type = "primary" def autoview_structure(self): """Load structure automatically if it has been modified recently""" # check if rst7 file was rewritten recently update_time = os.path.getmtime( os.path.join(self.md_dir.value, self.rst_traj.value)) # and viewing the latest rst7 file if (update_time > self.last_rst_update) and ( self.rst_traj.value == self.rst_traj.options[0]): log.debug( f"Updating {self.rst_traj.value} with more recent version: {update_time}" ) self.last_rst_update = update_time self.view_structure() else: log.debug(f"No recent update of restart {self.rst_traj.value}") def view_structure(self): """Visualize a restart file with NGL""" log.debug( f"Visualizing top {self.topology.value} and restart {self.rst_traj.value}" ) # load rst7 with pytraj (NGL cannot read it directly) traj = pt.load(os.path.join(self.md_dir.value, self.rst_traj.value), os.path.join(self.md_dir.value, self.topology.value)) traj = pt.autoimage(traj) ## pass to parmed to write the pdb data in a StringIO # struct = pmd.load_file(os.path.join(self.md_dir.value, self.topology.value), xyz=traj.xyz) # f = StringIO() # struct.write_pdb(f) # pdb_data = f.getvalue().encode("ascii") # f.close() # write as pdb to temporary file (much faster than parmed + StringIO) with NamedTemporaryFile() as f: pt.write_traj(f.name, traj, format="pdb", overwrite=True) pdb_data = f.read() # create javascript code with open(os.path.join(self.src_dir, "static", "js", "nglviewer.js")) as f: JS_TEMPLATE = f.read() self.js_view_structure.code = JS_TEMPLATE % (pdb_data) # trigger javascript callback by adding an invisible character to the button label self.view_button.label += " " def clear_canvas(self): """Clear the canvas""" log.debug("Clearing canvas") self.mdinfo_CDS.data = copy.deepcopy(empty_mddata_dic) def read_mdout_header(self, mdout): """Read the header of mdout file to search for info on minimization, dt, and output files""" log.debug(f"Reading header of {mdout} mdout file") mdout_path = os.path.join(self.md_dir.value, mdout) found_min, found_dt, found_rst, found_mdcrd = (False, False, False, False) with open(mdout_path, 'r') as f: for i, line in enumerate(f): re1 = re.search(r"imin\s*=\s*([01])", line) if re1: self.mdout_info[mdout]["min"] = bool(int(re1.group(1))) found_min = True re2 = re.search(r"dt\s*=\s*([\.0-9]+)", line) if re2: self.mdout_info[mdout]["dt"] = float(re2.group(1)) found_dt = True re3 = re.search(r"^\| RESTRT: ([^\s]+)\s*$", line) if re3: self.mdout_info[mdout]["rst"] = re3.group(1) found_rst = True re4 = re.search(r"^\| MDCRD: ([^\s]+)\s*$", line) if re4: self.mdout_info[mdout]["mdcrd"] = re4.group(1) found_mdcrd = True if found_min and found_rst and found_mdcrd: if self.mdout_info[mdout][ "min"]: # if min, there's no dt to find log.debug( f"Finished reading header of {mdout}. Closing minimization file." ) break else: if found_dt: log.debug( f"Finished reading header of {mdout}. Closing MD file." ) break elif i > 150: log.debug( f"Could not find all the information within the first 150 lines of {mdout}. Closing file." ) break def is_min(self, mdout): """Returns True if minimization, False if MD, None if the 'imin' keyword was not found""" try: t = self.mdout_info[mdout]["min"] except KeyError: log.debug( f"Parsing {mdout} mdout file to see if it's a minimization") self.read_mdout_header(mdout) t = self.mdout_info[mdout].get("min", None) log.debug(f"{mdout} is a minimization: {t}") return t def stream_mdout(self): """Parse and stream data from mdout files (minimization or MD simulation)""" self.mdout_button.button_type = "default" self.clear_canvas() mdout = self.mdout_sel.value mdout_path = os.path.join(self.md_dir.value, mdout) mdout_data = copy.deepcopy(empty_mddata_dic) # open file with open(mdout_path, 'r') as f: log.debug(f"Parsing data from {mdout} mdout file") lines = [] # stop reading when reaching the following lines for line in f: if ("A V E R A G E S O V E R" in line) or ( "Maximum number of minimization cycles reached" in line): break lines.append(line) # check if min or md: parse_func = parse_min_data if self.is_min(mdout) else parse_md_data # parse in parallel with ProcessPoolExecutor(max_workers=max_workers) as ex: for res in ex.map(parse_func, lines): for k, v in res.items(): mdout_data[k].extend(v) # convert to numpy for key, lst in mdout_data.items(): mdout_data[key] = np.array(lst) # stream to CDS log.debug(f"Done. Streaming the data from {mdout}") self.mdinfo_CDS.stream(mdout_data) mdout_data = copy.deepcopy(empty_mddata_dic) self.mdout_button.button_type = "primary" def latest_mdout_files(self): """List all mdout files present in the MD directory, sorted by modification time""" mdout_files = [ f for f in os.listdir(self.md_dir.value) if re.search(r'.+\.(md)?out$', f) and ("nohup.out" not in f) ] mdout_files.sort( key=lambda f: os.path.getmtime(os.path.join(self.md_dir.value, f)), reverse=True) return mdout_files def get_mdout_files(self): """Update the list of mdout files and automatically select the latest one""" log.debug("Updating the list of mdout files") self.mdout_files = [None] # set mdout file to read self.mdout_files = self.latest_mdout_files() for mdout in self.mdout_files: if not mdout in self.mdout_info: self.mdout_info[mdout] = {} mdout_options = self.mdout_sel.options self.mdout_sel.options = self.mdout_files # if new mdout is created if len(self.mdout_files) > len(mdout_options): self.mdout_sel.value = self.mdout_files[0] def parse_mdinfo(self): """Parse and stream data read from the mdinfo file""" log.debug("Parsing mdinfo file") mdinfo_path = os.path.join(self.md_dir.value, "mdinfo") try: with open(mdinfo_path, 'r') as f: lines = f.readlines() except FileNotFoundError: log.error("No mdinfo file in the current directory") return mdinfo_data = copy.deepcopy(empty_mddata_dic) # min or md latest_mdout_file = self.latest_mdout_files()[0] parse_func = parse_min_data if self.is_min( latest_mdout_file) else parse_md_data for i, line in enumerate(lines): # data res = parse_func(line) for k, v in res.items(): mdinfo_data[k].extend(v) # number of steps re_steps = re.search( r"Total steps :\s*(\d+) \| Completed :\s*(\d+) \| Remaining :\s*(\d+)", line) if re_steps: total = int(re_steps.group(1)) completed = int(re_steps.group(2)) remaining = int(re_steps.group(3)) steps_patch = { "total": [(0, total)], "completed": [(0, completed)], "remaining": [(0, remaining)], } self.steps_CDS.patch(steps_patch) progress = 100 * completed / total self.progressbar.title.text = f"Progress: {progress:6.2f}%" self.progressbar.x_range.set_from_json("end", total) # calculation speed (ns/day) re_speed = re.search(r'Average timings for last', line) if re_speed: re_speed = re.search(r'ns/day =\s*([\.0-9]+)', lines[i + 2]) speed = float(re_speed.group(1)) self.calc_speed.text = f"Calculation speed:<br/>{speed} ns/day" # time remaining re_time = re.search(r'Estimated time remaining:\s*(.+).$', line) if re_time: time_left = re_time.group(1) time_left = pretty_time(time_left) self.eta.text = f"Estimated time remaining:<br/>{time_left}" break # last update self.last_update.text = f"Last update:<br/>{time_passed(os.path.getmtime(mdinfo_path))}" update_time = os.path.getmtime(mdinfo_path) if time.time() - update_time > 5 * 60: # not updated recently self.last_update.style = { "font-weight": "bold", "color": "#d62727", "margin-top": "5px" } else: self.last_update.style = { "font-weight": "bold", "color": "#444444", "margin-top": "5px" } # only update plots if monitoring the latest mdout file if self.mdout_sel.value == latest_mdout_file: log.debug( f"Currently watching the latest mdout '{self.mdout_sel.value}'" ) # fetch previous stream data as dict last_mdinfo_stream = self.mdinfo_CDS.to_df().tail(1).reset_index( drop=True).T.to_dict().get(0) if last_mdinfo_stream: # format the dict for key, value in last_mdinfo_stream.items(): last_mdinfo_stream[key] = [value] # update if mdinfo is different from the previous stream if mdinfo_data != last_mdinfo_stream: log.debug("Streaming new data from mdinfo") for key, value in mdinfo_data.items(): mdinfo_data[key] = np.array(value) self.mdinfo_CDS.stream(mdinfo_data) else: log.debug( f"No previous mdinfo data could be retrieved. Streaming new data" ) for key, value in mdinfo_data.items(): mdinfo_data[key] = np.array(value) self.mdinfo_CDS.stream(mdinfo_data) else: log.debug( f"Currently watching mdout '{self.mdout_sel.value}' != '{latest_mdout_file}'" ) def display_simulations_length(self): """Displays simulation length""" log.debug("Computing total time of simulation(s) displayed") current_time = OrderedDict() # discard min files and limit to XX most recent MD files self.md_mdout_files = [ f for f in self.mdout_sel.options if not self.is_min(f) ][:self.slider.value] for mdout in self.md_mdout_files: mdout_path = os.path.join(self.md_dir.value, mdout) i = 0 for line in readlines_reverse(mdout_path): i += 1 re1 = re.search(r"NSTEP =\s*(\d+)", line) if re1: current_time[mdout] = int( re1.group(1)) * self.mdout_info[mdout].get( "dt", 0.002) * 1e-3 # in ns break if i > 150: break data = pd.DataFrame.from_dict( current_time, orient="index", columns=["time"]).reset_index().rename(columns={"index": "mdout"}) # compute properties for the pie plot data['angle'] = data['time'] / data['time'].sum() * 2 * pi # color palette data['color'] = sim_palette[:len(current_time)] # reverse index order for the barplot data = data.reindex(index=data.index[::-1]).reset_index(drop=True) data = data.reset_index().rename(columns={"index": "y_coords"}) # update self.dashboard_CDS.data = {k: data[k].tolist() for k in data.columns} total_time = data["time"].sum() self.pie.title.text = f"Simulations length: {total_time:.2f} ns" def update_dashboard(self): log.debug("Starting update of the dashboard") self.get_mdout_files() self.parse_mdinfo() self.display_simulations_length() self.autoview_structure() log.debug("Finished updating the dashboard") def callback_slider(self, attr, old, new): log.debug(f"Slider update detected: from {old} to {new}") self.display_simulations_length() def add_callbacks(self): log.debug("Adding callbacks to the widgets") # User input self.md_dir.on_change("value_input", self.autocomp_callback) self.md_dir.on_change("value", self.traj_top_callback) # RMSD self.rmsd_button.on_click(self.compute_rmsd) # NGLView self.js_view_structure = CustomJS(code="", args={ "ligand_mask": self.ngl_lig, "repr": self.ngl_representations }) self.ngl_help_button.on_click(self.ngl_help) # hack to execute both python and JS code on button click self.view_button.js_on_change("label", self.js_view_structure) self.view_button.on_click(self.view_structure) # MDout parsing self.mdout_button.on_click(self.stream_mdout) self.slider.on_change("value_throttled", self.callback_slider)
def clear_plot(): source_data.data = dict(x=[], y=[]) pred_data.data = dict(x=[], y=[]) source_bars.data = dict(y=[], right=[]) pred_line.data = dict(y=[], x=[]) tsne.data = dict(y=[], x=[]) def update_plot_signature(attr, old, new): # print(attr, old, new) update_plot() # Disable controls initially change_model() plot_run.on_click(update_plot) plot_clear.on_click(clear_plot) ctl_model.on_click(change_model) ctl_feat_reduce.on_click(update_plot) ctl_kernel.on_click(update_plot) # Page Layout col_inputs = column(plot_ctls, ctl_inputs, disp_disclaimer) row_plots = row(plot_mood_scatter, plot_mood_bar) row_page = row(col_inputs, column(row_plots, plot_feature_scatter), column(disp_features, disp_score), width=1200) curdoc().add_root(row_page) curdoc().title = "Daylio Data Display"
class signalHandler: def __init__(self, signalFilename="", lyricsFilename="", signal=[], sampleRate=0, color="red"): self.path = os.getcwd() logger.logData(source="Signal handler", priority="INFO", msgType="Setup start", msgData=()) self.setupVariables() self.color = color self.setupMapper() self.setupColorBar() self.lyricsImported = 0 if lyricsFilename: self.setupLyricTable(lyricsFilename) self.lyricsImported = 1 self.setupPlotWindow() self.setupControls() self.setupGUI() if signalFilename: self.importFile(signalFilename) if len(signal): self.addSignal(signal, sampleRate) logger.logData(source="Signal handler", priority="INFO", msgType="Setup done", msgData=((self.signalImported))) def setupGUI(self): """Wraps the plot, slider, and tool column into one layout""" audioCol = column(self.p, self.colorBar, self.timeWindowSlider, height=self.figureHeight) # dtgCol = column(self.dtg.gui,width=400) if self.lyricsImported: # self.gui = self.lyricTable self.gui = row(self.lyricsGui, audioCol, self.controls, height=self.figureHeight - 110) else: self.gui = row(audioCol, self.controls, height=self.figureHeight - 110) def setupVariables(self): """sets up important variables to the tool, including figure size dummy variables to use before file import tool options - loop, button sizes, button delay """ try: self.masterPath = os.getcwd() #use when running from Sublime os.listdir(self.masterPath) except: self.masterPath = os.path.join("soundTools", "") #use when running from Bokeh os.listdir(self.masterPath) self.subtitlePath = os.path.join(self.masterPath, "lyrics") self.audioPath = os.path.join(self.masterPath, "audio") self.webpagePath = os.path.join(self.masterPath, "webpages") self.figureWidth = 1000 self.figureHeight = 500 self.buttonWidth = 200 self.buttonHeight = 15 self.numXTicks = 4 self.soundPlaying = 0 self.signalImported = 0 self.lastChunkIndex = 0 self.strideMultiplier = 100 self.sweepStartSample = 0 self.sweepEndSample = 10 self.activeChannels = [] self.yRange = (-1, 1) self.channelAnchorYs = np.arange(0, self.yRange[1], 2) self.plotStyle = 0 self.glyphsSetup = 0 self.loop = 0 self.plotMode = 0 self.outputFilename = "output" self.updateDelay = 12 #delay period in milliseconds befwen timeline update while playing the active signal self.windowChunkLength = 10 #seconds def showGui(self): output_file(os.path.join("webpages", "signalHandler.html")) logger.logData(source="Signal handler", priority="INFO", msgType="Show", msgData=()) show(self.gui) curdoc().add_root(self.gui) def addSignal(self, signalIn, sampleRate): """Adds a prexisting list/mutliDim ndarray (with known sample rate) as the active signal and updates plot""" #keep the original in case of a reset self.originalSignal = signalIn #signal gets changed on update of time slider self.signal = self.originalSignal #activeSignal gets changed on update of of signal AND update of active channels self.activeSignal = self.signal self.sampleRate = sampleRate self.signalImported = 1 logger.logData(source="Signal handler", priority="INFO", msgType="Signal add", msgData=()) self.analyzeSignal() def importFile(self, filename): """imports a wav file into the tool and updates the plot and tools""" #check to make sure the filename is valid try: self.sampleRate, self.originalSignal = wavfile.read( os.path.join( self.audioPath, filename)) #keep the original for master reference except: logger.logData(source="Signal handler", priority="WARN", msgType="Import file fail", msgData=(filename)) return #update tool's internal filename self.filename = filename #import the wav file self.signal = self.originalSignal self.activeSignal = self.signal self.signalImported = 1 logger.logData(source="Signal handler", priority="INFO", msgType="Import file", msgData=(filename)) #get relevant signal info and update plot self.analyzeSignal() def analyzeSignal(self): """Parses the metadata from the active signal and updates plot and tools""" self.glyphsSetup = 0 #get number of channels of signal try: self.numChannels = self.signal.shape[1] except: #if it's single channel its imported as a list, make it a 1D ndarray self.numChannels = 1 self.signal = np.transpose(np.array([self.signal])) self.activeChannels = list(range(self.numChannels)) self.channelButtons.labels = list(map(str, self.activeChannels)) self.channelButtons.active = self.activeChannels if not np.any(self.signal): logger.logData(source="Signal handler", priority="WARN", msgType="Empty", msgData=()) return self.numSamples = len(self.signal) self.sampleIndices = range(self.numSamples) self.messedUpTs = self.sampleIndices self.updateMapperHigh(self.numSamples) self.updateColorBar(self.sampleIndices) self.signalDuration = self.numSamples / float(self.sampleRate) self.windowChunks = int( (self.signalDuration / self.windowChunkLength)) + 1 #update the time slider with the imported signal's duration self.timeWindowSlider.end = self.signalDuration self.timeWindowSlider.value = [0, self.signalDuration] self.sweepStartSample = 0 self.sweepEndSample = self.numSamples #setup the ticker to replace indices with timestamps self.setMasterXAxisTicker() #PLOT SCATTER PARAMS #get max amplitude of signal to adjust y range and channel spacing self.sigPeak = np.amax(self.signal) self.yRange = (0, 2 * self.numChannels * self.sigPeak) #generate offsets to space multiple channels out in the y axis self.channelAnchorYs = np.arange(self.sigPeak, self.yRange[1], 2 * self.sigPeak) logger.logData(source="Signal handler", priority="INFO", msgType="Analyze", msgData=(round(self.signalDuration, 3), self.numChannels, self.numSamples, self.sampleRate)) self.drawActivePlot() # self.drawFullSignal() def setMasterXAxisTicker(self): #ticker dictionary to rpaplce x axis index ticks with their coirrosponding timestamps self.masterTicker = list( range(0, self.numSamples, int(self.numSamples / self.numXTicks))) self.masterXAxisOverrideDict = {} timeLabels = np.linspace(0, self.signalDuration, self.numXTicks) for sampleInd, timeLabel in zip(self.masterTicker, timeLabels): self.masterXAxisOverrideDict[sampleInd] = str(round(timeLabel, 3)) #set up the size and duration of the sub-chunks displayed while the signal is playing self.samplesPerChunk = int(self.numSamples / self.windowChunks) self.chunkDuration = self.samplesPerChunk / float(self.sampleRate) #the absolute index values comprising the x axis ticks self.chunkTicker = list( range(0, self.samplesPerChunk, int(self.samplesPerChunk / self.numXTicks))) # self.chunkLabels = np.linspace(0,self.chunkDuration,10) # self.chunkTickOverride = {} # for sampleInd,timeLabel in zip(self.chunkTicker,self.chunkLabels): # self.chunkTickOverride[sampleInd] = str(round(timeLabel,3)) def fileCallback(self, attr, old, new): """Callback assigned to choosing a file from the file browser""" filename = new['file_name'][0] self.importFile(filename) def fileButtonSetup(self): """Creates a "File opener" button and assigns a javascript callback to it that opens an os-independent file picker window imports chosen file into the class""" fileSource = ColumnDataSource({'file_name': []}) self.fileImportButton.callback = CustomJS( args=dict(file_source=fileSource), code=""" function read_file(filename) { var reader = new FileReader(); reader.onload = load_handler; reader.onerror = error_handler; // readAsDataURL represents the file's data as a base64 encoded string reader.readAsDataURL(filename); } function load_handler(event) { file_source.data = {'file_name':[input.files[0].name]}; file_source.trigger("change"); } function error_handler(evt) { if(evt.target.error.name == "NotReadableError") { alert("Can't read file!"); } } var input = document.createElement('input'); input.setAttribute('type', 'file'); input.onchange = function(){ if (window.FileReader) { read_file(input.files[0]); } else { alert('FileReader is not supported in this browser'); } } input.click(); """) fileSource.on_change('data', self.fileCallback) def setupControls(self): """Called on setup, creates buttons and sliders to: open a local audio file set loop mode update the active timespan play the active timespan set filename to save active signal to save active signal to that filename """ #check boxes to choose what plots to display self.plotModeButtons = RadioButtonGroup( labels=["Wav", "FFT", "Spectrogram"], active=self.plotMode, button_type="warning", width=self.buttonWidth, height=self.buttonHeight) self.plotModeButtons.on_change("active", self.plotModeCallback) #choose betwen line or scatter plot self.plotStyleButtons = RadioButtonGroup(labels=["Line", "Scatter"], active=0, button_type="danger", width=self.buttonWidth, height=self.buttonHeight) self.plotStyleButtons.on_change("active", self.plotStyleCallback) channelTitle = Div(text="""<b>Audio Channels:</b>""", width=self.buttonWidth, height=2) self.channelButtons = CheckboxButtonGroup(labels=["-"], active=[0], button_type="primary", width=self.buttonWidth, height=self.buttonHeight) self.channelButtonRow = column(channelTitle, self.channelButtons, width=self.buttonWidth, height=self.buttonHeight * 2) self.channelButtons.on_change("active", self.channelButtonCallback) #creates a filebutton and assigns it a callback linked to a broser-based file browser self.fileImportButton = Button(label="Import File", button_type="success", width=self.buttonWidth, height=self.buttonHeight) self.fileButtonSetup() #create a loop toggle button and assigns a callback to it self.loopAudioToggle = Toggle(label="Loop", button_type="success", width=self.buttonWidth, height=self.buttonHeight) self.loopAudioToggle.on_click(self.loopAudioCallback) #double ended slider to clip audio by time self.timeWindowSlider = RangeSlider(start=0, end=1, value=[0, 1], step=.05, title="Wav File Window", width=self.figureWidth, height=self.buttonHeight) self.timeWindowSlider.on_change("value", self.timeSliderCallback) #button to commit clip changes to active signal self.updateButton = Button(label="Update", button_type="success", width=self.buttonWidth, height=self.buttonHeight) self.updateButton.on_click(self.updateButtonCallback) #button to play active signal, self.playButton = Button(label="Play", button_type="success", width=self.buttonWidth, height=self.buttonHeight) self.playButton.on_click(self.playSound) self.filenameBox = TextInput(value="output", title="Output Filename:", width=self.buttonWidth) #button to write active signal to file self.writeFileButton = Button(label="Write Active File", button_type="success", width=self.buttonWidth, height=self.buttonHeight) self.writeFileButton.on_click(self.writeFileButtonCallback) #button to reset tool to state right after signal import self.resetButton = Button(label="Reset", button_type="success", width=self.buttonWidth, height=self.buttonHeight) self.resetButton.on_click(self.resetButtonCallback) self.resetZoomButton = Button(label="Reset Zoom", button_type="success", width=self.buttonWidth, height=self.buttonHeight) self.resetZoomButton.js_on_click( CustomJS(args=dict(p=self.p), code=""" p.reset.emit() """)) self.generalControlsColumn = column(self.plotModeButtons, self.plotStyleButtons, self.filenameBox, self.channelButtonRow, width=self.buttonWidth) self.buttonColumn = column( self.resetZoomButton, self.fileImportButton, self.updateButton, self.loopAudioToggle, self.playButton, self.writeFileButton, self.resetButton, width=self.buttonWidth) #,height=self.figureHeight) self.controls = row(self.generalControlsColumn, self.buttonColumn) #wrap buttons and text box in a column of fixed width # self.buttonColumn = column(self.plotModeButtons,self.plotStyleButtons,self.channelButtonRow,self.resetZoomButton,self.fileImportButton,self.updateButton,self.loopToggle,self.playButton,self.writeFileButton,self.resetButton,self.filenameBox,width=self.buttonWidth)#,height=self.figureHeight) #choose active channels def channelButtonCallback(self, attr, old, new): if not self.signalImported: return try: self.activeChannels = new self.activeSignal = self.signal[:, self.activeChannels] self.glyphsSetup = 0 self.drawActivePlot() logger.logData(source="Signal handler", priority="INFO", msgType="Channel update", msgData=((old, new))) except: logger.logData(source="Signal handler", priority="WARN", msgType="Channel fail", msgData=(old)) return #choose between line or scatter plot def plotStyleCallback(self, attr, old, new): self.plotStyle = new self.glyphsSetup = 0 # self.drawFullSignal() self.drawActivePlot() def plotModeCallback(self, att, old, new): self.plotMode = new self.drawActivePlot() def loopAudioCallback(self, event): """Called on toggling of the loop button, binary inverts previous loop val""" self.loop = 1 - self.loop def writeFileButtonCallback(self): """Called on click of the write Fiile button Writes the active signal to the filename set by the textbox""" outputFilename = self.filenameBox.value + ".wav" outputPath = os.path.join(self.path, "audio", outputFilename) numChannels = len(self.activeChannels) logger.logData(source="Signal handler", priority="INFO", msgType="Write", msgData=(outputFilename, numChannels, self.sampleRate, self.signalDuration)) wavfile.write(outputPath, self.sampleRate, self.activeSignal) # def resetZoomCallback(self): # print(1) def resetButtonCallback(self): """Returns the tool to state it was immediately after file was imported""" #if no signal is imported, do nothing if not self.signalImported: return #reset active to signal to the original, unclipped signal self.signal = self.originalSignal logger.logData(source="Signal handler", priority="INFO", msgType="Reset", msgData=()) #return variables and plot to original state self.analyzeSignal() def updateButtonCallback(self): """Called on press of the update button, clips the signsal by the estart and end trimes decreed by the time slider, resets the plot to like the clipped signal is the new full signal""" #if no signal is imported, do nothing if not self.signalImported: logger.logData(source="Signal handler", priority="WARN", msgType="Update failed", msgData=()) return #clip all channel samples corresponding to the times on slider self.signal = self.signal[self.sweepStartSample:self.sweepEndSample, :] logger.logData(source="Signal handler", priority="INFO", msgType="Update signal", msgData=()) #update variables and plot with clipped signal self.analyzeSignal() def timeSliderCallback(self, attr, old, new): """Called on update of the time slider moves the sweep start/end lines used for clipping the signal when the update button is pressed""" if not self.signalImported: return try: #convert the start and end times to integer sample numbers and update internal locations self.sweepStartSample = int(new[0] * self.sampleRate) self.sweepEndSample = int(new[1] * self.sampleRate) #update sweep line graphics startLine = self.p.select_one({'name': 'sweepStartLine'}) startLine.data_source.data = { 'x': [self.sweepStartSample, self.sweepStartSample], 'y': self.yRange } endLine = self.p.select_one({'name': 'sweepEndLine'}) endLine.data_source.data = { 'x': [self.sweepEndSample, self.sweepEndSample], 'y': self.yRange } except: return def shiftSamples(self, channelIndex): """Element wise adds to the channel's vector to offset a channel so it can vbe plotted alongside other channels""" channelSamples = self.signal[:, channelIndex] reducedSamples = channelSamples[::self.strideMultiplier] return reducedSamples + self.channelAnchorYs[channelIndex] def setupPlotWindow(self): """Creates a window containing the channel plots""" p = figure(height=300, width=self.figureWidth, x_range=(0, 1), y_range=(0, 1), tools="box_zoom", toolbar_location=None, output_backend="webgl") # p.toolbar.active_scroll = "auto" p.yaxis.visible = False p.grid.visible = False self.p = p def updateFigureForSpectrogram(self): self.p.x_range.end = self.numSamples self.p.y_range.end = self.yRange[1] self.p.xaxis.ticker = self.masterTicker self.p.xaxis.major_label_overrides = self.masterXAxisOverrideDict def plotSpectrogram(self): """Plots a log spectrogram of the active audio, returns figure object""" #max freq represetnedf (nyquist constrained) imgHeight = self.sampleRate / 2 self.p.y_range.end = imgHeight * self.numChannels imgWidth = self.signalDuration self.p.x_range.end = imgWidth for channelNum in self.activeChannels: channelSignal = self.signal[:, channelNum] freqs, times, data = self.log_specgram(channelSignal, self.sampleRate) self.p.image(image=[data], x=0, y=imgHeight * channelNum, dw=imgWidth, dh=imgHeight, palette="Spectral11") def log_specgram(self, audio, sampleRate, window_size=20, step_size=10, eps=1e-10): """Kraggin log spectrogram useful for MFCC analysis""" nperseg = int(round(window_size * sampleRate / 1e3)) noverlap = int(round(step_size * sampleRate / 1e3)) freqs, times, spec = spectrogram(audio, fs=sampleRate, window='hann', nperseg=nperseg, noverlap=noverlap, detrend=False) return freqs, times, np.log(spec.T.astype(np.float32) + eps) def setupPlotScatterGlyphs(self): self.p.line([self.sweepStartSample, self.sweepStartSample], self.yRange, color="blue", line_width=2, name="sweepStartLine") self.p.line([self.sweepEndSample, self.sweepEndSample], self.yRange, color="blue", line_width=2, name="sweepEndLine") self.p.line([0, 0], self.yRange, color='red', line_width=2, name='timeLine') # self.scatterSource = {"x":[],"place":[]} self.scatterSources = [] for channelNum in self.activeChannels: self.scatterSources.append( ColumnDataSource({ "x": list(self.sampleIndices), "y": list(self.sampleIndices), "place": self.messedUpTs })) # self.p.scatter(x=[],y=[],radius=.1, fill_color={'field':"place",'transform': self.mapper},name="audioLine" + str(channelNum)) self.p.scatter(x="x", y="y", radius=1, source=self.scatterSources[channelNum], fill_color={ 'field': "place", 'transform': self.mapper }, line_color={ 'field': "place", 'transform': self.mapper }, name="audioLine" + str(channelNum)) def setupLinePlotGlyphs(self): self.p.line([self.sweepStartSample, self.sweepStartSample], self.yRange, color="blue", line_width=2, name="sweepStartLine") self.p.line([self.sweepEndSample, self.sweepEndSample], self.yRange, color="blue", line_width=2, name="sweepEndLine") self.p.line([0, 0], self.yRange, color='red', line_width=2, name='timeLine') for channelNum in self.activeChannels: self.p.line(x=[], y=[], line_width=.3, color=self.color, name="audioLine" + str(channelNum)) def drawActivePlot(self): if not self.signalImported: return if self.plotMode == 0: self.drawFullSignal() elif self.plotMode == 1: self.getFFT() else: self.plotSpectrogram() def drawFullSignal(self): if self.glyphsSetup == 0: self.p.renderers = [] if self.plotStyle: self.setupPlotScatterGlyphs() else: self.setupLinePlotGlyphs() self.glyphsSetup = 1 """redraws each channel of the full plot and updates the xaxis to the full signal duration""" for channelNum in self.activeChannels: shiftedSamples = self.shiftSamples(channelNum) reducedSampleIndices = self.sampleIndices[::self.strideMultiplier] if self.plotStyle: self.scatterSources[channelNum].data = { 'x': reducedSampleIndices, 'y': list(shiftedSamples), "place": reducedSampleIndices } else: channelLine = self.p.select_one( {'name': 'audioLine' + str(channelNum)}) channelLine.data_source.data = { 'x': reducedSampleIndices, 'y': shiftedSamples, "place": reducedSampleIndices } #update x axis with full timespan self.p.x_range.end = self.numSamples self.p.y_range.end = self.yRange[1] self.p.xaxis.ticker = self.masterTicker self.p.xaxis.major_label_overrides = self.masterXAxisOverrideDict def playSound(self): """Starts playing the signal, and draws a sweeping vertical line on actively updating sub-samples of the audfio""" #if the "Play" button is pushed during play, it acts as a stop button if self.soundPlaying == 1: logger.logData(source="Signal handler", priority="INFO", msgType="Pause", msgData=()) self.stopAudio() return #if no signal is imported, do nothing if not self.signalImported: return #hide sweep lines until their chunk occurs startLine = self.p.select_one({'name': 'sweepStartLine'}) startLine.visible = False endLine = self.p.select_one({'name': 'sweepEndLine'}) endLine.visible = False ##Chunk-specific sweep lines self.startLineAdded = 0 self.endLineAdded = 0 #precompute which chunk the sweep lines are in for speed self.sweepStartChunk = int( np.floor(self.sweepStartSample / (self.samplesPerChunk + 1))) self.sweepEndChunk = int( np.floor(self.sweepEndSample / (self.samplesPerChunk + 1))) #precompute their indices in their chunk self.shiftedSweepStart = self.sweepStartSample - self.sweepStartChunk * self.samplesPerChunk self.shiftedSweepEnd = self.sweepEndSample - self.sweepEndChunk * self.samplesPerChunk if self.p.select_one({'name': 'sweepEndLineChunk'}) == None: #preadd the lines for speed self.p.line([self.shiftedSweepStart, self.shiftedSweepStart], self.yRange, color="blue", line_width=2, visible=False, name="sweepStartLineChunk") self.p.line([self.shiftedSweepEnd, self.shiftedSweepEnd], self.yRange, color="blue", line_width=2, visible=False, name="sweepEndLineChunk") #update the x axis with the sub-chunk values self.p.x_range.end = self.samplesPerChunk self.p.xaxis.ticker = self.chunkTicker self.p.xaxis.major_label_overrides = self.createChunkXAxisOverrideDict( 0) #set the play button to read "Pause" to pull double duty self.playButton.label = "Pause" logger.logData(source="Signal handler", priority="INFO", msgType="Play", msgData=()) #log start time to keep track of where the time line should be self.startTime = time.time() #start playing the sound try: sd.play(self.activeSignal, self.sampleRate, loop=self.loop, blocking=False) except: logger.logData(source="Signal handler", priority="CRIT", msgType="Play failed", msgData=()) self.playButton.label = "Play" return self.soundPlaying = 1 #add a call callback to trigger periodcially and update the timeline and sub-samples self.perCallback = curdoc().add_periodic_callback( self.update, self.updateDelay) def createChunkXAxisOverrideDict(self, chunkIndex): """ creates a dictionary replacing absolute index ticks on the x axis with their corrosponding times """ #get the time labels corrosponding to this chunk chunkTimeLabels = np.linspace(self.chunkDuration * chunkIndex, self.chunkDuration * (chunkIndex + 1), self.numXTicks) chunkTickOverride = {} for sampleInd, timeLabel in zip(self.chunkTicker, chunkTimeLabels): #replace each sample index x tick with the time label chunkTickOverride[sampleInd] = str(round(timeLabel, 3)) return chunkTickOverride def update(self): """Set to be called periodically when audio is playing to draw the active time line on the audio signal""" if self.loop: #mod the time played by total signal duration to keep the time line accurate for multiple plays deltaTime = ( time.time() - self.startTime ) % self.signalDuration #get time elapsed since the file started playing else: deltaTime = time.time( ) - self.startTime #get time elapsed since the file started playing #if signal not done playing if deltaTime < self.signalDuration: #number of samples elapsed dSamples = deltaTime * self.sampleRate #get the active chunk chunkIndex = int(self.windowChunks * (dSamples / self.numSamples)) #if the chunk is different, need to update the audio plot window to the next chunk if self.lastChunkIndex != chunkIndex: #get the starting and ending sample indices for the next chunk chunkStartIndex = self.samplesPerChunk * chunkIndex chunkEndIndex = self.samplesPerChunk * (chunkIndex + 1) #check if any of the sweep lines lie in this chunk if self.startLineAdded: self.p.select_one({ 'name': 'sweepStartLineChunk' }).visible = False self.startLineAdded = 0 if chunkIndex == self.sweepStartChunk: self.p.select_one({ 'name': 'sweepStartLineChunk' }).visible = True self.startLineAdded = 1 if self.endLineAdded: self.p.select_one({ 'name': 'sweepEndLineChunk' }).visible = False self.endLineAdded = 0 if chunkIndex == self.sweepEndChunk: self.p.select_one({ 'name': 'sweepEndLineChunk' }).visible = True self.endLineAdded = 1 #get the signal samples from this chunk and downsample them and shift them by channel reducedChunkSamps = self.signal[ chunkStartIndex:chunkEndIndex:self. strideMultiplier] + self.channelAnchorYs reducedPlaces = list( range(chunkStartIndex, chunkEndIndex, self.strideMultiplier)) #original # chunkSamps = self.signal[chunkStartIndex:chunkEndIndex] # shiftedChunkSamps = chunkSamps + self.channelAnchorYs reducedSampleIndices = list( range(0, self.samplesPerChunk, self.strideMultiplier)) #update plot for each channel for channelIndex in self.activeChannels: if self.plotMode == 0: audioLine = self.p.select_one( {'name': "audioLine" + str(channelIndex)}) audioLine.data_source.data = { 'x': reducedSampleIndices, 'y': reducedChunkSamps[:, channelIndex], "place": reducedPlaces } # audioLine.data_source.data = {'x': reducedSampleIndices, 'y': shiftedChunkSamps[:,channelIndex],"place":reducedPlaces} else: self.scatterSources[channelIndex].data = { "x": reducedSampleIndices, "y": self.sampleIndices, "place": self.messedUpTs } #update the x-axis ticks with the new times self.p.xaxis.major_label_overrides = self.createChunkXAxisOverrideDict( chunkIndex) #update chunk index with new one self.lastChunkIndex = chunkIndex ##time line update #get the glyph for the time line timeLine = self.p.select_one({'name': 'timeLine'}) #sample index of the timeline is total samples elapsed less the number of samples in all previous chunks timeLineIndex = dSamples - chunkIndex * self.samplesPerChunk #update the time line with the new times timeLine.data_source.data = { 'x': [timeLineIndex, timeLineIndex], 'y': self.yRange } #signal IS done playing else: if self.loop: return else: self.stopAudio() def stopAudio(self): """Stops the audio playing, returns the plot to state before audio started playing""" #stop the updating of the time line curdoc().remove_periodic_callback(self.perCallback) #stop playing the signal sd.stop() self.soundPlaying = 0 #change play button back to play from pause self.playButton.label = "Play" logger.logData(source="Signal handler", priority="INFO", msgType="Play done", msgData=()) #restore plot to full signal self.drawActivePlot() #redraw sweep lines on the full signal plot startLine = self.p.select_one({'name': 'sweepStartLine'}) startLine.visible = True endLine = self.p.select_one({'name': 'sweepEndLine'}) endLine.visible = True #return time line to t=0 timeLine = self.p.select_one({'name': 'timeLine'}) timeLine.data_source.data["x"] = [0, 0] def setupMapper(self): self.mapper = LinearColorMapper(palette="Inferno256", low=0, high=10) def updateMapperPalette(self, newColors): self.mapper.palette = newColors def updateMapperHigh(self, newHigh): self.mapper.high = newHigh def updateColorBar(self, times): colorBarPlot = self.gui.select_one({'name': 'colorBarPlot'}) colorBarPlot.x_range.end = self.numSamples colorBar = self.gui.select_one({'name': 'colorBar'}) self.messedUpTs = times self.colorSource.data = { "x": self.sampleIndices, "place": self.messedUpTs } # if self.plotMode == 1: # for channelInd in self.activeChannels: # self.scatterSources[channelIndex].data = {"x":self.sampleIndices,"y":self.sampleIndices,"place":self.messedUpTs} def setupColorBar(self): colorTimeline = figure(height=30, y_range=(-.5, .5), width=self.figureWidth, x_range=(0, 10), toolbar_location=None, output_backend="webgl", name="colorBarPlot", tools="") colorTimeline.axis.visible = False colorTimeline.grid.visible = False # colorTimeline.image(image=range(self.numSamples),x=0,y=.5,dh=1,dw=1,fill_color={'field':"x",'transform': self.mappers[colorBarType-1]}, # name="cbar" + str(colorBarType)) self.colorSource = ColumnDataSource({ "x": range(10), "place": range(10) }) # colorTimeline.rect(x="x", y=0, width=1, height=1,fill_color={'field':"place",'transform': self.mapper},name="colorBar", # line_width=0.0,line_color= None,line_alpha = 0.0,source=colorSource # ) colorBar = Rect(x="x", y=0, width=1, height=1, fill_color={ 'field': "place", 'transform': self.mapper }, name="colorBar", line_width=0.0, line_color=None, line_alpha=0.0) colorTimeline.add_glyph(self.colorSource, colorBar) self.colorBar = colorTimeline def getFFT(self): """Plots the fast fourier transform of the active audio, returns a figure object""" fftHeight = self.numChannels self.p.y_range.end = fftHeight maxFreq = self.sampleRate / 2 self.p.x_range.end = maxFreq for channelNum in self.activeChannels: sigPadded = self.signal[:, channelNum] # Determine frequencies f = np.fft.fftfreq(self.numSamples) * self.sampleRate #pull out only positive frequencies (upper half) upperHalf = int(len(f) / 2) fHalf = f[:upperHalf] # Compute power spectral density psd = np.abs(np.fft.fft(sigPadded))**2 / self.numSamples #pull out only power densities for the positive frequencies psdHalf = psd[:upperHalf] #nromalize y vals psdHalf = psdHalf / max(psdHalf) #shift them to allow multiple channels psdHalf += channelNum self.p.line(fHalf, psdHalf) def lyricModeCallback(self, event): self.lyricMode = 1 - self.lyricMode if self.lyricMode: self.lyricsHandler.lyricModeButton.label = "Change to start lyric" else: self.lyricsHandler.lyricModeButton.label = "Change to end lyric" def dataTableCallback(self, att, old, new): if not self.activeChannels: logger.logData(source="Signal handler", priority="WARN", msgType="Lyric empty", msgData=()) return selectionIndex = self.lyricsHandler.lyricTableHandler.source.selected.indices[ 0] timestamp = self.lyricsHandler.lyricTableHandler.source.data[ "timestamps"][selectionIndex] lyricText = self.lyricsHandler.lyricTableHandler.source.data["lyrics"][ selectionIndex] timestampSeconds = timestamp.seconds lyricSample = int(timestampSeconds * self.sampleRate) if self.lyricMode == 0: #update sweep line graphics self.sweepStartSample = lyricSample startLine = self.p.select_one({'name': 'sweepStartLine'}) startLine.data_source.data = { 'x': [lyricSample, lyricSample], 'y': self.yRange } logger.logData(source="Lyrics", priority="INFO", msgType="Start lyric", msgData=(timestamp, lyricText)) else: self.sweepEndSample = lyricSample endLine = self.p.select_one({'name': 'sweepEndLine'}) endLine.data_source.data = { 'x': [lyricSample, lyricSample], 'y': self.yRange } logger.logData(source="Lyrics", priority="INFO", msgType="End lyric", msgData=(timestamp, lyricText)) def setupLyricTable(self, lyricsFilename): lyricsPath = os.path.join(self.path, "lyrics", lyricsFilename) self.lyricsHandler = lyricsHandler(lyricsPath) self.lyricMode = 0 self.lyricsHandler.lyricTableHandler.source.selected.on_change( 'indices', self.dataTableCallback) #create a loop toggle button and assigns a callback to it self.lyricsHandler.lyricModeButton.on_click(self.lyricModeCallback) self.lyricsGui = self.lyricsHandler.gui
click_reset_tool = CustomJS(args=dict(p=plot), code=""" p.toolbar.tools[""" + util.reset_tool_index(g.TOOLS3) + """].trigger('do'); """) device_slc.on_change('value', device_slc_callback) device_slc.js_on_change( 'value', CustomJS(args=dict(btn=clear_everything_btn), code=""" document.getElementById('modelid_' + btn.id).click(); """)) device_conn_btn.on_click(device_conn_btn_callback) conn_status.js_on_change( 'value', CustomJS(args=dict(btn=device_conn_btn), code=""" console.log('This application is connected to ESP device: ' + cb_obj.value); if(cb_obj.value) { btn.label = "Disconnect from device"; btn.button_type = "warning"; } else { btn.label = "Connect to device"; btn.button_type = "success"; } """))
label="Start streaming", button_type="primary", ) source1.on_change('selected', clear_message) device_slc.on_change('value', device_slc_callback) device_slc.js_on_change( 'value', CustomJS(args=dict(btn=reset_stream_btn), code=""" document.getElementById('modelid_' + btn.id).click(); """)) device_conn_btn.on_click(device_conn_btn_callback) device_conn_btn.callback = CustomJS(args=dict(btn=stream_data_btn), code=""" if (!btn.active) { document.getElementById('modelid_' + btn.id).click(); } """) conn_status.js_on_change( 'value', CustomJS(args=dict(btn=device_conn_btn), code=""" console.log('This application is connected to ESP device: ' + cb_obj.value); if(cb_obj.value) { btn.label = "Disconnect from device";
class CustomCounterWidget(BaseWidget): """Produces a widget for plotting any counters""" def __init__(self, doc, refresh_rate=1000, collection=None, **kwargs): """Produces a widget that allows the user to add / remove plots for any counters from any collection Arguments --------- doc : Bokeh Document bokeh document for auto-updating the widget refresh_rate : int refresh rate at which the Select refreshes and checks for new data collections (in ms) **kwargs arguments for the bokeh Select widget """ super().__init__(doc, refresh_rate=refresh_rate, collection=collection, **kwargs) self._defaults_opts = dict(plot_width=800, plot_height=300) self._defaults_opts.update((key, value) for key, value in kwargs.items()) self._lines = {} self._lines_info = set() self._line_counter = 0 # Buttons for editing the lines self._add_line_b = Button(label="+", width=40) self._add_line_b.on_click(self._add_line) # Toggle button for the shading of the plots self._shade_b = Toggle(label="Toggle plot shading", width=150) self._shade_b.on_click(self._toggle_shade) # Buttons for adding and removing plots self._add_plot_b = Button(label="+", width=40) self._add_plot_b.on_click(self._add_plot) self._remove_plot_b = Button(label="-", width=40) self._remove_plot_b.on_click(self._remove_plot) # For editing the lines self._edit_button = Toggle(label="Edit lines", width=100) self._edit_button.on_click(self._toggle_edit) self._json_input = TextAreaInput( title="Export / inport widget:", width=500, max_length=20000 ) self._json_update_button = Button(label="Update from input", width=150) self._json_update_button.on_click(self._set_from_input) self._save_button = Button(label="Save state of widget to session", width=170) self._save_button.on_click(self._save_widget) self._root = column( row( Div(text="Add or remove plots:"), self._remove_plot_b, self._add_plot_b, self._edit_button, self._shade_b, self._save_button, ), empty_placeholder(), empty_placeholder(), ) self._plots = [] self._add_plot() # If there is a saved state in the session of the widget json_txt = DataAggregator().get_custom_widget_config() if json_txt: self.from_json(json_txt) def _remove_line(self, idx): del self._lines[idx] self._update_line_widget() def _add_line(self, update=True): plots_text = [f"Plot {i + 1}" for i, _ in enumerate(self._plots)] self._line_counter += 1 self._lines[self._line_counter] = SelectCustomLine( self._doc, self._line_counter, plots_text, self._remove_line, ) if update: self._update_line_widget() def _toggle_shade(self, shade): for plot in self._plots: plot.toggle_shade() def _save_widget(self): DataAggregator().set_custom_widget_config(json.loads(self.to_json())) def _update_plots(self): plots = [plot.layout() for plot in self._plots] self._root.children[2] = column(*plots) # Update the lines with the available plots plots_text = [f"Plot {i + 1}" for i, _ in enumerate(self._plots)] for line in self._lines.values(): line.set_plots(plots_text) def _update_line_widget(self): lines = [line.layout() for line in self._lines.values()] self._root.children[1] = column( row(self._json_input, self._json_update_button), row(self._add_line_b, Div(text="Add line")), *lines, ) def _toggle_edit(self, edit): if edit: self._update_line_widget() else: self._root.children[1] = empty_placeholder() def _add_plot(self): opts = copy.deepcopy(self._defaults_opts) self._plots.append( generator.TimeSeries( self._doc, refresh_rate=self._refresh_rate, title=f"Plot {len(self._plots) + 1}", **opts, ) ) self._update_plots() def _set_from_input(self): self._toggle_edit(False) self._edit_button.active = False self.from_json(self._json_input.value) def to_json(self): """Converts the state of the widget (number of plots, lines) to json""" json_dict = {"num_plots": len(self._plots), "lines": []} for plot_id, _, countername, instance, name in self._lines_info: json_dict["lines"].append( {"plot_id": plot_id, "countername": countername, "instance": instance, "name": name} ) return json.dumps(json_dict) def from_json(self, json_txt): """Takes a json as input and generates the corresponding plots and widgets. Returns True if successful, False otherwise.""" json_dict = {} try: json_dict = json.loads(json_txt.rstrip()) except json.decoder.JSONDecodeError as e: logger.error(f"JSON decode error: {e.msg}") if "lines" not in json_dict: return False num_plots = 1 if "num_plots" in json_dict: num_plots = json_dict["num_plots"] # Remove all the lines self._lines.clear() # Set the correct number of plots if num_plots > len(self._plots): for _ in range(num_plots - len(self._plots)): self._add_plot() elif num_plots < len(self._plots): for _ in range(len(self._plots) - num_plots): self._remove_plot() for line in json_dict["lines"]: if not isinstance(line, dict): return False if ( "plot_id" not in line or "countername" not in line or "instance" not in line or "name" not in line ): return False if not from_instance(tuple(line["instance"])): return False locality_id, pool, thread_id = from_instance(line["instance"]) self._add_line(False) self._lines[self._line_counter].set_properties( line["plot_id"], None, line["countername"], locality_id, pool, thread_id, line["name"], ) return True def update(self): lines = set() for line in self._lines.values(): lines.add(line.properties()) deleted_lines = self._lines_info.difference(lines) new_lines = lines.difference(self._lines_info) for plot_id, collection, countername, instance, name in deleted_lines: if len(self._plots) >= plot_id: self._plots[plot_id - 1].remove_line(countername, instance, collection, name) for plot_id, collection, countername, instance, name in new_lines: self._plots[plot_id - 1].add_line(countername, instance, collection, name) self._lines_info = lines self._json_input.value = self.to_json() def _remove_plot(self): if len(self._plots) == 1: return del self._plots[-1] self._update_plots()
item.value = 'best_mt_score' except AttributeError: continue if presets.labels[presets.active] == "Tumor Clonality and Expression": for item in widgets: try: if item.title == 'X-Axis Value': item.value = 'tumor_dna_vaf' elif item.title == 'Y-Axis Value': item.value = 'tumor_rna_vaf' except AttributeError: continue #Add callbacks to the 3 widgets manually created back at the start x_field.on_change('value', lambda a,r,g: update()) y_field.on_change('value', lambda a,r,g: update()) presets.on_change('active', lambda a,r,g: change_preset()) hide_null.on_click(lambda arg: update()) #Add all models and widgets to the document box = widgetbox(*widgets, sizing_mode='stretch_both') fig = column( row(box, p), table, sizing_mode='scale_width' ) update() #initial update curdoc().add_root(fig) curdoc().title = sample
TableColumn(field="value_all", title="All Data"), TableColumn(field="value_selection", title="Selected Data"), ] data_table = DataTable(source=datatable_source, columns=datatable_columns, width=450, height=125, index_position=None) # callback for updating the plot based on a changes to inputs station_name_input.on_change('value', update_station) simulation_number_input.on_change('value', update_n_simulations) msmt_error_input.on_change('value', update_msmt_error) sample_size_input.on_change('value', update_simulation_sample_size) toggle_button.on_click(update_simulated_msmt_error) # see documentation for threading information # https://docs.bokeh.org/en/latest/docs/user_guide/server.html update() # widgets ts_plot = create_ts_plot(peak_source, peak_flagged_source) peak_source.selected.on_change('indices', update_UI) vh1, pv, hist_source = create_vhist(peak_source, ts_plot) ffa_plot = create_ffa_plot(peak_source, peak_flagged_source, distribution_source)
def save(self): filename = 'sensitivity_curve_' + str(armlength.value) + '_' + str( wavelength.value) + '_' + str(power.value) + '_' + str( trans.value) + '_' + str(sqzamp.value) + '_' + str( sqzang.value) + '_' + str(sqzlength.value) + '_' + str( sqzdet.value) with open(filename, 'a') as f: for i, j in zip(np.logspace(1, 4, 10000), np.sqrt(traces["Total"][0])): #print(i, j) f.write('%f %e\n' % (i, j)) toggle = Toggle(label="Total noise", button_type="warning") toggle.on_click(totalnoise_handler) toggle_aL = Toggle(label="aLIGO", button_type="warning") toggle_aL.on_click(totalnoise_handler_aL) toggle_save = Toggle(label="Save sensitivity curve", button_type="success") toggle_save.on_click(save) #if (toggle=active) # Set up callbacks def update_data(attrname, old, new): # Get the current slider values L = armlength.value
max(i if i else 0 for i in data[x] or [1])) y_minmax = (min(i if i else 0 for i in data[y] or [0]), max(i if i else 0 for i in data[y] or [1])) p.x_range.start = x_minmax[0] - (x_minmax[1] - x_minmax[0] + 1) / 20 p.x_range.end = x_minmax[1] + (x_minmax[1] - x_minmax[0] + 1) / 20 p.y_range.start = y_minmax[0] - (y_minmax[1] - y_minmax[0] + 1) / 20 p.y_range.end = y_minmax[1] + (y_minmax[1] - y_minmax[0] + 1) / 20 # Maintain current axes ranges upon reset p.on_event('reset', readjust_axes) # Add callbacks to the 3 widgets manually created back at the start x_field.on_change('value', lambda a, r, g: update()) y_field.on_change('value', lambda a, r, g: update()) hide_null.on_click(lambda arg: update()) download_button = Button(label="Download", button_type="success") download_button.callback = CustomJS(args=dict(source=source), code=open( join(dirname(__file__), "js/csv_download_all.js")).read()) download_selected_button = Button(label="Download Selected", button_type="success") download_selected_button.callback = CustomJS( args=dict(source=source), code=open(join(dirname(__file__), "js/csv_download_selected.js")).read()) widgets.extend((download_button, download_selected_button))
}, code=code) hover = HoverTool(tooltips=[('Sites', '@sites'), ('Type', '@type'), ('Year', '@year')], callback=callback, renderers=[el]) p.add_tools(hover) hover2 = HoverTool(tooltips=[('Name', '@name'), ('Type', '@type'), ('Year', '@year')], renderers=[cr]) p.add_tools(hover2) #add toggle buttons for ellipses and markers toggleEllipses = Toggle(label="Hide Ellipses", active=True) toggleEllipses.on_click(updateEllipses) toggleMarkers = Toggle(label="Hide Markers", active=True) toggleMarkers.on_click(updateMarkers) #add slider slider = Slider(title='Time', start=minTime, end=maxTime, step=1, value=minTime) slider.on_change('value', update_plot) #add play button bPlay = Toggle(label="Play", active=False, button_type="success") bPlay.on_click(automateMap)
x.on_change('value', update_plot) # create y-axis dropdown widget with continuous var columns y = Select(title='Y-Axis', value=Y_INIT, options=continuous) # link widget callback to update_plot() y.on_change('value', update_plot) # create dot color dropdown widget with "countable" var columns color = Select(title='Color', value=COLOR_INIT, options=countable) # link widget callback to update_plot() color.on_change('value', update_plot) # create image glyph toggle button toggle = Toggle(label="Show Images", button_type="success") # link button callback to toggle_callback() toggle.on_click(toggle_callback) # create lasso lasso = LassoSelectTool() # download button button = Button(label="Download", button_type="success") # button.callback = download_callback() button.callback = CustomJS(args=dict(source=source), code=open(join(dirname(__file__), "download.js")).read()) # add button and dropdown selections to a widgetbox widgets = [x, y, color, toggle, button] controls = widgetbox(widgets, sizing_mode='scale_both')
from bokeh.models.layouts import WidgetBox from bokeh.models.widgets import ( Button, Toggle, Dropdown, CheckboxGroup, RadioGroup, CheckboxButtonGroup, RadioButtonGroup, ) button = Button(label="Button (enabled) - has click event", button_type="primary") button.on_click(lambda: print('button: click')) button.js_on_click(CustomJS(code="console.log('button: click', this.toString())")) button_disabled = Button(label="Button (disabled) - no click event", button_type="primary", disabled=True) button_disabled.on_click(lambda: print('button_disabled: click')) button_disabled.js_on_click(CustomJS(code="console.log('button_disabled: click', this.toString())")) toggle_inactive = Toggle(label="Toggle button (initially inactive)", button_type="success") toggle_inactive.on_click(lambda value: print('toggle_inactive: %s' % value)) toggle_inactive.js_on_click(CustomJS(code="console.log('toggle_inactive: ' + this.active, this.toString())")) toggle_active = Toggle(label="Toggle button (initially active)", button_type="success", active=True) toggle_active.on_click(lambda value: print('toggle_active: %s' % value)) toggle_active.js_on_click(CustomJS(code="console.log('toggle_active: ' + this.active, this.toString())")) menu = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), None, ("Item 3", "item_3_value")] dropdown = Dropdown(label="Dropdown button", button_type="warning", menu=menu) dropdown.on_click(lambda value: print('dropdown: %s' % value)) dropdown.js_on_click(CustomJS(code="console.log('dropdown: ' + this.value, this.toString())")) dropdown_disabled = Dropdown(label="Dropdown button (disabled)", button_type="warning", menu=menu) dropdown_disabled.on_click(lambda value: print('dropdown_disabled: %s' % value)) dropdown_disabled.js_on_click(CustomJS(code="console.log('dropdown_disabled: ' + this.value, this.toString())"))
button = Button(label="Button (enabled) - has click event", button_type="primary") button.on_click(lambda: print('button: click')) button.js_on_click( CustomJS(code="console.log('button: click', this.toString())")) button_disabled = Button(label="Button (disabled) - no click event", button_type="primary", disabled=True) button_disabled.on_click(lambda: print('button_disabled: click')) button_disabled.js_on_click( CustomJS(code="console.log('button_disabled: click', this.toString())")) toggle_inactive = Toggle(label="Toggle button (initially inactive)", button_type="success") toggle_inactive.on_click(lambda value: print('toggle_inactive: %s' % value)) toggle_inactive.js_on_click( CustomJS( code="console.log('toggle_inactive: ' + this.active, this.toString())") ) toggle_active = Toggle(label="Toggle button (initially active)", button_type="success", active=True) toggle_active.on_click(lambda value: print('toggle_active: %s' % value)) toggle_active.js_on_click( CustomJS( code="console.log('toggle_active: ' + this.active, this.toString())")) menu = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), None, ("Item 3", "item_3_value")]
### set up callbacks ##all_image data_directory_text_input.on_change("value", data_directory_text_handler) refresh_directory_button.on_click(refresh_directory) #select_channel.on_change('value', select_channel_handler) color_palette_menu.on_change('value', select_palette_handler) fwd_bwd_button.on_click(fwd_bwd_handler) select_all_button.on_click(button_change_all) grid_view_button.on_click(grid_view_handler) update_button.on_click(update) update_resolution.on_click(update_plot_ranges) max_slider.on_change("value", slider_callback_high) min_slider.on_change("value", slider_callback_low) ## single image viewer select_file.on_change('value', select_file_handler) file_forward_button.on_click(lambda: button_change_index(1))