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()
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)
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)
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
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()
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
class AUILayout(TaskLayout): """ The layout for a main window's dock area using AUI Perspectives """ perspective = Str()
class Foo(HasTraits): name = Str()
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
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)
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()
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()
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
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
class RosslerIPlottable2dAdapter(Adapter, IModel3dIPlottable2dMixin): adaptee = Instance(Rossler) plot_type = Str('line')
class FooModel(HasTraits): my_str = Str('hallo')
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)
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')
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
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()
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']
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
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
class FooModel(HasTraits): my_str = Str("hallo")
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)
class Person(HasTraits): name = Str() age = Int() def __repr__(self): return "Person(name={self.name!r}, age={self.age!r})".format(self=self)
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()
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)
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
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
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')