def addGeneTools(recs, sourceData): """ define tools to change the outline and fill colors of genes""" def colorStr(color_element): """javascript code to switch between partition, family and module color for the given 'color_element'""" return f""" if(this.active == 0){{ source.data['{color_element}'] = source.data['partition_color']; }}else if(this.active == 1){{ source.data['{color_element}'] = source.data['family_color']; }}else if(this.active == 2){{ source.data['{color_element}'] = source.data['module_color']; }} recs.{color_element} = source.data['{color_element}']; source.change.emit(); """ radio_line_color = RadioGroup(labels=["partition", "family", "module"], active=0) radio_fill_color = RadioGroup(labels=["partition", "family", "module"], active=1) radio_line_color.js_on_click( CustomJS(args=dict(recs=recs, source=sourceData), code=colorStr("line_color"))) radio_fill_color.js_on_click( CustomJS(args=dict(recs=recs, source=sourceData), code=colorStr("fill_color"))) color_header = Div(text="<b>Genes:</b>") line_title = Div(text="""Color to use for gene outlines:""", width=200, height=100) fill_title = Div(text="""Color to fill genes with:""", width=200, height=100) gene_outline_size = Slider(start=0, end=10, value=5, step=0.1, title="Gene outline size:") gene_outline_size.js_on_change( 'value', CustomJS(args=dict(other=recs), code=""" other.glyph.line_width = this.value; """)) return column( color_header, row(column(line_title, radio_line_color), column(fill_title, radio_fill_color)), gene_outline_size)
def _init_recorder(self): self.rec_button = Toggle(label=u"\u25CF Record", button_type="default", active=False, width=210) self.file_name_widget = TextInput(value="test_file", title="File name:", width=210) self.file_type_widget = RadioGroup(labels=["EDF (BDF+)", "CSV"], active=0, width=210) columns = [ widgets.TableColumn( field='timer', title="Record time", formatter=widgets.StringFormatter(text_align='center')) ] self.timer = widgets.DataTable(source=self._timer_source, index_position=None, sortable=False, reorderable=False, header_row=False, columns=columns, width=210, height=50, css_classes=["timer_widget"]) self.rec_button.on_click(self._toggle_rec) return column(Spacer(width=210, height=5), self.file_name_widget, self.file_type_widget, self.rec_button, self.timer)
def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: group = RadioGroup(labels=LABELS) def modify_doc(doc): source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) plot.add_glyph(source, Circle(x='x', y='y', size=20)) plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) def cb(attr, old, new): source.data['val'] = [new, "b"] group.on_change('active', cb) doc.add_root(column(group, plot)) page = bokeh_server_page(modify_doc) el = find_element_for(page.driver, group, 'input[value="2"]') el.click() page.eval_custom_action() results = page.results assert results['data']['val'] == [2, "b"] el = find_element_for(page.driver, group, 'input[value="0"]') el.click() page.eval_custom_action() results = page.results assert results['data']['val'] == [0, "b"]
def make_audio_panel(): PLAY_TEST_AUDIO = """ var path = 'Groove-Explorer-2/static/Test Audio/List Interface/' var index = selector.active; var labels = ['A', 'B', 'C', 'D', 'E']; var filename = path + labels[index] + '.mp3'; audio_player.stop_audio(); audio_player.play_audio(filename); """ STOPCODE = """ audio_player.stop_audio(); """ labels = ['A', 'B', 'C', 'D', 'E'] audio_selector = RadioGroup(labels=labels, height_policy="auto", sizing_mode='scale_width', active=0) play_button = Button(label='Play') play_button.js_on_click( CustomJS(args=dict(selector=audio_selector), code=PLAY_TEST_AUDIO)) stop_button = Button(label='Stop') stop_button.js_on_click(CustomJS(code=STOPCODE)) audio_panel = column(audio_selector, play_button, stop_button) return audio_panel
def __init__(self, flexname: str = "Default", name: str = "OvioSlit", display=fakedisplay, width=200): # BokehOVIOSlit::__init__() """Initialize this class.""" #super().__init__() # (wg-python-property-variables) self.display = display self.flexname = flexname # the name of the instrument for this instance self.name = name # the name of the device in this instrument self.wwidth = width # display width for its Bokeh widgets self.slit = "20" # initial condition self.state = 'undefined' # haven't started yet self.lamp = 0 # the illumination lamp associated self.validp = False # wake up in false position self.spacer = Spacer(width=self.wwidth, height=5, background='black') self.slitlamp = RadioGroup( labels=[f"Illuminator Off", f"Illuminator On"], active=0, height=50, #orientation='horizontal', width=self.wwidth) self.slitchoices = Select(title=f"OVIO Slits", value='20', options=self.oviodropdowns, width=self.wwidth) self.slitchoices.on_change( 'value', lambda attr, old, new: self.update_dropdown(attr, old, new)) self.slitlamp.on_change( 'active', lambda attr, old, new: self.radio_handler(attr, old, new)) self.send_state() # set the initial state.
def addGeneLabels(fig, sourceData): labels = LabelSet(x='x_label', y='y_label', text='label', source=sourceData, render_mode='canvas', text_font_size="18px") slider_font = Slider(start=0, end=64, value=16, step=1, title="Gene label font size in px") slider_angle = Slider(start=0, end=pi / 2, value=0, step=0.01, title="Gene label angle in radian") radio_label_type = RadioGroup(labels=[ "name", "product", "family", "local identifier", "gene ID", "none" ], active=0) slider_angle.js_link('value', labels, 'angle') slider_font.js_on_change( 'value', CustomJS(args=dict(other=labels), code="other.text_font_size = this.value+'px';")) radio_label_type.js_on_click( CustomJS(args=dict(other=labels, source=sourceData), code=""" if(this.active == 5){ source.data['label'] = []; for(var i=0;i<source.data['name'].length;i++){ source.data['label'].push(''); } }else if(this.active == 3){ source.data['label'] = source.data['gene_local_ID']; }else if(this.active == 4){ source.data['label'] = source.data['gene_ID']; } else{ source.data['label'] = source.data[this.labels[this.active]]; } other.source = source; source.change.emit(); """)) label_header = Div(text="<b>Gene labels:</b>") radio_title = Div(text="""Gene labels to use:""", width=200, height=100) labels_block = column(label_header, row(slider_font, slider_angle), column(radio_title, radio_label_type)) fig.add_layout(labels) return labels_block, labels
def modify_doc(doc): source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) plot = Plot(plot_height=400, plot_width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) plot.add_glyph(source, Circle(x='x', y='y', size=20)) plot.add_tools(CustomAction(callback=CustomJS(args=dict(s=source), code=RECORD("data", "s.data")))) group = RadioGroup(labels=LABELS, css_classes=["foo"]) def cb(active): source.data['val'] = [active, "b"] group.on_click(cb) doc.add_root(column(group, plot))
def test_displays_options_list_of_string_labels_setting_inline(self, inline, bokeh_model_page: BokehModelPage) -> None: group = RadioGroup(labels=LABELS, inline=inline) page = bokeh_model_page(group) labels = find_elements_for(page.driver, group, "label") assert len(labels) == 3 for i, label in enumerate(labels): assert label.text == LABELS[i] input = label.find_element(By.TAG_NAME, 'input') assert input.get_attribute('value') == str(i) assert input.get_attribute('type') == 'radio'
def __init__(self): self._word_filter_select = RadioGroup( labels=[word_filter.label for word_filter in WordFilter], active=1, ) self._should_normalize = CheckboxGroup( labels=["Normalize word frequencies per day"], active=[]) self.widget = column( self._word_filter_select, self._should_normalize, sizing_mode="stretch_width", )
def __init__(self): pd.set_option('display.max_colwidth', -1) try: self.base_df = load_clean_df() except: fpath = BUNDLED_DATA_DIR + '/clean-light.csv' self.base_df = load_clean_df(fpath=fpath) self.base_df[SEARCH_FIELD] = self.base_df.apply(lambda row: ' '.join(str(x).lower() for x in row[SEARCH_COLS] if pd.notnull(x)), axis=1) # divs holding the charts self.sim_input_desc_div = Div(text='', width=CHARTS_WIDTH) self.sim_input_div = Div(text='', width=CHARTS_WIDTH) self.hm_divs = [Div(text='', width=DIV_WIDTH) for _ in range(2)] self.hist_divs = [Div(text='', width=DIV_WIDTH) for _ in range(2)] self.bc_divs = [Div(text='', width=DIV_WIDTH) for _ in range(2)] self.comp_div = Div(text='', width=CHARTS_WIDTH) # other divs self.top_div = Div(text='', width=CHARTS_WIDTH) # controls self.search_text_ctrl = TextInput(title=None, value='', width=WIDGET_BOX_WIDTH - 50) self.sim_ctrl = Select(title=None, options=all_sims.get_sim_names(), value=all_sims.get_sim_name(TfidfCosSim)) self.analysed_cols_ctrl = CheckboxGroup(labels=COL_OPTIONS, active=[COL_OPTIONS.index(c) for c in ['suff_qtext', 'type']]) self.sim_params_ctrl = CheckboxGroup(labels=list(SIM_PARAMS_CHECKOPTS), active=list(range(len(SIM_PARAMS_CHECKOPTS)))) self.wv_dict_model_ctrl = RadioGroup(labels=[e.name for e in qsim.W2vModelName], active=0) self.sim_input_nres_ctrl = Slider(title="Number of results", value=10, start=1, end=100, step=1) self.sim_input_sample_size_ctrl = Slider(title="Sample size", value=1000, start=100, end=len(self.base_df), step=100) self.sim_input_text_ctrl = TextInput(title=None, value='', width=WIDGET_BOX_WIDTH - 50) self.hm_sample_size_ctrl = Slider(title=SAMPLE_SIZE_LABEL, value=30, start=10, end=50, step=5) self.hist_sample_size_ctrl = Slider(title=SAMPLE_SIZE_LABEL, value=30, start=10, end=len(self.base_df), step=10) self.bc_sample_size_ctrl = Slider(title=SAMPLE_SIZE_LABEL, value=30, start=10, end=len(self.base_df), step=5) self.bc_bars_ctrl = Slider(title="Number of bars", value=10, start=5, end=25, step=1) self.spectrum_start_ctrl = Slider(title="from", value=0, start=0, end=1, step=0.01) self.spectrum_end_ctrl = Slider(title="to", value=1, start=0, end=1, step=0.01) self.spectrum_buckets_ctrl = Slider(title="Number of buckets", value=5, start=1, end=20, step=1) self.spectrum_bucket_size_ctrl = Slider(title="Questions per bucket", value=2, start=1, end=10, step=1) self.spectrum_cs_only_ctrl = CheckboxGroup(labels=['Cross survey pairs only'], active=[]) self.spectrum_spectrum_sample_size_ctrl = Slider(title=SAMPLE_SIZE_LABEL, value=30, start=30, end=len(self.base_df), step=10) self.submit_btn = Button(label="Update", button_type="success") self.submit_btn.on_click(self.update) self.update()
def test_displays_options_list_of_string_labels_setting_inline(self, inline, bokeh_model_page) -> None: group = RadioGroup(labels=LABELS, css_classes=["foo"], inline=inline) page = bokeh_model_page(group) el = page.driver.find_element_by_css_selector('.foo') labels = el.find_elements_by_tag_name('label') assert len(labels) == 3 for i, label in enumerate(labels): assert label.text == LABELS[i] input = label.find_element_by_tag_name('input') assert input.get_attribute('value') == str(i) assert input.get_attribute('type') == 'radio'
def test_js_on_change_executes(self, bokeh_model_page) -> None: group = RadioGroup(labels=LABELS, css_classes=["foo"]) group.js_on_click(CustomJS(code=RECORD("active", "cb_obj.active"))) page = bokeh_model_page(group) el = page.driver.find_element_by_css_selector('.foo input[value="2"]') el.click() results = page.results assert results['active'] == 2 el = page.driver.find_element_by_css_selector('.foo input[value="0"]') el.click() results = page.results assert results['active'] == 0 assert page.has_no_console_errors()
def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: group = RadioGroup(labels=LABELS) group.js_on_change('active', CustomJS(code=RECORD("active", "cb_obj.active"))) page = bokeh_model_page(group) el = find_element_for(page.driver, group, 'input[value="2"]') el.click() results = page.results assert results['active'] == 2 el = find_element_for(page.driver, group, 'input[value="0"]') el.click() results = page.results assert results['active'] == 0 assert page.has_no_console_errors()
def test_callback_property_executes(self, bokeh_model_page): group = RadioGroup(labels=LABELS, css_classes=["foo"]) group.callback = CustomJS(code=RECORD("active", "cb_obj.active")) page = bokeh_model_page(group) el = page.driver.find_element_by_css_selector( 'div.foo div label input[value="2"]') el.click() results = page.results assert results['active'] == 2 el = page.driver.find_element_by_css_selector( 'div.foo div label input[value="0"]') el.click() results = page.results assert results['active'] == 0 assert page.has_no_console_errors()
def make_plot(results, title): import json import bokeh.plotting as bp from bokeh.transform import dodge from bokeh.layouts import column from bokeh.models import CustomJS, RadioGroup, FactorRange data, time_unit = preprocess_results(results) sort_options = ["total", "encode", "decode"] sort_orders = [ list( zip(*sorted(zip(data[order], data["benchmark"]), reverse=True)))[1] for order in sort_options ] source = bp.ColumnDataSource(data=data) tooltips = [("time", "@$name")] x_range = FactorRange(*sort_orders[0]) p = bp.figure( x_range=x_range, plot_height=250, plot_width=660, title=title, toolbar_location=None, tools="", tooltips=tooltips, sizing_mode="scale_width", ) p.vbar( x=dodge("benchmark", -0.25, range=p.x_range), top="encode", width=0.2, source=source, color="#c9d9d3", legend_label="encode", name="encode_labels", ) p.vbar( x=dodge("benchmark", 0.0, range=p.x_range), top="decode", width=0.2, source=source, color="#718dbf", legend_label="decode", name="decode_labels", ) p.vbar( x=dodge("benchmark", 0.25, range=p.x_range), top="total", width=0.2, source=source, color="#e84d60", legend_label="total", name="total_labels", ) p.x_range.range_padding = 0.1 p.xgrid.grid_line_color = None p.ygrid.grid_line_color = None p.yaxis.axis_label = f"Time ({time_unit})" p.yaxis.minor_tick_line_color = None p.legend.location = "top_right" p.legend.orientation = "horizontal" # Setup widget select = RadioGroup(labels=sort_options, active=0, inline=True, css_classes=["centered-radio"]) callback = CustomJS( args=dict(x_range=x_range), code=""" var lookup = {lookup_table}; x_range.factors = lookup[this.active]; x_range.change.emit(); """.format(lookup_table=json.dumps(sort_orders)), ) select.js_on_click(callback) out = column(p, select, sizing_mode="scale_width") return out
def __init__( self, options, name, data, data_types, type_categories, ): """ Configuration options: - default/X Axis: name - default/Y Axis: name - default/Group by: name - default/Exclude: name1;name2;...;nameN - default/discard_axis_x: name1;name2;...;nameN - default/discard_axis_y: name1;name2;...;nameN - default/discard_group_by: name1;name2;...;nameN - default/discard_exclude_max_values: number. the maximum number of excluded values for a category to be shown. If an exclusion category has more values, no values added for this category - default/number_of_columns: int Args: options: name: data: data_types: type_categories: """ self.discard_group_by = safe_lookup(options.config, name, 'default', 'discard_group_by', default='').split(';') self.discard_axis_x = safe_lookup(options.config, name, 'default', 'discard_axis_x', default='').split(';') self.discard_axis_y = safe_lookup(options.config, name, 'default', 'discard_axis_y', default='').split(';') self.discard_exclude_max_values = int(safe_lookup(options.config, name, 'default', 'discard_exclude_max_values', default='15')) self.group_by = CheckboxGroup() self.y_axis = CheckboxGroup() self.x_axis = RadioGroup() self.exclude = CheckboxGroup() controls_list = [ PreText(text='Group by:'), self.group_by, PreText(text='Y Axis:'), self.y_axis, PreText(text='X Axis:'), self.x_axis, PreText(text='Exclude:'), self.exclude, ] controls = column(controls_list, name='PanelDataGraph_controls') controls.sizing_mode = "fixed" self.update_controls(options, name, data, data_types, type_categories) self.options = options self.layout = row(controls, sizing_mode='stretch_both') self.last_data = data self.last_data_types = data_types self.last_type_categories = type_categories self.last_figs_by_group = {} self.number_of_columns = int(safe_lookup(options.config, name, 'default', 'number_of_columns', default='2')) for control in controls_list: if hasattr(control, 'active'): control.on_change('active', lambda attr, old, new: self._update_and_clear_plots()) self._update() super().__init__(ui=Panel(child=self.layout, title=name))
def plot(tables, output_filename): ''' This is the plot function that uses Bokeh functions and widgets to make an interactive hexagon plot. This function recieves: - tables: dictionary with tables used to create arrays of repeated x, y coordinates (depending on the counts) for the hexagon plot. - output_filename: filename of .html output in the plots folder The coordinate arrays are used to create a pandas dataframe with Bokeh functions. This dataframe contains the q, r coordinates and counts used to plot the hexagons. To this dataframe, extra information is added (e.g. most common chemicals), which is displayed in the hover tooltip. Gaussian blur is added to copies of this dataframe and given as input to the Bokeh slider widget. Other widgets are added as well, for saturation, normalisation etc. Bokeh allows to customize these widges with javascript code. The hexagon plot is saved as a .html file and also shown in the browser. ''' file_name = 'plots/' + str(output_filename) + '.html' output_file(file_name) # Blur and saturation values BLUR_MAX = 3 BLUR_STEP_SIZE = 1 SATURATION_MAX = 5 SATURATION_STEP_SIZE = 0.25 # First, create array for plot properties ( ratio, size of hexagons etc.) default_term = list(tables.keys())[0] x, y, ids = create_array(tables[default_term]['table'], normalisation=False) # Hexagon plot properties length = len(x) orientation = 'flattop' ratio = ((max(y) - min(y)) / (max(x) - min(x))) size = 10 / ratio h = sqrt(3) * size h = h * ratio title = 'Hexbin plot for ' + str( length) + ' annotated chemicals with query ' + str(default_term) # make figure p = figure(title=title, x_range=[min(x) - 0.5, max(x) + 0.5], y_range=[0 - (h / 2), max(y) + 100], tools="wheel_zoom,reset,save", background_fill_color='#440154') p.grid.visible = False p.xaxis.axis_label = "log(P)" p.yaxis.axis_label = "mass in Da" p.xaxis.axis_label_text_font_style = 'normal' p.yaxis.axis_label_text_font_style = 'normal' # source for plot term_to_source, term_to_metadata, options = make_plot_sources( tables, size, ratio, orientation, BLUR_MAX, BLUR_STEP_SIZE) # start source for plot, this is the source that is first displayed in the hexagon figure x, y, ids = create_array(tables[default_term]['table'], normalisation=False) df = hexbin(x, y, ids, size, aspect_scale=ratio, orientation=orientation) df = add_counts(df, tables[default_term]['table']) source = ColumnDataSource(df) metadata = term_to_metadata[default_term] metadata = return_html(metadata) # color mapper mapper = linear_cmap('scaling', 'Viridis256', 0, max(source.data['scaling'])) # plot hex = p.hex_tile(q="q", r="r", size=size, line_color=None, source=source, aspect_scale=ratio, orientation=orientation, fill_color=mapper) # HOVER TOOLTIPS = return_tooltip() code_callback_hover = return_code('hover') callback_hover = CustomJS(code=code_callback_hover) hover = HoverTool(tooltips=TOOLTIPS, callback=callback_hover, show_arrow=False) p.add_tools(hover) # WIDGETS slider1 = Slider(start=1, end=SATURATION_MAX, value=1, step=SATURATION_STEP_SIZE, title="Saturation", width=100) slider2 = Slider(start=0, end=BLUR_MAX, value=0, step=BLUR_STEP_SIZE, title="Blur", width=100) checkbox = CheckboxGroup(labels=["TFIDF"], active=[]) radio_button_group = RadioGroup(labels=["Viridis256", "Greys256"], active=0) button = Button(label="Metadata", button_type="default", width=100) multi_select = MultiSelect(title=output_filename, value=[default_term], options=options, width=100, height=300) # WIDGETS CODE FOR CALLBACK code_callback_slider1 = return_code('slider1') code_callback_slider2 = return_code('slider2') code_callback_checkbox = return_code('checkbox') code_callback_rbg = return_code('rbg') code_callback_button = return_code('button') code_callback_ms = return_code('multi_select') # WIDGETS CALLBACK callback_slider1 = CustomJS(args={ 'source': source, 'mapper': mapper }, code=code_callback_slider1) callback_slider2 = CustomJS(args={ 'source': source, 'mapper': mapper, 'slider1': slider1, 'multi_select': multi_select, 'checkbox': checkbox, 'term_to_source': term_to_source, 'step_size': BLUR_STEP_SIZE }, code=code_callback_slider2) callback_checkbox = CustomJS(args={ 'source': source, 'term_to_source': term_to_source, 'multi_select': multi_select, 'step_size': BLUR_STEP_SIZE, 'slider1': slider1, 'slider2': slider2, 'mapper': mapper }, code=code_callback_checkbox) callback_radio_button_group = CustomJS(args={ 'p': p, 'mapper': mapper, 'Viridis256': Viridis256, 'Greys256': Greys256 }, code=code_callback_rbg) callback_button = CustomJS(args={ 'term_to_metadata': term_to_metadata, 'multi_select': multi_select }, code=code_callback_button) callback_ms = CustomJS(args={ 'source': source, 'term_to_source': term_to_source, 'checkbox': checkbox, 'metadata': metadata, 'step_size': BLUR_STEP_SIZE, 'slider2': slider2, 'slider1': slider1, 'p': p, 'mapper': mapper }, code=code_callback_ms) # # WIDGETS INTERACTION slider1.js_on_change('value', callback_slider1) slider2.js_on_change('value', callback_slider2) checkbox.js_on_change('active', callback_checkbox) radio_button_group.js_on_change('active', callback_radio_button_group) button.js_on_event(events.ButtonClick, callback_button) multi_select.js_on_change("value", callback_ms) # LAYOUT layout = row( multi_select, p, column(slider1, slider2, checkbox, radio_button_group, button)) show(layout)
def __init__(self, image_views, disp_min=0, disp_max=1000, colormap="plasma"): """Initialize a colormapper. Args: image_views (ImageView): Associated streamvis image view instances. disp_min (int, optional): Initial minimal display value. Defaults to 0. disp_max (int, optional): Initial maximal display value. Defaults to 1000. colormap (str, optional): Initial colormap. Defaults to 'plasma'. """ lin_colormapper = LinearColorMapper( palette=cmap_dict[colormap], low=disp_min, high=disp_max ) log_colormapper = LogColorMapper(palette=cmap_dict[colormap], low=disp_min, high=disp_max) for image_view in image_views: image_view.image_glyph.color_mapper = lin_colormapper color_bar = ColorBar( color_mapper=lin_colormapper, location=(0, -5), orientation="horizontal", height=15, width=100, padding=5, ) self.color_bar = color_bar # ---- selector def select_callback(_attr, _old, new): if new in cmap_dict: lin_colormapper.palette = cmap_dict[new] log_colormapper.palette = cmap_dict[new] high_color.color = cmap_dict[new][-1] select = Select( title="Colormap:", value=colormap, options=list(cmap_dict.keys()), default_size=100 ) select.on_change("value", select_callback) self.select = select # ---- auto toggle button def auto_toggle_callback(state): if state: display_min_spinner.disabled = True display_max_spinner.disabled = True else: display_min_spinner.disabled = False display_max_spinner.disabled = False auto_toggle = CheckboxGroup(labels=["Auto Colormap Range"], default_size=145) auto_toggle.on_click(auto_toggle_callback) self.auto_toggle = auto_toggle # ---- scale radiobutton group def scale_radiobuttongroup_callback(selection): if selection == 0: # Linear for image_view in image_views: image_view.image_glyph.color_mapper = lin_colormapper color_bar.color_mapper = lin_colormapper color_bar.ticker = BasicTicker() else: # Logarithmic if self.disp_min > 0: for image_view in image_views: image_view.image_glyph.color_mapper = log_colormapper color_bar.color_mapper = log_colormapper color_bar.ticker = LogTicker() else: scale_radiobuttongroup.active = 0 scale_radiobuttongroup = RadioGroup( labels=["Linear", "Logarithmic"], active=0, default_size=145 ) scale_radiobuttongroup.on_click(scale_radiobuttongroup_callback) self.scale_radiobuttongroup = scale_radiobuttongroup # ---- display max value def display_max_spinner_callback(_attr, _old_value, new_value): self.display_min_spinner.high = new_value - STEP if new_value <= 0: scale_radiobuttongroup.active = 0 lin_colormapper.high = new_value log_colormapper.high = new_value display_max_spinner = Spinner( title="Max Display Value:", low=disp_min + STEP, value=disp_max, step=STEP, disabled=bool(auto_toggle.active), default_size=145, ) display_max_spinner.on_change("value", display_max_spinner_callback) self.display_max_spinner = display_max_spinner # ---- display min value def display_min_spinner_callback(_attr, _old_value, new_value): self.display_max_spinner.low = new_value + STEP if new_value <= 0: scale_radiobuttongroup.active = 0 lin_colormapper.low = new_value log_colormapper.low = new_value display_min_spinner = Spinner( title="Min Display Value:", high=disp_max - STEP, value=disp_min, step=STEP, disabled=bool(auto_toggle.active), default_size=145, ) display_min_spinner.on_change("value", display_min_spinner_callback) self.display_min_spinner = display_min_spinner # ---- high color def high_color_callback(_attr, _old_value, new_value): lin_colormapper.high_color = new_value log_colormapper.high_color = new_value high_color = ColorPicker( title="High Color:", color=cmap_dict[colormap][-1], default_size=90 ) high_color.on_change("color", high_color_callback) self.high_color = high_color # ---- mask color def mask_color_callback(_attr, _old_value, new_value): lin_colormapper.nan_color = new_value log_colormapper.nan_color = new_value mask_color = ColorPicker(title="Mask Color:", color="gray", default_size=90) mask_color.on_change("color", mask_color_callback) self.mask_color = mask_color
def __init__(self, sv_rt): """Initialize a stream control widget. """ doc = curdoc() self.receiver = doc.receiver self.stats = doc.stats self.jf_adapter = doc.jf_adapter self._sv_rt = sv_rt # connect toggle button def toggle_callback(_active): if _active or not self._prev_image_buffer: self.prev_image_slider.disabled = True else: self.prev_image_slider.disabled = False self._update_toggle_view() toggle = Toggle(label="Connect", button_type="primary", tags=[True], default_size=145) toggle.js_on_change("tags", CustomJS(code=js_backpressure_code)) toggle.on_click(toggle_callback) self.toggle = toggle # data type select datatype_select = Select( title="Data type:", value="Image", options=["Image", "Gains"], default_size=145 ) self.datatype_select = datatype_select # conversion options conv_opts_div = Div(text="Conversion options:", margin=(5, 5, 0, 5)) conv_opts_cbg = CheckboxGroup( labels=["Mask", "Gap pixels", "Geometry"], active=[0, 1, 2], default_size=145 ) self.conv_opts_cbg = conv_opts_cbg self.conv_opts = column(conv_opts_div, conv_opts_cbg) # double pixels handling double_pixels_div = Div(text="Double pixels:", margin=(5, 5, 0, 5)) double_pixels_rg = RadioGroup(labels=DP_LABELS, active=0, default_size=145) self.double_pixels_rg = double_pixels_rg self.double_pixels = column(double_pixels_div, double_pixels_rg) # rotate image select rotate_values = ["0", "90", "180", "270"] rotate_image = Select( title="Rotate image (deg):", value=rotate_values[0], options=rotate_values, default_size=145, ) self.rotate_image = rotate_image # show only events self.show_only_events_toggle = CheckboxGroup(labels=["Show Only Events"], default_size=145) # Previous Image slider self._prev_image_buffer = deque(maxlen=60) def prev_image_slider_callback(_attr, _old, new): sv_rt.metadata, sv_rt.image = self._prev_image_buffer[new] # TODO: fix this workaround sv_rt.aggregated_image = sv_rt.image prev_image_slider = Slider( start=0, end=59, value_throttled=0, step=1, title="Previous Image", disabled=True, ) prev_image_slider.on_change("value_throttled", prev_image_slider_callback) self.prev_image_slider = prev_image_slider doc.add_periodic_callback(self._update_toggle_view, 1000)
def update(): # Compute new y values: y y = np.sin(x) + np.random.random(N) # Update the ColumnDataSource data dictionary source.data = {'x': x, 'y': y} # Add the update callback to the button button.on_click(update) # Create layout and add to current document layout = column(widgetbox(button), plot) curdoc().add_root(layout) # Different types of buttons # Import CheckboxGroup, RadioGroup, Toggle from bokeh.models from bokeh.models import CheckboxGroup, RadioGroup, Toggle # Add a Toggle: toggle toggle = Toggle(button_type='success', label='Toggle button') # Add a CheckboxGroup: checkbox checkbox = CheckboxGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add a RadioGroup: radio radio = RadioGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add widgetbox(toggle, checkbox, radio) to the current document curdoc().add_root(widgetbox(toggle, checkbox, radio))
def __init__(self, doc, idx, plots, callback=None, refresh_rate=500, collection=None, **kwargs): super().__init__(doc, callback=callback, refresh_rate=refresh_rate, collection=collection, **kwargs) self._countername_autocomplete = AutocompleteInput( name=f"Autocomplete_{BaseWidget.instance_num}", title="Countername:", completions=counternames, width=200, ) self._collection_widget = DataCollectionSelect(doc, self._set_collection, width=120) self._selected_collection = None self._name = f"Line {idx}" self._name_edit = TextInput(title="Change name:", value=self._name, width=150) self._name_edit.on_change("value", self._change_name) self._title = Div(text=f"<h3>{self._name}</h3>") self._delete = Button(label="Remove", width=70, button_type="danger") self._delete.on_click(lambda: callback(idx)) self._to_plot = Select(options=plots, value=plots[0], title="To plot:", width=70) # Instance infos self._locality_input = TextInput(title="Locality #id:", value="0", width=70) self._locality_select = Select(options=[], title="Locality #id:", value="0", width=70) self._thread_id = TextInput(title="Worker #id:", width=70, value="0") self._pool = TextInput(title="Pool name:", width=70) self._pool_select = Select(options=[], title="Pool name:", width=70) self._is_total = RadioGroup(labels=["Yes", "No"], active=0, width=30) self._is_total.on_change("active", self._change_is_total) self._root = column( row(self._title, self._name_edit), self._delete, row( self._to_plot, self._collection_widget.layout(), self._countername_autocomplete, self._locality_input, self._pool, row(Div(text="Is total?"), self._is_total), empty_placeholder(), ), )
# ~ btn_rst: reset user selection of visible spectra # ============================================================================= slider = Slider(start=0, end=rm.n_peaks - 1, value=0, step=1, title='Select a peak') slider.on_change('value', peakSelector) # n_features_input = RadioGroup( # labels=['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], active=2, inline=True) # n_features_input.on_change('active', featureCounter) # n_features_input_title = Div(text="""# features """) n_pca_input = RadioGroup(labels=['1', '2', '3', '4', '5'], active=1, inline=True) n_pca_input.on_change('active', repredict) n_pca_input_title = Div(text="""# pca: """) n_materials = RadioGroup(labels=['2', '3', '4', '5', '6', '7', '8', '9'], active=1, inline=True) n_materials.on_change('active', repredict) n_materials_title = Div(text="""# Materials """) btn_rst = Button(label='reset') btn_rst.on_click(selectSpectra_reset) # ============================================================================= # ~ Layout setup # =============================================================================
dropdown_split.js_on_click( CustomJS(code="console.log('dropdown(split): click ' + this.toString())")) dropdown_split.js_on_event( "menu_item_click", CustomJS( code="console.log('dropdown(split): ' + this.item, this.toString())")) checkbox_group = CheckboxGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1]) checkbox_group.js_on_click( CustomJS( code= "console.log('checkbox_group: active=' + this.active, this.toString())" )) radio_group = RadioGroup(labels=["Option 1", "Option 2", "Option 3"], active=0) radio_group.js_on_click( CustomJS( code= "console.log('radio_group: active=' + this.active, this.toString())")) checkbox_button_group = CheckboxButtonGroup( labels=["Option 1", "Option 2", "Option 3"], active=[0, 1]) checkbox_button_group.js_on_click( CustomJS( code= "console.log('checkbox_button_group: active=' + this.active, this.toString())" )) radio_button_group = RadioButtonGroup( labels=["Option 1", "Option 2", "Option 3"], active=0)
from bokeh.io import show from bokeh.models import CustomJS, RadioGroup LABELS = ["Option 1", "Option 2", "Option 3"] radio_group = RadioGroup(labels=LABELS, active=0) radio_group.js_on_event('button_click', CustomJS(code=""" console.log('radio_group: active=' + this.origin.active, this.toString()) """)) show(radio_group)
def make_layout(data_filtered_kabir_part, injured_killed, cont_factor): def make_dataset(selected_options): by_factor_injured = pd.DataFrame( columns=['no_factors', 'no_victims', 'axis_type']) group_by = data_filtered_kabir_part.groupby( selected_options[1], as_index=False)[selected_options[0]].sum() #print(group_by) by_factor_injured['no_factors'] = group_by[selected_options[1]] by_factor_injured['no_victims'] = group_by[selected_options[0]] by_factor_injured['axis_type'] = selected_options[2] by_factor_injured['height'] = 450 + 5 * selected_options[3] by_factor_injured = by_factor_injured.sort_values(['no_victims'], ascending=True) by_factor_range_injured = by_factor_injured.tail(selected_options[3]) #print(by_factor_range_injured) return ColumnDataSource(by_factor_range_injured) def make_plot(src): # Blank plot in linear scale p_lin = figure( y_range=FactorRange(factors=list(src.data['no_factors'])), plot_width=700, plot_height=450, title='Bar plot of number of victims injured or killed', y_axis_label='Contributing factor', x_axis_label='No of victims', toolbar_location=None) glyph = HBar(y='no_factors', right="no_victims", left=0, height=0.5, fill_color="#460E61") p_lin.add_glyph(src, glyph) # Blank plot in log scale p_log = figure( y_range=FactorRange(factors=list(src.data['no_factors'])), plot_width=700, plot_height=450, title='Bar plot of no of victims injured or killed', y_axis_label='Contributing factor', x_axis_label='No of victims', x_axis_type='log', toolbar_location=None) glyph = HBar(y='no_factors', right="no_victims", left=0.00001, height=0.5, fill_color="#460E61") p_log.add_glyph(src, glyph) # Hover tool with hline mode hover = HoverTool(tooltips=[('Number of victims', '@no_victims'), ('Contributing Factor', '@no_factors')], mode='hline') p_lin.add_tools(hover) p_log.add_tools(hover) return p_lin, p_log def update(attr, old, new): group_by_text = [ select_inj_kill.value, select_factor.value, radio_group.active, factor_range.value ] #print(group_by_text) new_src = make_dataset(group_by_text) src.data.update(new_src.data) p_lin.y_range.factors = list(src.data['no_factors']) p_log.y_range.factors = list(src.data['no_factors']) #print(src.data['height'][0]) p_lin.height = src.data['height'][0] p_log.height = src.data['height'][0] if src.data['axis_type'][0] == 0: p_log.visible = False p_lin.visible = True else: p_lin.visible = False p_log.visible = True # dropdown list select_inj_kill = Select(title="Select type of injury", value="NUMBER OF PERSONS INJURED", options=injured_killed) select_inj_kill.on_change('value', update) select_factor = Select(title="Contributing factor", value="CONTRIBUTING FACTOR VEHICLE 1", options=cont_factor) select_factor.on_change('value', update) ### radio group list div = Div(text="Choose Axis Type", style={ 'font-size': '11pt', 'color': 'black', 'font-family': 'sans-serif' }) radio_group = RadioGroup(labels=["Linear Scale", "Log Scale"], active=0) radio_group.on_change('active', update) factor_range = Slider(start=5, end=65, step=5, value=10, title='Contributing Factors') factor_range.on_change('value', update) ## initial selection initial_selections = [ select_inj_kill.value, select_factor.value, radio_group.active, factor_range.value ] src = make_dataset(initial_selections) # creating plot p_lin, p_log = make_plot(src) p_log.visible = False scale_layout = column(div, radio_group) # Put controls in a single element controls = WidgetBox(select_inj_kill, select_factor, scale_layout, factor_range) # Create a row layout layout = row(p_lin, p_log, controls) return layout
def setup_dashboard(self): output_notebook() x = [0, 1, 1, 2, 2, 3, 3, 4] y = 8 * [10**-7] source = ColumnDataSource(data=dict(x=x, y=y)) plot = figure(plot_width=300, plot_height=150, y_axis_type="log", y_range=[0.0000000001, 1], x_range=[0, 4], x_axis_label='Epoch', y_axis_label='Learning Rate') plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) learn_inputs = 4 * [3] base_code = """ var data = source.data; var f = cb_obj.value x = data['x'] y = data['y'] y[{}] = Math.pow(10.0, -1.0 * (10-f)) y[{}] = Math.pow(10.0, -1.0 * (10-f)) source.trigger('change'); var command = 'dashboard.learn_inputs[{}] = ' + f; var kernel = IPython.notebook.kernel; kernel.execute(command) """ # set up figure fig = figure(name="cost", y_axis_label="Cost", x_range=(0, 4), x_axis_label="Epoch", plot_width=300, plot_height=300) self.fig = fig train_source = ColumnDataSource(data=dict(x=[], y=[])) train_cost = fig.line('x', 'y', source=train_source) self.train_source = train_source val_source = ColumnDataSource(data=dict(x=[], y=[])) val_cost = fig.line('x', 'y', source=val_source, color='red') self.val_source = val_source # set up sliders and callback callbacks = [ CustomJS(args=dict(source=source), code=base_code.format(k, k + 1, k / 2)) for k in [0, 2, 4, 6] ] slider = [ Slider(start=0.1, end=10, value=3, step=.1, title=None, callback=C, orientation='vertical', width=80, height=50) for C in callbacks ] radio_group = RadioGroup(labels=[""], active=0, width=65) def train_model_button(run=True): train_model(slider, fig=fig, handle=fh, train_source=train_source, val_source=val_source) sliders = row(radio_group, slider[0], slider[1], slider[2], slider[3]) settings = column(plot, sliders) layout = gridplot([[settings, fig]], sizing_mode='fixed', merge_tools=True, toolbar_location=None) self.fh = show(layout, notebook_handle=True)
def plot_benchmarks_as_lines(title, *bm, transform=None, line_title_transform=None, logx=True, logy=True): import bokeh from bokeh.plotting import figure, output_file, show from bokeh.palettes import Dark2_5 as palette from bokeh.layouts import row, column from bokeh.models import (Legend, LegendItem, CheckboxGroup, CustomJS, Div, RadioGroup, Toggle, ColumnDataSource, DataTable, TableColumn) from bokeh.models.markers import marker_types import itertools # ids = entry_ids(*bm, transform=transform) colors = itertools.cycle(palette) markers = itertools.cycle(marker_types) p = figure(title=title, x_axis_type="log" if logx else "linear", y_axis_type="log" if logy else "linear", #background_fill_color="#fafafa", plot_width=1000, x_axis_label="Number of pixels", y_axis_label="Throughput (MB/s)", ) p.toolbar.autohide = True #p.toolbar.active_inspect = [hover_tool, crosshair_tool] p.toolbar.active_drag = "auto" p.toolbar.active_scroll = "auto" # def dft(v): return v if v else (lambda n: n) tr = dft(transform) lttr = dft(line_title_transform) # for results in bm: x = [ids[name] for name in results.names] y = [bps/1e6 for bps in results.bytes_per_second] c = next(colors) marker = next(markers) next(markers) # advance two line_name = lttr(results.first) #legends.append(LegendItem(name=c, label=line_name)) p.scatter(x, y, marker=marker, size=8, color=c, legend_label=line_name) p.line(x, y, color=c, alpha=0.9, #muted_color=c, muted_alpha=0.05, legend_label=line_name) p.legend.click_policy = "hide" p.legend.label_text_font_size = "10px" # def input_title(title): return Div(text=f"<h3>{title}</h3>") inputs = [] first = bm[0].first.meta for k, g in first.checkbox_groups().items(): cb = CheckboxGroup(labels=[str(v) for v in g], active=[i for i in range(len(g))], inline=True) inputs.append(input_title(k)) inputs.append(cb) # # https://github.com/bokeh/bokeh/blob/branch-2.3/examples/app/export_csv/main.py x_axis_values = [f"{m.num_pixels}px" for m in bm[0].meta] table_sources = [] for i, px in enumerate(x_axis_values): c = ColumnDataSource(data={ 'name': [nth(results.filtered_names, i) for results in bm], 'bytes_per_second': [nth(results.bytes_per_second, i) for results in bm], 'items_per_second': [nth(results.items_per_second, i) for results in bm], 'cpu_time': [nth(results.real_time, i) for results in bm], 'real_time': [nth(results.real_time, i) for results in bm], 'iterations': [nth(results.iterations, i) for results in bm], 'threads': [nth(results.threads, i) for results in bm], }) table_sources.append(c) selected_x_index = 8 # FIXME (currently 2000 pixels) table_source = copy.deepcopy(table_sources[selected_x_index]) relvalues = Toggle(label="Table: Relative values") px_title = input_title("Table: number of pixels") px_radiogroup = RadioGroup(labels=x_axis_values, active=selected_x_index) table_inputs = [relvalues, px_title, px_radiogroup] # table_cols = [ TableColumn(field='name', title='Name'), TableColumn(field='bytes_per_second', title='Bytes/second'), TableColumn(field='items_per_second', title='Items/second'), TableColumn(field='cpu_time', title='CPU time'), TableColumn(field='real_time', title='Real time'), TableColumn(field='iterations', title='Iterations'), TableColumn(field='threads', title='Threads'), ] data_table = DataTable(source=table_source, columns=table_cols, width=1200) callback = CustomJS(args=dict( radiogroup=px_radiogroup, source=table_source, table=table_sources ), code=""" console.log(`active=${radiogroup.active}`); /*source.data=table[radiogroup.active];*/ var nrows = source.data['name'].length; var ts = table[radiogroup.active].data; var names = ["name", "bytes_per_second", "items_per_second", "cpu_time", "real_time", "iterations", "threads"]; var ncols = names.length; console.log(`names=${names} nrows=${nrows} ncols=${ncols}`); for(var i = 0; i < nrows; i++) { for(var j = 0; j < ncols; ++j) { var name = names[j]; /*console.log(`i=${i} j=${j} name=${name}`);*/ source.data[name][i] = ts[name][i]; } } source.change.emit(); """) px_radiogroup.js_on_change('active', callback) # lambda attr, old, new: log(f"attr={attr} old={old} new={new} active={px_radiogroup.active}")) # layout = column( row(column(*inputs), p), row(column(*table_inputs), data_table)) show(layout)
'Orange', ] df = pd.DataFrame({'x': x, 'y': y, 'label': label}) source = ColumnDataSource(data=dict(x=df.x, y=df.y, label=df.label)) plot_figure = figure(title='Radio Group', height=450, width=600, tools="save,reset", toolbar_location="below") plot_figure.scatter('x', 'y', color='label', source=source, size=10) radio_group = RadioGroup(labels=["Red", "Orange"]) def radiogroup_click(attr, old, new): active_radio = radio_group.active ##Getting radio button value # filter the dataframe with value in radio-button if active_radio == 0: selected_df = df[df['label'] == 'Red'] elif active_radio == 1: selected_df = df[df['label'] == "Orange"] source.data = dict(x=selected_df.x, y=selected_df.y, label=selected_df.label)
button = Button(label='press me') def update(): # Do something interesting button.on_click(update) # Button types from bokeh.models import CheckboxGroup, RadioGroup, Toggle toggle = Toggle(label='Some on/off', button_type='success') checkbox = CheckboxGroup(labels=['foo', 'bar', 'baz']) radio = RadioGroup(labels=['2000', '2010', '2020']) def callback(active) # Active tells which button is active curdoc().add_root(widgetbox(toggle, checkbox, radio)) '''Case study''' # EDA from bokeh.io import output_file, show from bokeh.plotting import figure from bokeh.models import HoverTool, ColumnDataSource source = ColumnDataSource(data={
def bkapp(doc): ### Functions ### # functions for user dialogs def open_file(ftype): root = Tk() root.withdraw() file = askopenfilename(filetypes=ftype, title='Open File', initialdir=os.getcwd()) root.destroy() return file def choose_directory(): root = Tk() root.withdraw() out_dir = askdirectory() root.destroy() return out_dir def write_output_directory(output_dir): root = Tk() root.withdraw() makeDir = askquestion('Make Directory','Output directory not set. Make directory: ' +output_dir+'? If not, you\'ll be prompted to change directories.',icon = 'warning') root.destroy() return makeDir def overwrite_file(): root = Tk() root.withdraw() overwrite = askquestion('Overwrite File','File already exits. Do you want to overwrite?',icon = 'warning') root.destroy() return overwrite def update_filename(): filetype = [("Video files", "*.mp4")] fname = open_file(filetype) if fname: #print('Successfully loaded file: '+fname) load_data(filename=fname) def change_directory(): out_dir = choose_directory() if out_dir: source.data["output_dir"] = [out_dir] outDir.text = out_dir return out_dir # load data from file def load_data(filename): vidcap = cv2.VideoCapture(filename) success,frame = vidcap.read() img_tmp,_,__ = cv2.split(frame) h,w = np.shape(img_tmp) img = np.flipud(img_tmp) radio_button_gp.active = 0 fname = os.path.split(filename)[1] input_dir = os.path.split(filename)[0] if source.data['output_dir'][0]=='': output_dir = os.path.join(input_dir,fname.split('.')[0]) else: output_dir = source.data['output_dir'][0] if not os.path.isdir(output_dir): makeDir = write_output_directory(output_dir) if makeDir=='yes': os.mkdir(output_dir) else: output_dir = change_directory() if output_dir: source.data = dict(image_orig=[img], image=[img], bin_img=[0], x=[0], y=[0], dw=[w], dh=[h], num_contours=[0], roi_coords=[0], img_name=[fname], input_dir=[input_dir], output_dir=[output_dir]) curr_img = p.select_one({'name':'image'}) if curr_img: p.renderers.remove(curr_img) p.image(source=source, image='image', x='x', y='y', dw='dw', dh='dh', color_mapper=cmap,level='image',name='image') p.plot_height=int(h/2) p.plot_width=int(w/2) #p.add_tools(HoverTool(tooltips=IMG_TOOLTIPS)) inFile.text = fname outDir.text = output_dir else: print('Cancelled. To continue please set output directory.{:<100}'.format(' '),end="\r") # resetting sources for new data or new filters/contours def remove_plot(): source.data["num_contours"]=[0] contours_found.text = 'Droplets Detected: 0' source_contours.data = dict(xs=[], ys=[]) source_label.data = dict(x=[], y=[], label=[]) # apply threshold filter and display binary image def apply_filter(): if source.data['input_dir'][0] == '': print('No image loaded! Load image first.{:<100}'.format(' '),end="\r") else: img = np.squeeze(source.data['image_orig']) # remove contours if present if source.data["num_contours"]!=[0]: remove_plot() if radio_button_gp.active == 1: thresh = filters.threshold_otsu(img) binary = img > thresh bin_img = binary*255 source.data["bin_img"] = [bin_img] elif radio_button_gp.active == 2: thresh = filters.threshold_isodata(img) binary = img > thresh bin_img = binary*255 source.data["bin_img"] = [bin_img] elif radio_button_gp.active == 3: thresh = filters.threshold_mean(img) binary = img > thresh bin_img = binary*255 source.data["bin_img"] = [bin_img] elif radio_button_gp.active == 4: thresh = filters.threshold_li(img) binary = img > thresh bin_img = binary*255 source.data["bin_img"] = [bin_img] elif radio_button_gp.active == 5: thresh = filters.threshold_yen(img) binary = img > thresh bin_img = binary*255 source.data["bin_img"] = [bin_img] elif radio_button_gp.active == 6: off = offset_spinner.value block_size = block_spinner.value thresh = filters.threshold_local(img,block_size,offset=off) binary = img > thresh bin_img = binary*255 source.data["bin_img"] = [bin_img] else: bin_img = img source.data['image'] = [bin_img] # image functions for adjusting the binary image def close_img(): if source.data["num_contours"]!=[0]: remove_plot() if radio_button_gp.active == 0: print("Please Select Filter for Threshold{:<100}".format(' '),end="\r") else: source.data["image"] = source.data["bin_img"] img = np.squeeze(source.data['bin_img']) closed_img = binary_closing(255-img)*255 source.data['image'] = [255-closed_img] source.data['bin_img'] = [255-closed_img] def dilate_img(): if source.data["num_contours"]!=[0]: remove_plot() if radio_button_gp.active == 0: print("Please Select Filter for Threshold{:<100}".format(' '),end="\r") else: img = np.squeeze(source.data['bin_img']) dilated_img = binary_dilation(255-img)*255 source.data['image'] = [255-dilated_img] source.data['bin_img'] = [255-dilated_img] def erode_img(): if source.data["num_contours"]!=[0]: remove_plot() if radio_button_gp.active == 0: print("Please Select Filter for Threshold{:<100}".format(' '),end="\r") else: img = np.squeeze(source.data['bin_img']) eroded_img = binary_erosion(255-img)*255 source.data['image'] = [255-eroded_img] source.data['bin_img'] = [255-eroded_img] # the function for identifying closed contours in the image def find_contours(level=0.8): min_drop_size = contour_rng_slider.value[0] max_drop_size = contour_rng_slider.value[1] min_dim = 20 max_dim = 200 if radio_button_gp.active == 0: print("Please Select Filter for Threshold{:<100}".format(' '),end="\r") elif source.data['input_dir'][0] == '': print('No image loaded! Load image first.{:<100}'.format(' '),end="\r") else: img = np.squeeze(source.data['bin_img']) h,w = np.shape(img) contours = measure.find_contours(img, level) length_cnt_x = [cnt[:,1] for cnt in contours if np.shape(cnt)[0] < max_drop_size and np.shape(cnt)[0] > min_drop_size] length_cnt_y = [cnt[:,0] for cnt in contours if np.shape(cnt)[0] < max_drop_size and np.shape(cnt)[0] > min_drop_size] matched_cnt_x = [] matched_cnt_y = [] roi_coords = [] label_text = [] label_y = np.array([]) count=0 for i in range(len(length_cnt_x)): cnt_x = length_cnt_x[i] cnt_y = length_cnt_y[i] width = np.max(cnt_x)-np.min(cnt_x) height = np.max(cnt_y)-np.min(cnt_y) if width>min_dim and width<max_dim and height>min_dim and height<max_dim: matched_cnt_x.append(cnt_x) matched_cnt_y.append(cnt_y) roi_coords.append([round(width),round(height),round(np.min(cnt_x)),round(h-np.max(cnt_y))]) label_text.append(str(int(count)+1)) label_y = np.append(label_y,np.max(cnt_y)) count+=1 curr_contours = p.select_one({'name':'overlay'}) if curr_contours: p.renderers.remove(curr_contours) #if source.data["num_contours"]==[0]: #remove_plot() #p.image(source=source, image='image_orig', x=0, y=0, dw=w, dh=h, color_mapper=cmap, name='overlay',level='underlay') source.data["image"] = source.data["image_orig"] source.data["num_contours"] = [count] #source.data["cnt_x"] = [matched_cnt_x] #source.data["cnt_y"] = [matched_cnt_y] source.data["roi_coords"] = [roi_coords] source_contours.data = dict(xs=matched_cnt_x, ys=matched_cnt_y) p.multi_line(xs='xs',ys='ys',source=source_contours, color=(255,127,14),line_width=2, name="contours",level='glyph') if len(np.array(roi_coords).shape)<2: if len(np.array(roi_coords)) <4: print('No contours found. Try adjusting parameters or filter for thresholding.{:<100}'.format(' '),end="\r") source_label.data = dict(x=[],y=[],label=[]) else: source_label.data = dict(x=np.array(roi_coords)[2], y=label_y, label=label_text) else: source_label.data = dict(x=np.array(roi_coords)[:,2], y=label_y, label=label_text) contours_found.text = 'Droplets Detected: '+str(len(matched_cnt_x)) # write the contours and parameters to files def export_ROIs(): if source.data["num_contours"]==[0]: print("No Contours Found! Find contours first.{:<100}".format(' '),end="\r") else: hdr = 'threshold filter,contour min,contour max' thresh_filter = radio_button_gp.active cnt_min = contour_rng_slider.value[0] cnt_max = contour_rng_slider.value[1] params = [thresh_filter,cnt_min,cnt_max] if radio_button_gp.active == 6: off = offset_spinner.value block_size = block_spinner.value hdr = hdr + ',local offset,local block size' params.append(off,block_size) params_fname = 'ContourParams.csv' params_out = os.path.join(source.data['output_dir'][0],params_fname) overwrite = 'no' if os.path.exists(params_out): overwrite = overwrite_file() if overwrite == 'yes' or not os.path.exists(params_out): np.savetxt(params_out,np.array([params]),delimiter=',',header=hdr,comments='') roi_coords = np.array(source.data["roi_coords"][0]) out_fname = 'ROI_coords.csv' out_fullpath = os.path.join(source.data['output_dir'][0],out_fname) if overwrite == 'yes' or not os.path.exists(out_fullpath): hdr = 'width,height,x,y' np.savetxt(out_fullpath,roi_coords,delimiter=',',header=hdr,comments='') print('Successfully saved ROIs coordinates as: '+out_fullpath,end='\r') source.data['roi_coords'] = [roi_coords] # function for loading previously made files or error handling for going out of order def check_ROI_files(): coords_file = os.path.join(source.data["output_dir"][0],'ROI_coords.csv') n_cnt_curr = source.data["num_contours"][0] roi_coords_curr = source.data["roi_coords"][0] if os.path.exists(coords_file): df_tmp=pd.read_csv(coords_file, sep=',') roi_coords = np.array(df_tmp.values) n_cnt = len(roi_coords) if n_cnt_curr != n_cnt or not np.array_equal(roi_coords_curr,roi_coords): print('Current ROIs are different from saved ROI file! ROIs from saved file will be used instead and plot updated.',end="\r") source.data["num_contours"] = [n_cnt] source.data["roi_coords"] = [roi_coords] params_file = os.path.join(source.data['output_dir'][0],'ContourParams.csv') params_df = pd.read_csv(params_file) thresh_ind = params_df["threshold filter"].values[0] radio_button_gp.active = int(thresh_ind) if thresh_ind == 6: offset_spinner.value = int(params_df["local offset"].values[0]) block_spinner.value = int(params_df["local block size"].values[0]) contour_rng_slider.value = tuple([int(params_df["contour min"].values[0]),int(params_df["contour max"].values[0])]) find_contours() else: print("ROI files not found! Check save directory or export ROIs.{:<100}".format(' '),end="\r") # use FFMPEG to crop out regions from original mp4 and save to file def create_ROI_movies(): if source.data['input_dir'][0] == '': print('No image loaded! Load image first.{:<100}'.format(' '),end="\r") else: check_ROI_files() side = 100 # for square ROIs, replace first two crop parameters with side & uncomment padding = 20 roi_coords_file = os.path.join(source.data['output_dir'][0],'ROI_coords.csv') if source.data["num_contours"]==[0]: print("No contours found! Find contours first.{:<100}".format(' '),end="\r") elif not os.path.exists(roi_coords_file): print("ROI file does not exist! Check save directory or export ROIs.{:<100}".format(' '),end="\r") else: print('Creating Movies...{:<100}'.format(' '),end="\r") pbar = tqdm(total=source.data["num_contours"][0]) for i in range(source.data["num_contours"][0]): roi_coords = np.array(source.data["roi_coords"][0]) inPath = os.path.join(source.data['input_dir'][0],source.data['img_name'][0]) #out_fname = source.data['filename'][0].split('.')[0] +'_ROI'+str(i+1)+'.mp4' out_fname = 'ROI'+str(i+1)+'.mp4' outPath = os.path.join(source.data['output_dir'][0],out_fname) #command = f"ffmpeg -i \'{(inPath)}\' -vf \"crop={(roi_coords[i,0]+padding*2)}:{(roi_coords[i,1]+padding*2)}:{(roi_coords[i,2]-padding)}:{(roi_coords[i,3]+padding)}\" -y \'{(outPath)}\'" command = f"ffmpeg -i \'{(inPath)}\' -vf \"crop={side}:{side}:{(roi_coords[i,2]-padding)}:{(roi_coords[i,3]-padding)}\" -y \'{(outPath)}\'" overwrite = 'no' if os.path.exists(outPath): overwrite = overwrite_file() if overwrite == 'yes' or not os.path.exists(outPath): saved = subprocess.check_call(command,shell=True) if saved != 0: print('An error occurred while creating movies! Check terminal window.{:<100}'.format(' '),end="\r") pbar.update() # change the display range on images from slider values def update_image(): cmap.low = display_range_slider.value[0] cmap.high = display_range_slider.value[1] # create statistics files for each mp4 region specific file def process_ROIs(): if source.data['input_dir'][0] == '': print('No image loaded! Load image first.{:<100}'.format(' '),end="\r") else: check_ROI_files() hdr = 'roi,time,area,mean,variance,min,max,median,skewness,kurtosis,rawDensity,COMx,COMy' cols = hdr.split(',') all_stats = np.zeros((0,13)) n_cnt = source.data["num_contours"][0] if n_cnt == 0: print("No contours found! Find contours first.{:<100}".format(' '),end="\r") for i in range(n_cnt): #in_fname = source.data['filename'][0].split('.')[0] +'_ROI'+str(i+1)+'.mp4' in_fname = 'ROI'+str(i+1)+'.mp4' inPath = os.path.join(source.data['output_dir'][0],in_fname) #out_fname = source.data['filename'][0].split('.')[0] +'_ROI'+str(i+1)+'_stats.csv' out_fname = 'stats_ROI'+str(i+1)+'.csv' outPath = os.path.join(source.data['output_dir'][0],out_fname) if not os.path.exists(inPath): print('ROI movie not found! Create ROI movie first.{:<100}'.format(' '),end="\r") break vidcap = cv2.VideoCapture(inPath) last_frame = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT)) if i==0: pbar = tqdm(total=last_frame*n_cnt) success,frame = vidcap.read() img_tmp,_,__ = cv2.split(frame) h,w = np.shape(img_tmp) img = np.zeros((last_frame,h,w)) img_stats = np.zeros((last_frame,13)) stats = describe(img_tmp,axis=None) median = np.median(img_tmp) density = np.sum(img_tmp) cx, cy = center_of_mass(img_tmp) img_stats[0,0:13] = [i,0,stats.nobs,stats.mean,stats.variance, stats.minmax[0],stats.minmax[1],median,stats.skewness, stats.kurtosis,density,cx,cy] img[0,:,:] = np.flipud(img_tmp) pbar.update() overwrite = 'no' if os.path.exists(outPath): overwrite = overwrite_file() if overwrite=='no': pbar.update(last_frame-1) if overwrite == 'yes' or not os.path.exists(outPath): for j in range(1,last_frame): vidcap.set(1, j) success,frame = vidcap.read() img_tmp,_,__ = cv2.split(frame) stats = describe(img_tmp,axis=None) t = j*5/60 density = np.sum(img_tmp) cx, cy = center_of_mass(img_tmp) median = np.median(img_tmp) img_stats[j,0:13] = [i,t,stats.nobs,stats.mean,stats.variance, stats.minmax[0],stats.minmax[1],median,stats.skewness, stats.kurtosis,density,cx,cy] img[j,:,:] = np.flipud(img_tmp) pbar.update(1) all_stats = np.append(all_stats,img_stats,axis=0) np.savetxt(outPath,img_stats,delimiter=',',header=hdr,comments='') if i==(n_cnt-1): df = pd.DataFrame(all_stats,columns=cols) group = df.groupby('roi') for i in range(len(group)): sources_stats[i] = ColumnDataSource(group.get_group(i)) # load statistics CSVs and first ROI mp4 files and display in plots def load_ROI_files(): if source.data['input_dir'][0] == '': print('No image loaded! Load image first.{:<100}'.format(' '),end="\r") else: check_ROI_files() n_cnt = source.data["num_contours"][0] basepath = os.path.join(source.data["output_dir"][0],'stats') all_files = [basepath+'_ROI'+str(i+1)+'.csv' for i in range(n_cnt)] files_exist = [os.path.exists(f) for f in all_files] if all(files_exist) and n_cnt != 0: df = pd.concat((pd.read_csv(f) for f in all_files), ignore_index=True) group = df.groupby('roi') OPTIONS = [] LABELS = [] pbar = tqdm(total=len(stats)*len(group)) j=0 colors_ordered = list(Category20[20]) idx_reorder = np.append(np.linspace(0,18,10),np.linspace(1,19,10)) idx = idx_reorder.astype(int) colors = [colors_ordered[i] for i in idx] for roi, df_roi in group: sources_stats[roi] = ColumnDataSource(df_roi) OPTIONS.append([str(int(roi)+1),'ROI '+(str(int(roi)+1))]) LABELS.append('ROI '+str(int(roi)+1)) color = colors[j] j+=1 if j>=20: j=0 for i in range(3,len(df.columns)): name = 'ROI '+str(int(roi)+1) plot_check = p_stats[i-3].select_one({'name':name}) if not plot_check: p_stats[i-3].line(x='time',y=str(df.columns[i]),source=sources_stats[roi], name=name,visible=False,line_color=color) p_stats[i-3].xaxis.axis_label = "Time [h]" p_stats[i-3].yaxis.axis_label = str(df.columns[i]) p_stats[i-3].add_tools(HoverTool(tooltips=TOOLTIPS)) p_stats[i-3].toolbar_location = "left" pbar.update(1) ROI_multi_select.options = OPTIONS ROI_multi_select.value = ["1"] ROI_movie_radio_group.labels = LABELS ROI_movie_radio_group.active = 0 else: print('Not enough files! Check save directory or calculate new stats.{:<100}'.format(' '),end="\r") # show/hide curves from selected/deselected labels for ROIs in statistics plots def update_ROI_plots(): n_cnt = source.data["num_contours"][0] pbar = tqdm(total=len(stats)*n_cnt) for j in range(n_cnt): for i in range(len(stats)): name = 'ROI '+str(int(j)+1) glyph = p_stats[i].select_one({'name': name}) if str(j+1) in ROI_multi_select.value: glyph.visible = True else: glyph.visible = False pbar.update(1) # load and display the selected ROI's mp4 def load_ROI_movie(): idx = ROI_movie_radio_group.active in_fname = 'ROI'+str(idx+1)+'.mp4' inPath = os.path.join(source.data['output_dir'][0],in_fname) if not os.path.exists(inPath): print('ROI movie not found! Check save directory or create ROI movie.',end="\r") else: old_plot = p_ROI.select_one({'name': sourceROI.data['img_name'][0]}) if old_plot: p_ROI.renderers.remove(old_plot) vidcap = cv2.VideoCapture(inPath) last_frame = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT)) ROI_movie_slider.end = (last_frame-1)*5/60 ROI_movie_slider.value = 0 vidcap.set(1, 0) success,frame = vidcap.read() img_tmp,_,__ = cv2.split(frame) h,w = np.shape(img_tmp) img = np.flipud(img_tmp) name = 'ROI'+str(idx+1) sourceROI.data = dict(image=[img],x=[0], y=[0], dw=[w], dh=[h], img_name=[name]) p_ROI.image(source=sourceROI, image='image', x='x', y='y', dw='dw', dh='dh', color_mapper=cmap, name='img_name') # change the displayed frame from slider movement def update_ROI_movie(): frame_idx = round(ROI_movie_slider.value*60/5) in_fname = sourceROI.data['img_name'][0]+'.mp4' inPath = os.path.join(source.data['output_dir'][0],in_fname) vidcap = cv2.VideoCapture(inPath) vidcap.set(1, frame_idx) success,frame = vidcap.read() img_tmp,_,__ = cv2.split(frame) img = np.flipud(img_tmp) sourceROI.data['image'] = [img] # the following 2 functions are used to animate the mp4 def update_ROI_slider(): time = ROI_movie_slider.value + 5/60 end = ROI_movie_slider.end if time > end: animate_ROI_movie() else: ROI_movie_slider.value = time return callback_id def animate_ROI_movie(): global callback_id if ROI_movie_play_button.label == '► Play': ROI_movie_play_button.label = '❚❚ Pause' callback_id = curdoc().add_periodic_callback(update_ROI_slider, 10) else: ROI_movie_play_button.label = '► Play' curdoc().remove_periodic_callback(callback_id) return callback_id ### Application Content ### # main plot for segmentation and contour finding cmap = LinearColorMapper(palette="Greys256", low=0, high=255) TOOLS = "pan,wheel_zoom,box_zoom,reset,save,box_select,lasso_select" IMG_TOOLTIPS = [('name', "@img_name"),("x", "$x"),("y", "$y"),("value", "@image")] source = ColumnDataSource(data=dict(image=[0],bin_img=[0],image_orig=[0], x=[0], y=[0], dw=[0], dh=[0], num_contours=[0], roi_coords=[0], input_dir=[''],output_dir=[''],img_name=[''])) source_label = ColumnDataSource(data=dict(x=[0], y=[0], label=[''])) source_contours = ColumnDataSource(data=dict(xs=[0], ys=[0])) roi_labels = LabelSet(x='x', y='y', text='label',source=source_label, level='annotation',text_color='white',text_font_size='12pt') # create a new plot and add a renderer p = figure(tools=TOOLS, toolbar_location=("right")) p.add_layout(roi_labels) p.x_range.range_padding = p.y_range.range_padding = 0 # turn off gridlines p.xgrid.grid_line_color = None p.ygrid.grid_line_color = None p.axis.visible = False # ROI plots sourceROI = ColumnDataSource(data=dict(image=[0], x=[0], y=[0], dw=[0], dh=[0], img_name=[0])) sources_stats = {} TOOLTIPS = [('name','$name'),('time', '@time'),('stat', "$y")] stats = np.array(['mean','var','min','max','median','skew','kurt','rawDensity','COMx','COMy']) p_stats = [] tabs = [] for i in range(len(stats)): p_stats.append(figure(tools=TOOLS, plot_height=300, plot_width=600)) p_stats[i].x_range.range_padding = p_stats[i].y_range.range_padding = 0 tabs.append(Panel(child=p_stats[i], title=stats[i])) # create a new plot and add a renderer p_ROI = figure(tools=TOOLS, toolbar_location=("right"), plot_height=300, plot_width=300) p_ROI.x_range.range_padding = p_ROI.y_range.range_padding = 0 # turn off gridlines p_ROI.xgrid.grid_line_color = p_ROI.ygrid.grid_line_color = None p_ROI.axis.visible = False # Widgets - Buttons, Sliders, Text, Etc. intro = Div(text="""<h2>Droplet Recognition and Analysis with Bokeh</h2> This application is designed to help segment a grayscale image into regions of interest (ROIs) and perform analysis on those regions.<br> <h4>How to Use This Application:</h4> <ol> <li>Load in a grayscale mp4 file and choose a save directory.</li> <li>Apply various filters for thresholding. Use <b>Close</b>, <b>Dilate</b> and <b>Erode</b> buttons to adjust each binary image further.</li> <li>Use <b>Find Contours</b> button to search the image for closed shape. The <b>Contour Size Range</b> slider will change size of the perimeter to be identified. You can apply new thresholds and repeat until satisfied with the region selection. Total regions detected is displayed next to the button.</li> <li>When satisfied, use <b>Export ROIs</b> to write ROI locations and contour finding parameters to file.</li> <li><b>Create ROI Movies</b> to write mp4s of the selected regions.</li> <li>Use <b>Calculate ROI Stats</b> to perform calculations on the newly created mp4 files.</li> <li>Finally, use <b>Load ROI Files</b> to load in the data that you just created and view the plots. The statistics plots can be overlaid by selecting multiple labels. Individual ROI mp4s can be animated or you can use the slider to move through the frames.</li> </ol> Note: messages and progress bars are displayed below the GUI.""", style={'font-size':'10pt'},width=1000) file_button = Button(label="Choose File",button_type="primary") file_button.on_click(update_filename) inFile = PreText(text='Input File:\n'+source.data["img_name"][0], background=(255,255,255,0.5), width=500) filter_LABELS = ["Original","OTSU", "Isodata", "Mean", "Li","Yen","Local"] radio_button_gp = RadioButtonGroup(labels=filter_LABELS, active=0, width=600) radio_button_gp.on_change('active', lambda attr, old, new: apply_filter()) offset_spinner = Spinner(low=0, high=500, value=1, step=1, width=100, title="Local: Offset", background=(255,255,255,0.5)) offset_spinner.on_change('value', lambda attr, old, new: apply_filter()) block_spinner = Spinner(low=1, high=101, value=25, step=2, width=100, title="Local: Block Size", background=(255,255,255,0.5)) block_spinner.on_change('value', lambda attr, old, new: apply_filter()) closing_button = Button(label="Close",button_type="default", width=100) closing_button.on_click(close_img) dilation_button = Button(label="Dilate",button_type="default", width=100) dilation_button.on_click(dilate_img) erosion_button = Button(label="Erode",button_type="default", width=100) erosion_button.on_click(erode_img) contour_rng_slider = RangeSlider(start=10, end=500, value=(200,350), step=1, width=300, title="Contour Size Range", background=(255,255,255,0.5), bar_color='gray') contour_button = Button(label="Find Contours", button_type="success") contour_button.on_click(find_contours) contours_found = PreText(text='Droplets Detected: '+str(source.data["num_contours"][0]), background=(255,255,255,0.5)) exportROIs_button = Button(label="Export ROIs", button_type="success", width=200) exportROIs_button.on_click(export_ROIs) changeDir_button = Button(label="Change Directory",button_type="primary", width=150) changeDir_button.on_click(change_directory) outDir = PreText(text='Save Directory:\n'+source.data["output_dir"][0], background=(255,255,255,0.5), width=500) create_ROIs_button = Button(label="Create ROI Movies",button_type="success", width=200) create_ROIs_button.on_click(create_ROI_movies) process_ROIs_button = Button(label="Calculate ROI Stats",button_type="success") process_ROIs_button.on_click(process_ROIs) display_rng_text = figure(title="Display Range", title_location="left", width=40, height=300, toolbar_location=None, min_border=0, outline_line_color=None) display_rng_text.title.align="center" display_rng_text.title.text_font_size = '10pt' display_rng_text.x_range.range_padding = display_rng_text.y_range.range_padding = 0 display_range_slider = RangeSlider(start=0, end=255, value=(0,255), step=1, orientation='vertical', direction='rtl', bar_color='gray', width=40, height=300, tooltips=True) display_range_slider.on_change('value', lambda attr, old, new: update_image()) load_ROIfiles_button = Button(label="Load ROI Files",button_type="primary") load_ROIfiles_button.on_click(load_ROI_files) ROI_multi_select = MultiSelect(value=[], width=100, height=300) ROI_multi_select.on_change('value', lambda attr, old, new: update_ROI_plots()) ROI_movie_radio_group = RadioGroup(labels=[],width=60) ROI_movie_radio_group.on_change('active', lambda attr, old, new: load_ROI_movie()) ROI_movie_slider = Slider(start=0,end=100,value=0,step=5/60,title="Time [h]", width=280) ROI_movie_slider.on_change('value', lambda attr, old, new: update_ROI_movie()) callback_id = None ROI_movie_play_button = Button(label='► Play',width=50) ROI_movie_play_button.on_click(animate_ROI_movie) # initialize some data without having to choose file # fname = os.path.join(os.getcwd(),'data','Droplets.mp4') # load_data(filename=fname) ### Layout & Initialize application ### ROI_layout = layout([ [ROI_movie_radio_group, p_ROI], [ROI_movie_slider,ROI_movie_play_button] ]) app = layout(children=[ [intro], [file_button,inFile], [radio_button_gp, offset_spinner, block_spinner], [closing_button, dilation_button, erosion_button], [contour_rng_slider, contour_button, contours_found], [exportROIs_button, outDir, changeDir_button], [create_ROIs_button, process_ROIs_button], [display_rng_text, display_range_slider, p], [load_ROIfiles_button], [ROI_layout, ROI_multi_select, Tabs(tabs=tabs)] ]) doc.add_root(app)