Пример #1
0
class Str(Variable):
    """A variable wrapper for a string variable.
       """

    def __init__(self, default_value='', iotype=None, desc=None,
                 **metadata):
        if not isinstance(default_value, str):
            raise ValueError("Default value for a Str must be a string")

        # Put iotype in the metadata dictionary
        if iotype is not None:
            metadata['iotype'] = iotype

        # Put desc in the metadata dictionary
        if desc is not None:
            metadata['desc'] = desc

        self._validator = Enthought_Str(default_value=default_value, **metadata)

        super(Str, self).__init__(default_value=default_value, **metadata)

    def validate(self, obj, name, value):
        """ Use the Enthought trait's validate.
        """
        return self._validator.validate(obj, name, value)

    def create_editor(self):
        """ User the one in the Enthought trait.
        """
        return self._validator.create_editor()
Пример #2
0
    def __init__(self, default_value='', iotype=None, desc=None,
                 **metadata):
        if not isinstance(default_value, str):
            raise ValueError("Default value for a Str must be a string")

        # Put iotype in the metadata dictionary
        if iotype is not None:
            metadata['iotype'] = iotype

        # Put desc in the metadata dictionary
        if desc is not None:
            metadata['desc'] = desc

        self._validator = Enthought_Str(default_value=default_value, **metadata)

        super(Str, self).__init__(default_value=default_value, **metadata)
Пример #3
0
class ImagePanel(ThemedWindow):

    # The optional text to display in the top or bottom of the image slice:
    text = Str(event='updated')

    # Can the application change the theme contents?
    mutable_theme = Bool(False)

    # Is the image panel capable of displaying text?
    can_show_text = Property

    # The adjusted size of the panel, taking into account the size of its
    # current children and the image border:
    adjusted_size = Property

    # The best size of the panel, taking into account the best size of its
    # children and the image border:
    best_size = Property

    # The underlying wx control:
    control = Instance(wx.Window)

    #-- Private Traits ---------------------------------------------------------

    # The size of the current text:
    text_size = Property(depends_on='text, control')

    #-- Public Methods ---------------------------------------------------------

    def create_control(self, parent):
        """ Creates the underlying wx.Panel control.
        """
        self.control = control = wx.Panel(parent,
                                          -1,
                                          style=wx.TAB_TRAVERSAL
                                          | wx.FULL_REPAINT_ON_RESIZE)

        # Set up the sizer for the control:
        control.SetSizer(ImageSizer(self.theme))

        # Initialize the control (set-up event handlers, ...)
        self.init_control()

        # Attach the image slice to the control:
        control._image_slice = self.theme.image_slice

        # Set the panel's background colour to the image slice bg_color:
        control.SetBackgroundColour(control._image_slice.bg_color)

        return control

    def layout(self):
        """ Lays out the contents of the image panel.
        """
        self.control.Layout()
        self.control.Refresh()

    #-- Property Implementations -----------------------------------------------

    def _get_adjusted_size(self):
        """ Returns the adjusted size of the panel taking into account the
            size of its current children and the image border.
        """
        control = self.control
        dx, dy = 0, 0
        for child in control.GetChildren():
            dx, dy = child.GetSizeTuple()

        size = self._adjusted_size_of(dx, dy)
        control.SetSize(size)

        return size

    def _get_best_size(self):
        """ Returns the best size of the panel taking into account the
            best size of its current children and the image border.
        """
        control = self.control
        dx, dy = 0, 0
        for child in control.GetChildren():
            dx, dy = child.GetBestSize()

        return self._adjusted_size_of(dx, dy)

    @cached_property
    def _get_can_show_text(self):
        """ Returns whether or not the image panel is capable of displaying
            text.
        """
        tdx, tdy, descent, leading = self.control.GetFullTextExtent('Myj')
        slice = self.theme.image_slice
        tdy += 4
        return ((tdy <= slice.xtop) or (tdy <= slice.xbottom)
                or (slice.xleft >= 40) or (slice.xright >= 40))

    @cached_property
    def _get_text_size(self):
        """ Returns the text size information for the window.
        """
        if (self.text == '') or (self.control is None):
            return ZeroTextSize

        return self.control.GetFullTextExtent(self.text)

    #-- Trait Event Handlers ---------------------------------------------------

    def _updated_changed(self):
        """ Handles a change that requires the control to be updated.
        """
        if self.control is not None:
            self.control.Refresh()

    def _mutable_theme_changed(self, state):
        """ Handles a change to the 'mutable_theme' trait.
        """
        self.on_trait_change(
            self._theme_modified,
            'theme.[border.-,content.-,label.-,alignment,content_color,'
            'label_color]',
            remove=not state)

    def _theme_modified(self):
        if self.control is not None:
            self.layout()

    def _theme_changed(self, theme):
        """ Handles the 'theme' trait being changed.
        """
        super(ImagePanel, self)._theme_changed()

        control = self.control
        if (control is not None) and (theme is not None):
            # Attach the image slice to the control:
            control._image_slice = theme.image_slice

            # Set the panel's background colour to the image slice bg_color:
            control.SetBackgroundColour(control._image_slice.bg_color)

    #-- wx.Python Event Handlers -----------------------------------------------

    def _paint_fg(self, dc):
        """ Paints the foreground into the specified device context.
        """
        # If we have text and have room to draw it, then do so:
        text = self.text
        if (text != '') and self.can_show_text:
            theme = self.theme
            dc.SetBackgroundMode(wx.TRANSPARENT)
            dc.SetTextForeground(theme.label_color)
            dc.SetFont(self.control.GetFont())

            alignment = theme.alignment
            label = theme.label
            wdx, wdy = self.control.GetClientSizeTuple()
            tdx, tdy, descent, leading = self.text_size
            tx = None
            slice = theme.image_slice
            xleft = slice.xleft
            xright = slice.xright
            xtop = slice.xtop
            xbottom = slice.xbottom
            ltop = label.top
            lbottom = label.bottom
            tdyp = tdy + ltop + lbottom
            cl = xleft + label.left
            cr = wdx - xright - label.right
            if (tdyp <= xtop) and (xtop >= xbottom):
                ty = ((ltop + xtop - lbottom - tdy) / 2) + 1
            elif tdy <= xbottom:
                ty = wdy + ((ltop - xbottom - lbottom - tdy) / 2)
            else:
                ty = (wdy + xtop + label.top - xbottom - label.bottom -
                      tdy) / 2
                if xleft >= xright:
                    cl = label.left
                    cr = xleft - label.right
                else:
                    cl = wdx - xright + label.left
                    cr = wdx - label.right

            # Calculate the x coordinate for the specified alignment type:
            if alignment == 'left':
                tx = cl
            elif alignment == 'right':
                tx = cr - tdx
            else:
                tx = (cl + cr - tdx) / 2

            # Draw the (clipped) text string:
            dc.SetClippingRegion(cl, ty, cr - cl, tdy)
            dc.DrawText(text, tx, ty)
            dc.DestroyClippingRegion()

    #-- Private Methods --------------------------------------------------------

    def _adjusted_size_of(self, dx, dy):
        """ Returns the adjusted size of its children, taking into account the
            image slice border.
        """
        slice = self.theme.image_slice
        content = self.theme.content
        sizer = self.control.GetSizer()
        return wx.Size(
            dx + min(slice.left, slice.xleft) +
            min(slice.right, slice.xright) + content.left + content.right,
            dy + min(slice.top, slice.xtop) +
            min(slice.bottom, slice.xbottom) + content.top + content.bottom)
Пример #4
0
class ComparePrediction(HasTraits):
    root_path = Directory(entries=10)
    score_path = File(entries=10)
    pred_list = List(MedicalItem)
    cur_case = Any
    splitter = Str
    space = Str(" " * 20)
    contour = Bool(True)
    mask = Bool(False)
    liver = Bool(False)
    alpha = Float(0.3)
    title1 = Str("Phase2")
    title2 = Str("Prediction")
    color1 = Color("red")
    color2 = Color("yellow")
    label_num = Int(2)

    cur_ind = Int
    total_ind = Int

    figure1 = Instance(Figure, ())
    figure2 = Instance(Figure, ())
    showButton = Button("Show")
    lastButton = Button("Last")
    nextButton = Button("Next")
    view = View(HGroup(
        VGroup(VGroup(
            Item("root_path", width=250),
            Item("score_path"),
        ),
               Item("pred_list",
                    editor=TableEditor(columns=[
                        ObjectColumn(name="name", width=0.3),
                        ObjectColumn(name="slices", width=0.2),
                        ObjectColumn(name="liver_score", width=0.1),
                        ObjectColumn(name="tumor_score", width=0.1),
                    ],
                                       auto_size=True,
                                       orientation="vertical",
                                       row_factory=MedicalItem,
                                       editable=False,
                                       selected="cur_case"),
                    show_label=False),
               show_border=True),
        VSplit(
            HSplit(
                VGroup(
                    HGroup(Item("title1", show_label=False, style="readonly"),
                           Item("space", show_label=False, style="readonly"),
                           Item("color1", label="Color")),
                    Item("figure1",
                         editor=MPLFigureEditor(toolbar=True),
                         show_label=False,
                         height=542)),
                VGroup(
                    HGroup(Item("title2", show_label=False, style="readonly"),
                           Item("space", show_label=False, style="readonly"),
                           Item("color2", label="Color")),
                    Item("figure2",
                         editor=MPLFigureEditor(toolbar=True),
                         show_label=False))),
            HGroup(
                Item("space", show_label=False, style="readonly"),
                Item("showButton", show_label=False),
                Item("liver"),
                Item("contour"),
                Item("mask"),
                Item("alpha"),
                Item("label_num", label="Label"),
                Item("cur_ind", label="Index"),
                Item("splitter", label="/", style="readonly"),
                Item("total_ind", show_label=False, style="readonly"),
                Item("lastButton", show_label=False),
                Item("nextButton", show_label=False),
                Item("space", show_label=False, style="readonly"),
            )),
    ),
                width=1324,
                height=580,
                title="Image Viewer",
                resizable=True,
                handler=ViewHandler())

    def __init__(self, adapter, **kw):
        super(ComparePrediction, self).__init__(**kw)

        self.adap = adapter

        self.cur_ind = 0
        self.total_ind = 0
        self.accelerate = 1
        self.cur_show = ""
        self.gesture = Gesture.Axial

    def connect(self):
        # Figure events
        self.figure1.canvas.mpl_connect("button_press_event",
                                        self.button_press_event)
        self.figure1.canvas.mpl_connect("button_release_event",
                                        self.button_release_event)
        self.figure1.canvas.mpl_connect("scroll_event", self.scroll_event)
        self.figure1.canvas.mpl_connect("key_press_event",
                                        self.key_press_event)
        self.figure1.canvas.mpl_connect("key_release_event",
                                        self.key_release_event)
        self.figure2.canvas.mpl_connect("button_press_event",
                                        self.button_press_event)
        self.figure2.canvas.mpl_connect("button_release_event",
                                        self.button_release_event)
        self.figure2.canvas.mpl_connect("scroll_event", self.scroll_event)

        self.figure1.canvas.setFocusPolicy(Qt.ClickFocus)

    def scroll_event(self, event):
        if event.button == "down":
            self._nextButton_fired()
        else:
            self._lastButton_fired()

    def button_press_event(self, event):
        if event.button == 1:
            self.accelerate = 3
        elif event.button == 3:
            self.accelerate = 6
        self.figure1.canvas.setFocus()

    def button_release_event(self, event):
        _ = event
        self.accelerate = 1

    def key_press_event(self, event):
        if event.key == "control":
            self.contour = False
            self._contour_changed()

    def key_release_event(self, event):
        if event.key == "control":
            self.contour = True
            self._contour_changed()
        elif event.key == "shift":
            self.liver = not self.liver
            self._liver_changed()
        elif event.key == "down":
            self._nextButton_fired()
        elif event.key == "up":
            self._lastButton_fired()
        elif event.key == "right":
            self._nextButton_fired()
        elif event.key == "left":
            self._lastButton_fired()
        elif event.key == "1":
            if self.gesture != Gesture.Axial:
                self.gesture = Gesture.Axial
                self.reset_index()
                self.refresh()
        elif event.key == "2":
            if self.gesture != Gesture.Coronal:
                self.gesture = Gesture.Coronal
                self.reset_index()
                self.refresh()
        elif event.key == "3":
            if self.gesture != Gesture.Sagittal:
                self.gesture = Gesture.Sagittal
                self.reset_index()
                self.refresh()

    def reset_index(self):
        self.cur_ind = self.adap.get_min_idx(self.gesture)
        self.total_ind = self.adap.get_num_slices(self.gesture) - 1

    def image_show(self, ind):
        self.axesImage1.set_data(
            self.adap.get_slice1(ind,
                                 self.color1.getRgb()[:-1],
                                 alpha=self.alpha,
                                 contour=self.contour,
                                 mask_lab=self.mask,
                                 ges=self.gesture))
        self.axesImage2.set_data(
            self.adap.get_slice2(ind,
                                 self.color2.getRgb()[:-1],
                                 alpha=self.alpha,
                                 contour=self.contour,
                                 mask_lab=self.mask,
                                 ges=self.gesture))
        self.connect()
        self.cur_ind = self.adap.real_ind(ind)
        self.update_figure()

    def update_figure(self):
        if self.figure1.canvas is not None:
            self.figure1.canvas.draw_idle()
        if self.figure2.canvas is not None:
            self.figure2.canvas.draw_idle()

    def refresh(self):
        self.image_show(self.cur_ind)

    def _root_path_default(self):
        return self.adap.get_root_path()

    def _score_path_default(self):
        return ""

    def _pred_list_default(self):
        if Path(self.root_path).exists():
            return [
                MedicalItem(name=x, slices=y)
                for x, y in self.adap.get_file_list()
            ]
        else:
            return []

    def _root_path_changed(self):
        if Path(self.root_path).exists():
            self.adap.update_root_path(self.root_path)
            self.pred_list = [
                MedicalItem(name=x, slices=y)
                for x, y in self.adap.get_file_list()
            ]

    def _score_path_changed(self):
        if Path(self.score_path).exists():
            self.pred_list = [
                MedicalItem(name=x, slices=y, liver_score=z1, tumor_score=z2)
                for x, y, z1, z2 in self.adap.get_pair_list(self.score_path)
            ]
        else:
            print("Warning: {} not exists".format(self.score_path))

    def _cur_ind_changed(self):
        if 0 <= self.cur_ind <= self.total_ind and self.total_ind > 0:
            self.refresh()

    def _alpha_changed(self):
        if 0.0 <= self.alpha <= 1.0 and self.total_ind > 0:
            self.refresh()

    def _color1_changed(self):
        if self.total_ind > 0:
            self.refresh()

    def _color2_changed(self):
        if self.total_ind > 0:
            self.refresh()

    def _contour_changed(self):
        if self.total_ind > 0:
            self.refresh()

    def _mask_changed(self):
        if self.total_ind > 0:
            self.refresh()

    def _liver_changed(self):
        if self.total_ind > 0:
            self.adap.update_choice(liver=self.liver)
            self.refresh()

    def _label_num_changed(self):
        if self.total_ind > 0:
            self.adap.update_choice(label=self.label_num)
            self.refresh()

    def _figure1_default(self):
        figure = Figure()
        figure.add_axes([0.0, 0.0, 1.0, 1.0])
        figure.axes[0].axis("off")
        self.axesImage1 = figure.axes[0].imshow(np.ones((512, 512)),
                                                cmap="gray")
        return figure

    def _figure2_default(self):
        figure = Figure()
        figure.add_axes([0.0, 0.0, 1.0, 1.0])
        figure.axes[0].axis("off")
        self.axesImage2 = figure.axes[0].imshow(np.ones((512, 512)),
                                                cmap="gray")
        return figure

    def _lastButton_fired(self):
        if self.total_ind > 0:
            self.image_show(self.cur_ind - self.accelerate)

    def _nextButton_fired(self):
        if self.total_ind > 0:
            self.image_show(self.cur_ind + self.accelerate)

    def _showButton_fired(self):
        if self.cur_case and self.cur_show != self.cur_case:
            self.adap.update_case(self.cur_case.name,
                                  liver=self.liver,
                                  label=self.label_num)
            self.reset_index()
            self.refresh()
            self.cur_show = self.cur_case
