Ejemplo n.º 1
0
class BoolElement(FormElement):
    """
    A checkbox representing a boolean setting

    The shorthand notation is True or False::

        e = FormElement.auto(False)
    """
    state = ButtonProperty('ui')

    @classmethod
    def recognizes(cls, params):
        return isinstance(params, bool)

    def _build_ui(self):
        w = QtWidgets.QCheckBox()
        w.setChecked(self.params)
        w.toggled.connect(nonpartial(self.changed))
        return w

    def value(self, layer=None, view=None):
        return self.ui.isChecked()
Ejemplo n.º 2
0
class PreferencesDialog(QtWidgets.QDialog):

    theme = CurrentComboTextProperty('ui.combo_theme')
    background = ColorProperty('ui.color_background')
    foreground = ColorProperty('ui.color_foreground')
    data_color = ColorProperty('ui.color_default_data')
    data_alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1))
    data_apply = ButtonProperty('ui.checkbox_apply')
    save_to_disk = ButtonProperty('ui.checkbox_save')
    font_size = ValueProperty('ui.spinner_font_size')

    def __init__(self, application, parent=None):

        super(PreferencesDialog, self).__init__(parent=parent)

        self._app = weakref.ref(application)

        self.ui = load_ui('preferences.ui', self,
                          directory=os.path.dirname(__file__))

        self.ui.cancel.clicked.connect(self.reject)
        self.ui.ok.clicked.connect(self.accept)

        self.ui.combo_theme.currentIndexChanged.connect(self._update_colors_from_theme)

        self.ui.button_reset_dialogs.clicked.connect(self._reset_dialogs)

        # The following is needed because of a bug in Qt which means that
        # tab titles don't get scaled right.
        if platform.system() == 'Darwin':
            app = get_qapp()
            app_font = app.font()
            self.ui.tab_widget.setStyleSheet('font-size: {0}px'.format(app_font.pointSize()))

        from glue.config import settings
        self.background = settings.BACKGROUND_COLOR
        self.foreground = settings.FOREGROUND_COLOR
        self.data_color = settings.DATA_COLOR
        self.data_alpha = settings.DATA_ALPHA
        self.font_size = settings.FONT_SIZE

        self._update_theme_from_colors()

        self.panes = []

        from glue.config import preference_panes
        for label, widget_cls in sorted(preference_panes):
            pane = widget_cls()
            self.ui.tab_widget.addTab(pane, label)
            self.panes.append(pane)

    def _update_theme_from_colors(self, *args):
        if (rgb(self.background) == (1, 1, 1) and rgb(self.foreground) == (0, 0, 0) and
                rgb(self.data_color) == (0.35, 0.35, 0.35) and np.allclose(self.data_alpha, 0.8)):
            self.theme = 'Black on White'
        elif (rgb(self.background) == (0, 0, 0) and rgb(self.foreground) == (1, 1, 1) and
                rgb(self.data_color) == (0.75, 0.75, 0.75) and np.allclose(self.data_alpha, 0.8)):
            self.theme = 'White on Black'
        else:
            self.theme = 'Custom'

    def _update_colors_from_theme(self, *args):
        if self.theme == 'Black on White':
            self.foreground = 'black'
            self.background = 'white'
            self.data_color = '0.35'
            self.data_alpha = 0.8
        elif self.theme == 'White on Black':
            self.foreground = 'white'
            self.background = 'black'
            self.data_color = '0.75'
            self.data_alpha = 0.8
        elif self.theme != 'Custom':
            raise ValueError("Unknown theme: {0}".format(self.theme))

    def _reset_dialogs(self, *args):
        from glue.config import settings
        for key, _, _ in settings:
            if key.lower().startswith(('show_info', 'show_warn', 'show_large')):
                setattr(settings, key, True)

    def accept(self):

        # Update default settings

        from glue.config import settings
        settings.FOREGROUND_COLOR = self.foreground
        settings.BACKGROUND_COLOR = self.background
        settings.DATA_COLOR = self.data_color
        settings.DATA_ALPHA = self.data_alpha
        settings.FONT_SIZE = self.font_size

        for pane in self.panes:
            pane.finalize()

        # Save to disk if requested
        if self.save_to_disk:
            save_settings()
        else:
            settings._save_to_disk = True

        # Trigger viewers to update defaults

        app = self._app()

        if app is not None:
            app._hub.broadcast(SettingsChangeMessage(self, ('FOREGROUND_COLOR', 'BACKGROUND_COLOR', 'FONT_SIZE')))
            if self.data_apply:  # If requested, trigger data to update color
                app.set_data_color(settings.DATA_COLOR, settings.DATA_ALPHA)

        super(PreferencesDialog, self).accept()
Ejemplo n.º 3
0
class HistogramWidget(DataViewer):
    LABEL = "Histogram"
    _property_set = DataViewer._property_set + \
        'component xlog ylog normed cumulative autoscale xmin xmax nbins'.split(
        )

    xmin = FloatLineProperty('ui.xmin', 'Minimum value')
    xmax = FloatLineProperty('ui.xmax', 'Maximum value')
    normed = ButtonProperty('ui.normalized_box', 'Normalized?')
    autoscale = ButtonProperty('ui.autoscale_box',
                               'Autoscale view to histogram?')
    cumulative = ButtonProperty('ui.cumulative_box', 'Cumulative?')
    nbins = ValueProperty('ui.binSpinBox', 'Number of bins')
    xlog = ButtonProperty('ui.xlog_box', 'Log-scale the x axis?')
    ylog = ButtonProperty('ui.ylog_box', 'Log-scale the y axis?')

    def __init__(self, session, parent=None):
        super(HistogramWidget, self).__init__(session, parent)

        self.central_widget = MplWidget()
        self.setCentralWidget(self.central_widget)
        self.option_widget = QtGui.QWidget()
        self.ui = load_ui('options_widget.ui', self.option_widget,
                          directory=os.path.dirname(__file__))
        self._tweak_geometry()
        self.client = HistogramClient(self._data,
                                      self.central_widget.canvas.fig,
                                      layer_artist_container=self._layer_artist_container)
        self._init_limits()
        self.make_toolbar()
        self._connect()
        # maps _hash(componentID) -> componentID
        self._component_hashes = {}

    @staticmethod
    def _get_default_tools():
        return []

    def _init_limits(self):
        validator = QtGui.QDoubleValidator(None)
        validator.setDecimals(7)
        self.ui.xmin.setValidator(validator)
        self.ui.xmax.setValidator(validator)
        lo, hi = self.client.xlimits
        self.ui.xmin.setText(str(lo))
        self.ui.xmax.setText(str(hi))

    def _tweak_geometry(self):
        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

    def _connect(self):

        ui = self.ui
        cl = self.client

        ui.attributeCombo.currentIndexChanged.connect(self._set_attribute_from_combo)

        ui.normalized_box.toggled.connect(partial(setattr, cl, 'normed'))
        ui.autoscale_box.toggled.connect(partial(setattr, cl, 'autoscale'))
        ui.cumulative_box.toggled.connect(partial(setattr, cl, 'cumulative'))

        connect_int_spin(cl, 'nbins', ui.binSpinBox)
        connect_float_edit(cl, 'xmin', ui.xmin)
        connect_float_edit(cl, 'xmax', ui.xmax)
        connect_bool_button(cl, 'xlog', ui.xlog_box)
        connect_bool_button(cl, 'ylog', ui.ylog_box)

    def make_toolbar(self):
        result = GlueToolbar(self.central_widget.canvas, self,
                             name='Histogram')
        for mode in self._mouse_modes():
            result.add_mode(mode)
        self.addToolBar(result)
        return result

    def _mouse_modes(self):
        axes = self.client.axes

        def apply_mode(mode):
            return self.apply_roi(mode.roi())

        rect = HRangeMode(axes, roi_callback=apply_mode)
        return [rect]

    @defer_draw
    def _update_attributes(self):
        """Repopulate the combo box that selects the quantity to plot"""
        combo = self.ui.attributeCombo
        component = self.component
        new = self.client.component or component

        combo.blockSignals(True)
        combo.clear()

        # implementation note:
        # PySide doesn't robustly store python objects with setData
        # use _hash(x) instead
        model = QtGui.QStandardItemModel()
        data_ids = set(_hash(d) for d in self._data)
        self._component_hashes = dict((_hash(c), c) for d in self._data
                                      for c in d.components)

        found = False
        for d in self._data:
            if d not in self._layer_artist_container:
                continue
            item = QtGui.QStandardItem(d.label)
            item.setData(_hash(d), role=Qt.UserRole)
            assert item.data(Qt.UserRole) == _hash(d)
            item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
            model.appendRow(item)
            for c in d.visible_components:
                if (not d.get_component(c).categorical and
                        not d.get_component(c).numeric):
                    continue
                if c is new:
                    found = True
                item = QtGui.QStandardItem(c.label)
                item.setData(_hash(c), role=Qt.UserRole)
                model.appendRow(item)
        combo.setModel(model)

        # separators below data items
        for i in range(combo.count()):
            if combo.itemData(i) in data_ids:
                combo.insertSeparator(i + 1)

        combo.blockSignals(False)

        if found:
            self.component = new
        else:
            combo.setCurrentIndex(2)  # skip first data + separator
        self._set_attribute_from_combo()

    @property
    def component(self):
        combo = self.ui.attributeCombo
        index = combo.currentIndex()
        return self._component_hashes.get(combo.itemData(index), None)

    @component.setter
    def component(self, component):
        combo = self.ui.attributeCombo
        if combo.count() == 0:  # cold start problem, when restoring
            self._update_attributes()

        # combo.findData doesn't seem to work robustly
        for i in range(combo.count()):
            data = combo.itemData(i)
            if data == _hash(component):
                combo.setCurrentIndex(i)
                return
        raise IndexError("Component not present: %s" % component)

    @defer_draw
    def _set_attribute_from_combo(self, *args):
        if self.component is not None:
            for d in self._data:
                try:
                    component = d.get_component(self.component)
                except:
                    continue
                else:
                    break
            if component.categorical:
                if self.ui.xlog_box.isEnabled():
                    self.ui.xlog_box.setEnabled(False)
                    self.xlog = False
            else:
                if not self.ui.xlog_box.isEnabled():
                    self.ui.xlog_box.setEnabled(True)
        self.client.set_component(self.component)
        self.update_window_title()

    @defer_draw
    def add_data(self, data):
        """ Add data item to combo box.
        If first addition, also update attributes """

        if self.data_present(data):
            return True

        if data.size > WARN_SLOW and not self._confirm_large_data(data):
            return False

        self.client.add_layer(data)
        self._update_attributes()

        return True

    def add_subset(self, subset):
        pass

    def _remove_data(self, data):
        """ Remove data item from the combo box """
        pass

    def data_present(self, data):
        return data in self._layer_artist_container

    def register_to_hub(self, hub):
        super(HistogramWidget, self).register_to_hub(hub)
        self.client.register_to_hub(hub)

        hub.subscribe(self,
                      msg.DataCollectionDeleteMessage,
                      handler=lambda x: self._remove_data(x.data))

        hub.subscribe(self,
                      msg.DataUpdateMessage,
                      handler=lambda *args: self._update_labels())

        hub.subscribe(self,
                      msg.ComponentsChangedMessage,
                      handler=lambda x: self._update_attributes())

    def unregister(self, hub):
        super(HistogramWidget, self).unregister(hub)
        self.client.unregister(hub)
        hub.unsubscribe_all(self)

    @property
    def window_title(self):
        c = self.client.component
        if c is not None:
            label = str(c.label)
        else:
            label = 'Histogram'
        return label

    def _update_labels(self):
        self.update_window_title()
        self._update_attributes()

    def __str__(self):
        return "Histogram Widget"

    def options_widget(self):
        return self.option_widget
