Esempio n. 1
0
class SubsetFacetState(State):

    log = CallbackProperty(False)
    v_min = CallbackProperty(0.)
    v_max = CallbackProperty(1.)
    steps = CallbackProperty(5)
    data = SelectionCallbackProperty()
    att = SelectionCallbackProperty()
    cmap = CallbackProperty()

    def __init__(self, data_collection):

        super(SubsetFacetState, self).__init__()

        self.data_helper = DataCollectionComboHelper(self, 'data',
                                                     data_collection)
        self.att_helper = ComponentIDComboHelper(self, 'att')
        self.lim_helper = StateAttributeLimitsHelper(self,
                                                     attribute='att',
                                                     lower='v_min',
                                                     upper='v_max',
                                                     log='log')

        self.add_callback('data', self._on_data_change)
        self._on_data_change()

    def _on_data_change(self, *args, **kwargs):
        self.att_helper.set_multiple_data(
            [] if self.data is None else [self.data])
Esempio n. 2
0
class SaveDataState(State):

    data = SelectionCallbackProperty()
    subset = SelectionCallbackProperty()
    component = SelectionCallbackProperty()
    exporter = SelectionCallbackProperty()

    def __init__(self, data_collection=None):

        super(SaveDataState, self).__init__()

        self.data_helper = DataCollectionComboHelper(self, 'data',
                                                     data_collection)
        self.component_helper = ComponentIDComboHelper(
            self, 'component', data_collection=data_collection)

        self.add_callback('data', self._on_data_change)
        self._on_data_change()

        self._sync_data_exporters()

    def _sync_data_exporters(self):

        exporters = list(config.data_exporter)

        def display_func(exporter):
            if exporter.extension == '':
                return "{0} (*)".format(exporter.label)
            else:
                return "{0} ({1})".format(
                    exporter.label,
                    ' '.join('*.' + ext for ext in exporter.extension))

        SaveDataState.exporter.set_choices(self, exporters)
        SaveDataState.exporter.set_display_func(self, display_func)

    def _on_data_change(self, event=None):
        self.component_helper.set_multiple_data([self.data])
        self._sync_subsets()

    def _sync_subsets(self):
        def display_func(subset):
            if subset is None:
                return "All data (no subsets applied)"
            else:
                return subset.label

        subsets = [None] + list(self.data.subsets)

        SaveDataState.subset.set_choices(self, subsets)
        SaveDataState.subset.set_display_func(self, display_func)
Esempio n. 3
0
class TutorialViewerState(ViewerState):

    x_att = SelectionCallbackProperty(
        docstring='The attribute to use on the x-axis')
    y_att = SelectionCallbackProperty(
        docstring='The attribute to use on the y-axis')

    def __init__(self, *args, **kwargs):
        super(TutorialViewerState, self).__init__(*args, **kwargs)
        self._x_att_helper = ComponentIDComboHelper(self, 'x_att')
        self._y_att_helper = ComponentIDComboHelper(self, 'y_att')
        self.add_callback('layers', self._on_layers_change)

    def _on_layers_change(self, value):
        # self.layers_data is a shortcut for
        # [layer_state.layer for layer_state in self.layers]
        self._x_att_helper.set_multiple_data(self.layers_data)
        self._y_att_helper.set_multiple_data(self.layers_data)
Esempio n. 4
0
class WWTImageLayerState(LayerState):
    """A state object for WWT image layers

    """
    layer = CallbackProperty()
    color = CallbackProperty()
    alpha = CallbackProperty()
    vmin = CallbackProperty()
    vmax = CallbackProperty()

    img_data_att = SelectionCallbackProperty(default_index=0)
    stretch = SelectionCallbackProperty(default_index=0,
                                        choices=VALID_STRETCHES)

    def __init__(self, layer=None, **kwargs):
        super(WWTImageLayerState, self).__init__(layer=layer)

        self._sync_color = keep_in_sync(self, 'color', self.layer.style,
                                        'color')
        self._sync_alpha = keep_in_sync(self, 'alpha', self.layer.style,
                                        'alpha')

        self.color = self.layer.style.color

        self.img_data_att_helper = ComponentIDComboHelper(self,
                                                          'img_data_att',
                                                          numeric=True,
                                                          categorical=False)

        self.add_callback('layer', self._on_layer_change)
        if layer is not None:
            self._on_layer_change()

        self.update_from_dict(kwargs)

    def _on_layer_change(self, layer=None):
        if self.layer is None:
            self.img_data_att_helper.set_multiple_data([])
        else:
            self.img_data_att_helper.set_multiple_data([self.layer])

    def update_priority(self, name):
        return 0 if name.endswith(('vmin', 'vmax')) else 1