Пример #5
0
class SolutionView(HasTraits):
    python_console_cmds = Dict()
    # we need to doubleup on Lists to store the psuedo absolutes separately
    # without rewriting everything
    """
  logging_v : toggle logging for velocity files
  directory_name_v : location and name of velocity files
  logging_p : toggle logging for position files
  directory_name_p : location and name of velocity files
  """
    plot_history_max = Int(1000)
    last_plot_update_time = Float()
    last_stale_update_time = Float()
    logging_v = Bool(False)
    display_units = Enum(["degrees", "meters"])
    directory_name_v = File

    logging_p = Bool(False)
    directory_name_p = File

    lats_psuedo_abs = List()
    lngs_psuedo_abs = List()
    alts_psuedo_abs = List()

    table = List()
    dops_table = List()
    pos_table = List()
    vel_table = List()

    rtk_pos_note = Str(
        "It is necessary to enter the \"Surveyed Position\" settings for the base station in order to view the RTK Positions in this tab."
    )

    plot = Instance(Plot)
    plot_data = Instance(ArrayPlotData)
    # Store plots we care about for legend

    running = Bool(True)
    zoomall = Bool(False)
    position_centered = Bool(False)

    clear_button = SVGButton(
        label='',
        tooltip='Clear',
        filename=resource_filename('console/images/iconic/x.svg'),
        width=16,
        height=16)
    zoomall_button = SVGButton(
        label='',
        tooltip='Zoom All',
        toggle=True,
        filename=resource_filename('console/images/iconic/fullscreen.svg'),
        width=16,
        height=16)
    center_button = SVGButton(
        label='',
        tooltip='Center on Solution',
        toggle=True,
        filename=resource_filename('console/images/iconic/target.svg'),
        width=16,
        height=16)
    paused_button = SVGButton(
        label='',
        tooltip='Pause',
        toggle_tooltip='Run',
        toggle=True,
        filename=resource_filename('console/images/iconic/pause.svg'),
        toggle_filename=resource_filename('console/images/iconic/play.svg'),
        width=16,
        height=16)

    traits_view = View(
        HSplit(
            VGroup(
                Item('table',
                     style='readonly',
                     editor=TabularEditor(adapter=SimpleAdapter()),
                     show_label=False,
                     width=0.3),
                Item('rtk_pos_note',
                     show_label=False,
                     resizable=True,
                     editor=MultilineTextEditor(TextEditor(multi_line=True)),
                     style='readonly',
                     width=0.3,
                     height=-40),
            ),
            VGroup(
                HGroup(
                    Item('paused_button', show_label=False),
                    Item('clear_button', show_label=False),
                    Item('zoomall_button', show_label=False),
                    Item('center_button', show_label=False),
                    Item('display_units', label="Display Units"),
                ),
                Item('plot',
                     show_label=False,
                     editor=ComponentEditor(bgcolor=(0.8, 0.8, 0.8))),
            )))

    def _zoomall_button_fired(self):
        self.zoomall = not self.zoomall

    def _center_button_fired(self):
        self.position_centered = not self.position_centered

    def _paused_button_fired(self):
        self.running = not self.running

    def _reset_remove_current(self):
        self.plot_data.update_data(self._get_update_current())

    def _get_update_current(self, current_dict={}):
        out_dict = {
            'cur_lat_spp': [],
            'cur_lng_spp': [],
            'cur_lat_dgnss': [],
            'cur_lng_dgnss': [],
            'cur_lat_float': [],
            'cur_lng_float': [],
            'cur_lat_fixed': [],
            'cur_lng_fixed': [],
            'cur_lat_sbas': [],
            'cur_lng_sbas': [],
            'cur_lat_dr': [],
            'cur_lng_dr': []
        }
        out_dict.update(current_dict)
        return out_dict

    def _synchronize_plot_data_by_mode(self,
                                       mode_string,
                                       update_current=False):
        # do all required plot_data updates for a single
        # new solution with mode defined by mode_string
        pending_update = {
            'lat_' + mode_string:
            [x for x in self.slns['lat_' + mode_string] if not np.isnan(x)],
            'lng_' + mode_string:
            [y for y in self.slns['lng_' + mode_string] if not np.isnan(y)]
        }
        if update_current:
            current = {}
            if len(pending_update['lat_' + mode_string]) != 0:
                current = {
                    'cur_lat_' + mode_string:
                    [pending_update['lat_' + mode_string][-1]],
                    'cur_lng_' + mode_string:
                    [pending_update['lng_' + mode_string][-1]]
                }
            else:
                current = {
                    'cur_lat_' + mode_string: [],
                    'cur_lng_' + mode_string: []
                }
            pending_update.update(self._get_update_current(current))
        self.plot_data.update_data(pending_update)

    def _append_empty_sln_data(self, exclude_mode=None):
        for each_mode in mode_string_dict.values():
            if exclude_mode is None or each_mode != exclude_mode:
                self.slns['lat_' + each_mode].append(np.nan)
                self.slns['lng_' + each_mode].append(np.nan)

    def _update_sln_data_by_mode(self, soln, mode_string):
        # do backend deque updates for a new solution of type
        # mode string
        self.scaling_lock.acquire()
        lat = (soln.lat - self.offset[0]) * self.sf[0]
        lng = (soln.lon - self.offset[1]) * self.sf[1]
        self.scaling_lock.release()
        self.slns['lat_' + mode_string].append(lat)
        self.slns['lng_' + mode_string].append(lng)
        # Rotate old data out by appending to deque
        self._append_empty_sln_data(exclude_mode=mode_string)

    def _clr_sln_data(self):
        for each in self.slns:
            self.slns[each].clear()

    def _clear_history(self):
        for each in self.slns:
            self.slns[each].clear()
        pending_update = {
            'lat_spp': [],
            'lng_spp': [],
            'alt_spp': [],
            'lat_dgnss': [],
            'lng_dgnss': [],
            'alt_dgnss': [],
            'lat_float': [],
            'lng_float': [],
            'alt_float': [],
            'lat_fixed': [],
            'lng_fixed': [],
            'alt_fixed': [],
            'lat_sbas': [],
            'lng_sbas': [],
            'alt_sbas': [],
            'lat_dr': [],
            'lng_dr': [],
            'alt_dr': []
        }
        pending_update.update(self._get_update_current())
        self.plot_data.update(pending_update)

    def _clear_button_fired(self):
        self._clear_history()

    def age_corrections_callback(self, sbp_msg, **metadata):
        age_msg = MsgAgeCorrections(sbp_msg)
        if age_msg.age != 0xFFFF:
            self.age_corrections = age_msg.age / 10.0
        else:
            self.age_corrections = None

    def update_table(self):
        self.table = self.pos_table + self.vel_table + self.dops_table

    def auto_survey(self):
        if len(self.lats) != 0:
            self.latitude = sum(self.lats) / len(self.lats)
            self.altitude = sum(self.alts) / len(self.alts)
            self.longitude = sum(self.lngs) / len(self.lngs)

    def pos_llh_callback(self, sbp_msg, **metadata):
        if sbp_msg.msg_type == SBP_MSG_POS_LLH_DEP_A:
            soln = MsgPosLLHDepA(sbp_msg)
        else:
            soln = MsgPosLLH(sbp_msg)

        self.last_pos_mode = get_mode(soln)
        if self.last_pos_mode != 0:
            self.last_soln = soln
            mode_string = mode_string_dict[self.last_pos_mode]
            if mode_string not in self.pending_draw_modes:
                # this list allows us to tell GUI thread which solutions to update
                # (if we decide not to update at full data rate)
                # we use short strings to identify each solution mode
                self.pending_draw_modes.append(mode_string)
            self.list_lock.acquire()
            self._update_sln_data_by_mode(soln, mode_string)
            self.list_lock.release()
        else:
            self.list_lock.acquire()
            self._append_empty_sln_data()
            self.list_lock.release()
        self.ins_used = ((soln.flags & 0x8) >> 3) == 1
        pos_table = []
        soln.h_accuracy *= 1e-3
        soln.v_accuracy *= 1e-3

        tow = soln.tow * 1e-3
        if self.nsec is not None:
            tow += self.nsec * 1e-9

        # Return the best estimate of my local and receiver time in convenient
        # format that allows changing precision of the seconds
        ((tloc, secloc), (tgps, secgps)) = log_time_strings(self.week, tow)
        if self.utc_time:
            ((tutc, secutc)) = datetime_2_str(self.utc_time)

        if (self.directory_name_p == ''):
            filepath_p = time.strftime("position_log_%Y%m%d-%H%M%S.csv")
        else:
            filepath_p = os.path.join(
                self.directory_name_p,
                time.strftime("position_log_%Y%m%d-%H%M%S.csv"))

        if not self.logging_p:
            self.log_file = None

        if self.logging_p:
            if self.log_file is None:
                self.log_file = sopen(filepath_p, 'w')
                self.log_file.write(
                    "pc_time,gps_time,tow(sec),latitude(degrees),longitude(degrees),altitude(meters),"
                    "h_accuracy(meters),v_accuracy(meters),n_sats,flags\n")
            log_str_gps = ""
            if tgps != "" and secgps != 0:
                log_str_gps = "{0}:{1:06.6f}".format(tgps, float(secgps))
            self.log_file.write(
                '%s,%s,%.3f,%.10f,%.10f,%.4f,%.4f,%.4f,%d,%d\n' %
                ("{0}:{1:06.6f}".format(tloc, float(secloc)), log_str_gps, tow,
                 soln.lat, soln.lon, soln.height, soln.h_accuracy,
                 soln.v_accuracy, soln.n_sats, soln.flags))
            self.log_file.flush()

        if self.last_pos_mode == 0:
            pos_table.append(('GPS Week', EMPTY_STR))
            pos_table.append(('GPS TOW', EMPTY_STR))
            pos_table.append(('GPS Time', EMPTY_STR))
            pos_table.append(('Num. Signals', EMPTY_STR))
            pos_table.append(('Lat', EMPTY_STR))
            pos_table.append(('Lng', EMPTY_STR))
            pos_table.append(('Height', EMPTY_STR))
            pos_table.append(('Horiz Acc', EMPTY_STR))
            pos_table.append(('Vert Acc', EMPTY_STR))
        else:
            self.last_stime_update = monotonic()

            if self.week is not None:
                pos_table.append(('GPS Week', str(self.week)))
            pos_table.append(('GPS TOW', "{:.3f}".format(tow)))

            if self.week is not None:
                pos_table.append(
                    ('GPS Time', "{0}:{1:06.3f}".format(tgps, float(secgps))))
            if self.utc_time is not None:
                pos_table.append(
                    ('UTC Time', "{0}:{1:06.3f}".format(tutc, float(secutc))))
                pos_table.append(('UTC Src', self.utc_source))
            if self.utc_time is None:
                pos_table.append(('UTC Time', EMPTY_STR))
                pos_table.append(('UTC Src', EMPTY_STR))

            pos_table.append(('Sats Used', soln.n_sats))
            pos_table.append(('Lat', "{:.12g}".format(soln.lat)))
            pos_table.append(('Lng', "{:.12g}".format(soln.lon)))
            pos_table.append(('Height', "{0:.3f}".format(soln.height)))
            pos_table.append(('Horiz Acc', "{:.12g}".format(soln.h_accuracy)))
            pos_table.append(('Vert Acc', "{:.12g}".format(soln.v_accuracy)))

        pos_table.append(('Pos Flags', '0x%03x' % soln.flags))
        pos_table.append(('INS Used', '{}'.format(self.ins_used)))
        pos_table.append(('Pos Fix Mode', mode_dict[self.last_pos_mode]))
        if self.age_corrections is not None:
            pos_table.append(('Corr. Age [s]', self.age_corrections))

        # only store valid solutions for auto survey and degrees to meter transformation
        if self.last_pos_mode != 0:
            self.lats.append(soln.lat)
            self.lngs.append(soln.lon)
            self.alts.append(soln.height)
            self.tows.append(soln.tow)
            self.modes.append(self.last_pos_mode)
        self.auto_survey()

        # set-up table variables
        self.pos_table = pos_table
        self.update_table()
        # setup_plot variables
        # Updating array plot data is not thread safe, so we have to fire an event
        # and have the GUI thread do it
        if monotonic() - self.last_plot_update_time > GUI_UPDATE_PERIOD:
            self.update_scheduler.schedule_update('_solution_draw',
                                                  self._solution_draw)

    def _display_units_changed(self):
        # we store current extents of plot and current scalefactlrs
        self.scaling_lock.acquire()
        self.recenter = True  # recenter flag tells _solution_draw to update view extents
        self.prev_extents = (self.plot.index_range.low_setting,
                             self.plot.index_range.high_setting,
                             self.plot.value_range.low_setting,
                             self.plot.value_range.high_setting)
        self.prev_offsets = (self.offset[0], self.offset[1])
        self.prev_sfs = (self.sf[0], self.sf[1])
        if self.display_units == "meters":
            self.offset = (
                np.mean(
                    np.array(self.lats)[~(np.equal(np.array(self.modes), 0))]),
                np.mean(
                    np.array(self.lngs)[~(np.equal(np.array(self.modes), 0))]),
                np.mean(
                    np.array(self.alts)[~(np.equal(np.array(self.modes), 0))]))
            (self.meters_per_lat, self.meters_per_lon) = meters_per_deg(
                np.mean(
                    np.array(self.lats)[~(np.equal(np.array(self.modes), 0))]))
            self.sf = (self.meters_per_lat, self.meters_per_lon)
            self.plot.value_axis.title = 'Latitude (meters)'
            self.plot.index_axis.title = 'Longitude (meters)'
        else:
            self.offset = (0, 0, 0)
            self.sf = (1, 1)
            self.plot.value_axis.title = 'Latitude (degrees)'
            self.plot.index_axis.title = 'Longitude (degrees)'
        self.scaling_lock.release()
        self.list_lock.acquire()
        # now we update the existing sln deques to go from meters back to degrees or vice versa
        for each_array in self.slns:
            index = 0 if 'lat' in str(each_array) else 1
            # going from degrees to meters; do scaling with new offset and sf
            if self.display_units == "meters":
                self.slns[each_array] = deque(
                    (np.array(self.slns[each_array]) - self.offset[index]) *
                    self.sf[index],
                    maxlen=PLOT_HISTORY_MAX)
            # going from degrees to meters; do inverse scaling with former offset and sf
            if self.display_units == "degrees":
                self.slns[each_array] = deque(
                    np.array(self.slns[each_array]) / self.prev_sfs[index] +
                    self.prev_offsets[index],
                    maxlen=PLOT_HISTORY_MAX)
        self.pending_draw_modes = list(mode_string_dict.values())
        self.list_lock.release()

    def rescale_for_units_change(self):
        # Chaco scales view automatically when 'auto' is stored
        if self.prev_extents[0] != 'auto':
            # Otherwise use has used mousewheel zoom and we need to transform
            if self.display_units == 'meters':
                new_scaling = (
                    (self.prev_extents[0] - self.offset[1]) * self.sf[1],
                    (self.prev_extents[1] - self.offset[1]) * self.sf[1],
                    (self.prev_extents[2] - self.offset[0]) * self.sf[0],
                    (self.prev_extents[3] - self.offset[0]) * self.sf[0])
            else:
                new_scaling = (self.prev_extents[0] / self.prev_sfs[1] +
                               self.prev_offsets[1],
                               self.prev_extents[1] / self.prev_sfs[1] +
                               self.prev_offsets[1],
                               self.prev_extents[2] / self.prev_sfs[0] +
                               self.prev_offsets[0],
                               self.prev_extents[3] / self.prev_sfs[0] +
                               self.prev_offsets[0])

            # set plot scaling accordingly
            self.plot.index_range.low_setting = new_scaling[0]
            self.plot.index_range.high_setting = new_scaling[1]
            self.plot.value_range.low_setting = new_scaling[2]
            self.plot.value_range.high_setting = new_scaling[3]

    def _solution_draw(self):
        self.list_lock.acquire()
        current_time = monotonic()
        self.last_plot_update_time = current_time
        pending_draw_modes = self.pending_draw_modes
        current_mode = pending_draw_modes[-1] if len(
            pending_draw_modes) > 0 else None
        # Periodically, we make sure to redraw older data to expire old plot data
        if current_time - self.last_stale_update_time > STALE_DATA_PERIOD:
            # we don't update old solution modes every timestep to try and save CPU
            pending_draw_modes = list(mode_string_dict.values())
            self.last_stale_update_time = current_time
        for mode_string in pending_draw_modes:
            if self.running:
                update_current = mode_string == current_mode if current_mode else True
                self._synchronize_plot_data_by_mode(
                    mode_string, update_current=update_current)
                if mode_string in self.pending_draw_modes:
                    self.pending_draw_modes.remove(mode_string)

        self.list_lock.release()
        if not self.zoomall and self.position_centered and self.running:
            d = (self.plot.index_range.high - self.plot.index_range.low) / 2.
            self.plot.index_range.set_bounds(
                (self.last_soln.lon - self.offset[1]) * self.sf[1] - d,
                (self.last_soln.lon - self.offset[1]) * self.sf[1] + d)
            d = (self.plot.value_range.high - self.plot.value_range.low) / 2.
            self.plot.value_range.set_bounds(
                (self.last_soln.lat - self.offset[0]) * self.sf[0] - d,
                (self.last_soln.lat - self.offset[0]) * self.sf[0] + d)
        if self.zoomall:
            self.recenter = False
            plot_square_axes(self.plot, ('lng_spp', 'lng_dgnss', 'lng_float',
                                         'lng_fixed', 'lng_sbas', 'lng_dr'),
                             ('lat_spp', 'lat_dgnss', 'lat_float', 'lat_fixed',
                              'lat_sbas', 'lat_dr'))
        if self.recenter:
            try:
                self.rescale_for_units_change()
                self.recenter = False
            except AttributeError:
                pass

    def dops_callback(self, sbp_msg, **metadata):
        flags = 0
        if sbp_msg.msg_type == SBP_MSG_DOPS_DEP_A:
            dops = MsgDopsDepA(sbp_msg)
            flags = 1
        else:
            dops = MsgDops(sbp_msg)
            flags = dops.flags
        if flags != 0:
            self.dops_table = [('PDOP', '%.1f' % (dops.pdop * 0.01)),
                               ('GDOP', '%.1f' % (dops.gdop * 0.01)),
                               ('TDOP', '%.1f' % (dops.tdop * 0.01)),
                               ('HDOP', '%.1f' % (dops.hdop * 0.01)),
                               ('VDOP', '%.1f' % (dops.vdop * 0.01))]
        else:
            self.dops_table = [('PDOP', EMPTY_STR), ('GDOP', EMPTY_STR),
                               ('TDOP', EMPTY_STR), ('HDOP', EMPTY_STR),
                               ('VDOP', EMPTY_STR)]

        self.dops_table.append(('DOPS Flags', '0x%03x' % flags))

    def vel_ned_callback(self, sbp_msg, **metadata):
        flags = 0
        if sbp_msg.msg_type == SBP_MSG_VEL_NED_DEP_A:
            vel_ned = MsgVelNEDDepA(sbp_msg)
            flags = 1
        else:
            vel_ned = MsgVelNED(sbp_msg)
            flags = vel_ned.flags
        tow = vel_ned.tow * 1e-3
        if self.nsec is not None:
            tow += self.nsec * 1e-9

        ((tloc, secloc), (tgps, secgps)) = log_time_strings(self.week, tow)

        if self.directory_name_v == '':
            filepath_v = time.strftime("velocity_log_%Y%m%d-%H%M%S.csv")
        else:
            filepath_v = os.path.join(
                self.directory_name_v,
                time.strftime("velocity_log_%Y%m%d-%H%M%S.csv"))

        if not self.logging_v:
            self.vel_log_file = None

        if self.logging_v:
            if self.vel_log_file is None:
                self.vel_log_file = sopen(filepath_v, 'w')
                self.vel_log_file.write(
                    'pc_time,gps_time,tow(sec),north(m/s),east(m/s),down(m/s),speed(m/s),flags,num_signals\n'
                )
            log_str_gps = ''
            if tgps != "" and secgps != 0:
                log_str_gps = "{0}:{1:06.6f}".format(tgps, float(secgps))
            self.vel_log_file.write(
                '%s,%s,%.3f,%.6f,%.6f,%.6f,%.6f,%d,%d\n' %
                ("{0}:{1:06.6f}".format(tloc, float(secloc)), log_str_gps, tow,
                 vel_ned.n * 1e-3, vel_ned.e * 1e-3, vel_ned.d * 1e-3,
                 math.sqrt(vel_ned.n * vel_ned.n + vel_ned.e * vel_ned.e) *
                 1e-3, flags, vel_ned.n_sats))
            self.vel_log_file.flush()
        if (flags & 0x7) != 0:
            self.vel_table = [
                ('Vel. N', '% 8.4f' % (vel_ned.n * 1e-3)),
                ('Vel. E', '% 8.4f' % (vel_ned.e * 1e-3)),
                ('Vel. D', '% 8.4f' % (vel_ned.d * 1e-3)),
            ]
        else:
            self.vel_table = [
                ('Vel. N', EMPTY_STR),
                ('Vel. E', EMPTY_STR),
                ('Vel. D', EMPTY_STR),
            ]
        self.vel_table.append(('Vel Flags', '0x%03x' % flags))
        self.update_table()

    def gps_time_callback(self, sbp_msg, **metadata):
        if sbp_msg.msg_type == SBP_MSG_GPS_TIME_DEP_A:
            time_msg = MsgGPSTimeDepA(sbp_msg)
            flags = 1
        elif sbp_msg.msg_type == SBP_MSG_GPS_TIME:
            time_msg = MsgGPSTime(sbp_msg)
            flags = time_msg.flags
            if flags != 0:
                self.week = time_msg.wn
                self.nsec = time_msg.ns_residual

    def utc_time_callback(self, sbp_msg, **metadata):
        tmsg = MsgUtcTime(sbp_msg)
        microseconds = int(tmsg.ns / 1000.00)
        if tmsg.flags & 0x7 != 0:
            dt = datetime.datetime(tmsg.year, tmsg.month, tmsg.day, tmsg.hours,
                                   tmsg.minutes, tmsg.seconds, microseconds)
            self.utc_time = dt
            self.utc_time_flags = tmsg.flags
            if (tmsg.flags >> 3) & 0x3 == 0:
                self.utc_source = "Factory Default"
            elif (tmsg.flags >> 3) & 0x3 == 1:
                self.utc_source = "Non Volatile Memory"
            elif (tmsg.flags >> 3) & 0x3 == 2:
                self.utc_source = "Decoded this Session"
            else:
                self.utc_source = "Unknown"
        else:
            self.utc_time = None
            self.utc_source = None

    def __init__(self, link, dirname=''):
        super(SolutionView, self).__init__()
        self.pending_draw_modes = []
        self.recenter = False
        self.offset = (0, 0, 0)
        self.sf = (1, 1)
        self.list_lock = threading.Lock()
        self.scaling_lock = threading.Lock()
        self.slns = {
            'lat_spp': deque(maxlen=PLOT_HISTORY_MAX),
            'lng_spp': deque(maxlen=PLOT_HISTORY_MAX),
            'alt_spp': deque(maxlen=PLOT_HISTORY_MAX),
            'lat_dgnss': deque(maxlen=PLOT_HISTORY_MAX),
            'lng_dgnss': deque(maxlen=PLOT_HISTORY_MAX),
            'alt_dgnss': deque(maxlen=PLOT_HISTORY_MAX),
            'lat_float': deque(maxlen=PLOT_HISTORY_MAX),
            'lng_float': deque(maxlen=PLOT_HISTORY_MAX),
            'alt_float': deque(maxlen=PLOT_HISTORY_MAX),
            'lat_fixed': deque(maxlen=PLOT_HISTORY_MAX),
            'lng_fixed': deque(maxlen=PLOT_HISTORY_MAX),
            'alt_fixed': deque(maxlen=PLOT_HISTORY_MAX),
            'lat_sbas': deque(maxlen=PLOT_HISTORY_MAX),
            'lng_sbas': deque(maxlen=PLOT_HISTORY_MAX),
            'alt_sbas': deque(maxlen=PLOT_HISTORY_MAX),
            'lat_dr': deque(maxlen=PLOT_HISTORY_MAX),
            'lng_dr': deque(maxlen=PLOT_HISTORY_MAX),
            'alt_dr': deque(maxlen=PLOT_HISTORY_MAX)
        }
        self.lats = deque(maxlen=PLOT_HISTORY_MAX)
        self.lngs = deque(maxlen=PLOT_HISTORY_MAX)
        self.alts = deque(maxlen=PLOT_HISTORY_MAX)
        self.tows = deque(maxlen=PLOT_HISTORY_MAX)
        self.modes = deque(maxlen=PLOT_HISTORY_MAX)
        self.log_file = None
        self.directory_name_v = dirname
        self.directory_name_p = dirname
        self.vel_log_file = None
        self.last_stime_update = 0
        self.last_soln = None

        self.altitude = 0
        self.longitude = 0
        self.latitude = 0
        self.last_pos_mode = 0
        self.ins_used = False
        self.last_plot_update_time = 0
        self.last_stale_update_time = 0

        self.plot_data = ArrayPlotData(lat_spp=[],
                                       lng_spp=[],
                                       alt_spp=[],
                                       cur_lat_spp=[],
                                       cur_lng_spp=[],
                                       lat_dgnss=[],
                                       lng_dgnss=[],
                                       alt_dgnss=[],
                                       cur_lat_dgnss=[],
                                       cur_lng_dgnss=[],
                                       lat_float=[],
                                       lng_float=[],
                                       alt_float=[],
                                       cur_lat_float=[],
                                       cur_lng_float=[],
                                       lat_fixed=[],
                                       lng_fixed=[],
                                       alt_fixed=[],
                                       cur_lat_fixed=[],
                                       cur_lng_fixed=[],
                                       lat_sbas=[],
                                       lng_sbas=[],
                                       cur_lat_sbas=[],
                                       cur_lng_sbas=[],
                                       lng_dr=[],
                                       lat_dr=[],
                                       cur_lat_dr=[],
                                       cur_lng_dr=[])
        self.plot = Plot(self.plot_data)

        # 1000 point buffer
        self.plot.plot(('lng_spp', 'lat_spp'),
                       type='line',
                       line_width=0.1,
                       name='',
                       color=color_dict[SPP_MODE])
        self.plot.plot(('lng_spp', 'lat_spp'),
                       type='scatter',
                       name='',
                       color=color_dict[SPP_MODE],
                       marker='dot',
                       line_width=0.0,
                       marker_size=1.0)
        self.plot.plot(('lng_dgnss', 'lat_dgnss'),
                       type='line',
                       line_width=0.1,
                       name='',
                       color=color_dict[DGNSS_MODE])
        self.plot.plot(('lng_dgnss', 'lat_dgnss'),
                       type='scatter',
                       name='',
                       color=color_dict[DGNSS_MODE],
                       marker='dot',
                       line_width=0.0,
                       marker_size=1.0)
        self.plot.plot(('lng_float', 'lat_float'),
                       type='line',
                       line_width=0.1,
                       name='',
                       color=color_dict[FLOAT_MODE])
        self.plot.plot(('lng_float', 'lat_float'),
                       type='scatter',
                       name='',
                       color=color_dict[FLOAT_MODE],
                       marker='dot',
                       line_width=0.0,
                       marker_size=1.0)
        self.plot.plot(('lng_fixed', 'lat_fixed'),
                       type='line',
                       line_width=0.1,
                       name='',
                       color=color_dict[FIXED_MODE])
        self.plot.plot(('lng_fixed', 'lat_fixed'),
                       type='scatter',
                       name='',
                       color=color_dict[FIXED_MODE],
                       marker='dot',
                       line_width=0.0,
                       marker_size=1.0)
        self.plot.plot(('lng_sbas', 'lat_sbas'),
                       type='line',
                       line_width=0.1,
                       name='',
                       color=color_dict[SBAS_MODE])
        self.plot.plot(('lng_sbas', 'lat_sbas'),
                       type='scatter',
                       name='',
                       color=color_dict[SBAS_MODE],
                       marker='dot',
                       line_width=0.0,
                       marker_size=1.0)
        self.plot.plot(('lng_dr', 'lat_dr'),
                       type='line',
                       line_width=0.1,
                       name='',
                       color=color_dict[DR_MODE])
        self.plot.plot(('lng_dr', 'lat_dr'),
                       type='scatter',
                       color=color_dict[DR_MODE],
                       marker='dot',
                       line_width=0.0,
                       marker_size=1.0)
        # current values
        spp = self.plot.plot(('cur_lng_spp', 'cur_lat_spp'),
                             type='scatter',
                             name=mode_dict[SPP_MODE],
                             color=color_dict[SPP_MODE],
                             marker='plus',
                             line_width=1.5,
                             marker_size=5.0)
        dgnss = self.plot.plot(('cur_lng_dgnss', 'cur_lat_dgnss'),
                               type='scatter',
                               name=mode_dict[DGNSS_MODE],
                               color=color_dict[DGNSS_MODE],
                               marker='plus',
                               line_width=1.5,
                               marker_size=5.0)
        rtkfloat = self.plot.plot(('cur_lng_float', 'cur_lat_float'),
                                  type='scatter',
                                  name=mode_dict[FLOAT_MODE],
                                  color=color_dict[FLOAT_MODE],
                                  marker='plus',
                                  line_width=1.5,
                                  marker_size=5.0)
        rtkfix = self.plot.plot(('cur_lng_fixed', 'cur_lat_fixed'),
                                type='scatter',
                                name=mode_dict[FIXED_MODE],
                                color=color_dict[FIXED_MODE],
                                marker='plus',
                                line_width=1.5,
                                marker_size=5.0)
        sbas = self.plot.plot(('cur_lng_sbas', 'cur_lat_sbas'),
                              type='scatter',
                              name=mode_dict[SBAS_MODE],
                              color=color_dict[SBAS_MODE],
                              marker='plus',
                              line_width=1.5,
                              marker_size=5.0)
        dr = self.plot.plot(('cur_lng_dr', 'cur_lat_dr'),
                            type='scatter',
                            name=mode_dict[DR_MODE],
                            color=color_dict[DR_MODE],
                            marker='plus',
                            line_width=1.5,
                            marker_size=5.0)
        plot_labels = ['SPP', 'SBAS', 'DGPS', 'RTK float', 'RTK fixed', 'DR']
        plots_legend = dict(
            zip(plot_labels, [spp, sbas, dgnss, rtkfloat, rtkfix, dr]))
        self.plot.legend.plots = plots_legend
        self.plot.legend.labels = plot_labels  # sets order
        self.plot.legend.visible = True

        self.plot.index_axis.tick_label_position = 'inside'
        self.plot.index_axis.tick_label_color = 'gray'
        self.plot.index_axis.tick_color = 'gray'
        self.plot.index_axis.title = 'Longitude (degrees)'
        self.plot.index_axis.title_spacing = 5
        self.plot.value_axis.tick_label_position = 'inside'
        self.plot.value_axis.tick_label_color = 'gray'
        self.plot.value_axis.tick_color = 'gray'
        self.plot.value_axis.title = 'Latitude (degrees)'
        self.plot.value_axis.title_spacing = 5
        self.plot.padding = (25, 25, 25, 25)

        self.plot.tools.append(PanTool(self.plot))
        zt = ZoomTool(self.plot,
                      zoom_factor=1.1,
                      tool_mode="box",
                      always_on=False)
        self.plot.overlays.append(zt)

        self.link = link
        self.link.add_callback(self.pos_llh_callback,
                               [SBP_MSG_POS_LLH_DEP_A, SBP_MSG_POS_LLH])
        self.link.add_callback(self.vel_ned_callback,
                               [SBP_MSG_VEL_NED_DEP_A, SBP_MSG_VEL_NED])
        self.link.add_callback(self.dops_callback,
                               [SBP_MSG_DOPS_DEP_A, SBP_MSG_DOPS])
        self.link.add_callback(self.gps_time_callback,
                               [SBP_MSG_GPS_TIME_DEP_A, SBP_MSG_GPS_TIME])
        self.link.add_callback(self.utc_time_callback, [SBP_MSG_UTC_TIME])
        self.link.add_callback(self.age_corrections_callback,
                               SBP_MSG_AGE_CORRECTIONS)
        self.week = None
        self.utc_time = None
        self.age_corrections = None
        self.nsec = 0
        self.meters_per_lat = None
        self.meters_per_lon = None
        self.python_console_cmds = {'solution': self}
        self.update_scheduler = UpdateScheduler()
Пример #6
0
class FitOptions(AuxPlotFigureOptions):
    global_fit = Str('Fit')
    global_error_type = Str('Error')
    nsigma = Int(1)

    def set_names(self, names):
        for ai in self.aux_plots:
            if ai.name not in names:
                ai.plot_enabled = False
                ai.save_enabled = False
                ai.name = ''
            ai.names = names

    def set_detectors(self, dets):
        for p in self.aux_plots:
            p.detectors = dets

    # def traits_view(self):
    #     bg_grp = self._get_bg_group()
    #     pd_grp = self._get_padding_group()
    #     a_grp = self._get_axes_group()
    #     appear_grp = VGroup(bg_grp, pd_grp, a_grp, label='Appearance')
    #
    #     p_grp = self._get_aux_plots_group()
    #
    #     hgrp = self._misc_grp()
    #     v = View(VGroup(hgrp, Tabbed(p_grp, appear_grp)))
    #     return v
    #
    # def _get_columns(self):
    #     return [object_column(name='name'),
    #             checkbox_column(name='plot_enabled', label='Enabled'),
    #             checkbox_column(name='save_enabled', label='Save'),
    #             object_column(name='fit',
    #                           editor=EnumEditor(name='fit_types'),
    #                           width=75),
    #             object_column(name='error_type',
    #                           editor=EnumEditor(name='error_types'),
    #                           width=75, label='Error'),
    #             # checkbox_column(name='filter_outliers', label='Out.'),
    #             # object_column(name='filter_outlier_iterations', label='Iter.'),
    #             # object_column(name='filter_outlier_std_devs', label='SD'),
    #             # object_column(name='truncate', label='Trunc.'),
    #             # checkbox_column(name='include_baseline_error', label='Inc. BsErr')
    #             ]
    #
    # def _get_name_fit_group(self):
    #     h = HGroup(Item('name', editor=EnumEditor(name='names')),
    #                Item('fit', editor=EnumEditor(name='fit_types')),
    #                UItem('error_type', editor=EnumEditor(name='error_types'))),
    #     return h
    #
    # def _get_edit_view(self):
    #     return View(VGroup(self._get_name_fit_group(),
    #                        Item('marker', editor=EnumEditor(values=marker_names)),
    #                        Item('marker_size'),
    #                        HGroup(Item('ymin', label='Min'),
    #                               Item('ymax', label='Max'),
    #                               show_border=True,
    #                               label='Y Limits'),
    #                        show_border=True))

    # def _get_aux_plots_item(self):
    #     aux_plots_item = UItem('aux_plots',
    #                            style='custom',
    #                            show_label=False,
    #                            editor=myTableEditor(columns=self._get_columns(),
    #                                                 sortable=False,
    #                                                 deletable=False,
    #                                                 clear_selection_on_dclicked=True,
    #                                                 edit_on_first_click=False,
    #                                                 selection_mode='rows',
    #                                                 selected='selected_aux_plots',
    #                                                 # on_select=lambda *args: setattr(self, 'selected', True),
    #                                                 # selected='selected',
    #                                                 edit_view=self._get_edit_view(),
    #                                                 reorderable=False))
    #     return aux_plots_item
    #
    # def _get_aux_plots_group(self):
    #     ggrp = VGroup(HGroup(UItem('global_fit', editor=EnumEditor(name='fit_types')),
    #                          UItem('global_error_type', editor=EnumEditor(name='error_types'))))
    #     api = self._get_aux_plots_item()
    #     return Group(VGroup(ggrp, api), label='Fits')
    #
    # def _misc_grp(self):
    #     ogrp = HGroup(Item('use_plotting',
    #                        label='Use Plotting',
    #                        tooltip='(Checked) Plot the isotope evolutions '
    #                                '(Non-checked) Only calculate new fit results. Do not plot'))
    #     return ogrp

    # def _get_aux_plots_group(self):
    #     return Group(self._get_aux_plots_item(), label='Fits')

    def _get_aux_plots(self):
        fs = self.selected_aux_plots
        if not fs:
            fs = self.aux_plots
        return fs

    def _global_fit_changed(self):
        # if self.global_fit in self.fit_types:
        fs = self._get_aux_plots()
        for fi in fs:
            fi.fit = self.global_fit

    def _global_error_type_changed(self):
        if self.global_error_type in FIT_ERROR_TYPES:
            fs = self._get_aux_plots()
            for fi in fs:
                fi.error_type = self.global_error_type
Пример #7
0
class AUILayout(TaskLayout):
    """ The layout for a main window's dock area using AUI Perspectives
    """

    perspective = Str()
Пример #8
0
class Foo(HasTraits):

    name = Str()
Пример #9
0
class Application(HasStrictTraits):
    """ A base class for applications.

    This class handles the basic lifecycle of an application and a few
    fundamental facilities.  It is suitable as a base for any application,
    not just GUI applications.
    """

    # 'Application' traits ----------------------------------------------------

    # Branding ----------------------------------------------------------------

    #: Human-readable application name
    name = Str("Pyface Application")

    #: Human-readable company name
    company = Str()

    #: Human-readable description of the application
    description = Str()

    # Infrastructure ---------------------------------------------------------

    #: The application's globally unique identifier.
    id = Str()

    #: Application home directory (for preferences, logging, etc.)
    home = Directory()

    #: User data directory (for user files, projects, etc)
    user_data = Directory()

    # Application lifecycle --------------------------------------------------

    #: Fired when the application is starting. Called immediately before the
    #: start method is run.
    starting = Event(Instance(ApplicationEvent))

    #: Upon successful completion of the start method.
    started = Event(Instance(ApplicationEvent))

    #: Fired after the GUI event loop has been started during the run method.
    application_initialized = Event(Instance(ApplicationEvent))

    #: Fired when the application is starting. Called immediately before the
    #: stop method is run.
    exiting = VetoableEvent()

    #: Fired when the application is starting. Called immediately before the
    #: stop method is run.
    stopping = Event(Instance(ApplicationEvent))

    #: Upon successful completion of the stop method.
    stopped = Event(Instance(ApplicationEvent))

    # -------------------------------------------------------------------------
    # Application interface
    # -------------------------------------------------------------------------

    # Application lifecycle methods ------------------------------------------

    def start(self):
        """ Start the application, setting up things that are required

        Subclasses should call the superclass start() method before doing any
        work themselves.
        """
        return True

    def stop(self):
        """ Stop the application, cleanly releasing resources if possible.

        Subclasses should call the superclass stop() method after doing any
        work themselves.
        """
        return True

    def run(self):
        """ Run the application.

        Return
        ------
        status : bool
            Whether or not the application ran normally
        """
        run = stopped = False

        # Start up the application.
        logger.info("---- Application starting ----")
        self._fire_application_event("starting")
        started = self.start()
        if started:

            logger.info("---- Application started ----")
            self._fire_application_event("started")

            try:
                run = self._run()
            except ApplicationExit as exc:
                if exc.args == ():
                    logger.info("---- ApplicationExit raised ----")
                else:
                    logger.exception("---- ApplicationExit raised ----")
                run = exc.args == ()
            finally:
                # Try to shut the application down.
                logger.info("---- Application stopping ----")
                self._fire_application_event("stopping")
                stopped = self.stop()
                if stopped:
                    self._fire_application_event("stopped")
                    logger.info("---- Application stopped ----")

        return started and run and stopped

    def exit(self, force=False):
        """ Exits the application.

        This method handles a request to shut down the application by the user,
        eg. from a menu.  If force is False, the application can potentially
        veto the close event, leaving the application in the state that it was
        before the exit method was called.

        Parameters
        ----------
        force : bool, optional (default False)
            If set, windows will receive no closing events and will be
            destroyed unconditionally. This can be useful for reliably tearing
            down regression tests, but should be used with caution.

        Raises
        ------
        ApplicationExit
            Some subclasses may trigger the exit by raising ApplicationExit.
        """
        logger.info("---- Application exit started ----")
        if force or self._can_exit():
            try:
                self._prepare_exit()
            except Exception:
                logger.exception("Error preparing for application exit")
            finally:
                logger.info("---- Application exit ----")
                self._exit()
        else:
            logger.info("---- Application exit vetoed ----")

    # Initialization utilities -----------------------------------------------

    def initialize_application_home(self):
        """ Set up the home directory for the application

        This is where logs, preference files and other config files will be
        stored.
        """
        if not os.path.exists(self.home):
            logger.info("Application home directory does not exist, creating")
            os.makedirs(self.home)

    # -------------------------------------------------------------------------
    # Private interface
    # -------------------------------------------------------------------------

    # Main method -------------------------------------------------------------

    def _run(self):
        """ Actual implementation of running the application

        This should be completely overriden by applications which want to
        actually do something.  Usually this method starts an event loop and
        blocks, but for command-line applications this could be where the
        main application logic is called from.
        """
        # Fire a notification that the app is running.  If the app has an
        # event loop (eg. a GUI, Tornado web app, etc.) then this should be
        # fired _after_ the event loop starts using an appropriate callback
        # (eg. gui.set_trait_later).
        self._fire_application_event("application_initialized")
        return True

    # Utilities ---------------------------------------------------------------

    def _fire_application_event(self, event_type):
        event = ApplicationEvent(application=self, event_type=event_type)
        setattr(self, event_type, event)

    # Destruction methods -----------------------------------------------------

    def _can_exit(self):
        """ Is exit vetoed by anything?

        The default behaviour is to fire the :py:attr:`exiting` event and check
        to see if any listeners veto.  Subclasses may wish to override to
        perform additional checks.

        Returns
        -------
        can_exit : bool
            Return True if exit is OK, False if something vetoes the exit.
        """
        self.exiting = event = Vetoable()
        return not event.veto

    def _prepare_exit(self):
        """ Do any application-level state saving and clean-up

        Subclasses should override this method.
        """
        pass

    def _exit(self):
        """ Shut down the application

        This is where application event loops and similar should be shut down.
        """
        # invoke a normal exit from the application
        raise ApplicationExit()

    # Traits defaults ---------------------------------------------------------

    def _id_default(self):
        """ Use the application's directory as the id """
        from traits.etsconfig.etsconfig import ETSConfig

        return ETSConfig._get_application_dirname()

    def _home_default(self):
        """ Default home comes from ETSConfig. """
        from traits.etsconfig.etsconfig import ETSConfig

        return os.path.join(ETSConfig.application_data, self.id)

    def _user_data_default(self):
        """ Default user_data comes from ETSConfig. """
        from traits.etsconfig.etsconfig import ETSConfig

        return ETSConfig.user_data

    def _company_default(self):
        """ Default company comes from ETSConfig. """
        from traits.etsconfig.etsconfig import ETSConfig

        return ETSConfig.company

    def _description_default(self):
        """ Default description is the docstring of the application class. """
        from inspect import getdoc

        text = getdoc(self)
        return text