Ejemplo n.º 4
0
class DendroWidget(DataViewer):
    """
    An interactive dendrogram display
    """

    LABEL = 'Dendrogram'

    _property_set = DataViewer._property_set + \
        'ylog height parent order'.split()

    ylog = ButtonProperty('ui.ylog', 'log scaling on y axis?')
    height = CurrentComboProperty('ui.heightCombo', 'height attribute')
    parent = CurrentComboProperty('ui.parentCombo', 'parent attribute')
    order = CurrentComboProperty('ui.orderCombo', 'layout sorter attribute')

    _toolbar_cls = MatplotlibViewerToolbar
    tools = ['Pick']

    def __init__(self, session, parent=None):
        super(DendroWidget, self).__init__(session, parent)

        self.central_widget = MplWidget()
        self.option_widget = QtWidgets.QWidget()
        self.setCentralWidget(self.central_widget)

        self.ui = load_ui('options_widget.ui',
                          self.option_widget,
                          directory=os.path.dirname(__file__))
        self.client = DendroClient(
            self._data,
            self.central_widget.canvas.fig,
            layer_artist_container=self._layer_artist_container)

        self._connect()

        self.initialize_toolbar()
        self.statusBar().setSizeGripEnabled(False)

    def _connect(self):
        ui = self.ui
        cl = self.client

        connect_bool_button(cl, 'ylog', ui.ylog)
        connect_current_combo(cl, 'parent_attr', ui.parentCombo)
        connect_current_combo(cl, 'height_attr', ui.heightCombo)
        connect_current_combo(cl, 'order_attr', ui.orderCombo)

    def initialize_toolbar(self):

        super(DendroWidget, self).initialize_toolbar()

        def on_move(mode):
            if mode._drag:
                self.client.apply_roi(mode.roi())

        self.toolbar.tools['Pick']._move_callback = on_move

    def apply_roi(self, roi):
        self.client.apply_roi(roi)

    def _update_combos(self, data=None):
        data = data or self.client.display_data
        if data is None:
            return

        for combo in [
                self.ui.heightCombo, self.ui.parentCombo, self.ui.orderCombo
        ]:
            combo.blockSignals(True)
            ids = []
            idx = combo.currentIndex()
            old = combo.itemData(idx) if idx > 0 else None
            combo.clear()
            for cid in data.components:
                if cid.hidden and cid is not data.pixel_component_ids[0]:
                    continue
                combo.addItem(cid.label, userData=cid)
                ids.append(cid)
            try:
                combo.setCurrentIndex(ids.index(old))
            except ValueError:
                combo.setCurrentIndex(0)

            combo.blockSignals(False)

    def add_data(self, data):
        """Add a new data set to the widget

        :returns: True if the addition was expected, False otherwise
        """
        if data in self.client:
            return

        self._update_combos(data)
        self.client.add_layer(data)
        return True

    def add_subset(self, subset):
        """Add a subset to the widget

        :returns: True if the addition was accepted, False otherwise
        """
        self.add_data(subset.data)
        if subset.data in self.client:
            self.client.add_layer(subset)
            return True

    def register_to_hub(self, hub):
        super(DendroWidget, self).register_to_hub(hub)
        self.client.register_to_hub(hub)
        hub.subscribe(self, core.message.ComponentsChangedMessage,
                      nonpartial(self._update_combos()))

    def unregister(self, hub):
        super(DendroWidget, self).unregister(hub)
        hub.unsubscribe_all(self.client)
        hub.unsubscribe_all(self)

    def options_widget(self):
        return self.option_widget

    @defer_draw
    def restore_layers(self, rec, context):

        from glue.core.callback_property import delay_callback

        with delay_callback(self.client, 'height_attr', 'parent_attr',
                            'order_attr'):
            self.client.restore_layers(rec, context)
            self._update_combos()
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)
Ejemplo n.º 6
0
class QtPlotlyExporter(QtWidgets.QDialog):

    save_settings = ButtonProperty('checkbox_save')
    username = TextProperty('text_username')
    api_key = TextProperty('text_api_key')
    title = TextProperty('text_title')
    legend = ButtonProperty('checkbox_legend')

    def __init__(self, plotly_args=[], plotly_kwargs={}, parent=None):

        super(QtPlotlyExporter, self).__init__(parent=parent)

        self.plotly_args = plotly_args
        self.plotly_kwargs = plotly_kwargs

        self.ui = load_ui('exporter.ui', self,
                          directory=os.path.dirname(__file__))

        self.button_cancel.clicked.connect(self.reject)
        self.button_export.clicked.connect(self.accept)

        # Set up radio button groups

        self._radio_account = QtWidgets.QButtonGroup()
        self._radio_account.addButton(self.ui.radio_account_glue)
        self._radio_account.addButton(self.ui.radio_account_config)
        self._radio_account.addButton(self.ui.radio_account_manual)

        self._radio_sharing = QtWidgets.QButtonGroup()
        self._radio_sharing.addButton(self.ui.radio_sharing_public)
        self._radio_sharing.addButton(self.ui.radio_sharing_secret)
        self._radio_sharing.addButton(self.ui.radio_sharing_private)

        # Find out stored credentials (note that this will create the
        # credentials file if it doesn't already exist)

        from plotly import plotly

        credentials = plotly.get_credentials()
        config_available = credentials['username'] != "" and credentials['api_key'] != ""

        if config_available:
            self.ui.radio_account_config.setChecked(True)
            label = self.ui.radio_account_config.text()
            self.ui.radio_account_config.setText(label + " (username: {0})".format(credentials['username']))
        else:
            self.ui.radio_account_glue.setChecked(True)
            self.ui.radio_account_config.setEnabled(False)

        self.ui.radio_sharing_secret.setChecked(True)

        self.ui.text_username.textChanged.connect(nonpartial(self._set_manual_mode))
        self.ui.text_api_key.textChanged.connect(nonpartial(self._set_manual_mode))
        self.ui.radio_account_glue.toggled.connect(nonpartial(self._set_allowed_sharing_modes))

        self.set_status('', color='black')

        self._set_allowed_sharing_modes()

    def _set_manual_mode(self):
        self.ui.radio_account_manual.setChecked(True)

    def _set_allowed_sharing_modes(self):
        if self.ui.radio_account_glue.isChecked():
            self.ui.radio_sharing_public.setChecked(True)
            self.ui.radio_sharing_secret.setEnabled(False)
            self.ui.radio_sharing_private.setEnabled(False)
        else:
            self.ui.radio_sharing_secret.setEnabled(True)
            self.ui.radio_sharing_private.setEnabled(True)
        QtWidgets.QApplication.instance().processEvents()

    def accept(self):

        # In future we might be able to use more fine-grained exceptions
        # https://github.com/plotly/plotly.py/issues/524

        self.set_status('Signing in and plotting...', color='blue')

        auth = {}

        if self.ui.radio_account_glue.isChecked():
            auth['username'] = '******'
            auth['api_key'] = 't24aweai14'
        elif self.ui.radio_account_config.isChecked():
            auth['username'] = ''
            auth['api_key'] = ''
        else:
            if self.username == "":
                self.set_status("Username not set", color='red')
                return
            elif self.api_key == "":
                self.set_status("API key not set", color='red')
                return
            else:
                auth['username'] = self.username
                auth['api_key'] = self.api_key

        from plotly import plotly
        from plotly.exceptions import PlotlyError
        from plotly.tools import set_credentials_file

        if self.ui.radio_sharing_public.isChecked():
            self.plotly_kwargs['sharing'] = 'public'
        elif self.ui.radio_sharing_secret.isChecked():
            self.plotly_kwargs['sharing'] = 'secret'
        else:
            self.plotly_kwargs['sharing'] = 'private'

        # We need to fix URLs, so we can't let plotly open it yet
        # https://github.com/plotly/plotly.py/issues/526
        self.plotly_kwargs['auto_open'] = False

        # Get title and legend preferences from the window
        self.plotly_args[0]['layout']['showlegend'] = self.legend
        self.plotly_args[0]['layout']['title'] = self.title

        try:
            plotly.sign_in(auth['username'], auth['api_key'])
            plotly_url = plotly.plot(*self.plotly_args, **self.plotly_kwargs)
        except PlotlyError as exc:
            print("Plotly exception:")
            print('-' * 60)
            traceback.print_exc(file=sys.stdout)
            print('-' * 60)
            if ('the supplied API key doesn\'t match our records' in exc.args[0] or
                    'Sign in failed' in exc.args[0]):
                username = auth['username'] or plotly.get_credentials()['username']
                self.set_status("Authentication failed".format(username), color='red')
            elif "filled its quota of private files" in exc.args[0]:
                self.set_status("Maximum number of private plots reached", color='red')
            else:
                self.set_status("An unexpected error occurred", color='red')
            return
        except Exception:
            print("Plotly exception:")
            print('-' * 60)
            traceback.print_exc(file=sys.stdout)
            print('-' * 60)
            self.set_status("An unexpected error occurred", color='red')
            return

        self.set_status('Exporting succeeded', color='blue')

        if self.save_settings and self.ui.radio_account_manual.isChecked():
            try:
                set_credentials_file(**auth)
            except Exception:
                print("Plotly exception:")
                print('-' * 60)
                traceback.print_exc(file=sys.stdout)
                print('-' * 60)
                self.set_status('Exporting succeeded (but saving login failed)', color='blue')

        # We need to fix URL
        # https://github.com/plotly/plotly.py/issues/526
        if self.plotly_kwargs['sharing'] == 'secret':
            pos = plotly_url.find('?share_key')
            if pos >= 0:
                if plotly_url[pos - 1] != '/':
                    plotly_url = plotly_url.replace('?share_key', '/?share_key')

        print("Plotly URL: {0}".format(plotly_url))

        webbrowser.open_new_tab(plotly_url)

        super(QtPlotlyExporter, self).accept()

    def set_status(self, text, color):
        self.ui.text_status.setText(text)
        self.ui.text_status.setStyleSheet("color: {0}".format(color))
        QtWidgets.QApplication.instance().processEvents()