Esempio n. 5
0
    def __new__(cls,
                function,
                data1=None,
                data2=None,
                cids1=None,
                cid_out=None,
                names1=None,
                names2=None,
                display=None,
                description=None):

        if isinstance(function, ComponentLink):
            names1 = function.input_names
            names2 = [function.output_name]
        elif isinstance(function, LinkCollection):
            names1 = function.labels1
            names2 = function.labels2
        elif type(function) is type and issubclass(function, LinkCollection):
            names1 = function.labels1
            names2 = function.labels2

        class CustomizedStateClass(EditableLinkFunctionState):
            pass

        if names1 is None:
            names1 = getfullargspec(function)[0]

        if names2 is None:
            names2 = []

        setattr(CustomizedStateClass, 'names1', names1)
        setattr(CustomizedStateClass, 'names2', names2)

        for index, input_arg in enumerate(CustomizedStateClass.names1):
            setattr(CustomizedStateClass, input_arg,
                    SelectionCallbackProperty(default_index=index))

        for index, output_arg in enumerate(CustomizedStateClass.names2):
            setattr(CustomizedStateClass, output_arg,
                    SelectionCallbackProperty(default_index=index))

        return super(EditableLinkFunctionState,
                     cls).__new__(CustomizedStateClass)
Esempio n. 6
0
 def ui_and_state(self):
     if isinstance(self.params, list):
         choices = self.params
         display_func = None
     else:
         params_inv = dict((value, key) for key, value in self.params.items())
         choices = list(params_inv.keys())
         display_func = params_inv.get
     property = SelectionCallbackProperty(default_index=0, choices=choices,
                                          display_func=display_func)
     return 'combosel_', QComboBox, property
Esempio n. 7
0
class VolumeLayerState(VispyLayerState):
    """
    A state object for volume layers
    """

    attribute = SelectionCallbackProperty()
    vmin = CallbackProperty()
    vmax = CallbackProperty()
    subset_mode = CallbackProperty('data')
    limits_cache = CallbackProperty({})

    def __init__(self, layer=None, **kwargs):

        super(VolumeLayerState, self).__init__(layer=layer)

        if self.layer is not None:

            self.color = self.layer.style.color
            self.alpha = self.layer.style.alpha

        self.att_helper = ComponentIDComboHelper(self, 'attribute')

        self.lim_helper = StateAttributeLimitsHelper(self,
                                                     attribute='attribute',
                                                     lower='vmin',
                                                     upper='vmax',
                                                     cache=self.limits_cache)

        self.add_callback('layer', self._on_layer_change)
        if layer is not None:
            self._on_layer_change()

        if isinstance(self.layer, Subset):
            self.vmin = 0
            self.vmax = 1

        self.update_from_dict(kwargs)

    def _on_layer_change(self, layer=None):

        with delay_callback(self, 'vmin', 'vmin'):

            if self.layer is None:
                self.att_helper.set_multiple_data([])
            else:
                self.att_helper.set_multiple_data([self.layer])

    def update_priority(self, name):
        return 0 if name.endswith(('vmin', 'vmax')) else 1
Esempio n. 8
0
class TutorialViewerState(MatplotlibDataViewerState):

    x_att = SelectionCallbackProperty(
        docstring='The attribute to use on the x-axis')
    y_att = SelectionCallbackProperty(
        docstring='The attribute to use on the y-axis')

    def __init__(self, *args, **kwargs):
        super(TutorialViewerState, self).__init__(*args, **kwargs)
        self._x_att_helper = ComponentIDComboHelper(self, 'x_att')
        self._y_att_helper = ComponentIDComboHelper(self, 'y_att')
        self.add_callback('layers', self._on_layers_change)
        self.add_callback('x_att', self._on_attribute_change)
        self.add_callback('y_att', self._on_attribute_change)

    def _on_layers_change(self, value):
        self._x_att_helper.set_multiple_data(self.layers_data)
        self._y_att_helper.set_multiple_data(self.layers_data)

    def _on_attribute_change(self, value):
        if self.x_att is not None:
            self.x_axislabel = self.x_att.label
        if self.y_att is not None:
            self.y_axislabel = self.y_att.label
Esempio n. 9
0
class Scatter3DLayerState(ScatterLayerState):

    # FIXME: the following should be a SelectionCallbackProperty
    geo = CallbackProperty('diamond', docstring="Type of marker")
    vz_att = SelectionCallbackProperty(
        docstring="The attribute to use for the z vector arrow")

    def __init__(self, viewer_state=None, layer=None, **kwargs):
        self.vz_att_helper = ComponentIDComboHelper(self,
                                                    'vz_att',
                                                    numeric=True,
                                                    categorical=False)
        super(Scatter3DLayerState, self).__init__(viewer_state=viewer_state,
                                                  layer=layer)
        # self.update_from_dict(kwargs)

    def _on_layer_change(self, layer=None):
        super(Scatter3DLayerState, self)._on_layer_change(layer=layer)
        if self.layer is None:
            self.vz_att_helper.set_multiple_data([])
        else:
            self.vz_att_helper.set_multiple_data([self.layer])