Пример #10
0
class _ListStrEditor(Editor):
    """ Traits UI editor for editing lists of strings.
    """

    # -- Trait Definitions ----------------------------------------------------

    #: The title of the editor:
    title = Str()

    #: The current set of selected items (which one is used depends upon the
    #: initial state of the editor factory 'multi_select' trait):
    selected = Any()
    multi_selected = List()

    #: The current set of selected item indices (which one is used depends upon
    #: the initial state of the editor factory 'multi_select' trait):
    selected_index = Int()
    multi_selected_indices = List(Int)

    #: The most recently actived item and its index:
    activated = Any()
    activated_index = Int()

    #: The most recently right_clicked item and its index:
    right_clicked = Event()
    right_clicked_index = Event()

    #: Is the list editor scrollable? This value overrides the default.
    scrollable = True

    #: Index of item to select after rebuilding editor list:
    index = Any()

    #: Should the selected item be edited after rebuilding the editor list:
    edit = Bool(False)

    #: The adapter from list items to editor values:
    adapter = Instance(ListStrAdapter)

    #: Dictionaly mapping image names to wx.ImageList indices:
    images = Any({})

    #: Dictionary mapping ImageResource objects to wx.ImageList indices:
    image_resources = Any({})

    #: The current number of item currently in the list:
    item_count = Property()

    #: The current search string:
    search = Str()

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        factory = self.factory

        # Set up the adapter to use:
        self.adapter = factory.adapter
        self.sync_value(factory.adapter_name, "adapter", "from")

        # Determine the style to use for the list control:
        style = wx.LC_REPORT | wx.LC_VIRTUAL

        if factory.editable:
            style |= wx.LC_EDIT_LABELS

        if factory.horizontal_lines:
            style |= wx.LC_HRULES

        if not factory.multi_select:
            style |= wx.LC_SINGLE_SEL

        if (factory.title == "") and (factory.title_name == ""):
            style |= wx.LC_NO_HEADER

        # Create the list control and link it back to us:
        self.control = control = wxListCtrl(parent, -1, style=style)
        control._editor = self

        # Create the list control column:
        control.InsertColumn(0, "")

        # Set up the list control's event handlers:
        control.Bind(wx.EVT_LIST_BEGIN_DRAG, self._begin_drag)
        control.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self._begin_label_edit)
        control.Bind(wx.EVT_LIST_END_LABEL_EDIT, self._end_label_edit)
        control.Bind(wx.EVT_LIST_ITEM_SELECTED, self._item_selected)
        control.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._item_selected)
        control.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._right_clicked)
        control.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._item_activated)
        control.Bind(wx.EVT_SIZE, self._size_modified)

        # Handle key events:
        control.Bind(wx.EVT_CHAR, self._key_pressed)

        # Handle mouse events:
        if "edit" in factory.operations:
            control.Bind(wx.EVT_LEFT_DOWN, self._left_down)

        # Set up the drag and drop target:
        if PythonDropTarget is not None:
            control.SetDropTarget(PythonDropTarget(self))

        # Initialize the editor title:
        self.title = factory.title
        self.sync_value(factory.title_name, "title", "from")

        # Set up the selection listener (if necessary):
        if factory.multi_select:
            self.sync_value(
                factory.selected, "multi_selected", "both", is_list=True
            )
            self.sync_value(
                factory.selected_index,
                "multi_selected_indices",
                "both",
                is_list=True,
            )
        else:
            self.sync_value(factory.selected, "selected", "both")
            self.sync_value(factory.selected_index, "selected_index", "both")

        # Synchronize other interesting traits as necessary:
        self.sync_value(factory.activated, "activated", "to")
        self.sync_value(factory.activated_index, "activated_index", "to")

        self.sync_value(factory.right_clicked, "right_clicked", "to")
        self.sync_value(
            factory.right_clicked_index, "right_clicked_index", "to"
        )

        # Make sure we listen for 'items' changes as well as complete list
        # replacements:
        self.context_object.on_trait_change(
            self.update_editor, self.extended_name + "_items", dispatch="ui"
        )

        # Create the mapping from user supplied images to wx.ImageList indices:
        for image_resource in factory.images:
            self._add_image(image_resource)

        # Refresh the editor whenever the adapter changes:
        self.on_trait_change(self._refresh, "adapter.+update", dispatch="ui")

        # Set the list control's tooltip:
        self.set_tooltip()

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        disconnect_no_id(
            self.control,
            wx.EVT_SIZE,
            wx.EVT_CHAR,
            wx.EVT_LEFT_DOWN,
            wx.EVT_LIST_BEGIN_DRAG,
            wx.EVT_LIST_BEGIN_LABEL_EDIT,
            wx.EVT_LIST_END_LABEL_EDIT,
            wx.EVT_LIST_ITEM_SELECTED,
            wx.EVT_LIST_ITEM_DESELECTED,
            wx.EVT_LIST_ITEM_RIGHT_CLICK,
            wx.EVT_LIST_ITEM_ACTIVATED,
        )

        self.context_object.on_trait_change(
            self.update_editor, self.extended_name + "_items", remove=True
        )
        self.on_trait_change(self._refresh, "adapter.+update", remove=True)

        super(_ListStrEditor, self).dispose()

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        control = self.control
        top = control.GetTopItem()
        pn = control.GetCountPerPage()
        n = self.adapter.len(self.object, self.name)
        if self.factory.auto_add:
            n += 1

        control.DeleteAllItems()
        control.SetItemCount(n)
        if control.GetItemCount() > 0:
            control.RefreshItems(0, control.GetItemCount() - 1)
        control.SetColumnWidth(0, control.GetClientSize()[0])

        edit, self.edit = self.edit, False
        index, self.index = self.index, None

        if index is not None:
            if index >= n:
                index -= 1
                if index < 0:
                    index = None

        if index is None:
            visible = top + pn - 2
            if visible >= 0 and visible < control.GetItemCount():
                control.EnsureVisible(visible)
            if self.factory.multi_select:
                for index in self.multi_selected_indices:
                    if 0 <= index < n:
                        control.SetItemState(
                            index,
                            wx.LIST_STATE_SELECTED,
                            wx.LIST_STATE_SELECTED,
                        )
            else:
                if 0 <= self.selected_index < n:
                    control.SetItemState(
                        self.selected_index,
                        wx.LIST_STATE_SELECTED,
                        wx.LIST_STATE_SELECTED,
                    )
            return

        if 0 <= (index - top) < pn:
            control.EnsureVisible(min(top + pn - 2, control.GetItemCount() - 1))
        elif index < top:
            control.EnsureVisible(min(index + pn - 1, control.GetItemCount() - 1))
        else:
            control.EnsureVisible(index)

        control.SetItemState(
            index,
            wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED,
            wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED,
        )

        if edit:
            control.EditLabel(index)

    # -- Property Implementations ---------------------------------------------

    def _get_item_count(self):
        return self.control.GetItemCount() - self.factory.auto_add

    # -- Trait Event Handlers -------------------------------------------------

    def _title_changed(self, title):
        """ Handles the editor title being changed.
        """
        list_item = wx.ListItem()
        list_item.SetText(title)
        self.control.SetColumn(0, list_item)

    def _selected_changed(self, selected):
        """ Handles the editor's 'selected' trait being changed.
        """
        if not self._no_update:
            try:
                self.control.SetItemState(
                    self.value.index(selected),
                    wx.LIST_STATE_SELECTED,
                    wx.LIST_STATE_SELECTED,
                )
            except Exception:
                pass

    def _selected_index_changed(self, selected_index):
        """ Handles the editor's 'selected_index' trait being changed.
        """
        if not self._no_update:
            try:
                self.control.SetItemState(
                    selected_index,
                    wx.LIST_STATE_SELECTED,
                    wx.LIST_STATE_SELECTED,
                )
            except Exception:
                pass

    def _multi_selected_changed(self, selected):
        """ Handles the editor's 'multi_selected' trait being changed.
        """
        if not self._no_update:
            values = self.value
            try:
                self._multi_selected_indices_changed(
                    [values.index(item) for item in selected]
                )
            except Exception:
                pass

    def _multi_selected_items_changed(self, event):
        """ Handles the editor's 'multi_selected' trait being modified.
        """
        values = self.value
        try:
            self._multi_selected_indices_items_changed(
                TraitListEvent(
                    index=0,
                    removed=[values.index(item) for item in event.removed],
                    added=[values.index(item) for item in event.added],
                )
            )
        except Exception:
            pass

    def _multi_selected_indices_changed(self, selected_indices):
        """ Handles the editor's 'multi_selected_indices' trait being changed.
        """
        if not self._no_update:
            control = self.control
            selected = self._get_selected()

            # Select any new items that aren't already selected:
            for index in selected_indices:
                if index in selected:
                    selected.remove(index)
                else:
                    try:
                        control.SetItemState(
                            index,
                            wx.LIST_STATE_SELECTED,
                            wx.LIST_STATE_SELECTED,
                        )
                    except Exception:
                        pass

            # Unselect all remaining selected items that aren't selected now:
            for index in selected:
                control.SetItemState(index, 0, wx.LIST_STATE_SELECTED)

    def _multi_selected_indices_items_changed(self, event):
        """ Handles the editor's 'multi_selected_indices' trait being modified.
        """
        control = self.control

        # Remove all items that are no longer selected:
        for index in event.removed:
            control.SetItemState(index, 0, wx.LIST_STATE_SELECTED)

        # Select all newly added items:
        for index in event.added:
            control.SetItemState(
                index, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED
            )

    # -- List Control Event Handlers ------------------------------------------

    def _begin_drag(self, event):
        """ Handles the user beginning a drag operation with the left mouse
            button.
        """
        if PythonDropSource is not None:
            adapter = self.adapter
            object, name = self.object, self.name
            index = event.GetIndex()
            selected = self._get_selected()
            drag_items = []

            # Collect all of the selected items to drag:
            for index in selected:
                drag = adapter.get_drag(object, name, index)
                if drag is None:
                    return

                drag_items.append(drag)

            # Save the drag item indices, so that we can later handle a
            # completed 'move' operation:
            self._drag_indices = selected

            try:
                # If only one item is being dragged, drag it as an item, not a
                # list:
                if len(drag_items) == 1:
                    drag_items = drag_items[0]

                # Perform the drag and drop operation:
                ds = PythonDropSource(self.control, drag_items)

                # If moves are allowed and the result was a drag move:
                if (ds.result == wx.DragMove) and (
                    self._drag_local or self.factory.drag_move
                ):
                    # Then delete all of the original items (in reverse order
                    # from highest to lowest, so the indices don't need to be
                    # adjusted):
                    indices = self._drag_indices
                    indices.reverse()
                    for index in indices:
                        adapter.delete(object, name, index)
            finally:
                self._drag_indices = None
                self._drag_local = False

    def _begin_label_edit(self, event):
        """ Handles the user starting to edit an item label.
        """
        index = event.GetIndex()

        if (not self._is_auto_add(index)) and (
            not self.adapter.get_can_edit(self.object, self.name, index)
        ):
            event.Veto()

    def _end_label_edit(self, event):
        """ Handles the user finishing editing an item label.
        """
        self._set_text_current(event.GetIndex(), event.GetText())

    def _item_selected(self, event):
        """ Handles an item being selected.
        """
        self._no_update = True
        try:
            get_item = self.adapter.get_item
            object, name = self.object, self.name
            selected_indices = self._get_selected()
            if self.factory.multi_select:
                self.multi_selected_indices = selected_indices
                self.multi_selected = [
                    get_item(object, name, index) for index in selected_indices
                ]
            elif len(selected_indices) == 0:
                self.selected_index = -1
                self.selected = None
            else:
                self.selected_index = selected_indices[0]
                self.selected = get_item(object, name, selected_indices[0])
        finally:
            self._no_update = False

    def _item_activated(self, event):
        """ Handles an item being activated (double-clicked or enter pressed).
        """
        self.activated_index = event.GetIndex()
        if "edit" in self.factory.operations:
            self._edit_current()
        else:
            self.activated = self.adapter.get_item(
                self.object, self.name, self.activated_index
            )

    def _right_clicked(self, event):
        """ Handles an item being right clicked.
        """
        index = event.GetIndex()
        if index == -1:
            return
        self.right_clicked_index = index
        self.right_clicked = self.adapter.get_item(
            self.object, self.name, index
        )

    def _key_pressed(self, event):
        key = event.GetKeyCode()
        control = event.ControlDown()

        if 32 <= key <= 126:
            self.search += chr(key).lower()
            self._search_for_string()
        elif key in (wx.WXK_HOME, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN):
            self.search = ""
            event.Skip()
        elif key == wx.WXK_END:
            self.search = ""
            self._append_new()
        elif (key == wx.WXK_UP) and control:
            self._search_for_string(-1)
        elif (key == wx.WXK_DOWN) and control:
            self._search_for_string(1)
        elif key in (wx.WXK_BACK, wx.WXK_DELETE):
            self._delete_current()
        elif key == wx.WXK_INSERT:
            self._insert_current()
        elif key == wx.WXK_LEFT:
            self._move_up_current()
        elif key == wx.WXK_RIGHT:
            self._move_down_current()
        elif key == wx.WXK_RETURN:
            self._edit_current()
        elif key == 3:  # Ctrl-C
            self._copy_current()
        elif key == 22:  # Ctrl-V
            self._paste_current()
        elif key == 24:  # Ctrl-X
            self._cut_current()
        else:
            event.Skip()

    def _size_modified(self, event):
        """ Handles the size of the list control being changed.
        """
        dx, dy = self.control.GetClientSize()
        self.control.SetColumnWidth(0, dx - 1)
        event.Skip()

    def _left_down(self, event):
        """ Handles the user pressing the left mouse button.
        """
        index, flags = self.control.HitTest(
            wx.Point(event.GetX(), event.GetY())
        )
        selected = self._get_selected()
        if (len(selected) == 1) and (index == selected[0]):
            self._edit_current()
        else:
            event.Skip()

    # -- Drag and Drop Event Handlers -----------------------------------------

    def wx_dropped_on(self, x, y, data, drag_result):
        """ Handles a Python object being dropped on the list control.
        """
        index, flags = self.control.HitTest(wx.Point(x, y))

        # If the user dropped it on an empty list, set the target as past the
        # end of the list:
        if (
            (index == -1)
            and ((flags & wx.LIST_HITTEST_NOWHERE) != 0)
            and (self.control.GetItemCount() == 0)
        ):
            index = 0

        # If we have a valid drop target index, proceed:
        if index != -1:
            if not isinstance(data, list):
                # Handle the case of just a single item being dropped:
                self._wx_dropped_on(index, data)
            else:
                # Handles the case of a list of items being dropped, being
                # careful to preserve the original order of the source items if
                # possible:
                data.reverse()
                for item in data:
                    self._wx_dropped_on(index, item)

            # If this was an inter-list drag, mark it as 'local':
            if self._drag_indices is not None:
                self._drag_local = True

            # Return a successful drop result:
            return drag_result

        # Indicate we could not process the drop:
        return wx.DragNone

    def _wx_dropped_on(self, index, item):
        """ Helper method for handling a single item dropped on the list
            control.
        """
        adapter = self.adapter
        object, name = self.object, self.name

        # Obtain the destination of the dropped item relative to the target:
        destination = adapter.get_dropped(object, name, index, item)

        # Adjust the target index accordingly:
        if destination == "after":
            index += 1

        # Insert the dropped item at the requested position:
        adapter.insert(object, name, index, item)

        # If the source for the drag was also this list control, we need to
        # adjust the original source indices to account for their new position
        # after the drag operation:
        indices = self._drag_indices
        if indices is not None:
            for i in range(len(indices) - 1, -1, -1):
                if indices[i] < index:
                    break

                indices[i] += 1

    def wx_drag_over(self, x, y, data, drag_result):
        """ Handles a Python object being dragged over the tree.
        """
        if isinstance(data, list):
            rc = wx.DragNone
            for item in data:
                rc = self.wx_drag_over(x, y, item, drag_result)
                if rc == wx.DragNone:
                    break

            return rc

        index, flags = self.control.HitTest(wx.Point(x, y))

        # If the user is dragging over an empty list, set the target to the end
        # of the list:
        if (
            (index == -1)
            and ((flags & wx.LIST_HITTEST_NOWHERE) != 0)
            and (self.control.GetItemCount() == 0)
        ):
            index = 0

        # If the drag target index is valid and the adapter says it is OK to
        # drop the data here, then indicate the data can be dropped:
        if (index != -1) and self.adapter.get_can_drop(
            self.object, self.name, index, data
        ):
            return drag_result

        # Else indicate that we will not accept the data:
        return wx.DragNone

    # -- Private Methods ------------------------------------------------------

    def _refresh(self):
        """ Refreshes the contents of the editor's list control.
        """
        self.control.RefreshItems(0, len(self.value) - 1)

    def _add_image(self, image_resource):
        """ Adds a new image to the wx.ImageList and its associated mapping.
        """
        bitmap = image_resource.create_image().ConvertToBitmap()

        image_list = self._image_list
        if image_list is None:
            self._image_list = image_list = wx.ImageList(
                bitmap.GetWidth(), bitmap.GetHeight()
            )
            self.control.AssignImageList(image_list, wx.IMAGE_LIST_SMALL)

        self.image_resources[image_resource] = self.images[
            image_resource.name
        ] = index = image_list.Add(bitmap)

        return index

    def _get_image(self, image):
        """ Converts a user specified image to a wx.ListCtrl image index.
        """
        if isinstance(image, ImageResource):
            result = self.image_resources.get(image)
            if result is not None:
                return result

            return self._add_image(image)

        return self.images.get(image)

    def _get_selected(self):
        """ Returns a list of the indices of all currently selected list items.
        """
        selected = []
        item = -1
        control = self.control
        while True:
            item = control.GetNextItem(
                item, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED
            )
            if item == -1:
                break

            selected.append(item)

        return selected

    def _search_for_string(self, increment=0):
        """ Searches for the next occurrence of the current search string.
        """
        selected = self._get_selected()
        if len(selected) > 1:
            return

        start = 0
        if len(selected) == 1:
            start = selected[0] + increment

        get_text = self.adapter.get_text
        search = self.search
        object = self.object
        name = self.name

        if increment >= 0:
            items = range(start, self.item_count)
        else:
            items = range(start, -1, -1)

        for index in items:
            if search in get_text(object, name, index).lower():
                self.index = index
                self.update_editor()
                break

    def _append_new(self):
        """ Append a new item to the end of the list control.
        """
        if "append" in self.factory.operations:
            self.edit = True
            adapter = self.adapter
            index = self.control.GetItemCount()
            if self.factory.auto_add:
                self.index = index - 1
                self.update_editor()
            else:
                self.index = index
                adapter.insert(
                    self.object,
                    self.name,
                    self.index,
                    adapter.get_default_value(self.object, self.name),
                )

    def _copy_current(self):
        """ Copies the currently selected list control item to the clipboard.
        """
        selected = self._get_selected()
        if len(selected) == 1:
            index = selected[0]
            if index < self.item_count:
                try:
                    from pyface.wx.clipboard import clipboard

                    clipboard.data = self.adapter.get_text(
                        self.object, self.name, index
                    )
                except:
                    # Handle the traits.util package not being installed by
                    # just ignoring the request:
                    pass

    def _cut_current(self):
        """ Cuts the currently selected list control item and places its value
            in the clipboard.
        """
        ops = self.factory.operations
        if ("insert" in ops) and ("delete" in ops):
            selected = self._get_selected()
            if len(selected) == 1:
                index = selected[0]
                if index < self.item_count:
                    try:
                        from pyface.wx.clipboard import clipboard

                        clipboard.data = self.adapter.get_text(
                            self.object, self.name, index
                        )
                        self.index = index
                        self.adapter.delete(self.object, self.name, index)
                    except:
                        # Handle the traits.util package not being installed
                        # by just ignoring the request:
                        pass

    def _paste_current(self):
        """ Pastes the clipboard contents into the currently selected list
            control item.
        """
        if "insert" in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                try:
                    from pyface.wx.clipboard import clipboard

                    self._set_text_current(
                        selected[0], clipboard.text_data, insert=True
                    )
                except:
                    # Handle the traits.util package not being installed by
                    # just ignoring the request:
                    pass

    def _insert_current(self):
        """ Inserts a new item after the currently selected list control item.
        """
        if "insert" in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                self.index = selected[0]
                self.edit = True
                adapter = self.adapter
                adapter.insert(
                    self.object,
                    self.name,
                    selected[0],
                    adapter.get_default_value(self.object, self.name),
                )

    def _delete_current(self):
        """ Deletes the currently selected items from the list control.
        """
        if "delete" in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 0:
                return

            n = self.item_count
            delete = self.adapter.delete
            selected.reverse()
            self.index = selected[-1]
            for index in selected:
                if index < n:
                    delete(self.object, self.name, index)

    def _move_up_current(self):
        """ Moves the currently selected item up one line in the list control.
        """
        if "move" in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                index = selected[0]
                n = self.item_count
                if 0 < index < n:
                    adapter = self.adapter
                    object, name = self.object, self.name
                    item = adapter.get_item(object, name, index)
                    adapter.delete(object, name, index)
                    self.index = index - 1
                    adapter.insert(object, name, index - 1, item)

    def _move_down_current(self):
        """ Moves the currently selected item down one line in the list control.
        """
        if "move" in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                index = selected[0]
                n = self.item_count - 1
                if index < n:
                    adapter = self.adapter
                    object, name = self.object, self.name
                    item = adapter.get_item(object, name, index)
                    adapter.delete(object, name, index)
                    self.index = index + 1
                    adapter.insert(object, name, index + 1, item)

    def _edit_current(self):
        """ Allows the user to edit the current item in the list control.
        """
        if "edit" in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                self.control.EditLabel(selected[0])

    def _is_auto_add(self, index):
        """ Returns whether or not the index is the special 'auto add' item at
            the end of the list.
        """
        return self.factory.auto_add and (
            index >= self.adapter.len(self.object, self.name)
        )

    def _set_text_current(self, index, text, insert=False):
        """ Sets the text value of the specified list control item.
        """
        if text.strip() != "":
            object, name, adapter = self.object, self.name, self.adapter
            if insert or self._is_auto_add(index):
                adapter.insert(
                    object,
                    name,
                    index,
                    adapter.get_default_value(object, name),
                )
                self.edit = not insert

            self.index = index + 1
            adapter.set_text(object, name, index, text)
Пример #11
0
class SplashScreen(MSplashScreen, Window):
    """ The toolkit specific implementation of a SplashScreen.  See the
    ISplashScreen interface for the API documentation.
    """

    # 'ISplashScreen' interface --------------------------------------------

    image = Instance(ImageResource, ImageResource("splash"))

    log_level = Int(DEBUG)

    show_log_messages = Bool(True)

    text = Str()

    text_color = Any()

    text_font = Any()

    text_location = Tuple(5, 5)

    # ------------------------------------------------------------------------
    # Protected 'IWidget' interface.
    # ------------------------------------------------------------------------

    def _create_control(self, parent):
        # Get the splash screen image.
        image = self.image.create_image()

        splash_screen = wx.adv.SplashScreen(
            # The bitmap to display on the splash screen.
            image.ConvertToBitmap(),
            # Splash Style.
            wx.adv.SPLASH_NO_TIMEOUT | wx.adv.SPLASH_CENTRE_ON_SCREEN,
            # Timeout in milliseconds (we don't currently timeout!).
            0,
            # The parent of the splash screen.
            parent,
            # wx Id.
            -1,
            # Window style.
            style=wx.SIMPLE_BORDER | wx.FRAME_NO_TASKBAR,
        )

        # By default we create a font slightly bigger and slightly more italic
        # than the normal system font ;^)  The font is used inside the event
        # handler for 'EVT_PAINT'.
        self._wx_default_text_font = new_font_like(
            wx.NORMAL_FONT,
            point_size=wx.NORMAL_FONT.GetPointSize() + 1,
            style=wx.ITALIC,
        )

        # This allows us to write status text on the splash screen.
        splash_screen.Bind(wx.EVT_PAINT, self._on_paint)

        return splash_screen

    # ------------------------------------------------------------------------
    # Private interface.
    # ------------------------------------------------------------------------

    def _text_changed(self):
        """ Called when the splash screen text has been changed. """

        # Passing 'False' to 'Refresh' means "do not erase the background".
        if self.control is not None:
            self.control.Refresh(False)
            self.control.Update()
        wx.GetApp().Yield(True)

    def _on_paint(self, event):
        """ Called when the splash window is being repainted. """

        if self.control is not None:
            # Get the window that the splash image is drawn in.
            window = self.control  # .GetSplashWindow()

            dc = wx.PaintDC(window)

            if self.text_font is None:
                text_font = self._wx_default_text_font
            else:
                text_font = self.text_font

            dc.SetFont(text_font)

            if self.text_color is None:
                text_color = "black"
            else:
                text_color = self.text_color

            dc.SetTextForeground(text_color)

            x, y = self.text_location
            dc.DrawText(self.text, x, y)

        # Let the normal wx paint handling do its stuff.
        event.Skip()
