class GUIApplication(Application): """ The entry point for an Envisage GUI application. This class handles the life-cycle of a Pyface GUI. Plugins can display windows via mechinisms such as edit_traits(). This is intended to be a very simple shell for lifting an existing pure TraitsUI or Pyface (or even Qt) app into an Envisage app. More sophisticated applications should use Tasks. """ #### 'GUIApplication' interface ######################################### #: The PyFace GUI for the application. gui = Supports('pyface.i_gui.IGUI') #: The splash screen for the application. By default, there is no splash #: screen. splash_screen = Supports('pyface.i_splash_screen.ISplashScreen') #### Application lifecycle events ######################################### #: Fired after the GUI event loop has been started. application_initialized = Event ########################################################################### # 'IApplication' interface. ########################################################################### def run(self): """ Run the application. Returns: -------- Whether the application started successfully (i.e., without a veto). """ # Make sure the GUI has been created (so that, if required, the splash # screen is shown). gui = self.gui started = self.start() if started: gui.set_trait_later(self, 'application_initialized', self) # Start the GUI event loop. The application will block here. gui.start_event_loop() # clean up plugins once event loop stops self.stop() return started #### Trait initializers ################################################### def _gui_default(self): from pyface.api import GUI return GUI(splash_screen=self.splash_screen)
class SurveyLineGroup(HasTraits): """ An interface representing a group of survey lines """ #: the user-defined name of the group name = Str #: the survey lines in the group survey_lines = List #: the lake depth algorithm to apply to the group lake_depth_algorithm = Supports(IAlgorithm) #: the preimpoundment depth algorithm to apply to the group preimpoundment_depth_algorithm = Supports(IAlgorithm) # XXX may want to add some analysis data here that is applied to the lines # in this group (eg. contrast settings, data view, etc.) so users can have # consistent settings for viewing a collection of lines def add_survey_lines(self, lines): """ Add lines to the group """ lines_added = [line for line in lines if line not in self.survey_lines] self.survey_lines += lines_added def remove_survey_lines(self, lines): """ Remove lines from the group """ lines_removed = [line for line in lines if line in self.survey_lines] self.survey_lines[:] = [ line for line in self.survey_lines if line not in lines_removed ]
class TraitsHolder(HasTraits): a_no = Instance(IAverage, adapt="no") a_yes = Instance(IAverage, adapt="yes") a_default = Instance(IAverage, adapt="default") list_adapted_to = Supports(IList) foo_adapted_to = Supports(IFoo) foo_plus_adapted_to = Supports(IFooPlus) list_adapts_to = AdaptsTo(IList) foo_adapts_to = AdaptsTo(IFoo) foo_plus_adapts_to = AdaptsTo(IFooPlus)
class ISurveyLineGroup(Interface): """ An interface representing a group of survey lines """ #: the user-defined name of the group name = Str #: the survey lines in the group survey_lines = List #: the lake depth algorithm to apply to the group lake_depth_algorithm = Supports(IAlgorithm) #: the preimpoundment depth algorithm to apply to the group preimpoundment_depth_algorithm = Supports(IAlgorithm)
class Survey(HasTraits): """ The a basic implementation of the ISurvey interface A survey has a lake, a set of survey lines, and a collection of user-assigned line groups. """ # XXX there should probably be some other survey metadata saved in this object #: The name of the survey name = Str #: Notes about the survey as a whole comments = Str #: The lake being surveyed lake = Supports(ILake) #: The lines in the survey survey_lines = List(Supports(ISurveyLine)) #: The groupings of survey lines survey_line_groups = List(Supports(ISurveyLineGroup)) #: The core samples taken in the survey core_samples = List(Supports(ICoreSample)) #: backend hdf5 file hdf5_file = File def add_survey_line_group(self, group): """ Create a new line group, optionally with a set of lines """ self.survey_line_groups.append(group) logger.debug("Added survey line group '{}'".format(group.name)) def insert_survey_line_group(self, index, group): """ Create a new line group, optionally with a set of lines """ self.survey_line_groups.insert(index, group) logger.debug("Inserted survey line group '{}' at index {}".format( group.name, index)) def delete_survey_line_group(self, group): """ Delete a line group, returning its index """ index = self.survey_line_groups.index(group) self.survey_line_groups.remove(group) logger.debug("Removed survey line group '{}' from index {}".format( group.name, index)) return index
class MOTDModelView(ModelView): #: the current message we are displaying message = Supports('motd.model.i_message.IMessage') #: the text of the message, formatted using HTML html = Property(HTML, depends_on=['message.text', 'message.author']) @cached_property def _get_html(self): html = html_template.format(message=self.message) return html def _model_changed(self, new): if new: self.message = self.model.get_message() def _model_default(self): from motd.model.motd import MOTD return MOTD() def _message_default(self): message = self.model.get_message() return message def on_new_message(self, event=None): """ Get a new message from the MOTD instance """ self.message = self.model.get_message() view = View( Item('html', show_label=False), width=400, height=200, title="Message of the day", buttons=[new_message_action], )
class MOTD(HasTraits): """ A 'Message of the Day' implementation """ # The list of possible messages. messages = List(Supports(IMessage), minlen=1) ########################################################################### # 'IMOTD' interface. ########################################################################### def get_message(self): """ Prints a random message. """ from random import choice message = choice(self.messages) return message ########################################################################### # 'MOTD' interface. ########################################################################### def _messages_default(self): """ The default message list to use when no other messages are provided """ from message import Message messages = [ Message(author='Anon', text='Work hard and be good to your Mother'), ] return messages
class ISurvey(Interface): """ The abstract interface for a survey object. A survey has a lake, a set of survey lines, and a collection of user-assigned line groups. """ # XXX there should probably be some other survey metadata saved in this object #: The name of the survey name = Str #: Notes about the survey as a whole comments = Str #: The lake being surveyed lake = Supports(ILake) #: The lines in the survey survey_lines = List(Supports(ISurveyLine)) #: The groupings of survey lines survey_line_groups = List(Supports(ISurveyLineGroup)) #: The core samples taken in the survey core_samples = List(Supports(ICoreSample)) def add_survey_line_group(self, group): """ Create a new line group, optionally with a set of lines """ raise NotImplementedError def insert_survey_line_group(self, index, group): """ Create a new line group, optionally with a set of lines """ raise NotImplementedError def delete_survey_line_group(self, group): """ Delete a line group, returning its index """ raise NotImplementedError
class MOTDStartupPlugin(Plugin): """ The 'Message of the Day' gui plugin. This plugin prints a MOTD on application startup. """ #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'motd.motd_startup' # The plugin's name (suitable for displaying to the user). name = 'MOTD Startup' @on_trait_change("application:started") def on_application_started(self): """ Print the 'Message of the Day' to stdout! """ from motd.util import print_message # Get the message of the day... message = self.motd.get_message() # ... and print it. print_message(message) ########################################################################### # Private interface. ########################################################################### motd = Supports('motd.model.i_motd.IMOTD') def _motd_default(self): """ Create MOTD instance using list of messages """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs the motd attribute. from motd.model.i_motd import IMOTD # ask the application for an IMOTD instance motd = self.application.get_service(IMOTD) return motd
class SurveyDataPane(TraitsDockPane): """ The dock pane holding the data view of the survey """ id = 'hydropick.survey_data' name = 'Survey' #: reference to the task's undo manager undo_manager = DelegatesTo('task') #: proxy for the task's current survey line current_survey_line = DelegatesTo('task') #: proxy for the task's current survey line current_survey_line_group = DelegatesTo('task') #: reference to the task's selected survey lines selected_survey_lines = DelegatesTo('task') #: reference to the survey lines survey = Supports(ISurvey) #: proxy for the survey's name survey_name = UndoingDelegate('survey', 'name', 'undo_manager', trait=Str, name='Survey Name', mergeable=True) #: proxy for the survey's comments field survey_comments = UndoingDelegate('survey', 'comments', 'undo_manager', trait=Str, name='Survey Comments', mergeable=True) #: the currently selected items in the view selection = List #: the item which has just been activated by double-clicking activated = Event def _selected_survey_lines_changed(self): self.selection = self.selected_survey_lines @on_trait_change('selection,selection_items') def _selection_updated(self): selection = self.selection if all(isinstance(item, ISurveyLine) for item in self.selection): self.selected_survey_lines = selection selected_lines = set() for item in selection: if isinstance(item, ISurveyLine): selected_lines.add(item) elif isinstance(item, (ISurveyLineGroup, ISurvey)): selected_lines |= set(item.survey_lines) if len(selection) == 1 and isinstance(selection[0], ISurveyLineGroup): self.current_survey_line_group = selection[0] self.selected_survey_lines = list(sorted(selected_lines)) def _activated_changed(self, item): print 'activated', item if isinstance(item, ISurveyLine): self.current_survey_line = item elif isinstance(item, ISurveyLineGroup): self.current_survey_line_group = item self.selected_survey_lines = item.survey_lines view = View(Item('survey', editor=survey_line_tree, show_label=False), )
class MyView(HasTraits): tree_node = Supports(ITreeNode)
class SurveyLine(HasTraits): """ A class representing a single survey line """ #: the user-visible name for the line name = Str #: sample locations, an Nx2 array (example: easting/northing?) locations = Array(shape=(None, 2)) #: specifies unit for values in locations array locations_unit = Str('feet') #: array of associated lat/long available for display lat_long = Array(shape=(None, 2)) #: a dictionary mapping frequencies to intensity arrays frequencies = Dict #: complete trace_num set. array = combined freq_trace_num arrays trace_num = Array #: array of trace numbers corresponding to each intensity pixel/column #: ! NOTE ! starts at 1, not 0, so need to subtract 1 to use as index freq_trace_num = Dict #: relevant core samples core_samples = List(Supports(ICoreSample)) #: depth of the lake at each location as generated by various soruces lake_depths = Dict(Str, Supports(IDepthLine)) #: name of final choice for line used as current lake depth for volume calculations final_lake_depth = Str # and event fired when the lake depths are updated lake_depths_updated = Event #: The navigation track of the survey line in map coordinates navigation_line = Instance(LineString) #: pre-impoundment depth at each location as generated by various soruces preimpoundment_depths = Dict(Str, Supports(IDepthLine)) #: name of final choice for pre-impoundment depth to track sedimentation final_preimpoundment_depth = Str # and event fired when the lake depth is updated preimpoundment_depths_updated = Event # power values for entire trace set power = Array # gain values for entire trace set gain = Array #: Depth corrections: #: depth = (pixel_number_from_top * pixel_resolution) + draft - heave #: distance from sensor to water. Constant offset added to depth draft = CFloat #: array of depth corrections. Changes vertical offset of each column. heave = Array #: pixel resolution, depth/pixel pixel_resolution = CFloat # XXX probably other metadata should be here # if some check results in a bad survey line then some text should be # put here stating why or where the check was. bad_survey_line = Str('') def load_data(self, hdf5_file): ''' Called by UI to load this survey line when selected to edit ''' # read in sdi dictionary. Only use 'frequencies' item. # sdi_dict_separated = binary.read(self.data_file_path) # sdi_dict_raw = binary.read(self.data_file_path, separate=False) # freq_dict_list = sdi_dict_separated['frequencies'] from ..io import survey_io # read frequency dict from hdf5 file. sdi_dict_raw = survey_io.read_sdi_data_unseparated_from_hdf(hdf5_file, self.name) freq_dict_list = survey_io.read_frequency_data_from_hdf(hdf5_file, self.name) # fill frequncies and freq_trace_num dictionaries with freqs as keys. for freq_dict in freq_dict_list: key = freq_dict['kHz'] # transpose array to go into image plot correctly oriented intensity = freq_dict['intensity'].T self.frequencies[str(key)] = intensity self.freq_trace_num[str(key)] = freq_dict['trace_num'] # for all other traits, use un-freq-sorted values self.trace_num = sdi_dict_raw['trace_num'] self.locations = np.vstack([sdi_dict_raw['interpolated_easting'], sdi_dict_raw['interpolated_northing']]).T self.lat_long = np.vstack([sdi_dict_raw['latitude'], sdi_dict_raw['longitude']]).T self.draft = (np.mean(sdi_dict_raw['draft'])) self.heave = sdi_dict_raw['heave'] self.pixel_resolution = (np.mean(sdi_dict_raw['pixel_resolution'])) self.power = sdi_dict_raw['power'] self.gain = sdi_dict_raw['gain'] self.array_sizes_ok() filename = os.path.basename(sdi_dict_raw['filepath']) sdi_surface = DepthLine( name='current_surface_from_bin', survey_line_name=self.name, line_type='current surface', source='sdi_file', source_name=filename, index_array=self.trace_num - 1, depth_array=sdi_dict_raw['depth_r1'] ) survey_io.write_depth_line_to_hdf(hdf5_file, sdi_surface, self.name) # depth lines stored separately self.lake_depths = survey_io.read_pick_lines_from_hdf( hdf5_file, self.name, 'current') self.preimpoundment_depths = survey_io.read_pick_lines_from_hdf( hdf5_file, self.name, 'preimpoundment') def nearby_core_samples(self, core_samples, dist_tol=100): """ Find core samples from a list of CoreSample instances that lie within dist_tol units of this survey line. """ def distance(core, line): """ Calculate distance between a core sample and a survey line """ from shapely.geometry import Point return self.navigation_line.distance(Point(core.location)) cores = [core for core in core_samples if distance(core, self) < dist_tol] return cores def array_sizes_ok(self): ''' this is an check that the arrays for this line make sense All the non-separated arrays should be the same size and the trace_num array should be range(1,N). This could be slightly more general by assuming and order array instead of contiguous''' name = self.name logger.info('Checking all array integrity for line {}'.format(name)) arrays = ['trace_num', 'locations', 'lat_long', 'heave', 'power', 'gain'] # N = self.trace_num.shape[0] # check = self.trace_num - np.arange(N) - 1 # if np.any(check != 0): # bad_traces = np.nonzero(check)[0] + 1 # values = self.trace_num[bad_traces - 1] # print check, bad_traces, values # s = '''trace_num not contiguous for array: {}. # values of {} at traces {} # '''.format(name, values, bad_traces) # logger.warn(s) # self.fix_trace_num(N, bad_traces, values) # now check rest of arrays from ..io import survey_io bad_indices, bad_vals = survey_io.check_trace_num_array(self.trace_num, self.name) tn, fn = survey_io.fix_trace_num_arrays(self.trace_num, bad_indices, self.freq_trace_num) self.trace_num = tn self.freq_trace_num = fn N = len(tn) for a in arrays: if getattr(self, a).shape[0] != N: s = '{} is not size {}'.format(a, N) logger.warn(s) self.bad_survey_line = "Array sizes don't match on load" def fix_trace_num(self, N, bad_traces, values): for freq, trace_array in self.freq_trace_num.items(): for t, v in zip(bad_traces, values): if v in trace_array: i = np.floor((t - 1) / 3.0) print 'freq trace i value is',freq, i, trace_array[i], i+1 trace_array[i] = t self.freq_trace_num[freq] = trace_array for f, v in self.freq_trace_num.items(): print 'max is ', f, v.max(), v.shape self.trace_num = np.arange(1, N + 1)
class DataContext(ListenableMixin, PersistableMixin, DictMixin): """ A simple context which fires events. """ # The name of the context. name = Str() # The underlying dictionary. subcontext = Supports(IContext, factory=dict) #### IContext interface #################################################### def __contains__(self, key): return key in self.subcontext def __getitem__(self, key): return self.subcontext[key] def __setitem__(self, key, value): if not self.allows(value, key): raise ValueError("cannot assign value: %s = %s" % (key, value)) # Figure out if the item was added or modified added = [] modified = [] if key in self.subcontext.keys(): modified = [key] else: added = [key] self.subcontext[key] = value # Event fired so that GUI listeners can update self._fire_event(added=added, modified=modified) def __delitem__(self, key): if key in self.subcontext: del self.subcontext[key] self._fire_event(removed=[key]) else: raise KeyError(key) def keys(self): """ Returns the list of keys available in the context. Returns ------- keys : list of str """ return self.subcontext.keys() # Expose DictMixin's get method over HasTraits'. get = DictMixin.get def __str__(self): # Maybe a good default string return '%s(name=%r)' % (type(self).__name__, self.name) def __repr__(self): # Maybe a good default representation return '%s(name=%r)' % (type(self).__name__, self.name) #### DictMixin interface ################################################## def __cmp__(self, other): # Dont allow objects of different inherited classes to be equal. # This WILL ALLOW different instances with different names but the # same keys and values to be equal # # Subclasses may wish to override this to compare different attributes # cls_cmp = cmp(self.__class__, other.__class__) if cls_cmp != 0: return cls_cmp return DictMixin.__cmp__(self, other) #### IRestrictedContext interface ########################################## def allows(self, value, name=None): """ Determines whether this value is allowed in this context. Only strings are allowed for 'name'. Typically, this is used to limit the types of objects allowed into the context. It could also be used to restrict specific values (ie. the shape of an array) and even on the name... Parameters ---------- value : object name : str, optional Returns ------- allowed : bool """ # WORKAROUND: subclassing from DataContext can cause the adapter to put the real # context in subcontext if used in a class which adapts. In this case # call allows on the real context. This only happens occasionally and is a bug. if self.subcontext is not None and isinstance(self.subcontext, DataContext): return self.subcontext.allows(value, name) return True #### ICheckpointable interface ############################################ def checkpoint(self): """ Make a shallow copy of the context. Technically, this is actually a fairly deep copy. All of the object structure should be replicated, but the actual dictionary storage will be shallowly copied:: copy = context.shallow_copy() copy[key] is context[key] for key in context.keys() These semantics are useful for saving out checkpointed versions of the context for implementing an undo/redo stack. They may not be useful for other purposes. Returns ------- copy : IContext """ copy = self.clone_traits() checkpointable_subcontext = adapt(self.subcontext, ICheckpointable) copy.subcontext = checkpointable_subcontext.checkpoint() return copy
class Application(HasTraits): """ The main Hydropick application object """ #: application data directory application_home = Directory #: the root logger instance logger = Instance('logging.Logger') #: application data directory logging_handler = Instance('logging.Handler') #: the PyFace GUI for the application gui = Supports('pyface.i_gui.IGUI') #: the splash-screen for the application splash_screen = Supports('pyface.i_splash_screen.ISplashScreen') #: the main task window task_window = Supports('pyface.tasks.task_window.TaskWindow') #: the main task window task = Instance('pyface.tasks.task.Task') def exception_handler(self, exc_type, exc_value, exc_traceback): """ Handle un-handled exceptions """ if not isinstance(exc_value, Exception): # defer to usual exception handler sys.__excepthook__(exc_type, exc_value, exc_traceback) logging.error('Unhandled exception:', exc_info=(exc_type, exc_value, exc_traceback)) from traceback import format_tb from pyface.api import MessageDialog informative = "{0}: {1}".format(exc_type.__name__, str(exc_value)) detail = '\n'.join(format_tb(exc_traceback)) dlg = MessageDialog(severity='error', message="Unhandled Exception", informative=informative, detail=detail, size=(800,600)) dlg.open() def parse_arguments(self): import argparse parser = argparse.ArgumentParser(description="Hydropick: a hydrological survey editor") parser.add_argument('--import', help='survey data to import', dest='import_', metavar='DIR') parser.add_argument('--with-picks', help='if included, then pre and pick files will be imported', dest='with_picks_', action='store_true') parser.add_argument('-v', '--verbose', action='store_const', dest='logging', const=logging.INFO, help='verbose logging') parser.add_argument('-q', '--quiet', action='store_const', dest='logging', const=logging.WARNING, help='quiet logging') parser.add_argument('-d', '--debug', action='store_const', dest='logging', const=logging.DEBUG, help='debug logging') args = parser.parse_args() return args def init(self): # set up logging self.logger.addHandler(self.logging_handler) # parse commandline arguments args = self.parse_arguments() if args.import_: from ..io.import_survey import import_survey survey = import_survey(args.import_, args.with_picks_) self.task.survey = survey if args.logging is not None: self.logger.setLevel(args.logging) def start(self): self.logger.info('Starting application') # override the exceptionhook to display MessageDialog sys.excepthook = self.exception_handler # set up tasks self.task_window.add_task(self.task) self.task_window.open() # and we're done successfully return True def run(self): # ensure GUI instance is created gui = self.gui started = self.start() if started: self.logger.info('Starting event loop') gui.start_event_loop() self.logger.info('Event loop finished') self.stop() def stop(self): self.logger.info('Stopping application') def cleanup(self): logging.shutdown() def _application_home_default(self): home = ETSConfig.application_home if not os.path.exists(home): os.makedirs(home) return home def _logger_default(self): return logging.getLogger() def _logging_handler_default(self): from logging.handlers import RotatingFileHandler logfile = os.path.join(self.application_home, 'hydropick.log') print logfile, self.application_home handler = RotatingFileHandler(logfile, backupCount=5) handler.doRollover() # format output datefmt = '%Y%m%d:%H%M%S' line_fmt = '%(asctime)s :: %(name)s : %(levelname)s : %(message)s' formatter = logging.Formatter(line_fmt, datefmt=datefmt) handler.setFormatter(formatter) return handler def _gui_default(self): from pyface.api import GUI return GUI(splash_screen=self.splash_screen) def _task_window_default(self): from pyface.tasks.api import TaskWindow window = TaskWindow(size=(960, 720)) return window def _task_default(self): from .tasks.survey_task import SurveyTask return SurveyTask()
class MultiContext(ListenableMixin, PersistableMixin, DictMixin): """ Wrap several subcontexts. """ #: The name of the context. name = Str("multidummy") #: The underlying dictionary. subcontexts = List(Supports(IRestrictedContext, factory=DataContext)) #: Suppress subcontext modified events veto_subcontext_modified = Bool(True) def __init__(self, *subcontexts, **traits): subcontexts = list(subcontexts) super(MultiContext, self).__init__(subcontexts=subcontexts, **traits) #### IContext interface #################################################### def __contains__(self, key): for c in self.subcontexts: if key in c: return True return False def __delitem__(self, key): """ Remove the given key with [] access. Only deletes the first instance of the key. Parameters ---------- key : str Raises ------ KeyError if the kew is not available in the context. """ for c in self.subcontexts: try: del c[key] return except KeyError: continue raise KeyError(key) def __getitem__(self, key): for c in self.subcontexts: try: return c[key] except KeyError: continue raise KeyError(key) def __setitem__(self, key, value): """ Set item with [] access. The first subcontext which allows the key/value pair will get it. If an earlier subcontext has the key, but does not allow the assignment, then that key will be deleted. Later contexts with the key will be untouched. If the key/value pair cannot be assigned to anything, no deletion will take place. Parameters ---------- key : str value : object Raises ------ ValueError if the key is not permitted to be assigned that value. """ # Let subtypes dictate compatibility independently of contained contexts if not self.allows(value, key): raise ValueError('Disallowed mapping: %s = %s' % (key, safe_repr(value))) set = False blocking_contexts = [] for c in self.subcontexts: if not set: if c.allows(value, key): if key in c: added = [] current_value = c[key] try: is_modified = bool(current_value != value) except Exception: is_modified = current_value is not value if is_modified: modified = [key] c[key] = value else: modified = [] else: added = [key] modified = [] c[key] = value set = True break elif key in c: # Record this context as blocking access to the final # location of the value. blocking_contexts.append(c) # Remove all blocking instances. for c in blocking_contexts: del c[key] if not set: raise ValueError('Disallowed mapping: %s = %s' % (key, safe_repr(value))) def keys(self): return list(set(chain(*[c.keys() for c in self.subcontexts]))) # Expose DictMixin's get method over HasTraits'. get = DictMixin.get def __str__(self): # Maybe a good default string subcontext_str = '[%s]' % ', '.join([str(x) for x in self.subcontexts]) return '%s(name=%r, subcontexts=%s)' % (type(self).__name__, self.name, subcontext_str) def __repr__(self): # Maybe a good default representation return '%s(name=%r)' % (type(self).__name__, self.name) #### IRestrictedContext interface ########################################## def allows(self, value, name=None): for c in self.subcontexts: if c.allows(value, name=name): return True return False #### Trait Event Handlers ################################################## @on_trait_change('subcontexts:items_modified') def subcontexts_items_modified(self, event): """ Pass events up. """ if event is Undefined: # Nothing to do. return event.veto = self.veto_subcontext_modified self._fire_event(added=event.added, removed=event.removed, modified=event.modified, context=event.context) def _subcontexts_items_changed(self, event): """ Trait listener for items of subcontexts list. """ added = [] removed = [] # Add to the list of items added if len(event.added): for context in event.added: added.extend(context.keys()) # Add to the list of items removed if len(event.removed): for context in event.removed: removed.extend(context.keys()) self._fire_event(added=added, removed=removed) #### ICheckpointable interface ############################################ def checkpoint(self): """ Make a shallow copy of the context. Technically, this is actually a fairly deep copy. All of the object structure should be replicated, but the actual dictionary storage will be shallowly copied:: copy = context.shallow_copy() copy[key] is context[key] for key in context.keys() These semantics are useful for saving out checkpointed versions of the context for implementing an undo/redo stack. They may not be useful for other purposes. Returns ------- copy : IContext """ copy = self.clone_traits() new_subcontexts = [] for context in self.subcontexts: checkpointable_subcontext = adapt(context, ICheckpointable) new_subcontexts.append(checkpointable_subcontext.checkpoint()) copy.subcontexts = new_subcontexts return copy
class DepthLineView(HasTraits): """ View Class for working with survey line data to find depth profile. Uses a Survey class as a model and allows for viewing of various depth picking algorithms and manual editing of depth profiles. """ #========================================================================== # Traits Attributes #========================================================================== # current data session with relevant info for the current line data_session = Instance(SurveyDataSession) # name of current line in editor survey_line_name = Property(depends_on=['data_session'] ) # list of available depth lines extracted from survey line depth_lines = Property(depends_on=['data_session', 'data_session.depth_lines_updated'] ) # name of depth_line to view chosen from pulldown of all available lines. selected_depth_line_name = Str # name of hdf5_file for this survey in case we need to load survey lines hdf5_file = Str # current depth line object model = Instance(DepthLine) # set of arguments for algorithms. Assume keyword. makes dict args = Property(Str, depends_on=['model.args', 'model']) # arrays to plot index_array_size = Property(Int, depends_on=['model.index_array, model']) depth_array_size = Property(Int, depends_on=['model.depth_array, model']) # changes model to empty DepthLine for creating new line new_button = Button('New Line') # updates the data arrays for the selected line. Apply does not do this update_arrays_button = Button('Update Data') # applys settings to DepthLine updating object and updating survey line apply_button = Button('Apply') # applys settings each survey line in selected lines apply_to_group = Button('Apply to Group') # create local traits so that these options can be dynamically changed source_name = Str source_names = Property(depends_on=['model.source']) # flag allows line creation/edit to continue in apply method no_problem = Bool(False) # determines whether to show the list of selected groups and lines show_selected = Bool(False) # list of selected groups and lines by name str for information only selected = Property(List, depends_on=['current_survey_line_group', 'selected_survey_lines']) # currently selected group current_survey_line_group = Supports(ISurveyLineGroup) # Set of selected survey lines (including groups) to apply algorithm to selected_survey_lines = List(Supports(ISurveyLine)) # dict of algorithms algorithms = Dict #========================================================================== # Define Views #========================================================================== traits_view = View( 'survey_line_name', HGroup( Item('show_selected', label='Selected(show)'), UItem('selected', editor=ListEditor(style='readonly'), style='readonly', visible_when='show_selected') ), Item('selected_depth_line_name', label='View Depth Line', editor=EnumEditor(name='depth_lines')), Item('_'), VGroup(Item('object.model.survey_line_name', style='readonly'), Item('object.model.name'), Item('object.model.line_type'), Item('object.model.source'), Item('source_name', editor=EnumEditor(name='source_names')), Item('args', editor=TextEditor(auto_set=False, enter_set=False), tooltip=ARG_TOOLTIP, visible_when='object.model.source=="algorithm"' ), Item('index_array_size', style='readonly'), Item('depth_array_size', style='readonly'), Item('object.model.edited', style='readonly'), Item('object.model.color'), Item('object.model.notes', editor=TextEditor(auto_set=False, enter_set=False), style='custom', height=75, resizable=True ), Item('object.model.lock'), ), HGroup(UItem('new_button'), UItem('update_arrays_button', tooltip=UPDATE_ARRAYS_TOOLTIP), UItem('apply_button', tooltip=APPLY_TOOLTIP), UItem('apply_to_group', tooltip=APPLY_TOOLTIP) ), height=500, resizable=True, ) #========================================================================== # Defaults #========================================================================== def _selected_depth_line_name_default(self): ''' provide initial value for selected depth line in view''' return 'none' #========================================================================== # Notifications or Callbacks #========================================================================== def update_plot(self): self.data_session.depth_lines_updated = True @on_trait_change('new_button') def load_new_blank_line(self): ''' prepare for creation of new line if "none" is already selected, change depth line as if view_depth_line was "changed" to "none" (call change depth line with "none"). Otherwise change selected line to none and listener will handle it''' self.no_problem = True if self.selected_depth_line_name == 'none': self.change_depth_line(new='none') else: self.selected_depth_line_name = 'none' def depth_line_name_new(self, proposed_line, data_session=None): '''check that name is not in survey line depth lines already. Allow same name for PRE and POST lists since these are separate ''' if data_session is None: data_session = self.data_session p = proposed_line # new names should begin and end with printable characters. p.name = p.name.strip() if p.line_type == 'current surface': used = p.name in data_session.lake_depths.keys() elif p.line_type == 'pre-impoundment surface': used = p.name in data_session.preimpoundment_depths.keys() else: self.log_problem('problem checking depth_line_name_new') used = True if used: s = 'name already used. Unlock to edit existing line' self.log_problem(s) self.model.lock = True return not used @on_trait_change('update_arrays_button') def update_arrays(self, new): ''' apply chosen method to fill line arrays ''' logger.info('applying arrays update {}'.format(self.no_problem)) model = self.model if model.lock: self.log_problem('locked so cannot change/create anything') # if line is 'none' then this is a new line --'added line'-- # not a changed line. check name is new if self.selected_depth_line_name == 'none': self.depth_line_name_new(model) if self.no_problem: logger.info('no problem in update. try update') # name valid. Try to update data. if model.source == 'algorithm': alg_name = model.source_name args = model.args logger.info('applying algorithm : {}'.format(alg_name)) self.make_from_algorithm(alg_name, args) elif model.source == 'previous depth line': line_name = model.source_name self.make_from_depth_line(line_name) else: # source is sdi line. create only from sdi data s = 'source "sdi" only available at survey load' self.log_problem(s) @on_trait_change('apply_button') def apply(self, new): ''' save current setting and data to current line''' model = self.model no_depth_array = self.depth_array_size == 0 no_index_array = self.index_array_size == 0 depth_notequal_index = self.depth_array_size != self.index_array_size if no_depth_array or no_index_array or depth_notequal_index: self.no_problem = False s = 'data arrays sizes are 0 or not equal' self.log_problem(s) if self.model.name.strip() == '': self.no_problem = False s = 'depth line has no printable name' self.log_problem(s) if self.model.name != self.selected_depth_line_name: self.depth_line_name_new(model) if model.lock: self.log_problem('locked so cannot change/create anything') # add to the survey line's appropriate dictionary if self.no_problem: logger.info('saving new line') ds = self.data_session if model.line_type == 'current surface': ds.lake_depths[self.model.name] = model ds.final_lake_depth = self.model.name key = 'POST_' + model.name else: ds.preimpoundment_depths[self.model.name] = model ds.final_preimpoundment_depth = self.model.name key = 'PRE_' + model.name # set form to new line self.selected_depth_line_name = key self.update_plot() else: s = '''Could not make new line. Did you update Data? Check log for details''' self.log_problem(s) def check_name_and_arrays(self, depth_line=None): if depth_line is None: depth_line = self.model d_array_size = self._array_size(depth_line.depth_array) i_array_size = self._array_size(depth_line.index_array) no_depth_array = d_array_size == 0 no_index_array = i_array_size == 0 depth_notequal_index = d_array_size != i_array_size name = self.survey_line_name if no_depth_array or no_index_array or depth_notequal_index: self.no_problem = False s = 'data arrays sizes are 0 or not equal for {}'.format(name) self.log_problem(s) if self.model.name.strip() == '': self.no_problem = False s = 'depth line has no printable name' self.log_problem(s) @on_trait_change('apply_to_group') def apply_to_selected(self, new): ''' Apply current settings to all selected survey lines the will step through selected lines list and - check that valid algorithm selected - check if depth line exists (overwrite?) - check if line is approved (apply?) - check if line is bad - create line with name and algorithm, args color etc. - apply data and apply to make line - set as final (?) ''' # save current model to duplicate model = self.model # list of selected lines selected = self.selected_survey_lines # check that algorithm is selected and valid not_alg = self.model.source != 'algorithm' alg_choices = self.algorithms.keys() good_alg_name = self.model.source_name in alg_choices if not_alg or not good_alg_name: self.no_problem = False self.log_problem('must select valid algorithm') else: self.no_problem = True # apply to each survey line if self.no_problem: # log parameters lines_str = '\n'.join([line.name for line in selected]) s = '''Creating depth line for the following surveylines: {lines} with the following parameters: name = {name} algorithm = {algorithm} args = {args} color = {color} '''.format(lines=lines_str, name=self.model.name, algorithm=self.source_name, args=self.model.args, color=self.model.color) logger.info(s) for line in self.selected_survey_lines: if line.trace_num.size == 0: # need to load line line.load_data(self.hdf5_file) self.model = deepcopy(model) self.model.survey_line_name = line.name alg_name = model.source_name args = model.args logger.info('applying algorithm : {}'.format(alg_name)) self.make_from_algorithm(alg_name, args, survey_line=line) self.check_name_and_arrays(self.model) if self.no_problem: lname = line.name s = 'saving new depth line to surveyline {}'.format(lname) logger.info(s) print 'saving', self.model, line.name if model.line_type == 'current surface': line.lake_depths[self.model.name] = self.model line.final_lake_depth = self.model.name else: line.preimpoundment_depths[self.model.name] = self.model print line.preimpoundment_depths.keys() line.final_preimpoundment_depth = self.model.name self.model = model @on_trait_change('selected_depth_line_name') def change_depth_line(self, new): ''' selected line has changed so use the selection to change the current model to selected or create new one if none''' if new != 'none': # Existing line: edit copy of line until apply button clicked new_line = self.data_session.depth_dict[new] selected_line = deepcopy(new_line) else: selected_line = self.create_new_line() self.model = selected_line self.source_name = selected_line.source_name @on_trait_change('source_name') def _update_source_name(self): self.model.source_name = self.source_name #========================================================================== # Helper functions #========================================================================== def message(self, msg='my message'): dialog = MsgView(msg=msg) dialog.configure_traits() def log_problem(self, msg): ''' if there is a problem with any part of creating/updating a line, log it and notify user and set no_problem flag false''' self.no_problem = False logger.error(msg) self.message(msg) def make_from_algorithm(self, alg_name, args, model=None, survey_line=None): if model is None: model = self.model if survey_line is None: survey_line = self.data_session.survey_line algorithm = self.data_session.algorithms[alg_name]() trace_array, depth_array = algorithm.process_line(survey_line, **args) model.index_array = np.asarray(trace_array, dtype=np.int32) - 1 model.depth_array = np.asarray(depth_array, dtype=np.float32) return model def make_from_depth_line(self, line_name): source_line = self.data_session.depth_dict[line_name] self.model.index_array = source_line.index_array self.model.depth_array = source_line.depth_array def create_new_line(self): ''' fill in some default value and return new depth line object''' new_dline = DepthLine( survey_line_name=self.survey_line_name, name='Type New Name', line_type='pre-impoundment surface', source='algorithm', edited=False, lock=False ) logger.info('creating new depthline template') return new_dline def _array_size(self, array=None): if array is not None: size = len(array) else: size = 0 return size #========================================================================== # Get/Set methods #========================================================================== def _get_source_names(self): source = self.model.source if source == 'algorithm': names = self.data_session.algorithms.keys() elif source == 'previous depth line': names = self.data_session.depth_dict.keys() else: # if source is sdi the source name is just the file it came from names = [self.model.source_name] return names def _get_survey_line_name(self): if self.data_session: name = self.data_session.survey_line.name else: name = 'No Survey Line Selected' return name def _get_depth_lines(self): # get list of names of depthlines for the UI if self.data_session: lines = ['none'] + self.data_session.depth_dict.keys() else: lines = [] return lines def _get_index_array_size(self): a = self.model.index_array if a is not None: size = len(a) else: size = 0 return size def _get_depth_array_size(self): a = self.model.depth_array if a is not None: size = len(a) else: size = 0 return size def _get_args(self): d = self.model.args s = ','.join(['{}={}'.format(k, v) for k, v in d.items()]) return s def _set_args(self, args): ''' This is currently not very safe. Probably better and easier just to pass a string that each algorithm will parse appropriately''' s = 'dict({})'.format(args) d = eval('dict({})'.format(args)) mod_args = self.model.args if isinstance(d, dict): if mod_args != d: self.model.args = d else: s = '''Cannot make dictionary out of these arguments, Please check the format -- x=1, key=True, ...''' self.log_problem(s) if mod_args != {}: self.model.args = {} def _get_selected(self): '''make list of selected lines with selected group on top and all lines ''' group_string = 'No Group Selected' all_lines = [] if self.current_survey_line_group: group_name = self.current_survey_line_group.name group_string = 'GROUP: ' + group_name if self.selected_survey_lines: all_lines = [line.name for line in self.selected_survey_lines] num_lines = len(all_lines) else: num_lines = 0 return [group_string] + ['LINES: {}'.format(num_lines)] + all_lines
class SurveyTask(Task): """ A task for viewing and editing hydrological survey data """ #### Task interface ####################################################### id = 'hydropick.survey_task' name = 'Survey Editor' #### SurveyTask interface ################################################# # XXX perhaps bundle the survey specific things into survey manager object? #: the survey object that we are viewing survey = Supports(ISurvey) #: the currently active survey line group current_survey_line_group = Supports(ISurveyLineGroup) #: the currently active survey line that we are viewing current_survey_line = Supports(ISurveyLine) # Instance(SurveyLine)# # data object for maninpulating data for survey view and depth lines current_data_session = Instance(SurveyDataSession) #: the selected survey lines selected_survey_lines = List(Supports(ISurveyLine)) #: reference to dictionary of available depth pic algorithms # (IAlgorithm Classes) algorithms = Dict # selected depth line selected_depth_line_name = Str # traits for managing Action state ######################################## #: whether the undo stack is "clean" dirty = Property(Bool, depends_on='command_stack.clean') #: whether or not there are selected lines have_selected_lines = Property(Bool, depends_on='selected_survey_lines') #: whether or not there is a current group have_current_group = Property(Bool, depends_on='current_survey_line_group') #: the object that manages Undo/Redo stacks undo_manager = Supports(IUndoManager) #: the object that holds the Task's commands command_stack = Supports(ICommandStack) ########################################################################### # 'Task' interface. ########################################################################### def _default_layout_default(self): return TaskLayout(left=VSplitter( PaneItem('hydropick.survey_data'), PaneItem('hydropick.survey_map'), PaneItem('hydropick.survey_depth_line'), )) def _menu_bar_default(self): from apptools.undo.action.api import UndoAction, RedoAction menu_bar = SMenuBar( SMenu( SGroup(TaskAction(name="Import", method='on_import', accelerator='Ctrl+I'), id='New', name='New'), SGroup(TaskAction(name="Open", method='on_open', accelerator='Ctrl+O'), id='Open', name='Open'), SGroup(TaskAction(name="Save", method='on_save', accelerator='Ctrl+S', enabled_name='dirty'), TaskAction(name="Save As...", method='on_save_as', accelerator='Ctrl+Shift+S', enabled_name='survey'), id='Save', name='Save'), id='File', name="&File", ), SMenu( # XXX can't integrate easily with TraitsUI editors :P SGroup( UndoAction(undo_manager=self.undo_manager, accelerator='Ctrl+Z'), RedoAction(undo_manager=self.undo_manager, accelerator='Ctrl+Shift+Z'), id='UndoGroup', name="Undo Group", ), SGroup( TaskCommandAction(name='New Group', method='on_new_group', accelerator='Ctrl+Shift+N', command_stack_name='command_stack'), TaskCommandAction(name='Delete Group', method='on_delete_group', accelerator='Ctrl+Delete', enabled_name='have_current_group', command_stack_name='command_stack'), id='LineGroupGroup', name="Line Group Group", ), id='Edit', name="&Edit", ), SMenu( SGroup( TaskAction(name='Next Line', method='on_next_line', enabled_name='survey.survey_lines', accelerator='Ctrl+Right'), TaskCommandAction(name='Previous Line', method='on_previous_line', enabled_name='survey.survey_lines', accelerator='Ctrl+Left'), id='LineGroup', name='Line Group', ), SGroup( CentralPaneAction(name='Location Data', method='on_show_location_data', enabled_name='show_view', accelerator='Ctrl+Shift+D'), CentralPaneAction(name='Plot View Selection', method='on_show_plot_view_selection', enabled_name='show_view', accelerator='Ctrl+Shift+S'), id='DataGroup', name='Data Group', ), DockPaneToggleGroup(), id='View', name="&View", ), SMenu( SGroup( CentralPaneAction(name='Image Adjustment', method='on_image_adjustment', enabled_name='show_view', accelerator='Ctrl+Shift+I'), CentralPaneAction(name='Change Colormap', method='on_change_colormap', enabled_name='show_view'), CentralPaneAction(name='Cursor Freeze Key = Alt+c', method='on_cursor_freeze', enabled_name='show_view'), CentralPaneAction(name='Box zoom enable = z'), id='ToolGroup', name='Tool Group', ), id='Tools', name="&Tools", ), ) return menu_bar def _tool_bars_default(self): toolbars = [ SToolBar(TaskAction(name="Import", method='on_import', image=ImageResource('import')), TaskAction(name="Open", method='on_open', image=ImageResource('survey')), TaskAction(name="Save", method='on_save', enabled_name='dirty', image=ImageResource('save')), id='File', name="File", show_tool_names=False, image_size=(24, 24)), SToolBar(TaskCommandAction(name='New Group', method='on_new_group', command_stack_name='command_stack', image=ImageResource('new-group')), TaskCommandAction(name='Delete Group', method='on_delete_group', enabled_name='have_current_group', command_stack_name='command_stack', image=ImageResource('delete-group')), TaskAction(name='Previous Line', method='on_previous_line', enabled_name='survey.survey_lines', image=ImageResource("arrow-left")), TaskAction(name='Next Line', method='on_next_line', enabled_name='survey.survey_lines', image=ImageResource("arrow-right")), id='Survey', name="Survey", show_tool_names=False, image_size=(24, 24)), ] return toolbars def activated(self): """ Overriden to set the window's title. """ self.window.title = self._window_title() def create_central_pane(self): """ Create the central pane: the editor pane. """ from .survey_line_pane import SurveyLinePane pane = SurveyLinePane(survey_task=self) # listen for changes to the current survey line self.on_trait_change(lambda new: setattr(pane, 'survey_line', new), 'current_survey_line') return pane def create_dock_panes(self): """ Create the map pane and hook up listeners """ from .survey_data_pane import SurveyDataPane from .survey_map_pane import SurveyMapPane from .survey_depth_pane import SurveyDepthPane data = SurveyDataPane(survey=self.survey) self.on_trait_change(lambda new: setattr(data, 'survey', new), 'survey') map = SurveyMapPane(survey=self.survey) self.on_trait_change(lambda new: setattr(map, 'survey', new), 'survey') depth = SurveyDepthPane() return [data, map, depth] def _survey_changed(self): from apptools.undo.api import CommandStack self.current_survey_line = None self.current_survey_line_group = None self.selected_survey_lines = [] # reset undo stack self.command_stack = CommandStack(undo_manager=self.undo_manager) self.undo_manager.active_stack = self.command_stack @on_trait_change('survey.name') def update_title(self): if self.window and self.window.active_task is self: self.window.title = self._window_title() @on_trait_change('survey.survey_lines') def survey_lines_updated(self): if self.current_survey_line not in self.survey.survey_lines: self.current_survey_line = None self.selected_survey_lines[:] = [ line for line in self.selected_survey_lines if line in self.survey_lines ] @on_trait_change('survey.survey_line_groups') def survey_line_groups_updated(self): if self.current_survey_line_group not in self.survey.survey_line_groups: self.current_survey_line_group = None ########################################################################### # 'SurveyTask' interface. ########################################################################### def on_import(self): """ Imports hydrological survey data """ from pyface.api import DirectoryDialog, OK from ...io.import_survey import import_survey # ask the user for save if needed self._prompt_for_save() survey_directory = DirectoryDialog(message="Select survey to import:", new_directory=False) if survey_directory.open() == OK: survey = import_survey(survey_directory.path) self.survey = survey def on_open(self): """ Opens a hydrological survey file """ self._prompt_for_save() raise NotImplementedError def on_save(self): """ Saves a hydrological survey file """ raise NotImplementedError def on_save_as(self): """ Saves a hydrological survey file in a different location """ raise NotImplementedError def on_new_group(self): """ Adds a new survey line group to a survey """ from ...model.survey_line_group import SurveyLineGroup from ...model.survey_commands import AddSurveyLineGroup group = SurveyLineGroup(name='Untitled', survey_lines=self.selected_survey_lines) command = AddSurveyLineGroup(data=self.survey, group=group) return command def on_delete_group(self): """ Deletes a survey line group from a survey """ from ...model.survey_line_group import SurveyLineGroup from ...model.survey_commands import DeleteSurveyLineGroup group = self.current_survey_line_group command = DeleteSurveyLineGroup(data=self.survey, group=group) return command def on_next_line(self): """ Move to the next selected line """ self.current_survey_line = self._get_next_survey_line() def on_previous_line(self): """ Move to the previous selected line """ self.current_survey_line = self._get_previous_survey_line() def _get_dirty(self): return not self.command_stack.clean def _get_have_selected_lines(self): return len(self.selected_survey_lines) != 0 def _get_have_current_group(self): return self.current_survey_line_group is not None def _command_stack_default(self): """ Return the default undo manager """ from apptools.undo.api import CommandStack command_stack = CommandStack() return command_stack def _undo_manager_default(self): """ Return the default undo manager """ from apptools.undo.api import UndoManager undo_manager = UndoManager(active_stack=self.command_stack) self.command_stack.undo_manager = undo_manager return undo_manager def _survey_default(self): from ...model.survey import Survey return Survey(name='New Survey') def _algorithms_default(self): name_list = algorithms.ALGORITHM_LIST classes = [getattr(algorithms, cls_name) for cls_name in name_list] names = [cls().name for cls in classes] logger.debug('found these algorithms: {}'.format(names)) return dict(zip(names, classes)) ########################################################################### # private interface. ########################################################################### def _window_title(self): """ Get the title of the window """ name = self.survey.name return name if name else 'Untitled' def _prompt_for_save(self): """ Check if the user wants to save changes """ from pyface.api import ConfirmationDialog, CANCEL, YES if not self.command_stack.clean: message = 'The current survey has unsaved changes. ' \ 'Do you want to save your changes?' dialog = ConfirmationDialog(parent=self.window.control, message=message, cancel=True, default=CANCEL, title='Save Changes?') result = dialog.open() if result == CANCEL: return False elif result == YES: if not self._save(): return self._prompt_for_save() return True def _save(self): """ Save changes to a survey file """ raise NotImplementedError def _get_next_survey_line(self): """ Get the next selected survey line, or next line if nothing selected """ survey_lines = self.selected_survey_lines[:] previous_survey_line = self.current_survey_line # if nothing selected, use all survey lines if len(survey_lines) == 0: survey_lines = self.survey.survey_lines[:] # if still nothing, can't do anything reasonable, but we shouldn't # have been called if len(survey_lines) == 0: return None if previous_survey_line in survey_lines: index = (survey_lines.index(previous_survey_line)+1) % \ len(survey_lines) return survey_lines[index] else: return survey_lines[0] def _get_previous_survey_line(self): """ Get the previous selected survey line, or previous line if nothing selected """ survey_lines = self.selected_survey_lines[:] previous_survey_line = self.current_survey_line # if nothing selected, use all survey lines if len(survey_lines) == 0: survey_lines = self.survey.survey_lines[:] # if still nothing, can't do anything reasonable, but we shouldn't # have been called if len(survey_lines) == 0: return None if previous_survey_line in survey_lines: index = (survey_lines.index(previous_survey_line)-1) % \ len(survey_lines) return survey_lines[index] else: return survey_lines[-1]
class SurveyLinePane(TraitsTaskPane): """ The dock pane holding the map view of the survey """ id = 'hydropick.survey_line' name = "Survey Line" survey = DelegatesTo('task') # current survey line viewed in editing pane. # listener is set up in 'task.create_central_pane' to change line. survey_line = Instance(ISurveyLine) current_data_session = DelegatesTo('task') core_samples = List(Supports(ICoreSample)) # provides string with name of line for keys or info. line_name = Property(depends_on='survey_line.name') def _get_line_name(self): if self.survey_line: return self.survey_line.name else: return 'None' # instance of survey_line view which displays selected surveyline survey_line_view = Instance(SurveyLineView) # once a valid survey line is selected a datasession will # created and stored for quick retrieval on line changes data_session_dict = Dict(Str, Instance(SurveyDataSession)) #: dictionary of (name, class) pairs for available depth pic algorithms algorithms = DelegatesTo('task') # set when survey_line is none to prevent showing invalid view. show_view = Bool(False) def on_image_adjustment(self): ''' Open dialog to adjust image (B&C : task menu)''' self.survey_line_view.image_adjustment_dialog() def on_cursor_freeze(self): ''' Currently just shows Key Binding to freeze cursor''' pass def on_change_colormap(self): ''' Open dialog to adjust image (B&C : task menu)''' self.survey_line_view.cmap_edit_dialog() def on_show_location_data(self): ''' Open dialog to show location data (task menu)''' self.survey_line_view.show_data_dialog() def on_show_plot_view_selection(self): ''' Open dialog to change which plots to view (task menu)''' self.survey_line_view.plot_view_selection_dialog() def _survey_line_changed(self): ''' handle loading of survey line view if valid line provide or else provide an empty view. ''' if self.survey_line is None: logger.warning('current survey line is None') self.show_view = False self.survey_line_view = None else: data_session = self.data_session_dict.get(self.line_name, None) if data_session is None: # create new datasession object and entry for this surveyline. if self.survey_line.trace_num.size == 0: # need to load data for this line self.survey_line.load_data(self.survey.hdf5_file) data_session = SurveyDataSession(survey_line=self.survey_line, algorithms=self.algorithms) self.data_session_dict[self.line_name] = data_session self.current_data_session = data_session # load relevant core samples into survey line # must do this before creating survey line view all_samples = self.survey.core_samples near_samples = self.survey_line.nearby_core_samples(all_samples) self.survey_line.core_samples = near_samples # create survey line view self.survey_line_view = SurveyLineView(model=data_session) self.show_view = True view = View( Item('survey_line_view', style='custom', show_label=False, visible_when='show_view'))
class AsyncExecutingContext(ExecutingContext): """Sequential, threaded pipeline for execution of a Block. This uses a (possibly shared) Executor to asynchronously execute a block within a context. This will only consume a single worker at a time from the Executor. Changes to the context are made with the '__setitem__' method (dictionary update) and may trigger an execution of the block, with inputs restricted to the current, cumulative set of context changes. Updates made while the block is being executed will result in an accumulation of the changes and execution of the block once the current execution completes. """ # The code string code = Code # The codeblock that contains the pipeline code executable = Any # The local execution namespace subcontext = Supports(IListenableContext, factory=DataContext, comparison_mode=OBJECT_IDENTITY_COMPARE) # Fired when an exception occurs during execution. # Carries the exception. exception = Event # An Executor with which to dispatch work. executor = Instance(Executor) # The cumulative changes in the context since the last successful execution _context_delta = Dict # Flag used to suppress events when internally modifying subcontext _suppress_events = Bool(False) # A lock for shared data _data_lock = Instance(threading.Lock, ()) _full_update = Bool(False) ########################################################################### #### AsyncExecutingContext Interface ########################################################################### def execute(self): """Update the context by executing the full block. This executes asynchronously. """ self._full_update = True self._update() ########################################################################### #### ExecutingContext Interface ########################################################################### def execute_for_names(self, names=None): """ Possibly execute for a set of names which have been changed. Parameters ---------- names : list of str, optional If not provided, then we'll just re-execute the whole thing. """ if names is None or None in names: # A total re-execution has been requested. self.execute() else: # Copy the affected names into the delta to signal that the block # should be executed as though they changed affected_names = list(set(names)) context = {} with self._data_lock: for key in affected_names: context[key] = self.subcontext[key] with self._data_lock: self._context_delta.update(context) self._update() def __setitem__(self, name, value): """Assign a new value into the namespace. This triggers an asyncronous update of the context via execution of the block. The 'updated' event will be fired when the execution completes. Parameters ---------- name : str The name of the variable to assign value : any The value to be assigned """ with self._data_lock: self._context_delta[name] = value context_copy = dict(self.subcontext) if name in context_copy: added = [] modified = [name] else: modified = [] added = [name] context_copy.update(self._context_delta) self._fire_event(added=added, modified=modified, context=context_copy) self._update() def __delitem__(self, name): del self.subcontext[name] ########################################################################### #### Concurrency methods and state machine. ########################################################################### # A lock for state changes. _state_lock = Instance(threading.Condition, ()) # State flag: true iff execution is currently deferred. _execution_deferred = Bool(False) # Flag indicating whether there's a need to do a new update at # some point. _update_pending = Bool(False) # The current Future instance, or None. _future = Instance(Future) @contextlib.contextmanager def _update_state(self): """Helper for state updates. Applies the state update in the body of the associated with block; starts a new future execution if necessary, and notifies all listeners of the state change. """ with self._state_lock: yield submit_new = ( self._future is None and self._update_pending and not self._execution_deferred ) if submit_new: self._update_pending = False self._future = self.executor.submit(self._worker) self._state_lock.notify_all() if submit_new: self._future.add_done_callback(self._callback) # Transition methods. def _update(self): """Update based on changes in _context_delta.""" with self._update_state(): self._update_pending = True def _callback(self, future): """Callback for the _worker""" with self._update_state(): exception = future.exception() if exception is not None: self.exception = exception self._future = None def _pause(self): """Temporarily pause execution.""" with self._update_state(): self._execution_deferred = True def _resume(self): """Resume execution.""" with self._update_state(): self._execution_deferred = False def _wait(self): """Wait until all previous assignments are finished, possibly longer.""" with self._state_lock: while self._future is not None: self._state_lock.wait() def _worker(self): """Worker for the _update method. """ # Get the current context, apply then delete the delta with self._data_lock: updated_vars = set(self._context_delta.keys()) self._suppress_events = True self.subcontext.update(self._context_delta) self._suppress_events = False context_delta = self._context_delta.copy() self._context_delta.clear() if not updated_vars and not self._full_update: # don't execute if no context delta or full update requested return if self._full_update: # signal to self.executable that we want the unrestricted block to # be executed and then clear the flag updated_vars = [] self._full_update = False try: self.subcontext.defer_events = True self.executable.execute(self.subcontext, inputs=updated_vars) self.subcontext.defer_events = False except Exception: self.subcontext.defer_events = False # If we failed to execute, put changes back into _context_delta with self._data_lock: context_delta.update(self._context_delta) self._context_delta = context_delta raise ########################################################################### #### Trait defaults ########################################################################### def _executor_default(self): return ThreadPoolExecutor(max_workers=2) def _executable_default(self): return RestrictingCodeExecutable(code=self.code) ########################################################################### #### Trait change handlers ########################################################################### def _code_changed(self, old, new): try: self.executable.code = new except Exception as e: self.exception = e return self.execute() def _defer_execution_changed(self, new): if new: self._pause() else: self._resume() @on_trait_change('subcontext.items_modified') def subcontext_items_modified(self, event): if event is Undefined: # Nothing to do. return if self._suppress_events: # These events were already manually fired in __setitem__() return event.veto = True self._fire_event(added=event.added, removed=event.removed, modified=event.modified, context=event.context)
class FormulaExecutingContext(DataContext): """A class that manages execution between a code block, and spreadsheet like expressions that can be assigned to variables""" # The underlying context data_context = Supports(IListenableContext) # The block that is generated by the expressions in the context external_code = Str("pass\n") # The user-supplied block that is merged with the expressions external_block = Instance(Block) # Whether data has changed so as to need execution -- we may want # to store up a list of variables that have changed to aid this. execution_needed = Bool(False) # Whether to automatically re-execute when a variable changes auto_execute = Bool(True) # Whether to continue on exceptions from execution continue_on_errors = Bool(True) # Whether to swallow exceptions from the block swallow_exceptions = Bool(False) # A block representing the external block plus all of the expressions _composite_block = Instance(Block) # A block representing just the expressions _expression_block = Instance(Block) # The expressions to execute _expressions = Dict _globals_context = Instance( DataContext) # May want to change to an interface spec # Whether we are currently executing on the DataContext _executing = Bool(False) # Dict interface def __setitem__(self, key, value): # FIXME this heuristic of looking for an = is somewhat brittle, but will work for our application if isinstance(value, str) and '=' in value: #This is a formula self._expressions[key] = value.split('=')[1] self._regenerate_expression_block() self._regenerate_composite_block() self.execute_block(outputs=[key]) # FIXME we should be caching these restrictions else: self.data_context[key] = value def __getitem__(self, key): try: if key in self._expressions: return repr( self.data_context[key]) + '=' + self._expressions[key] else: return self.data_context[key] except: import pdb pdb.set_trace() def keys(self): return list(self.data_context.keys()) # FIXME implement __delitem__ # Public interface def __init__(self, **kwtraits): if 'external_block' in kwtraits: self.external_block = kwtraits.pop('external_block') super(FormulaExecutingContext, self).__init__(**kwtraits) self._regenerate_expression_block() if self.external_block is None: self._external_code_changed(self.external_code) else: self.external_block = Block( [self.external_block, Block(self.external_code)]) self._regenerate_composite_block() def execute_block_if_auto(self, inputs=(), outputs=()): if self.auto_execute: self.execute_block(inputs=inputs, outputs=outputs) else: self.execution_needed = True return def execute_if_needed(self): if self.execution_needed: self.execute_block() return def execute_block(self, inputs=(), outputs=()): if self.data_context is None: return self._executing = True with self.data_context.deferred_events(): if self._composite_block is None: self._regenerate_composite_block() if inputs != () or outputs != (): block = self._composite_block.restrict(inputs=inputs, outputs=outputs) else: block = self._composite_block if self.swallow_exceptions: with self.data_context.deferred_events(): try: block.execute(self.data_context, self._globals_context, continue_on_errors=self.continue_on_errors) except Exception: pass else: with self.data_context.deferred_events(): block.execute(self.data_context, self._globals_context, continue_on_errors=self.continue_on_errors) self._executing = False execution_needed = False return def copy(self): """Make a deep copy of this FormulaExecutingContext. Useful for plot shadowing.""" new_datacontext = DataContext() for key in list(self.data_context.keys()): try: new_datacontext[key] = copy(self.data_context[key]) except: new_datacontext[key] = self.data_context[key] # turn off auto-firing of events during construction, then turn it back on # after everything is set up new = FormulaExecutingContext(data_context=new_datacontext, external_block=self.external_block, execution_needed=self.execution_needed, auto_execute=False, _expressions=self._expressions) new._regenerate_expression_block() new._regenerate_composite_block() new.auto_execute = self.auto_execute return new # Trait listeners def _data_context_changed(self): self.execution_needed = True def _external_code_changed(self, new): self.external_block = Block(new) return def _external_block_changed(self, new): self._regenerate_composite_block() self.execute_block_if_auto() def _expression_block_changed(self, new): self._regenerate_composite_block() self.execute_block_if_auto() def _regenerate_composite_block(self): self._composite_block = Block( (self.external_block, self._expression_block)) return def _regenerate_expression_block(self): exprs = [ '%s = %s' % (var, expr) for var, expr in list(self._expressions.items()) ] expression_code = '\n'.join(exprs) + '\n' self._expression_block = Block(expression_code) @on_trait_change('data_context:items_modified') def _data_context_items_modified(self, event): if not self._executing and isinstance(event, ItemsModified): inputs = set(self._composite_block.inputs).intersection( set(event.added + event.removed + event.modified)) if len(inputs) == 0: return self.execute_block_if_auto(inputs=inputs) return def __composite_block_default(self): return Block('') def __expression_block_default(self): return Block('')
class ExecutingContext(DataContext): """ A context which will execute code in response to changes in its namespace. """ # Override to provide a more specific requirement. subcontext = Supports(IListenableContext, factory=DataContext, rich_compare=False) executable = Supports(IExecutable, factory=CodeExecutable) defer_execution = Bool(False) # When execution is deferred, we need to keep a list of these events. _deferred_execution_names = List(Str, transient=True) def execute_for_names(self, names): """ Possibly execute for a set of names which have been changed. Parameters ---------- names : list of str, optional If not provided, then we'll just re-execute the whole thing. """ if self.defer_execution: if names is None: names = [None] self._deferred_execution_names.extend(names) return if names is None or None in names: # A total re-execution has been requested. affected_names = None else: affected_names = list(set(names)) with self.subcontext.deferred_events(): self.executable.execute(self.subcontext, inputs=affected_names) #### IContext interface #################################################### def __setitem__(self, key, value): self.subcontext[key] = value self.execute_for_names([key]) def __delitem__(self, key): del self.subcontext[key] self.execute_for_names([key]) #### Trait Event Handlers ################################################## @on_trait_change('defer_execution') def _defer_execution_changed(self, old, new): self._defer_execution_changed_refire(new) def _defer_execution_changed_refire(self, new): if not new: self.execute_for_names(self._deferred_execution_names) # Reset the deferred names. self._deferred_execution_names = [] @on_trait_change('subcontext.items_modified') def subcontext_items_modified(self, event): if event is Undefined: # Nothing to do. return event.veto = True self._fire_event(added=event.added, removed=event.removed, modified=event.modified, context=event.context)
class ISurveyLine(Interface): """ A class representing a single survey line """ #: the user-visible name for the line name = Str #: file location for this surveyline. Used to load data when needed. data_file_path = Str #: sample locations, an Nx2 array (example: easting/northing?) locations = Array(shape=(None, 2)) #: specifies unit for values in locations array locations_unit = Str #: array of associated lat/long available for display lat_long = Array(shape=(None, 2)) #: a dictionary mapping frequencies to intensity arrays frequencies = Dict #: array of trace numbers corresponding to each intensity pixel columns freq_trace_num = Dict #: complete trace_num set. array = combined freq_trace_num arrays trace_num = Array #: relevant core samples core_samples = List(Supports(ICoreSample)) #: depth of the lake at each location as generated by various soruces lake_depths = Dict(Str, Supports(IDepthLine)) #: final choice for line used as current lake depth for volume calculations final_lake_depth = Instance(IDepthLine) # and event fired when the lake depths are updated lake_depths_updated = Event #: The navigation track of the survey line in map coordinates navigation_line = Instance(LineString) #: pre-impoundment depth at each location as generated by various soruces preimpoundment_depths = Dict(Str, Supports(IDepthLine)) #: final choice for pre-impoundment depth to track sedimentation final_pre_imp_depth = Instance(IDepthLine) # and event fired when the lake depth is updated preimpoundment_depths_updated = Event # power values for entire trace set power = Array # gain values for entire trace set gain = Array ##: Depth corrections: ##: depth = (pixel_number_from_top * pixel_resolution) + draft - heave #: distance from sensor to water. Constant offset added to depth draft = CFloat #: array of depth corrections to vertical offset of each column (waves etc) heave = Array #: pixel resolution, depth/pixel pixel_resolution = CFloat
class TraitslikeContextWrapper(HasTraits): """ Wrap a context with traits, primarily for use with Traits UI. """ # These traits are _private so as to avoid name conflicts with the traits # that mirror the context. # The context we are wrapping. _context = Supports(IListenableContext, comparison_mode=OBJECT_IDENTITY_COMPARE) # Whether the communication between traits and context is currently active # or not. _synched = Bool(True) def add_traits(self, *args, **kwds): """ Add a set of traits to this object. Each trait corresponds to a name in the context. Each CTrait which is added will have .in_context metadata attribute as True. Parameters ---------- ``*args`` : strs These attributes will be created as Any traits. ``**kwds`` : str -> Trait These attributes will be created as the specified Traits. Example ------- >>> from traits.api import Int >>> from codetools.contexts.traitslike_context_wrapper import TraitsLikeContextWrapper >>> tcw = TraitsLikeContextWrapper(_context={}) >>> tcw.add_traits('a', 'b', c=Int) """ if self._context is not None: keys = list(self._context.keys()) else: keys = [] with self._synch_off(): for name, trait in [(x, Any()) for x in args] + list(kwds.items()): self.add_trait(name, Trait(trait, in_context=True)) # Set the value to that in the context. if name in keys: setattr(self, name, self._context[name]) else: # ... or vice-versa. self._context[name] = getattr(self, name) def _in_context_traits(self): """ Return a list of names of all of the traits which mirror context variables. """ in_context_traits = [] for name in self.trait_names(): ctrait = self.trait(name) if ctrait.in_context: in_context_traits.append(name) return in_context_traits @on_trait_change('_context') def _context_changed(self, object, name, old, new): if old is not None: old.on_trait_change(self._context_items_modified, 'items_modified', remove=True) if new is not None: new.on_trait_change(self._context_items_modified, 'items_modified') #@on_trait_change('_context.items_modified') def _context_items_modified(self, event): """ Receive change notifications from the context. """ if not self._synched or event is Undefined: # Nothing to do. return with self._synch_off(): in_context_traits = self._in_context_traits() for name in event.added + event.modified: if name in in_context_traits: setattr(self, name, self._context[name]) # The decorator doesn't work. See #1327 #@on_trait_change('+in_context') def _in_context_trait_changed(self, name, new): """ Generic trait change handler to propogate mirrored trait changes into the context. """ if not self._synched: # Nothing to do. return with self._context.deferred_events(): # Set the trait with synching off, then turn synching on before # firing events with self._synch_off(): self._context[name] = new # Instead, do this. def _anytrait_changed(self, name, old, new): trait = self.trait(name) if trait.in_context: self._in_context_trait_changed(name, new) @contextmanager def _synch_off(self): # XXX not thread-safe _old_synched = self._synched self._synched = False try: yield finally: self._synched = _old_synched
class SurveyMapPane(TraitsDockPane): """ The dock pane holding the map view of the survey """ id = 'hydropick.survey_map' name = "Map" survey = Supports(ISurvey) #: proxy for the task's current survey line current_survey_line = DelegatesTo('task') #: proxy for the task's current survey line group current_survey_line_group = DelegatesTo('task') #: reference to the task's selected survey lines selected_survey_lines = DelegatesTo('task') #: model view for map pane survey_map_view = Instance(ModelView) #: plot for map view plot = Property(Instance(Plot), depends_on='survey_map_view') def _survey_map_view_default(self): return self._get_survey_map_view() ######### Notifications ########### def _current_survey_line_changed(self): self.survey_map_view.current_survey_line = self.current_survey_line def _selected_survey_lines_changed(self): self.survey_map_view.selected_survey_lines = self.selected_survey_lines @on_trait_change('survey') def _set_survey_map_view(self): self.survey_map_view = self._get_survey_map_view() @on_trait_change('survey_map_view.current_survey_line') def map_selection_changed(self, new): if new: # survey line cannot be None line = self.survey_map_view.current_survey_line self.current_survey_line = line @on_trait_change('survey_map_view.selected_survey_lines') def lines_selection_changed(self, new): if new: # selected lines list cannot be None lines = self.survey_map_view.selected_survey_lines self.selected_survey_lines = lines ######### Pane methods ########### def _get_survey_map_view(self): if self.task is None: selected_survey_lines = [] else: selected_survey_lines = self.selected_survey_lines return SurveyMapView(model=self.survey, selected_survey_lines=selected_survey_lines) def _get_plot(self): return self.survey_map_view.plot view = View( Item('plot', editor=ComponentEditor(), width=400, height=200, show_label=False))
class MOTDServicePlugin(Plugin): """ The 'Message of the Day' plugin. This plugin provides an MOTD object as a service. """ # The Ids of the extension points that this plugin offers. MESSAGES = 'motd.messages' # The IDs of the extension points that this plugin contributes to. SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'motd.motd_service' # The plugin's name (suitable for displaying to the user). name = 'MOTD Service' #### Extension points offered by this plugin ############################## # The messages extension point. # # Notice that we use the string name of the 'IMessage' interface rather # than actually importing it. This makes sure that the import only happens # when somebody actually gets the contributions to the extension point. messages = ExtensionPoint(List(Supports('motd.model.i_message.IMessage')), id=MESSAGES, desc=""" This extension point allows you to contribute messages to the 'Message Of The Day'. """) #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ # Register the protocol as a string containing the actual module path # (do not use a module path that goes via an 'api.py' file as this does # not match what Python thinks the module is!). This allows the service # to be looked up by passing either the exact same string, or the # actual protocol object itself. motd_service_offer = ServiceOffer(protocol='motd.model.i_motd.IMOTD', factory=self._create_motd) return [motd_service_offer] ########################################################################### # Private interface. ########################################################################### def _create_motd(self): """ Create MOTD instance using list of messages """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs the motd attribute. from motd.model.motd import MOTD motd = MOTD(messages=self.messages) return motd
class MOTDStartupPlugin(Plugin): """ The 'Message of the Day' plugin. This plugin simply prints the 'Message of the Day' to stdout on application startup. """ # The Ids of the extension points that this plugin offers. MESSAGES = 'motd.messages' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'motd.motd_startup' # The plugin's name (suitable for displaying to the user). name = 'MOTD Startup' # This plugin does all of its work in this method which gets called # after application startup @on_trait_change("application:started") def on_application_started(self): """ Print the 'Message of the Day' to stdout! """ from motd.util import print_message # Get the message of the day... message = self.motd.get_message() # ... and print it. print_message(message) #### Extension points offered by this plugin ############################## # The messages extension point. # # Notice that we use the string name of the 'IMessage' interface rather # than actually importing it. This makes sure that the import only happens # when somebody actually gets the contributions to the extension point. messages = ExtensionPoint(List(Supports('motd.model.i_message.IMessage')), id=MESSAGES, desc=""" This extension point allows you to contribute messages to the 'Message Of The Day'. """) ########################################################################### # Private interface. ########################################################################### #: The motd instance motd = Supports('motd.model.i_motd.IMOTD') def _motd_default(self): """ Create MOTD instance using list of messages """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs the motd attribute. from motd.model.motd import MOTD motd = MOTD(messages=self.messages) return motd