Esempio n. 10
0
class Vispy3DViewerState(ViewerState):
    """
    A common state object for all vispy 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(True)
    native_aspect = CallbackProperty(False)
    line_width = CallbackProperty(1.)

    layers = ListCallbackProperty()

    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(Vispy3DViewerState, 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', self._update_limits_cache)

    def reset_limits(self):
        self.x_lim_helper.log = False
        self.x_lim_helper.percentile = 100.
        self.x_lim_helper.update_values(force=True)
        self.y_lim_helper.log = False
        self.y_lim_helper.percentile = 100.
        self.y_lim_helper.update_values(force=True)
        self.z_lim_helper.log = False
        self.z_lim_helper.percentile = 100.
        self.z_lim_helper.update_values(force=True)

    def _update_limits_cache(self, *args):
        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()

    @property
    def clip_limits(self):
        return (self.x_min, self.x_max, self.y_min, self.y_max, self.z_min,
                self.z_max)

    def set_limits(self, x_min, x_max, y_min, y_max, z_min, z_max):
        with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max', 'z_min',
                            'z_max'):
            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
Esempio n. 11
0
class ProfileTools(QtWidgets.QWidget):

    fit_function = SelectionCallbackProperty()
    collapse_function = SelectionCallbackProperty()

    def __init__(self, parent=None):

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

        self._viewer = weakref.ref(parent)

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

        fix_tab_widget_fontsize(self.ui.tabs)

        self.image_viewer = None

    @property
    def viewer(self):
        return self._viewer()

    def show(self, *args):
        super(ProfileTools, self).show(*args)
        self._on_tab_change()

    def hide(self, *args):
        super(ProfileTools, self).hide(*args)
        self.rng_mode.deactivate()
        self.nav_mode.deactivate()

    def enable(self):

        self.nav_mode = NavigateMouseMode(self.viewer,
                                          press_callback=self._on_nav_activate)
        self.rng_mode = RangeMouseMode(self.viewer)

        self.nav_mode.state.add_callback('x', self._on_slider_change)

        self.ui.tabs.setCurrentIndex(0)

        self.ui.tabs.currentChanged.connect(self._on_tab_change)

        self.ui.button_settings.clicked.connect(self._on_settings)
        self.ui.button_fit.clicked.connect(self._on_fit)
        self.ui.button_clear.clicked.connect(self._on_clear)
        self.ui.button_collapse.clicked.connect(self._on_collapse)

        font = QtGui.QFont("Courier")
        font.setStyleHint(font.Monospace)
        self.ui.text_log.document().setDefaultFont(font)
        self.ui.text_log.setLineWrapMode(self.ui.text_log.NoWrap)

        self.axes = self.viewer.axes
        self.canvas = self.axes.figure.canvas

        self._fit_artists = []

        ProfileTools.fit_function.set_choices(self, list(fit_plugin))
        ProfileTools.fit_function.set_display_func(self, lambda fitter: fitter.label)
        self._connection_fit = connect_combo_selection(self, 'fit_function', self.ui.combosel_fit_function)

        ProfileTools.collapse_function.set_choices(self, list(COLLAPSE_FUNCS))
        ProfileTools.collapse_function.set_display_func(self, COLLAPSE_FUNCS.get)
        self._connection_collapse = connect_combo_selection(self, 'collapse_function', self.ui.combosel_collapse_function)

        self._toolbar_connected = False

        self.viewer.toolbar_added.connect(self._on_toolbar_added)

        self.viewer.state.add_callback('x_att', self._on_x_att_change)

    def _on_x_att_change(self, *event):
        self.nav_mode.clear()
        self.rng_mode.clear()

    def _on_nav_activate(self, *args):
        self._nav_data = self._visible_data()
        self._nav_viewers = {}
        for data in self._nav_data:
            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att_pixel)
            self._nav_viewers[data] = self._viewers_with_data_slice(data, pix_cid)

    def _on_slider_change(self, *args):

        x = self.nav_mode.state.x

        if x is None:
            return

        for data in self._nav_data:

            axis, slc = self._get_axis_and_pixel_slice(data, x)

            for viewer in self._nav_viewers[data]:
                slices = list(viewer.state.slices)
                slices[axis] = slc
                viewer.state.slices = tuple(slices)

    def _get_axis_and_pixel_slice(self, data, x):

        if self.viewer.state.x_att in data.pixel_component_ids:
            axis = self.viewer.state.x_att.axis
            slc = int(round(x))
        else:
            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att_pixel)
            axis = pix_cid.axis
            axis_view = [0] * data.ndim
            axis_view[pix_cid.axis] = slice(None)
            axis_values = data[self.viewer.state.x_att, axis_view]
            slc = int(np.argmin(np.abs(axis_values - x)))

        return axis, slc

    def _on_settings(self):
        d = FitSettingsWidget(self.fit_function())
        d.exec_()

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

        The fitting happens on a dedicated thread, to keep the UI
        responsive
        """

        if self.rng_mode.state.x_min is None or self.rng_mode.state.x_max is None:
            return

        x_range = self.rng_mode.state.x_range
        fitter = self.fit_function()

        def on_success(result):
            fit_results, x, y = result
            report = ""
            normalize = {}
            for layer_artist in fit_results:
                report += ("<b><font color='{0}'>{1}</font>"
                           "</b>".format(color2hex(layer_artist.state.color),
                                         layer_artist.layer.label))
                report += "<pre>" + fitter.summarize(fit_results[layer_artist], x, y) + "</pre>"
                if self.viewer.state.normalize:
                    normalize[layer_artist] = layer_artist.state.normalize_values
            self._report_fit(report)
            self._plot_fit(fitter, fit_results, x, y, normalize)

        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.button_fit.setText("Fit")
            self.ui.button_fit.setEnabled(True)
            self.canvas.draw_idle()

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

        w = Worker(self._fit, fitter, xlim=x_range)
        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 wait_for_fit(self):
        self._fit_worker.wait()

    def _report_fit(self, report):
        self.ui.text_log.document().setHtml(report)

    def _on_clear(self):
        self.ui.text_log.document().setPlainText('')
        self._clear_fit()
        self.canvas.draw_idle()

    def _fit(self, fitter, xlim=None):

        # We cycle through all the visible layers and get the plotted data
        # for each one of them.

        results = {}
        for layer in self.viewer.layers:
            if layer.enabled and layer.visible:
                x, y = layer.state.profile
                x = np.asarray(x)
                y = np.asarray(y)
                keep = (x >= min(xlim)) & (x <= max(xlim))
                if len(x) > 0:
                    results[layer] = fitter.build_and_fit(x[keep], y[keep])

        return results, x, y

    def _clear_fit(self):
        for artist in self._fit_artists[:]:
            artist.remove()
            self._fit_artists.remove(artist)

    def _plot_fit(self, fitter, fit_result, x, y, normalize):

        self._clear_fit()

        for layer in fit_result:
            # y_model = fitter.predict(fit_result[layer], x)
            self._fit_artists.append(fitter.plot(fit_result[layer], self.axes, x,
                                                 alpha=layer.state.alpha,
                                                 linewidth=layer.state.linewidth * 0.5,
                                                 color=layer.state.color,
                                                 normalize=normalize.get(layer, None))[0])

        self.canvas.draw_idle()

    def _visible_data(self):
        datasets = set()
        for layer_artist in self.viewer.layers:
            if layer_artist.enabled and layer_artist.visible:
                if isinstance(layer_artist.state.layer, BaseData):
                    datasets.add(layer_artist.state.layer)
                elif isinstance(layer_artist.state.layer, Subset):
                    datasets.add(layer_artist.state.layer.data)
        return list(datasets)

    def _viewers_with_data_slice(self, data, xatt):

        if self.viewer.session.application is None:
            return []

        viewers = []
        for tab in self.viewer.session.application.viewers:
            for viewer in tab:
                if isinstance(viewer, ImageViewer):
                    for layer_artist in viewer._layer_artist_container[data]:
                        if layer_artist.enabled and layer_artist.visible:
                            if len(viewer.state.slices) >= xatt.axis:
                                viewers.append(viewer)
        return viewers

    def _on_collapse(self):

        if self.rng_mode.state.x_min is None or self.rng_mode.state.x_max is None:
            return

        func = self.collapse_function
        x_range = self.rng_mode.state.x_range

        for data in self._visible_data():

            pix_cid = is_convertible_to_single_pixel_cid(data, self.viewer.state.x_att_pixel)

            for viewer in self._viewers_with_data_slice(data, pix_cid):

                slices = list(viewer.state.slices)

                # TODO: don't need to fetch axis twice
                axis, imin = self._get_axis_and_pixel_slice(data, x_range[0])
                axis, imax = self._get_axis_and_pixel_slice(data, x_range[1])

                current_slice = slices[axis]

                if isinstance(current_slice, AggregateSlice):
                    current_slice = current_slice.center

                imin, imax = min(imin, imax), max(imin, imax)

                slices[axis] = AggregateSlice(slice(imin, imax),
                                              current_slice,
                                              func)

                viewer.state.slices = tuple(slices)

    @property
    def mode(self):
        return MODES[self.tabs.currentIndex()]

    def _on_toolbar_added(self, *event):
        self.viewer.toolbar.tool_activated.connect(self._on_toolbar_activate)
        self.viewer.toolbar.tool_deactivated.connect(self._on_tab_change)

    def _on_toolbar_activate(self, *event):
        self.rng_mode.deactivate()
        self.nav_mode.deactivate()

    def _on_tab_change(self, *event):
        mode = self.mode
        if mode == 'navigate':
            self.rng_mode.deactivate()
            self.nav_mode.activate()
        else:
            self.rng_mode.activate()
            self.nav_mode.deactivate()
