Beispiel #1
0
class ChoiceElement(FormElement):

    """
    A dropdown selector to choose between a set of items

    Shorthand notation is a sequence of strings or a dict::

        e = FormElement.auto({'a':1, 'b':2})
        e = FormElement.auto(['a', 'b', 'c'])
    """
    state = CurrentComboProperty('ui')

    @classmethod
    def recognizes(cls, params):
        if isinstance(params, six.string_types):
            return False
        try:
            return all(isinstance(p, six.string_types) for p in params)
        except TypeError:
            return False

    def _build_ui(self):
        w = QtGui.QComboBox()
        for p in sorted(self.params):
            w.addItem(p)

        if isinstance(self.params, list):
            self.params = dict((p, p) for p in self.params)

        w.currentIndexChanged.connect(nonpartial(self.changed))
        return w

    def value(self, layer=None, view=None):
        return self.params[self.ui.currentText()]
Beispiel #2
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)
Beispiel #3
0
class ComponenentElement(FormElement, core.hub.HubListener):

    """
    A dropdown selector to choose a component

    The shorthand notation is 'att'::

        e = FormElement.auto('att')
    """
    _component = CurrentComboProperty('ui')

    @property
    def state(self):
        return self._component

    @state.setter
    def state(self, value):
        self._update_components()
        if value is None:
            return
        self._component = value

    @classmethod
    def recognizes(cls, params):
        return params == 'att'

    def _build_ui(self):
        result = QtGui.QComboBox()
        result.currentIndexChanged.connect(nonpartial(self.changed))
        return result

    def value(self, layer=None, view=None):
        cid = self._component
        if layer is None or cid is None:
            return AttributeInfo.make(cid, [], None)
        return AttributeInfo.from_layer(layer, cid, view)

    def _list_components(self):
        """
        Determine which components to list.


        This can be overridden by subclassing to limit which components are
        visible to the user.

        """
        comps = list(set([c for l in self.container.layers
                          for c in l.data.components if not c._hidden]))
        comps = sorted(comps, key=lambda x: x.label)
        return comps

    def _update_components(self):
        combo = self.ui
        old = self._component

        combo.blockSignals(True)
        combo.clear()

        comps = self._list_components()
        for c in comps:
            combo.addItem(c.label, userData=c)

        try:
            combo.setCurrentIndex(comps.index(old))
        except ValueError:
            combo.setCurrentIndex(0)

        combo.blockSignals(False)

    def register_to_hub(self, hub):
        hub.subscribe(self, core.message.ComponentsChangedMessage,
                      nonpartial(self._update_components))

    def add_data(self, data):
        self._update_components()
Beispiel #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 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
Beispiel #6
0
class FitContext(SpectrumContext):
    """
    Mode to fit a range of a spectrum with a model fitter.

    Fitters are taken from user-defined fit plugins, or
    :class:`~glue.core.fitters.BaseFitter1D` subclasses
    """
    error = CurrentComboProperty('ui.uncertainty_combo')
    fitter = CurrentComboProperty('ui.profile_combo')

    def _setup_grip(self):
        self.grip = self.main.profile.new_range_grip()

    def _setup_widget(self):
        self.ui = load_ui('spectrum_fit_panel.ui',
                          None,
                          directory=os.path.dirname(__file__))
        self.ui.uncertainty_combo.hide()
        self.ui.uncertainty_label.hide()
        font = QtGui.QFont("Courier")
        font.setStyleHint(font.Monospace)
        self.ui.results_box.document().setDefaultFont(font)
        self.ui.results_box.setLineWrapMode(self.ui.results_box.NoWrap)
        self.widget = self.ui

        for fitter in list(fit_plugin):
            self.ui.profile_combo.addItem(fitter.label, userData=fitter())

    def _edit_model_options(self):

        d = FitSettingsWidget(self.fitter)
        d.exec_()

    def _connect(self):
        self.ui.fit_button.clicked.connect(nonpartial(self.fit))
        self.ui.clear_button.clicked.connect(nonpartial(self.clear))
        self.ui.settings_button.clicked.connect(
            nonpartial(self._edit_model_options))

    def fit(self):
        """
        Fit a model to the data

        The fitting happens on a dedicated thread, to keep the UI
        responsive
        """
        xlim = self.grip.range
        fitter = self.fitter

        def on_success(result):
            fit_result, _, _, _ = result
            self._report_fit(fitter.summarize(*result))
            self.main.profile.plot_fit(fitter, fit_result)

        def on_fail(exc_info):
            exc = '\n'.join(traceback.format_exception(*exc_info))
            self._report_fit("Error during fitting:\n%s" % exc)

        def on_done():
            self.ui.fit_button.setText("Fit")
            self.ui.fit_button.setEnabled(True)
            self.canvas.draw()

        self.ui.fit_button.setText("Running...")
        self.ui.fit_button.setEnabled(False)

        w = Worker(self.main.profile.fit, fitter, xlim=xlim)
        w.result.connect(on_success)
        w.error.connect(on_fail)
        w.finished.connect(on_done)

        self._fit_worker = w  # hold onto a reference
        w.start()

    def _report_fit(self, report):
        self.ui.results_box.document().setPlainText(report)

    def clear(self):
        self.ui.results_box.document().setPlainText('')
        self.main.profile.clear_fit()
        self.canvas.draw()
Beispiel #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)
Beispiel #8
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
Beispiel #9
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)
Beispiel #10
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)
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
Beispiel #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
Beispiel #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