Ejemplo n.º 7
0
class VispyOptionsWidget(QtWidgets.QWidget):

    x_att = CurrentComboProperty('ui.combo_x_attribute')
    x_min = FloatLineProperty('ui.value_x_min')
    x_max = FloatLineProperty('ui.value_x_max')
    x_stretch = FloatLineProperty('ui.value_x_stretch')

    y_att = CurrentComboProperty('ui.combo_y_attribute')
    y_min = FloatLineProperty('ui.value_y_min')
    y_max = FloatLineProperty('ui.value_y_max')
    y_stretch = FloatLineProperty('ui.value_y_stretch')

    z_att = CurrentComboProperty('ui.combo_z_attribute')
    z_min = FloatLineProperty('ui.value_z_min')
    z_max = FloatLineProperty('ui.value_z_max')
    z_stretch = FloatLineProperty('ui.value_z_stretch')

    visible_box = ButtonProperty('ui.checkbox_axes')
    perspective_view = ButtonProperty('ui.checkbox_perspective')

    def __init__(self, parent=None, vispy_widget=None, data_viewer=None):

        super(VispyOptionsWidget, self).__init__(parent=parent)

        self.ui = load_ui('viewer_options.ui', self,
                          directory=os.path.dirname(__file__))

        self._vispy_widget = vispy_widget
        vispy_widget.options = self
        self._data_viewer = data_viewer

        self.stretch_sliders = [self.ui.slider_x_stretch,
                                self.ui.slider_y_stretch,
                                self.ui.slider_z_stretch]

        self.stretch_values = [self.ui.value_x_stretch,
                               self.ui.value_y_stretch,
                               self.ui.value_z_stretch]

        self._event_lock = False

        for slider, label in zip(self.stretch_sliders, self.stretch_values):
            slider.valueChanged.connect(partial(self._update_labels_from_sliders, label, slider))
            label.editingFinished.connect(partial(self._update_sliders_from_labels, slider, label))
            label.setText('1.0')
            label.editingFinished.emit()
            slider.valueChanged.connect(self._update_stretch)

        connect_bool_button(self._vispy_widget, 'visible_axes', self.ui.checkbox_axes)
        connect_bool_button(self._vispy_widget, 'perspective_view', self.ui.checkbox_perspective)

        if self._data_viewer is not None:
            self.ui.combo_x_attribute.currentIndexChanged.connect(self._data_viewer._update_attributes)
            self.ui.combo_y_attribute.currentIndexChanged.connect(self._data_viewer._update_attributes)
            self.ui.combo_z_attribute.currentIndexChanged.connect(self._data_viewer._update_attributes)

        self.ui.combo_x_attribute.currentIndexChanged.connect(self._update_attribute_limits)
        self.ui.combo_y_attribute.currentIndexChanged.connect(self._update_attribute_limits)
        self.ui.combo_z_attribute.currentIndexChanged.connect(self._update_attribute_limits)

        self.ui.value_x_min.editingFinished.connect(self._update_limits)
        self.ui.value_y_min.editingFinished.connect(self._update_limits)
        self.ui.value_z_min.editingFinished.connect(self._update_limits)

        self.ui.value_x_max.editingFinished.connect(self._update_limits)
        self.ui.value_y_max.editingFinished.connect(self._update_limits)
        self.ui.value_z_max.editingFinished.connect(self._update_limits)

        self.ui.button_flip_x.clicked.connect(self._flip_x)
        self.ui.button_flip_y.clicked.connect(self._flip_y)
        self.ui.button_flip_z.clicked.connect(self._flip_z)

        self.ui.reset_button.clicked.connect(self._vispy_widget._reset_view)

        self._components = {}

        self._set_attributes_enabled(False)
        self._set_limits_enabled(False)

        self._first_attributes = True

    def set_limits(self, x_min, x_max, y_min, y_max, z_min, z_max):

        self._set_limits_enabled(False)

        self.x_min = x_min
        self.x_max = x_max
        self.y_min = y_min
        self.y_max = y_max
        self.z_min = z_min
        self.z_max = z_max

        self._set_limits_enabled(True)

        self.ui.value_x_min.editingFinished.emit()

    def _flip_x(self):
        self._set_limits_enabled(False)
        self.x_min, self.x_max = self.x_max, self.x_min
        self._set_limits_enabled(True)
        self.ui.value_x_min.editingFinished.emit()

    def _flip_y(self):
        self._set_limits_enabled(False)
        self.y_min, self.y_max = self.y_max, self.y_min
        self._set_limits_enabled(True)
        self.ui.value_y_min.editingFinished.emit()

    def _flip_z(self):
        self._set_limits_enabled(False)
        self.z_min, self.z_max = self.z_max, self.z_min
        self._set_limits_enabled(True)
        self.ui.value_z_min.editingFinished.emit()

    def _set_attributes_enabled(self, value):

        self.ui.combo_x_attribute.setEnabled(value)
        self.ui.combo_y_attribute.setEnabled(value)
        self.ui.combo_z_attribute.setEnabled(value)

        self.ui.combo_x_attribute.blockSignals(not value)
        self.ui.combo_y_attribute.blockSignals(not value)
        self.ui.combo_z_attribute.blockSignals(not value)

    def _set_limits_enabled(self, value):

        self.ui.value_x_min.setEnabled(value)
        self.ui.value_y_min.setEnabled(value)
        self.ui.value_z_min.setEnabled(value)

        self.ui.value_x_max.setEnabled(value)
        self.ui.value_y_max.setEnabled(value)
        self.ui.value_z_max.setEnabled(value)

        self.ui.value_x_min.blockSignals(not value)
        self.ui.value_y_min.blockSignals(not value)
        self.ui.value_z_min.blockSignals(not value)

        self.ui.value_x_max.blockSignals(not value)
        self.ui.value_y_max.blockSignals(not value)
        self.ui.value_z_max.blockSignals(not value)

    def _update_attributes_from_data(self, data):

        components = data.visible_components

        for component_id in components:
            component = data.get_component(component_id)
            if component.categorical:
                continue
            if self.ui.combo_x_attribute.findData(component_id) == -1:
                self.ui.combo_x_attribute.addItem(component_id.label, userData=component_id)
            if self.ui.combo_y_attribute.findData(component_id) == -1:
                self.ui.combo_y_attribute.addItem(component_id.label, userData=component_id)
            if self.ui.combo_z_attribute.findData(component_id) == -1:
                self.ui.combo_z_attribute.addItem(component_id.label, userData=component_id)
            self._components[component_id] = component

        if self._first_attributes:
            n_max = len(components)
            self.ui.combo_x_attribute.setCurrentIndex(0)
            self.ui.combo_y_attribute.setCurrentIndex(min(1, n_max - 1))
            self.ui.combo_z_attribute.setCurrentIndex(min(2, n_max - 1))
            self._set_attributes_enabled(True)
            self._first_attributes = False

        self._update_attribute_limits()

    def _update_attribute_limits(self):

        if not hasattr(self, '_limits'):
            self._limits = {}

        self._set_limits_enabled(False)

        if self.x_att not in self._limits:
            data = self._components[self.x_att].data
            self._limits[self.x_att] = np.nanmin(data), np.nanmax(data)
        self.x_min, self.x_max = self._limits[self.x_att]

        if self.y_att not in self._limits:
            data = self._components[self.y_att].data
            self._limits[self.y_att] = np.nanmin(data), np.nanmax(data)
        self.y_min, self.y_max = self._limits[self.y_att]

        if self.z_att not in self._limits:
            data = self._components[self.z_att].data
            self._limits[self.z_att] = np.nanmin(data), np.nanmax(data)
        self.z_min, self.z_max = self._limits[self.z_att]

        self._set_limits_enabled(True)

        self.ui.value_x_min.editingFinished.emit()

    def _update_limits(self):

        if not hasattr(self, '_limits'):
            self._limits = {}

        self._limits[self.x_att] = self.x_min, self.x_max
        self._limits[self.y_att] = self.y_min, self.y_max
        self._limits[self.z_att] = self.z_min, self.z_max

        self._vispy_widget._update_limits()

    def _update_stretch(self):
        self._vispy_widget._update_stretch(self.x_stretch,
                                           self.y_stretch,
                                           self.z_stretch)

    def _update_labels_from_sliders(self, label, slider):
        if self._event_lock:
            return  # prevent infinite event loop
        self._event_lock = True
        try:
            label.setText("{0:6.2f}".format(10 ** (slider.value() / 1e4)))
        finally:
            self._event_lock = False

    def _update_sliders_from_labels(self, slider, label):
        if self._event_lock:
            return  # prevent infinite event loop
        self._event_lock = True
        try:
            slider.setValue(1e4 * math.log10(float(label.text())))
        finally:
            self._event_lock = False

    def __gluestate__(self, context):
        return dict(x_att=context.id(self.x_att),
                    x_min=self.x_min, x_max=self.x_max,
                    x_stretch=self.x_stretch,
                    y_att=context.id(self.y_att),
                    y_min=self.y_min, y_max=self.y_max,
                    y_stretch=self.y_stretch,
                    z_att=context.id(self.z_att),
                    z_min=self.z_min, z_max=self.z_max,
                    z_stretch=self.z_stretch,
                    visible_box=self.visible_box,
                    perspective_view=self.perspective_view)