Esempio n. 12
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
Esempio n. 13
0
class ScatterLayerState(VispyLayerState):
    """
    A state object for volume layers
    """

    size_mode = CallbackProperty('Fixed')
    size = CallbackProperty()
    size_attribute = SelectionCallbackProperty()
    size_vmin = CallbackProperty()
    size_vmax = CallbackProperty()
    size_scaling = CallbackProperty(1)

    color_mode = CallbackProperty('Fixed')
    cmap_attribute = SelectionCallbackProperty()
    cmap_vmin = CallbackProperty()
    cmap_vmax = CallbackProperty()
    cmap = CallbackProperty()

    xerr_visible = CallbackProperty(False)
    xerr_attribute = SelectionCallbackProperty()
    yerr_visible = CallbackProperty(False)
    yerr_attribute = SelectionCallbackProperty()
    zerr_visible = CallbackProperty(False)
    zerr_attribute = SelectionCallbackProperty()

    vector_visible = CallbackProperty(False)
    vx_attribute = SelectionCallbackProperty()
    vy_attribute = SelectionCallbackProperty()
    vz_attribute = SelectionCallbackProperty()
    vector_scaling = CallbackProperty(1)
    vector_origin = SelectionCallbackProperty(default_index=1)
    vector_arrowhead = CallbackProperty()

    size_limits_cache = CallbackProperty({})
    cmap_limits_cache = CallbackProperty({})

    def __init__(self, layer=None, **kwargs):

        self._sync_markersize = None

        super(ScatterLayerState, self).__init__(layer=layer)

        if self.layer is not None:

            self.color = self.layer.style.color
            self.size = self.layer.style.markersize
            self.alpha = self.layer.style.alpha

        self.size_att_helper = ComponentIDComboHelper(self, 'size_attribute')
        self.cmap_att_helper = ComponentIDComboHelper(self, 'cmap_attribute')
        self.xerr_att_helper = ComponentIDComboHelper(self,
                                                      'xerr_attribute',
                                                      categorical=False)
        self.yerr_att_helper = ComponentIDComboHelper(self,
                                                      'yerr_attribute',
                                                      categorical=False)
        self.zerr_att_helper = ComponentIDComboHelper(self,
                                                      'zerr_attribute',
                                                      categorical=False)

        self.vx_att_helper = ComponentIDComboHelper(self,
                                                    'vx_attribute',
                                                    categorical=False)
        self.vy_att_helper = ComponentIDComboHelper(self,
                                                    'vy_attribute',
                                                    categorical=False)
        self.vz_att_helper = ComponentIDComboHelper(self,
                                                    'vz_attribute',
                                                    categorical=False)

        self.size_lim_helper = StateAttributeLimitsHelper(
            self,
            attribute='size_attribute',
            lower='size_vmin',
            upper='size_vmax',
            cache=self.size_limits_cache)

        self.cmap_lim_helper = StateAttributeLimitsHelper(
            self,
            attribute='cmap_attribute',
            lower='cmap_vmin',
            upper='cmap_vmax',
            cache=self.cmap_limits_cache)

        vector_origin_display = {
            'tail': 'Tail of vector',
            'middle': 'Middle of vector',
            'tip': 'Tip of vector'
        }
        ScatterLayerState.vector_origin.set_choices(self,
                                                    ['tail', 'middle', 'tip'])
        ScatterLayerState.vector_origin.set_display_func(
            self, vector_origin_display.get)

        self.add_callback('layer', self._on_layer_change)
        if layer is not None:
            self._on_layer_change()

        self.cmap = colormaps.members[0][1]

        self.update_from_dict(kwargs)

    def _on_layer_change(self, layer=None):

        with delay_callback(self, 'cmap_vmin', 'cmap_vmax', 'size_vmin',
                            'size_vmax'):
            helpers = [
                self.size_att_helper, self.cmap_att_helper,
                self.xerr_att_helper, self.yerr_att_helper,
                self.zerr_att_helper, self.vx_att_helper, self.vy_att_helper,
                self.vz_att_helper
            ]
            if self.layer is None:
                for helper in helpers:
                    helper.set_multiple_data([])
            else:
                for helper in helpers:
                    helper.set_multiple_data([self.layer])

    def update_priority(self, name):
        return 0 if name.endswith(('vmin', 'vmax')) else 1

    def _layer_changed(self):

        super(ScatterLayerState, self)._layer_changed()

        if self._sync_markersize is not None:
            self._sync_markersize.stop_syncing()

        if self.layer is not None:
            self.size = self.layer.style.markersize
            self._sync_markersize = keep_in_sync(self, 'size',
                                                 self.layer.style,
                                                 'markersize')

    def flip_size(self):
        self.size_lim_helper.flip_limits()

    def flip_cmap(self):
        self.cmap_lim_helper.flip_limits()
