class CalibrationObject(HasTraits): tweak_dict = Dict cx = Float cy = Float rx = Float ry = Float rotation = Property(depends_on='rx,ry,_rotation') _rotation = Float center = Property(depends_on='cx,cy') scale = Float(1) def _set_rotation(self, rot): self._rotation = rot def _get_rotation(self): # if not (self.rx and self.rx): # return self._rotation rot = self._rotation if not rot: rot = self.calculate_rotation(self.rx, self.ry) return rot def _get_center(self): return self.cx, self.cy def set_right(self, x, y): self.rx = x self.ry = y self._rotation = 0 def set_center(self, x, y): self.cx = x self.cy = y def calculate_rotation(self, x, y, sense='east'): def rotation(a, b): return calc_rotation(self.cx, self.cy, a, b) if sense == 'west': print('x={}, y={}, cx={}, cy={}'.format(x, y, self.cx, self.cy)) if y > self.cy: rot = calc_rotation(self.cx, self.cy, x, y) - 180 else: rot = calc_rotation(self.cx, self.cy, x, y) + 180 elif sense == 'north': if x > self.cx: rot = rotation(x, -y) else: rot = rotation(y, -x) elif sense == 'south': rot = rotation(-y, x) else: rot = rotation(x, y) return rot
class PadEntry(HasTraits): pad = Float olow_post = Date ohigh_post = Date low_post = Property(depends_on='low_post_data, low_post_time') low_post_date = Date low_post_time = Time high_post = Property(depends_on='high_post_data, high_post_time') high_post_date = Date high_post_time = Time def update_pad(self): self._pad_changed(self.pad) def _pad_changed(self, pad): if self.olow_post and self.ohigh_post: td = timedelta(hours=pad) lp, hp = self.olow_post, self.ohigh_post lp -= td hp += td self.low_post, self.high_post = lp, hp def _set_high_post(self, v): self.high_post_date = v.date() self.high_post_time = v.time() def _set_low_post(self, v): self.low_post_date = v.date() self.low_post_time = v.time() def _get_high_post(self): return datetime.combine(self.high_post_date, self.high_post_time) def _get_low_post(self): return datetime.combine(self.low_post_date, self.low_post_time) def traits_view(self): l = VGroup(UItem('low_post_time'), UItem('low_post_date', style='custom')) h = VGroup(UItem('high_post_time'), UItem('high_post_date', style='custom')) v = View(UItem('pad'), HGroup(l, h), kind='livemodal', width=500, resizable=True, buttons=['OK', 'Cancel']) return v
class PadEntry(HasTraits): pad = Float olow_post = Date ohigh_post = Date low_post = Property(depends_on='low_post_data, low_post_time') low_post_date = Date low_post_time = Time high_post = Property(depends_on='high_post_data, high_post_time') high_post_date = Date high_post_time = Time def update_pad(self): self._pad_changed(self.pad) def _pad_changed(self, pad): if self.olow_post and self.ohigh_post: td = timedelta(hours=pad) lp, hp = self.olow_post, self.ohigh_post lp -= td hp += td self.low_post, self.high_post = lp, hp def _set_high_post(self, v): self.high_post_date = v.date() self.high_post_time = v.time() def _set_low_post(self, v): self.low_post_date = v.date() self.low_post_time = v.time() def _get_high_post(self): return datetime.combine(self.high_post_date, self.high_post_time) def _get_low_post(self): return datetime.combine(self.low_post_date, self.low_post_time) def traits_view(self): l = VGroup(UItem('low_post_time'), UItem('low_post_date', style='custom')) h = VGroup(UItem('high_post_time'), UItem('high_post_date', style='custom')) v = okcancel_view(Item('pad', label='Pad (hrs)'), HGroup(l, h), width=500) return v
class MandelbrotParameters(HasTraits): """ Stores the parameters needed to visualize the Mandelbrot set. """ # The number of points on each dimension of the grid. grid_size = Int(500) # Left limit for the x coordinates. xmin = Float(-2.0) # Right limit for the x coordinates. xmax = Float(1.0, auto_set=False) # Bottom limit for the y coordinates. ymin = Float(-1.5, auto_set=False) # Upper limit for the y coordinates. ymax = Float(1.5, auto_set=False) # Convenience property that returns a tuple with the bounds on the x axis. x_bounds = Property def _get_x_bounds(self): return (self.xmin, self.xmax) # 1D array with the x coordinates of the boundaries of the grid element. x_coords = Property def _get_x_coords(self): # The coordinates have 1 more element than the number of points in # the grid, because they represent the locations of pixel boundaries. return numpy.linspace(self.xmin, self.xmax, self.grid_size+1) # Convenience property that returns a tuple with the bounds on the y axis. y_bounds = Property(depends_on='ymin,ymax') def _get_y_bounds(self): return (self.ymin, self.ymax) # 1D array with the y coordinates of the boundaries of the grid element. y_coords = Property(depends_on='ymin,ymax') def _get_y_coords(self): # The coordinates have 1 more element than the number of points in # the grid, because they represent the locations of pixel boundaries. return numpy.linspace(self.ymin, self.ymax, self.grid_size+1)
class FluxTool(HasTraits): calculate_button = Button('Calculate') monitor_age = Property(depends_on='monitor') color_map_name = Str('jet') levels = Int(10, auto_set=False, enter_set=True) model_kind = Str('Plane') data_source = Str('database') # plot_kind = Str('Contour') plot_kind=Enum('Contour','Hole vs J') # def _plot_kind_default(self,): monitor=Any monitors=List group_positions=Bool(False) def _monitor_default(self): return DummyFluxMonitor() def _get_monitor_age(self): ma = 28.02e6 if self.monitor: ma=self.monitor.age return ma def traits_view(self): contour_grp = VGroup(Item('color_map_name', label='Color Map', editor=EnumEditor(values=sorted(color_map_name_dict.keys()))), Item('levels'), visible_when='plot_kind=="Contour"') monitor_grp=Item('monitor', editor=EnumEditor(name='monitors')) v = View( VGroup(HGroup(UItem('calculate_button'), UItem('data_source', editor=EnumEditor(values=['database', 'file'])), monitor_grp), HGroup(Item('group_positions'), Item('object.monitor.sample', style='readonly',label='Sample')), HGroup(UItem('plot_kind'), Item('model_kind', label='Fit Model', editor=EnumEditor(values=['Bowl', 'Plane']))), # UItem('plot_kind', editor=EnumEditor(values=['Contour', 'Hole vs J']))), contour_grp)) return v
class NewBranchView(HasTraits): name = Property(depends_on='_name') branches = Any _name = Str def traits_view(self): v = okcancel_view(UItem('name'), width=200, title='New Branch') return v def _get_name(self): return self._name def _set_name(self, v): self._name = v def _validate_name(self, v): if v not in self.branches: if ' ' not in v: return v
class FitSelector(HasTraits): fits = List(Fit) update_needed = Event suppress_refresh_unknowns = Bool save_event = Event fit_klass = Fit command_key = Bool auto_update = Bool(True) plot_button = Button('Plot') default_error_type = 'SD' show_all_button = Event show_state = Bool show_all_label = Property(depends_on='show_state') use_all_button = Button('Toggle Save') use_state = Bool global_fit = Str('Fit') global_error_type = Str('Error') fit_types = List(['Fit', ''] + FIT_TYPES) error_types = List(['Error', ''] + FIT_ERROR_TYPES) selected = Any def _get_show_all_label(self): return 'Hide All' if self.show_state else 'Show All' def _plot_button_fired(self): self.update_needed = True def _auto_update_changed(self): self.update_needed = True def _get_fits(self): fs = self.selected if not fs: fs = self.fits return fs def _show_all_button_fired(self): self.show_state = not self.show_state fs = self._get_fits() for fi in fs: fi.show = self.show_state def _use_all_button_fired(self): self.use_state = not self.use_state fs = self._get_fits() for fi in fs: fi.use = self.use_state def _global_fit_changed(self): if self.global_fit in self.fit_types: fs = self._get_fits() 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_fits() for fi in fs: fi.error_type = self.global_error_type def _get_auto_group(self): return VGroup( HGroup( icon_button_editor( 'plot_button', 'refresh', tooltip='Replot the isotope evolutions. ' 'This may take awhile if many analyses are selected'), icon_button_editor('save_event', 'database_save', tooltip='Save fits to database'), Item( 'auto_update', label='Auto Plot', tooltip= 'Should the plot refresh after each change ie. "fit" or "show". ' 'It is not advisable to use this option with many analyses' )), HGroup( UItem('global_fit', editor=EnumEditor(name='fit_types')), UItem('global_error_type', editor=EnumEditor(name='error_types')))) def traits_view(self): v = View( VGroup(self._get_auto_group(), self._get_toggle_group(), self._get_fit_group())) return v def _get_toggle_group(self): g = HGroup( # UItem('global_fit',editor=EnumEditor(name='fit_types')), # UItem('global_error_type', editor=EnumEditor(name='error_types')), UItem('show_all_button', editor=ButtonEditor(label_value='show_all_label')), UItem('use_all_button')) return g def _get_columns(self): cols = [ ObjectColumn(name='name', editable=False), CheckboxColumn(name='show'), CheckboxColumn(name='use', label='Save'), ObjectColumn(name='fit', editor=EnumEditor(name='fit_types'), width=150), ObjectColumn(name='error_type', editor=EnumEditor(name='error_types'), width=50) ] return cols def _get_fit_group(self): cols = self._get_columns() editor = myTableEditor(columns=cols, selected='selected', selection_mode='rows', sortable=False, edit_on_first_click=False, clear_selection_on_dclicked=True, on_command_key=self._update_command_key, cell_bg_color='red', cell_font='modern 10') grp = UItem('fits', style='custom', editor=editor) return grp @on_trait_change('fits:[show, fit, use]') def _fit_changed(self, obj, name, old, new): if self.command_key: for fi in self.fits: fi.trait_set(**{name: new}) self.command_key = False if self.auto_update: if name in ('show', 'fit'): self.update_needed = True def load_fits(self, keys, fits): nfs = [] for ki, fi in zip(keys, fits): pf = next((fa for fa in self.fits if fa.name == ki), None) fit, et, fod = fi if pf is None: pf = self.fit_klass(name=ki) pf.fit = fit pf.filter_outliers = fod.get('filter_outliers', False) pf.filter_iterations = fod.get('iterations', 0) pf.filter_std_devs = fod.get('std_devs', 0) pf.error_type = et nfs.append(pf) self.fits = nfs # def load_baseline_fits(self, keys): # fits = self.fits # if not fits: # fits = [] # # fs = [ # self.fit_klass(name='{}bs'.format(ki), fit='average') # for ki in keys] # # fits.extend(fs) # self.fits = fits # def add_peak_center_fit(self): # fits = self.fits # if not fits: # fits = [] # # fs = self.fit_klass(name='PC', fit='average') # # fits.append(fs) # self.fits = fits # # def add_derivated_fits(self, keys): # fits = self.fits # if not fits: # fits = [] # # fs = [ # self.fit_klass(name='{}E'.format(ki), fit='average') # for ki in keys # ] # # fits.extend(fs) # self.fits = fits def _update_command_key(self, new): self.command_key = new
class MarkerLabel(Label): rotate_angle = 0 zero_y = 0 xoffset = 10 indicator_width = 4 indicator_height = 10 visible = Bool(True) component_height = 100 x = Float y = Float data_y = Float vertical = False horizontal_line_visible = False label_with_intensity = False text = Property(depends_on='_text, label_with_intensity') _text = Str def _set_text(self, text): self._text = text def _get_text(self): if self.label_with_intensity: return '{:0.4f}'.format(self.data_y) else: return self._text def draw(self, gc, component_height): if not self.text: self.text = '' if self.bgcolor != "transparent": gc.set_fill_color(self.bgcolor_) #draw tag border with gc: if self.vertical: self.rotate_angle = 90 width, height = self.get_bounding_box(gc) gc.translate_ctm(self.x, self.zero_y_vert - 35 - height) else: if self.horizontal_line_visible: self._draw_horizontal_line(gc) gc.translate_ctm(self.x + self.xoffset, self.y) self._draw_tag_border(gc) super(MarkerLabel, self).draw(gc) with gc: gc.translate_ctm(self.x - self.indicator_width / 2.0, self.zero_y) self._draw_index_indicator(gc, component_height) def _draw_horizontal_line(self, gc): with gc: # print self.x, self.y gc.set_stroke_color((0, 0, 0, 1)) gc.set_line_width(2) oy = 7 if self.text else 4 y = self.y + oy gc.move_to(0, y) gc.line_to(self.x, y) gc.stroke_path() def _draw_index_indicator(self, gc, component_height): # gc.set_fill_color((1, 0, 0, 1)) w, h = self.indicator_width, self.indicator_height gc.draw_rect((0, 0, w, h), FILL) gc.draw_rect((0, component_height, w, h), FILL) def _draw_tag_border(self, gc): gc.set_stroke_color((0, 0, 0, 1)) gc.set_line_width(2) # gc.set_fill_color((1, 1, 1, 1)) bb_width, bb_height = self.get_bounding_box(gc) offset = 2 xoffset = self.xoffset gc.lines([(-xoffset, (bb_height + offset) / 2.0), (0, bb_height + 2 * offset), (bb_width + offset, bb_height + 2 * offset), (bb_width + offset, -offset), (0, -offset), (-xoffset, (bb_height + offset) / 2.0)]) gc.draw_path()
class BaseTemplater(HasTraits): formatter = Property(depends_on='label') clear_button = Button keywords = List non_keywords = List label = String activated = Any example = Property(depends_on='label') view_title = Str predefined_label = Str predefined_labels = Property(depends_on='user_predefined_labels') base_predefined_labels = List(['']) user_predefined_labels = List add_enabled = Property(depends_on='label') delete_enabled = Property(depends_on='label') add_label_button = Button delete_label_button = Button attribute_keys = Property(depends_on='label') persistence_name = '' attributes = List example_context = Dict attribute_formats = Dict def __init__(self, *args, **kw): super(BaseTemplater, self).__init__(*args, **kw) self.load() #persistence def load(self): p = os.path.join(paths.hidden_dir, self.persistence_name) if os.path.isfile(p): with open(p, 'r') as rfile: try: self.user_predefined_labels = pickle.load(rfile) except BaseException: pass def dump(self): p = os.path.join(paths.hidden_dir, self.persistence_name) with open(p, 'w') as wfile: try: pickle.dump(self.user_predefined_labels, wfile) except BaseException: pass def _get_attribute_keys(self): ks = [] for k in self.label.split(' '): if k in self.attributes: ks.append(k.lower()) return ks def _get_formatter(self): ns = [] for k in self.label.split(' '): if k in self.attributes: if k == '<SPACE>': k = ' ' else: k = k.lower() try: f = self.attribute_formats[k] except KeyError: f = 's' k = '{{{}:{}}}'.format(k, f) ns.append(k) s = ''.join(ns) return s def _get_example(self): f = self.formatter return f.format(**self.example_context) def _get_predefined_labels(self): return self.base_predefined_labels + self.user_predefined_labels def _get_add_enabled(self): return self.label and self.label not in self.predefined_labels def _get_delete_enabled(self): return self.label in self.user_predefined_labels def _delete_label_button_fired(self): if self.label in self.user_predefined_labels: self.user_predefined_labels.remove(self.label) self.dump() self.load() self.label = '' #handlers def _add_label_button_fired(self): if self.label not in self.predefined_labels: self.user_predefined_labels.append(self.label) self.dump() self.load() def _clear_button_fired(self): self.label = '' self.predefined_label = '' def _activated_changed(self, new): if new: self.keywords.append(new) if self.label: self.label += ' {}'.format(new) else: self.label = new self.activated = None def _predefined_label_changed(self, new): self.label = new
class PyannoApplication(HasTraits): database = Instance(PyannoDatabase) main_window = Instance(ModelDataView) database_window = Instance(DatabaseView) database_ui = Instance(UI) logging_level = Int(logging.INFO) pyanno_pathname = Str db_window_open = Property(Bool) def _get_db_window_open(self): return (self.database_ui is not None and self.database_ui.control is not None) def open(self): self._start_logging() self._open_pyanno_database() self._open_main_window() def close(self): self.database.close() logger.info('Closing pyAnno -- Goodbye!') def _start_logging(self): logging.basicConfig(level=self.logging_level) logger.info('Starting pyAnno') def _create_pyanno_directory(self): """Create a pyanno directort in the user's home if it is missing.""" home_dir = os.getenv('HOME') or os.getenv('HOMEPATH') logger.debug('Found home directory at ' + str(home_dir)) self.pyanno_pathname = os.path.join(home_dir, PYANNO_PATH_NAME) try: logger.debug('Creating pyAnno directory at ' + self.pyanno_pathname) os.makedirs(self.pyanno_pathname) except OSError as e: logger.debug('pyAnno directory already existing') if e.errno != errno.EEXIST: raise def _open_pyanno_database(self): # database filename self._create_pyanno_directory() db_filename = os.path.join(self.pyanno_pathname, DATABASE_FILENAME) self.database = PyannoDatabase(db_filename) def _open_main_window(self): self.main_window = ModelDataView(application=self) model = ModelBt.create_initial_state(5, 8) self.main_window.set_model(model=model) self.main_window.configure_traits() def open_database_window(self): if self.db_window_open: # windows exists, raise self.database_ui.control.Raise() else: # window was closed or not existent logger.debug('Open database window') database_window = DatabaseView(database=self.database, application=self) database_ui = database_window.edit_traits(kind='live') self.database_window = database_window self.database_ui = database_ui def close_database_window(self): # wx specific self.database_ui.control.Close() def update_window_from_database_record(self, record): """Update main window from pyanno database record. """ self.main_window.set_from_database_record(record) def add_current_state_to_database(self): mdv = self.main_window # file name may contain unicode characters data_id = mdv.annotations_view.annotations_container.name if data_id is '': data_id = 'anonymous_annotations' elif type(data_id) is unicode: u_data_id = unicodedata.normalize('NFKD', data_id) data_id = u_data_id.encode('ascii', 'ignore') try: self.database.store_result( data_id, mdv.annotations_view.annotations_container, mdv.model, mdv.log_likelihood) except PyannoValueError as e: logger.info(e) errmsg = e.args[0] error('Error: ' + errmsg) if self.db_window_open: self.database_window.db_updated = True def _create_debug_database(self): """Create and populate a test database in a temporary file. """ from tempfile import mktemp from pyanno.modelA import ModelA from pyanno.modelB import ModelB from pyanno.annotations import AnnotationsContainer # database filename tmp_filename = mktemp(prefix='tmp_pyanno_db_') db = PyannoDatabase(tmp_filename) def _create_new_entry(model, annotations, id): value = model.log_likelihood(annotations) ac = AnnotationsContainer.from_array(annotations, name=id) db.store_result(id, ac, model, value) # populate database model = ModelA.create_initial_state(5) annotations = model.generate_annotations(100) _create_new_entry(model, annotations, 'test_id') modelb = ModelB.create_initial_state(5, 8) _create_new_entry(modelb, annotations, 'test_id') annotations = model.generate_annotations(100) _create_new_entry(modelb, annotations, 'test_id2') self.database = db
class AnnotationsContainer(HasStrictTraits): """Translate from general annotations files and arrays to pyAnno's format. This class exposes a few methods to import data from files and arrays, and converts them to pyAnno's format: * annotations are 2D integer arrays; rows index items, and columns annotators * label classes are numbered 0 to :attr:`nclasses`-1 . The attribute :attr:`labels` defines a mapping from label tokens to label classes * missing values are defined as :attr:`pyanno.util.MISSING_VALUE`. The attribute :attr:`missing_values` contains the missing values tokens found in the original, raw data The converted data can be accessed through the :attr:`annotations` property. The `AnnotationsContainer` is also used as the format to store annotations in :class:`~pyanno.database.PyannoDatabase` objects. """ DEFAULT_MISSING_VALUES_STR = ['-1', 'NA', 'None', '*'] DEFAULT_MISSING_VALUES_NUM = [-1, np.nan, None] DEFAULT_MISSING_VALUES_ALL = (DEFAULT_MISSING_VALUES_STR + DEFAULT_MISSING_VALUES_NUM) #: raw annotations, as they are imported from file or array raw_annotations = List(List) #: name of file or array from which the annotations were imported name = Str #: list of all labels found in file/array labels = List #: labels corresponding to a missing value missing_values = List #: number of classes found in the annotations nclasses = Property(Int, depends_on='labels') def _get_nclasses(self): return len(self.labels) #: number of annotators nannotators = Property(Int, depends_on='raw_annotations') def _get_nannotators(self): return len(self.raw_annotations[0]) #: number of annotations nitems = Property(Int, depends_on='raw_annotations') def _get_nitems(self): return len(self.raw_annotations) #: annotations in pyAnno format annotations = Property(Array, depends_on='raw_annotations') @cached_property def _get_annotations(self): nitems, nannotators = len(self.raw_annotations), self.nannotators anno = np.empty((nitems, nannotators), dtype=int) # build map from labels and missing values to annotation values raw2val = dict(list(zip(self.labels, list(range(self.nclasses))))) raw2val.update([(mv, MISSING_VALUE) for mv in self.missing_values]) # translate nan_in_missing_values = _is_nan_in_list(self.missing_values) for i, row in enumerate(self.raw_annotations): for j, lbl in enumerate(row): if nan_in_missing_values and _robust_isnan(lbl): # workaround for the fact that np.nan cannot be used as # the key to a dictionary, since np.nan != np.nan anno[i, j] = MISSING_VALUE else: anno[i, j] = raw2val[lbl] return anno @staticmethod def _from_generator(rows_generator, missing_values, name=''): missing_set = set(missing_values) labels_set = set() raw_annotations = [] nannotators = None for n, row in enumerate(rows_generator): # verify that number of lines is consistent in the whole file if nannotators is None: nannotators = len(row) else: if len(row) != nannotators: raise PyannoValueError( 'File has inconsistent number of entries ' 'on separate lines (line {})'.format(n)) raw_annotations.append(row) labels_set.update(row) # remove missing values from set of labels all_labels = sorted(list(labels_set - missing_set)) missing_values = sorted(list(missing_set & labels_set)) # workaround for np.nan != np.nan, so intersection does not work if _is_nan_in_list(all_labels): # uses fact that np.nan < x, for every x all_labels = all_labels[1:] missing_values.insert(0, np.nan) # create annotations object anno = AnnotationsContainer(raw_annotations=raw_annotations, labels=all_labels, missing_values=missing_values, name=name) return anno @staticmethod def _from_file_object(fobj, missing_values=None, name=''): """Useful for testing, as it can be called using a StringIO object. """ if missing_values is None: missing_values = AnnotationsContainer.DEFAULT_MISSING_VALUES_STR # generator for rows of file-like object def file_row_generator(): for line in fobj.readlines(): # remove commas and split in individual tokens line = line.strip().replace(',', ' ') # ignore empty lines if len(line) == 0: continue labels = line.split() yield labels return AnnotationsContainer._from_generator(file_row_generator(), missing_values, name=name) @staticmethod def from_file(filename, missing_values=None): """Load annotations from a file. The file is a text file with a columns separated by spaces and/or commas, and rows on different lines. Arguments --------- filename : string File name missing_values : list List of labels that are considered missing values. Default is :attr:`DEFAULT_MISSING_VALUES_STR` """ if missing_values is None: missing_values = AnnotationsContainer.DEFAULT_MISSING_VALUES_STR with open(filename) as fh: anno = AnnotationsContainer._from_file_object( fh, missing_values=missing_values, name=filename) return anno @staticmethod def from_array(x, missing_values=None, name=''): """Create an annotations object from an array or list-of-lists. Arguments --------- x : ndarray or list-of-lists Array or list-of-lists containing numerical or string annotations missing_values : list List of values that are considered missing values. Default is :attr:`DEFAULT_MISSING_VALUES_ALL` name : string Name of the annotations (for user interaction and used as key in databases). """ if missing_values is None: missing_values = AnnotationsContainer.DEFAULT_MISSING_VALUES_ALL # generator for array objects def array_rows_generator(): for row in x: yield list(row) return AnnotationsContainer._from_generator(array_rows_generator(), missing_values, name=name) def save_to(self, filename, set_name=False): """Save raw annotations to file. Arguments --------- filename : string File name set_name : bool Set the :attr:`name` of the annotation container to the file name """ if set_name: self.name = filename with open(filename, 'w') as f: f.writelines((' '.join(map(str, row)) + '\n' for row in self.raw_annotations))
def func(fget): fget.__name__ = '_get_' + fget.__name__ fget = cached_property(fget) x = Property(depends_on=depends_on, fget=fget, force=True, **kwargs) return x
class FluxTool(HasTraits): calculate_button = Button('Calculate') monitor_age = Property(depends_on='monitor') color_map_name = Str('jet') marker_size = Int(5) levels = Int(50, auto_set=False, enter_set=True) model_kind = Str('Plane') data_source = Str('database') # plot_kind = Str('Contour') plot_kind = Enum('Contour', 'Hole vs J') # def _plot_kind_default(self,): monitor = Any monitors = List group_positions = Bool(False) show_labels = Bool(True) mean_j_error_type = Enum(*ERROR_TYPES) predicted_j_error_type = Enum(*ERROR_TYPES) _prev_predicted_j_error_type = Str use_weighted_fit = Bool(False) use_monte_carlo = Bool(False) monte_carlo_ntrials =Int(10000) save_mean_j = Bool(False) auto_clear_cache = Bool(False) def _monitor_default(self): return DummyFluxMonitor() def _get_monitor_age(self): ma = 28.02e6 if self.monitor: ma = self.monitor.age return ma def _use_weighted_fit_changed(self, new): if new: self._prev_predicted_j_error_type = self.predicted_j_error_type self.predicted_j_error_type = 'SD' else: self.predicted_j_error_type = self._prev_predicted_j_error_type def traits_view(self): contour_grp = HGroup(Item('color_map_name', label='Color Map', editor=EnumEditor(values=sorted(color_map_name_dict.keys()))), Item('levels'), Item('marker_size'), visible_when='plot_kind=="Contour"') monitor_grp = Item('monitor', editor=EnumEditor(name='monitors')) v = View( VGroup(HGroup(UItem('calculate_button'), UItem('data_source', editor=EnumEditor(values=['database', 'file'])), monitor_grp), HGroup(Item('save_mean_j', label='Save Mean J'), Item('auto_clear_cache', label='Auto Clear Cache')), Item('mean_j_error_type', label='Mean J Error'), HGroup(Item('use_weighted_fit'), Item('use_monte_carlo'), Item('monte_carlo_ntrials', enabled_when='object.use_monte_carlo')), Item('predicted_j_error_type', label='Predicted J Error', enabled_when='not (object.use_weighted_fit or object.use_monte_carlo)'), HGroup(Item('group_positions'), Item('object.monitor.sample', style='readonly', label='Sample')), Item('show_labels', label='Display Labels', tooltip='Display hole labels on plot'), HGroup(UItem('plot_kind'), Item('model_kind', label='Fit Model', editor=EnumEditor(values=['Bowl', 'Plane']))), # UItem('plot_kind', editor=EnumEditor(values=['Contour', 'Hole vs J']))), contour_grp)) return v
class SystemObject(HasStrictTraits): """ Baseclass for Programs, Sensor, Actuators """ #: Names of attributes that accept Callables. If there are custom callables being used, they must be added here. #: The purpose of this list is that these Callables will be initialized properly. #: :class:`~automate.program.ProgrammableSystemObject` introduces 5 basic callables #: (see also :ref:`automate-programs`). callables = [] def get_default_callables(self): """ Get a dictionary of default callables, in form {name:callable}. Re-defined in subclasses.""" return {} #: Reference to System object system = Instance(SystemBase, transient=True) #: Description of the object (shown in WEB interface) description = CUnicode #: Python Logger instance for this object. System creates each object its own logger instance. logger = Instance(logging.Logger, transient=True) #: Tags are used for (for example) grouping objects. See :ref:`groups`. tags = TagSet(trait=CUnicode) #: Name property is determined by System namespace. Can be read/written. name = Property(trait=Unicode, depends_on='name_changed_event') log_level = Int(logging.INFO) def _log_level_changed(self, new_value): if self.logger: self.logger.setLevel(new_value) @cached_property def _get_name(self): try: return self.system.reverse[self] except (KeyError, AttributeError): return 'System not initialized!' def _set_name(self, new_name): if not is_valid_variable_name(new_name): raise NameError('Illegal name %s' % new_name) try: if self in list(self.system.namespace.values()): del self.system.namespace[self.name] except NameError: pass self.system.namespace[new_name] = self self.logger = self.system.logger.getChild('%s.%s' % (self.__class__.__name__, new_name)) #: If set to *True*, current SystemObject is hidden in the UML diagram of WEB interface. hide_in_uml = CBool(False) _order = Int _count = 0 #: Attributes that can be edited by user in WEB interface view = ['hide_in_uml'] #: The data type name (as string) of the object. This is written in the initialization, and is used by WEB #: interface Django templates. data_type = '' #: If editable=True, a quick edit widget will appear in the web interface. Define in subclasses. editable = False # Namespace triggers this event when object name name is changed name_changed_event = Event _passed_arguments = Tuple(transient=True) _postponed_callables = Dict(transient=True) @property def class_name(self): # For Django templates return self.__class__.__name__ @property def object_type(self): """ A read-only property that gives the object type as string; sensor, actuator, program, other. Used by WEB interface templates. """ from .statusobject import AbstractSensor, AbstractActuator from .program import Program if isinstance(self, AbstractSensor): return 'sensor' elif isinstance(self, AbstractActuator): return 'actuator' elif isinstance(self, Program): return 'program' else: return 'other' def __init__(self, name='', **traits): # Postpone traits initialization to be launched by System self.logger = logging.getLogger('automate.%s' % self.__class__.__name__) self._order = SystemObject._count SystemObject._count += 1 self._passed_arguments = name, traits if 'system' in traits: self.setup_system(traits.pop('system')) self.setup_callables() def __setstate__(self, state, trait_change_notify=True): self.logger = logging.getLogger('automate.%s' % self.__class__.__name__) self._order = state.pop('_order') self._passed_arguments = None, state def get_status_display(self, **kwargs): """ Redefine this in subclasses if status can be represented in human-readable way (units etc.) """ if 'value' in kwargs: return str(kwargs['value']) return self.class_name def get_as_datadict(self): """ Get information about this object as a dictionary. Used by WebSocket interface to pass some relevant information to client applications. """ return dict(type=self.__class__.__name__, tags=list(self.tags)) def setup(self, *args, **kwargs): """ Initialize necessary services etc. here. Define this in subclasses. """ pass def setup_system(self, system, name_from_system='', **kwargs): """ Set system attribute and do some initialization. Used by System. """ if not self.system: self.system = system name, traits = self._passed_arguments new_name = self.system.get_unique_name(self, name, name_from_system) if not self in self.system.reverse: self.name = new_name self.logger = self.system.logger.getChild('%s.%s' % (self.__class__.__name__, self.name)) self.logger.setLevel(self.log_level) if name is None and 'name' in traits: # Only __setstate__ sets name to None. Default is ''. del traits['name'] for cname in self.callables: if cname in traits: c = self._postponed_callables[cname] = traits.pop(cname) c.setup_callable_system(self.system) getattr(self, cname).setup_callable_system(self.system) if not self.traits_inited(): super().__init__(**traits) self.name_changed_event = True self.setup() def setup_callables(self): """ Setup Callable attributes that belong to this object. """ defaults = self.get_default_callables() for key, value in list(defaults.items()): self._postponed_callables.setdefault(key, value) for key in self.callables: value = self._postponed_callables.pop(key) value.setup_callable_system(self.system, init=True) setattr(self, key, value) def cleanup(self): """ Write here whatever cleanup actions are needed when object is no longer used. """ def __str__(self): return self.name def __repr__(self): return u"'%s'" % self.name