Ejemplo n.º 8
0
class SubsetFacet(QtGui.QDialog):

    log = ButtonProperty('ui.checkbox_log')
    vmin = FloatLineProperty('ui.value_min')
    vmax = FloatLineProperty('ui.value_max')
    steps = ValueProperty('ui.value_n_subsets')
    data = Pointer('ui.component_selector.data')
    component = Pointer('ui.component_selector.component')

    def __init__(self, collect, default=None, parent=None):
        """Create a new dialog for subset faceting

        :param collect: The :class:`~glue.core.data_collection.DataCollection` to use
        :param default: The default dataset in the collection (optional)
        """

        super(SubsetFacet, self).__init__(parent=parent)

        self.ui = load_ui('subset_facet.ui',
                          self,
                          directory=os.path.dirname(__file__))
        self.ui.setWindowTitle("Subset Facet")
        self._collect = collect

        self.ui.component_selector.setup(self._collect)
        if default is not None:
            self.ui.component_selector.data = default

        val = QtGui.QDoubleValidator(-1e100, 1e100, 4, None)
        self.ui.component_selector.component_changed.connect(self._set_limits)

        combo = self.ui.color_scale
        for cmap in [cm.cool, cm.RdYlBu, cm.RdYlGn, cm.RdBu, cm.Purples]:
            combo.addItem(QtGui.QIcon(cmap2pixmap(cmap)), cmap.name, cmap)

    def _set_limits(self):
        data = self.ui.component_selector.data
        cid = self.ui.component_selector.component

        vals = data[cid]

        wmin = self.ui.value_min
        wmax = self.ui.value_max

        wmin.setText(pretty_number(np.nanmin(vals)))
        wmax.setText(pretty_number(np.nanmax(vals)))

    @property
    def cmap(self):
        combo = self.ui.color_scale
        index = combo.currentIndex()
        return combo.itemData(index)

    def _apply(self):
        try:
            lo, hi = self.vmin, self.vmax
        except ValueError:
            return  # limits not set. Abort
        if not np.isfinite(lo) or not np.isfinite(hi):
            return

        subsets = facet_subsets(self._collect,
                                self.component,
                                lo=lo,
                                hi=hi,
                                steps=self.steps,
                                log=self.log)
        colorize_subsets(subsets, self.cmap)

    @classmethod
    def facet(cls, collect, default=None, parent=None):
        """Class method to create facted subsets
        The arguments are the same as __init__
        """
        self = cls(collect, parent=parent, default=default)
        value = self.exec_()

        if value == QtGui.QDialog.Accepted:
            self._apply()