Esempio n. 14
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()
Esempio n. 15
0
class WWTTableLayerState(LayerState):
    """
    A state object for WWT layers
    """

    layer = CallbackProperty()
    color = CallbackProperty()
    size = CallbackProperty()
    alpha = CallbackProperty()

    size_mode = SelectionCallbackProperty(default_index=0)
    size = CallbackProperty()
    size_att = SelectionCallbackProperty()
    size_vmin = CallbackProperty()
    size_vmax = CallbackProperty()
    size_scaling = CallbackProperty(1)

    color_mode = SelectionCallbackProperty(default_index=0)
    cmap_att = SelectionCallbackProperty()
    cmap_vmin = CallbackProperty()
    cmap_vmax = CallbackProperty()
    cmap = CallbackProperty()
    cmap_mode = color_mode

    size_limits_cache = CallbackProperty({})
    cmap_limits_cache = CallbackProperty({})

    img_data_att = SelectionCallbackProperty(default_index=0)

    def __init__(self, layer=None, **kwargs):

        self._sync_markersize = None

        super(WWTTableLayerState, self).__init__(layer=layer)

        self._sync_color = keep_in_sync(self, 'color', self.layer.style, 'color')
        self._sync_alpha = keep_in_sync(self, 'alpha', self.layer.style, 'alpha')
        self._sync_size = keep_in_sync(self, 'size', self.layer.style, 'markersize')

        self.color = self.layer.style.color
        self.size = self.layer.style.markersize
        self.alpha = self.layer.style.alpha

        self.size_att_helper = ComponentIDComboHelper(self, 'size_att',
                                                      numeric=True,
                                                      categorical=False)
        self.cmap_att_helper = ComponentIDComboHelper(self, 'cmap_att',
                                                      numeric=True,
                                                      categorical=False)
        self.img_data_att_helper = ComponentIDComboHelper(self, 'img_data_att',
                                                          numeric=True,
                                                          categorical=False)

        self.size_lim_helper = StateAttributeLimitsHelper(self, attribute='size_att',
                                                          lower='size_vmin', upper='size_vmax',
                                                          cache=self.size_limits_cache)

        self.cmap_lim_helper = StateAttributeLimitsHelper(self, attribute='cmap_att',
                                                          lower='cmap_vmin', upper='cmap_vmax',
                                                          cache=self.cmap_limits_cache)

        self.add_callback('layer', self._on_layer_change)
        if layer is not None:
            self._on_layer_change()

        self.cmap = colormaps.members[0][1]

        # Color and size encoding depending on attributes is only available
        # in PyWWT 0.6 or later.
        if PYWWT_LT_06:
            modes = ['Fixed']
        else:
            modes = ['Fixed', 'Linear']

        WWTTableLayerState.color_mode.set_choices(self, modes)
        WWTTableLayerState.size_mode.set_choices(self, modes)

        self.update_from_dict(kwargs)

    def _on_layer_change(self, layer=None):
        with delay_callback(self, 'cmap_vmin', 'cmap_vmax', 'size_vmin', 'size_vmax'):
            if self.layer is None:
                self.cmap_att_helper.set_multiple_data([])
                self.size_att_helper.set_multiple_data([])
                self.img_data_att_helper.set_multiple_data([])
            else:
                self.cmap_att_helper.set_multiple_data([self.layer])
                self.size_att_helper.set_multiple_data([self.layer])
                self.img_data_att_helper.set_multiple_data([self.layer])

    def update_priority(self, name):
        return 0 if name.endswith(('vmin', 'vmax')) else 1

    def _layer_changed(self):

        super(WWTTableLayerState, self)._layer_changed()

        if self._sync_markersize is not None:
            self._sync_markersize.stop_syncing()

        if self.layer is not None:
            self.size = self.layer.style.markersize
            self._sync_markersize = keep_in_sync(self, 'size', self.layer.style, 'markersize')

    def flip_size(self):
        self.size_lim_helper.flip_limits()

    def flip_cmap(self):
        self.cmap_lim_helper.flip_limits()