Пример #12
0
class TDViz(HasTraits):
    fitsfile = File(filter=[u"*.fits"])
    plotbutton1 = Button(u"Plot")
    plotbutton2 = Button(u"Plot")
    plotbutton3 = Button(u"Plot")
    clearbutton = Button(u"Clear")
    scene = Instance(MlabSceneModel, ())
    rendering = Enum("Surface-Spectrum", "Surface-Intensity",
                     "Volume-Intensity")
    save_the_scene = Button(u"Save")
    save_in_file = Str("test.x3d")
    movie = Button(u"Movie")
    iteration = Int(0)
    quality = Int(8)
    delay = Int(0)
    angle = Int(360)
    spin = Button(u"Spin")
    #zscale = Float(1.0)
    zscale = Int(1)
    xstart = Int(0)
    xend = Int(1)
    ystart = Int(0)
    yend = Int(1)
    zstart = Int(0)
    zend = Int(1)
    datamin = Float(0.0)
    datamax = Float(1.0)
    opacity = Float(0.4)
    dist = Float(0.0)
    leng = Float(0.0)
    vsp = Float(0.0)
    contfile = File(filter=[u"*.fits"])

    view = View(HSplit(
        VGroup(
            Item("fitsfile",
                 label=u"Select a FITS datacube",
                 show_label=True,
                 editor=FileEditor(dialog_style='open')),
            Item("rendering",
                 tooltip=u"Choose the rendering type",
                 show_label=True),
            Item('plotbutton1',
                 tooltip=u"Plot 3D surfaces, color coded by velocities",
                 visible_when="rendering=='Surface-Spectrum'"),
            Item('plotbutton2',
                 tooltip=u"Plot 3D surfaces, color coded by intensities",
                 visible_when="rendering=='Surface-Intensity'"),
            Item('plotbutton3',
                 tooltip=u"Plot 3D dots, color coded by intensities",
                 visible_when="rendering=='Volume-Intensity'"),
            "clearbutton",
            HGroup(
                Item('xstart',
                     tooltip=u"starting pixel in X axis",
                     show_label=True,
                     springy=True),
                Item('xend',
                     tooltip=u"ending pixel in X axis",
                     show_label=True,
                     springy=True)),
            HGroup(
                Item('ystart',
                     tooltip=u"starting pixel in Y axis",
                     show_label=True,
                     springy=True),
                Item('yend',
                     tooltip=u"ending pixel in Y axis",
                     show_label=True,
                     springy=True)),
            HGroup(
                Item('zstart',
                     tooltip=u"starting pixel in Z axis",
                     show_label=True,
                     springy=True),
                Item('zend',
                     tooltip=u"ending pixel in Z axis",
                     show_label=True,
                     springy=True)),
            HGroup(
                Item('datamax',
                     tooltip=u"Maximum datapoint shown",
                     show_label=True,
                     springy=True),
                Item('datamin',
                     tooltip=u"Minimum datapoint shown",
                     show_label=True,
                     springy=True)),
            HGroup(
                Item('dist', tooltip=u"Put a distance in kpc",
                     show_label=True),
                Item('leng',
                     tooltip=
                     u"Put a non-zero bar length in pc to show the scale bar",
                     show_label=True),
                Item(
                    'vsp',
                    tooltip=
                    u"Put a non-zero velocity range in km/s to show the scale bar",
                    show_label=True)),
            HGroup(Item('zscale',
                        tooltip=u"Stretch the datacube in Z axis",
                        show_label=True),
                   Item('opacity',
                        tooltip=u"Opacity of the scene",
                        show_label=True),
                   show_labels=False),
            Item('_'),
            Item(
                "contfile",
                label=u"Add background contours",
                tooltip=
                u"This file must be of the same (first two) dimension as the datacube!!!",
                show_label=True,
                editor=FileEditor(dialog_style='open')),
            Item('_'),
            HGroup(Item("spin", tooltip=u"Spin 360 degrees", show_label=False),
                   Item("movie", tooltip="Make a GIF movie",
                        show_label=False)),
            HGroup(
                Item('iteration',
                     tooltip=u"number of iterations, 0 means inf.",
                     show_label=True),
                Item('quality',
                     tooltip=u"quality of plots, 0 is worst, 8 is good.",
                     show_label=True)),
            HGroup(
                Item('delay',
                     tooltip=u"time delay between frames, in millisecond.",
                     show_label=True),
                Item('angle', tooltip=u"angle the cube spins",
                     show_label=True)),
            Item('_'),
            HGroup(
                Item("save_the_scene",
                     tooltip=u"Save current scene in a 3D model file"),
                Item("save_in_file",
                     tooltip=u"3D model file name",
                     show_label=False),
                visible_when=
                "rendering=='Surface-Spectrum' or rendering=='Surface-Intensity'"
            ),
            show_labels=False),
        VGroup(Item(name='scene',
                    editor=SceneEditor(scene_class=MayaviScene),
                    resizable=True,
                    height=600,
                    width=600),
               show_labels=False)),
                resizable=True,
                title=u"TDViz")

    def _fitsfile_changed(self):
        img = fits.open(self.fitsfile)  # Read the fits data
        dat = img[0].data
        self.hdr = img[0].header

        naxis = self.hdr['NAXIS']
        ## The three axes loaded by fits are: velo, dec, ra
        ## Swap the axes, RA<->velo
        if naxis == 4:
            self.data = np.swapaxes(dat[0], 0, 2) * 1000.0
        elif naxis == 3:
            self.data = np.swapaxes(dat, 0, 2) * 1000.0
        #onevpix = self.hdr['CDELT3']
        self.data[np.isnan(self.data)] = 0.0
        self.data[np.isinf(self.data)] = 0.0

        self.datamax = np.asscalar(np.max(self.data))
        self.datamin = np.asscalar(np.min(self.data))
        self.xend = self.data.shape[0] - 1
        self.yend = self.data.shape[1] - 1
        self.zend = self.data.shape[2] - 1

        self.data[self.data < self.datamin] = self.datamin

    def loaddata(self):
        channel = self.data
        ## Reset the range if it is beyond the cube:
        if self.xstart < 0:
            print('Wrong number!')
            self.xstart = 0
        if self.xend > channel.shape[0] - 1:
            print('Wrong number!')
            self.xend = channel.shape[0] - 1
        if self.ystart < 0:
            print('Wrong number!')
            self.ystart = 0
        if self.yend > channel.shape[1] - 1:
            print('Wrong number!')
            self.yend = channel.shape[1] - 1
        if self.zstart < 0:
            print('Wrong number!')
            self.zstart = 0
        if self.zend > channel.shape[2] - 1:
            print('Wrong number!')
            self.zend = channel.shape[2] - 1
        ## Select a region, use mJy unit
        region = channel[self.xstart:self.xend, self.ystart:self.yend,
                         self.zstart:self.zend]

        ## Stretch the cube in V axis
        from scipy.interpolate import splrep
        from scipy.interpolate import splev
        vol = region.shape
        stretch = self.zscale
        ## Stretch parameter: how many times longer/shorter the V axis will be
        #sregion=np.empty((vol[0],vol[1],round(vol[2]*stretch)))
        sregion = np.empty((vol[0], vol[1], vol[2] * stretch))
        chanindex = np.linspace(0, vol[2] - 1, vol[2])
        #chanindex2=np.linspace(0,vol[2]-1,round(vol[2]*stretch))
        chanindex2 = np.linspace(0, vol[2] - 1, vol[2] * stretch)
        for j in range(0, vol[0] - 1):
            for k in range(0, vol[1] - 1):
                spec = region[j, k, :]
                tck = splrep(chanindex, spec, k=1)
                sregion[j, k, :] = splev(chanindex2, tck)
        self.sregion = sregion
        # Reset the max/min values
        if self.datamin < np.asscalar(np.min(self.sregion)):
            print('Wrong number!')
            self.datamin = np.asscalar(np.min(self.sregion))
        if self.datamax > np.asscalar(np.max(self.sregion)):
            print('Wrong number!')
            self.datamax = np.asscalar(np.max(self.sregion))
        self.xrang = abs(self.xstart - self.xend)
        self.yrang = abs(self.ystart - self.yend)
        #self.zrang = round(abs(self.zstart - self.zend)*stretch)
        self.zrang = abs(self.zstart - self.zend) * stretch

        ## Keep a record of the coordinates:
        crval1 = self.hdr['crval1']
        cdelt1 = self.hdr['cdelt1']
        crpix1 = self.hdr['crpix1']
        crval2 = self.hdr['crval2']
        cdelt2 = self.hdr['cdelt2']
        crpix2 = self.hdr['crpix2']
        crval3 = self.hdr['crval3']
        cdelt3 = self.hdr['cdelt3']
        crpix3 = self.hdr['crpix3']

        ra_start = (self.xstart + 1 - crpix1) * cdelt1 + crval1
        ra_end = (self.xend + 1 - crpix1) * cdelt1 + crval1
        #if ra_start < ra_end:
        #	ra_start, ra_end = ra_end, ra_start
        dec_start = (self.ystart + 1 - crpix2) * cdelt2 + crval2
        dec_end = (self.yend + 1 - crpix2) * cdelt2 + crval2
        #if dec_start > dec_end:
        #	dec_start, dec_end = dec_end, dec_start
        vel_start = (self.zstart + 1 - crpix3) * cdelt3 + crval3
        vel_end = (self.zend + 1 - crpix3) * cdelt3 + crval3
        #if vel_start < vel_end:
        #	vel_start, vel_end = vel_end, vel_start
        vel_start /= 1e3
        vel_end /= 1e3

        ## Flip the V axis
        if cdelt3 > 0:
            self.sregion = self.sregion[:, :, ::-1]
            vel_start, vel_end = vel_end, vel_start

        self.extent = [
            ra_start, ra_end, dec_start, dec_end, vel_start, vel_end
        ]

    def labels(self):
        '''
        Add 3d text to show the axes.
        '''
        fontsize = max(self.xrang, self.yrang) / 40.
        tcolor = (1, 1, 1)
        mlab.text3d(self.xrang / 2,
                    -10,
                    self.zrang + 10,
                    'R.A.',
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)
        mlab.text3d(-10,
                    self.yrang / 2,
                    self.zrang + 10,
                    'Decl.',
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)
        mlab.text3d(-10,
                    -10,
                    self.zrang / 2 - 10,
                    'V (km/s)',
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)

        # Add scale bars
        if self.leng != 0.0:
            distance = self.dist * 1e3
            length = self.leng
            leng_pix = np.round(length / distance / np.pi * 180. /
                                np.abs(self.hdr['cdelt1']))
            bar_x = [self.xrang - 20 - leng_pix, self.xrang - 20]
            bar_y = [self.yrang - 10, self.yrang - 10]
            bar_z = [0, 0]
            mlab.plot3d(bar_x, bar_y, bar_z, color=tcolor, tube_radius=1.)
            mlab.text3d(self.xrang - 30 - leng_pix,
                        self.yrang - 25,
                        0,
                        '{:.2f} pc'.format(length),
                        scale=fontsize,
                        orient_to_camera=False,
                        color=tcolor)
        if self.vsp != 0.0:
            vspan = self.vsp
            vspan_pix = np.round(vspan / np.abs(self.hdr['cdelt3'] / 1e3))
            bar_x = [self.xrang, self.xrang]
            bar_y = [self.yrang - 10, self.yrang - 10]
            bar_z = np.array([5, 5 + vspan_pix]) * self.zscale
            mlab.plot3d(bar_x, bar_y, bar_z, color=tcolor, tube_radius=1.)
            mlab.text3d(self.xrang,
                        self.yrang - 25,
                        10,
                        '{:.1f} km/s'.format(vspan),
                        scale=fontsize,
                        orient_to_camera=False,
                        color=tcolor,
                        orientation=(0, 90, 0))

        # Label the coordinates of the corners
        # Lower left corner
        ra0 = self.extent[0]
        dec0 = self.extent[2]
        c = SkyCoord(ra=ra0 * u.degree, dec=dec0 * u.degree, frame='icrs')
        RA_ll = str(int(c.ra.hms.h)) + 'h' + str(int(c.ra.hms.m)) + 'm' + str(
            round(c.ra.hms.s, 1)) + 's'
        mlab.text3d(0,
                    -10,
                    self.zrang + 5,
                    RA_ll,
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)
        DEC_ll = str(int(c.dec.dms.d)) + 'd' + str(int(abs(
            c.dec.dms.m))) + 'm' + str(round(abs(c.dec.dms.s), 1)) + 's'
        mlab.text3d(-40,
                    0,
                    self.zrang + 5,
                    DEC_ll,
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)
        # Upper right corner
        ra0 = self.extent[1]
        dec0 = self.extent[3]
        c = SkyCoord(ra=ra0 * u.degree, dec=dec0 * u.degree, frame='icrs')
        RA_ll = str(int(c.ra.hms.h)) + 'h' + str(int(c.ra.hms.m)) + 'm' + str(
            round(c.ra.hms.s, 1)) + 's'
        mlab.text3d(self.xrang,
                    -10,
                    self.zrang + 5,
                    RA_ll,
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)
        DEC_ll = str(int(c.dec.dms.d)) + 'd' + str(int(abs(
            c.dec.dms.m))) + 'm' + str(round(abs(c.dec.dms.s), 1)) + 's'
        mlab.text3d(-40,
                    self.yrang,
                    self.zrang + 5,
                    DEC_ll,
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)
        # V axis
        if self.extent[5] > self.extent[4]:
            v0 = self.extent[4]
            v1 = self.extent[5]
        else:
            v0 = self.extent[5]
            v1 = self.extent[4]
        mlab.text3d(-10,
                    -10,
                    self.zrang,
                    str(round(v0, 1)),
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)
        mlab.text3d(-10,
                    -10,
                    0,
                    str(round(v1, 1)),
                    scale=fontsize,
                    orient_to_camera=True,
                    color=tcolor)

        mlab.axes(self.field,
                  ranges=self.extent,
                  x_axis_visibility=False,
                  y_axis_visibility=False,
                  z_axis_visibility=False)
        mlab.outline()

    def _plotbutton1_fired(self):
        mlab.clf()
        self.loaddata()
        self.sregion[np.where(self.sregion < self.datamin)] = self.datamin
        self.sregion[np.where(self.sregion > self.datamax)] = self.datamax

        # The following codes from: http://docs.enthought.com/mayavi/mayavi/auto/example_atomic_orbital.html#example-atomic-orbital
        field = mlab.pipeline.scalar_field(
            self.sregion)  # Generate a scalar field
        colored = self.sregion
        vol = self.sregion.shape
        for v in range(0, vol[2] - 1):
            colored[:, :,
                    v] = self.extent[4] + v * (-1) * abs(self.hdr['cdelt3'])
        field.image_data.point_data.add_array(colored.T.ravel())
        field.image_data.point_data.get_array(1).name = 'color'
        field.update()

        field2 = mlab.pipeline.set_active_attribute(field,
                                                    point_scalars='scalar')
        contour = mlab.pipeline.contour(field2)
        contour2 = mlab.pipeline.set_active_attribute(contour,
                                                      point_scalars='color')

        mlab.pipeline.surface(contour2, colormap='jet', opacity=self.opacity)

        ## Insert a continuum plot
        if self.contfile != '':
            im = fits.open(self.contfile)
            dat = im[0].data
            ##dat0 = dat[0]
            channel = dat[0]
            region = np.swapaxes(
                channel[self.ystart:self.yend, self.xstart:self.xend] * 1000.,
                0, 1)
            field = mlab.contour3d(region, colormap='gist_ncar')
            field.contour.minimum_contour = 5

        self.field = field2
        self.field.scene.render()
        self.labels()
        mlab.view(azimuth=0, elevation=0, distance='auto')
        mlab.show()

    def _plotbutton2_fired(self):
        mlab.clf()
        self.loaddata()
        #field=mlab.contour3d(self.sregion,colormap='gist_ncar')     # Generate a scalar field
        field = mlab.contour3d(self.sregion)  # Generate a scalar field
        field.contour.maximum_contour = self.datamax
        field.contour.minimum_contour = self.datamin
        field.actor.property.opacity = self.opacity

        self.field = field
        self.labels()
        mlab.view(azimuth=0, elevation=0, distance='auto')
        mlab.show()

    def _plotbutton3_fired(self):
        mlab.clf()
        self.loaddata()
        field = mlab.pipeline.scalar_field(
            self.sregion)  # Generate a scalar field
        mlab.pipeline.volume(field, vmax=self.datamax, vmin=self.datamin)

        self.field = field
        self.labels()
        mlab.view(azimuth=0, elevation=0, distance='auto')
        mlab.colorbar()
        mlab.show()


#   def _datamax_changed(self):
#       if hasattr(self, "field"):
#           self.field.contour.maximum_contour = self.datamax

    def _save_the_scene_fired(self):
        mlab.savefig(self.save_in_file)

    def _movie_fired(self):
        if os.path.exists("./tenpfigz"):
            print("The chance of you using this name is really small...")
        else:
            os.system("mkdir tenpfigz")

        if filter(os.path.isfile, glob.glob("./tenpfigz/*.jpg")) != []:
            os.system("rm -rf ./tenpfigz/*.jpg")

        i = 0
        ## Quality of the movie: 0 is the worst, 8 is ok.
        self.field.scene.anti_aliasing_frames = self.quality
        self.field.scene.disable_render = True
        mlab.savefig('./tenpfigz/screenshot0' + str(i) + '.jpg')
        while i < (self.angle / 5):
            self.field.scene.camera.azimuth(5)
            self.field.scene.render()
            i += 1
            if i < 10:
                mlab.savefig('./tenpfigz/screenshot0' + str(i) + '.jpg')
            elif 9 < i < 100:
                mlab.savefig('./tenpfigz/screenshot' + str(i) + '.jpg')
        self.field.scene.disable_render = False

        os.system("convert -delay " + str(self.delay) + " -loop " +
                  str(self.iteration) +
                  " ./tenpfigz/*.jpg ./tenpfigz/animation.gif")

    def _spin_fired(self):
        i = 0
        self.field.scene.disable_render = True

        @mlab.animate
        def anim():
            while i < 72:
                self.field.scene.camera.azimuth(5)
                self.field.scene.render()
                yield

        a = anim()
        #while i<72:
        #	self.field.scene.camera.azimuth(5)
        #	self.field.scene.render()
        #	i += 1
        #	#mlab.savefig('./'+str(i)+'.png')
        self.field.scene.disable_render = False

    def _clearbutton_fired(self):
        mlab.clf()
Пример #13
0
class GSODDataPlotterView(HasTraits):
    """ Application of the zoom tool to the GSOD plotting tool.
    Load a HDF file containing one or more timeseries and plot the entire data inside.
    The zoom tool allows to explore a subset of it. The legend allows to (de)select some
    timeseries.
    """
    # UI controls
    data_file = File()

    # Tool controls
    tool_list = List([MA, CORRELATION])
    tool_chooser = Enum(values="tool_list")
    ts_list = List()
    ts1_chooser = Enum(values="ts_list")
    ts2_chooser = Enum(values="ts_list")
    # Moving average window size (in number of observations)
    ma_window_size = Int(0)
    # Analysis details
    ts_analysis_details = Str("No details available")

    # Data
    ts_data = Dict()
    arr_plot_data = Instance(ArrayPlotData, ())
    times_ds = Any()  # arraydatasource for the time axis data
    index_is_dates = Bool()

    # Plots
    ts_plot = Instance(ToolbarPlot, ())
    ts_analysis_plot = Instance(ToolbarPlot, ())

    def trait_view(self, view):
        """ Build the view. The local namespace is 
        """
        return View(VGroup(
            Item('data_file', style='simple', label="HDF file to load"),
            HSplit(
                Item('ts_plot',
                     editor=ComponentEditor(size=(400, 600)),
                     show_label=False),
                VGroup(
                    Item('tool_chooser', show_label=True, label="Choose tool"),
                    Item('ts1_chooser', label="TS 1"),
                    Item('ts2_chooser',
                         label="TS 2",
                         visible_when="tool_chooser in ['%s']" % CORRELATION),
                    Item('ma_window_size',
                         label="MA window size",
                         visible_when="tool_chooser in ['%s']" % MA),
                    Item('ts_analysis_plot',
                         editor=ComponentEditor(size=(400, 600)),
                         show_label=False),
                    Item('ts_analysis_details',
                         show_label=False,
                         style='readonly',
                         visible_when=("tool_chooser in ['%s']" %
                                       CORRELATION))),
            ),
        ),
                    title='Time-series plotter and analyzer',
                    width=1300,
                    height=800,
                    resizable=True)

    def __init__(self, pandas_list=[], array_dict={}, *args, **kw):
        """ If a (list of) pandas or a dict of arrays is passed, load them up. 
        """
        # Initialize the data content of the analysis tool
        ts_data = {}
        super(GSODDataPlotterView, self).__init__(*args, **kw)
        if not isinstance(pandas_list, list):
            pandas_list = [pandas_list]
        if pandas_list:
            array_dict_from_pandas, self.index_is_dates = pandas2array_dict(
                pandas_list)
            ts_data.update(array_dict_from_pandas)
        if array_dict:
            ts_data.update(array_dict)

        if ts_data:
            # Now trigger the plot redraw
            self.ts_data = ts_data

    def _data_file_changed(self):
        """ Update the data from the HDF5 file.
       """
        ts_data, self.index_is_dates = pandas_hdf_to_data_dict2(self.data_file)
        assert ("index" in ts_data)
        self.ts_data = ts_data

    def _ts_data_changed(self):
        """ Dataset has changed: update the plots.
        ENH: add the possibility to pass a dict to ArrayPlotData constructor.
        """
        for k, v in self.ts_data.items():
            self.arr_plot_data.set_data(k, v)
        self.ts_list = self.ts_data.keys()
        self.update_main_plot()
        self.update_analysis_plot()

    def update_main_plot(self):
        """ Build main plot
        """
        self.ts_plot = ToolbarPlot(self.arr_plot_data)
        for i, k in enumerate([k for k in self.ts_data.keys()
                               if k != "index"]):
            renderer = self.ts_plot.plot(("index", k),
                                         name=k,
                                         color=colors[i % len(colors)])[0]
        if self.index_is_dates:
            # Index was an array of datetime: overwrite the x axis
            self.ts_plot.x_axis = None
            x_axis = PlotAxis(self.ts_plot,
                              orientation="bottom",
                              tick_generator=ScalesTickGenerator(
                                  scale=CalendarScaleSystem()))
            self.ts_plot.overlays.append(x_axis)
            self.ts_plot.x_grid.tick_generator = x_axis.tick_generator

        if self.data_file:
            self.ts_plot.title = ("Time series visualization from %s" %
                                  (os.path.split(self.data_file)[1]))
        else:
            self.ts_plot.title = "Time series visualization"
        attach_tools(self.ts_plot)

        # Attach the range selection to the last renderer; any one will do
        self.ts_plot.tools.append(
            RangeSelection(renderer,
                           left_button_selects=False,
                           auto_handle_event=False))
        # Attach the corresponding overlay
        self._range_selection_overlay = RangeSelectionOverlay(
            renderer, metadata_name="selections")
        self.ts_plot.overlays.append(self._range_selection_overlay)
        # Grab a reference to the Time axis datasource and add a listener to its
        # selections metadata
        self.times_ds = renderer.index
        self.times_ds.on_trait_change(self._selections_changed)

    def _selections_changed(self, event):
        """ Selection of a time range on the first plot will triger a redraw of 
        the correlation plot if present.
        """
        if self.tool_chooser != CORRELATION:
            return
        if not isinstance(event, dict) or "selections" not in event:
            return
        corr_index = self.corr_renderer.index
        selections = event["selections"]
        if selections is None:
            corr_index.metadata.pop("selections", None)
            return
        else:
            low, high = selections
            data = self.times_ds.get_data()
            low_ndx = data.searchsorted(low)
            high_ndx = data.searchsorted(high)
            corr_index.metadata["selections"] = np.arange(low_ndx,
                                                          high_ndx + 1,
                                                          1,
                                                          dtype=int)
            self.ts_analysis_plot.request_redraw()

    @on_trait_change("tool_chooser, ts1_chooser, ts2_chooser, ma_window_size")
    def update_analysis_plot(self):
        """ Build analysis plot
        """
        self.ts_analysis_plot = ToolbarPlot(self.arr_plot_data)
        if self.tool_chooser == CORRELATION:
            self.corr_renderer = self.ts_analysis_plot.plot(
                (self.ts1_chooser, self.ts2_chooser),
                type="scatter",
                color="blue")[0]
            self.ts_analysis_plot.title = "%s plotted against %s" % (
                self.ts1_chooser, self.ts2_chooser)
            self.ts_analysis_plot.index_axis.title = self.ts1_chooser
            self.ts_analysis_plot.value_axis.title = self.ts2_chooser
        elif self.tool_chooser == MA and self.ma_window_size > 0:
            ts1_ma = pandas.rolling_mean(
                self.arr_plot_data.get_data(self.ts1_chooser),
                self.ma_window_size)
            self.arr_plot_data.set_data("ts1_ma", ts1_ma)
            self.ts_analysis_plot.plot(("index", self.ts1_chooser),
                                       type="scatter",
                                       color="blue")
            self.ts_analysis_plot.plot(("index", "ts1_ma"),
                                       type="line",
                                       color="blue")

    @on_trait_change("tool_chooser, ts1_chooser, ts2_chooser")
    def update_analysis_details(self):
        if self.tool_chooser == CORRELATION:
            # Compute the correlation coefficients between the chosen TS
            ts1 = pandas.Series(self.ts_data[self.ts1_chooser])
            ts2 = pandas.Series(self.ts_data[self.ts2_chooser])
            corr_coefs = ts1.corr(ts2), ts1.corr(
                ts2, method='spearman'), ts1.corr(ts2, method='kendall')
            self.ts_analysis_details = (
                "Coefficients of correlation: Std = %5.3f, Spearman = %5.3f, Kendall = %5.3f."
                % corr_coefs)
            return
Пример #14
0
class TemplateView(HasPrivateTraits):
    """ A feature-based Traits UI plug-in for viewing templates.
    """

    #-- Public Traits ----------------------------------------------------------

    # The name of the plugin:
    name = Str('Template View')

    # The data context supplying the data to be viewed:
    context = Instance(ITemplateDataContext, connect='to: data context')

    # The name of the file containing a template view:
    file_name = File(drop_file=DropFile(
        extensions=['.py', '.tv', '.las'],
        tooltip='Drop a LAS data file, a saved view template '
        'or a Python source file containing a view '
        'template here.'),
                     connect='to: template view')

    # The template to view:
    template = Instance(ITemplate)

    #-- Private Traits ---------------------------------------------------------

    # The name of the file to save the template in:
    save_file_name = File

    # The current data names:
    data_names = Instance(TemplateDataNames)

    # The TemplateDataNames or Message being viewed:
    names_view = Any(no_context)

    # The name of the most recently loaded template file:
    template_file_name = File

    # The template or message being viewed:
    template_view = Any(no_template)

    # The options view for the currently active template:
    options_view = Any(no_options)

    # The event fired when the user wants to save the template:
    save_template = Button('Save Template')

    #-- Traits View Definitions ------------------------------------------------

    view = View(VGroup(
        HGroup(Item('file_name', show_label=False, width=350),
               TButton('save_template',
                       label='Save Template',
                       enabled_when='template is not None'),
               group_theme=Theme('@GFB', margins=(-7, -5))),
        Tabbed(VGroup('8',
                      Label('Data Bindings',
                            item_theme=Theme('@GBB', alignment='center')),
                      Item('names_view',
                           style='custom',
                           resizable=True,
                           editor=InstanceEditor(),
                           export='DockWindowShell',
                           item_theme=Theme('@GFB', margins=(-5, -1))),
                      label='Data Bindings',
                      show_labels=False),
               Item('template_view',
                    label='Template View',
                    style='custom',
                    resizable=True,
                    editor=InstanceEditor(view='template_view'),
                    export='DockWindowShell'),
               Item('options_view',
                    label='Template Options',
                    style='custom',
                    resizable=True,
                    editor=InstanceEditor(view='options_view'),
                    export='DockWindowShell'),
               id='tabbed',
               dock='horizontal',
               show_labels=False),
    ),
                id='template.test.template_view.TemplateView')

    #-- Trait Event Handlers ---------------------------------------------------

    def _context_changed(self, context):
        """ Handles the 'context' trait being changed.
        """
        if context is None:
            self.names_view = no_context
        elif self.template is None:
            self.names_view = no_template
        else:
            self._create_view()

    def _template_changed(self, template):
        """ Handles the 'template' trait being changed.
        """
        if self.context is None:
            self.template_view = no_context
        else:
            self._create_view()

    def _file_name_changed(self, file_name):
        """ Handles the 'file_name' trait being changed.
        """
        ext = splitext(file_name)[1]
        if ext == '.py':
            self._load_python_template(file_name)
        elif ext == '.tv':
            self._load_pickled_template(file_name)
        elif ext == '.las':
            self._load_las_file(file_name)
        else:
            # fixme: Display an informational message here...
            pass

    def _save_template_changed(self):
        """ Handles the user clicking the 'Save Template' button.
        """
        self._save_pickled_template()

    @on_trait_change('data_names.unresolved_data_names')
    def _on_unresolved_data_names(self):
        if len(self.data_names.unresolved_data_names) == 0:
            self._create_object_view()
        elif not isinstance(self.template_view, Message):
            self.template_view = no_bindings
            self.options_view = no_options

    @on_trait_change('template.template_mutated?')
    def _on_template_mutated(self):
        """ Handles a mutable template changing.
        """
        if self.context is not None:
            self._create_view()

    #-- Private Methods --------------------------------------------------------

    def _load_python_template(self, file_name):
        """ Attempts to load a template from a Python source file.
        """
        path, name = split(file_name)
        sys.path[0:0] = [path]
        try:
            ###values = {}
            module_name, ext = splitext(name)
            module = __import__(module_name)
            values = module.__dict__
            ###execfile( file_name, values )
            template = values.get('template')
            if template is None:
                templates = []
                for value in list(values.values()):
                    try:
                        if (issubclass(value, Template) and
                                ###(value.__module__ == '__builtin__')):
                            (value.__module__ == module_name)):
                            templates.append(value)
                    except:
                        pass

                for i, template in enumerate(templates):
                    for t in templates[i + 1:]:
                        if issubclass(template, t):
                            break
                    else:
                        break
                else:
                    self.template_view = no_template_found
                    return

            if not isinstance(template, Template):
                template = template()

            self.template = template
            self.template_file_name = file_name

        except Exception as excp:
            self.template_view = Message(str(excp))

        # Clean up the Python path:
        del sys.path[0]

    def _load_pickled_template(self, file_name):
        """ Attempts to load a template from a pickle.
        """
        # fixme: Implement this...load template from .tv pickle file.
        fh = None
        delete = False
        try:
            fh = open(file_name, 'rb')
            file_name = load(fh)
            path, name = split(file_name)
            sys.path[0:0] = [path]
            delete = True
            module_name, ext = splitext(name)
            module = __import__(module_name)
            self.template = load(fh)
            self.template_file_name = file_name
        except Exception as excp:
            import traceback
            traceback.print_exc()
            self.template_view = Message(str(excp))

        if fh is not None:
            fh.close()

        if delete:
            del sys.path[0]

    def _load_las_file(self, file_name):
        """ Creates a data context from the specified LAS file.
        """
        try:
            self.context = import_log_files(file_name, 'las')
        except Exception as excp:
            self.names_view = Message(str(excp))

    def _save_pickled_template(self):
        file_name = self.save_file_name or self.file_name
        fd = FileDialog(action='save as', default_path=file_name)
        #wildcard     = 'Template files (*.tv)|*.tv|' )
        if fd.open() == OK:
            self.save_file_name = file_name = fd.path
            fh = None
            try:
                fh = open(file_name, 'wb')
                dump(self.template_file_name, fh, -1)
                dump(self.template.template_from_object(), fh, -1)
            except:
                # fixme: Display an informational message here...
                import traceback
                traceback.print_exc()

            if fh is not None:
                fh.close()

    def _create_view(self):
        """ Begins the process of creating a live view from a template and
            data context object.
        """
        self.data_names = self.names_view = nv = TemplateDataNames(
            context=self.context,
            data_names=self.template.names_from_template())

        if len(nv.unresolved_data_names) == 0:
            self._create_object_view()
        else:
            self.template_view = no_bindings

    def _create_object_view(self):
        """ Create the object view from the current template.
        """
        self.template.object_from_template()
        self.template_view = self.options_view = self.template
Пример #15
0
class RosslerIPlottable2dAdapter(Adapter, IModel3dIPlottable2dMixin):

    adaptee = Instance(Rossler)

    plot_type = Str('line')
Пример #16
0
class FooModel(HasTraits):
    my_str = Str('hallo')
Пример #17
0
class BrowserPane(TraitsDockPane):
    name = 'Browser'
    id = 'pychron.browser'
    multi_select = True
    analyses_defined = Str('1')

    labnumber_tabular_adapter = Instance(LabnumberAdapter, ())
    # analysis_tabular_adapter = Instance(AnalysisAdapter, ())
    analysis_group_tabular_adapter = Instance(AnalysisGroupAdapter, ())

    sample_view = Instance(BrowserSampleView)
    # query_view = Instance(BrowserQueryView)
    time_view = Instance(TimeViewModel)

    def _get_browser_group(self):
        grp = Group(
            UItem('pane.sample_view',
                  style='custom',
                  visible_when='sample_view_active'),
            UItem('time_view_model',
                  style='custom',
                  visible_when='not sample_view_active')
            # UItem('pane.query_view',
            # style='custom',
            # visible_when='not sample_view_active')
        )
        return grp

    def traits_view(self):
        main_grp = self._get_browser_group()

        v = View(
            VGroup(
                HGroup(
                    # icon_button_editor('advanced_query', 'application_form_magnify',
                    # tooltip='Advanced Query'),
                    icon_button_editor(
                        'filter_by_button',
                        'find',
                        tooltip='Filter analyses using defined criteria'),
                    icon_button_editor(
                        'graphical_filter_button',
                        'chart_curve_go',
                        # enabled_when='samples',
                        tooltip='Filter analyses graphically'),
                    icon_button_editor(
                        'toggle_view',
                        'arrow_switch',
                        tooltip='Toggle between Sample and Time views'),
                    spring,
                    UItem('use_focus_switching',
                          tooltip='Show/Hide Filters on demand'),
                    Spring(springy=False, width=10),
                    icon_button_editor(
                        'toggle_focus',
                        'arrow_switch',
                        enabled_when='use_focus_switching',
                        tooltip='Toggle Filter and Result focus'),
                    spring,
                    # UItem('current_task_name', style='readonly'),
                    CustomLabel('datasource_url', color='maroon'),
                ),
                main_grp),
            # handler=TablesHandler()
            # handler=UnselectTabularEditorHandler(selected_name='selected_projects')
        )

        return v

    def _sample_view_default(self):
        return BrowserSampleView(model=self.model, pane=self)