Ejemplo n.º 9
0
class ImageWidgetBase(DataViewer):
    """
    Widget for ImageClient

    This base class avoids any matplotlib-specific logic
    """

    LABEL = "Image Viewer"
    _property_set = DataViewer._property_set + \
        'data attribute rgb_mode rgb_viz ratt gatt batt slice'.split()

    attribute = CurrentComboProperty('ui.attributeComboBox',
                                     'Current attribute')
    data = CurrentComboProperty('ui.displayDataCombo', 'Current data')
    aspect_ratio = CurrentComboProperty('ui.aspectCombo',
                                        'Aspect ratio for image')
    rgb_mode = ButtonProperty('ui.rgb', 'RGB Mode?')
    rgb_viz = Pointer('ui.rgb_options.rgb_visible')

    _layer_style_widget_cls = {ScatterLayerArtist: ScatterLayerStyleWidget}

    def __init__(self, session, parent=None):
        super(ImageWidgetBase, self).__init__(session, parent)
        self._setup_widgets()
        self.client = self.make_client()
        self._connect()

    def _setup_widgets(self):
        self.central_widget = self.make_central_widget()
        self.label_widget = QtWidgets.QLabel("", self.central_widget)
        self.setCentralWidget(self.central_widget)
        self.option_widget = QtWidgets.QWidget()
        self.ui = load_ui('options_widget.ui',
                          self.option_widget,
                          directory=os.path.dirname(__file__))
        self.ui.slice = DataSlice()
        self.ui.slice_layout.addWidget(self.ui.slice)
        self._tweak_geometry()

        self.ui.aspectCombo.addItem("Square Pixels", userData='equal')
        self.ui.aspectCombo.addItem("Automatic", userData='auto')

    def make_client(self):
        """ Instantiate and return an ImageClient subclass """
        raise NotImplementedError()

    def make_central_widget(self):
        """ Create and return the central widget to display the image """
        raise NotImplementedError()

    def _tweak_geometry(self):
        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())
        self.ui.rgb_options.hide()
        self.statusBar().setSizeGripEnabled(False)
        self.setFocusPolicy(Qt.StrongFocus)

    @defer_draw
    def add_data(self, data):
        """
        Add a new dataset to the viewer
        """
        # overloaded from DataViewer

        # need to delay callbacks, otherwise might
        # try to set combo boxes to nonexisting items
        with delay_callback(self.client, 'display_data', 'display_attribute'):

            # If there is not already any image data set, we can't add 1-D
            # datasets (tables/catalogs) to the image widget yet.
            if data.data.ndim == 1 and self.client.display_data is None:
                QtWidgets.QMessageBox.information(
                    self.window(),
                    "Note", "Cannot create image viewer from a 1-D "
                    "dataset. You will need to first "
                    "create an image viewer using data "
                    "with 2 or more dimensions, after "
                    "which you will be able to overlay 1-D "
                    "data as a scatter plot.",
                    buttons=QtWidgets.QMessageBox.Ok)
                return

            r = self.client.add_layer(data)
            if r is not None and self.client.display_data is not None:
                self.add_data_to_combo(data)
                if self.client.can_image_data(data):
                    self.client.display_data = data
                self.set_attribute_combo(self.client.display_data)

        return r is not None

    @defer_draw
    def add_subset(self, subset):
        self.client.add_scatter_layer(subset)
        assert subset in self.client.artists

    def add_data_to_combo(self, data):
        """ Add a data object to the combo box, if not already present
        """
        if not self.client.can_image_data(data):
            return
        combo = self.ui.displayDataCombo
        try:
            pos = _find_combo_data(combo, data)
        except ValueError:
            combo.addItem(data.label, userData=data)

    @property
    def ratt(self):
        """ComponentID assigned to R channel in RGB Mode"""
        return self.ui.rgb_options.attributes[0]

    @ratt.setter
    def ratt(self, value):
        att = list(self.ui.rgb_options.attributes)
        att[0] = value
        self.ui.rgb_options.attributes = att

    @property
    def gatt(self):
        """ComponentID assigned to G channel in RGB Mode"""
        return self.ui.rgb_options.attributes[1]

    @gatt.setter
    def gatt(self, value):
        att = list(self.ui.rgb_options.attributes)
        att[1] = value
        self.ui.rgb_options.attributes = att

    @property
    def batt(self):
        """ComponentID assigned to B channel in RGB Mode"""
        return self.ui.rgb_options.attributes[2]

    @batt.setter
    def batt(self, value):
        att = list(self.ui.rgb_options.attributes)
        att[2] = value
        self.ui.rgb_options.attributes = att

    @property
    def slice(self):
        return self.client.slice

    @slice.setter
    def slice(self, value):
        self.client.slice = value

    def set_attribute_combo(self, data):
        """ Update attribute combo box to reflect components in data"""
        labeldata = ((f.label, f) for f in data.visible_components)
        update_combobox(self.ui.attributeComboBox, labeldata)

    def _connect(self):
        ui = self.ui

        ui.monochrome.toggled.connect(self._update_rgb_console)
        ui.rgb_options.colors_changed.connect(self.update_window_title)

        # sync client and widget slices
        ui.slice.slice_changed.connect(
            lambda: setattr(self, 'slice', self.ui.slice.slice))
        update_ui_slice = lambda val: setattr(ui.slice, 'slice', val)
        add_callback(self.client, 'slice', update_ui_slice)
        add_callback(self.client, 'display_data', self.ui.slice.set_data)

        # sync window title to data/attribute
        add_callback(self.client, 'display_data',
                     nonpartial(self._display_data_changed))
        add_callback(self.client, 'display_attribute',
                     nonpartial(self._display_attribute_changed))
        add_callback(self.client, 'display_aspect',
                     nonpartial(self.client._update_aspect))

        # sync data/attribute combos with client properties
        connect_current_combo(self.client, 'display_data',
                              self.ui.displayDataCombo)
        connect_current_combo(self.client, 'display_attribute',
                              self.ui.attributeComboBox)
        connect_current_combo(self.client, 'display_aspect',
                              self.ui.aspectCombo)

    def _display_data_changed(self):

        if self.client.display_data is None:
            self.ui.attributeComboBox.clear()
            return

        with self.client.artists.ignore_empty():
            self.set_attribute_combo(self.client.display_data)
        self.client.add_layer(self.client.display_data)
        self.client._update_and_redraw()
        self.update_window_title()

    def _display_attribute_changed(self):

        if self.client.display_attribute is None:
            return

        self.client._update_and_redraw()
        self.update_window_title()

    @defer_draw
    def _update_rgb_console(self, is_monochrome):
        if is_monochrome:
            self.ui.rgb_options.hide()
            self.ui.mono_att_label.show()
            self.ui.attributeComboBox.show()
            self.client.rgb_mode(False)
        else:
            self.ui.mono_att_label.hide()
            self.ui.attributeComboBox.hide()
            self.ui.rgb_options.show()
            rgb = self.client.rgb_mode(True)
            if rgb is not None:
                self.ui.rgb_options.artist = rgb

    def register_to_hub(self, hub):
        super(ImageWidgetBase, self).register_to_hub(hub)
        self.client.register_to_hub(hub)

        dc_filt = lambda x: x.sender is self.client._data
        display_data_filter = lambda x: x.data is self.client.display_data

        hub.subscribe(self,
                      core.message.DataCollectionAddMessage,
                      handler=lambda x: self.add_data_to_combo(x.data),
                      filter=dc_filt)
        hub.subscribe(self,
                      core.message.DataCollectionDeleteMessage,
                      handler=lambda x: self.remove_data_from_combo(x.data),
                      filter=dc_filt)
        hub.subscribe(self,
                      core.message.DataUpdateMessage,
                      handler=lambda x: self._sync_data_labels())
        hub.subscribe(self,
                      core.message.ComponentsChangedMessage,
                      handler=lambda x: self.set_attribute_combo(x.data),
                      filter=display_data_filter)

    def unregister(self, hub):
        super(ImageWidgetBase, self).unregister(hub)
        for obj in [self, self.client]:
            hub.unsubscribe_all(obj)

    def remove_data_from_combo(self, data):
        """ Remove a data object from the combo box, if present """
        combo = self.ui.displayDataCombo
        pos = combo.findText(data.label)
        if pos >= 0:
            combo.removeItem(pos)

    def _set_norm(self, mode):
        """ Use the `ContrastMouseMode` to adjust the transfer function """

        # at least one of the clip/vmin pairs will be None
        clip_lo, clip_hi = mode.get_clip_percentile()
        vmin, vmax = mode.get_vmin_vmax()
        stretch = mode.stretch
        return self.client.set_norm(clip_lo=clip_lo,
                                    clip_hi=clip_hi,
                                    stretch=stretch,
                                    vmin=vmin,
                                    vmax=vmax,
                                    bias=mode.bias,
                                    contrast=mode.contrast)

    @property
    def window_title(self):
        if self.client.display_data is None or self.client.display_attribute is None:
            title = ''
        else:
            data = self.client.display_data.label
            a = self.client.rgb_mode()
            if a is None:  # monochrome mode
                title = "%s - %s" % (self.client.display_data.label,
                                     self.client.display_attribute.label)
            else:
                r = a.r.label if a.r is not None else ''
                g = a.g.label if a.g is not None else ''
                b = a.b.label if a.b is not None else ''
                title = "%s Red = %s  Green = %s  Blue = %s" % (data, r, g, b)

        return title

    def _sync_data_combo_labels(self):
        combo = self.ui.displayDataCombo
        for i in range(combo.count()):
            combo.setItemText(i, combo.itemData(i).label)

    def _sync_data_labels(self):
        self.update_window_title()
        self._sync_data_combo_labels()

    def __str__(self):
        return "Image Widget"

    def _confirm_large_image(self, data):
        """Ask user to confirm expensive operations

        :rtype: bool. Whether the user wishes to continue
        """

        warn_msg = ("WARNING: Image has %i pixels, and may render slowly."
                    " Continue?" % data.size)
        title = "Contour large image?"
        ok = QtWidgets.QMessageBox.Ok
        cancel = QtWidgets.QMessageBox.Cancel
        buttons = ok | cancel
        result = QtWidgets.QMessageBox.question(self,
                                                title,
                                                warn_msg,
                                                buttons=buttons,
                                                defaultButton=cancel)
        return result == ok

    def options_widget(self):
        return self.option_widget

    @defer_draw
    def restore_layers(self, rec, context):
        with delay_callback(self.client, 'display_data', 'display_attribute'):
            self.client.restore_layers(rec, context)

            for artist in self.layers:
                self.add_data_to_combo(artist.layer.data)

            self.set_attribute_combo(self.client.display_data)

        self._sync_data_combo_labels()

    def closeEvent(self, event):
        # close window and all plugins
        super(ImageWidgetBase, self).closeEvent(event)
