class LayerWidget(QtGui.QWidget): layer = CurrentComboDataProperty('ui.combo_active_layer') def __init__(self, parent=None, data_viewer=None): super(LayerWidget, self).__init__(parent=parent) self.ui = load_ui('layer_widget.ui', self, directory=os.path.dirname(__file__)) self._layers = [] def __contains__(self, layer): return layer in self._layers def add_layer(self, layer): self._layers.append(layer) self._update_combobox() def remove_layer(self, layer): self._layers.remove(layer) self._update_combobox() def _update_combobox(self): labeldata = [(layer.label, layer) for layer in self._layers] update_combobox(self.ui.combo_active_layer, labeldata)
class OptionsWidget(QWidget): file_att = CurrentComboDataProperty('ui.combo_file_attribute') def __init__(self, parent=None, data_viewer=None): super(OptionsWidget, self).__init__(parent=parent) self.ui = load_ui('viewer_options.ui', self, directory=os.path.dirname(__file__)) self.file_helper = ComponentIDComboHelper(self.ui.combo_file_attribute, data_viewer._data) self._data_viewer = data_viewer self._data = None def set_data(self, data): self.file_helper.clear() if isinstance(data, Subset): self.file_helper.append_data(data.data) else: self.file_helper.append_data(data)
class BaseDataComboHelper(HubListener): """ This is a base class for helpers for combo boxes that need to show a list of data objects. Parameters ---------- data_combo : Qt combo widget The Qt widget for the data combo box """ _data = CurrentComboDataProperty('_data_combo') def __init__(self, data_combo): super(BaseDataComboHelper, self).__init__() self._data_combo = data_combo self._component_id_helpers = [] self._data_combo.currentIndexChanged.connect( self.refresh_component_ids) def refresh(self): label_data = [(data.label, data) for data in self._datasets] update_combobox(self._data_combo, label_data) self.refresh_component_ids() def refresh_component_ids(self): for helper in self._component_id_helpers: helper.clear() if self._data is not None: helper.append_data(self._data) helper.refresh() def add_component_id_combo(self, combo): helper = ComponentIDComboHelper(combo) self._component_id_helpers.append_data(helper) if self._data is not None: helper.append_data(self._data) @property def hub(self): return self._hub @hub.setter def hub(self, value): self._hub = value if value is not None: self.register_to_hub(value) def register_to_hub(self, hub): pass
class AttributeLimitsHelper(object): """ This class is a helper for attribute-dependent min/max level values. Given an attribute combo as well as line edit widgets for the min/max values, this helper takes care of populating the attribute combo, setting the initial values of the min/max values, and keeping a cache of the min/max values as a function of attribute. This means that if the user edits the min/max values and then changes attribute then changes back, the original min/max values will be retained. In addition, this helper class can optionally link a combo for the scale mode, for example using the min/max values or percentile values, as well as a button for flipping the min/max values. Parameters ---------- attribute_combo : ``QComboBox`` instance The attribute combo - this will be populated once a dataset is assigned to the helper. lower_value, upper_value : ``QLineEdit`` instances The fields for the lower/upper levels mode_combo : ``QComboBox`` instance, optional The scale mode combo - this will be populated by presets such as Min/Max, various percentile levels, and Custom. flip_button : ``QToolButton`` instance, optional The flip button log_button : ``QToolButton`` instance, optional A button indicating whether the attribute should be shown in log space data : :class:`glue.core.data.Data` The dataset to attach to the helper - this will be used to populate the attribute combo as well as determine the limits automatically given the scale mode preset. Notes ----- Once the helper is instantiated, the data associated with the helper can be set/changed with: >>> helper = AttributeLimitsHelper(...) >>> helper.data = data The data can also be passed to the initializer as described in the list of parameters above. """ component_data = CurrentComboDataProperty('component_id_combo') scale_mode = CurrentComboTextProperty('mode_combo') percentile = CurrentComboDataProperty('mode_combo') vlo = FloatLineProperty('lower_value') vhi = FloatLineProperty('upper_value') vlog = ButtonProperty('log_button') def __init__(self, attribute_combo, lower_value, upper_value, mode_combo=None, flip_button=None, log_button=None, data=None, limits_cache=None): self.component_id_combo = attribute_combo self.mode_combo = mode_combo self.lower_value = lower_value self.upper_value = upper_value self.flip_button = flip_button self.log_button = log_button self.component_id_combo.currentIndexChanged.connect( self._update_limits) self.lower_value.editingFinished.connect(self._manual_edit) self.upper_value.editingFinished.connect(self._manual_edit) if self.log_button is None: self.log_button = QtWidgets.QToolButton() self.log_button.toggled.connect(self._manual_edit) if self.mode_combo is None: # Make hidden combo box to avoid having to always figure out if the # combo mode exists. This will then always be set to Min/Max. self.mode_combo = QtWidgets.QComboBox() self._setup_mode_combo() self.mode_combo.currentIndexChanged.connect(self._update_mode) if self.flip_button is not None: self.flip_button.clicked.connect(self._flip_limits) if limits_cache is None: limits_cache = {} self._limits = limits_cache self._callbacks = [] def set_limits(self, vlo, vhi): self.lower_value.blockSignals(True) self.upper_value.blockSignals(True) self.vlo = vlo self.vhi = vhi self.lower_value.blockSignals(False) self.upper_value.blockSignals(False) self.lower_value.editingFinished.emit() self.upper_value.editingFinished.emit() def _setup_mode_combo(self): self.mode_combo.clear() self.mode_combo.addItem("Min/Max", userData=100) self.mode_combo.addItem("99.5%", userData=99.5) self.mode_combo.addItem("99%", userData=99) self.mode_combo.addItem("95%", userData=95) self.mode_combo.addItem("90%", userData=90) self.mode_combo.addItem("Custom", userData=None) self.mode_combo.setCurrentIndex(-1) def _flip_limits(self): self.set_limits(self.vhi, self.vlo) def _manual_edit(self): self._cache_limits() def _update_mode(self): if self.scale_mode != 'Custom': self._auto_limits() self._cache_limits() def _invalidate_cache(self): self._limits.clear() def _cache_limits(self): self._limits[ self.component_id] = self.scale_mode, self.vlo, self.vhi, self.vlog def _update_limits(self): if self.component_id in self._limits: self.scale_mode, lower, upper, self.vlog = self._limits[ self.component_id] self.set_limits(lower, upper) else: self.mode_combo.blockSignals(True) self.scale_mode = 'Min/Max' self.mode_combo.blockSignals(False) self._auto_limits() self.vlog = False @property def component_id(self): if self.component_data is not None: return self.component_data else: return None @property def data(self): if self.component_data is not None: return self.component_data.parent else: return None def _auto_limits(self): if self.component_data is None: return exclude = (100 - self.percentile) / 2. # For subsets in 'data' mode, we want to compute the limits based on # the full dataset, not just the subset. if isinstance(self.data, Subset): data_values = self.data.data[self.component_id] else: data_values = self.data[self.component_id] try: lower = np.nanpercentile(data_values, exclude) upper = np.nanpercentile(data_values, 100 - exclude) except AttributeError: # Numpy < 1.9 data_values = data_values[~np.isnan(data_values)] lower = np.percentile(data_values, exclude) upper = np.percentile(data_values, 100 - exclude) if isinstance(self.data, Subset): lower = 0 self.set_limits(lower, upper)
class WWTOptionPanel(QtWidgets.QWidget): ra_att = CurrentComboDataProperty('ui.combo_ra_att') dec_att = CurrentComboDataProperty('ui.combo_dec_att') background = CurrentComboDataProperty('ui.combo_background') opacity = ValueProperty('ui.value_opacity') foreground = CurrentComboDataProperty('ui.combo_foreground') galactic_plane = ButtonProperty('ui.checkbox_galactic_plane') def __init__(self, viewer, parent=None): super(WWTOptionPanel, self).__init__(parent=parent) self.viewer = viewer self.ui = load_ui('options_widget.ui', self, directory=os.path.dirname(__file__)) self._setup_combos() self._connect() @property def ra(self): if self.ra_att is None: return None else: return self.ra_att[0] @property def dec(self): if self.dec_att is None: return None else: return self.dec_att[0] def _setup_combos(self): layers = [ 'Digitized Sky Survey (Color)', 'VLSS: VLA Low-frequency Sky Survey (Radio)', 'WMAP ILC 5-Year Cosmic Microwave Background', 'SFD Dust Map (Infrared)', 'WISE All Sky (Infrared)', 'GLIMPSE/MIPSGAL', 'Hydrogen Alpha Full Sky Map' ] labels = ['DSS', 'VLSS', 'WMAP', 'SFD', 'WISE', 'GLIMPSE', 'H Alpha'] thumbnails = [ 'DSS', 'VLA', 'wmap5yr_ilc_200uk', 'dust', 'glimpsemipsgaltn', 'halpha' ] base = ('http://www.worldwidetelescope.org/wwtweb/' 'thumbnail.aspx?name=%s') for i, row in enumerate(zip(layers, labels, thumbnails)): layer, text, thumb = row url = base % thumb data = urlopen(url).read() pm = QtGui.QPixmap() pm.loadFromData(data) icon = QtGui.QIcon(pm) self.ui.combo_foreground.addItem(icon, text, layer) self.ui.combo_foreground.setItemData(i, layer, role=Qt.ToolTipRole) self.ui.combo_background.addItem(icon, text, layer) self.ui.combo_background.setItemData(i, layer, role=Qt.ToolTipRole) self.ui.combo_foreground.setIconSize(QtCore.QSize(60, 60)) self.ui.combo_background.setIconSize(QtCore.QSize(60, 60)) self.ra_att_helper = ComponentIDComboHelper(self.ui.combo_ra_att, self.viewer._data, categorical=False, numeric=True) self.dec_att_helper = ComponentIDComboHelper(self.ui.combo_dec_att, self.viewer._data, categorical=False, numeric=True) def add_data(self, data): # TODO: the following logic should go in the component ID helpers. It # isn't quite right at the moment because if there are multiple # datasets/subsets with the same components, we only want to show those # once. if isinstance(data, Subset): self.ra_att_helper.append(data.data) self.dec_att_helper.append(data.data) else: self.ra_att_helper.append(data) self.dec_att_helper.append(data) def remove_data(self, data): if isinstance(data, Subset): self.ra_att_helper.remove(data.data) self.dec_att_helper.remove(data.data) else: self.ra_att_helper.remove(data) self.dec_att_helper.remove(data) def _connect(self): self.ui.combo_ra_att.currentIndexChanged.connect( self.viewer._update_all) self.ui.combo_dec_att.currentIndexChanged.connect( self.viewer._update_all) self.ui.combo_foreground.currentIndexChanged.connect( self.viewer._update_foreground) self.ui.combo_background.currentIndexChanged.connect( self.viewer._update_background) self.ui.value_opacity.valueChanged.connect(self.viewer._update_opacity) self.ui.checkbox_galactic_plane.toggled.connect( self.viewer._update_galactic_plane_mode) self.opacity = 100
class LinkEquation(QtWidgets.QWidget): """ Interactively define ComponentLinks from existing functions This widget inspects the calling signatures of helper functions, and presents the user with an interface for assigning componentIDs to the input and output arguments. It also generates ComponentLinks from this information. ComponentIDs are assigned to arguments via drag and drop. This widget is used within the LinkEditor dialog Usage:: widget = LinkEquation() """ category = CurrentComboTextProperty('_ui.category') function = CurrentComboDataProperty('_ui.function') def __init__(self, parent=None): super(LinkEquation, self).__init__(parent) # Set up mapping of function/helper name -> function/helper tuple. For the helpers, we use the 'display' name if available. self._argument_widgets = [] self._output_widget = ArgumentWidget("") # pyqt4 can't take self as second argument here # for some reason. Manually embed self._ui = load_ui('link_equation.ui', None, directory=os.path.dirname(__file__)) l = QtWidgets.QHBoxLayout() l.addWidget(self._ui) self.setLayout(l) self._ui.outputs_layout.addWidget(self._output_widget) self._populate_category_combo() self.category = 'General' self._populate_function_combo() self._connect() self._setup_editor() def set_result_visible(self, state): self._output_widget.setVisible(state) self._ui.output_label.setVisible(state) def is_helper(self): return self.function is not None and \ type(self.function).__name__ == 'LinkHelper' def is_function(self): return self.function is not None and \ type(self.function).__name__ == 'LinkFunction' @property def signature(self): """ Returns the ComponentIDs assigned to the input and output arguments :rtype: tuple of (input, output). Input is a list of ComponentIDs. output is a ComponentID """ inp = [a.component_id for a in self._argument_widgets] out = self._output_widget.component_id return inp, out @signature.setter def signature(self, inout): inp, out = inout for i, a in zip(inp, self._argument_widgets): a.component_id = i self._output_widget.component_id = out @messagebox_on_error("Failed to create links") def links(self): """ Create ComponentLinks from the state of the widget :rtype: list of ComponentLinks that can be created. If no links can be created (e.g. because of missing input), the empty list is returned """ inp, out = self.signature if self.is_function(): using = self.function.function if not all(inp) or not out: return [] link = core.component_link.ComponentLink(inp, out, using) return [link] if self.is_helper(): helper = self.function.helper if not all(inp): return [] return helper(*inp) def _update_add_enabled(self): state = True for a in self._argument_widgets: state = state and a.component_id is not None if self.is_function(): state = state and self._output_widget.component_id is not None def _connect(self): signal = self._ui.function.currentIndexChanged signal.connect(nonpartial(self._setup_editor)) signal.connect(nonpartial(self._update_add_enabled)) self._output_widget.editor.textChanged.connect( nonpartial(self._update_add_enabled)) self._ui.category.currentIndexChanged.connect( self._populate_function_combo) def clear_inputs(self): for w in self._argument_widgets: w.clear() self._output_widget.clear() def _setup_editor(self): if self.is_function(): self._setup_editor_function() else: self._setup_editor_helper() def _setup_editor_function(self): """ Prepare the widget for the active function.""" assert self.is_function() self.set_result_visible(True) func = self.function.function args = getfullargspec(func)[0] label = function_label(self.function) self._ui.info.setText(label) self._output_widget.label = self.function.output_labels[0] self._clear_input_canvas() for a in args: self._add_argument_widget(a) def _setup_editor_helper(self): """Setup the editor for the selected link helper""" assert self.is_helper() self.set_result_visible(False) label = helper_label(self.function) args = self.function.input_labels self._ui.info.setText(label) self._clear_input_canvas() for a in args: self._add_argument_widget(a) def _add_argument_widget(self, argument): """ Create and add a single argument widget to the input canvas :param arguement: The argument name (string) """ widget = ArgumentWidget(argument) widget.editor.textChanged.connect(nonpartial(self._update_add_enabled)) self._ui.inputs_layout.addWidget(widget) self._argument_widgets.append(widget) def _clear_input_canvas(self): """ Remove all widgets from the input canvas """ layout = self._ui.inputs_layout for a in self._argument_widgets: layout.removeWidget(a) a.close() self._argument_widgets = [] def _populate_category_combo(self): f = [f for f in link_function.members if len(f.output_labels) == 1] categories = sorted(set(l.category for l in f + link_helper.members)) update_combobox(self._ui.category, list(zip(categories, categories))) def _populate_function_combo(self): """ Add name of functions to function combo box """ f = [f for f in link_function.members if len(f.output_labels) == 1] functions = ((get_function_name(l[0]), l) for l in f + link_helper.members if l.category == self.category) update_combobox(self._ui.function, functions)