Пример #18
0
class TableEditor(Editor, BaseTableEditor):
    """ Editor that presents data in a table. Optionally, tables can have
        a set of filters that reduce the set of data displayed, according to
        their criteria.
    """

    # -------------------------------------------------------------------------
    #  Trait definitions:
    # -------------------------------------------------------------------------

    #: The table view control associated with the editor:
    table_view = Any()

    def _table_view_default(self):
        return TableView(editor=self)

    #: A wrapper around the source model which provides filtering and sorting:
    model = Instance(SortFilterTableModel)

    def _model_default(self):
        return SortFilterTableModel(editor=self)

    #: The table model associated with the editor:
    source_model = Instance(TableModel)

    def _source_model_default(self):
        return TableModel(editor=self)

    #: The set of columns currently defined on the editor:
    columns = List(TableColumn)

    #: The currently selected row(s), column(s), or cell(s).
    selected = Any()

    #: The current selected row
    selected_row = Property(Any, depends_on="selected")

    selected_indices = Property(Any, depends_on="selected")

    #: Current filter object (should be a TableFilter or callable or None):
    filter = Any()

    #: The indices of the table items currently passing the table filter:
    filtered_indices = List(Int)

    #: Current filter summary message
    filter_summary = Str("All items")

    #: Update the filtered contents.
    update_filter = Event()

    #: The event fired when a cell is clicked on:
    click = Event()

    #: The event fired when a cell is double-clicked on:
    dclick = Event()

    #: The Traits UI associated with the table editor toolbar:
    toolbar_ui = Instance(UI)

    #: The context menu associated with empty space in the table
    empty_menu = Instance(QtGui.QMenu)

    #: The context menu associated with the vertical header
    header_menu = Instance(QtGui.QMenu)

    #: The context menu actions for moving rows up and down
    header_menu_up = Instance(QtGui.QAction)
    header_menu_down = Instance(QtGui.QAction)

    #: The index of the row that was last right clicked on its vertical header
    header_row = Int()

    #: Whether to auto-size the columns or not.
    auto_size = Bool(False)

    #: Dictionary mapping image names to QIcons
    images = Any({})

    #: Dictionary mapping ImageResource objects to QIcons
    image_resources = Any({})

    #: An image being converted:
    image = Image

    def init(self, parent):
        """Finishes initializing the editor by creating the underlying toolkit
        widget."""

        factory = self.factory
        self.filter = factory.filter

        columns = factory.columns[:]
        if (len(columns) == 0) and (len(self.value) > 0):
            columns = [
                ObjectColumn(name=name)
                for name in self.value[0].editable_traits()
            ]
        self.columns = columns

        if factory.table_view_factory is not None:
            self.table_view = factory.table_view_factory(editor=self)
        if factory.source_model_factory is not None:
            self.source_model = factory.source_model_factory(editor=self)
        if factory.model_factory is not None:
            self.model = factory.model_factory(editor=self)

        # Create the table view and model
        self.model.setDynamicSortFilter(True)
        self.model.setSourceModel(self.source_model)
        self.table_view.setModel(self.model)

        # Create the vertical header context menu and connect to its signals
        self.header_menu = QtGui.QMenu(self.table_view)
        insertable = factory.row_factory is not None
        if factory.editable:
            if insertable:
                action = self.header_menu.addAction("Insert new item")
                action.triggered.connect(self._on_context_insert)
            if factory.deletable:
                action = self.header_menu.addAction("Delete item")
                action.triggered.connect(self._on_context_remove)
        if factory.reorderable:
            if factory.editable and (insertable or factory.deletable):
                self.header_menu.addSeparator()
            self.header_menu_up = self.header_menu.addAction("Move item up")
            self.header_menu_up.triggered.connect(self._on_context_move_up)
            self.header_menu_down = self.header_menu.addAction(
                "Move item down"
            )
            self.header_menu_down.triggered.connect(self._on_context_move_down)

        # Create the empty space context menu and connect its signals
        self.empty_menu = QtGui.QMenu(self.table_view)
        action = self.empty_menu.addAction("Add new item")
        action.triggered.connect(self._on_context_append)

        # When sorting is enabled, the first column is initially displayed with
        # the triangle indicating it is the sort index, even though no sorting
        # has actually been done. Sort here for UI/model consistency.
        if self.factory.sortable and not self.factory.reorderable:
            self.model.sort(0, QtCore.Qt.AscendingOrder)

        # Connect to the mode specific selection handler and select the first
        # row/column/cell. Do this before creating the edit_view to make sure
        # that it has a valid item to use when constructing its view.
        smodel = self.table_view.selectionModel()
        mode_slot = getattr(self, "_on_%s_selection" % factory.selection_mode)
        smodel.selectionChanged.connect(mode_slot)
        self.table_view.setCurrentIndex(self.model.index(0, 0))

        # Create the toolbar if necessary
        if factory.show_toolbar and len(factory.filters) > 0:
            main_view = QtGui.QWidget()
            layout = QtGui.QVBoxLayout(main_view)
            layout.setContentsMargins(0, 0, 0, 0)
            self.toolbar_ui = self.edit_traits(
                parent=parent,
                kind="subpanel",
                view=View(
                    Group(
                        Item("filter{View}", editor=factory._filter_editor),
                        Item("filter_summary{Results}", style="readonly"),
                        spring,
                        orientation="horizontal",
                    ),
                    resizable=True,
                ),
            )
            self.toolbar_ui.parent = self.ui
            layout.addWidget(self.toolbar_ui.control)
            layout.addWidget(self.table_view)
        else:
            main_view = self.table_view

        # Create auxiliary editor and encompassing splitter if necessary
        mode = factory.selection_mode
        if (factory.edit_view == " ") or mode not in {"row", "rows"}:
            self.control = main_view
        else:
            if factory.orientation == "horizontal":
                self.control = QtGui.QSplitter(QtCore.Qt.Horizontal)
            else:
                self.control = QtGui.QSplitter(QtCore.Qt.Vertical)
            self.control.setSizePolicy(
                QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding
            )
            self.control.addWidget(main_view)
            self.control.setStretchFactor(0, 2)

            # Create the row editor below the table view
            editor = InstanceEditor(view=factory.edit_view, kind="subpanel")
            self._ui = self.edit_traits(
                parent=self.control,
                kind="subpanel",
                view=View(
                    Item(
                        "selected_row",
                        style="custom",
                        editor=editor,
                        show_label=False,
                        resizable=True,
                        width=factory.edit_view_width,
                        height=factory.edit_view_height,
                    ),
                    resizable=True,
                    handler=factory.edit_view_handler,
                ),
            )
            self._ui.parent = self.ui
            self.control.addWidget(self._ui.control)
            self.control.setStretchFactor(1, 1)

        # Connect to the click and double click handlers
        self.table_view.clicked.connect(self._on_click)
        self.table_view.doubleClicked.connect(self._on_dclick)

        # Make sure we listen for 'items' changes as well as complete list
        # replacements
        self.context_object.on_trait_change(
            self.update_editor, self.extended_name + "_items", dispatch="ui"
        )

        # Listen for changes to traits on the objects in the list
        self.context_object.on_trait_change(
            self.refresh_editor, self.extended_name + ".-", dispatch="ui"
        )

        # Listen for changes on column definitions
        self.on_trait_change(self._update_columns, "columns", dispatch="ui")
        self.on_trait_change(
            self._update_columns, "columns_items", dispatch="ui"
        )

        # Set up the required externally synchronized traits
        is_list = mode in ("rows", "columns", "cells")
        self.sync_value(factory.click, "click", "to")
        self.sync_value(factory.dclick, "dclick", "to")
        self.sync_value(factory.columns_name, "columns", is_list=True)
        self.sync_value(factory.selected, "selected", is_list=is_list)
        self.sync_value(
            factory.selected_indices, "selected_indices", is_list=is_list
        )
        self.sync_value(factory.filter_name, "filter", "from")
        self.sync_value(factory.filtered_indices, "filtered_indices", "to")
        self.sync_value(factory.update_filter_name, "update_filter", "from")

        self.auto_size = self.factory.auto_size

        # Initialize the ItemDelegates for each column
        self._update_columns()

    def dispose(self):
        """ Disposes of the contents of an editor."""
        self.model.beginResetModel()
        self.model.endResetModel()

        # Make sure that the auxiliary UIs are properly disposed
        if self.toolbar_ui is not None:
            self.toolbar_ui.dispose()
        if self._ui is not None:
            self._ui.dispose()

        # Remove listener for 'items' changes on object trait
        self.context_object.on_trait_change(
            self.update_editor, self.extended_name + "_items", remove=True
        )

        # Remove listener for changes to traits on the objects in the list
        self.context_object.on_trait_change(
            self.refresh_editor, self.extended_name + ".-", remove=True
        )

        # Remove listeners for column definition changes
        self.on_trait_change(self._update_columns, "columns", remove=True)
        self.on_trait_change(
            self._update_columns, "columns_items", remove=True
        )

        super(TableEditor, self).dispose()

    def update_editor(self):
        """Updates the editor when the object trait changes externally to the
        editor."""

        if self._no_notify:
            return

        self.table_view.setUpdatesEnabled(False)
        try:
            filtering = (
                len(self.factory.filters) > 0 or self.filter is not None
            )
            if filtering:
                self._update_filtering()

            # invalidate the model, but do not reset it. Resetting the model
            # may cause problems if the selection sync'ed traits are being used
            # externally to manage the selections
            self.model.invalidate()

            self.table_view.resizeColumnsToContents()
            if self.auto_size:
                self.table_view.resizeRowsToContents()

        finally:
            self.table_view.setUpdatesEnabled(True)

    def restore_prefs(self, prefs):
        """ Restores any saved user preference information associated with the
            editor.
        """
        header = self.table_view.horizontalHeader()
        if header is not None and "column_state" in prefs:
            header.restoreState(prefs["column_state"])

    def save_prefs(self):
        """ Returns any user preference information associated with the editor.
        """
        prefs = {}
        header = self.table_view.horizontalHeader()
        if header is not None:
            prefs["column_state"] = header.saveState().data()
        return prefs

    def refresh_editor(self):
        """Requests that the underlying table widget to redraw itself."""

        self.table_view.viewport().update()

    def create_new_row(self):
        """Creates a new row object using the provided factory."""

        factory = self.factory
        kw = factory.row_factory_kw.copy()
        if "__table_editor__" in kw:
            kw["__table_editor__"] = self

        return self.ui.evaluate(
            factory.row_factory, *factory.row_factory_args, **kw
        )

    def items(self):
        """Returns the raw list of model objects."""

        items = self.value
        if not isinstance(items, SequenceTypes):
            items = [items]

        if self.factory and self.factory.reverse:
            items = ReversedList(items)

        return items

    def callx(self, func, *args, **kw):
        """Call a function without notifying the underlying table view or
        model."""

        old = self._no_notify
        self._no_notify = True
        try:
            func(*args, **kw)
        finally:
            self._no_notify = old

    def setx(self, **keywords):
        """Set one or more attributes without notifying the underlying table
        view or model."""

        old = self._no_notify
        self._no_notify = True
        try:
            for name, value in keywords.items():
                setattr(self, name, value)
        finally:
            self._no_notify = old

    def set_selection(self, objects=[], notify=True):
        """Sets the current selection to a set of specified objects."""

        if not isinstance(objects, list):
            objects = [objects]

        mode = self.factory.selection_mode
        indexes = []
        flags = QtGui.QItemSelectionModel.ClearAndSelect

        # In the case of row or column selection, we need a dummy value for the
        # other dimension that has not been filtered.
        source_index = self.model.mapToSource(self.model.index(0, 0))
        source_row, source_column = source_index.row(), source_index.column()

        # Selection mode is 'row' or 'rows'
        if mode.startswith("row"):
            flags |= QtGui.QItemSelectionModel.Rows
            items = self.items()
            for obj in objects:
                try:
                    row = items.index(obj)
                except ValueError:
                    continue
                indexes.append(self.source_model.index(row, source_column))

        # Selection mode is 'column' or 'columns'
        elif mode.startswith("column"):
            flags |= QtGui.QItemSelectionModel.Columns
            for name in objects:
                column = self._column_index_from_name(name)
                if column != -1:
                    indexes.append(self.source_model.index(source_row, column))

        # Selection mode is 'cell' or 'cells'
        else:
            items = self.items()
            for obj, name in objects:
                try:
                    row = items.index(obj)
                except ValueError:
                    continue
                column = self._column_index_from_name(name)
                if column != -1:
                    indexes.append(self.source_model.index(row, column))

        # Perform the selection so that only one signal is emitted
        selection = QtGui.QItemSelection()
        smodel = self.table_view.selectionModel()
        if smodel is None:
            # guard against selection during tear-down
            return
        for index in indexes:
            index = self.model.mapFromSource(index)
            if index.isValid():
                smodel.setCurrentIndex(
                    index, QtGui.QItemSelectionModel.NoUpdate
                )
                selection.select(index, index)

        smodel.blockSignals(not notify)
        try:
            if len(selection.indexes()):
                smodel.clear()
                smodel.select(selection, flags)
            else:
                smodel.clear()
        finally:
            smodel.blockSignals(False)

        self.refresh_editor()

    # -------------------------------------------------------------------------
    #  Private methods:
    # -------------------------------------------------------------------------

    def _column_index_from_name(self, name):
        """Returns the index of the column with the given name or -1 if no
        column exists with that name."""

        for i, column in enumerate(self.columns):
            if name == column.name:
                return i
        return -1

    def _customize_filters(self, filter):
        """Allows the user to customize the current set of table filters."""

        filter_editor = TableFilterEditor(editor=self)
        ui = filter_editor.edit_traits(parent=self.control)
        if ui.result:
            self.factory.filters = filter_editor.templates
            self.filter = filter_editor.selected_filter
        else:
            self.setx(filter=filter)

    def _update_filtering(self):
        """Update the filter summary and the filtered indices."""

        items = self.items()
        num_items = len(items)

        f = self.filter
        if f is None:
            self._filtered_cache = None
            self.filtered_indices = list(range(num_items))
            self.filter_summary = "All %i items" % num_items
        else:
            if not callable(f):
                f = f.filter
            self._filtered_cache = fc = [f(item) for item in items]
            self.filtered_indices = fi = [i for i, ok in enumerate(fc) if ok]
            self.filter_summary = "%i of %i items" % (len(fi), num_items)

    def _add_image(self, image_resource):
        """ Adds a new image to the image map.
        """
        image = image_resource.create_icon()

        self.image_resources[image_resource] = image
        self.images[image_resource.name] = image

        return image

    def _get_image(self, image):
        """ Converts a user specified image to a QIcon.
        """
        if isinstance(image, str):
            self.image = image
            image = self.image

        if isinstance(image, ImageResource):
            result = self.image_resources.get(image)
            if result is not None:
                return result
            return self._add_image(image)

        return self.images.get(image)

    # -- Trait Property getters/setters ---------------------------------------

    @cached_property
    def _get_selected_row(self):
        """Gets the selected row, or the first row if multiple rows are
        selected."""

        mode = self.factory.selection_mode

        if mode.startswith("column"):
            return None
        elif mode == "row":
            return self.selected

        try:
            if mode == "rows":
                return self.selected[0]
            elif mode == "cell":
                return self.selected[0]
            elif mode == "cells":
                return self.selected[0][0]
        except IndexError:
            return None

    @cached_property
    def _get_selected_indices(self):
        """Gets the row,column indices which match the selected trait"""
        selection_items = self.table_view.selectionModel().selection()
        indices = self.model.mapSelectionToSource(selection_items).indexes()
        if self.factory.selection_mode.startswith("row"):
            indices = sorted(set(index.row() for index in indices))
        elif self.factory.selection_mode.startswith("column"):
            indices = sorted(set(index.column() for index in indices))
        else:
            indices = [(index.row(), index.column()) for index in indices]

        if self.factory.selection_mode in {"rows", "columns", "cells"}:
            return indices
        elif len(indices) > 0:
            return indices[0]
        else:
            return -1

    def _set_selected_indices(self, indices):
        if not isinstance(indices, list):
            indices = [indices]
        selected = []
        if self.factory.selection_mode.startswith("row"):
            for row in indices:
                selected.append(self.value[row])
        elif self.factory.selection_mode.startswith("column"):
            for col in indices:
                selected.append(self.columns[col].name)
        else:
            for row, col in indices:
                selected.append((self.value[row], self.columns[col].name))

        self.selected = selected
        self.set_selection(self.selected, False)

    # -- Trait Change Handlers ------------------------------------------------

    def _filter_changed(self, old_filter, new_filter):
        """Handles the current filter being changed."""

        if not self._no_notify:
            if new_filter is customize_filter:
                do_later(self._customize_filters, old_filter)
            else:
                self._update_filtering()
                self.model.invalidate()
                self.set_selection(self.selected)

    def _update_columns(self):
        """Handle the column list being changed."""

        self.table_view.setItemDelegate(TableDelegate(self.table_view))
        for i, column in enumerate(self.columns):
            if column.renderer:
                self.table_view.setItemDelegateForColumn(i, column.renderer)

        self.model.invalidate()
        self.table_view.resizeColumnsToContents()
        if self.auto_size:
            self.table_view.resizeRowsToContents()

    def _selected_changed(self, new):
        """Handle the selected row/column/cell being changed externally."""
        if not self._no_notify:
            self.set_selection(self.selected, notify=False)

    def _update_filter_changed(self):
        """ The filter has changed internally.
        """
        self._filter_changed(self.filter, self.filter)

    # -- Event Handlers -------------------------------------------------------

    def _on_row_selection(self, added, removed):
        """Handle the row selection being changed."""

        items = self.items()
        indexes = self.table_view.selectionModel().selectedRows()
        if len(indexes):
            index = self.model.mapToSource(indexes[0])
            selected = items[index.row()]
        else:
            selected = None

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_rows_selection(self, added, removed):
        """Handle the rows selection being changed."""
        items = self.items()
        indexes = self.table_view.selectionModel().selectedRows()
        selected = [
            items[self.model.mapToSource(index).row()] for index in indexes
        ]

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_column_selection(self, added, removed):
        """Handle the column selection being changed."""

        indexes = self.table_view.selectionModel().selectedColumns()
        if len(indexes):
            index = self.model.mapToSource(indexes[0])
            selected = self.columns[index.column()].name
        else:
            selected = ""

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_columns_selection(self, added, removed):
        """Handle the columns selection being changed."""

        indexes = self.table_view.selectionModel().selectedColumns()
        selected = [
            self.columns[self.model.mapToSource(index).column()].name
            for index in indexes
        ]

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_cell_selection(self, added, removed):
        """Handle the cell selection being changed."""

        items = self.items()
        indexes = self.table_view.selectionModel().selectedIndexes()
        if len(indexes):
            index = self.model.mapToSource(indexes[0])
            obj = items[index.row()]
            column_name = self.columns[index.column()].name
        else:
            obj = None
            column_name = ""
        selected = (obj, column_name)

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_cells_selection(self, added, removed):
        """Handle the cells selection being changed."""

        items = self.items()
        indexes = self.table_view.selectionModel().selectedIndexes()
        selected = []
        for index in indexes:
            index = self.model.mapToSource(index)
            obj = items[index.row()]
            column_name = self.columns[index.column()].name
            selected.append((obj, column_name))

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_click(self, index):
        """Handle a cell being clicked."""

        index = self.model.mapToSource(index)
        column = self.columns[index.column()]
        obj = self.items()[index.row()]

        # Fire the same event on the editor after mapping it to a model object
        # and column name:
        self.click = (obj, column)

        # Invoke the column's click handler:
        column.on_click(obj)

    def _on_dclick(self, index):
        """Handle a cell being double clicked."""

        index = self.model.mapToSource(index)
        column = self.columns[index.column()]
        obj = self.items()[index.row()]

        # Fire the same event on the editor after mapping it to a model object
        # and column name:
        self.dclick = (obj, column)

        # Invoke the column's double-click handler:
        column.on_dclick(obj)

    def _on_context_insert(self):
        """Handle 'insert item' being selected from the header context menu."""

        self.model.insertRow(self.header_row)

    def _on_context_append(self):
        """Handle 'add item' being selected from the empty space context
        menu."""

        self.model.insertRow(self.model.rowCount())

    def _on_context_remove(self):
        """Handle 'remove item' being selected from the header context menu."""

        self.model.removeRow(self.header_row)

    def _on_context_move_up(self):
        """Handle 'move up' being selected from the header context menu."""

        self.model.moveRow(self.header_row, self.header_row - 1)

    def _on_context_move_down(self):
        """Handle 'move down' being selected from the header context menu."""

        self.model.moveRow(self.header_row, self.header_row + 1)
class SpectrumAnalyzerView(HasTraits):
    python_console_cmds = Dict()
    plot = Instance(Plot)
    plot_data = Instance(ArrayPlotData)
    which_plot = Str("Channel 1")
    traits_view = View(
        Item('plot',
             editor=ComponentEditor(bgcolor=(0.8, 0.8, 0.8)),
             show_label=False),
        Item(name='which_plot',
             show_label=False,
             editor=CheckListEditor(
                 values=["Channel 1", "Channel 2", "Channel 3", "Channel 4"])))

    def parse_payload(self, raw_payload):
        """
        Params
        ======
        payload: is an array of ints representing bytes from the SBP_MSG_USER_DATA message 'contents'

        Returns
        =======
        JSON dict of a payload based on this format, except all N of the diff_amplitude
        are together in a list under 'diff_amplitudes' and rx_time is split into TOW and week

        Frequency is in Hz, Amplitude is in dB

        FIELD               TYPE    OFFSET  SHORT EXPLANATION

        user_msg_tag        u16     0       bespoke preamble for spectrum message

        rx_time             struct  2       struct gps_time_t defined as double TOW + s16 WEEK

        starting_frequency  float  12       starting frequency for this packet

        frequency_step      float  16       frequency step for points in this packet

        min_amplitude       float  20       minimum level of amplitude

        amplitude_step      float  24       amplitude unit

        diff_amplitude      u8     28       N values in the above units
        """
        # Turn the array of ints representing uint8 bytes back to binary, so you can use struct
        # formatting to unpack it. Otherwise you would have to manually parse each byte.
        pack_fmt_str = 'B' * len(raw_payload)
        payload = struct.pack(pack_fmt_str, *raw_payload)
        payload_header_fmt_str = '<Hdhffff'
        payload_header_bytes = struct.calcsize(payload_header_fmt_str)
        diff_amplitude_n = (len(raw_payload) - payload_header_bytes)
        diff_amplitude_fmt_str = 'B' * diff_amplitude_n
        fmt_str = payload_header_fmt_str + diff_amplitude_fmt_str
        parsed_payload = struct.unpack(fmt_str, payload)
        fft_msg_header = [
            'user_msg_tag', 'TOW', 'week', 'starting_frequency',
            'frequency_step', 'min_amplitude', 'amplitude_step'
        ]
        payload_json = dict(
            zip(fft_msg_header, parsed_payload[:len(fft_msg_header)]))
        fft_msg_payload = parsed_payload[len(fft_msg_header):]
        payload_json['diff_amplitudes'] = fft_msg_payload
        return payload_json

    def get_frequencies(self, start_freq, freq_step, n):
        '''
        start_freq: float (Hz)
        freq_step: float (Hz)
        n: int
        '''
        return np.array([start_freq + freq_step * i for i in range(n)])

    def get_amplitudes(self, min_amplitude, diffs, unit):
        '''
        min_amplitude: float (dB)
        diffs: tuple of floats (dB)
        unit: float (dB)
        '''
        return np.array([min_amplitude + diff * unit for diff in diffs])

    def spectrum_analyzer_state_callback(self, sbp_msg, **metadata):
        '''
        Params
        ======
        sbp_msg: sbp.msg.SBP object

        Updates the view's data for use in self.update_plot
        '''
        # Need to figure out which user_msg_tag means it's an FFT message
        # for now assume that all SBP_MSG_USER_DATA is relevant
        fft = MsgSpecan(sbp_msg)
        frequencies = self.get_frequencies(fft.freq_ref, fft.freq_step,
                                           len(fft.amplitude_value))
        amplitudes = self.get_amplitudes(fft.amplitude_ref,
                                         fft.amplitude_value,
                                         fft.amplitude_unit)

        tag = fft.channel_tag
        if (tag == 1 and self.which_plot != "Channel 1"):
            return
        if (tag == 2 and self.which_plot != "Channel 2"):
            return
        if (tag == 3 and self.which_plot != "Channel 3"):
            return
        if (tag == 4 and self.which_plot != "Channel 4"):
            return
        timestamp = GpsTime(fft.t.wn, fft.t.tow)
        if len(self.incomplete_data[timestamp]['frequencies']) + len(
                frequencies) == NUM_POINTS:
            self.most_recent_complete_data['frequencies'] = np.append(
                self.incomplete_data[timestamp]['frequencies'],
                frequencies,
                axis=0)
            self.most_recent_complete_data['amplitudes'] = np.append(
                self.incomplete_data[timestamp]['amplitudes'],
                amplitudes,
                axis=0)
            self.incomplete_data.pop(timestamp)
            if timestamp is None or timestamp > self.most_recent:
                self.most_recent = timestamp
            GUI.invoke_later(self.update_plot)
        else:
            self.incomplete_data[timestamp]['frequencies'] = np.append(
                self.incomplete_data[timestamp]['frequencies'],
                frequencies,
                axis=0)
            self.incomplete_data[timestamp]['amplitudes'] = np.append(
                self.incomplete_data[timestamp]['amplitudes'],
                amplitudes,
                axis=0)

    def update_plot(self):
        most_recent_fft = self.most_recent_complete_data
        if len(most_recent_fft['frequencies']) != 0:
            self.plot_data.set_data('frequency',
                                    most_recent_fft['frequencies'])
            self.plot_data.set_data('amplitude', most_recent_fft['amplitudes'])
            self.plot.value_mapper.range.low = min(
                most_recent_fft['amplitudes'])
            self.plot.value_mapper.range.high = max(
                most_recent_fft['amplitudes'])

    def __init__(self, link):
        super(SpectrumAnalyzerView, self).__init__()
        self.link = link
        self.link.add_callback(self.spectrum_analyzer_state_callback,
                               SBP_MSG_SPECAN)
        self.python_console_cmds = {'spectrum': self}

        # keys are GpsTime
        self.incomplete_data = defaultdict(lambda: {
            'frequencies': np.array([]),
            'amplitudes': np.array([])
        })
        self.most_recent_complete_data = {
            'frequencies': np.array([]),
            'amplitudes': np.array([])
        }
        self.most_recent = None

        self.plot_data = ArrayPlotData()
        self.plot = Plot(self.plot_data, emphasized=True)

        self.plot.title = 'Spectrum Analyzer'
        self.plot.title_color = [0, 0, 0.43]

        self.plot.value_axis.orientation = 'right'
        self.plot.value_axis.title = 'Amplitude (dB)'

        self.plot.index_axis.title = 'Frequency (MHz)'
        self.plot_data.set_data('frequency', [0])
        self.plot_data.set_data('amplitude', [0])
        self.plot.plot(('frequency', 'amplitude'),
                       type='line',
                       name='spectrum')
Пример #20
0
class InterpolatorView(HasTraits):

    # The bounds on which to interpolate.
    bounds = Array(cols=3,
                   dtype=float,
                   desc='spatial bounds for the interpolation '
                   '(xmin, xmax, ymin, ymax, zmin, zmax)')

    # The number of points to interpolate onto.
    num_points = Int(100000,
                     enter_set=True,
                     auto_set=False,
                     desc='number of points on which to interpolate')

    # The particle arrays to interpolate from.
    particle_arrays = List

    # The scalar to interpolate.
    scalar = Str('rho', desc='name of the active scalar to view')

    # Sync'd trait with the scalar lut manager.
    show_legend = Bool(False, desc='if the scalar legend is to be displayed')

    # Enable/disable the interpolation
    visible = Bool(False, desc='if the interpolation is to be displayed')

    # A button to use the set bounds.
    set_bounds = Button('Set Bounds')

    # A button to recompute the bounds.
    recompute_bounds = Button('Recompute Bounds')

    # Private traits. ######################################################

    # The interpolator we are a view for.
    interpolator = Instance(Interpolator)

    # The mlab plot for this particle array.
    plot = Instance(PipelineBase)

    scalar_list = List

    scene = Instance(MlabSceneModel)

    source = Instance(PipelineBase)

    _arrays_changed = Bool(False)

    # View definition ######################################################
    view = View(
        Item(name='visible'),
        Item(name='scalar', editor=EnumEditor(name='scalar_list')),
        Item(name='num_points'),
        Item(name='bounds'),
        Item(name='set_bounds', show_label=False),
        Item(name='recompute_bounds', show_label=False),
        Item(name='show_legend'),
    )

    # Private protocol  ###################################################
    def _change_bounds(self):
        interp = self.interpolator
        if interp is not None:
            interp.set_domain(self.bounds, self.interpolator.shape)
            self._update_plot()

    def _setup_interpolator(self):
        if self.interpolator is None:
            interpolator = Interpolator(self.particle_arrays,
                                        num_points=self.num_points)
            self.bounds = interpolator.bounds
            self.interpolator = interpolator
        else:
            if self._arrays_changed:
                self.interpolator.update_particle_arrays(self.particle_arrays)
                self._arrays_changed = False

    # Trait handlers  #####################################################
    def _particle_arrays_changed(self, pas):
        if len(pas) > 0:
            all_props = reduce(set.union,
                               [set(x.properties.keys()) for x in pas])
        else:
            all_props = set()
        self.scalar_list = list(all_props)
        self._arrays_changed = True
        self._update_plot()

    def _num_points_changed(self, value):
        interp = self.interpolator
        if interp is not None:
            bounds = self.interpolator.bounds
            shape = get_nx_ny_nz(value, bounds)
            interp.set_domain(bounds, shape)
            self._update_plot()

    def _recompute_bounds_fired(self):
        bounds = get_bounding_box(self.particle_arrays)
        self.bounds = bounds
        self._change_bounds()

    def _set_bounds_fired(self):
        self._change_bounds()

    def _bounds_default(self):
        return [0, 1, 0, 1, 0, 1]

    @on_trait_change('scalar, visible')
    def _update_plot(self):
        if self.visible:
            mlab = self.scene.mlab
            self._setup_interpolator()
            interp = self.interpolator
            prop = interp.interpolate(self.scalar)
            if self.source is None:
                src = mlab.pipeline.scalar_field(interp.x, interp.y, interp.z,
                                                 prop)
                self.source = src
            else:
                self.source.mlab_source.reset(x=interp.x,
                                              y=interp.y,
                                              z=interp.z,
                                              scalars=prop)
            src = self.source

            if self.plot is None:
                if interp.dim == 3:
                    plot = mlab.pipeline.scalar_cut_plane(src)
                else:
                    plot = mlab.pipeline.surface(src)
                self.plot = plot
                scm = plot.module_manager.scalar_lut_manager
                scm.set(show_legend=self.show_legend,
                        use_default_name=False,
                        data_name=self.scalar)
                self.sync_trait('show_legend', scm, mutual=True)
            else:
                self.plot.visible = True
                scm = self.plot.module_manager.scalar_lut_manager
                scm.data_name = self.scalar
        else:
            if self.plot is not None:
                self.plot.visible = False