Esempio n. 16
0
class LinkEditorState(State):

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

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

        super(LinkEditorState, self).__init__()

        # Find identity function
        for func in link_function:
            if func.function.__name__ == 'identity':
                self._identity = func
                break
        else:
            raise ValueError("Could not find identity link function")

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

        self.att1_helper = ComponentIDComboHelper(self,
                                                  'att1',
                                                  pixel_coord=True,
                                                  world_coord=True)
        self.att2_helper = ComponentIDComboHelper(self,
                                                  'att2',
                                                  pixel_coord=True,
                                                  world_coord=True)

        # 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._on_data1_change()
        self._on_data2_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.data1 is self.data1 and link.data2 is self.data2) or
                    (link.data1 is self.data2 and link.data2 is self.data1)):
                    links.append(link)

        return links

    def flip_data(self, *args):
        # FIXME: since the links will be the same in the list of current links,
        # we can make sure we reselect the same one as before - it would be
        # better if this didn't change in the first place though.
        _original_current_link = self.current_link
        with delay_callback(self, 'data1', 'data2'):
            self.data1, self.data2 = self.data2, self.data1
        self.current_link = _original_current_link

    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()
        self.att1_helper.set_multiple_data(
            [] if self.data1 is None else [self.data1])

    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()
        self.att2_helper.set_multiple_data(
            [] if self.data2 is None else [self.data2])

    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 simple_link(self, *args):
        self.new_link(self._identity)
        self.current_link.x = self.att1
        self.current_link.y = self.att2

    def new_link(self, function_or_helper):

        if hasattr(function_or_helper, 'function'):
            link = EditableLinkFunctionState(
                function_or_helper.function,
                data1=self.data1,
                data2=self.data2,
                names2=function_or_helper.output_labels,
                description=function_or_helper.info,
                display=function_or_helper.function.__name__)
        elif function_or_helper.helper.cid_independent:
            # This shortcut is needed for e.g. the WCS auto-linker, which has a dynamic
            # description but doesn't need to take any component IDs.
            link = EditableLinkFunctionState(
                function_or_helper.helper(data1=self.data1, data2=self.data2))
        else:
            link = EditableLinkFunctionState(function_or_helper.helper,
                                             data1=self.data1,
                                             data2=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)
