Exemple #1
0
class NumberElement(FormElement):

    """
    A form element representing a number

    The shorthand is a tuple of 2 or 3 numbers:
    (min, max) or (min, max default)::

        e = FormElement.auto((0., 1.))
    """
    state = ValueProperty('ui')

    @classmethod
    def recognizes(cls, params):
        try:
            if len(params) not in [2, 3]:
                return False
            return all(isinstance(p, six.integer_types + (float,)) for p in params)
        except TypeError:
            return False

    def _build_ui(self):
        w = LabeledSlider(*self.params[:3])
        w.valueChanged.connect(nonpartial(self.changed))
        return w

    def value(self, layer=None, view=None):
        return self.ui.value()
Exemple #2
0
class TextBoxElement(FormElement):
    """
    A form element representing a generic textbox

    The shorthand is any string starting with an _.::

        e = FormElement.auto("_default")

    Everything after the underscore is taken as the default value.
    """
    state = ValueProperty('ui')

    def _build_ui(self):
        self._widget = GenericTextBox()
        self._widget.textChanged.connect(nonpartial(self.changed))
        self.set_value(self.params[1:])
        return self._widget

    def value(self, layer=None, view=None):
        return self._widget.text()

    def set_value(self, val):
        self._widget.setText(str(val))

    @classmethod
    def recognizes(cls, params):
        try:
            if isinstance(params, str) & params.startswith('_'):
                return True
        except AttributeError:
            return None
Exemple #3
0
class FloatElement(FormElement):
    """
    A form element representing a generic number box.

    The shorthand is any number::

        e = FormElement.auto(2)

    The number itself is taken as the default value.
    """
    state = ValueProperty('ui')

    def _build_ui(self):
        self._widget = GenericTextBox()
        self._widget.textChanged.connect(nonpartial(self.changed))
        self.set_value(self.params)
        return self._widget

    def value(self, layer=None, view=None):
        try:
            return float(self._widget.text())
        except ValueError:
            return None

    def set_value(self, val):
        self._widget.setText(str(val))

    @classmethod
    def recognizes(cls, params):
        return isinstance(params, (int, float)) and not isinstance(params, bool)
Exemple #4
0
class ScatterLayerStyleWidget(QtGui.QWidget):

    size = ValueProperty('ui.value_size')
    symbol = CurrentComboProperty('ui.combo_symbol')
    alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1))

    def __init__(self, layer_artist):

        super(ScatterLayerStyleWidget, self).__init__()

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

        self._setup_symbol_combo()

        self.layer = layer_artist.layer

        # Set up connections
        self._connect_global()

        # Set initial values
        self.symbol = self.layer.style.marker
        self.size = self.layer.style.markersize
        self.ui.label_color.setColor(self.layer.style.color)
        self.alpha = self.layer.style.alpha

    def _connect_global(self):
        connect_current_combo(self.layer.style, 'marker', self.ui.combo_symbol)
        connect_value(self.layer.style, 'markersize', self.ui.value_size)
        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_symbol_combo(self):
        self._symbols = list(POINT_ICONS.keys())
        for idx, symbol in enumerate(self._symbols):
            icon = symbol_icon(symbol)
            self.ui.combo_symbol.addItem(icon, '', userData=symbol)
        self.ui.combo_symbol.setIconSize(QtCore.QSize(20, 20))
        self.ui.combo_symbol.setMinimumSize(10, 32)
Exemple #5
0
class HistogramLayerStyleWidget(QtWidgets.QWidget):

    alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1))

    def __init__(self, layer_artist):

        super(HistogramLayerStyleWidget, self).__init__()

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

        self.layer = layer_artist.layer

        # Set up connections
        self._connect_global()

        # Set initial values
        self.ui.label_color.setColor(self.layer.style.color)
        self.alpha = self.layer.style.alpha

    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))