Пример #21
0
class Primitive(HasTraits):
    identifier = Str
    identifier_visible = True
    type_tag = Str
    scene_visible = True

    x = Float
    y = Float
    ox = Float
    oy = Float
    offset_x = Float
    offset_y = Float
    force_layout = False

    state = False
    selected = False

    default_color = Color('red')
    active_color = Color('(0,255,0)')
    selected_color = Color('blue')
    name_color = Color('black')
    text_color = Color('black')

    canvas = Any

    line_width = 1

    name = Str
    name_visible = True
    name_offsetx = 0
    name_offsety = 0

    klass_name = Property

    space = 'data'
    visible = True

    primitives = List
    font = Str('modern 14')

    width = 0
    height = 0
    _initialized = False

    _cached_wh = None
    _cached_xy = None
    _layout_needed = True

    def __init__(self, x, y, *args, **kw):
        self.x = x
        self.y = y
        self.ox = x
        self.oy = y
        super(Primitive, self).__init__(*args, **kw)
        self._initialized = True

    @property
    def gfont(self):
        return str_to_font(self.font)

    @property
    def label(self):
        return '{} {} {}'.format(self.klass_name, self.name, self.identifier)

    def request_layout(self):
        self._cached_wh = None
        self._cached_xy = None
        self._layout_needed = True

    def render(self, gc):

        with gc:
            if self.visible:
                self.set_stroke_color(gc)
                self.set_fill_color(gc)

                gc.set_font(self.gfont)
                gc.set_line_width(self.line_width)

                self._render_(gc)

    def set_stroke_color(self, gc):
        if self.state:
            c = self._convert_color(self.active_color)
        else:
            c = self._convert_color(self.default_color)

        gc.set_stroke_color(c)

    def set_fill_color(self, gc):
        if self.state:
            c = self._convert_color(self.active_color)
        else:
            c = self._convert_color(self.default_color)
        gc.set_fill_color(c)

    def adjust(self, dx, dy):
        args = self.canvas.map_data((dx, dy))
        aargs = self.canvas.map_data((0, 0))
        dx = args[0] - aargs[0]
        dy = args[1] - aargs[1]
        self.x += dx
        self.y += dy

    def get_xy(self, x=None, y=None, clear_layout_needed=True):
        if self._layout_needed or not self._cached_xy:

            if x is None:
                x = self.x
            if y is None:
                y = self.y
            # x, y = self.x, self.y
            offset = 0
            if self.space == 'data':
                # if self.canvas is None:
                # print self
                if self.canvas:
                    x, y = self.canvas.map_screen([(x, y)])[0]
                    # offset = self.canvas.offset
                    offset = 1
                    x += self.offset_x
                    y += self.offset_y

            rx, ry = x + offset, y + offset
            if clear_layout_needed:
                self._layout_needed = False
        else:
            rx, ry = self._cached_xy
        self._cached_xy = rx, ry

        return rx, ry

    def get_wh(self):
        w, h = 0, 0
        if self.canvas:
            if self._layout_needed or not self._cached_wh:
                w, h = self.width, self.height
                # w, h = 20, 20
                if self.space == 'data':
                    (w, h), (ox, oy) = self.canvas.map_screen([(self.width,
                                                                self.height),
                                                               (0, 0)])
                    w, h = w - ox, h - oy
            else:
                w, h = self._cached_wh
            self._cached_wh = w, h

        return w, h

    def map_dimension(self, d, keep_square=False):
        (w, h), (ox, oy) = self.canvas.map_screen([(d, d), (0, 0)])
        w, h = w - ox, h - oy
        if keep_square:
            w = min(w, h)

        return w

    bounds = None

    def set_canvas(self, canvas):
        if canvas:
            self._layout_needed = canvas != self.canvas or self.bounds != canvas.bounds

        if self.force_layout:
            self._layout_needed = True
            self.force_layout = False

        self.canvas = canvas
        if canvas:
            self.bounds = canvas.bounds
        else:
            self.bounds = None

        for pi in self.primitives:
            pi.set_canvas(canvas)

    def set_state(self, state):
        self.state = state

    def set_selected(self, selected):
        self.selected = selected

    def is_in_region(self, x1, x2, y1, y2):
        """

          |------------- x2,y2
          |       T      |
          |              |
        x1,y1------------|    F


        check to see if self.x and self.y within region
        :param x1: float
        :param x2: float
        :param y1: float
        :param y2: float
        :return: bool

        """

        return x1 <= self.x <= x2 and y1 <= self.y <= y2

    # private
    def _render_(self, gc):
        pass

    def _render_name(self, gc, x, y, w, h):
        if self.name and self.name_visible:
            with gc:
                # c = self.text_color if self.text_color else self.default_color
                gc.set_fill_color(self._convert_color(self.name_color))
                txt = str(self.name)
                self._render_textbox(gc, x, y, w, h, txt)

    def _render_textbox(self, gc, x, y, w, h, txt):

        tw, th, _, _ = gc.get_full_text_extent(txt)
        x = x + w / 2. - tw / 2.
        y = y + h / 2. - th / 2.

        self._render_text(gc, txt, x, y)

    def _render_text(self, gc, t, x, y):
        with gc:
            gc.translate_ctm(x, y)
            gc.set_fill_color((0, 0, 0))
            gc.set_text_position(0, 0)
            gc.show_text(t)

    def _convert_color(self, c):
        if not isinstance(c, (list, tuple)):
            c = c.red, c.green, c.blue
        c = map(lambda x: x / 255., c)
        return c

    # handlers
    @on_trait_change('default_color, active_color, x, y')
    def _refresh_canvas(self):
        self.request_redraw()

    def request_redraw(self):
        if self.canvas:
            self.canvas.request_redraw()
Пример #22
0
class ParticleArrayHelper(HasTraits):
    """
    This class manages a particle array and sets up the necessary
    plotting related information for it.
    """

    # The particle array we manage.
    particle_array = Instance(ParticleArray)

    # The name of the particle array.
    name = Str

    # Current time.
    time = Float(0.0)

    # The active scalar to view.
    scalar = Str('rho', desc='name of the active scalar to view')

    # The mlab scalar plot for this particle array.
    plot = Instance(PipelineBase)

    # The mlab vectors plot for this particle array.
    plot_vectors = Instance(PipelineBase)

    # List of available scalars in the particle array.
    scalar_list = List(Str)

    scene = Instance(MlabSceneModel)

    # Sync'd trait with the scalar lut manager.
    show_legend = Bool(False, desc='if the scalar legend is to be displayed')

    # Show all scalars.
    list_all_scalars = Bool(False, desc='if all scalars should be listed')

    # Sync'd trait with the dataset to turn on/off visibility.
    visible = Bool(True, desc='if the particle array is to be displayed')

    # Show the time of the simulation on screen.
    show_time = Bool(False, desc='if the current time is displayed')

    # Edit the scalars.
    edit_scalars = Button('More options ...')

    # Show vectors.
    show_vectors = Bool(False, desc='if vectors should be displayed')

    vectors = Str('u, v, w',
                  enter_set=True,
                  auto_set=False,
                  desc='the vectors to display')

    mask_on_ratio = Int(3, desc='mask one in specified points')

    scale_factor = Float(1.0,
                         desc='scale factor for vectors',
                         enter_set=True,
                         auto_set=False)

    edit_vectors = Button('More options ...')

    # Private attribute to store the Text module.
    _text = Instance(PipelineBase)

    # Extra scalars to show.  These will be added and saved to the data if
    # needed.
    extra_scalars = List(Str)

    # Set to True when the particle array is updated with a new property say.
    updated = Event

    # Private attribute to store old value of visibility in case of empty
    # arrays.
    _old_visible = Bool(True)

    ########################################
    # View related code.
    view = View(
        Item(name='name', show_label=False, editor=TitleEditor()),
        Group(Group(
            Group(
                Item(name='visible'),
                Item(name='show_legend'),
                Item(name='scalar', editor=EnumEditor(name='scalar_list')),
                Item(name='list_all_scalars'),
                Item(name='show_time'),
                columns=2,
            ),
            Item(name='edit_scalars', show_label=False),
            label='Scalars',
        ),
              Group(
                  Item(name='show_vectors'),
                  Item(name='vectors'),
                  Item(name='mask_on_ratio'),
                  Item(name='scale_factor'),
                  Item(name='edit_vectors', show_label=False),
                  label='Vectors',
              ),
              layout='tabbed'))

    # Private protocol ############################################
    def _add_vmag(self, pa):
        if 'vmag' not in pa.properties:
            if 'vmag2' in pa.output_property_arrays:
                vmag = numpy.sqrt(pa.get('vmag2', only_real_particles=False))
            else:
                u, v, w = pa.get('u', 'v', 'w', only_real_particles=False)
                vmag = numpy.sqrt(u**2 + v**2 + w**2)
            pa.add_property(name='vmag', data=vmag)
            if len(pa.output_property_arrays) > 0:
                # We do not call add_output_arrays when the default is empty
                # as if it is empty, all arrays are saved anyway. However,
                # adding just vmag in this case will mean that when the
                # particle array is saved it will only save vmag!  This is
                # not what we want, hence we add vmag *only* if the
                # output_property_arrays is non-zero length.
                pa.add_output_arrays(['vmag'])
            self.updated = True

    def _get_scalar(self, pa, scalar):
        """Return the requested scalar from the given particle array.
        """
        if scalar in self.extra_scalars:
            method_name = '_add_' + scalar
            method = getattr(self, method_name)
            method(pa)
        return pa.get(scalar, only_real_particles=False)

    #  Traits handlers #############################################
    def _edit_scalars_fired(self):
        self.plot.edit_traits()

    def _edit_vectors_fired(self):
        self.plot_vectors.edit_traits()

    def _particle_array_changed(self, old, pa):
        self.name = pa.name

        self._list_all_scalars_changed(self.list_all_scalars)

        # Update the plot.
        x, y, z = pa.get('x', 'y', 'z', only_real_particles=False)
        s = self._get_scalar(pa, self.scalar)
        p = self.plot
        mlab = self.scene.mlab
        empty = len(x) == 0
        if old is None:
            old_empty = True
        else:
            old_x = old.get('x', only_real_particles=False)
            old_empty = len(old_x) == 0
        if p is None and not empty:
            src = mlab.pipeline.scalar_scatter(x, y, z, s)
            p = mlab.pipeline.glyph(src, mode='point', scale_mode='none')
            p.actor.property.point_size = 3
            scm = p.module_manager.scalar_lut_manager
            scm.set(show_legend=self.show_legend,
                    use_default_name=False,
                    data_name=self.scalar)
            self.sync_trait('visible', p, mutual=True)
            self.sync_trait('show_legend', scm, mutual=True)
            # set_arrays(p.mlab_source.m_data, pa)
            self.plot = p
        elif not empty:
            if len(x) == len(p.mlab_source.x):
                p.mlab_source.set(x=x, y=y, z=z, scalars=s)
                if self.plot_vectors:
                    self._vectors_changed(self.vectors)
            else:
                if self.plot_vectors:
                    u, v, w = self._get_vectors_for_plot(self.vectors)
                    p.mlab_source.reset(x=x,
                                        y=y,
                                        z=z,
                                        scalars=s,
                                        u=u,
                                        v=v,
                                        w=w)
                else:
                    p.mlab_source.reset(x=x, y=y, z=z, scalars=s)
                p.mlab_source.update()

        if empty and not old_empty:
            if p is not None:
                src = p.parent.parent
                self._old_visible = src.visible
                src.visible = False
        if old_empty and not empty:
            if p is not None:
                p.parent.parent.visible = self._old_visible
                self._show_vectors_changed(self.show_vectors)

        # Setup the time.
        self._show_time_changed(self.show_time)

    def _scalar_changed(self, value):
        p = self.plot
        if p is not None:
            p.mlab_source.scalars = self._get_scalar(self.particle_array,
                                                     value)
            p.module_manager.scalar_lut_manager.data_name = value

    def _list_all_scalars_changed(self, list_all_scalars):
        pa = self.particle_array
        if list_all_scalars:
            sc_list = pa.properties.keys()
            self.scalar_list = sorted(set(sc_list + self.extra_scalars))
        else:
            if len(pa.output_property_arrays) > 0:
                self.scalar_list = sorted(
                    set(pa.output_property_arrays + self.extra_scalars))
            else:
                sc_list = pa.properties.keys()
                self.scalar_list = sorted(set(sc_list + self.extra_scalars))

    def _show_time_changed(self, value):
        txt = self._text
        mlab = self.scene.mlab
        if value:
            if txt is not None:
                txt.visible = True
            elif self.plot is not None:
                mlab.get_engine().current_object = self.plot
                txt = mlab.text(0.01, 0.01, 'Time = 0.0', width=0.35)
                self._text = txt
                self._time_changed(self.time)
        else:
            if txt is not None:
                txt.visible = False

    def _get_vectors_for_plot(self, vectors):
        pa = self.particle_array
        comps = [x.strip() for x in vectors.split(',')]
        if len(comps) == 3:
            try:
                vec = pa.get(*comps, only_real_particles=False)
            except AttributeError:
                return None
            else:
                return vec

    def _vectors_changed(self, value):
        vec = self._get_vectors_for_plot(value)
        if vec is not None:
            self.plot.mlab_source.set(vectors=numpy.c_[vec[0], vec[1], vec[2]])

    def _show_vectors_changed(self, value):
        pv = self.plot_vectors
        if pv is not None:
            pv.visible = value
        elif self.plot is not None and value:
            self._vectors_changed(self.vectors)
            pv = self.scene.mlab.pipeline.vectors(
                self.plot.mlab_source.m_data,
                mask_points=self.mask_on_ratio,
                scale_factor=self.scale_factor)
            self.plot_vectors = pv

    def _mask_on_ratio_changed(self, value):
        pv = self.plot_vectors
        if pv is not None:
            pv.glyph.mask_points.on_ratio = value

    def _scale_factor_changed(self, value):
        pv = self.plot_vectors
        if pv is not None:
            pv.glyph.glyph.scale_factor = value

    def _time_changed(self, value):
        txt = self._text
        if txt is not None:
            txt.text = 'Time = %.3e' % (value)

    def _extra_scalars_default(self):
        return ['vmag']
Пример #23
0
class HighlightTool(BaseTool):
    """ A tool that enables the user to select a plot to be highlighted on the
    graph by clicking on it.
    """

    # The name of the data source metadata which controls selections.
    metadata_name = Str('selections')

    # The mouse button that initiates the selection.
    drag_button = Enum("left", "right")

    # Threshold distance for hit-testing.
    threshold = Float(20.0)

    #---------------------------------------------------------------------
    # Inherited BaseTool traits
    #---------------------------------------------------------------------

    # This tool is not drawn. Overrides BaseTool.
    draw_mode = "none"

    # This tool is not visible. Overrides BaseTool.
    visible = False

    def normal_left_down(self, event):
        """ Handles the left mouse button being pressed.

        If the left mouse button initiates the selection, this method does so.
        """
        if self.drag_button == "left":
            self._highlight(event)
        return

    def normal_right_down(self, event):
        """ Handles the right mouse button being pressed.

        If the right mouse button initiates the selection, this method does so.
        """
        if self.drag_button == "right":
            self._highlight(event)
        return

    def _highlight(self, event):
        if isinstance(self.component, BasePlotContainer):
            event.offset_xy(self.component.x, self.component.y)
            closest_plot = self._find_curve(self.component.components, event)
            if closest_plot:
                index = closest_plot.index
                index.metadata[self.metadata_name] = ones(len(
                    index.get_data()),
                                                          dtype=bool)
                closest_plot.request_redraw()
            else:
                # If we are attached to a plot container, then we can deselect
                # all of the plots in the container
                for p in self.component.components:
                    if self.metadata_name in p.index.metadata:
                        del p.index.metadata[self.metadata_name]
                        p.request_redraw()
            event.pop()

        elif hasattr(self.component, "hittest"):
            hit_point = self.component.hittest((event.x, event.y),
                                               self.threshold)
            index = self.component.index
            if hit_point is not None:
                index.metadata[self.metadata_name] = ones(len(
                    index.get_data()),
                                                          dtype=bool)
                self.component.request_redraw()
            elif self.metadata_name in index.metadata:
                del index.metadata[self.metadata_name]
                self.component.request_redraw()

        event.handled = True
        return

    def _find_curve(self, plots, event):
        # need to change to use distance - not just return first plot within threshold
        for p in plots:
            if hasattr(p, "hittest"):
                cpoint = p.hittest((event.x, event.y), self.threshold)
                if cpoint:
                    return p
        return None
Пример #24
0
class MayaviViewer(HasTraits):
    """
    This class represents a Mayavi based viewer for the particles.  They
    are queried from a running solver.
    """

    particle_arrays = List(Instance(ParticleArrayHelper), [])
    pa_names = List(Str, [])

    interpolator = Instance(InterpolatorView)

    # The default scalar to load up when running the viewer.
    scalar = Str("rho")

    scene = Instance(MlabSceneModel, ())

    ########################################
    # Traits to pull data from a live solver.
    live_mode = Bool(False,
                     desc='if data is obtained from a running solver '
                     'or from saved files')

    shell = Button('Launch Python Shell')
    host = Str('localhost', desc='machine to connect to')
    port = Int(8800, desc='port to use to connect to solver')
    authkey = Password('pysph', desc='authorization key')
    host_changed = Bool(True)
    client = Instance(MultiprocessingClient)
    controller = Property(depends_on='live_mode, host_changed')

    ########################################
    # Traits to view saved solver output.
    files = List(Str, [])
    directory = Directory()
    current_file = Str('', desc='the file being viewed currently')
    update_files = Button('Refresh')
    file_count = Range(low='_low',
                       high='_n_files',
                       value=0,
                       desc='the file counter')
    play = Bool(False, desc='if all files are played automatically')
    play_delay = Float(0.2, desc='the delay between loading files')
    loop = Bool(False, desc='if the animation is looped')
    # This is len(files) - 1.
    _n_files = Int(0)
    _low = Int(0)

    ########################################
    # Timer traits.
    timer = Instance(Timer)
    interval = Range(0.5,
                     20.0,
                     2.0,
                     desc='frequency in seconds with which plot is updated')

    ########################################
    # Solver info/control.
    current_time = Float(0.0, desc='the current time in the simulation')
    time_step = Float(0.0, desc='the time-step of the solver')
    iteration = Int(0, desc='the current iteration number')
    pause_solver = Bool(False, desc='if the solver should be paused')

    ########################################
    # Movie.
    record = Bool(False, desc='if PNG files are to be saved for animation')
    frame_interval = Range(1, 100, 5, desc='the interval between screenshots')
    movie_directory = Str
    # internal counters.
    _count = Int(0)
    _frame_count = Int(0)
    _last_time = Float
    _solver_data = Any
    _file_name = Str
    _particle_array_updated = Bool

    ########################################
    # The layout of the dialog created
    view = View(HSplit(
        Group(
            Group(Group(
                Item(name='directory'),
                Item(name='current_file'),
                Item(name='file_count'),
                HGroup(Item(name='play'),
                       Item(name='play_delay', label='Delay', resizable=True),
                       Item(name='loop'),
                       Item(name='update_files', show_label=False),
                       padding=0),
                padding=0,
                label='Saved Data',
                selected=True,
                enabled_when='not live_mode',
            ),
                  Group(
                      Item(name='live_mode'),
                      Group(
                          Item(name='host'),
                          Item(name='port'),
                          Item(name='authkey'),
                          enabled_when='live_mode',
                      ),
                      label='Connection',
                  ),
                  layout='tabbed'),
            Group(
                Group(
                    Item(name='current_time'),
                    Item(name='time_step'),
                    Item(name='iteration'),
                    Item(name='pause_solver', enabled_when='live_mode'),
                    Item(name='interval', enabled_when='not live_mode'),
                    label='Solver',
                ),
                Group(
                    Item(name='record'),
                    Item(name='frame_interval'),
                    Item(name='movie_directory'),
                    label='Movie',
                ),
                layout='tabbed',
            ),
            Group(Item(name='particle_arrays',
                       style='custom',
                       show_label=False,
                       editor=ListEditor(use_notebook=True,
                                         deletable=False,
                                         page_name='.name')),
                  Item(name='interpolator', style='custom', show_label=False),
                  layout='tabbed'),
            Item(name='shell', show_label=False),
        ),
        Group(
            Item('scene',
                 editor=SceneEditor(scene_class=MayaviScene),
                 height=400,
                 width=600,
                 show_label=False), )),
                resizable=True,
                title='PySPH Particle Viewer',
                height=640,
                width=1024,
                handler=ViewerHandler)

    ######################################################################
    # `MayaviViewer` interface.
    ######################################################################
    def on_close(self):
        self._handle_particle_array_updates()

    @on_trait_change('scene:activated')
    def start_timer(self):
        if not self.live_mode:
            # No need for the timer if we are rendering files.
            return

        # Just accessing the timer will start it.
        t = self.timer
        if not t.IsRunning():
            t.Start(int(self.interval * 1000))

    @on_trait_change('scene:activated')
    def update_plot(self):

        # No need to do this if files are being used.
        if not self.live_mode:
            return

        # do not update if solver is paused
        if self.pause_solver:
            return

        if self.client is None:
            self.host_changed = True

        controller = self.controller
        if controller is None:
            return

        self.current_time = t = controller.get_t()
        self.time_step = controller.get_dt()
        self.iteration = controller.get_count()

        arrays = []
        for idx, name in enumerate(self.pa_names):
            pa = controller.get_named_particle_array(name)
            arrays.append(pa)
            pah = self.particle_arrays[idx]
            pah.set(particle_array=pa, time=t)

        self.interpolator.particle_arrays = arrays

        if self.record:
            self._do_snap()

    def run_script(self, path):
        """Execute a script in the namespace of the viewer.
        """
        with open(path) as fp:
            data = fp.read()
            ns = self._get_shell_namespace()
            exec(compile(data, path, 'exec'), ns)

    ######################################################################
    # Private interface.
    ######################################################################
    def _do_snap(self):
        """Generate the animation."""
        p_arrays = self.particle_arrays
        if len(p_arrays) == 0:
            return
        if self.current_time == self._last_time:
            return

        if len(self.movie_directory) == 0:
            controller = self.controller
            output_dir = controller.get_output_directory()
            movie_dir = os.path.join(output_dir, 'movie')
            self.movie_directory = movie_dir
        else:
            movie_dir = self.movie_directory
        if not os.path.exists(movie_dir):
            os.mkdir(movie_dir)

        interval = self.frame_interval
        count = self._count
        if count % interval == 0:
            fname = 'frame%06d.png' % (self._frame_count)
            p_arrays[0].scene.save_png(os.path.join(movie_dir, fname))
            self._frame_count += 1
            self._last_time = self.current_time
        self._count += 1

    @on_trait_change('host,port,authkey')
    def _mark_reconnect(self):
        if self.live_mode:
            self.host_changed = True

    @cached_property
    def _get_controller(self):
        ''' get the controller, also sets the iteration count '''
        if not self.live_mode:
            return None

        reconnect = self.host_changed
        if not reconnect:
            try:
                c = self.client.controller
            except Exception as e:
                logger.info('Error: no connection or connection closed: '
                            'reconnecting: %s' % e)
                reconnect = True
                self.client = None
            else:
                try:
                    self.client.controller.get_count()
                except IOError:
                    self.client = None
                    reconnect = True

        if reconnect:
            self.host_changed = False
            try:
                if MultiprocessingClient.is_available((self.host, self.port)):
                    self.client = MultiprocessingClient(address=(self.host,
                                                                 self.port),
                                                        authkey=self.authkey)
                else:
                    logger.info('Could not connect: Multiprocessing Interface'
                                ' not available on %s:%s' %
                                (self.host, self.port))
                    return None
            except Exception as e:
                logger.info('Could not connect: check if solver is '
                            'running:%s' % e)
                return None
            c = self.client.controller
            self.iteration = c.get_count()

        if self.client is None:
            return None
        else:
            return self.client.controller

    def _client_changed(self, old, new):
        if not self.live_mode:
            return

        self._clear()
        if new is None:
            return
        else:
            self.pa_names = self.client.controller.get_particle_array_names()

        self.particle_arrays = [
            self._make_particle_array_helper(self.scene, x)
            for x in self.pa_names
        ]
        self.interpolator = InterpolatorView(scene=self.scene)
        # Turn on the legend for the first particle array.
        if len(self.particle_arrays) > 0:
            self.particle_arrays[0].set(show_legend=True, show_time=True)

    def _timer_event(self):
        # catch all Exceptions else timer will stop
        try:
            self.update_plot()
        except Exception as e:
            logger.info('Exception: %s caught in timer_event' % e)

    def _interval_changed(self, value):
        t = self.timer
        if t is None:
            return
        if t.IsRunning():
            t.Stop()
            t.Start(int(value * 1000))

    def _timer_default(self):
        return Timer(int(self.interval * 1000), self._timer_event)

    def _pause_solver_changed(self, value):
        if self.live_mode:
            c = self.controller
            if c is None:
                return
            if value:
                c.pause_on_next()
            else:
                c.cont()

    def _record_changed(self, value):
        if value:
            self._do_snap()

    def _files_changed(self, value):
        if len(value) == 0:
            return
        else:
            d = os.path.dirname(os.path.abspath(value[0]))
            self.movie_directory = os.path.join(d, 'movie')
            self.set(directory=d, trait_change_notify=False)
        self._n_files = len(value) - 1
        self._frame_count = 0
        self._count = 0
        self.frame_interval = 1
        fc = self.file_count
        self.file_count = 0
        if fc == 0:
            # Force an update when our original file count is 0.
            self._file_count_changed(fc)
        t = self.timer
        if not self.live_mode:
            if t.IsRunning():
                t.Stop()
        else:
            if not t.IsRunning():
                t.Stop()
                t.Start(self.interval * 1000)

    def _file_count_changed(self, value):
        # Save out any updates for the previous file if needed.
        self._handle_particle_array_updates()
        # Load the new file.
        fname = self.files[value]
        self._file_name = fname
        self.current_file = os.path.basename(fname)
        # Code to read the file, create particle array and setup the helper.
        data = load(fname)
        solver_data = data["solver_data"]
        arrays = data["arrays"]
        self._solver_data = solver_data
        self.current_time = t = float(solver_data['t'])
        self.time_step = float(solver_data['dt'])
        self.iteration = int(solver_data['count'])
        names = list(arrays.keys())
        pa_names = self.pa_names

        if len(pa_names) == 0:
            self.interpolator = InterpolatorView(scene=self.scene)
            self.pa_names = names
            pas = []
            for name in names:
                pa = arrays[name]
                pah = self._make_particle_array_helper(self.scene, name)
                # Must set this after setting the scene.
                pah.set(particle_array=pa, time=t)
                pas.append(pah)
            self.particle_arrays = pas
        else:
            for idx, name in enumerate(pa_names):
                pa = arrays[name]
                pah = self.particle_arrays[idx]
                pah.set(particle_array=pa, time=t)

        self.interpolator.particle_arrays = list(arrays.values())

        if self.record:
            self._do_snap()

    def _loop_changed(self, value):
        if value and self.play:
            self._play_changed(self.play)

    def _play_changed(self, value):
        t = self.timer
        if value:
            t.Stop()
            t.callable = self._play_event
            t.Start(1000 * self.play_delay)
        else:
            t.Stop()
            t.callable = self._timer_event

    def _clear(self):
        self.pa_names = []
        self.scene.mayavi_scene.children[:] = []

    def _play_event(self):
        nf = self._n_files
        pc = self.file_count
        pc += 1
        if pc > nf:
            if self.loop:
                pc = 0
            else:
                self.timer.Stop()
                pc = nf
        self.file_count = pc
        self._handle_particle_array_updates()

    def _play_delay_changed(self):
        if self.play:
            self._play_changed(self.play)

    def _scalar_changed(self, value):
        for pa in self.particle_arrays:
            pa.scalar = value

    def _update_files_fired(self):
        fc = self.file_count
        files = glob_files(self.files[fc])
        sort_file_list(files)
        self.files = files
        self.file_count = fc
        if self.play:
            self._play_changed(self.play)

    def _shell_fired(self):
        ns = self._get_shell_namespace()
        obj = PythonShellView(ns=ns)
        obj.edit_traits()

    def _get_shell_namespace(self):
        return dict(viewer=self,
                    particle_arrays=self.particle_arrays,
                    interpolator=self.interpolator,
                    scene=self.scene,
                    mlab=self.scene.mlab)

    def _directory_changed(self, d):
        ext = os.path.splitext(self.files[-1])[1]
        files = glob.glob(os.path.join(d, '*' + ext))
        if len(files) > 0:
            self._clear()
            sort_file_list(files)
            self.files = files
            self.file_count = min(self.file_count, len(files))
        else:
            pass

    def _live_mode_changed(self, value):
        if value:
            self._file_name = ''
            self.client = None
            self._clear()
            self._mark_reconnect()
            self.start_timer()
        else:
            self.client = None
            self._clear()
            self.timer.Stop()

    def _particle_array_helper_updated(self, value):
        self._particle_array_updated = True

    def _handle_particle_array_updates(self):
        # Called when the particle array helper fires an updated event.
        if self._particle_array_updated and self._file_name:
            sd = self._solver_data
            arrays = [x.particle_array for x in self.particle_arrays]
            detailed = self._requires_detailed_output(arrays)
            dump(self._file_name,
                 arrays,
                 sd,
                 detailed_output=detailed,
                 only_real=False)
            self._particle_array_updated = False

    def _requires_detailed_output(self, arrays):
        detailed = False
        for pa in arrays:
            props = set(pa.properties.keys())
            output = set(pa.output_property_arrays)
            diff = props - output
            for prop in diff:
                array = pa.get(prop)
                if (array.max() - array.min()) > 0:
                    detailed = True
                    break
            if detailed:
                break
        return detailed

    def _make_particle_array_helper(self, scene, name):
        pah = ParticleArrayHelper(scene=scene, name=name, scalar=self.scalar)
        pah.on_trait_change(self._particle_array_helper_updated, 'updated')
        return pah
Пример #25
0
class FooModel(HasTraits):
    my_str = Str("hallo")
