Source code for jdaviz.configs.default.plugins.line_lists.line_lists

import numpy as np
import os

import astropy.units as u
from astropy import constants as const
from astropy.table import QTable

from glue.core.message import (SubsetCreateMessage,
                               SubsetDeleteMessage,
                               SubsetUpdateMessage)
from glue_jupyter.common.toolbar_vuetify import read_icon
from traitlets import Any, Bool, Float, Int, List, Unicode, Dict, observe

from jdaviz.core.custom_traitlets import FloatHandleEmpty
from jdaviz.core.events import (AddDataMessage,
                                RemoveDataMessage,
                                AddLineListMessage,
                                LineIdentifyMessage,
                                SnackbarMessage,
                                RedshiftMessage,
                                SpectralMarksChangedMessage)
from jdaviz.core.linelists import load_preset_linelist, get_linelist_metadata
from jdaviz.core.marks import SpectralLine
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import PluginTemplateMixin
from jdaviz.core.tools import ICON_DIR
from jdaviz.core.validunits import create_spectral_equivalencies_list

__all__ = ['LineListTool']


[docs] @tray_registry( 'g-line-list', label="Line Lists", viewer_requirements=['spectrum'] ) class LineListTool(PluginTemplateMixin): dialog = Bool(False).tag(sync=True) template_file = __file__, "line_lists.vue" rs_enabled = Bool(False).tag(sync=True) # disabled until lines are plotted rs_slider = Float(0).tag(sync=True) # in units of delta-redshift rs_slider_range_auto = Bool(True).tag(sync=True) rs_slider_half_range = Float(0.1).tag(sync=True) rs_slider_step_auto = Bool(True).tag(sync=True) rs_slider_step = Float(0.01).tag(sync=True) rs_slider_ndigits = Int(1).tag(sync=True) rs_slider_throttle = Int(100).tag(sync=True) rs_redshift = FloatHandleEmpty(0).tag(sync=True) rs_rv = FloatHandleEmpty(0).tag(sync=True) rs_rv_step = Float(1).tag(sync=True) dc_items = List([]).tag(sync=True) available_lists = List([]).tag(sync=True) loaded_lists = List([]).tag(sync=True) list_contents = Dict({}).tag(sync=True) custom_name = Unicode().tag(sync=True) custom_rest = Unicode().tag(sync=True) custom_unit_choices = List([]).tag(sync=True) custom_unit = Unicode().tag(sync=True) lines_filter = Any().tag(sync=True) # string or None filter_range = Bool(False).tag(sync=True) spectrum_viewer_min = Float(0.01).tag(sync=True) spectrum_viewer_max = Float(0.01).tag(sync=True) identify_label = Unicode().tag(sync=True) identify_line_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'line_select.svg'), 'svg+xml')).tag(sync=True) # noqa filter_range_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'spectral_range.svg'), 'svg+xml')).tag(sync=True) # noqa def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._viewer = self.app.get_viewer(self._default_spectrum_viewer_reference_name) self._spectrum1d = None if self._viewer: self.available_lists = self._viewer.available_linelists() self.list_to_load = None self.loaded_lists = ["Custom"] self.list_contents = {"Custom": {"lines": [], "color": "#FF0000FF", "medium": "Unknown (Custom)"}} self.line_mark_dict = {} self._units = {} self._bounds = {} self._global_redshift = 0 self._rs_disable_observe = False self._rs_pause_tables = False # track which line was recently changed to avoid recursive updates due to imprecise # roundtripping self._rs_line_obs_change = (None, None) # Watch for messages from Specviz helper redshift functions self.hub.subscribe(self, RedshiftMessage, handler=self._parse_redshift_msg) self.hub.subscribe(self, AddDataMessage, handler=self._on_viewer_data_changed) self.hub.subscribe(self, RemoveDataMessage, handler=self._on_viewer_data_changed) self.hub.subscribe(self, SubsetCreateMessage, handler=lambda x: self._on_viewer_data_changed()) self.hub.subscribe(self, SubsetDeleteMessage, handler=lambda x: self._on_viewer_data_changed()) self.hub.subscribe(self, SubsetUpdateMessage, handler=lambda x: self._on_viewer_data_changed()) self.hub.subscribe(self, AddLineListMessage, handler=self._list_from_notebook) self.hub.subscribe(self, LineIdentifyMessage, handler=self._process_identify_change) self.hub.subscribe(self, SpectralMarksChangedMessage, handler=lambda x: self.update_line_mark_dict()) # if set to auto (default), update the slider range when zooming on the spectrum viewer if self._viewer: self._viewer.state.add_callback("x_min", lambda x_min: self._on_spectrum_viewer_limits_changed()) self._viewer.state.add_callback("x_max", lambda x_max: self._on_spectrum_viewer_limits_changed()) self._disable_if_no_data() @property def _default_spectrum_viewer_reference_name(self): return getattr( self.app._jdaviz_helper, '_default_spectrum_viewer_reference_name', 'spectrum-viewer' ) @property def _default_flux_viewer_reference_name(self): return getattr( self.app._jdaviz_helper, '_default_flux_viewer_reference_name', 'flux-viewer' ) def _disable_if_no_data(self): if len(self.app.data_collection) == 0: self.disabled_msg = 'Line Lists unavailable when no data is loaded' else: self.disabled_msg = '' def _on_viewer_data_changed(self, msg=None): """ Callback method for when data is added or removed from a viewer, or when a subset is created, deleted, or updated. This method receives a glue message containing viewer information in the case of the former set of events, and updates the units in which to display the lines. Notes ----- We do not attempt to parse any data at this point, at it can cause visible lag in the application. Parameters ---------- msg : `glue.core.Message` The glue message passed to this callback method. """ self._disable_if_no_data() self._viewer_id = self.app._viewer_item_by_reference( self._default_spectrum_viewer_reference_name).get('id') # Subsets are global and are not linked to specific viewer instances, # so it's not required that we match any specific ids for that case. # However, if the msg is not none, check to make sure that it's the # viewer we care about and that the message contains the data label. if msg is None or msg.viewer_id != self._viewer_id or msg.data is None: return viewer = self.app.get_viewer(self._default_spectrum_viewer_reference_name) viewer_data_labels = [layer.layer.label for layer in viewer.layers] if msg.data.label not in viewer_data_labels: return label = msg.data.label try: viewer_data = self.app._jdaviz_helper.get_data(data_label=label) except TypeError: warn_message = SnackbarMessage("Line list plugin could not retrieve data from viewer", sender=self, color="error") self.hub.broadcast(warn_message) return # If no data is currently plotted, don't attempt to update if viewer_data is None: return if viewer_data.spectral_axis.unit == u.pix: # disable the plugin until we can address this properly (either using the wavelength # solution to support pixels in line-lists, or properly displaying the extracted # 1d spectrum in wavelength-space) self.disabled_msg = 'Line Lists unavailable when x-axis is in pixels' else: self.disabled_msg = '' self._units["x"] = str(viewer_data.spectral_axis.unit) self._units["y"] = str(viewer_data.flux.unit) self._bounds["min"] = viewer_data.spectral_axis[0] self._bounds["max"] = viewer_data.spectral_axis[-1] # set redshift slider to redshift stored in Spectrum1D object if viewer_data.meta.get('plugin', None) is not None: self.rs_redshift = (viewer_data.redshift.value if hasattr(viewer_data.redshift, 'value') else viewer_data.redshift) self._on_spectrum_viewer_limits_changed() # will also trigger _auto_slider_step # set the choices (and default) for the units for new custom lines self.custom_unit_choices = create_spectral_equivalencies_list( viewer_data.spectral_axis.unit) self.custom_unit = str(viewer_data.spectral_axis.unit) def _parse_redshift_msg(self, msg): ''' Handle incoming redshift messages from the app hub. Generally these will be created by Specviz helper methods. ''' if msg.sender == self: return param = msg.param if param == "rs_slider_range": if msg.value == 'auto': # observer will handle setting rs_slider_range self.rs_slider_range_auto = True else: self.rs_slider_range_auto = False self.rs_slider_half_range = float(msg.value)/2 elif param == "rs_slider_step": if msg.value == 'auto': # observer will handle setting rs_slider_step self.rs_slider_step_auto = True else: self.rs_slider_step_auto = False slider_step = float(msg.value) if slider_step > self.rs_slider_half_range: raise ValueError("step must be smaller than range/2") self.rs_slider_step = slider_step self.rs_rv_step = self._redshift_to_velocity(slider_step) elif param == "redshift": # NOTE: this should trigger the observe to update rs_rv, line positions, and # update self._global_redshift self.rs_redshift = float(msg.value) elif param == 'rv': # NOTE: this should trigger the observe to update rs_redshift, line positions, and # update self._global_redshift self.rs_rv = float(msg.value) else: raise NotImplementedError(f"RedshiftMessage with param {param} not implemented.") def _velocity_to_redshift(self, velocity): """ Convert a velocity to a relativistic redshift. Assumes km/s (float) as input and returns float. """ # NOTE: if supporting non-km/s units in the future, try to leave # the default case to avoid quantity math as below for efficiency beta = velocity * 1000 / const.c.value return np.sqrt((1 + beta) / (1 - beta)) - 1 def _redshift_to_velocity(self, redshift): """ Convert a relativistic redshift to a velocity. Returns in km/s (float) """ zponesq = (1 + redshift) ** 2 # NOTE: if supporting non-km/s units in the future, try to leave # the default case to avoid quantity math as below for efficiency return const.c.value * (zponesq - 1) / (zponesq + 1) / 1000 # km/s def _update_line_positions(self): # update all lines, self._global_redshift, and emit message back to Specviz helper z = u.Quantity(self.rs_redshift) for mark in self.app.get_viewer(self._default_spectrum_viewer_reference_name).figure.marks: # update ALL to this redshift, if adding support for per-line redshift # this logic will need to change to not affect ALL lines if not isinstance(mark, SpectralLine): continue mark.redshift = z @observe('rs_slider') def _on_slider_updated(self, event): if self._rs_disable_observe: return self._rs_pause_tables = True # NOTE: _on_rs_redshift_updated will handle updating rs_rv # NOTE: the input has a custom @input method in line_lists.vue to cast # to float so that we can assume its a float here to minimize lag # when interacting with the slider. self.rs_redshift = np.round(self.rs_redshift + event['new'] - event['old'], self.rs_slider_ndigits) def _rest_to_obs(self, rest, redshift=None): if redshift is None: redshift = float(self.rs_redshift) return rest * (1+redshift) @observe('rs_redshift') def _on_rs_redshift_updated(self, event): if self._rs_disable_observe: return if not isinstance(event['new'], float): # then blank or None or '.' return value = event['new'] # update _global_redshift so new lines, etc, will adopt this latest value self._global_redshift = value self._rs_disable_observe = True self.rs_rv = self._redshift_to_velocity(value) self._rs_disable_observe = False self._update_line_positions() if not self._rs_pause_tables: # TODO: try to avoid essentially repeating the loop from above, careful to minimize # updates to vue, maybe pause traitlets? self._update_line_list_obs() # Send the redshift back to the Specviz helper (and also trigger # self._update_global_redshift) msg = RedshiftMessage("redshift", value, sender=self) self.app.hub.broadcast(msg) def _update_line_list_obs(self, *args): for list_name, line_list in self.list_contents.items(): for i, line in enumerate(line_list['lines']): if self._rs_line_obs_change[0] == list_name and self._rs_line_obs_change[1] == i: # noqa # this trigger is coming from a manual change to the observed # wavelength and would result in a small change to the value before the # user can finish typing. So we'll just keep the old value until the # widget is blurred (loses focus) line_list['lines'][i]['obs'] = self._rs_line_obs_change[2] else: line_list['lines'][i]['obs'] = self._rest_to_obs(float(line['rest'])) self.list_contents[list_name] = line_list self.send_state('list_contents')
[docs] def vue_change_line_obs(self, kwargs): # NOTE: we can only pass one argument from vue (it seems), so we'll pass as # a dictionary (kwargs) instead of positional or keyword arguments (**kwargs) line_obs = kwargs.get('obs_new') if isinstance(line_obs, str) and not len(line_obs): # empty string, we don't want to revert yet because then # the user can never delete the entry and type something new # so we'll just leave empty return list_name = kwargs.get('list_name') line_ind = kwargs.get('line_ind') line = self.list_contents[list_name]['lines'][line_ind] line_rest = float(line['rest']) if line_obs is None: # then coming from the blur, we'll keep the latest update from the @change line_obs = float(line['obs']) # we don't want this call to recursively update THIS obs wavelength, but DO want it to # update the RV and all other obs wavelengths. Once tabbing or losing focus, vue will # send another event with avoid_feedback=False so that the wavelength updates to # exactly match the redshift (so that can be considered the ground truth value consistently) if kwargs.get('avoid_feedback', False): self._rs_line_obs_change = (list_name, line_ind, line_obs) # ensure tables will update when rs_redshift change is observed self._rs_pause_tables = kwargs.get('avoid_feedback', False) self.rs_redshift = (line_obs - line_rest) / line_rest self._rs_line_obs_change = (None, None)
[docs] def vue_unpause_tables(self, event=None): # after losing focus, update any elements that were paused during changes self._rs_pause_tables = False self._rs_disable_observe = False self._on_rs_redshift_updated({'new': self.rs_redshift})
@observe('rs_rv') def _on_rs_rv_updated(self, event): if self._rs_disable_observe: return if not isinstance(event['new'], float): # then blank or None or '.' return value = event['new'] redshift = self._velocity_to_redshift(value) # prevent update the redshift from propagating back to an update in the rv self._rs_disable_observe = True # we'll wait until the blur event (which will call vue_unpause_tables) # to update the value in the MOS table and observed wavelengths self._rs_pause_tables = True self.rs_redshift = redshift # but we do want to update the plotted lines self._update_line_positions() self._rs_disable_observe = False
[docs] def vue_slider_reset(self, event): self._rs_disable_observe = True self.rs_slider = 0.0 self._rs_disable_observe = False self._rs_pause_tables = False # the redshift value in the MOS table and observed wavelengths weren't # updating during slide, so update them now self.vue_unpause_tables()
def _on_spectrum_viewer_limits_changed(self, event=None): sv = self.app.get_viewer(self._default_spectrum_viewer_reference_name) if sv.state.x_min is None or sv.state.x_max is None: return self.spectrum_viewer_min = float(sv.state.x_min) self.spectrum_viewer_max = float(sv.state.x_max) # Also update the slider range self._auto_slider_range() def _auto_slider_range(self, event=None): """ Automatically adjusts the Redshift slider range to the values of the spectrum_viewer_min and spectrum_viewer_max traitlets """ if not self.rs_slider_range_auto: return # if set to auto, default the range based on the limits of the spectrum plot x_min, x_max = self.spectrum_viewer_min, self.spectrum_viewer_max x_mid = abs(x_max + x_min) / 2. # we'll *estimate* the redshift range to shift the range of the viewer # (for a line with a rest wavelength in the center of the viewer), # by taking abs, this will work for wavelength or frequency units. half_range = abs(x_max - x_min) / x_mid ndec = -np.log10(half_range) if ndec > 0 and not np.isinf(ndec): # round to at least 2 digits, or the first significant digit ndec = np.max([2, int(np.ceil(ndec))]) else: ndec = 1 half_range = np.round(half_range, ndec) # this will trigger self._auto_slider_step to set self.rs_slider_step and # self.rs_rv_step, if applicable self.rs_slider_half_range = half_range @observe('rs_slider_range_auto') def _on_rs_slider_range_auto_updated(self, event): if event['new']: self._on_spectrum_viewer_limits_changed() @observe('rs_slider_half_range') def _auto_slider_step(self, event=None): if not self.rs_slider_step_auto: return # if set to auto, default to 1000 steps in the range self.rs_slider_step = self.rs_slider_half_range * 2 / 1000 self.rs_rv_step = abs(self._redshift_to_velocity(self._global_redshift+self.rs_slider_step) - self.rs_rv) # noqa @observe('rs_slider_step') def _on_rs_slider_step_updated(self, event): # When using the slider, we'll "round" redshift to the digits in the # slider step to avoid extra digits due to rounding errors ndec = -np.log10(event['new']) if ndec > 0 and not np.isinf(ndec): # round to at least 2 digits, or one past the first significant digit # note: the UI will not show trailing zeros, we just want to avoid # and 1 at floating point precision if not significant ndec = np.max([2, np.ceil(ndec)+1]) else: ndec = 1 self.rs_slider_ndigits = int(ndec) @observe('rs_slider_step_auto') def _on_rs_slider_step_auto_updated(self, event): if event['new']: self._auto_slider_step() def _update_global_redshift(self, msg): '''Handle updates to the Specviz redshift slider, to apply to lines''' if msg.param == "redshift": self._global_redshift = msg.value def _list_from_notebook(self, msg): """ Callback method for when a spectral line list is added to the specviz instance from the notebook. Parameters ---------- msg : `glue.core.Message` The glue message passed to this callback method. Includes the line data added in msg.table. """ loaded_lists = self.loaded_lists list_contents = self.list_contents tmp_names_rest = [] for row in msg.table: if row["listname"] not in loaded_lists: loaded_lists.append(row["listname"]) if row["listname"] not in list_contents: list_contents[row["listname"]] = {"lines": [], "color": "#FF0000FF"} temp_dict = {"linename": row["linename"], "rest": row["rest"].value, "obs": self._rest_to_obs(row["rest"].value), "unit": str(row["rest"].unit), "colors": row["colors"] if "colors" in row else "#FF0000FF", "show": row["show"], "name_rest": row["name_rest"]} list_contents[row["listname"]]["lines"].append(temp_dict) tmp_names_rest.append(row["name_rest"]) self.send_state('loaded_lists') self.send_state('list_contents') self._viewer.plot_spectral_lines(tmp_names_rest, global_redshift=self._global_redshift) self.update_line_mark_dict() msg_text = ("Spectral lines loaded from notebook. Lines can be hidden" "/shown in the Line Lists plugin") lines_loaded_message = SnackbarMessage(msg_text, sender=self, color="success", timeout=15000) self.hub.broadcast(lines_loaded_message)
[docs] def update_line_mark_dict(self): self.line_mark_dict = {} for m in self._viewer.figure.marks: if isinstance(m, SpectralLine): self.line_mark_dict[m.table_index] = m n_lines_shown = len(self.line_mark_dict) # redshift controls are enabled if any lines are currently plotted self.rs_enabled = n_lines_shown > 0 if n_lines_shown > 0: # with a lot of lines, a quick slider move will lag. Let's scale the # timeout based on the number of lines, roughtly between 50-500 ms throttle = n_lines_shown * 5 if throttle < 50: throttle = 50 if throttle > 500: throttle = 500 self.rs_slider_throttle = throttle
[docs] def vue_list_selected(self, event): """ Handle list selection from presets dropdown selector """ self.list_to_load = event
[docs] def vue_load_list(self, event): """ Load one of the preset line lists, storing it's info in a vuetify-friendly manner in addition to loading the astropy table into the viewer's spectral_lines attribute. """ # Don't need to reload an already loaded list if self.list_to_load in self.loaded_lists: return temp_table = load_preset_linelist(self.list_to_load) # Also store basic list contents in a form that vuetify can handle # Adds line style parameters that can be changed on the front end temp_table["colors"] = "#FF0000FF" # Load the table into the main astropy table and get it back, to make # sure all values match between the main table and local plugin temp_table = self._viewer.load_line_list(temp_table, return_table=True, show=False) metadata = get_linelist_metadata() list_medium = metadata[self.list_to_load].get('medium', 'Unknown').capitalize() line_list_dict = {"lines": [], "color": "#FF000080", "medium": list_medium} # extra_fields = [x for x in temp_table.colnames if x not in # ("linename", "rest", "name_rest")] for row in temp_table: temp_dict = {"linename": row["linename"], "rest": row["rest"].value, "obs": self._rest_to_obs(row["rest"].value), "unit": str(row["rest"].unit), "colors": row["colors"], "show": False, "name_rest": str(row["name_rest"])} # for field in extra_fields: # temp_dict[field] = row[field] line_list_dict["lines"].append(temp_dict) list_contents = self.list_contents list_contents[self.list_to_load] = line_list_dict self.list_contents = {} self.list_contents = list_contents loaded_lists = self.loaded_lists + [self.list_to_load] self.loaded_lists = [] self.loaded_lists = loaded_lists self._viewer.plot_spectral_lines() self.update_line_mark_dict() msg_text = ("Spectral lines loaded from preset. Lines can be shown/hidden" f" in the {self.list_to_load} dropdown in the Line Lists plugin") lines_loaded_message = SnackbarMessage(msg_text, sender=self, color="success", timeout=15000) self.hub.broadcast(lines_loaded_message)
[docs] def vue_add_custom_line(self, event): """ Add a line to the "Custom" line list from UI input """ list_contents = self.list_contents temp_dict = {"linename": self.custom_name, "rest": float(self.custom_rest), "obs": self._rest_to_obs(float(self.custom_rest)), "unit": self.custom_unit, "colors": list_contents["Custom"]["color"], "show": True } # Add to viewer astropy table with u.set_enabled_equivalencies(u.spectral()): temp_table = QTable() temp_table["linename"] = [temp_dict["linename"]] temp_table["rest"] = [temp_dict["rest"]*u.Unit(temp_dict["unit"])] temp_table["colors"] = [temp_dict["colors"]] temp_table = self._viewer.load_line_list(temp_table, return_table=True) # Add line to Custom lines in local list temp_dict["name_rest"] = str(temp_table[0]["name_rest"]) list_contents["Custom"]["lines"].append(temp_dict) self.list_contents = {} self.list_contents = list_contents self._viewer.plot_spectral_line(temp_dict["name_rest"]) self.update_line_mark_dict() lines_loaded_message = SnackbarMessage("Custom spectral line loaded", sender=self, color="success") self.hub.broadcast(lines_loaded_message)
[docs] def vue_show_all_in_list(self, listname): """ Toggle all lines in list to be visible """ lc = self.list_contents for line in lc[listname]["lines"]: line["show"] = True self._viewer.spectral_lines.loc[line["name_rest"]]["show"] = True self.list_contents = lc self.send_state('list_contents') self._viewer.plot_spectral_lines(global_redshift=self._global_redshift) self.update_line_mark_dict()
[docs] def vue_hide_all_in_list(self, listname): """ Toggle all lines in list to be hidden """ name_rests = [] for line in self.list_contents[listname]["lines"]: line["show"] = False name_rests.append(line["name_rest"]) self.send_state('list_contents') self._viewer.erase_spectral_lines(name_rest=name_rests) self.update_line_mark_dict()
[docs] def vue_plot_all_lines(self, event): """ Plot all the currently loaded lines in the viewer """ if self._viewer.spectral_lines is None: warn_message = SnackbarMessage("No spectral lines loaded to plot", sender=self, color="error") self.hub.broadcast(warn_message) return for listname in self.list_contents: for line in self.list_contents[listname]["lines"]: line["show"] = True self._viewer.spectral_lines["show"] = True self.send_state('list_contents') self._viewer.plot_spectral_lines(global_redshift=self._global_redshift) self.update_line_mark_dict()
[docs] def vue_erase_all_lines(self, event): """ Erase all lines from the viewer """ if self._viewer.spectral_lines is None: warn_message = SnackbarMessage("No spectral lines to erase", sender=self, color="error") self.hub.broadcast(warn_message) return for listname in self.list_contents: for line in self.list_contents[listname]["lines"]: line["show"] = False self.send_state('list_contents') self._viewer.erase_spectral_lines() self.update_line_mark_dict()
[docs] def vue_change_visible(self, data): """ Plot or erase a single line as needed when "Visible" checkbox is changed """ listname, line, line_ind = data name_rest = line["name_rest"] show = not line['show'] list_contents = self.list_contents list_contents[listname]['lines'][line_ind]['show'] = show if not show: # then make sure to also disable the identify flag list_contents[listname]['lines'][line_ind]['identify'] = False self.list_contents = {} self.list_contents = list_contents if show: self._viewer.plot_spectral_line(name_rest, global_redshift=self._global_redshift) else: self._viewer.erase_spectral_lines(name_rest=name_rest) self.update_line_mark_dict()
def _update_identify_to_line(self, name_rest, listname=None, identify=True): list_contents = self.list_contents for this_listname, this_list in list_contents.items(): for i, line in enumerate(this_list['lines']): if ((this_listname == listname or listname is None) and line['name_rest'] == name_rest): list_contents[this_listname]['lines'][i]['identify'] = identify else: list_contents[this_listname]['lines'][i]['identify'] = False self.list_contents = {} self.list_contents = list_contents self.identify_label = name_rest if identify else "" def _process_identify_change(self, msg): if msg.sender == self: return # event from some other plugin (LineAnalysis, for example) requesting a change # in the identified line self._update_identify_to_line(msg.name_rest) # then line mark themselves will also respond to the same event, so there is # no need to broadcast another
[docs] def vue_set_identify(self, data=None): """ Set the selected line as "identified" """ if data is None: # then default to the currently identified (which will unidentify it) for listname, this_list in self.list_contents.items(): for line_ind, line in enumerate(this_list['lines']): if line['identify']: return self.vue_set_identify((listname, line, line_ind)) listname, line, line_ind = data identify = not line.get('identify', False) if identify and not line['show']: # first show the line self.vue_change_visible(data) self._update_identify_to_line(name_rest=line['name_rest'], listname=listname, identify=identify) # broadcast and event to update the marks msg = LineIdentifyMessage(name_rest=line['name_rest'] if identify else '', sender=self) self.hub.broadcast(msg)
[docs] def vue_set_color(self, data): """ Change the color either of all members of a line list, or of an individual line. """ color = data['color'] if "listname" in data: listname = data["listname"] self.list_contents[listname]["color"] = color for line in self.list_contents[listname]["lines"]: line["colors"] = color # Update the astropy table entry name_rest = line["name_rest"] self._viewer.spectral_lines.loc[name_rest]["colors"] = color # Update the color on the plot if name_rest in self.line_mark_dict: self.line_mark_dict[name_rest].colors = [color] self.send_state('list_contents') elif "linename" in data: pass
[docs] def vue_remove_list(self, listname): """ Method to remove line list from available expansion panels when the x on the panel header is clicked. Also removes line marks from plot and updates the "show" value in the astropy table to False. """ lc = self.list_contents[listname] name_rests = [] for line in lc["lines"]: name_rests.append(self.vue_remove_line(line, erase=False)) self._viewer.erase_spectral_lines(name_rest=name_rests) self.update_line_mark_dict() self.loaded_lists = [x for x in self.loaded_lists if x != listname] self.list_contents = {k: v for k, v in self.list_contents.items() if k != listname} row_inds = [i for i, ln in enumerate(self._viewer.spectral_lines['listname']) if ln != listname] if len(row_inds) == 0: self._viewer.spectral_lines = None else: self._viewer.spectral_lines = self._viewer.spectral_lines[row_inds]
[docs] def vue_remove_line(self, line, erase=True): """ Method to remove a line from the plot when the line is deselected in the expansion panel content. Input must have "linename" and "rest" values for indexing on the astropy table. """ name_rest = line["name_rest"] # Keep in our spectral line astropy table, but set it to not show on plot self._viewer.spectral_lines.loc[name_rest]["show"] = False # Remove the line from the plot marks if erase: try: self._viewer.erase_spectral_lines(name_rest=name_rest) del self.line_mark_dict[name_rest] except KeyError: raise KeyError("line marks: {}".format(self._viewer.figure.marks)) else: return name_rest