示例#1
0
class LinkEquation(QtWidgets.QWidget):

    """ Interactively define ComponentLinks from existing functions

    This widget inspects the calling signatures of helper functions,
    and presents the user with an interface for assigning componentIDs
    to the input and output arguments. It also generates ComponentLinks
    from this information.

    ComponentIDs are assigned to arguments via drag and drop. This
    widget is used within the LinkEditor dialog

    Usage::

       widget = LinkEquation()
    """

    category = SelectionCallbackProperty()
    function = SelectionCallbackProperty()

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

        # Set up mapping of function/helper name -> function/helper tuple. For the helpers, we use the 'display' name if available.
        self._argument_widgets = []
        self._output_widget = ArgumentWidget("")

        # pyqt4 can't take self as second argument here
        # for some reason. Manually embed
        self._ui = load_ui('link_equation.ui', None,
                           directory=os.path.dirname(__file__))

        l = QtWidgets.QHBoxLayout()
        l.addWidget(self._ui)
        self.setLayout(l)

        self._ui.outputs_layout.addWidget(self._output_widget)

        self._populate_category_combo()
        self.category = 'General'
        self._populate_function_combo()
        self._connect()
        self._setup_editor()

    def set_result_visible(self, state):
        self._output_widget.setVisible(state)
        self._ui.output_label.setVisible(state)

    def is_helper(self):
        return self.function is not None and \
            type(self.function).__name__ == 'LinkHelper'

    def is_function(self):
        return self.function is not None and \
            type(self.function).__name__ == 'LinkFunction'

    @property
    def signature(self):
        """ Returns the ComponentIDs assigned to the input and output arguments

        :rtype: tuple of (input, output). Input is a list of ComponentIDs.
                output is a ComponentID
        """
        inp = [a.component_id for a in self._argument_widgets]
        out = self._output_widget.component_id
        return inp, out

    @signature.setter
    def signature(self, inout):
        inp, out = inout
        for i, a in zip(inp, self._argument_widgets):
            a.component_id = i
        self._output_widget.component_id = out

    @messagebox_on_error("Failed to create links")
    def links(self):
        """ Create ComponentLinks from the state of the widget

        :rtype: list of ComponentLinks that can be created.

        If no links can be created (e.g. because of missing input),
        the empty list is returned
        """
        inp, out = self.signature
        if self.is_function():
            using = self.function.function
            if not all(inp) or not out:
                return []
            link = core.component_link.ComponentLink(inp, out, using)
            return [link]
        if self.is_helper():
            helper = self.function.helper
            if not all(inp):
                return []
            return helper(*inp)

    def _update_add_enabled(self):
        state = True
        for a in self._argument_widgets:
            state = state and a.component_id is not None
        if self.is_function():
            state = state and self._output_widget.component_id is not None

    def _connect(self):
        signal = self._ui.function.currentIndexChanged
        signal.connect(nonpartial(self._setup_editor))
        signal.connect(nonpartial(self._update_add_enabled))
        self._output_widget.editor.textChanged.connect(nonpartial(self._update_add_enabled))
        self._ui.category.currentIndexChanged.connect(self._populate_function_combo)

    def clear_inputs(self):
        for w in self._argument_widgets:
            w.clear()
        self._output_widget.clear()

    def _setup_editor(self):
        if self.is_function():
            self._setup_editor_function()
        elif self.is_helper():
            self._setup_editor_helper()

    def _setup_editor_function(self):
        """ Prepare the widget for the active function."""
        assert self.is_function()
        self.set_result_visible(True)
        func = self.function.function
        args = getfullargspec(func)[0]
        label = function_label(self.function)
        self._ui.info.setText(label)
        self._output_widget.label = self.function.output_labels[0]
        self._clear_input_canvas()
        for a in args:
            self._add_argument_widget(a)

    def _setup_editor_helper(self):
        """Setup the editor for the selected link helper"""
        assert self.is_helper()
        self.set_result_visible(False)
        label = helper_label(self.function)
        args = self.function.input_labels
        self._ui.info.setText(label)

        self._clear_input_canvas()
        for a in args:
            self._add_argument_widget(a)

    def _add_argument_widget(self, argument):
        """ Create and add a single argument widget to the input canvas
        :param arguement: The argument name (string)
        """
        widget = ArgumentWidget(argument)
        widget.editor.textChanged.connect(nonpartial(self._update_add_enabled))
        self._ui.inputs_layout.addWidget(widget)
        self._argument_widgets.append(widget)

    def _clear_input_canvas(self):
        """ Remove all widgets from the input canvas """
        layout = self._ui.inputs_layout
        for a in self._argument_widgets:
            layout.removeWidget(a)
            a.hide()
            a.close()

        self._argument_widgets = []

    def _populate_category_combo(self):
        f = [f for f in link_function.members if len(f.output_labels) == 1]
        categories = sorted(set(l.category for l in f + link_helper.members))
        LinkEquation.category.set_choices(self, categories)
        connect_combo_selection(self, 'category', self._ui.category)

    def _populate_function_combo(self):
        """ Add name of functions to function combo box """
        f = [f for f in link_function.members if len(f.output_labels) == 1]
        functions = [l for l in f + link_helper.members if l.category == self.category]
        LinkEquation.function.set_choices(self, functions)
        LinkEquation.function.set_display_func(self, lambda l: get_function_name(l[0]))
        connect_combo_selection(self, 'function', self._ui.function)