Пример #26
0
class OpenCFile(Action):
    """ An action that opens a data file depending on the supported
    extensions.  """

    name = "Open CFF File"
    description = "Open the File Dialog where you can select a .cff or meta.cml file"
    tooltip = "Open a CFF file"
    path = Str("MenuBar/File/LoadDataMenu")
    image = ImageResource("cff-open.png", search_path=[IMAGE_PATH])

    # Is the action enabled?
    enabled = Bool(True)

    # Is the action visible?
    visible = Bool(True)

    ###########################################################################
    # 'Action' interface.
    ###########################################################################

    def perform(self, event, cfile=None):
        """ Performs the action. """

        logger.info('Performing open connectome file action')

        # helper variable to use this function not only in the menubar
        exec_as_funct = True

        if cfile is None:
            # get the instance of the current CFile
            # with the help of the Service Registry
            cfile = self.window.application.get_service(
                'cviewer.plugins.cff2.cfile.CFile')
            exec_as_funct = False

        wildcard = "Connectome Markup File v2.0 (meta.cml)|meta.cml|" \
                    "Connectome File Format v2.0 (*.cff)|*.cff|" \
                    "All files (*.*)|*.*"
        dlg = FileDialog(wildcard=wildcard,title="Choose a Connectome File",\
                         resizeable=False, \
                         default_directory=preference_manager.cviewerui.cffpath,)

        if dlg.open() == OK:

            if not os.path.isfile(dlg.path):
                logger.error("File '%s' does not exist!" % dlg.path)
                return

            # if file exists and has .cff ending
            if os.path.exists(
                    dlg.paths[0]) and (dlg.paths[0]).endswith('.cff'):

                # close the cfile if one is currently loaded
                cfile.close_cfile()

                # load cfile data
                cfile.load_cfile(dlg.paths[0])

                self.window.status_bar_manager.message = ''
            elif os.path.exists(
                    dlg.paths[0]) and (dlg.paths[0]).endswith('meta.cml'):
                cfile.close_cfile()
                cfile.load_cfile(dlg.paths[0], ismetacml=True)

            else:
                logger.info('Could not load file: ' + dlg.paths)
Пример #27
0
class Person(HasTraits):
    name = Str()
    age = Int()

    def __repr__(self):
        return "Person(name={self.name!r}, age={self.age!r})".format(self=self)
Пример #28
0
class OpenFile(Action):
    name = "Open file"
    description = "Open the File Dialog where you can select a neuroimaging file"
    tooltip = "Open file"
    path = Str("MenuBar/File/LoadDataMenu")
    image = ImageResource("cff-open.png", search_path=[IMAGE_PATH])

    # Is the action enabled?
    enabled = Bool(True)

    # Is the action visible?
    visible = Bool(True)

    ###########################################################################
    # 'Action' interface.
    ###########################################################################
    def perform(self, event, cfile=None):
        """ Performs the action. """

        logger.info('Performing open connectome file action')

        # helper variable to use this function not only in the menubar
        exec_as_funct = True

        if cfile is None:
            # get the instance of the current CFile
            # with the help of the Service Registry
            cfile = self.window.application.get_service(
                'cviewer.plugins.cff2.cfile.CFile')
            exec_as_funct = False

        wildcard = "All files (*.*)|*.*" \
                   "Nifti-1 (*.nii.gz)|*.nii.gz|" \
                   "Gifti (*.gii)|*.gii|" \
                   "TrackVis tracks (*.trk)|*.trk|" \
                   "Network Pickle (*.gpickle)|*.gpickle|" \
                   "Network GraphML (*.graphml)|*.graphml|" \
                   "Numpy Data (*.npy)|*.npy|" \
                   "Pickle Data (*.pkl)|*.pkl|" \
                   "Text Data (*.txt)|*.txt|" \
                   "CSV Data (*.csv)|*.csv|"

        dlg = FileDialog(wildcard=wildcard,title="Choose a file",\
                         resizeable=False, \
                         default_directory=preference_manager.cviewerui.cffpath,)

        if dlg.open() == OK:

            if not os.path.isfile(dlg.path):
                logger.error("File '%s' does not exist!" % dlg.path)
                return

            logger.info('Read file: %s' % dlg.path)
            fname = os.path.split(dlg.paths[0])[-1]

            if os.path.exists(dlg.paths[0]) and (fname.endswith('.nii.gz')
                                                 or fname.endswith('.nii')):
                cvol = cfflib.CVolume.create_from_nifti(
                    name=fname, nii_filename=dlg.paths[0])
                cfile.obj.add_connectome_volume(cvol)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.gii'):
                csurf = cfflib.CSurface.create_from_gifti(
                    name=fname, gii_filename=dlg.paths[0])
                cfile.obj.add_connectome_surface(csurf)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.trk'):
                ctrk = cfflib.CTrack(name=fname, src=dlg.paths[0])
                cfile.obj.add_connectome_track(ctrk)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.gpickle'):
                ctrk = cfflib.CNetwork(name=fname,
                                       src=dlg.paths[0],
                                       fileformat="NXGPickle")
                cfile.obj.add_connectome_network(ctrk)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.graphml'):
                ctrk = cfflib.CNetwork.create_from_graphml(
                    name=fname, ml_filename=dlg.paths[0])
                cfile.obj.add_connectome_network(ctrk)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.npy'):
                cdat = cfflib.CData(name=fname,
                                    src=dlg.paths[0],
                                    fileformat='NumPy')
                cfile.obj.add_connectome_data(cdat)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.csv'):
                cdat = cfflib.CData(name=fname,
                                    src=dlg.paths[0],
                                    fileformat='CSV')
                cfile.obj.add_connectome_data(cdat)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.txt'):
                cdat = cfflib.CData(name=fname,
                                    src=dlg.paths[0],
                                    fileformat='TXT')
                cfile.obj.add_connectome_data(cdat)

            elif os.path.exists(dlg.paths[0]) and fname.endswith('.pkl'):
                cdat = cfflib.CData(name=fname,
                                    src=dlg.paths[0],
                                    fileformat='Pickle')
                cfile.obj.add_connectome_data(cdat)

            else:
                logger.info('Could not load file: ' + dlg.paths)

            cfile.update_children()
Пример #29
0
class QuadOp(HasStrictTraits):
    """Apply a quadrant gate to a cytometry experiment.
    
    Creates a new metadata column named `name`, with values `name_1`,
    `name_2`, `name_3`, `name_4` ordered CLOCKWISE from upper-left.
    
    Attributes
    ----------
    name : Str
        The operation name.  Used to name the new metadata field in the
        experiment that's created by apply()
        
    xchannel : Str
        The name of the first channel to apply the range gate.
        
    xthreshold : Float
        The threshold in the xchannel to gate with.

    ychannel : Str
        The name of the secon channel to apply the range gate.
        
    ythreshold : Float
        The threshold in ychannel to gate with.
        
    Examples
    --------
    
    >>> quad = flow.QuadOp(name = "Quad",
    ...                    xchannel = "V2-A",
    ...                    xthreshold = 0.5,
    ...                    ychannel = "Y2-A",
    ...                    ythreshold = 0.4)
    >>> ex3 = quad.apply(ex2)

    Alternately, in an IPython notebook with `%matplotlib notebook`
    
    >>> qv = quad.default_view()
    >>> qv.plot(ex2)
    >>> ### draw a box on the plot in the notebook ### 
    """

    # traits
    id = Constant('edu.mit.synbio.cytoflow.operations.quad')
    friendly_id = Constant("Quadrant Gate")

    name = CStr()

    xchannel = Str()
    xthreshold = CFloat()

    ychannel = Str()
    ythreshold = CFloat()

    def apply(self, experiment):
        """Applies the threshold to an experiment.
        
        Parameters
        ----------
        experiment : Experiment
            the old_experiment to which this op is applied
            
        Returns
        -------
            a new experiment, the same as old_experiment but with a new
            column the same as the operation name.  The new column is of type
            Enum, with values `name_1`, `name_2`, `name_3`, and `name_4`, 
            applied to events CLOCKWISE from upper-left.
            
            TODO - this is semantically weak sauce.  Add some (generalizable??)
            way to rename these populations?  It's an Enum; should be pretty
            easy.
            
        """

        if experiment is None:
            raise util.CytoflowOpError("No experiment specified")

        # make sure name got set!
        if not self.name:
            raise util.CytoflowOpError("You have to set the gate's name "
                                       "before applying it!")

        # make sure old_experiment doesn't already have a column named self.name
        if (self.name in experiment.data.columns):
            raise util.CytoflowOpError(
                "Experiment already contains a column {0}".format(self.name))

        if not self.xchannel or not self.ychannel:
            raise util.CytoflowOpError("Must specify xchannel and ychannel")

        if not self.xchannel in experiment.channels:
            raise util.CytoflowOpError("xchannel isn't in the experiment")

        if not self.ychannel in experiment.channels:
            raise util.CytoflowOpError("ychannel isn't in the experiment")

        if not self.xthreshold:
            raise util.CytoflowOpError('xthreshold must be set!')

        if not self.ythreshold:
            raise util.CytoflowOpError('ythreshold must be set!')

        gate = pd.Series([None] * len(experiment))

        # perhaps there's some more pythonic way to do this?

        # these gate names match FACSDiva.  They are ARBITRARY.

        # lower-left
        ll = np.logical_and(experiment[self.xchannel] < self.xthreshold,
                            experiment[self.ychannel] < self.ythreshold)
        gate.loc[ll] = self.name + '_3'

        # upper-left
        ul = np.logical_and(experiment[self.xchannel] < self.xthreshold,
                            experiment[self.ychannel] > self.ythreshold)
        gate.loc[ul] = self.name + '_1'

        # upper-right
        ur = np.logical_and(experiment[self.xchannel] > self.xthreshold,
                            experiment[self.ychannel] > self.ythreshold)
        gate.loc[ur] = self.name + '_2'

        # lower-right
        lr = np.logical_and(experiment[self.xchannel] > self.xthreshold,
                            experiment[self.ychannel] < self.ythreshold)
        gate.loc[lr] = self.name + '_4'

        new_experiment = experiment.clone()
        new_experiment.add_condition(self.name, "category", gate)
        new_experiment.history.append(
            self.clone_traits(transient=lambda t: True))
        return new_experiment

    def default_view(self, **kwargs):
        return QuadSelection(op=self, **kwargs)