Exemple #6
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
class ScatterLayerStyleWidget(QtWidgets.QWidget):

    # Size-related GUI elements
    size_mode = CurrentComboTextProperty('ui.combo_size_mode')
    size_attribute = CurrentComboProperty('ui.combo_size_attribute')
    size_vmin = FloatLineProperty('ui.value_size_vmin')
    size_vmax = FloatLineProperty('ui.value_size_vmax')
    size = FloatLineProperty('ui.value_fixed_size')

    try:
        size_scaling = ValueProperty('ui.slider_size_scaling', value_range=(0.1, 10), log=True)
    except TypeError:  # Glue < 0.8
        size_scaling = ValueProperty('ui.slider_size_scaling')

    # Color-related GUI elements
    color_mode = CurrentComboTextProperty('ui.combo_color_mode')
    cmap_attribute = CurrentComboProperty('ui.combo_cmap_attribute')
    cmap_vmin = FloatLineProperty('ui.value_cmap_vmin')
    cmap_vmax = FloatLineProperty('ui.value_cmap_vmax')
    cmap = CurrentComboProperty('ui.combo_cmap')
    alpha = ValueProperty('ui.slider_alpha')

    def __init__(self, layer_artist):

        super(ScatterLayerStyleWidget, 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 size and color options
        self._setup_size_options()
        self._setup_color_options()
        self._connect_global()

        # Set initial values
        self.layer_artist.size = self.layer.style.markersize
        self.layer_artist.size_scaling = 1
        self.layer_artist.size_mode = 'fixed'
        self.size_mode = 'Fixed'
        self.layer_artist.color = self.layer.style.color
        self.layer_artist.alpha = self.layer.style.alpha
        self.layer_artist.color_mode = 'fixed'
        self.color_mode = 'Fixed'
        self.ui.combo_size_attribute.setCurrentIndex(0)
        self.ui.combo_cmap_attribute.setCurrentIndex(0)
        self.ui.combo_cmap.setCurrentIndex(0)
        self.layer_artist.visible = True

        self._update_size_mode()
        self._update_color_mode()

    def _connect_global(self):
        connect_float_edit(self.layer.style, 'markersize', self.ui.value_fixed_size)
        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 _disconnect_global(self):
        # FIXME: Requires the ability to disconnect connections
        pass

    def _setup_size_options(self):

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

        # Set up connections with layer artist
        connect_float_edit(self.layer_artist, 'size', self.ui.value_fixed_size)
        connect_current_combo(self.layer_artist, 'size_attribute', self.ui.combo_size_attribute)
        connect_float_edit(self.layer_artist, 'size_vmin', self.ui.value_size_vmin)
        connect_float_edit(self.layer_artist, 'size_vmax', self.ui.value_size_vmax)
        connect_value(self.layer_artist, 'size_scaling', self.ui.slider_size_scaling, value_range=(0.1, 10), log=True)

        # Set up internal connections
        self.ui.combo_size_mode.currentIndexChanged.connect(self._update_size_mode)
        self.ui.combo_size_attribute.currentIndexChanged.connect(self._update_size_limits)
        self.ui.button_flip_size.clicked.connect(self._flip_size)

    def _update_size_mode(self):

        self.layer_artist.size_mode = self.size_mode.lower()

        if self.size_mode == "Fixed":
            self.ui.size_row_2.hide()
            self.ui.combo_size_attribute.hide()
            self.ui.value_fixed_size.show()
        else:
            self.ui.value_fixed_size.hide()
            self.ui.combo_size_attribute.show()
            self.ui.size_row_2.show()

    def _update_size_limits(self):

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

        if self.size_attribute in self._size_limits:
            self.size_vmin, self.size_vmax = self._size_limits[self.size_attribute]
        else:
            self.size_vmin, self.size_vmax = self.default_limits(self.size_attribute)
            self._size_limits[self.size_attribute] = self.size_vmin, self.size_vmax

    def _flip_size(self):
        self.size_vmin, self.size_vmax = self.size_vmax, self.size_vmin

    def _setup_color_options(self):

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

        # Set up connections with layer artist
        connect_color(self.layer_artist, 'color', self.ui.label_color)
        connect_current_combo(self.layer_artist, 'cmap_attribute', self.ui.combo_cmap_attribute)
        connect_float_edit(self.layer_artist, 'cmap_vmin', self.ui.value_cmap_vmin)
        connect_float_edit(self.layer_artist, 'cmap_vmax', self.ui.value_cmap_vmax)
        connect_current_combo(self.layer_artist, 'cmap', self.ui.combo_cmap)
        connect_value(self.layer_artist, 'alpha', self.ui.slider_alpha, value_range=(0, 1))

        # Set up internal connections
        self.ui.combo_color_mode.currentIndexChanged.connect(self._update_color_mode)
        self.ui.combo_cmap_attribute.currentIndexChanged.connect(self._update_cmap_limits)
        self.ui.button_flip_cmap.clicked.connect(self._flip_cmap)

    def _update_color_mode(self):

        self.layer_artist.color_mode = self.color_mode.lower()

        if self.color_mode == "Fixed":
            self.ui.color_row_2.hide()
            self.ui.color_row_3.hide()
            self.ui.combo_cmap_attribute.hide()
            self.ui.spacer_color_label.show()
            self.ui.label_color.show()
        else:
            self.ui.label_color.hide()
            self.ui.combo_cmap_attribute.show()
            self.ui.spacer_color_label.hide()
            self.ui.color_row_2.show()
            self.ui.color_row_3.show()

    def _update_cmap_limits(self):

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

        if self.cmap_attribute in self._cmap_limits:
            self.cmap_vmin, self.cmap_vmax = self._cmap_limits[self.cmap_attribute]
        else:
            self.cmap_vmin, self.cmap_vmax = self.default_limits(self.cmap_attribute)
            self._cmap_limits[self.cmap_attribute] = self.cmap_vmin, self.cmap_vmax

    def _flip_cmap(self):
        self.cmap_vmin, self.cmap_vmax = self.cmap_vmax, self.cmap_vmin

    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 = np.nanmin(self.layer.data[attribute])
            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
Exemple #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()
Exemple #9
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
class IsosurfaceLayerStyleWidget(QtWidgets.QWidget):

    # GUI elements
    attribute = CurrentComboProperty('ui.combo_attribute')
    level = FloatLineProperty('ui.value_level')
    alpha = ValueProperty('ui.slider_alpha')

    def __init__(self, layer_artist):

        super(IsosurfaceLayerStyleWidget, 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_levels()
        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 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, 'level', self.ui.value_level)
        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.value_level.editingFinished.connect(self._cache_levels)
        self.ui.combo_attribute.currentIndexChanged.connect(
            self._update_levels)

    def _update_levels(self):

        if isinstance(self.layer, Subset):
            self.level = 0.5
            return

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

        if self.attribute in self._levels:
            self.level = self._levels[self.attribute]
        else:
            self.level = self.default_levels(self.attribute)
            self._levels[self.attribute] = self.level

    def _cache_levels(self):
        if not isinstance(self.layer,
                          Subset) or self.layer_artist.subset_mode == 'data':
            self._levels[self.attribute] = self.level

    def default_levels(self, attribute):
        # For subsets, we want to compute the levels based on the full
        # dataset not just the subset.
        if isinstance(self.layer, Subset):
            return 0.5
        else:
            return np.nanmedian(self.layer[attribute])

    @property
    def visible_components(self):
        if isinstance(self.layer, Subset):
            return self.layer.data.visible_components
        else:
            return self.layer.visible_components
Exemple #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
Exemple #12
0
class SliceWidget(QtGui.QWidget):
    label = TextProperty('_ui_label')
    slice_center = ValueProperty('_ui_slider.slider')
    mode = CurrentComboProperty('_ui_mode')

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

    def __init__(self, label='', pix2world=None, lo=0, hi=10,
                 parent=None, aggregation=None):
        super(SliceWidget, self).__init__(parent)
        if aggregation is not None:
            raise NotImplemented("Aggregation option not implemented")
        if pix2world is not None:
            raise NotImplemented("Pix2world option not implemented")

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

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

        mode = QtGui.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__))
        slider.slider

        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(lambda x: slider.label.setText(str(x)))

        slider.label.setMinimumWidth(50)
        slider.label.setText(str(slider.slider.value()))
        slider.label.textChanged.connect(lambda x: slider.slider.setValue(int(x)))

        slider.first.clicked.connect(nonpartial(self._browse_slice, 'first'))
        slider.prev.clicked.connect(nonpartial(self._browse_slice, 'prev'))
        slider.next.clicked.connect(nonpartial(self._browse_slice, 'next'))
        slider.last.clicked.connect(nonpartial(self._browse_slice, 'last'))

        layout.addWidget(slider)

        self.setLayout(layout)

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

    def _browse_slice(self, action):
        imin = self._ui_slider.slider.minimum()
        imax = self._ui_slider.slider.maximum()
        value = self._ui_slider.slider.value()
        if action == 'first':
            value = imin
        elif action == 'last':
            value = imax
        elif action == 'prev':
            value = max(value - 1, imin)
        elif action == 'next':
            value = min(value + 1, imax)
        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()
        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
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
Exemple #14
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()
Exemple #15
0
class SliceWidget(QtGui.QWidget):
    label = TextProperty('_ui_label')
    slice_center = ValueProperty('_ui_slider.slider')
    mode = CurrentComboProperty('_ui_mode')

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

    def __init__(self,
                 label='',
                 pix2world=None,
                 lo=0,
                 hi=10,
                 parent=None,
                 aggregation=None):

        super(SliceWidget, self).__init__(parent)

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

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

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

        mode = QtGui.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__))
        slider.slider

        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(
            lambda x: slider.label.setText(str(x)))

        slider.label.setMinimumWidth(50)
        slider.label.setText(str(slider.slider.value()))
        slider.label.textChanged.connect(
            lambda x: slider.slider.setValue(int(x)))

        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'))

        layout.addWidget(slider)

        self.setLayout(layout)

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

        self._play_speed = 0

    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
Exemple #16
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()
Exemple #17
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()