class OpenSpaceViewerState(ViewerState):

    mode = SelectionCallbackProperty(default_index=0)
    frame = SelectionCallbackProperty(default_index=0)

    lon_att = SelectionCallbackProperty(default_index=0)
    lat_att = SelectionCallbackProperty(default_index=1)
    lum_att = SelectionCallbackProperty(default_index=0)
    vel_att = SelectionCallbackProperty(default_index=1)
    alt_att = SelectionCallbackProperty(default_index=2)
    alt_unit = SelectionCallbackProperty(default_index=4)
    alt_type = SelectionCallbackProperty(default_index=0)

    layers = ListCallbackProperty()

    def __init__(self, **kwargs):

        super(OpenSpaceViewerState, self).__init__()

        OpenSpaceViewerState.mode.set_choices(self, MODES)
        OpenSpaceViewerState.frame.set_choices(self, CELESTIAL_FRAMES)
        OpenSpaceViewerState.alt_unit.set_choices(
            self, [str(x) for x in ALTERNATIVE_UNITS])
        OpenSpaceViewerState.alt_type.set_choices(self, ALTERNATIVE_TYPES)

        self.lon_att_helper = ComponentIDComboHelper(self,
                                                     'lon_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False)

        self.lat_att_helper = ComponentIDComboHelper(self,
                                                     'lat_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False)

        self.lum_att_helper = ComponentIDComboHelper(self,
                                                     'lum_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False)

        self.vel_att_helper = ComponentIDComboHelper(self,
                                                     'vel_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False)

        self.alt_att_helper = ComponentIDComboHelper(self,
                                                     'alt_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False)

        self.add_callback('layers', self._on_layers_changed)
        self._on_layers_changed()

        self.update_from_dict(kwargs)

    def _on_layers_changed(self, *args):
        self.lon_att_helper.set_multiple_data(self.layers_data)
        self.lat_att_helper.set_multiple_data(self.layers_data)
        self.lum_att_helper.set_multiple_data(self.layers_data)
        self.vel_att_helper.set_multiple_data(self.layers_data)
        self.alt_att_helper.set_multiple_data(self.layers_data)

    def _update_priority(self, name):
        if name == 'layers':
            return 2
        else:
            return 0
示例#3
0
class CubeVizLayout(QtWidgets.QWidget):
    """
    The 'CubeViz' layout, with three image viewers and one spectrum viewer.
    """

    LABEL = "CubeViz"
    subWindowActivated = QtCore.Signal(object)

    single_viewer_attribute = SelectionCallbackProperty(default_index=0)
    viewer1_attribute = SelectionCallbackProperty(default_index=0)
    viewer2_attribute = SelectionCallbackProperty(default_index=1)
    viewer3_attribute = SelectionCallbackProperty(default_index=2)

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

        if not hasattr(session.application, '_has_cubeviz_toolbar'):
            cubeviz_toolbar = CubevizToolbar(application=session.application)
            session.application.insertToolBar(session.application._data_toolbar,
                                              cubeviz_toolbar)

        self.session = session
        self._has_data = False
        self._wavelengths = None
        self._option_buttons = []

        self._data = None

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

        # Create the views and register to the hub.
        self.single_view = WidgetWrapper(CubevizImageViewer(self.session, cubeviz_layout=self), tab_widget=self)
        self.left_view = WidgetWrapper(CubevizImageViewer(self.session, cubeviz_layout=self), tab_widget=self)
        self.middle_view = WidgetWrapper(CubevizImageViewer(self.session, cubeviz_layout=self), tab_widget=self)
        self.right_view = WidgetWrapper(CubevizImageViewer(self.session, cubeviz_layout=self), tab_widget=self)
        self.specviz = WidgetWrapper(SpecVizViewer(self.session), tab_widget=self)

        self.single_view._widget.register_to_hub(self.session.hub)
        self.left_view._widget.register_to_hub(self.session.hub)
        self.middle_view._widget.register_to_hub(self.session.hub)
        self.right_view._widget.register_to_hub(self.session.hub)
        self.specviz._widget.register_to_hub(self.session.hub)

        self.all_views = [self.single_view, self.left_view, self.middle_view, self.right_view]

        # TODO: determine whether to rename this or get rid of it
        self.cube_views = self.all_views
        self.split_views = self.cube_views[1:]

        self._synced_checkboxes = [
            self.ui.singleviewer_synced_checkbox,
            self.ui.viewer1_synced_checkbox,
            self.ui.viewer2_synced_checkbox,
            self.ui.viewer3_synced_checkbox
        ]

        for view, checkbox in zip(self.all_views, self._synced_checkboxes):
            view._widget.assign_synced_checkbox(checkbox)

        # Add the views to the layouts.
        self.ui.single_image_layout.addWidget(self.single_view)
        self.ui.image_row_layout.addWidget(self.left_view)
        self.ui.image_row_layout.addWidget(self.middle_view)
        self.ui.image_row_layout.addWidget(self.right_view)

        self.ui.specviz_layout.addWidget(self.specviz)

        self.subWindowActivated.connect(self._update_active_view)

        self.ui.sync_button.clicked.connect(self._on_sync_click)
        self.ui.button_toggle_image_mode.clicked.connect(
            self._toggle_image_mode)

        # This is a list of helpers for the viewer combo boxes. New data
        # collections should be added to each helper in this list using the
        # ``append_data`` method to ensure that the new data components are
        # populated into the combo boxes.
        self._viewer_combo_helpers = []

        # This tracks the current positions of cube viewer axes when they are hidden
        self._viewer_axes_positions = []

        # Indicates whether cube viewer toolbars are currently visible or not
        self._toolbars_visible = True

        self._slice_controller = SliceController(self)
        self._overlay_controller = OverlayController(self)
        self._units_controller = UnitController(self)

        # Add menu buttons to the cubeviz toolbar.
        self._init_menu_buttons()

        self.sync = {}
        # Track the slice index of the synced viewers. This is updated by the
        # slice controller
        self.synced_index = None

        app = get_qapp()
        app.installEventFilter(self)
        self._last_click = None
        self._active_view = None
        self._active_cube = None
        self._last_active_view = None
        self._active_split_cube = None

        # Set the default to parallel image viewer
        self._single_viewer_mode = False
        self.ui.button_toggle_image_mode.setText('Single Image Viewer')
        self.ui.viewer_control_frame.setCurrentIndex(0)

        # Add this class to the specviz dispatcher watcher
        dispatch.setup(self)

    def _init_menu_buttons(self):
        """
        Add the two menu buttons to the tool bar. Currently two are defined:
            View - for changing the view of the active window
            Data Processing - for applying a data processing step to the data.

        :return:
        """
        self._option_buttons = [
            self.ui.view_option_button,
            self.ui.cube_option_button
        ]

        # Create the View Menu
        view_menu = self._dict_to_menu(OrderedDict([
            ('Hide Axes', ['checkable', self._toggle_viewer_axes]),
            ('Hide Toolbars', ['checkable', self._toggle_toolbars]),
            ('Wavelength Units', lambda: self._open_dialog('Wavelength Units', None))
        ]))
        self.ui.view_option_button.setMenu(view_menu)

        # Create the Data Processing Menu
        cube_menu = self._dict_to_menu(OrderedDict([
            ('Collapse Cube', lambda: self._open_dialog('Collapse Cube', None)),
            ('Spatial Smoothing', lambda: self._open_dialog('Spatial Smoothing', None)),
            ('Moment Maps', lambda: self._open_dialog('Moment Maps', None)),
            ('Arithmetic Operations', lambda: self._open_dialog('Arithmetic Operations', None))
        ]))
        self.ui.cube_option_button.setMenu(cube_menu)

    def _dict_to_menu(self, menu_dict):
        '''Stolen shamelessly from specviz. Thanks!'''
        menu_widget = QMenu()
        for k, v in menu_dict.items():
            if isinstance(v, dict):
                new_menu = menu_widget.addMenu(k)
                self._dict_to_menu(v, menu_widget=new_menu)
            else:
                act = QAction(k, menu_widget)

                if isinstance(v, list):
                    if v[0] == 'checkable':
                        v = v[1]
                        act.setCheckable(True)
                        act.setChecked(False)

                act.triggered.connect(v)
                menu_widget.addAction(act)
        return menu_widget

    def _handle_settings_change(self, message):
        if isinstance(message, SettingsChangeMessage):
            self._slice_controller.update_index(self.synced_index)

    def _set_pos_and_margin(self, axes, pos, marg):
        axes.set_position(pos)
        freeze_margins(axes, marg)

    def _hide_viewer_axes(self):
        for viewer in self.cube_views:
            viewer._widget.toggle_hidden_axes(True)
            axes = viewer._widget.axes
            # Save current axes position and margins so they can be restored
            pos = axes.get_position(), axes.resizer.margins
            self._viewer_axes_positions.append(pos)
            self._set_pos_and_margin(axes, [0, 0, 1, 1], [0, 0, 0, 0])
            viewer._widget.figure.canvas.draw()

    def _toggle_viewer_axes(self):
        # If axes are currently hidden, restore the original positions
        if self._viewer_axes_positions:
            for viewer, pos in zip(self.cube_views, self._viewer_axes_positions):
                viewer._widget.toggle_hidden_axes(False)
                axes = viewer._widget.axes
                self._set_pos_and_margin(axes, *pos)
                viewer._widget.figure.canvas.draw()
            self._viewer_axes_positions = []
        # Record current positions if axes are currently hidden and hide them
        else:
            self._hide_viewer_axes()

    def _toggle_toolbars(self):
        self._toolbars_visible = not self._toolbars_visible
        for viewer in self.cube_views:
            viewer._widget.toolbar.setVisible(self._toolbars_visible)

    def _open_dialog(self, name, widget):

        if name == 'Collapse Cube':
            ex = collapse_cube.CollapseCube(self._data, parent=self, allow_preview=True)

        if name == 'Spatial Smoothing':
            ex = smoothing.SelectSmoothing(self._data, parent=self, allow_preview=True)

        if name == 'Arithmetic Operations':
            ex = arithmetic_gui.SelectArithmetic(self._data, self.session.data_collection, parent=self)

        if name == "Moment Maps":
            mm_gui = moment_maps.MomentMapsGUI(
                self._data, self.session.data_collection, parent=self)
            mm_gui.display()

        if name == 'Wavelength Units':
            current_unit = self._units_controller.units_titles.index(self._units_controller._new_units.long_names[0].title())
            wavelength, ok_pressed = QInputDialog.getItem(self, "Pick a wavelength", "Wavelengths:", self._units_controller.units_titles, current_unit, False)
            if ok_pressed:
                self._units_controller.on_combobox_change(wavelength)

    @property
    def data_components(self):
        return self._data.main_components + self._data.derived_components

    @property
    def component_labels(self):
        return [str(cid) for cid in self.data_components]

    def refresh_viewer_combo_helpers(self):
        for i, helper in enumerate(self._viewer_combo_helpers):
            helper.refresh()

    @dispatch.register_listener("apply_operations")
    def apply_to_cube(self, stack):
        """
        Listen for messages from specviz about possible spectral analysis
        operations that may be applied to the entire cube.
        """

        # Retrieve the current cube data object
        operation_handler = SpectralOperationHandler(self._data, stack=stack,
                                                     parent=self)
        operation_handler.exec_()

    def add_new_data_component(self, component_id):

        self.refresh_viewer_combo_helpers()

        if self._active_view in self.all_views:
            view_index = self.all_views.index(self._active_view)
            self.change_viewer_component(view_index, component_id)

    def remove_data_component(self, component_id):
        pass

    def _enable_option_buttons(self):
        for button in self._option_buttons:
            button.setEnabled(True)
        self.ui.sync_button.setEnabled(True)

    def _get_change_viewer_combo_func(self, combo, view_index):

        def _on_viewer_combo_change(dropdown_index):

            # This function gets called whenever one of the viewer combos gets
            # changed. The active combo is the one that comes from the parent
            # _get_change_viewer_combo_func function.

            # Find the relevant viewer
            viewer = self.all_views[view_index].widget()

            # Get the label of the component and the component ID itself
            label = combo.currentText()
            component = combo.currentData()

            viewer.has_2d_data = component.parent[label].ndim == 2

            # If the user changed the current component, stop previewing
            # smoothing.
            if viewer.is_smoothing_preview_active:
                viewer.end_smoothing_preview()

            # Change the title and unit shown in the viwer
            viewer.update_component_unit_label(component)
            viewer.update_axes_title(title=str(label))

            # Change the viewer's reference data to be the data containing the
            # current component.
            viewer.state.reference_data = component.parent

            # The viewer may have multiple layers, for instance layers for
            # the main cube and for any overlay datasets, as well as subset
            # layers. We go through all the layers and make sure that for the
            # layer which corresponds to the current dataset, the correct
            # attribute is shown.
            for layer_artist in viewer.layers:
                layer_state = layer_artist.state
                if layer_state.layer is component.parent:

                    # We call _update_attribute here manually so that if this
                    # function gets called before _update_attribute, it gets
                    # called before we try and set the attribute below
                    # (_update_attribute basically updates the internal list
                    # of available attributes for the attribute combo)
                    layer_state._update_attribute()
                    layer_state.attribute = component

                    # We then also make sure that this layer artist is the
                    # one that is selected so that if the user uses e.g. the
                    # contrast tool, it will change the right layer
                    viewer._view.layer_list.select_artist(layer_artist)

            # If the combo corresponds to the currently active cube viewer,
            # either activate or deactivate the slice slider as appropriate.
            if self.all_views[view_index] is self._active_cube:
                self._slice_controller.set_enabled(not viewer.has_2d_data)

            # If contours are being currently shown, we need to force a redraw
            if viewer.is_contour_active:
                viewer.draw_contour()

        return _on_viewer_combo_change

    def _enable_viewer_combo(self, data, index, combo_label, selection_label):
        combo = getattr(self.ui, combo_label)
        connect_combo_selection(self, selection_label, combo)
        helper = ComponentIDComboHelper(self, selection_label)
        helper.set_multiple_data([data])
        combo.setEnabled(True)
        combo.currentIndexChanged.connect(self._get_change_viewer_combo_func(combo, index))
        self._viewer_combo_helpers.append(helper)

    def _enable_all_viewer_combos(self, data):
        """
        Setup the dropdown boxes that correspond to each of the left, middle,
        and right views.  The combo boxes initially are set to have FLUX,
        Error, DQ but will be dynamic depending on the type of data available
        either from being loaded in or by being processed.

        :return:
        """
        self._enable_viewer_combo(
            data, 0, 'single_viewer_combo', 'single_viewer_attribute')
        view = self.all_views[0].widget()
        component = getattr(self, 'single_viewer_attribute')
        view.update_component_unit_label(component)
        view.update_axes_title(component.label)

        for i in range(1,4):
            combo_label = 'viewer{0}_combo'.format(i)
            selection_label = 'viewer{0}_attribute'.format(i)
            self._enable_viewer_combo(data, i, combo_label, selection_label)
            view = self.all_views[i].widget()
            component = getattr(self, selection_label)
            view.update_component_unit_label(component)
            view.update_axes_title(component.label)

    def change_viewer_component(self, view_index, component_id, force=False):
        """
        Given a viewer at an index view_index, change combo
        selection to component at an index component_index.
        :param view_index: int: Viewer index
        :param component_id: ComponentID: Component ID in viewer combo
        :param force: bool: force change if component is already displayed.
        """

        combo = self.get_viewer_combo(view_index)

        component_index = combo.findData(component_id)

        if combo.currentIndex() == component_index and force:
            combo.currentIndexChanged.emit(component_index)
        else:
            combo.setCurrentIndex(component_index)

    def get_viewer_combo(self, view_index):
        """
        Get viewer combo for a given viewer index
        """
        if view_index == 0:
            combo_label = 'single_viewer_combo'
        else:
            combo_label = 'viewer{0}_combo'.format(view_index)
        return getattr(self.ui, combo_label)

    def add_overlay(self, data, label):
        self._overlay_controller.add_overlay(data, label)

    def add_data(self, data):
        """
        Called by a function outside the class in order to add data to cubeviz.

        :param data:
        :return:
        """
        self._data = data
        self.specviz._widget.add_data(data)
        cid = self.specviz._widget._options_widget.file_att
        dispatch.changed_units.emit(y=data.get_component(cid).units)

        for checkbox in self._synced_checkboxes:
            checkbox.setEnabled(True)

        self._has_data = True
        self._active_view = self.left_view
        self._active_cube = self.left_view
        self._last_active_view = self.single_view
        self._active_split_cube = self.left_view

        # Store pointer to wavelength information
        self._wavelengths = self.single_view._widget._data[0].coords.world_axis(self.single_view._widget._data[0], axis=0)

        # Pass WCS and wavelength information to slider controller and enable
        wcs = self.session.data_collection.data[0].coords.wcs
        self._slice_controller.enable(wcs, self._wavelengths)
        self._units_controller.enable(wcs, self._wavelengths)

        self._enable_option_buttons()
        self._setup_syncing()

        self._enable_all_viewer_combos(data)

        self.subWindowActivated.emit(self._active_view)

    def eventFilter(self, obj, event):

        if event.type() == QtCore.QEvent.MouseButtonPress:

            if not (self.isVisible() and self.isActiveWindow()):
                return super(CubeVizLayout, self).eventFilter(obj, event)

            # Find global click position
            click_pos = event.globalPos()

            # If the click position is the same as the last one, we shouldn't
            # do anything.
            if click_pos != self._last_click:

                # Determine if the event falls inside any of the viewers
                for viewer in self.subWindowList():
                    relative_click_pos = viewer.mapFromGlobal(click_pos)
                    if viewer.rect().contains(relative_click_pos):
                        # We should only emit an event if the active subwindow
                        # has actually changed.
                        if viewer is not self.activeSubWindow():
                            self.subWindowActivated.emit(viewer)
                        break

                self._last_click = click_pos

        return super(CubeVizLayout, self).eventFilter(obj, event)

    def _toggle_image_mode(self, event=None):
        new_active_view = self._last_active_view
        self._last_active_view = self._active_view

        # Currently in single image, moving to split image
        if self._single_viewer_mode:
            self._active_cube = self._active_split_cube
            self._activate_split_image_mode(event)
            self._single_viewer_mode = False
            self.ui.button_toggle_image_mode.setText('Single Image Viewer')
            self.ui.viewer_control_frame.setCurrentIndex(0)

            for view in self.split_views:
                if self.single_view._widget.synced:
                    if view._widget.synced:
                        view._widget.update_slice_index(self.single_view._widget.slice_index)
                view._widget.update()
        # Currently in split image, moving to single image
        else:
            self._active_split_cube = self._active_cube
            self._active_view = self.single_view
            self._active_cube = self.single_view
            self._activate_single_image_mode(event)
            self._single_viewer_mode = True
            self.ui.button_toggle_image_mode.setText('Split Image Viewer')
            self.ui.viewer_control_frame.setCurrentIndex(1)
            self._active_view._widget.update()

        self.subWindowActivated.emit(new_active_view)

        # Update the slice index to reflect the state of the active cube
        self._slice_controller.update_index(self._active_cube._widget.slice_index)

    def _activate_single_image_mode(self, event=None):
        vsplitter = self.ui.vertical_splitter
        hsplitter = self.ui.horizontal_splitter
        vsizes = list(vsplitter.sizes())
        hsizes = list(hsplitter.sizes())
        vsizes = 0, max(10, vsizes[0] + vsizes[1])
        hsizes = max(10, sum(hsizes) * 0.4), max(10, sum(hsizes) * 0.6)
        vsplitter.setSizes(vsizes)
        hsplitter.setSizes(hsizes)

    def _activate_split_image_mode(self, event=None):
        vsplitter = self.ui.vertical_splitter
        hsplitter = self.ui.horizontal_splitter
        vsizes = list(vsplitter.sizes())
        hsizes = list(hsplitter.sizes())
        vsizes = max(10, sum(vsizes) / 2), max(10, sum(vsizes) / 2)

        # TODO:  Might be a bug here, should the hsizes be based on vsizes? If so, not sure we need to calculate
        # TODO:  the hsizes above.
        hsizes = 0, max(10, vsizes[0] + vsizes[1])
        vsplitter.setSizes(vsizes)
        hsplitter.setSizes(hsizes)

    def _update_active_view(self, view):
        if self._has_data:
            self._active_view = view
            if isinstance(view._widget, CubevizImageViewer):
                self._active_cube = view
                index = self._active_cube._widget.slice_index
                if view._widget.has_2d_data:
                    self._slice_controller.set_enabled(False)
                else:
                    self._slice_controller.set_enabled(True)
                    self._slice_controller.update_index(index)

    def activeSubWindow(self):
        return self._active_view

    def subWindowList(self):
        return [self.single_view, self.left_view, self.middle_view, self.right_view, self.specviz]

    def _setup_syncing(self):
        for attribute in ['x_min', 'x_max', 'y_min', 'y_max']:
            sync1 = keep_in_sync(self.left_view._widget.state, attribute,
                                 self.middle_view._widget.state, attribute)
            sync2 = keep_in_sync(self.middle_view._widget.state, attribute,
                                 self.right_view._widget.state, attribute)
            self.sync[attribute] = sync1, sync2
        self._on_sync_click()

    def _on_sync_click(self, event=None):
        index = self._active_cube._widget.slice_index
        for view in self.cube_views:
            view._widget.synced = True
            if view != self._active_cube:
                view._widget.update_slice_index(index)
        self._slice_controller.update_index(index)

    def start_smoothing_preview(self, preview_function, component_id, preview_title=None):
        """
        Starts smoothing preview. This function preforms the following steps
        1) SelectSmoothing passes parameters.
        2) The left and single viewers' combo box is set to component_id
        3) The set_smoothing_preview is called to setup on the fly smoothing
        :param preview_function: function: Single-slice smoothing function
        :param component_id: int: Which component to preview
        :param preview_title: str: Title displayed when previewing
        """
        # For single and first viewer:
        self._original_components = {}
        for view_index in [0, 1]:
            combo = self.get_viewer_combo(view_index)
            self._original_components[view_index] = combo.currentData()
            view = self.all_views[view_index].widget()
            self.change_viewer_component(view_index, component_id, force=True)
            view.set_smoothing_preview(preview_function, preview_title)

    def end_smoothing_preview(self):
        """
        End preview and change viewer combo index to the first component.
        """
        for view_index in [0, 1]:
            view = self.all_views[view_index].widget()
            view.end_smoothing_preview()
            if view_index in self._original_components:
                component_id = self._original_components[view_index]
                self.change_viewer_component(view_index, component_id, force=True)
        self._original_components = {}

    def showEvent(self, event):
        super(CubeVizLayout, self).showEvent(event)
        # Make split image mode the default layout
        self._activate_split_image_mode()
        self._update_active_view(self.left_view)

    def change_slice_index(self, amount):
        self._slice_controller.change_slider_value(amount)

    def get_wavelengths(self):
        return self._wavelengths

    def get_wavelengths_units(self):
        return self._units_controller.get_new_units()

    def set_wavelengths(self, new_wavelengths, new_units):
        self._wavelengths = new_wavelengths
        self._slice_controller.set_wavelengths(new_wavelengths, new_units)
示例#4
0
class WWTDataViewerState(ViewerState):

    mode = SelectionCallbackProperty(default_index=0)
    frame = SelectionCallbackProperty(default_index=0)

    lon_att = SelectionCallbackProperty(default_index=0)
    lat_att = SelectionCallbackProperty(default_index=1)
    alt_att = SelectionCallbackProperty(default_index=0)
    alt_unit = SelectionCallbackProperty(default_index=0)
    alt_type = SelectionCallbackProperty(default_index=0)

    foreground = SelectionCallbackProperty(default_index=1)
    foreground_opacity = CallbackProperty(1)
    background = SelectionCallbackProperty(default_index=8)

    galactic = CallbackProperty(False)

    layers = ListCallbackProperty()

    # For now we need to include this here otherwise when loading files, the
    # imagery layers are only available asynchronously and the session loading
    # fails.
    imagery_layers = ListCallbackProperty()

    def __init__(self, **kwargs):

        super(WWTDataViewerState, self).__init__()

        WWTDataViewerState.mode.set_choices(self,
                                            ['Sky'] + MODES_3D + MODES_BODIES)
        WWTDataViewerState.frame.set_choices(self, CELESTIAL_FRAMES)
        WWTDataViewerState.alt_unit.set_choices(self,
                                                [str(x) for x in ALT_UNITS])
        WWTDataViewerState.alt_type.set_choices(self, ALT_TYPES)

        self.add_callback('imagery_layers', self._update_imagery_layers)

        self.lon_att_helper = ComponentIDComboHelper(self,
                                                     'lon_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False)

        self.lat_att_helper = ComponentIDComboHelper(self,
                                                     'lat_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False)

        self.alt_att_helper = ComponentIDComboHelper(self,
                                                     'alt_att',
                                                     numeric=True,
                                                     categorical=False,
                                                     world_coord=True,
                                                     pixel_coord=False,
                                                     none='None')

        self.add_callback('layers', self._on_layers_changed)
        self._on_layers_changed()

        self.update_from_dict(kwargs)

    def _on_layers_changed(self, *args):
        self.lon_att_helper.set_multiple_data(self.layers_data)
        self.lat_att_helper.set_multiple_data(self.layers_data)
        self.alt_att_helper.set_multiple_data(self.layers_data)

    def _update_imagery_layers(self, *args):
        WWTDataViewerState.foreground.set_choices(self, self.imagery_layers)
        WWTDataViewerState.background.set_choices(self, self.imagery_layers)

    def _update_priority(self, name):
        if name == 'layers':
            return 2
        elif name == 'imagery_layers':
            return 1
        else:
            return 0
示例#5
0
class DummyState(State):
    """Mock state class for testing only."""

    x_att = SelectionCallbackProperty(docstring='x test attribute')
    y_att = SelectionCallbackProperty(docstring='y test attribute',
                                      default_index=-1)
示例#6
0
class ComponentManagerWidget(QtWidgets.QDialog):

    data = SelectionCallbackProperty()

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

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

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

        self.list = {}
        self.list = self.ui.list_main_components

        self.data_collection = data_collection

        self._components_main = defaultdict(list)
        self._components_other = defaultdict(list)
        self._state = defaultdict(dict)

        for data in data_collection:

            for cid in data.main_components:
                comp_state = {}
                comp_state['cid'] = cid
                comp_state['label'] = cid.label
                self._state[data][cid] = comp_state
                self._components_main[data].append(cid)

            # Keep track of all other components

            self._components_other[data] = []

            for cid in data.components:
                if cid not in self._components_main[data]:
                    self._components_other[data].append(cid)

        # Populate data combo
        ComponentManagerWidget.data.set_choices(self,
                                                list(self.data_collection))
        ComponentManagerWidget.data.set_display_func(self, lambda x: x.label)
        connect_combo_selection(self, 'data', self.ui.combosel_data)

        self.ui.combosel_data.setCurrentIndex(0)
        self.ui.combosel_data.currentIndexChanged.connect(
            self._update_component_lists)
        self._update_component_lists()

        self.ui.button_remove_main.clicked.connect(self._remove_main_component)

        self.ui.list_main_components.itemSelectionChanged.connect(
            self._update_selection_main)

        self._update_selection_main()

        self.ui.list_main_components.itemChanged.connect(self._update_state)
        self.ui.list_main_components.order_changed.connect(self._update_state)

        self.ui.button_ok.clicked.connect(self.accept)
        self.ui.button_cancel.clicked.connect(self.reject)

    def _update_selection_main(self):
        enabled = self.list.selected_cid is not None
        self.button_remove_main.setEnabled(enabled)

    def _update_component_lists(self, *args):

        # This gets called when the data is changed and we need to update the
        # components shown in the lists.

        self.list.blockSignals(True)

        self.list.clear()
        for cid in self._components_main[self.data]:
            self.list.add_cid_and_label(cid,
                                        [self._state[self.data][cid]['label']])

        self.list.blockSignals(False)

        self._validate()

    def _validate(self):

        # Construct a list of all labels for the current dataset so that
        # we can check which ones are duplicates
        labels = [c.label for c in self._components_other[self.data]]
        labels.extend([c['label'] for c in self._state[self.data].values()])
        if len(labels) == 0:
            return
        label_count = Counter(labels)

        # It's possible that the duplicates are entirely for components not
        # shown in this editor, so we keep track here of whether an invalid
        # component has been found.
        invalid = False

        if label_count.most_common(1)[0][1] > 1:

            # If we are here, there are duplicates somewhere in the list
            # of components.

            brush_red = QtGui.QBrush(Qt.red)
            brush_black = QtGui.QBrush(Qt.black)

            self.list.blockSignals(True)

            for item in self.list:
                label = item.text(0)
                if label_count[label] > 1:
                    item.setForeground(0, brush_red)
                    invalid = True
                else:
                    item.setForeground(0, brush_black)

            self.list.blockSignals(False)

        if invalid:
            self.ui.label_status.setStyleSheet('color: red')
            self.ui.label_status.setText(
                'Error: some components have duplicate names')
            self.ui.button_ok.setEnabled(False)
            self.ui.combosel_data.setEnabled(False)
        else:
            self.ui.label_status.setStyleSheet('')
            self.ui.label_status.setText('')
            self.ui.button_ok.setEnabled(True)
            self.ui.combosel_data.setEnabled(True)

    def _update_state(self, *args):

        self._components_main[self.data] = []
        for item in self.list:
            cid = item.data(0, Qt.UserRole)
            self._state[self.data][cid]['label'] = item.text(0)
            self._components_main[self.data].append(cid)

        self._update_component_lists()

    def _remove_main_component(self, *args):
        cid = self.list.selected_cid
        if cid is not None:
            self._components_main[self.data].remove(cid)
            self._state[self.data].pop(cid)
            self._update_component_lists()

    def accept(self):

        for data in self._components_main:

            cids_main = self._components_main[data]
            cids_existing = data.components
            cids_all = data.pixel_component_ids + data.world_component_ids + cids_main + data.derived_components

            # First deal with renaming of components
            for cid_new in cids_main:
                label = self._state[data][cid_new]['label']
                if label != cid_new.label:
                    cid_new.label = label

            # Second deal with the removal of components
            for cid_old in cids_existing:
                if not any(cid_old is cid_new for cid_new in cids_all):
                    data.remove_component(cid_old)

            # Findally, reorder components as needed
            data.reorder_components(cids_all)

        super(ComponentManagerWidget, self).accept()
示例#7
0
文件: layout.py 项目: saimn/cubeviz
class CubeVizLayout(QtWidgets.QWidget):
    """
    The 'CubeViz' layout, with three image viewers and one spectrum viewer.
    """

    LABEL = "CubeViz"
    subWindowActivated = QtCore.Signal(object)

    single_viewer_attribute = SelectionCallbackProperty(default_index=0)
    viewer1_attribute = SelectionCallbackProperty(default_index=0)
    viewer2_attribute = SelectionCallbackProperty(default_index=1)
    viewer3_attribute = SelectionCallbackProperty(default_index=2)

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

        self._cubeviz_toolbar = None

        if not getattr(session.application, '_has_cubeviz_toolbar', False):
            self._cubeviz_toolbar = CubevizToolbar(
                application=session.application)
            session.application.insertToolBar(
                session.application._data_toolbar, self._cubeviz_toolbar)
            session.application._has_cubeviz_toolbar = True

        self.session = session
        self._has_data = False
        self._option_buttons = []

        self._data = None

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

        self.cube_views = []

        # Create the cube viewers and register to the hub.
        for _ in range(DEFAULT_NUM_SPLIT_VIEWERS + 1):
            ww = WidgetWrapper(CubevizImageViewer(self.session,
                                                  cubeviz_layout=self),
                               tab_widget=self,
                               toolbar=True)
            self.cube_views.append(ww)
            ww._widget.register_to_hub(self.session.hub)

        self.set_toolbar_icon_size(DEFAULT_TOOLBAR_ICON_SIZE)

        # Create specviz viewer and register to the hub.
        self.specviz = WidgetWrapper(SpecvizDataViewer(self.session,
                                                       layout=self,
                                                       include_line=True),
                                     tab_widget=self,
                                     toolbar=False)
        self.specviz._widget.register_to_hub(self.session.hub)

        self.single_view = self.cube_views[0]
        self.split_views = self.cube_views[1:]

        self._synced_checkboxes = [view.checkbox for view in self.cube_views]

        for view, checkbox in zip(self.cube_views, self._synced_checkboxes):
            view._widget.assign_synced_checkbox(checkbox)

        # Add the views to the layouts.
        self.ui.single_image_layout.addWidget(self.single_view)
        for viewer in self.split_views:
            self.ui.image_row_layout.addWidget(viewer)

        self.ui.specviz_layout.addWidget(self.specviz)

        self.subWindowActivated.connect(self._update_active_view)

        self.ui.sync_button.clicked.connect(self._on_sync_click)
        self.ui.button_toggle_image_mode.clicked.connect(
            self._toggle_image_mode)

        # This is a list of helpers for the viewer combo boxes. New data
        # collections should be added to each helper in this list using the
        # ``append_data`` method to ensure that the new data components are
        # populated into the combo boxes.
        self._viewer_combo_helpers = []

        # This tracks the current positions of cube viewer axes when they are hidden
        self._viewer_axes_positions = []

        # Indicates whether cube viewer toolbars are currently visible or not
        self._toolbars_visible = True

        # Indicates whether subset stats should be displayed or not
        self._stats_visible = True

        self._slice_controller = SliceController(self)
        self._overlay_controller = OverlayController(self)

        self._wavelength_controller = WavelengthController(self)
        self._flux_unit_controller = FluxUnitController(self)

        # Add menu buttons to the cubeviz toolbar.
        self.ra_dec_format_menu = None
        self._init_menu_buttons()

        self.sync = {}

        app = get_qapp()
        app.installEventFilter(self)
        self._last_click = None
        self._active_view = None
        self._active_cube = None
        self._last_active_view = None
        self._active_split_cube = None

        # Set the default to parallel image viewer
        self._single_viewer_mode = False
        self.ui.button_toggle_image_mode.setText('Single Image Viewer')

        # Listen for unit changes in specviz
        self.specviz._widget.hub.plot_widget.spectral_axis_unit_changed.connect(
            self._wavelength_controller.update_units)

        def _update_displayed_units(unit):
            """
            Re-create minimum flux unit change functionality without having to
            spawn the ``QDialog`` object in cubeviz.
            """
            component_id = self.specviz._widget.layers[0].state.attribute
            cubeviz_unit = self._flux_unit_controller.add_component_unit(
                component_id, unit)
            self._flux_unit_controller.data.get_component(
                component_id).units = unit
            msg = FluxUnitsUpdateMessage(self, cubeviz_unit, component_id)
            self._wavelength_controller._hub.broadcast(msg)

        self.specviz._widget.hub.plot_widget.data_unit_changed.connect(
            _update_displayed_units)

    def _init_menu_buttons(self):
        """
        Add the two menu buttons to the tool bar. Currently two are defined:
            View - for changing the view of the active window
            Data Processing - for applying a data processing step to the data.

        :return:
        """
        self._option_buttons = [
            self.ui.view_option_button, self.ui.cube_option_button
        ]

        # Create the View Menu
        view_menu = self._dict_to_menu(
            OrderedDict([
                ('Hide Axes', ['checkable', self._toggle_viewer_axes]),
                ('Hide Toolbars', ['checkable', self._toggle_toolbars]),
                ('Hide Spaxel Value Tooltip',
                 ['checkable', self._toggle_hover_value]),
                ('Hide Stats', ['checkable', self._toggle_stats_display]),
                ('Flux Units',
                 OrderedDict([
                     ('Convert Displayed Units', lambda: self._open_dialog(
                         'Convert Displayed Units', None)),
                     ('Convert Data Values',
                      lambda: self._open_dialog('Convert Data Values', None)),
                 ])),
                ('Wavelength Units/Redshift',
                 lambda: self._open_dialog('Wavelength Units/Redshift', None))
            ]))

        # Add toggle RA-DEC format:
        format_menu = view_menu.addMenu("RA-DEC Format")
        format_action_group = QActionGroup(format_menu)
        self.ra_dec_format_menu = format_menu

        # Make sure to change all instances of the the names
        # of the formats if modifications are made to them.
        for format_name in ["Sexagesimal", "Decimal Degrees"]:
            act = QAction(format_name, format_menu)
            act.triggered.connect(self._toggle_all_coords_in_degrees)
            act.setActionGroup(format_action_group)
            act.setCheckable(True)
            act.setChecked(
                True) if format == "Sexagesimal" else act.setChecked(False)
            format_menu.addAction(act)

        self.ui.view_option_button.setMenu(view_menu)

        # Create the Data Processing Menu
        cube_menu = self._dict_to_menu(
            OrderedDict([
                ('Collapse Cube',
                 lambda: self._open_dialog('Collapse Cube', None)),
                ('Spatial Smoothing',
                 lambda: self._open_dialog('Spatial Smoothing', None)),
                ('Moment Maps',
                 lambda: self._open_dialog('Moment Maps', None)),
                ('Arithmetic Operations',
                 lambda: self._open_dialog('Arithmetic Operations', None))
            ]))
        self.ui.cube_option_button.setMenu(cube_menu)

    def _dict_to_menu(self, menu_dict, menu_widget=None):
        '''Stolen shamelessly from specviz. Thanks!'''
        if not menu_widget:
            menu_widget = QMenu()
        for k, v in menu_dict.items():
            if isinstance(v, dict):
                new_menu = menu_widget.addMenu(k)
                self._dict_to_menu(v, menu_widget=new_menu)
            else:
                act = QAction(k, menu_widget)

                if isinstance(v, list):
                    if v[0] == 'checkable':
                        v = v[1]
                        act.setCheckable(True)
                        act.setChecked(False)

                act.triggered.connect(v)
                menu_widget.addAction(act)
        return menu_widget

    def set_toolbar_icon_size(self, size):
        for view in self.cube_views:
            view._widget.toolbar.setIconSize(QtCore.QSize(size, size))

    def handle_settings_change(self, message):
        if isinstance(message, SettingsChangeMessage):
            self._slice_controller.update_index(self.synced_index)

    @property
    def synced_index(self):
        return self._slice_controller.synced_index

    def handle_subset_action(self, message):
        self.refresh_viewer_combo_helpers()
        if isinstance(message, SubsetUpdateMessage):
            for combo, viewer in zip(self._viewer_combo_helpers,
                                     self.cube_views):
                viewer._widget.show_roi_stats(combo.selection, message.subset)
        elif isinstance(message, SubsetDeleteMessage):
            for viewer in self.cube_views:
                viewer._widget.show_slice_stats()
        elif isinstance(message, EditSubsetMessage) and not message.subset:
            for viewer in self.cube_views:
                viewer._widget.show_slice_stats()

    def _set_pos_and_margin(self, axes, pos, marg):
        axes.set_position(pos)
        freeze_margins(axes, marg)

    def _hide_viewer_axes(self):
        for viewer in self.cube_views:
            viewer._widget.toggle_hidden_axes(True)
            axes = viewer._widget.axes
            # Save current axes position and margins so they can be restored
            pos = axes.get_position(), axes.resizer.margins
            self._viewer_axes_positions.append(pos)
            self._set_pos_and_margin(axes, [0, 0, 1, 1], [0, 0, 0, 0])
            viewer._widget.figure.canvas.draw()

    def _toggle_viewer_axes(self):
        # If axes are currently hidden, restore the original positions
        if self._viewer_axes_positions:
            for viewer, pos in zip(self.cube_views,
                                   self._viewer_axes_positions):
                viewer._widget.toggle_hidden_axes(False)
                axes = viewer._widget.axes
                self._set_pos_and_margin(axes, *pos)
                viewer._widget.figure.canvas.draw()
            self._viewer_axes_positions = []
        # Record current positions if axes are currently hidden and hide them
        else:
            self._hide_viewer_axes()

    def _toggle_toolbars(self):
        self._toolbars_visible = not self._toolbars_visible
        for viewer in self.cube_views:
            viewer._widget.toolbar.setVisible(self._toolbars_visible)

    def _toggle_hover_value(self):
        for viewer in self.cube_views:
            viewer._widget._is_tooltip_on = not viewer._widget._is_tooltip_on

    def _toggle_stats_display(self):
        self._stats_visible = not self._stats_visible
        for viewer in self.cube_views:
            viewer.set_stats_visible(self._stats_visible)

    def _open_dialog(self, name, widget):

        if name == 'Collapse Cube':
            ex = collapse_cube.CollapseCube(
                self._wavelength_controller.wavelengths,
                self._wavelength_controller.current_units,
                self._data,
                parent=self,
                allow_preview=True)

        if name == 'Spatial Smoothing':
            ex = smoothing.SelectSmoothing(self._data,
                                           parent=self,
                                           allow_preview=True)

        if name == 'Arithmetic Operations':
            dialog = ArithmeticEditorWidget(self.session.data_collection)
            dialog.exec_()

        if name == "Moment Maps":
            mm_gui = moment_maps.MomentMapsGUI(self._data,
                                               self.session.data_collection,
                                               parent=self)
            mm_gui.display()

        if name == 'Convert Displayed Units':
            self._flux_unit_controller.converter(parent=self)

        if name == 'Convert Data Values':
            self._flux_unit_controller.converter(parent=self,
                                                 convert_data=True)

        if name == "Wavelength Units/Redshift":
            WavelengthUI(self._wavelength_controller, parent=self)

    def refresh_flux_units(self, message):
        """
        Listens for flux unit update messages (this is called from
        `listeners`) and updates the displayed spectral units in the specviz
        data viewer.
        """
        unit = message.flux_units
        self.specviz._widget.hub.plot_widget.data_unit = unit.to_string()

    def _toggle_all_coords_in_degrees(self):
        """
        Switch ra-dec b/w "Sexagesimal" and "Decimal Degrees"
        """
        menu = self.ra_dec_format_menu
        for action in menu.actions():
            if "Decimal Degrees" == action.text():
                coords_in_degrees = action.isChecked()
                break

        for view in self.cube_views:
            viewer = view.widget()
            if viewer._coords_in_degrees != coords_in_degrees:
                viewer.toggle_coords_in_degrees()

    @property
    def data_components(self):
        return self._data.main_components + self._data.derived_components

    @property
    def component_labels(self):
        return [str(cid) for cid in self.data_components]

    def refresh_viewer_combo_helpers(self):
        for i, helper in enumerate(self._viewer_combo_helpers):
            helper.refresh()

    def remove_data_component(self, component_id):
        pass

    def _enable_option_buttons(self):
        for button in self._option_buttons:
            button.setEnabled(True)
        self.ui.sync_button.setEnabled(True)

    def _get_change_viewer_combo_func(self, combo, view_index):
        def _on_viewer_combo_change(dropdown_index):

            # This function gets called whenever one of the viewer combos gets
            # changed. The active combo is the one that comes from the parent
            # _get_change_viewer_combo_func function.

            # Find the relevant viewer
            viewer = self.cube_views[view_index].widget()

            # Get the label of the component and the component ID itself
            label = combo.currentText()
            component = combo.currentData()
            if isinstance(component, UserDataWrapper):
                component = component.data

            viewer.has_2d_data = component.parent[label].ndim == 2

            # If the user changed the current component, stop previewing
            # smoothing.
            if viewer.is_smoothing_preview_active:
                viewer.end_smoothing_preview()

            # Change the component label, title and unit shown in the viewer
            viewer.current_component_id = component
            viewer.cubeviz_unit = self._flux_unit_controller[component]
            viewer.update_axes_title(title=str(label))

            # Change the viewer's reference data to be the data containing the
            # current component.
            viewer.state.reference_data = component.parent

            # The viewer may have multiple layers, for instance layers for
            # the main cube and for any overlay datasets, as well as subset
            # layers. We go through all the layers and make sure that for the
            # layer which corresponds to the current dataset, the correct
            # attribute is shown.
            for layer_artist in viewer.layers:
                layer_state = layer_artist.state
                if layer_state.layer is component.parent:

                    # We call _update_attribute here manually so that if this
                    # function gets called before _update_attribute, it gets
                    # called before we try and set the attribute below
                    # (_update_attribute basically updates the internal list
                    # of available attributes for the attribute combo)
                    layer_state._update_attribute()
                    layer_state.attribute = component

                    # We then also make sure that this layer artist is the
                    # one that is selected so that if the user uses e.g. the
                    # contrast tool, it will change the right layer
                    viewer._view.layer_list.select_artist(layer_artist)

            # If the combo corresponds to the currently active cube viewer,
            # either activate or deactivate the slice slider as appropriate.
            if self.cube_views[view_index] is self._active_cube:
                self._slice_controller.set_enabled(not viewer.has_2d_data)

            # If contours are being currently shown, we need to force a redraw
            if viewer.is_contour_active:
                viewer.draw_contour()

            viewer.update_component(component)

        return _on_viewer_combo_change

    def _enable_viewer_combo(self, viewer, data, index, selection_label):
        connect_combo_selection(self, selection_label, viewer.combo)
        helper = ComponentIDComboHelper(self, selection_label)
        helper.set_multiple_data([data])
        viewer.combo.setEnabled(True)
        viewer.combo.currentIndexChanged.connect(
            self._get_change_viewer_combo_func(viewer.combo, index))
        self._viewer_combo_helpers.append(helper)

    def _enable_all_viewer_combos(self, data):
        """
        Setup the dropdown boxes that correspond to each of the left, middle,
        and right views.  The combo boxes initially are set to have FLUX,
        Error, DQ but will be dynamic depending on the type of data available
        either from being loaded in or by being processed.

        :return:
        """
        view = self.cube_views[0].widget()
        self._enable_viewer_combo(view.parent(), data, 0,
                                  'single_viewer_attribute')

        component = self.single_viewer_attribute
        view.current_component_id = component
        view.cubeviz_unit = self._flux_unit_controller[component]
        view.update_axes_title(component.label)

        for i in range(1, 4):
            view = self.cube_views[i].widget()
            selection_label = 'viewer{0}_attribute'.format(i)
            self._enable_viewer_combo(view.parent(), data, i, selection_label)

            component = getattr(self, selection_label)
            view.current_component_id = component
            view.cubeviz_unit = self._flux_unit_controller[component]
            view.update_axes_title(component.label)

    def change_viewer_component(self, view_index, component_id, force=False):
        """
        Given a viewer at an index view_index, change combo
        selection to component at an index component_index.
        :param view_index: int: Viewer index
        :param component_id: ComponentID: Component ID in viewer combo
        :param force: bool: force change if component is already displayed.
        """

        combo = self.get_viewer_combo(view_index)

        try:
            if isinstance(component_id, str):
                component_index = _find_combo_text(combo, component_id)
            else:
                component_index = _find_combo_data(combo, component_id)
        except ValueError:
            component_index = -1

        if combo.currentIndex() == component_index and force:
            combo.currentIndexChanged.emit(component_index)
        else:
            combo.setCurrentIndex(component_index)

    def display_component(self, component_id):
        """
        Displays data with given component ID in the active cube viewer.
        """
        self.refresh_viewer_combo_helpers()
        if self._single_viewer_mode:
            self.change_viewer_component(0, component_id)
        else:
            self.change_viewer_component(1, component_id)

    def get_viewer_combo(self, view_index):
        """
        Get viewer combo for a given viewer index
        """
        return self.cube_views[view_index].combo

    def add_overlay(self, data, label, display_now=True):
        self._overlay_controller.add_overlay(data, label, display=display_now)
        self.display_component(label)

    def _set_data_coord_system(self, data):
        """
        Check if data coordinates are in
        RA-DEC first. Then set viewers to
        the default coordinate system.
        :param data: input data
        """
        is_ra_dec = isinstance(wcs_to_celestial_frame(data.coords.wcs),
                               BaseRADecFrame)
        self.ra_dec_format_menu.setDisabled(not is_ra_dec)
        if not is_ra_dec:
            return

        is_coords_in_degrees = False
        for view in self.cube_views:
            viewer = view.widget()
            viewer.init_ra_dec()
            is_coords_in_degrees = viewer._coords_in_degrees

        if is_coords_in_degrees:
            format_name = "Decimal Degrees"
        else:
            format_name = "Sexagesimal"

        menu = self.ra_dec_format_menu
        for action in menu.actions():
            if format_name == action.text():
                action.setChecked(True)
                break

    def add_data(self, data):
        """
        Called by a function outside the class in order to add data to cubeviz.

        :param data:
        :return:
        """
        self._data = data
        self.specviz._widget.add_data(data)
        self._flux_unit_controller.set_data(data)

        for checkbox in self._synced_checkboxes:
            checkbox.setEnabled(True)

        self._has_data = True
        self._active_view = self.split_views[0]
        self._active_cube = self.split_views[0]
        self._last_active_view = self.single_view
        self._active_split_cube = self.split_views[0]

        # Store pointer to wcs and wavelength information
        wcs = self.session.data_collection.data[0].coords.wcs
        wavelengths = self.single_view._widget._data[0].coords.world_axis(
            self.single_view._widget._data[0], axis=0)

        self._enable_all_viewer_combos(data)

        # TODO: currently this way of accessing units is not flexible
        self._slice_controller.enable()
        self._wavelength_controller.enable(str(wcs.wcs.cunit[2]), wavelengths)

        self._enable_option_buttons()
        self._setup_syncing()

        for viewer in self.cube_views:
            viewer.slice_text.setText('slice: {:5}'.format(self.synced_index))

        self.subWindowActivated.emit(self._active_view)

        # Check if coord system is RA and DEC (ie not galactic etc..)
        self._set_data_coord_system(data)

    def eventFilter(self, obj, event):

        if isinstance(event, QtGui.QMouseEvent):
            if not (self.isVisible() and self.isActiveWindow()):
                return super(CubeVizLayout, self).eventFilter(obj, event)

            # Find global click position
            click_pos = event.globalPos()

            # If the click position is the same as the last one, we shouldn't
            # do anything.
            if click_pos != self._last_click:

                # Determine if the event falls inside any of the viewers
                for viewer in self.subWindowList():
                    relative_click_pos = viewer.mapFromGlobal(click_pos)
                    if viewer.rect().contains(relative_click_pos):
                        # We should only emit an event if the active subwindow
                        # has actually changed.
                        if viewer is not self.activeSubWindow():
                            self.subWindowActivated.emit(viewer)
                        break

                self._last_click = click_pos

        return super(CubeVizLayout, self).eventFilter(obj, event)

    def _toggle_image_mode(self, event=None):
        new_active_view = self._last_active_view
        self._last_active_view = self._active_view

        # Currently in single image, moving to split image
        if self._single_viewer_mode:
            self._active_cube = self._active_split_cube
            self._activate_split_image_mode(event)
            self._single_viewer_mode = False
            self.ui.button_toggle_image_mode.setText('Single Image Viewer')

            for view in self.split_views:
                if self.single_view._widget.synced:
                    if view._widget.synced:
                        view._widget.update_slice_index(
                            self.single_view._widget.slice_index)
                view._widget.update()
        # Currently in split image, moving to single image
        else:
            self._active_split_cube = self._active_cube
            self._active_view = self.single_view
            self._active_cube = self.single_view
            self._activate_single_image_mode(event)
            self._single_viewer_mode = True
            self.ui.button_toggle_image_mode.setText('Split Image Viewer')
            self._active_view._widget.update()

        self.subWindowActivated.emit(new_active_view)

        # Update the slice index to reflect the state of the active cube
        self._slice_controller.update_index(
            self._active_cube._widget.slice_index)

    def _activate_single_image_mode(self, event=None):
        vsplitter = self.ui.vertical_splitter
        hsplitter = self.ui.horizontal_splitter
        vsizes = list(vsplitter.sizes())
        hsizes = list(hsplitter.sizes())
        vsizes = 0, max(10, vsizes[0] + vsizes[1])
        hsizes = max(10, sum(hsizes) * 0.4), max(10, sum(hsizes) * 0.6)
        vsplitter.setSizes(vsizes)
        hsplitter.setSizes(hsizes)

    def _activate_split_image_mode(self, event=None):
        vsplitter = self.ui.vertical_splitter
        hsplitter = self.ui.horizontal_splitter
        vsizes = list(vsplitter.sizes())
        hsizes = list(hsplitter.sizes())
        vsizes = max(10, sum(vsizes) / 2), max(10, sum(vsizes) / 2)

        # TODO:  Might be a bug here, should the hsizes be based on vsizes? If so, not sure we need to calculate
        # TODO:  the hsizes above.
        hsizes = 0, max(10, vsizes[0] + vsizes[1])
        vsplitter.setSizes(vsizes)
        hsplitter.setSizes(hsizes)

    def _update_active_view(self, view):
        if self._has_data:
            self._active_view = view
            if isinstance(view._widget, CubevizImageViewer):
                self._active_cube = view
                index = self._active_cube._widget.slice_index
                if view._widget.has_2d_data:
                    self._slice_controller.set_enabled(False)
                else:
                    self._slice_controller.set_enabled(True)
                    self._slice_controller.update_index(index)

    def activeSubWindow(self):
        return self._active_view

    def subWindowList(self):
        return self.cube_views + [self.specviz]

    def _setup_syncing(self):
        for attribute in ['x_min', 'x_max', 'y_min', 'y_max']:
            # TODO: this will need to be generalized if we want to support an
            # arbitrary number of viewers.
            sync1 = keep_in_sync(self.split_views[0]._widget.state, attribute,
                                 self.split_views[1]._widget.state, attribute)
            sync2 = keep_in_sync(self.split_views[1]._widget.state, attribute,
                                 self.split_views[2]._widget.state, attribute)
            self.sync[attribute] = sync1, sync2
        self._on_sync_click()

    def _on_sync_click(self, event=None):
        index = self._active_cube._widget.slice_index
        for view in self.cube_views:
            view._widget.synced = True
            if view != self._active_cube:
                view._widget.update_slice_index(index)
        self._slice_controller.update_index(index)

    def start_smoothing_preview(self,
                                preview_function,
                                component_id,
                                preview_title=None):
        """
        Starts smoothing preview. This function preforms the following steps
        1) SelectSmoothing passes parameters.
        2) The left and single viewers' combo box is set to component_id
        3) The set_smoothing_preview is called to setup on the fly smoothing
        :param preview_function: function: Single-slice smoothing function
        :param component_id: int: Which component to preview
        :param preview_title: str: Title displayed when previewing
        """
        # For single and first viewer:
        self._original_components = {}
        for view_index in [0, 1]:
            combo = self.get_viewer_combo(view_index)
            self._original_components[view_index] = combo.currentData()
            view = self.cube_views[view_index].widget()
            self.change_viewer_component(view_index, component_id, force=True)
            view.set_smoothing_preview(preview_function, preview_title)

    def end_smoothing_preview(self):
        """
        End preview and change viewer combo index to the first component.
        """
        for view_index in [0, 1]:
            view = self.cube_views[view_index].widget()
            view.end_smoothing_preview()
            if view_index in self._original_components:
                component_id = self._original_components[view_index]
                self.change_viewer_component(view_index,
                                             component_id,
                                             force=True)
        self._original_components = {}

    def showEvent(self, event):
        super(CubeVizLayout, self).showEvent(event)
        # Make split image mode the default layout
        self._activate_split_image_mode()
        self._update_active_view(self.split_views[0])

    def change_slice_index(self, amount):
        self._slice_controller.change_slider_value(amount)

    def get_wavelengths(self):
        return self._wavelength_controller.wavelengths

    def get_wavelengths_units(self):
        return self._wavelength_controller.current_units

    def get_wavelength(self, index=None):
        if index is None:
            index = self.synced_index
        elif index > len(self.get_wavelengths()):
            return None
        wave = self.get_wavelengths()[index]
        units = self.get_wavelengths_units()
        return wave * units
示例#8
0
class ViewerState3D(ViewerState):
    """
    A common state object for all 3D viewers
    """

    x_att = SelectionCallbackProperty()
    x_min = CallbackProperty(0)
    x_max = CallbackProperty(1)
    # x_stretch = CallbackProperty(1.)

    y_att = SelectionCallbackProperty(default_index=1)
    y_min = CallbackProperty(0)
    y_max = CallbackProperty(1)
    # y_stretch = CallbackProperty(1.)

    z_att = SelectionCallbackProperty(default_index=2)
    z_min = CallbackProperty(0)
    z_max = CallbackProperty(1)
    # z_stretch = CallbackProperty(1.)

    visible_axes = CallbackProperty(True)
    # perspective_view = CallbackProperty(False)
    # clip_data = CallbackProperty(False)
    # native_aspect = CallbackProperty(False)

    limits_cache = CallbackProperty()

    # def _update_priority(self, name):
    #     if name == 'layers':
    #         return 2
    #     elif name.endswith(('_min', '_max')):
    #         return 0
    #     else:
    #         return 1

    def __init__(self, **kwargs):

        super(ViewerState3D, self).__init__(**kwargs)

        if self.limits_cache is None:
            self.limits_cache = {}

        self.x_lim_helper = StateAttributeLimitsHelper(self,
                                                       attribute='x_att',
                                                       lower='x_min',
                                                       upper='x_max',
                                                       cache=self.limits_cache)

        self.y_lim_helper = StateAttributeLimitsHelper(self,
                                                       attribute='y_att',
                                                       lower='y_min',
                                                       upper='y_max',
                                                       cache=self.limits_cache)

        self.z_lim_helper = StateAttributeLimitsHelper(self,
                                                       attribute='z_att',
                                                       lower='z_min',
                                                       upper='z_max',
                                                       cache=self.limits_cache)

        # TODO: if limits_cache is re-assigned to a different object, we need to
        # update the attribute helpers. However if in future we make limits_cache
        # into a smart dictionary that can call callbacks when elements are
        # changed then we shouldn't always call this. It'd also be nice to
        # avoid this altogether and make it more clean.
        self.add_callback('limits_cache',
                          nonpartial(self._update_limits_cache))

    def _update_limits_cache(self):
        self.x_lim_helper._cache = self.limits_cache
        self.x_lim_helper._update_attribute()
        self.y_lim_helper._cache = self.limits_cache
        self.y_lim_helper._update_attribute()
        self.z_lim_helper._cache = self.limits_cache
        self.z_lim_helper._update_attribute()

    # @property
    # def aspect(self):
    #     # TODO: this could be cached based on the limits, but is not urgent
    #     aspect = np.array([1, 1, 1], dtype=float)
    #     if self.native_aspect:
    #         aspect[0] = 1.
    #         aspect[1] = (self.y_max - self.y_min) / (self.x_max - self.x_min)
    #         aspect[2] = (self.z_max - self.z_min) / (self.x_max - self.x_min)
    #         aspect /= aspect.max()
    #     return aspect

    # def reset(self):
    #     pass

    def flip_x(self):
        self.x_lim_helper.flip_limits()

    def flip_y(self):
        self.y_lim_helper.flip_limits()

    def flip_z(self):
        self.z_lim_helper.flip_limits()
示例#9
0
文件: state.py 项目: cphyc/glue
class LinkEditorState(State):

    data1 = SelectionCallbackProperty()
    data2 = SelectionCallbackProperty()
    current_link = SelectionCallbackProperty()
    link_type = SelectionCallbackProperty()
    restrict_to_suggested = CallbackProperty(False)

    def __init__(self, data_collection, suggested_links=None):

        super(LinkEditorState, self).__init__()

        self.data1_helper = DataCollectionComboHelper(self, 'data1', data_collection)
        self.data2_helper = DataCollectionComboHelper(self, 'data2', data_collection)

        # FIXME: We unregister the combo helpers straight away to avoid issues with
        # leftover references once the dialog is closed. This shouldn't happen
        # ideally so in future we should investigate how to avoid it.
        self.data1_helper.unregister(data_collection.hub)
        self.data2_helper.unregister(data_collection.hub)

        self.data_collection = data_collection

        # Convert links to editable states
        links = [EditableLinkFunctionState(link) for link in data_collection.external_links]

        # If supplied, also add suggested links and make sure we toggle the
        # suggestion flag on the link state so that we can handle suggestions
        # differently in the link viewer.
        if suggested_links is not None:
            for link in suggested_links:
                link_state = EditableLinkFunctionState(link)
                link_state.suggested = True
                links.append(link_state)

        self.links = links

        if len(data_collection) == 2:
            self.data1, self.data2 = self.data_collection
        else:
            self.data1 = self.data2 = None

        self._on_data_change()

        self.add_callback('data1', self._on_data1_change)
        self.add_callback('data2', self._on_data2_change)
        self.add_callback('restrict_to_suggested', self._on_data_change)

        LinkEditorState.current_link.set_display_func(self, self._display_link)

    @property
    def visible_links(self):

        if self.data1 is None or self.data2 is None:
            return []

        links = []
        for link in self.links:
            if link.suggested or not self.restrict_to_suggested:
                if ((link.data_in is self.data1 and link.data_out is self.data2) or
                        (link.data_in is self.data2 and link.data_out is self.data1)):
                    links.append(link)

        return links

    def _on_data1_change(self, *args):
        if self.data1 is self.data2 and self.data1 is not None:
            self.data2 = next(data for data in self.data_collection if data is not self.data1)
        else:
            self._on_data_change()

    def _on_data2_change(self, *args):
        if self.data2 is self.data1 and self.data2 is not None:
            self.data1 = next(data for data in self.data_collection if data is not self.data2)
        else:
            self._on_data_change()

    def _on_data_change(self, *args):

        links = self.visible_links
        with delay_callback(self, 'current_link'):
            LinkEditorState.current_link.set_choices(self, links)
            if len(links) > 0:
                self.current_link = links[0]

    def _display_link(self, link):
        if link.suggested:
            return str(link) + ' [Suggested]'
        else:
            return str(link)

    def new_link(self, function_or_helper):

        if hasattr(function_or_helper, 'function'):
            link = EditableLinkFunctionState(function_or_helper.function,
                                             data_in=self.data1, data_out=self.data2,
                                             output_names=function_or_helper.output_labels,
                                             description=function_or_helper.info,
                                             display=function_or_helper.function.__name__)
        else:
            link = EditableLinkFunctionState(function_or_helper.helper,
                                             data_in=self.data1, data_out=self.data2)

        self.links.append(link)
        with delay_callback(self, 'current_link'):
            self._on_data_change()
            self.current_link = link

    def remove_link(self):
        self.links.remove(self.current_link)
        self._on_data_change()

    def update_links_in_collection(self):
        links = [link_state.link for link_state in self.links]
        self.data_collection.set_links(links)