Пример #30
0
class Tree(Widget):
    """ A tree control with a model/ui architecture. """

    # The default tree style.
    STYLE = wx.TR_EDIT_LABELS | wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN

    #### 'Tree' interface #####################################################

    # The tree's filters (empty if no filtering is required).
    filters = List(Filter)

    # Mode for lines connecting tree nodes which emphasize hierarchy:
    # 'appearance' - only on when lines look good,
    # 'on' - always on, 'off' - always off
    # NOTE: on and off are ignored in favor of show_lines for now
    lines_mode = Enum('appearance', 'on', 'off')

    # The model that provides the data for the tree.
    model = Instance(TreeModel, ())

    # The root of the tree (this is for convenience, it just delegates to
    # the tree's model).
    root = Property(Any)

    # The objects currently selected in the tree.
    selection = List

    # Selection mode.
    selection_mode = Enum('single', 'extended')

    # Should an image be shown for each node?
    show_images = Bool(True)

    # Should lines be drawn between levels in the tree.
    show_lines = Bool(True)

    # Should the root of the tree be shown?
    show_root = Bool(True)

    # The tree's sorter (None if no sorting is required).
    sorter = Instance(Sorter)

    #### Events ####

    # A right-click occurred on the control (not a node!).
    control_right_clicked = Event#(Point)

    # A key was pressed while the tree has focus.
    key_pressed = Event(KeyPressedEvent)

    # A node has been activated (ie. double-clicked).
    node_activated = Event#(Any)

    # A drag operation was started on a node.
    node_begin_drag = Event#(Any)

    # A (non-leaf) node has been collapsed.
    node_collapsed = Event#(Any)

    # A (non-leaf) node has been expanded.
    node_expanded = Event#(Any)

    # A left-click occurred on a node.
    #
    # Tuple(node, point).
    node_left_clicked = Event#(Tuple)

    # A right-click occurred on a node.
    #
    # Tuple(node, point)
    node_right_clicked = Event#(Tuple)

    #### Private interface ####################################################

    # A name to distinguish the tree for debugging!
    #
    # fixme: This turns out to be kinda useful... Should 'Widget' have a name
    # trait?
    _name = Str('Anonymous tree')

    # An optional callback to detect the end of a label edit.  This is
    # useful because the callback will be invoked even if the node label was
    # not actually changed.
    _label_edit_callback = Trait(None, Callable, None)

    # Flag for allowing selection events to be ignored
    _ignore_selection_events = Bool(False)

    ###########################################################################
    # 'object' interface.
    ###########################################################################

    def __init__(self, parent, image_size=(16, 16), **traits):
        """ Creates a new tree.

        'parent' is the toolkit-specific control that is the tree's parent.

        'image_size' is a tuple in the form (int width, int height) that
        specifies the size of the images (if required) displayed in the tree.

        """

        # Base class constructors.
        super(Tree, self).__init__(**traits)

        # Get our wx Id.
        wxid = wx.NewId()

        # Create the toolkit-specific control.
        self.control = tree = _Tree(self, parent, wxid,style=self._get_style())

        # Wire up the wx tree events.
        wx.EVT_CHAR(tree, self._on_char)
        wx.EVT_LEFT_DOWN(tree, self._on_left_down)
        # fixme: This is not technically correct as context menus etc should
        # appear on a right up (or right click).  Unfortunately,  if we
        # change this to 'EVT_RIGHT_UP' wx does not fire the event unless the
        # right mouse button is double clicked 8^()  Sad,  but true!
        wx.EVT_RIGHT_DOWN(tree, self._on_right_down)
        # fixme: This is not technically correct as we would really like to use
        # 'EVT_TREE_ITEM_ACTIVATED'. Unfortunately, (in 2.6 at least), it
        # throws an exception when the 'Enter' key is pressed as the wx tree
        # item Id in the event seems to be invalid. It also seems to cause
        # any child frames that my be created in response to the event to
        # appear *behind* the parent window, which is, errrr, not great ;^)
        wx.EVT_LEFT_DCLICK(tree, self._on_tree_item_activated)
        #wx.EVT_TREE_ITEM_ACTIVATED(tree, wxid, self._on_tree_item_activated)
        wx.EVT_TREE_ITEM_COLLAPSING(tree, wxid, self._on_tree_item_collapsing)
        wx.EVT_TREE_ITEM_COLLAPSED(tree, wxid, self._on_tree_item_collapsed)
        wx.EVT_TREE_ITEM_EXPANDING(tree, wxid, self._on_tree_item_expanding)
        wx.EVT_TREE_ITEM_EXPANDED(tree, wxid, self._on_tree_item_expanded)
        wx.EVT_TREE_BEGIN_LABEL_EDIT(tree, wxid,self._on_tree_begin_label_edit)
        wx.EVT_TREE_END_LABEL_EDIT(tree, wxid, self._on_tree_end_label_edit)
        wx.EVT_TREE_BEGIN_DRAG(tree, wxid, self._on_tree_begin_drag)
        wx.EVT_TREE_SEL_CHANGED(tree, wxid, self._on_tree_sel_changed)
        wx.EVT_TREE_DELETE_ITEM(tree, wxid, self._on_tree_delete_item)

        # Enable the tree as a drag and drop target.
        self.control.SetDropTarget(PythonDropTarget(self))

        # The image list is a wxPython-ism that caches all images used in the
        # control.
        self._image_list = ImageList(image_size[0], image_size[1])
        if self.show_images:
            tree.AssignImageList(self._image_list)

        # Mapping from node to wx tree item Ids.
        self._node_to_id_map = {}

        # Add the root node.
        if self.root is not None:
            self._add_root_node(self.root)

        # Listen for changes to the model.
        self._add_model_listeners(self.model)

        return

    ###########################################################################
    # 'Tree' interface.
    ###########################################################################

    #### Properties ###########################################################

    def _get_root(self):
        """ Returns the root node of the tree. """

        return self.model.root

    def _set_root(self, root):
        """ Sets the root node of the tree. """

        self.model.root = root

        return

    #### Methods ##############################################################

    def collapse(self, node):
        """ Collapses the specified node. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self.control.Collapse(wxid)

        return

    def edit_label(self, node, callback=None):
        """ Edits the label of the specified node.

        If a callback is specified it will be called when the label edit
        completes WHETHER OR NOT the label was actually changed.

        The callback must take exactly 3 arguments:- (tree, node, label)

        """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self._label_edit_callback = callback
            self.control.EditLabel(wxid)

        return

    def expand(self, node):
        """ Expands the specified node. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self.control.Expand(wxid)

        return

    def expand_all(self):
        """ Expands every node in the tree. """

        if self.show_root:
            self._expand_item(self._get_wxid(self.root))

        else:
            for child in self._get_children(self.root):
                self._expand_item(self._get_wxid(child))

        return

    def get_parent(self, node):
        """ Returns the parent of a node.

        This will only work iff the node has been displayed in the tree.  If it
        hasn't then None is returned.

        """

        # Has the node actually appeared in the tree yet?
        wxid = self._get_wxid(node)
        if wxid is not None:
            pid = self.control.GetItemParent(wxid)

            # The item data is a tuple.  The first element indicates whether or
            # not we have already populated the item with its children.  The
            # second element is the actual item data.
            populated, parent = self.control.GetPyData(pid)

        else:
            parent = None

        return parent

    def is_expanded(self, node):
        """ Returns True if the node is expanded, otherwise False. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            # If the root node is hidden then it is always expanded!
            if node is self.root and not self.show_root:
                is_expanded = True

            else:
                is_expanded = self.control.IsExpanded(wxid)

        else:
            is_expanded = False

        return is_expanded

    def is_selected(self, node):
        """ Returns True if the node is selected, otherwise False. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            is_selected = self.control.IsSelected(wxid)

        else:
            is_selected = False

        return is_selected

    def refresh(self, node):
        """ Refresh the tree starting from the specified node.

        Call this when the structure of the content has changed DRAMATICALLY.

        """

        # Has the node actually appeared in the tree yet?
        pid = self._get_wxid(node)
        if pid is not None:
            # Delete all of the node's children and re-add them.
            self.control.DeleteChildren(pid)
            self.control.SetPyData(pid, (False, node))

            # Does the node have any children?
            has_children = self._has_children(node)
            self.control.SetItemHasChildren(pid, has_children)

            # fixme: At least on Windows, wx does not fire an expanding
            # event for a hidden root node, so we have to populate the node
            # manually.
            if node is self.root and not self.show_root:
                # Add the child nodes.
                for child in self._get_children(node):
                    self._add_node(pid, child)

            else:
                # Expand it.
                if self.control.IsExpanded(pid):
                    self.control.Collapse(pid)

                self.control.Expand(pid)

        return

    def select(self, node):
        """ Selects the specified node. """

        wxid = self._get_wxid(node)
        if wxid is not None:
            self.control.SelectItem(wxid)

        return

    def set_selection(self, list):
        """ Selects the specified list of nodes. """
        logger.debug('Setting selection to [%s] within Tree [%s]', list, self)

        # Update the control to reflect the target list by unselecting
        # everything and then selecting each item in the list.  During this
        # process, we want to avoid changing our own selection.
        self._ignore_selection_events = True
        self.control.UnselectAll()
        for node in list:
            try:
                self.select(node)
            except:
                logger.exception('Unable to select node [%s]', node)

        self._ignore_selection_events = False

        # Update our selection to reflect the final selection state.
        self.selection = self._get_selection()

    ###########################################################################
    # 'PythonDropTarget' interface.
    ###########################################################################

    def on_drag_over(self, x, y, obj, default_drag_result):
        """ Called when a node is dragged over the tree. """

        result = wx.DragNone

        # Find the node that we are dragging over...
        node = self._get_drag_drop_node(x, y)
        if node is not None:
            # Ask the model if the node allows the object to be dropped onto
            # it.
            if self.model.can_drop(node, obj):
                result = default_drag_result

        return result

    def on_drop(self, x, y, obj, default_drag_result):
        """ Called when a node is dropped on the tree. """

        # Find the node that we are dragging over...
        node = self._get_drag_drop_node(x, y)
        if node is not None:
            self.model.drop(node, obj)

        return default_drag_result

    ###########################################################################
    # Private interface.
    ###########################################################################

    def _get_wxid(self, node):
        """ Returns the wxid for the specified node.

        Returns None if the node has not yet appeared in the tree.

        """

        # The model must generate a unique key for each node (unique within the
        # model).
        key = self.model.get_key(node)

        return self._node_to_id_map.get(key, None)

    def _set_wxid(self, node, wxid):
        """ Sets the wxid for the specified node. """

        # The model must generate a unique key for each node (unique within the
        # model).
        key = self.model.get_key(node)

        self._node_to_id_map[key] = wxid

        return

    def _remove_wxid(self, node):
        """ Removes the wxid for the specified node. """

        # The model must generate a unique key for each node (unique within the
        # model).
        key = self.model.get_key(node)

        try:
            del self._node_to_id_map[key]

        except KeyError:
            # fixme: No, really, this is a serious one... How do we get in this
            # situation.  It came up when using the canvas stuff...
            logger.warn('removing node: %s' % str(node))

        return

    def _get_style(self):
        """ Returns the wx style flags for creating the tree control. """

        # Start with the default flags.
        style = self.STYLE

        # Turn lines off for appearance on *nix.
        # ...for now, show_lines determines if lines are on or off, but
        # eventually lines_mode may eliminate the need for show_lines
        if self.lines_mode == 'appearance' and os.name == 'posix':
            self.show_lines = False

        if not self.show_lines:
            style = style | wx.TR_NO_LINES

        if not self.show_root:
            # fixme: It looks a little weird, but it we don't have the
            # 'lines at root' style then wx won't draw the expand/collapse
            # image on non-leaf nodes at the root level 8^()
            style = style | wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT

        if self.selection_mode != 'single':
            style = style | wx.TR_MULTIPLE | wx.TR_EXTENDED

        return style

    def _add_model_listeners(self, model):
        """ Adds listeners for model changes. """

        # Listen for changes to the model.
        model.on_trait_change(self._on_root_changed, 'root')
        model.on_trait_change(self._on_nodes_changed, 'nodes_changed')
        model.on_trait_change(self._on_nodes_inserted, 'nodes_inserted')
        model.on_trait_change(self._on_nodes_removed, 'nodes_removed')
        model.on_trait_change(self._on_nodes_replaced, 'nodes_replaced')
        model.on_trait_change(self._on_structure_changed, 'structure_changed')

        return

    def _remove_model_listeners(self, model):
        """ Removes listeners for model changes. """

        # Unhook the model event listeners.
        model.on_trait_change(
            self._on_root_changed, 'root', remove=True
        )

        model.on_trait_change(
            self._on_nodes_changed, 'nodes_changed', remove=True
        )

        model.on_trait_change(
            self._on_nodes_inserted, 'nodes_inserted', remove=True
        )

        model.on_trait_change(
            self._on_nodes_removed, 'nodes_removed', remove=True
        )

        model.on_trait_change(
            self._on_nodes_replaced, 'nodes_replaced', remove=True
        )

        model.on_trait_change(
            self._on_structure_changed, 'structure_changed', remove=True
        )

        return

    def _add_root_node(self, node):
        """ Adds the root node. """

        # Get the tree item image index and the label text.
        image_index = self._get_image_index(node)
        text = self._get_text(node)

        # Add the node.
        wxid = self.control.AddRoot(text, image_index, image_index)

        # This gives the model a chance to wire up trait handlers etc.
        self.model.add_listener(node)

        # If the root node is hidden, get its children.
        if not self.show_root:
            # Add the child nodes.
            for child in self._get_children(node):
                self._add_node(wxid, child)

        # Does the node have any children?
        has_children = self._has_children(node)
        self.control.SetItemHasChildren(wxid, has_children)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data (which in our case is an arbitrary
        # Python object provided by the tree model).
        if self.show_root:
            self.control.SetPyData(wxid, (not self.show_root, node))

        # Make sure that we can find the node's Id.
        self._set_wxid(node, wxid)

        # Automatically expand the root.
        if self.show_root:
            self.control.Expand(wxid)

        return

    def _add_node(self, pid, node):
        """ Adds 'node' as a child of the node identified by 'pid'.

        If 'pid' is None then we are adding the root node.

        """

        # Get the tree item image index and the label text.
        image_index = self._get_image_index(node)
        text = self._get_text(node)

        # Add the node.
        wxid = self.control.AppendItem(pid, text, image_index, image_index)

        # This gives the model a chance to wire up trait handlers etc.
        self.model.add_listener(node)

        # Does the node have any children?
        has_children = self._has_children(node)
        self.control.SetItemHasChildren(wxid, has_children)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data (which in our case is an arbitrary
        # Python object provided by the tree model).
        self.control.SetPyData(wxid, (False, node))

        # Make sure that we can find the node's Id.
        self._set_wxid(node, wxid)

        return

    def _insert_node(self, pid, node, index):
        """ Inserts 'node' as a child of the node identified by 'pid'.

        If 'pid' is None then we are adding the root node.

        """

        # Get the tree item image index and the label text.
        image_index = self._get_image_index(node)
        text = self._get_text(node)

        # Add the node.
        wxid = self.control.InsertItemBefore(
            pid, index, text, image_index, image_index
        )

        # This gives the model a chance to wire up trait handlers etc.
        self.model.add_listener(node)

        # Does the node have any children?
        has_children = self._has_children(node)
        self.control.SetItemHasChildren(wxid, has_children)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data (which in our case is an arbitrary
        # Python object provided by the tree model).
        self.control.SetPyData(wxid, (False, node))

        # Make sure that we can find the node's Id.
        self._set_wxid(node, wxid)

        return

    def _remove_node(self, wxid, node):
        """ Removes a node from the tree. """

        # This gives the model a chance to remove trait handlers etc.
        self.model.remove_listener(node)

        # Remove the reference to the item's data.
        self._remove_wxid(node)
        self.control.SetPyData(wxid, None)

        return

    def _update_node(self, wxid, node):
        """ Updates the image and text of the specified node. """

        # Get the tree item image index.
        image_index = self._get_image_index(node)
        self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Normal)
        self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Selected)

        # Get the tree item text.
        text = self._get_text(node)
        self.control.SetItemText(wxid, text)

        return

    def _has_children(self, node):
        """ Returns True if a node has children. """

        # fixme: To be correct we *should* apply filtering here, but that
        # seems to blow a hole throught models that have some efficient
        # mechanism for determining whether or not they have children.  There
        # is also a precedent for doing it this way in Windoze, where a node
        # gets marked as though it can be expanded, even thought when the
        # expansion occurs, no children are present!
        return self.model.has_children(node)

    def _get_children(self, node):
        """ Get the children of a node. """

        children = self.model.get_children(node)

        # Filtering....
        filtered_children = []
        for child in children:
            for filter in self.filters:
                if not filter.select(self, node, child):
                    break

            else:
                filtered_children.append(child)

        # Sorting...
        if self.sorter is not None:
            self.sorter.sort(self, node, filtered_children)

        return filtered_children

    def _get_image_index(self, node):
        """ Returns the tree item image index for a node. """

        expanded = self.is_expanded(node)
        selected = self.is_selected(node)

        # Get the image used to represent the node.
        image = self.model.get_image(node, selected, expanded)
        if image is not None:
            image_index = self._image_list.GetIndex(image)

        else:
            image_index = -1

        return image_index

    def _get_drag_drop_node(self, x, y):
        """ Returns the node that is being dragged/dropped on.

        Returns None if the cursor is not over the icon or label of a node.

        """

        data, wxid, flags, point = self._hit_test((x, y))
        if data is not None:
            populated, node = data

        else:
            node = None

        return node

    def _get_text(self, node):
        """ Returns the tree item text for a node. """

        text = self.model.get_text(node)
        if text is None:
            text = ''

        return text

    def _unpack_event(self, event, wxid=None):
        """ Unpacks the event to see whether a tree item was involved. """

        try:
            point = event.GetPosition()

        except:
            point = event.GetPoint()

        return self._hit_test(point, wxid)

    def _hit_test(self, point, wxid=None):
        """ Determines whether a point is within a node's label or icon. """

        flags = wx.TREE_HITTEST_ONITEMLABEL
        if (wxid is None) or (not wxid.IsOk()):
            wxid, flags = self.control.HitTest(point)

        # Warning: On GTK we have to check the flags before we call 'GetPyData'
        # because if we call it when the hit test returns 'nowhere' it will
        # barf (on Windows it simply returns 'None' 8^()
        if flags & wx.TREE_HITTEST_NOWHERE:
            data = None

        elif flags & wx.TREE_HITTEST_ONITEMICON \
             or flags & wx.TREE_HITTEST_ONITEMLABEL:

            data = self.control.GetPyData(wxid)

        # fixme: Not sure why 'TREE_HITTEST_NOWHERE' doesn't catch everything!
        else:
            data = None

        return data, wxid, flags, point

    def _get_selection(self):
        """ Returns a list of the selected nodes """

        selection = []
        for wxid in self.control.GetSelections():
            data = self.control.GetPyData(wxid)
            if data is not None:
                populated, node = data
                selection.append(self.model.get_selection_value(node))

        return selection

    def _expand_item(self, wxid):
        """ Recursively expand a tree item. """

        self.control.Expand(wxid)

        cid, cookie = self.control.GetFirstChild(wxid)
        while cid.IsOk():
            self._expand_item(cid)
            cid, cookie = self.control.GetNextChild(wxid, cookie)

        return

    #### Trait event handlers #################################################

    def _on_root_changed(self, root):
        """ Called when the root of the model has changed. """

        # Delete everything...
        if self.control is not None:
            self.control.DeleteAllItems()

            self._node_to_id_map = {}

            # ... and then add the root item back in.
            if root is not None:
                self._add_root_node(root)

        return

    def _on_nodes_changed(self, event):
        """ Called when nodes have been changed. """

        self._update_node(self._get_wxid(event.node), event.node)

        for child in event.children:
            cid = self._get_wxid(child)
            if cid is not None:
                self._update_node(cid, child)

        return

    def _on_nodes_inserted(self, event):
        """ Called when nodes have been inserted. """

        parent   = event.node
        children = event.children
        index    = event.index

        # Has the node actually appeared in the tree yet?
        pid = self._get_wxid(parent)
        if pid is not None:
            # The item data is a tuple.  The first element indicates whether or
            # not we have already populated the item with its children.  The
            # second element is the actual item data.
            if self.show_root or parent is not self.root:
                populated, node = self.control.GetPyData(pid)

            else:
                populated = True

            # If the node is not yet populated then just get the children and
            # add them.
            if not populated:
                for child in self._get_children(parent):
                    self._add_node(pid, child)

            # Otherwise, insert them.
            else:
                # An index of -1 means append!
                if index == -1:
                    index = self.control.GetChildrenCount(pid, False)

                for child in children:
                    self._insert_node(pid, child, index)
                    index += 1

            # The element is now populated!
            if self.show_root or parent is not self.root:
                self.control.SetPyData(pid, (True, parent))

            # Does the node have any children now?
            has_children = self.control.GetChildrenCount(pid) > 0
            self.control.SetItemHasChildren(pid, has_children)

            # If the node is not expanded then expand it.
            if not self.is_expanded(parent):
                self.expand(parent)

        return

    def _on_nodes_removed(self, event):
        """ Called when nodes have been removed. """

        parent   = event.node
        children = event.children

        # Has the node actually appeared in the tree yet?
        pid = self._get_wxid(parent)
        if pid is not None:
            for child in event.children:
                cid = self._get_wxid(child)
                if cid is not None:
                    self.control.Delete(cid)

            # Does the node have any children left?
            has_children = self.control.GetChildrenCount(pid) > 0
            self.control.SetItemHasChildren(pid, has_children)

        return

    def _on_nodes_replaced(self, event):
        """ Called when nodes have been replaced. """

        for old_child, new_child in zip(event.old_children, event.children):
            cid = self._get_wxid(old_child)
            if cid is not None:
                # Remove listeners from the old node.
                self.model.remove_listener(old_child)

                # Delete all of the node's children.
                self.control.DeleteChildren(cid)

                # Update the visual appearance of the node.
                self._update_node(cid, new_child)

                # Update the node data.
                #
                # The item data is a tuple.  The first element indicates
                # whether or not we have already populated the item with its
                # children. The second element is the actual item data (which
                # in our case is an arbitrary Python object provided by the
                # tree model).
                self.control.SetPyData(cid, (False, new_child))

                # Remove the old node from the node to Id map.
                self._remove_wxid(old_child)

                # Add the new node to the node to Id map.
                self._set_wxid(new_child, cid)

                # Add listeners to the new node.
                self.model.add_listener(new_child)

                # Does the new node have any children?
                has_children = self._has_children(new_child)
                self.control.SetItemHasChildren(cid, has_children)

        # Update the tree's selection (in case the old node that was replaced
        # was selected, the selection should now include the new node).
        self.selection = self._get_selection()
        return

    def _on_structure_changed(self, event):
        """ Called when the structure of a node has changed drastically. """

        self.refresh(event.node)

        return

    #### wx event handlers ####################################################

    def _on_char(self, event):
        """ Called when a key is pressed when the tree has focus. """

        self.key_pressed = KeyPressedEvent(
            alt_down     = event.m_altDown == 1,
            control_down = event.m_controlDown == 1,
            shift_down   = event.m_shiftDown == 1,
            key_code     = event.m_keyCode
        )

        event.Skip()

        return

    def _on_left_down(self, event):
        """ Called when the left mouse button is clicked on the tree. """

        data, id, flags, point = self._unpack_event(event)

        # Save point for tree_begin_drag method to workaround a bug in ?? when
        # wx.TreeEvent.GetPoint returns only (0,0).  This happens under linux
        # when using wx-2.4.2.4, for instance.
        self._point_left_clicked = point

        # Did the left click occur on a tree item?
        if data is not None:
            populated, node = data

            # Trait event notification.
            self.node_left_clicked = node, point

        # Give other event handlers a chance.
        event.Skip()

        return

    def _on_right_down(self, event):
        """ Called when the right mouse button is clicked on the tree. """

        data, id, flags, point = self._unpack_event(event)

        # Did the right click occur on a tree item?
        if data is not None:
            populated, node = data

            # Trait event notification.
            self.node_right_clicked = node, point

        # Otherwise notify that the control itself was clicked
        else:
            self.control_right_clicked = point

        # Give other event handlers a chance.
        event.Skip()

        return

    def _on_tree_item_activated(self, event):
        """ Called when a tree item is activated (i.e., double clicked). """

        # fixme: See the comment where the events are wired up for more
        # information.

##         # Which item was activated?
##         wxid = event.GetItem()

        # Which item was activated.
        point = event.GetPosition()
        wxid, flags = self.control.HitTest(point)

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetPyData(wxid)

        # Trait event notiification.
        self.node_activated = node

        return

    def _on_tree_item_collapsing(self, event):
        """ Called when a tree item is about to collapse. """

        # Which item is collapsing?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetPyData(wxid)

        # Give the model a chance to veto the collapse.
        if not self.model.is_collapsible(node):
            event.Veto()

        return

    def _on_tree_item_collapsed(self, event):
        """ Called when a tree item has been collapsed. """

        # Which item was collapsed?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetPyData(wxid)

        # Make sure that the item's 'closed' icon is displayed etc.
        self._update_node(wxid, node)

        # Trait event notification.
        self.node_collapsed = node

        return

    def _on_tree_item_expanding(self, event):
        """ Called when a tree item is about to expand. """

        # Which item is expanding?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetPyData(wxid)

        # Give the model a chance to veto the expansion.
        if self.model.is_expandable(node):
            # Lazily populate the item's children.
            if not populated:
                # Add the child nodes.
                for child in self._get_children(node):
                    self._add_node(wxid, child)

                # The element is now populated!
                self.control.SetPyData(wxid, (True, node))

        else:
            event.Veto()

        return

    def _on_tree_item_expanded(self, event):
        """ Called when a tree item has been expanded. """

        # Which item was expanded?
        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetPyData(wxid)

        # Make sure that the node's 'open' icon is displayed etc.
        self._update_node(wxid, node)

        # Trait event notification.
        self.node_expanded = node

        return

    def _on_tree_begin_label_edit(self, event):
        """ Called when the user has started editing an item's label. """

        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetPyData(wxid)

        # Give the model a chance to veto the edit.
        if not self.model.is_editable(node):
            event.Veto()

        return

    def _on_tree_end_label_edit(self, event):
        """ Called when the user has finished editing am item's label. """

        wxid = event.GetItem()

        # The item data is a tuple.  The first element indicates whether or not
        # we have already populated the item with its children.  The second
        # element is the actual item data.
        populated, node = self.control.GetPyData(wxid)

        # Give the model a chance to veto the edit.
        label = event.GetLabel()

        # Making sure the new label is not an empty string

        if label is not None and len(label) > 0 and \
            self.model.can_set_text(node, label):
            def end_label_edit():
                """ Called to complete the label edit. """

                # Set the node's text.
                self.model.set_text(node, label)

                # If a label edit callback was specified (in the call to
                # 'edit_label'), then call it).
                if self._label_edit_callback is not None:
                    self._label_edit_callback(self, node, label)

                return

            # We use a deffered call here, because a name change can trigger
            # the structure of a node to change, and hence the actual tree
            # nodes might get moved/deleted before the label edit operation has
            # completed.  When this happens wx gets very confused!  By using
            # 'invoke_later' we allow the label edit to complete.
            GUI.invoke_later(end_label_edit)

        else:
            event.Veto()

            # If a label edit callback was specified (in the call to
            # 'edit_label'), then call it).
            if self._label_edit_callback is not None:
                self._label_edit_callback(self, node, label)

        return

    def _on_tree_begin_drag(self, event):
        """ Called when a drag operation is starting on a tree item. """

        # Get the node, its id and the point where the event occurred.
        data, wxid, flags, point = self._unpack_event(event, event.GetItem())

        if point == (0,0):
            # Apply workaround for GTK.
            point = self.point_left_clicked
            wxid, flags = self.HitTest(point)
            data = self.control.GetPyData(wxid)

        if data is not None:
            populated, node = data

            # Give the model a chance to veto the drag.
            if self.model.is_draggable(node):
                # We ask the model for the actual value to drag.
                drag_value = self.model.get_drag_value(node)

                # fixme: This is a terrible hack to get the binding x passed
                # during a drag operation.  Bindings should probably *always*
                # be dragged and our drag and drop mechanism should allow
                # extendable ways to extract the actual data.
                from pyface.wx.drag_and_drop import clipboard
                clipboard.node = [node]

                # Make sure that the tree selection is updated before we start
                # the drag. If we don't do this then if the first thing a
                # user does is drag a tree item (i.e., without a separate click
                # to select it first) then the selection appears empty.
                self.selection = self._get_selection()

                # Start the drag.
                PythonDropSource(self.control, drag_value, self)

                # Trait event notification.
                self.node_begin_drag = node

            else:
                event.Veto()

        return

    # fixme: This is part of the drag and drop hack...
    def on_dropped(self):
        """ Callback invoked when a drag/drop operation has completed. """

        from pyface.wx.drag_and_drop import clipboard
        clipboard.node = None

        return

    def _on_tree_sel_changed(self, event):
        """ Called when the selection is changed. """

        # Update our record of the selection to whatever was selected in the
        # tree UNLESS we are ignoring selection events.
        if not self._ignore_selection_events:

            # Trait notification.
            self.selection = self._get_selection()

        return

    def _on_tree_delete_item(self, event):
        """ Called when a tree item is being been deleted. """

        # Which item is being deleted?
        wxid = event.GetItem()

        # Check if GetPyData() returned a valid to tuple to unpack
        # ...if so, remove the node from the tree, otherwise just return
        #
        # fixme: Whoever addeed this code (and the comment above) didn't say
        # when this was occurring. This is method is called in response to a wx
        # event to delete an item and hence the item data should never be None
        # surely?!? Was it happening just on one platform?!?
        data = self.control.GetPyData(wxid)
        if data is not None:
            # The item data is a tuple.  The first element indicates whether or
            # not we have already populated the item with its children.  The
            # second element is the actual item data.
            populated, node = data

            # Remove the node.
            self._remove_node(wxid, node)

        return
Пример #31
0
class calibration_gui(HasTraits):

    camera = List
    status_text = Str("")
    ori_img_name = []
    ori_img = []
    pass_init = Bool(False)
    pass_init_disabled = Bool(False)
    # -------------------------------------------------------------
    button_edit_cal_parameters = Button()
    button_showimg = Button()
    button_detection = Button()
    button_manual = Button()
    button_file_orient = Button()
    button_init_guess = Button()
    button_sort_grid = Button()
    button_sort_grid_init = Button()
    button_orient = Button()
    button_orient_part = Button()
    button_orient_shaking = Button()
    button_orient_dumbbell = Button()
    button_restore_orient = Button()
    button_checkpoint = Button()
    button_ap_figures = Button()
    button_edit_ori_files = Button()
    button_test = Button()

    # Defines GUI view --------------------------

    view = View(HGroup(VGroup(
        VGroup(
            Item(name='button_showimg',
                 label='Load/Show Images',
                 show_label=False),
            Item(name='button_detection',
                 label='Detection',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_manual',
                 label='Manual orient.',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_file_orient',
                 label='Orient. with file',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_init_guess',
                 label='Show initial guess',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_sort_grid',
                 label='Sortgrid',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_sort_grid_init',
                 label='Sortgrid = initial guess',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_orient',
                 label='Orientation',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_orient_part',
                 label='Orientation with particles',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_orient_dumbbell',
                 label='Orientation from dumbbell',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_restore_orient',
                 label='Restore ori files',
                 show_label=False,
                 enabled_when='pass_init'),
            Item(name='button_checkpoint',
                 label='Checkpoints',
                 show_label=False,
                 enabled_when='pass_init_disabled'),
            Item(name='button_ap_figures',
                 label='Ap figures',
                 show_label=False,
                 enabled_when='pass_init_disabled'),
            show_left=False,
        ),
        VGroup(
            Item(name='button_edit_cal_parameters',
                 label='Edit calibration parameters',
                 show_label=False),
            Item(
                name='button_edit_ori_files',
                label='Edit ori files',
                show_label=False,
            ),
            show_left=False,
        ),
    ),
                       Item('camera',
                            style='custom',
                            editor=ListEditor(
                                use_notebook=True,
                                deletable=False,
                                dock_style='tab',
                                page_name='.name',
                            ),
                            show_label=False),
                       orientation='horizontal'),
                title='Calibration',
                id='view1',
                width=1.,
                height=1.,
                resizable=True,
                statusbar='status_text')

    #--------------------------------------------------

    def _button_edit_cal_parameters_fired(self):
        cp = exp.Calib_Params(par_path=self.par_path)
        cp.edit_traits(kind='modal')
        par.copy_params_dir(self.par_path, par.temp_path)

    def _button_showimg_fired(self):
        if os.path.isfile(
                os.path.join(self.exp1.active_params.par_path, 'man_ori.dat')):
            shutil.copyfile(
                os.path.join(self.exp1.active_params.par_path, 'man_ori.dat'),
                os.path.join(os.getcwd(), 'man_ori.dat'))

        print("Load Image fired")
        self.load_init_v1(
        )  # < - this should be united with the Calib_Params in experiment_01a.py
        print(len(self.ori_img))
        self.ptv.py_calibration(1)
        self.pass_init = True
        self.status_text = "Initialization finished."

    def _button_detection_fired(self):
        if self.need_reset:
            self.reset_show_images()
            self.need_reset = 0
        print("Detection procedure")
        self.ptv.py_calibration(2)
        x = []
        y = []
        self.ptv.py_get_pix(x, y)
        self.drawcross("x", "y", x, y, "blue", 4)
        for i in range(len(self.camera)):
            self.camera[i]._right_click_avail = 1

    def _button_manual_fired(self):
        points_set = True
        for i in range(len(self.camera)):
            if len(self.camera[i]._x) < 4:
                print "inside manual click"
                print self.camera[i]._x
                points_set = False

        if points_set:
            man_ori_path = os.path.join(os.getcwd(), 'man_ori.dat')
            f = open(man_ori_path, 'w')
            if f is None:
                self.status_text = "Error saving man_ori.dat."
            else:
                for i in range(len(self.camera)):
                    for j in range(4):
                        f.write("%f %f\n" %
                                (self.camera[i]._x[j], self.camera[i]._y[j]))

                self.status_text = "man_ori.dat saved."
                f.close()
        else:
            self.status_text = "Set 4 points on each calibration image for manual orientation"

    def _button_file_orient_fired(self):
        if self.need_reset:
            self.reset_show_images()
            self.need_reset = 0

        man_ori_path = os.path.join(os.getcwd(), 'man_ori.dat')
        try:
            f = open(man_ori_path, 'r')
        except:
            self.status_text = "Error loading man_ori.dat."
        else:
            for i in range(len(self.camera)):
                self.camera[i]._x = []
                self.camera[i]._y = []
                for j in range(4):
                    line = f.readline().split()
                    self.camera[i]._x.append(float(line[0]))
                    self.camera[i]._y.append(float(line[1]))
            self.status_text = "man_ori.dat loaded."
            f.close()
            shutil.copyfile(
                man_ori_path,
                os.path.join(self.exp1.active_params.par_path, 'man_ori.dat'))

        # TODO: rewrite using Parameters subclass
        man_ori_par_path = os.path.join(os.getcwd(), 'parameters',
                                        'man_ori.par')
        f = open(man_ori_par_path, 'r')
        if f is None:
            self.status_text = "Error loading man_ori.par."
        else:
            for i in range(len(self.camera)):
                for j in range(4):
                    self.camera[i].man_ori[j] = int(f.readline().split()[0])
                self.status_text = "man_ori.par loded."
                self.camera[i].left_clicked_event()
            f.close()
            self.ptv.py_calibration(4)
            self.status_text = "Loading orientation data from file finished."

    def _button_init_guess_fired(self):
        if self.need_reset:
            self.reset_show_images()
            self.need_reset = 0
        self.ptv.py_calibration(9)
        x = []
        y = []
        self.ptv.py_get_from_calib(x, y)
        self.drawcross("init_x", "init_y", x, y, "yellow", 3)
        self.status_text = "Initial guess finished."

    def _button_sort_grid_fired(self):
        if self.need_reset:
            self.reset_show_images()
            self.need_reset = 0
        self.ptv.py_calibration(5)
        x = []
        y = []
        x1_cyan = []
        y1_cyan = []
        pnr = []
        self.ptv.py_get_from_sortgrid(x, y, pnr)

        # filter out -999 which is returned for the missing points:
        for i in range(len(self.camera)):
            while -999 in x[i]:
                id = x[i].index(-999)
                del x[i][id]
                del y[i][id]
                del pnr[i][id]

        self.drawcross("sort_x", "sort_y", x, y, "white", 4)
        self.ptv.py_get_from_calib(x1_cyan, y1_cyan)
        self.drawcross("init_x", "init_y", x1_cyan, y1_cyan, "cyan", 4)
        for i in range(len(self.camera)):
            self.camera[i]._plot.overlays = []
            self.camera[i].plot_num_overlay(x[i], y[i], pnr[i])
        self.status_text = "Sort grid finished."

    def _button_sort_grid_init_fired(self):
        if self.need_reset:
            self.reset_show_images()
            self.need_reset = 0
        self.ptv.py_calibration(14)
        x = []
        y = []
        x1_cyan = []
        y1_cyan = []
        pnr = []
        self.ptv.py_get_from_sortgrid(x, y, pnr)
        self.drawcross("sort_x_init", "sort_y_init", x, y, "white", 4)
        self.ptv.py_get_from_calib(x1_cyan, y1_cyan)
        self.drawcross("init_x", "init_y", x1_cyan, y1_cyan, "cyan", 4)
        for i in range(len(self.camera)):
            self.camera[i]._plot.overlays = []
            self.camera[i].plot_num_overlay(x[i], y[i], pnr[i])
        self.status_text = "Sort grid initial guess finished."

    def _button_orient_fired(self):
        # backup the ORI/ADDPAR files first
        self.backup_ori_files()
        self.ptv.py_calibration(6)
        self.protect_ori_files()
        self.need_reset = 1
        x1 = []
        y1 = []
        x2 = []
        y2 = []
        self.ptv.py_get_from_orient(x1, y1, x2, y2)

        self.reset_plots()
        for i in range(len(self.camera)):
            self.camera[i]._plot_data.set_data(
                'imagedata', self.ori_img[i].astype(np.float))
            self.camera[i]._img_plot = self.camera[i]._plot.img_plot(
                'imagedata', colormap=gray)[0]
            self.camera[i].drawquiver(x1[i],
                                      y1[i],
                                      x2[i],
                                      y2[i],
                                      "red",
                                      scale=10.0)
            self.camera[i]._plot.index_mapper.range.set_bounds(0, self.h_pixel)
            self.camera[i]._plot.value_mapper.range.set_bounds(0, self.v_pixel)

        self.drawcross("orient_x", "orient_y", x1, y1, "orange", 4)
        self.status_text = "Orientation finished."

    def _button_orient_part_fired(self):
        self.backup_ori_files()
        self.ptv.py_calibration(10)
        x1, y1, x2, y2 = [], [], [], []
        self.ptv.py_get_from_orient(x1, y1, x2, y2)

        self.reset_plots()
        for i in range(len(self.camera)):
            self.camera[i]._plot_data.set_data(
                'imagedata', self.ori_img[i].astype(np.float))
            self.camera[i]._img_plot = self.camera[i]._plot.img_plot(
                'imagedata', colormap=gray)[0]
            self.camera[i].drawquiver(x1[i], y1[i], x2[i], y2[i], "red")
            self.camera[i]._plot.index_mapper.range.set_bounds(0, self.h_pixel)
            self.camera[i]._plot.value_mapper.range.set_bounds(0, self.v_pixel)
            self.drawcross("orient_x", "orient_y", x1, y1, "orange", 4)

        self.status_text = "Orientation with particles finished."

    def _button_orient_dumbbell_fired(self):
        print "Starting orientation from dumbbell"
        self.backup_ori_files()
        self.ptv.py_ptv_set_dumbbell(1)
        n_camera = len(self.camera)
        print("Starting sequence action")
        seq_first = self.exp1.active_params.m_params.Seq_First
        seq_last = self.exp1.active_params.m_params.Seq_Last
        print seq_first, seq_last
        base_name = []
        for i in range(n_camera):
            exec(
                "base_name.append(self.exp1.active_params.m_params.Basename_%d_Seq)"
                % (i + 1))
            print base_name[i]
            self.ptv.py_sequence_init(1)
            stepshake = self.ptv.py_get_from_sequence_init()
            if not stepshake:
                stepshake = 1

        temp_img = np.array([], dtype=np.ubyte)
        for i in range(seq_first, seq_last + 1, stepshake):
            seq_ch = "%04d" % i
            print seq_ch
            for j in range(n_camera):
                print("j %d" % j)
                img_name = base_name[j] + seq_ch
                print("Setting image: ", img_name)
                try:
                    temp_img = imread(img_name).astype(np.ubyte)
                except:
                    print "Error reading file"
                    self.ptv.py_set_img(temp_img, j)

            self.ptv.py_sequence_loop(1, i)

        print "Orientation from dumbbell - sequence finished"
        self.ptv.py_calibration(12)
        self.ptv.py_ptv_set_dumbbell(1)
        print "Orientation from dumbbell finished"

    def _button_restore_orient_fired(self):
        self.restore_ori_files()

    def load_init_v1(self):
        calOriParams = par.CalOriParams(len(self.camera), path=self.par_path)
        calOriParams.read()
        (fixp_name, img_cal_name, img_ori, tiff_flag, pair_flag, chfield) = \
            (calOriParams.fixp_name, calOriParams.img_cal_name, calOriParams.img_ori,
             calOriParams.tiff_flag, calOriParams.pair_flag, calOriParams.chfield)
        self.ori_img_name = img_cal_name

        ptvParams = par.PtvParams(path=self.par_path)
        ptvParams.read()
        (n_img, img_name, img_cal, hp_flag, allCam_flag, tiff_flag, imx, imy, pix_x, pix_y, chfield, mmp_n1, mmp_n2, mmp_n3, mmp_d) = \
            (ptvParams.n_img, ptvParams.img_name, ptvParams.img_cal, ptvParams.hp_flag, ptvParams.allCam_flag, ptvParams.tiff_flag,
             ptvParams.imx, ptvParams.imy, ptvParams.pix_x, ptvParams.pix_y, ptvParams.chfield, ptvParams.mmp_n1, ptvParams.mmp_n2, ptvParams.mmp_n3, ptvParams.mmp_d)
        self.h_pixel = imx
        self.v_pixel = imy

        self.ori_img = []
        print("len(self.camera)")
        print(len(self.camera))
        for i in range(len(self.camera)):
            print("reading " + self.ori_img_name[i])
            try:
                img1 = imread(self.ori_img_name[i], flatten=1).astype(np.ubyte)
                print img1.shape
            except:
                print("Error reading image " + self.ori_img_name[i])
                break
            self.ori_img.append(img1)
            self.ptv.py_set_img(self.ori_img[i], i)

        self.reset_show_images()
        # Loading manual parameters here

        # TODO: rewrite using Parameters subclass
        man_ori_path = os.path.join(os.getcwd(), 'parameters', 'man_ori.par')
        f = open(man_ori_path, 'r')
        if f is None:
            print('\n Error loading man_ori.par')
        else:
            for i in range(len(self.camera)):
                for j in range(4):
                    self.camera[i].man_ori[j] = int(f.readline().strip())
        f.close()

    def reset_plots(self):
        for i in range(len(self.camera)):
            self.camera[i]._plot.delplot(
                *self.camera[i]._plot.plots.keys()[0:])
            self.camera[i]._plot.overlays = []
            for j in range(len(self.camera[i]._quiverplots)):
                self.camera[i]._plot.remove(self.camera[i]._quiverplots[j])
            self.camera[i]._quiverplots = []

    def reset_show_images(self):
        for i in range(len(self.camera)):
            self.camera[i]._plot.delplot(
                *self.camera[i]._plot.plots.keys()[0:])
            self.camera[i]._plot.overlays = []
            # self.camera[i]._plot_data.set_data('imagedata',self.ori_img[i].astype(np.byte))
            self.camera[i]._plot_data.set_data(
                'imagedata', self.ori_img[i].astype(np.ubyte))

            self.camera[i]._img_plot = self.camera[i]._plot.img_plot(
                'imagedata', colormap=gray)[0]
            self.camera[i]._x = []
            self.camera[i]._y = []
            self.camera[i]._img_plot.tools = []
            self.camera[i].attach_tools()
            self.camera[i]._plot.request_redraw()
            for j in range(len(self.camera[i]._quiverplots)):
                self.camera[i]._plot.remove(self.camera[i]._quiverplots[j])
            self.camera[i]._quiverplots = []

    def _button_edit_ori_files_fired(self):
        editor = codeEditor(path=self.par_path)
        editor.edit_traits(kind='livemodal')

    def drawcross(self, str_x, str_y, x, y, color1, size1):
        for i in range(len(self.camera)):
            self.camera[i].drawcross(str_x, str_y, x[i], y[i], color1, size1)

    def backup_ori_files(self):
        # backup ORI/ADDPAR files to the backup_cal directory
        calOriParams = par.CalOriParams(len(self.camera), path=self.par_path)
        calOriParams.read()
        for f in calOriParams.img_ori:
            shutil.copyfile(f, f + '.bck')
            g = f.replace('ori', 'addpar')
            shutil.copyfile(g, g + '.bck')

    def restore_ori_files(self):
        # backup ORI/ADDPAR files to the backup_cal directory
        calOriParams = par.CalOriParams(len(self.camera), path=self.par_path)
        calOriParams.read()

        for f in calOriParams.img_ori:
            print "restored %s " % f
            shutil.copyfile(f + '.bck', f)
            g = f.replace('ori', 'addpar')
            shutil.copyfile(g + '.bck', g)

    def protect_ori_files(self):
        # backup ORI/ADDPAR files to the backup_cal directory
        calOriParams = par.CalOriParams(len(self.camera), path=self.par_path)
        calOriParams.read()
        for f in calOriParams.img_ori:
            d = file(f, 'r').read().split()
            if not np.all(np.isfinite(np.asarray(d).astype('f'))):
                print "protected ORI file %s " % f
                shutil.copyfile(f + '.bck', f)

    def load_init(self):
        calOriParams = par.CalOriParams(len(self.camera), path=self.par_path)
        calOriParams.read()
        (fixp_name, img_cal_name, img_ori, tiff_flag, pair_flag, chfield) = \
            (calOriParams.fixp_name, calOriParams.img_cal_name, calOriParams.img_ori,
             calOriParams.tiff_flag, calOriParams.pair_flag, calOriParams.chfield)
        self.ori_img_name = img_cal_name
        for i in range(len(self.camera)):
            print("reading " + self.ori_img_name[i])
            try:
                img1 = imread(self.ori_img_name[i]).astype(np.ubyte)
            except:
                print("Error reading image " + self.ori_img_name[i])
                break
            self.ori_img.append(img1)
            if self.camera[i]._plot is not None:
                self.camera[i]._plot.delplot(
                    *self.camera[i]._plot.plots.keys()[0:])
            self.camera[i]._plot_data.set_data('imagedata',
                                               self.ori_img[i].astype(np.byte))
            self.camera[i]._img_plot = self.camera[i]._plot.img_plot(
                'imagedata', colormap=gray)[0]

            self.camera[i]._x = []
            self.camera[i]._y = []
            self.camera[i]._plot.overlays = []
            self.camera[i]._img_plot.tools = []
            self.camera[i].attach_tools()
            self.camera[i]._plot.request_redraw()

            self.ptv.py_set_img(self.ori_img[i], i)

        f.close()
        # Loading manual parameters here
        # TODO: rewrite using Parameters subclass
        man_ori_path = os.path.join(os.getcwd(), 'parameters', 'man_ori.par')
        f = open(man_ori_path, 'r')
        if f == None:
            printf('\nError loading man_ori.par')
        else:
            for i in range(len(self.camera)):
                for j in range(4):
                    self.camera[i].man_ori[j] = int(f.readline().strip())

#
#   def drawcross(self,str_x,str_y,x,y,color1,size1):
#           for i in range(len(self.camera)):
#                   self.camera[i].drawcross(str_x,str_y,x[i],y[i],color1,size1)

    def update_plots(self, images, is_float=0):
        for i in range(len(images)):
            self.camera[i].update_image(images[i], is_float)

    #---------------------------------------------------
    # Constructor
    #---------------------------------------------------
    def __init__(self, par_path):
        # this is needed according to chaco documentation
        super(calibration_gui, self).__init__()
        self.need_reset = 0
        self.par_path = par_path
        self.ptv = ptv
Пример #32
0
class ToolkitEditorFactory(EditorFactory):
    """ Editor factory for list editors.
    """

    #---------------------------------------------------------------------------
    #  Trait definitions:
    #---------------------------------------------------------------------------

    # The editor to use for each list item:
    editor = editor_trait

    # Can the list be reorganized, or have items added and deleted.
    mutable = Bool(True)

    # The style of editor to use for each item:
    style = style_trait

    # The trait handler for each list item:
    trait_handler = handler_trait

    # The number of list rows to display:
    rows = rows_trait

    # The number of list columns to create:
    columns = columns_trait

    # Use a notebook for a custom view?
    use_notebook = Bool(False)

    # Show a right-click context menu for the notebook tabs?  (Qt only)
    show_notebook_menu = Bool(False)

    #-- Notebook Specific Traits -----------------------------------------------

    # Are notebook items deletable?
    deletable = Bool(False)

    # The extended name of the trait on each page object which should be used
    # to determine whether or not an individual page should be deletable.
    deletable_trait = Str()

    # FIXME: Currently, this trait is used only in the wx backend.
    # The DockWindow graphical theme
    dock_theme = Any

    # FIXME: Currently, this trait is used only in the wx backend.
    # Dock page style to use for each DockControl:
    dock_style = DockStyle

    # Export class for each item in a notebook:
    export = Str

    # Name of the view to use in notebook mode:
    view = AView

    # The type of UI to construct ('panel', 'subpanel', etc)
    ui_kind = Enum('subpanel', 'panel')

    # A factory function that can be used to define that actual object to be
    # edited (i.e. view_object = factory( object )):
    factory = Callable

    # Extended name to use for each notebook page. It can be either the actual
    # name or the name of an attribute on the object in the form:
    # '.name[.name...]'
    page_name = Str

    # Name of the [object.]trait[.trait...] to synchronize notebook page
    # selection with:
    selected = Str

    #---------------------------------------------------------------------------
    #  Traits view definition:
    #---------------------------------------------------------------------------

    traits_view = View(
        [['use_notebook{Use a notebook in a custom view}', '|[Style]'],
         [
             Item('page_name', enabled_when='object.use_notebook'),
             Item('view', enabled_when='object.use_notebook'),
             '|[Notebook options]'
         ],
         [
             Item('rows', enabled_when='not object.use_notebook'),
             '|[Number of list rows to display]<>'
         ]])

    #---------------------------------------------------------------------------
    #  'Editor' factory methods:
    #---------------------------------------------------------------------------

    def _get_custom_editor_class(self):
        if self.use_notebook:
            return toolkit_object('list_editor:NotebookEditor')
        return toolkit_object('list_editor:CustomEditor')