Ejemplo n.º 10
0
class ScatterWidget(DataViewer):
    """
    An interactive scatter plot.
    """

    LABEL = "Scatter Plot"
    _property_set = DataViewer._property_set + \
        'xlog ylog xflip yflip hidden xatt yatt xmin xmax ymin ymax'.split()

    xlog = ButtonProperty('ui.xLogCheckBox', 'log scaling on x axis?')
    ylog = ButtonProperty('ui.yLogCheckBox', 'log scaling on y axis?')
    xflip = ButtonProperty('ui.xFlipCheckBox', 'invert the x axis?')
    yflip = ButtonProperty('ui.yFlipCheckBox', 'invert the y axis?')
    xmin = FloatLineProperty('ui.xmin', 'Lower x limit of plot')
    xmax = FloatLineProperty('ui.xmax', 'Upper x limit of plot')
    ymin = FloatLineProperty('ui.ymin', 'Lower y limit of plot')
    ymax = FloatLineProperty('ui.ymax', 'Upper y limit of plot')
    hidden = ButtonProperty('ui.hidden_attributes', 'Show hidden attributes')
    xatt = CurrentComboProperty('ui.xAxisComboBox',
                                'Attribute to plot on x axis')
    yatt = CurrentComboProperty('ui.yAxisComboBox',
                                'Attribute to plot on y axis')

    def __init__(self, session, parent=None):
        super(ScatterWidget, self).__init__(session, parent)
        self.central_widget = MplWidget()
        self.option_widget = QtGui.QWidget()

        self.setCentralWidget(self.central_widget)

        self.ui = load_ui('options_widget.ui',
                          self.option_widget,
                          directory=os.path.dirname(__file__))
        self._tweak_geometry()

        self.client = ScatterClient(
            self._data,
            self.central_widget.canvas.fig,
            layer_artist_container=self._layer_artist_container)

        self._connect()
        self.unique_fields = set()
        tb = self.make_toolbar()
        cache_axes(self.client.axes, tb)
        self.statusBar().setSizeGripEnabled(False)
        self.setFocusPolicy(Qt.StrongFocus)

    @staticmethod
    def _get_default_tools():
        return []

    def _tweak_geometry(self):
        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

    def _connect(self):
        ui = self.ui
        cl = self.client

        connect_bool_button(cl, 'xlog', ui.xLogCheckBox)
        connect_bool_button(cl, 'ylog', ui.yLogCheckBox)
        connect_bool_button(cl, 'xflip', ui.xFlipCheckBox)
        connect_bool_button(cl, 'yflip', ui.yFlipCheckBox)

        ui.xAxisComboBox.currentIndexChanged.connect(self.update_xatt)
        ui.yAxisComboBox.currentIndexChanged.connect(self.update_yatt)
        ui.hidden_attributes.toggled.connect(lambda x: self._update_combos())
        ui.swapAxes.clicked.connect(nonpartial(self.swap_axes))
        ui.snapLimits.clicked.connect(cl.snap)

        connect_float_edit(cl, 'xmin', ui.xmin)
        connect_float_edit(cl, 'xmax', ui.xmax)
        connect_float_edit(cl, 'ymin', ui.ymin)
        connect_float_edit(cl, 'ymax', ui.ymax)

    def make_toolbar(self):
        result = GlueToolbar(self.central_widget.canvas,
                             self,
                             name='Scatter Plot')
        for mode in self._mouse_modes():
            result.add_mode(mode)
        self.addToolBar(result)
        return result

    def _mouse_modes(self):
        axes = self.client.axes

        def apply_mode(mode):
            return self.apply_roi(mode.roi())

        rect = RectangleMode(axes, roi_callback=apply_mode)
        xra = HRangeMode(axes, roi_callback=apply_mode)
        yra = VRangeMode(axes, roi_callback=apply_mode)
        circ = CircleMode(axes, roi_callback=apply_mode)
        poly = PolyMode(axes, roi_callback=apply_mode)
        return [rect, xra, yra, circ, poly]

    @defer_draw
    def _update_combos(self):
        """ Update contents of combo boxes """

        # have to be careful here, since client and/or widget
        # are potentially out of sync

        layer_ids = []

        # show hidden attributes if needed
        if ((self.client.xatt and self.client.xatt.hidden)
                or (self.client.yatt and self.client.yatt.hidden)):
            self.hidden = True

        # determine which components to put in combos
        for l in self.client.data:
            if not self.client.is_layer_present(l):
                continue
            for lid in self.client.plottable_attributes(
                    l, show_hidden=self.hidden):
                if lid not in layer_ids:
                    layer_ids.append(lid)

        oldx = self.xatt
        oldy = self.yatt
        newx = self.client.xatt or oldx
        newy = self.client.yatt or oldy

        for combo, target in zip(
            [self.ui.xAxisComboBox, self.ui.yAxisComboBox], [newx, newy]):
            combo.blockSignals(True)
            combo.clear()

            if not layer_ids:  # empty component list
                continue

            # populate
            for lid in layer_ids:
                combo.addItem(lid.label, userData=lid)

            idx = layer_ids.index(target) if target in layer_ids else 0
            combo.setCurrentIndex(idx)

            combo.blockSignals(False)

        # ensure client and widget synced
        self.client.xatt = self.xatt
        self.client.lyatt = self.yatt

    @defer_draw
    def add_data(self, data):
        """Add a new data set to the widget

        :returns: True if the addition was expected, False otherwise
        """
        if self.client.is_layer_present(data):
            return

        if data.size > WARN_SLOW and not self._confirm_large_data(data):
            return False

        first_layer = self.client.layer_count == 0

        self.client.add_data(data)
        self._update_combos()

        if first_layer:  # forces both x and y axes to be rescaled
            self.update_xatt(None)
            self.update_yatt(None)

            self.ui.xAxisComboBox.setCurrentIndex(0)
            if len(data.visible_components) > 1:
                self.ui.yAxisComboBox.setCurrentIndex(1)
            else:
                self.ui.yAxisComboBox.setCurrentIndex(0)

        self.update_window_title()
        return True

    @defer_draw
    def add_subset(self, subset):
        """Add a subset to the widget

        :returns: True if the addition was accepted, False otherwise
        """
        if self.client.is_layer_present(subset):
            return

        data = subset.data
        if data.size > WARN_SLOW and not self._confirm_large_data(data):
            return False

        first_layer = self.client.layer_count == 0

        self.client.add_layer(subset)
        self._update_combos()

        if first_layer:  # forces both x and y axes to be rescaled
            self.update_xatt(None)
            self.update_yatt(None)

            self.ui.xAxisComboBox.setCurrentIndex(0)
            if len(data.visible_components) > 1:
                self.ui.yAxisComboBox.setCurrentIndex(1)
            else:
                self.ui.yAxisComboBox.setCurrentIndex(0)

        self.update_window_title()
        return True

    def register_to_hub(self, hub):
        super(ScatterWidget, self).register_to_hub(hub)
        self.client.register_to_hub(hub)
        hub.subscribe(self, core.message.DataUpdateMessage,
                      nonpartial(self._sync_labels))
        hub.subscribe(self, core.message.ComponentsChangedMessage,
                      nonpartial(self._update_combos))
        hub.subscribe(self, core.message.ComponentReplacedMessage,
                      self._on_component_replace)

    def _on_component_replace(self, msg):
        # let client update its state first
        self.client._on_component_replace(msg)
        self._update_combos()

    def unregister(self, hub):
        super(ScatterWidget, self).unregister(hub)
        hub.unsubscribe_all(self.client)
        hub.unsubscribe_all(self)

    @defer_draw
    def swap_axes(self):
        xid = self.ui.xAxisComboBox.currentIndex()
        yid = self.ui.yAxisComboBox.currentIndex()
        xlog = self.ui.xLogCheckBox.isChecked()
        ylog = self.ui.yLogCheckBox.isChecked()
        xflip = self.ui.xFlipCheckBox.isChecked()
        yflip = self.ui.yFlipCheckBox.isChecked()

        self.ui.xAxisComboBox.setCurrentIndex(yid)
        self.ui.yAxisComboBox.setCurrentIndex(xid)
        self.ui.xLogCheckBox.setChecked(ylog)
        self.ui.yLogCheckBox.setChecked(xlog)
        self.ui.xFlipCheckBox.setChecked(yflip)
        self.ui.yFlipCheckBox.setChecked(xflip)

    @defer_draw
    def update_xatt(self, index):
        component_id = self.xatt
        self.client.xatt = component_id

    @defer_draw
    def update_yatt(self, index):
        component_id = self.yatt
        self.client.yatt = component_id

    @property
    def window_title(self):
        data = self.client.data
        label = ', '.join([d.label for d in data if self.client.is_visible(d)])
        return label

    def _sync_labels(self):
        self.update_window_title()

    def options_widget(self):
        return self.option_widget

    @defer_draw
    def restore_layers(self, rec, context):
        self.client.restore_layers(rec, context)
        self._update_combos()
        # manually force client attributes to sync
        self.update_xatt(None)
        self.update_yatt(None)
