Source code for prospect.viewer.vi_widgets

# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-
"""
==========================
prospect.viewer.vi_widgets
==========================

Class containing bokeh widgets related to visual inspection

"""

from bokeh.models import CustomJS
from bokeh.models.widgets import (
    TextInput, CheckboxGroup, Select, RadioButtonGroup, Div, 
    TableColumn, DataTable, Toggle, Button)

from ..utilities import get_resources, vi_flags, vi_file_fields, vi_spectypes, vi_std_comments


[docs]class ViewerVIWidgets(object): """ Encapsulates Bokeh widgets, and related callbacks, used for VI """ def __init__(self, title, viewer_cds): self.vi_quality_labels = [ x["label"] for x in vi_flags if x["type"]=="quality" ] self.vi_issue_labels = [ x["label"] for x in vi_flags if x["type"]=="issue" ] self.vi_issue_slabels = [ x["shortlabel"] for x in vi_flags if x["type"]=="issue" ] self.js_files = get_resources('js') self.title = title self.vi_countdown_toggle = None #- List of fields to be recorded in output csv file, contains for each field: # [ field name (in VI file header), associated variable in viewer_cds.cds_metadata ] self.output_file_fields = [] for file_field in vi_file_fields: if file_field[1] in viewer_cds.cds_metadata.data.keys() : self.output_file_fields.append([file_field[0], file_field[1]]) def add_filename(self, username='unknown-user'): #- VI file name default_vi_filename = "desi-vi_"+self.title default_vi_filename += ("_"+username) default_vi_filename += ".csv" self.vi_filename_input = TextInput(value=default_vi_filename, title="VI file name:") def add_vi_issues(self, viewer_cds, widgets): #- Optional VI flags (issues) self.vi_issue_input = CheckboxGroup(labels=self.vi_issue_labels, active=[]) vi_issue_code = self.js_files["CSVtoArray.js"] + self.js_files["save_vi.js"] vi_issue_code += """ var issues = [] for (var i=0; i<vi_issue_labels.length; i++) { if (vi_issue_input.active.indexOf(i) >= 0) issues.push(vi_issue_slabels[i]) } if (issues.length > 0) { cds_metadata.data['VI_issue_flag'][ispectrumslider.value] = ( issues.join('') ) } else { cds_metadata.data['VI_issue_flag'][ispectrumslider.value] = " " } autosave_vi_localStorage(output_file_fields, cds_metadata.data, title) cds_metadata.change.emit() """ self.vi_issue_callback = CustomJS( args=dict(cds_metadata = viewer_cds.cds_metadata, ispectrumslider = widgets.ispectrumslider, vi_issue_input = self.vi_issue_input, vi_issue_labels = self.vi_issue_labels, vi_issue_slabels = self.vi_issue_slabels, title = self.title, output_file_fields = self.output_file_fields), code = vi_issue_code ) self.vi_issue_input.js_on_click(self.vi_issue_callback) def add_vi_z(self, viewer_cds, widgets): ## TODO: z_tovi behaviour if with_vi_widget=False ..? #- Optional VI information on redshift self.vi_z_input = TextInput(value='', title="VI redshift:") vi_z_code = self.js_files["CSVtoArray.js"] + self.js_files["save_vi.js"] vi_z_code += """ cds_metadata.data['VI_z'][ispectrumslider.value]=vi_z_input.value autosave_vi_localStorage(output_file_fields, cds_metadata.data, title) cds_metadata.change.emit() """ self.vi_z_callback = CustomJS( args=dict(cds_metadata = viewer_cds.cds_metadata, ispectrumslider = widgets.ispectrumslider, vi_z_input = self.vi_z_input, title = self.title, output_file_fields=self.output_file_fields), code = vi_z_code ) self.vi_z_input.js_on_change('value', self.vi_z_callback) # Copy z value from redshift slider to VI self.z_tovi_button = Button(label='Copy z to VI') self.z_tovi_callback = CustomJS( args=dict(z_input=widgets.z_input, vi_z_input=self.vi_z_input), code=""" vi_z_input.value = z_input.value """) self.z_tovi_button.js_on_event('button_click', self.z_tovi_callback) def add_vi_spectype(self, viewer_cds, widgets): #- Optional VI information on spectral type self.vi_category_select = Select(value=' ', title="VI spectype:", options=([' '] + vi_spectypes)) # The default value set to ' ' as setting value='' does not seem to work well with Select. vi_category_code = self.js_files["CSVtoArray.js"] + self.js_files["save_vi.js"] vi_category_code += """ if (vi_category_select.value == ' ') { cds_metadata.data['VI_spectype'][ispectrumslider.value]='' } else { cds_metadata.data['VI_spectype'][ispectrumslider.value]=vi_category_select.value } autosave_vi_localStorage(output_file_fields, cds_metadata.data, title) cds_metadata.change.emit() """ self.vi_category_callback = CustomJS( args=dict(cds_metadata=viewer_cds.cds_metadata, ispectrumslider = widgets.ispectrumslider, vi_category_select=self.vi_category_select, title=self.title, output_file_fields=self.output_file_fields), code=vi_category_code ) self.vi_category_select.js_on_change('value', self.vi_category_callback) def add_vi_comment(self, viewer_cds, widgets): #- Optional VI comment self.vi_comment_input = TextInput(value='', title="VI comment (see guidelines):") vi_comment_code = self.js_files["CSVtoArray.js"] + self.js_files["save_vi.js"] vi_comment_code += """ var stored_comment = (vi_comment_input.value).replace(/./g, function(char){ if ( char==',' ) { return ';' } else if ( char.charCodeAt(0)<=127 ) { return char } else { var char_list = ['Å','α','β','γ','δ','λ'] var char_replace = ['Angstrom','alpha','beta','gamma','delta','lambda'] for (var i=0; i<char_list.length; i++) { if ( char==char_list[i] ) return char_replace[i] } return '?' } }) cds_metadata.data['VI_comment'][ispectrumslider.value] = stored_comment autosave_vi_localStorage(output_file_fields, cds_metadata.data, title) cds_metadata.change.emit() """ self.vi_comment_callback = CustomJS( args=dict(cds_metadata = viewer_cds.cds_metadata, ispectrumslider = widgets.ispectrumslider, vi_comment_input = self.vi_comment_input, title=self.title, output_file_fields=self.output_file_fields), code=vi_comment_code ) self.vi_comment_input.js_on_change('value',self.vi_comment_callback) #- List of "standard" VI comment self.vi_std_comment_select = Select(value=" ", title="Standard comment:", options=([' '] + vi_std_comments)) vi_std_comment_code = """ if (vi_std_comment_select.value != ' ') { if (vi_comment_input.value != '') { vi_comment_input.value = vi_comment_input.value + " " + vi_std_comment_select.value } else { vi_comment_input.value = vi_std_comment_select.value } } """ self.vi_std_comment_callback = CustomJS( args = dict(vi_std_comment_select = self.vi_std_comment_select, vi_comment_input = self.vi_comment_input), code = vi_std_comment_code ) self.vi_std_comment_select.js_on_change('value', self.vi_std_comment_callback) def add_vi_quality(self, viewer_cds, widgets): #- Main VI quality widget self.vi_quality_input = RadioButtonGroup(labels=self.vi_quality_labels) vi_quality_code = self.js_files["CSVtoArray.js"] + self.js_files["save_vi.js"] vi_quality_code += """ if ( vi_quality_input.active >= 0 ) { cds_metadata.data['VI_quality_flag'][ispectrumslider.value] = vi_quality_labels[vi_quality_input.active] } else { cds_metadata.data['VI_quality_flag'][ispectrumslider.value] = "-1" } autosave_vi_localStorage(output_file_fields, cds_metadata.data, title) cds_metadata.change.emit() """ self.vi_quality_callback = CustomJS( args = dict(cds_metadata = viewer_cds.cds_metadata, vi_quality_input = self.vi_quality_input, vi_quality_labels = self.vi_quality_labels, ispectrumslider = widgets.ispectrumslider, title=self.title, output_file_fields = self.output_file_fields), code=vi_quality_code ) self.vi_quality_input.js_on_click(self.vi_quality_callback) def add_vi_scanner(self, viewer_cds): #- VI scanner name self.vi_name_input = TextInput(value=(viewer_cds.cds_metadata.data['VI_scanner'][0]).strip(), title="Your name (3-letter acronym):") vi_name_code = self.js_files["CSVtoArray.js"] + self.js_files["save_vi.js"] vi_name_code += """ for (var i=0; i<(cds_metadata.data['VI_scanner']).length; i++) { cds_metadata.data['VI_scanner'][i]=vi_name_input.value } var newname = vi_filename_input.value var name_chunks = newname.split("_") newname = ( name_chunks.slice(0,name_chunks.length-1).join("_") ) + ("_"+vi_name_input.value+".csv") vi_filename_input.value = newname autosave_vi_localStorage(output_file_fields, cds_metadata.data, title) """ self.vi_name_callback = CustomJS( args = dict(cds_metadata = viewer_cds.cds_metadata, vi_name_input = self.vi_name_input, vi_filename_input = self.vi_filename_input, title=self.title, output_file_fields = self.output_file_fields), code=vi_name_code ) self.vi_name_input.js_on_change('value', self.vi_name_callback) def add_guidelines(self): #- Guidelines for VI flags vi_guideline_txt = "<B> VI guidelines </B>" vi_guideline_txt += "<BR /> <B> Classification flags: </B>" for flag in vi_flags : if flag['type'] == 'quality' : vi_guideline_txt += ("<BR />&emsp;&emsp;[&emsp;"+flag['label']+"&emsp;] "+flag['description']) vi_guideline_txt += "<BR /> <B> Optional indications: </B>" for flag in vi_flags : if flag['type'] == 'issue' : vi_guideline_txt += ( "<BR />&emsp;&emsp;[&emsp;" + flag['label'] + "&emsp;(" + flag['shortlabel'] + ")&emsp;] " + flag['description'] ) vi_guideline_txt += "<BR /> <B> Comments: </B> <BR /> 100 characters max, avoid commas (automatically replaced by semi-columns), ASCII only." self.vi_guideline_div = Div(text=vi_guideline_txt) def add_vi_storage(self, viewer_cds, widgets): #- Save VI info to CSV file self.save_vi_button = Button(label="Download VI", button_type="success") save_vi_code = self.js_files["FileSaver.js"] + self.js_files["CSVtoArray.js"] + self.js_files["save_vi.js"] save_vi_code += """ download_vi_file(output_file_fields, cds_metadata.data, vi_filename_input.value) """ self.save_vi_callback = CustomJS( args = dict(cds_metadata = viewer_cds.cds_metadata, output_file_fields = self.output_file_fields, vi_filename_input = self.vi_filename_input), code=save_vi_code ) self.save_vi_button.js_on_event('button_click', self.save_vi_callback) #- Recover auto-saved VI data in browser self.recover_vi_button = Button(label="Recover auto-saved VI", button_type="default") recover_vi_code = self.js_files["CSVtoArray.js"] + self.js_files["recover_autosave_vi.js"] self.recover_vi_callback = CustomJS( args = dict(title=self.title, output_file_fields=self.output_file_fields, cds_metadata = viewer_cds.cds_metadata, ispectrumslider = widgets.ispectrumslider, vi_comment_input = self.vi_comment_input, vi_name_input=self.vi_name_input, vi_quality_input=self.vi_quality_input, vi_issue_input=self.vi_issue_input, vi_issue_slabels=self.vi_issue_slabels, vi_quality_labels=self.vi_quality_labels), code = recover_vi_code ) self.recover_vi_button.js_on_event('button_click', self.recover_vi_callback) #- Clear all auto-saved VI self.clear_vi_button = Button(label="Clear all auto-saved VI", button_type="default") self.clear_vi_callback = CustomJS( args = dict(), code = """ localStorage.clear() """ ) self.clear_vi_button.js_on_event('button_click', self.clear_vi_callback) def add_vi_table(self, viewer_cds): #- Show VI in a table vi_table_columns = [ TableColumn(field="VI_quality_flag", title="Flag", width=40), TableColumn(field="VI_issue_flag", title="Opt.", width=50), TableColumn(field="VI_z", title="VI z", width=50), TableColumn(field="VI_spectype", title="VI spectype", width=150), TableColumn(field="VI_comment", title="VI comment", width=200) ] self.vi_table = DataTable(source=viewer_cds.cds_metadata, columns=vi_table_columns, width=500) self.vi_table.height = 10 * self.vi_table.row_height def add_countdown(self, vi_countdown): #- VI countdown assert vi_countdown > 0 self.vi_countdown_callback = CustomJS(args=dict(vi_countdown=vi_countdown), code=""" if ( (cb_obj.label).includes('Start') ) { // Callback doesn't do anything after countdown started var countDownDate = new Date().getTime() + (1000 * 60 * vi_countdown); var countDownLoop = setInterval(function(){ var now = new Date().getTime(); var distance = countDownDate - now; if (distance<0) { cb_obj.label = "Time's up !"; clearInterval(countDownLoop); } else { var days = Math.floor(distance / (1000 * 60 * 60 * 24)); var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((distance % (1000 * 60)) / 1000); //var stuff = days + "d " + hours + "h " + minutes + "m " + seconds + "s "; var stuff = minutes + "m " + seconds + "s "; cb_obj.label = "Countdown: " + stuff; } }, 1000); } """) self.vi_countdown_toggle = Toggle(label='Start countdown ('+str(vi_countdown)+' min)', active=False, button_type="success") self.vi_countdown_toggle.js_on_change('active', self.vi_countdown_callback)