Esempio n. 17
0
class ArithmeticEditorWidget(QtWidgets.QDialog):

    data = SelectionCallbackProperty()

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

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

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

        self.list = self.ui.list_derived_components

        self.data_collection = data_collection

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

        for data in data_collection:

            # First find all derived components (only ones based on arithmetic
            # expressions)

            self._components_derived[data] = []

            for cid in data.derived_components:
                comp = data.get_component(cid)
                if isinstance(comp.link, ParsedComponentLink):
                    comp_state = {}
                    comp_state['cid'] = cid
                    comp_state['label'] = cid.label
                    comp_state['equation'] = comp.link._parsed
                    self._state[data][cid] = comp_state
                    self._components_derived[data].append(cid)

            # Keep track of all other components

            self._components_other[data] = []

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

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

        if initial_data is None:
            self.ui.combosel_data.setCurrentIndex(0)
        else:
            self.data = initial_data

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

        self.ui.button_add_derived.clicked.connect(self._add_derived_component)
        self.ui.button_edit_derived.clicked.connect(
            self._edit_derived_component)
        self.ui.button_remove_derived.clicked.connect(
            self._remove_derived_component)

        self.ui.list_derived_components.itemSelectionChanged.connect(
            self._update_selection_derived)

        self._update_selection_derived()

        self.ui.list_derived_components.itemChanged.connect(self._update_state)
        self.ui.list_derived_components.order_changed.connect(
            self._update_state)
        self.ui.list_derived_components.itemDoubleClicked.connect(
            self._edit_derived_component)

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

    def _update_selection_derived(self):
        enabled = self.list.selected_cid is not None
        self.button_edit_derived.setEnabled(enabled)
        self.button_remove_derived.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)

        mapping = {}
        for cid in self.data.components:
            mapping[cid] = cid.label

        self.list.clear()
        for cid in self._components_derived[self.data]:
            label = self._state[self.data][cid]['label']
            if self._state[self.data][cid]['equation'] is None:
                expression = ''
            else:
                expression = self._state[self.data][cid]['equation'].render(
                    mapping)
            self.list.add_cid_and_label(cid, [label, expression],
                                        editable=False)

        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_derived[self.data] = []
        for item in self.list:
            cid = item.data(0, Qt.UserRole)
            self._state[self.data][cid]['label'] = item.text(0)
            self._components_derived[self.data].append(cid)
        self._update_component_lists()

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

    def _add_derived_component(self, *args):

        comp_state = {}
        comp_state['cid'] = ComponentID('')
        comp_state['label'] = ''
        comp_state['equation'] = None

        self._components_derived[self.data].append(comp_state['cid'])
        self._state[self.data][comp_state['cid']] = comp_state

        self._update_component_lists()

        self.list.select_cid(comp_state['cid'])

        result = self._edit_derived_component()

        if not result:  # user cancelled
            self._components_derived[self.data].remove(comp_state['cid'])
            self._state[self.data].pop(comp_state['cid'])
            self._update_component_lists()

    def _edit_derived_component(self, event=None):

        derived_item = self.list.selected_item

        if derived_item is None:
            return False

        derived_cid = self.list.selected_cid

        # Note, we put the pixel/world components last as it's most likely the
        # user wants to use one of the main components.
        mapping = {}
        references = {}
        for cid in (self.data.main_components + self.data.pixel_component_ids +
                    self.data.world_component_ids):
            if cid is not derived_cid:
                mapping[cid] = cid.label
                references[cid.label] = cid

        label = self._state[self.data][derived_cid]['label']

        if self._state[self.data][derived_cid]['equation'] is None:
            equation = None
        else:
            equation = self._state[self.data][derived_cid]['equation'].render(
                mapping)

        dialog = EquationEditorDialog(label=label,
                                      equation=equation,
                                      references=references,
                                      parent=self)
        dialog.setWindowFlags(self.windowFlags() | Qt.Window)
        dialog.setFocus()
        dialog.raise_()
        dialog.exec_()

        if dialog.final_expression is None:
            return False

        name, equation = dialog.get_final_label_and_parsed_command()
        self._state[self.data][derived_cid]['label'] = name
        self._state[self.data][derived_cid]['equation'] = equation
        derived_item.setText(0, name)

        # Make sure we update the component list here since the equation may
        # have changed and we need to update the preview
        self._update_component_lists()

        return True

    def accept(self):

        for data in self._components_derived:

            cids_derived = self._components_derived[data]
            cids_other = self._components_other[data]
            cids_all = cids_other + cids_derived
            cids_existing = data.components
            components = dict((cid.uuid, cid) for cid in data.components)

            # First deal with renaming of components
            for cid_new in cids_derived:
                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)

            # Third, update/add arithmetic expressions as needed
            for cid_new in cids_derived:
                if any(cid_new is cid_old for cid_old in cids_existing):
                    comp = data.get_component(cid_new)
                    if comp.link._parsed._cmd != self._state[data][cid_new][
                            'equation']._cmd:
                        comp.link._parsed._cmd = self._state[data][cid_new][
                            'equation']._cmd
                        comp.link._parsed._references = components
                        if data.hub:
                            msg = NumericalDataChangedMessage(data)
                            data.hub.broadcast(msg)
                else:
                    pc = ParsedCommand(
                        self._state[data][cid_new]['equation']._cmd,
                        components)
                    link = ParsedComponentLink(cid_new, pc)
                    data.add_component_link(link)

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

        super(ArithmeticEditorWidget, self).accept()