Ejemplo n.º 11
0
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 VolumeLayerStyleWidget(QtGui.QWidget):

    # GUI elements
    attribute = CurrentComboProperty('ui.combo_attribute')
    vmin = FloatLineProperty('ui.value_min')
    vmax = FloatLineProperty('ui.value_max')
    alpha = ValueProperty('ui.slider_alpha')
    subset_outline = ButtonProperty('ui.radio_subset_outline')
    subset_data = ButtonProperty('ui.radio_subset_data')

    def __init__(self, layer_artist):

        super(VolumeLayerStyleWidget, self).__init__()

        self.ui = load_ui('layer_style_widget.ui',
                          self,
                          directory=os.path.dirname(__file__))

        self.layer_artist = layer_artist
        self.layer = layer_artist.layer

        # Set up attribute and visual options
        self._setup_options()
        self._connect_global()

        # Set initial values
        self.layer_artist.color = self.layer.style.color
        self.layer_artist.alpha = self.layer.style.alpha
        with delay_callback(self.layer_artist, 'attribute'):
            self.attribute = self.visible_components[0]
            self._update_limits()
        if isinstance(self.layer, Subset):
            self.ui.radio_subset_data.setChecked(True)
        self.layer_artist.visible = True

    def _connect_global(self):
        connect_color(self.layer.style, 'color', self.ui.label_color)
        connect_value(self.layer.style,
                      'alpha',
                      self.ui.slider_alpha,
                      value_range=(0, 1))

    def _setup_options(self):
        """
        Set up the combo box with the list of attributes
        """

        # Set up radio buttons for subset mode selection if this is a subset
        if isinstance(self.layer, Subset):
            self._radio_size = QtGui.QButtonGroup()
            self._radio_size.addButton(self.ui.radio_subset_outline)
            self._radio_size.addButton(self.ui.radio_subset_data)
        else:
            self.ui.radio_subset_outline.hide()
            self.ui.radio_subset_data.hide()
            self.ui.label_subset_mode.hide()

        # Set up attribute list
        label_data = [(comp.label, comp) for comp in self.visible_components]
        update_combobox(self.ui.combo_attribute, label_data)

        # Set up connections with layer artist
        connect_current_combo(self.layer_artist, 'attribute',
                              self.ui.combo_attribute)
        connect_float_edit(self.layer_artist, 'vmin', self.ui.value_min)
        connect_float_edit(self.layer_artist, 'vmax', self.ui.value_max)
        connect_color(self.layer_artist, 'color', self.ui.label_color)
        connect_value(self.layer_artist,
                      'alpha',
                      self.ui.slider_alpha,
                      value_range=(0, 1))

        # Set up internal connections
        self.ui.radio_subset_outline.toggled.connect(self._update_subset_mode)
        self.ui.radio_subset_data.toggled.connect(self._update_subset_mode)
        self.ui.value_min.editingFinished.connect(self._cache_limits)
        self.ui.value_max.editingFinished.connect(self._cache_limits)
        self.ui.combo_attribute.currentIndexChanged.connect(
            self._update_limits)

    def _update_subset_mode(self):
        if self.ui.radio_subset_outline.isChecked():
            self.layer_artist.subset_mode = 'outline'
        else:
            self.layer_artist.subset_mode = 'data'
        self._update_limits()

    def _update_limits(self):

        if isinstance(self.layer, Subset):
            if self.layer_artist.subset_mode == 'outline':
                self.ui.value_min.setEnabled(False)
                self.ui.value_max.setEnabled(False)
                self.vmin, self.vmax = 0, 2
                return
            else:
                self.ui.value_min.setEnabled(False)
                self.ui.value_max.setEnabled(True)

        if not hasattr(self, '_limits'):
            self._limits = {}

        if self.attribute in self._limits:
            self.vmin, self.vmax = self._limits[self.attribute]
        else:
            self.vmin, self.vmax = self.default_limits(self.attribute)
            self._limits[self.attribute] = self.vmin, self.vmax

    def _cache_limits(self):
        if not isinstance(self.layer,
                          Subset) or self.layer_artist.subset_mode == 'data':
            self._limits[self.attribute] = self.vmin, self.vmax

    def default_limits(self, attribute):
        # For subsets, we want to compute the limits based on the full
        # dataset not just the subset.
        if isinstance(self.layer, Subset):
            vmin = 0
            vmax = np.nanmax(self.layer.data[attribute])
        else:
            vmin = np.nanmin(self.layer[attribute])
            vmax = np.nanmax(self.layer[attribute])
        return vmin, vmax

    @property
    def visible_components(self):
        if isinstance(self.layer, Subset):
            return self.layer.data.visible_components
        else:
            return self.layer.visible_components
Ejemplo n.º 13
0
class PreferencesDialog(QtGui.QDialog):

    theme = CurrentComboTextProperty('ui.combo_theme')
    background = ColorProperty('ui.color_background')
    foreground = ColorProperty('ui.color_foreground')
    data_color = ColorProperty('ui.color_default_data')
    data_alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1))
    data_apply = ButtonProperty('ui.checkbox_apply')
    save_to_disk = ButtonProperty('ui.checkbox_save')

    def __init__(self, application, parent=None):

        super(PreferencesDialog, self).__init__(parent=parent)

        self.app = application

        self.ui = load_ui('preferences.ui',
                          self,
                          directory=os.path.dirname(__file__))

        self.ui.cancel.clicked.connect(self.reject)
        self.ui.ok.clicked.connect(self.accept)

        self.ui.combo_theme.currentIndexChanged.connect(
            nonpartial(self._update_colors_from_theme))

        from glue.config import settings
        self.background = settings.BACKGROUND_COLOR
        self.foreground = settings.FOREGROUND_COLOR
        self.data_color = settings.DATA_COLOR
        self.data_alpha = settings.DATA_ALPHA

        self._update_theme_from_colors()

        self.panes = []

        from glue.config import preference_panes
        for label, widget_cls in sorted(preference_panes):
            pane = widget_cls()
            self.ui.tab_widget.addTab(pane, label)
            self.panes.append(pane)

    def _update_theme_from_colors(self):
        if (rgb(self.background) == (1, 1, 1)
                and rgb(self.foreground) == (0, 0, 0)
                and rgb(self.data_color) == (0.35, 0.35, 0.35)
                and np.allclose(self.data_alpha, 0.8)):
            self.theme = 'Black on White'
        elif (rgb(self.background) == (0, 0, 0)
              and rgb(self.foreground) == (1, 1, 1)
              and rgb(self.data_color) == (0.75, 0.75, 0.75)
              and np.allclose(self.data_alpha, 0.8)):
            self.theme = 'White on Black'
        else:
            self.theme = 'Custom'

    def _update_colors_from_theme(self):
        if self.theme == 'Black on White':
            self.foreground = 'black'
            self.background = 'white'
            self.data_color = '0.35'
            self.data_alpha = 0.8
        elif self.theme == 'White on Black':
            self.foreground = 'white'
            self.background = 'black'
            self.data_color = '0.75'
            self.data_alpha = 0.8
        elif self.theme != 'Custom':
            raise ValueError("Unknown theme: {0}".format(self.theme))

    def accept(self):

        # Update default settings

        from glue.config import settings
        settings.FOREGROUND_COLOR = self.foreground
        settings.BACKGROUND_COLOR = self.background
        settings.DATA_COLOR = self.data_color
        settings.DATA_ALPHA = self.data_alpha

        for pane in self.panes:
            pane.finalize()

        # Save to disk if requested
        if self.save_to_disk:
            save_settings()

        # Trigger viewers to update defaults

        self.app._hub.broadcast(
            SettingsChangeMessage(self,
                                  ('FOREGROUND_COLOR', 'BACKGROUND_COLOR')))

        # If requested, trigger data to update color

        if self.data_apply:
            self.app.set_data_color(settings.DATA_COLOR, settings.DATA_ALPHA)

        super(PreferencesDialog, self).accept()
Ejemplo n.º 14
0
class SliceWidget(QtWidgets.QWidget):

    label = TextProperty('_ui_label')
    slider_label = TextProperty('_ui_slider.label')
    slider_unit = TextProperty('_ui_slider.text_unit')
    slice_center = ValueProperty('_ui_slider.slider')
    mode = CurrentComboProperty('_ui_mode')
    use_world = ButtonProperty('_ui_slider.checkbox_world')

    slice_changed = QtCore.Signal(int)
    mode_changed = QtCore.Signal(str)

    def __init__(self,
                 label='',
                 world=None,
                 lo=0,
                 hi=10,
                 parent=None,
                 aggregation=None,
                 world_unit=None,
                 world_warning=False):

        super(SliceWidget, self).__init__(parent)

        if aggregation is not None:
            raise NotImplemented("Aggregation option not implemented")

        self._world = np.asarray(world)
        self._world_warning = world_warning
        self._world_unit = world_unit

        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(3, 1, 3, 1)
        layout.setSpacing(0)

        top = QtWidgets.QHBoxLayout()
        top.setContentsMargins(3, 3, 3, 3)
        label = QtWidgets.QLabel(label)
        top.addWidget(label)

        mode = QtWidgets.QComboBox()
        mode.addItem('x', 'x')
        mode.addItem('y', 'y')
        mode.addItem('slice', 'slice')
        mode.currentIndexChanged.connect(
            lambda x: self.mode_changed.emit(self.mode))
        mode.currentIndexChanged.connect(self._update_mode)
        top.addWidget(mode)

        layout.addLayout(top)

        slider = load_ui('data_slice_widget.ui',
                         None,
                         directory=os.path.dirname(__file__))
        self._ui_slider = slider

        font = slider.label_warning.font()
        font.setPointSize(font.pointSize() * 0.75)
        slider.label_warning.setFont(font)

        slider.button_first.setStyleSheet('border: 0px')
        slider.button_first.setIcon(get_icon('playback_first'))
        slider.button_prev.setStyleSheet('border: 0px')
        slider.button_prev.setIcon(get_icon('playback_prev'))
        slider.button_back.setStyleSheet('border: 0px')
        slider.button_back.setIcon(get_icon('playback_back'))
        slider.button_stop.setStyleSheet('border: 0px')
        slider.button_stop.setIcon(get_icon('playback_stop'))
        slider.button_forw.setStyleSheet('border: 0px')
        slider.button_forw.setIcon(get_icon('playback_forw'))
        slider.button_next.setStyleSheet('border: 0px')
        slider.button_next.setIcon(get_icon('playback_next'))
        slider.button_last.setStyleSheet('border: 0px')
        slider.button_last.setIcon(get_icon('playback_last'))

        slider.slider.setMinimum(lo)
        slider.slider.setMaximum(hi)
        slider.slider.setValue((lo + hi) / 2)
        slider.slider.valueChanged.connect(
            lambda x: self.slice_changed.emit(self.mode))
        slider.slider.valueChanged.connect(
            nonpartial(self.set_label_from_slider))

        slider.label.setMinimumWidth(80)
        slider.label.setText(str(slider.slider.value()))
        slider.label.editingFinished.connect(
            nonpartial(self.set_slider_from_label))

        self._play_timer = QtCore.QTimer()
        self._play_timer.setInterval(500)
        self._play_timer.timeout.connect(nonpartial(self._play_slice))

        slider.button_first.clicked.connect(
            nonpartial(self._browse_slice, 'first'))
        slider.button_prev.clicked.connect(
            nonpartial(self._browse_slice, 'prev'))
        slider.button_back.clicked.connect(
            nonpartial(self._adjust_play, 'back'))
        slider.button_stop.clicked.connect(
            nonpartial(self._adjust_play, 'stop'))
        slider.button_forw.clicked.connect(
            nonpartial(self._adjust_play, 'forw'))
        slider.button_next.clicked.connect(
            nonpartial(self._browse_slice, 'next'))
        slider.button_last.clicked.connect(
            nonpartial(self._browse_slice, 'last'))

        slider.checkbox_world.toggled.connect(
            nonpartial(self.set_label_from_slider))

        if world is None:
            self.use_world = False
            slider.checkbox_world.hide()
        else:
            self.use_world = not world_warning

        if world_unit:
            self.slider_unit = world_unit
        else:
            self.slider_unit = ''

        layout.addWidget(slider)

        self.setLayout(layout)

        self._ui_label = label
        self._ui_mode = mode
        self._update_mode()
        self._frozen = False

        self._play_speed = 0

        self.set_label_from_slider()

    def set_label_from_slider(self):
        value = self._ui_slider.slider.value()
        if self.use_world:
            text = str(self._world[value])
            if self._world_warning:
                self._ui_slider.label_warning.show()
            else:
                self._ui_slider.label_warning.hide()
            self.slider_unit = self._world_unit
        else:
            text = str(value)
            self._ui_slider.label_warning.hide()
            self.slider_unit = ''
        self._ui_slider.label.setText(text)

    def set_slider_from_label(self):
        text = self._ui_slider.label.text()
        if self.use_world:
            # Don't want to assume world is sorted, pick closest value
            value = np.argmin(np.abs(self._world - float(text)))
            self._ui_slider.label.setText(str(self._world[value]))
        else:
            value = int(text)
        self._ui_slider.slider.setValue(value)

    def _adjust_play(self, action):
        if action == 'stop':
            self._play_speed = 0
        elif action == 'back':
            if self._play_speed > 0:
                self._play_speed = -1
            else:
                self._play_speed -= 1
        elif action == 'forw':
            if self._play_speed < 0:
                self._play_speed = +1
            else:
                self._play_speed += 1
        if self._play_speed == 0:
            self._play_timer.stop()
        else:
            self._play_timer.start()
            self._play_timer.setInterval(500 / abs(self._play_speed))

    def _play_slice(self):
        if self._play_speed > 0:
            self._browse_slice('next', play=True)
        elif self._play_speed < 0:
            self._browse_slice('prev', play=True)

    def _browse_slice(self, action, play=False):

        imin = self._ui_slider.slider.minimum()
        imax = self._ui_slider.slider.maximum()
        value = self._ui_slider.slider.value()

        # If this was not called from _play_slice, we should stop the
        # animation.
        if not play:
            self._adjust_play('stop')

        if action == 'first':
            value = imin
        elif action == 'last':
            value = imax
        elif action == 'prev':
            value = value - 1
            if value < imin:
                value = imax
        elif action == 'next':
            value = value + 1
            if value > imax:
                value = imin
        else:
            raise ValueError("Action should be one of first/prev/next/last")

        self._ui_slider.slider.setValue(value)

    def _update_mode(self, *args):
        if self.mode != 'slice':
            self._ui_slider.hide()
            self._adjust_play('stop')
        else:
            self._ui_slider.show()

    def freeze(self):
        self.mode = 'slice'
        self._ui_mode.setEnabled(False)
        self._ui_slider.hide()
        self._frozen = True

    @property
    def frozen(self):
        return self._frozen
Ejemplo n.º 15
0
class PreferencesDialog(QtWidgets.QDialog):

    theme = CurrentComboTextProperty('ui.combo_theme')
    background = ColorProperty('ui.color_background')
    foreground = ColorProperty('ui.color_foreground')
    data_color = ColorProperty('ui.color_default_data')
    data_alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1))
    data_apply = ButtonProperty('ui.checkbox_apply')
    show_large_data_warning = ButtonProperty(
        'ui.checkbox_show_large_data_warning')
    individual_subset_color = ButtonProperty(
        'ui.checkbox_individual_subset_color')
    save_to_disk = ButtonProperty('ui.checkbox_save')

    def __init__(self, application, parent=None):

        super(PreferencesDialog, self).__init__(parent=parent)

        self.app = application

        self.ui = load_ui('preferences.ui',
                          self,
                          directory=os.path.dirname(__file__))

        self.ui.cancel.clicked.connect(self.reject)
        self.ui.ok.clicked.connect(self.accept)

        self.ui.combo_theme.currentIndexChanged.connect(
            nonpartial(self._update_colors_from_theme))

        # The following is needed because of a bug in Qt which means that
        # tab titles don't get scaled right.
        app = get_qapp()
        app_font = app.font()
        self.ui.tab_widget.setStyleSheet('font-size: {0}px'.format(
            app_font.pointSize()))

        from glue.config import settings
        self.background = settings.BACKGROUND_COLOR
        self.foreground = settings.FOREGROUND_COLOR
        self.data_color = settings.DATA_COLOR
        self.data_alpha = settings.DATA_ALPHA
        self.show_large_data_warning = settings.SHOW_LARGE_DATA_WARNING
        self.individual_subset_color = settings.INDIVIDUAL_SUBSET_COLOR

        self._update_theme_from_colors()

        self.panes = []

        from glue.config import preference_panes
        for label, widget_cls in sorted(preference_panes):
            pane = widget_cls()
            self.ui.tab_widget.addTab(pane, label)
            self.panes.append(pane)

    def _update_theme_from_colors(self):
        if (rgb(self.background) == (1, 1, 1)
                and rgb(self.foreground) == (0, 0, 0)
                and rgb(self.data_color) == (0.35, 0.35, 0.35)
                and np.allclose(self.data_alpha, 0.8)):
            self.theme = 'Black on White'
        elif (rgb(self.background) == (0, 0, 0)
              and rgb(self.foreground) == (1, 1, 1)
              and rgb(self.data_color) == (0.75, 0.75, 0.75)
              and np.allclose(self.data_alpha, 0.8)):
            self.theme = 'White on Black'
        else:
            self.theme = 'Custom'

    def _update_colors_from_theme(self):
        if self.theme == 'Black on White':
            self.foreground = 'black'
            self.background = 'white'
            self.data_color = '0.35'
            self.data_alpha = 0.8
        elif self.theme == 'White on Black':
            self.foreground = 'white'
            self.background = 'black'
            self.data_color = '0.75'
            self.data_alpha = 0.8
        elif self.theme != 'Custom':
            raise ValueError("Unknown theme: {0}".format(self.theme))

    def accept(self):

        # Update default settings

        from glue.config import settings
        settings.FOREGROUND_COLOR = self.foreground
        settings.BACKGROUND_COLOR = self.background
        settings.DATA_COLOR = self.data_color
        settings.DATA_ALPHA = self.data_alpha
        settings.SHOW_LARGE_DATA_WARNING = self.show_large_data_warning
        settings.INDIVIDUAL_SUBSET_COLOR = self.individual_subset_color

        for pane in self.panes:
            pane.finalize()

        # Save to disk if requested
        if self.save_to_disk:
            save_settings()

        # Trigger viewers to update defaults

        self.app._hub.broadcast(
            SettingsChangeMessage(self,
                                  ('FOREGROUND_COLOR', 'BACKGROUND_COLOR')))

        # If requested, trigger data to update color

        if self.data_apply:
            self.app.set_data_color(settings.DATA_COLOR, settings.DATA_ALPHA)

        super(PreferencesDialog, self).accept()