Esempio n. 18
0
class ComponentManagerWidget(QtWidgets.QDialog):

    data = SelectionCallbackProperty()

    def __init__(self, data_collection=None, initial_data=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)

        if initial_data is None:
            self.ui.combosel_data.setCurrentIndex(0)
        else:
            self.data = initial_data

        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()
Esempio n. 19
0
class EquationEditorDialog(QtWidgets.QDialog):

    tip_text = (
        "<b>Note:</b> Attribute names in the expression should be surrounded "
        "by {{ }} brackets (e.g. {{{example}}}), and you can use "
        "Numpy functions using np.&lt;function&gt;, as well as any "
        "other function defined in your config.py file.<br><br>"
        "<b>Example expressions:</b><br><br>"
        "  - Subtract 10 from '{example}': {{{example}}} - 10<br>"
        "  - Scale '{example}' to [0:1]: ({{{example}}} - np.min({{{example}}})) / np.ptp({{{example}}})<br>"
        "  - Multiply '{example}' by pi: {{{example}}} * np.pi<br>"
        "  - Use masking: {{{example}}} * ({{{example}}} &lt; 1)<br>")

    placeholder_text = ("Type any mathematical expression here - "
                        "you can include attribute names from the "
                        "drop-down below by selecting them and "
                        "clicking 'Insert'. See below for examples "
                        "of valid expressions")

    attribute = SelectionCallbackProperty()

    def __init__(self,
                 label=None,
                 data=None,
                 equation=None,
                 references=None,
                 parent=None):

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

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

        # Get mapping from label to component ID
        if references is not None:
            self.references = references
        elif data is not None:
            self.references = OrderedDict()
            for cid in data.coordinate_components + data.main_components:
                self.references[cid.label] = cid

        example = sorted(self.references, key=len)[0]

        self.ui.text_label.setPlaceholderText("New attribute name")
        self.ui.expression.setPlaceholderText(
            self.placeholder_text.format(example=example))

        self.ui.label.setText(self.tip_text.format(example=example))

        if label is not None:
            self.ui.text_label.setText(label)

        self.ui.text_label.textChanged.connect(self._update_status)

        # Populate component combo
        EquationEditorDialog.attribute.set_choices(self, list(self.references))
        self._connection = connect_combo_selection(self, 'attribute',
                                                   self.ui.combosel_component)

        # Set up labels for auto-completion
        labels = ['{' + l + '}' for l in self.references]
        self.ui.expression.set_word_list(labels)

        if equation is not None:
            self.ui.expression.insertPlainText(equation)

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

        self.ui.button_insert.clicked.connect(self._insert_component)

        self.ui.expression.updated.connect(self._update_status)
        self._update_status()

    def _insert_component(self):
        label = self.attribute
        self.expression.insertPlainText('{' + label + '}')

    def _update_status(self):

        # If the text hasn't changed, no need to check again
        if hasattr(self,
                   '_cache') and self._cache == (self.ui.text_label.text(),
                                                 self._get_raw_command()):
            return

        if self.ui.text_label.text() == "":
            self.ui.label_status.setStyleSheet('color: red')
            self.ui.label_status.setText("Attribute name not set")
            self.ui.button_ok.setEnabled(False)
        elif self._get_raw_command() == "":
            self.ui.label_status.setText("")
            self.ui.button_ok.setEnabled(False)
        else:
            try:
                pc = self._get_parsed_command()
                pc.evaluate_test()
            except SyntaxError:
                self.ui.label_status.setStyleSheet('color: red')
                self.ui.label_status.setText("Incomplete or invalid syntax")
                self.ui.button_ok.setEnabled(False)
            except InvalidTagError as exc:
                self.ui.label_status.setStyleSheet('color: red')
                self.ui.label_status.setText("Invalid component: {0}".format(
                    exc.tag))
                self.ui.button_ok.setEnabled(False)
            except Exception as exc:
                self.ui.label_status.setStyleSheet('color: red')
                self.ui.label_status.setText(str(exc))
                self.ui.button_ok.setEnabled(False)
            else:
                self.ui.label_status.setStyleSheet('color: green')
                self.ui.label_status.setText("Valid expression")
                self.ui.button_ok.setEnabled(True)

        self._cache = self.ui.text_label.text(), self._get_raw_command()

    def _get_raw_command(self):
        return str(self.ui.expression.toPlainText())

    def _get_parsed_command(self):
        expression = self._get_raw_command()
        return ParsedCommand(expression, self.references)

    def get_final_label_and_parsed_command(self):
        return self.ui.text_label.text(), self._get_parsed_command()

    def accept(self):
        self.final_expression = self._get_parsed_command()._cmd
        super(EquationEditorDialog, self).accept()

    def reject(self):
        self.final_expression = None
        super(EquationEditorDialog, self).reject()
Esempio n. 20
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)