def test_single_frame_stage_intensity(stage_factory: StageFactoryBase): from ceed.function.plugin import ConstFunc shape = EllipseShapeP1( app=None, painter=stage_factory.shape_factory, show_in_gui=False, create_add_shape=True) f: FuncGroup = FuncGroup( function_factory=stage_factory.function_factory, timebase_numerator=1, timebase_denominator=120, loop=3) # give it a float duration to see if it can handle a integer float f1 = ConstFunc( function_factory=stage_factory.function_factory, a=0, duration=1.) f2 = ConstFunc( function_factory=stage_factory.function_factory, a=1, duration=1) f.add_func(f1) f.add_func(f2) stage = make_stage( stage_factory, color_r=True, color_g=False, color_b=True) stage_factory.add_stage(stage) stage.add_func(f) stage.add_shape(shape.shape) values, n = get_stage_time_intensity(stage_factory, stage.name, 120) assert n == 3 * 2 assert len(values) == 1 colors = values[shape.name] assert len(colors) == 3 * 2 for i, (r, g, b, a) in enumerate(colors): assert math.isclose(r, i % 2) assert math.isclose(b, i % 2) assert g == 0
def test_recover_ref_stages(stage_factory: StageFactoryBase): s1 = make_stage( stage_factory, name='stage1', order='parallel', complete_on='any', color_r=True) s2 = make_stage( stage_factory, name='stage2', order='serial', complete_on='all', color_g=True) s3 = make_stage( stage_factory, name='stage3', order='parallel', complete_on='all', color_b=True) s4 = make_stage( stage_factory, name='stage4', order='serial', complete_on='any', color_g=True) g = make_stage( stage_factory, name='grouped', order='parallel', complete_on='any', color_b=True) stage_factory.add_stage(s1) stage_factory.add_stage(s2) stage_factory.add_stage(s3) stage_factory.add_stage(s4) stage_factory.add_stage(g) g.add_stage(stage_factory.get_stage_ref(name='stage1')) g.add_stage(stage_factory.get_stage_ref(name='stage2')) g.add_stage(stage_factory.get_stage_ref(name='stage3')) g.add_stage(stage_factory.get_stage_ref(name='stage4')) stages = stage_factory.save_stages() assert len(stages) == 5 func_name_map = {} old_name_to_shape_map = {} recovered_stages, name_mapping = stage_factory.recover_stages( stages, func_name_map, old_name_to_shape_map) assert len(recovered_stages) == 5 assert len(name_mapping) == 5 for s_name in ('stage1', 'stage2', 'stage3', 'stage4', 'grouped'): assert s_name in name_mapping assert name_mapping[s_name] != s_name assert s_name in stage_factory.stage_names assert name_mapping[s_name] in stage_factory.stage_names original_s = stage_factory.stage_names[s_name] new_s = stage_factory.stage_names[name_mapping[s_name]] assert_stages_same(original_s, new_s, compare_name=False) assert original_s.name != new_s.name assert new_s.name.startswith(original_s.name) new_g: CeedStage = stage_factory.stage_names[name_mapping['grouped']] assert len(new_g.stages) == 4 stage: CeedStageRef for stage, name in zip( new_g.stages, ('stage1', 'stage2', 'stage3', 'stage4')): assert isinstance(stage, CeedStageRef) assert stage.stage is stage_factory.stage_names[name_mapping[name]]
def test_return_not_added_stage_ref(stage_factory: StageFactoryBase): from ceed.stage import CeedStageRef s = make_stage(stage_factory) ref = CeedStageRef( stage_factory=stage_factory, function_factory=stage_factory.function_factory, shape_factory=stage_factory.shape_factory, stage=s) with pytest.raises(ValueError): stage_factory.return_stage_ref(ref)
def __init__(self, open_player_thread=True, **kwargs): self.drag_controller = CeedDragNDrop() self.function_factory = FunctionFactoryBase() register_all_functions(self.function_factory) self.stage_factory = StageFactoryBase( function_factory=self.function_factory, shape_factory=None) self.player = CeedPlayer(open_player_thread=open_player_thread) self.view_controller = ControllerSideViewControllerBase() self.ceed_data = CeedDataWriterBase() self.data_serializer = DataSerializerBase() super(CeedApp, self).__init__(**kwargs) self.load_app_settings_from_file() self.apply_app_settings()
def test_stage_ref(stage_factory: StageFactoryBase): s = make_stage(stage_factory) s.name = 'me stage' s2 = make_stage(stage_factory) stage_factory.add_stage(s) ref1 = stage_factory.get_stage_ref(name='me stage') ref2 = stage_factory.get_stage_ref(stage=s2) assert ref1.stage is s assert ref2.stage is s2 assert s.has_ref assert s2.has_ref assert s in stage_factory._stage_ref assert s2 in stage_factory._stage_ref stage_factory.return_stage_ref(ref1) assert ref2.stage is s2 assert not s.has_ref assert s2.has_ref assert s not in stage_factory._stage_ref assert s2 in stage_factory._stage_ref stage_factory.return_stage_ref(ref2) assert not s.has_ref assert not s2.has_ref assert s not in stage_factory._stage_ref assert s2 not in stage_factory._stage_ref
def test_expand_ref_stages(stage_factory: StageFactoryBase): g1 = make_stage(stage_factory, order='parallel') g2 = make_stage(stage_factory, order='parallel') ref_g2 = stage_factory.get_stage_ref(stage=g2) g1.add_stage(ref_g2) s = make_stage( stage_factory, order='parallel', color_r=True, complete_on='any') g2.add_stage(s) s1 = make_stage( stage_factory, order='parallel', color_b=True, complete_on='any') g2.add_stage(s1) s2 = make_stage( stage_factory, order='parallel', color_r=False, complete_on='all') g1.add_stage(s2) s3 = make_stage( stage_factory, order='serial', color_r=True, color_g=True, complete_on='any') ref_f3 = stage_factory.get_stage_ref(stage=s3) g1.add_stage(ref_f3) g3 = make_stage(stage_factory, order='parallel') g1.add_stage(g3) s4 = make_stage( stage_factory, order='parallel', color_b=True, color_r=True, complete_on='any') ref_f4 = stage_factory.get_stage_ref(stage=s4) g3.add_stage(ref_f4) s5 = make_stage( stage_factory, order='parallel', color_b=True, color_g=True, complete_on='all') g3.add_stage(s5) assert list(g1.get_stages(step_into_ref=False)) == \ [g1, ref_g2, s2, ref_f3, g3, ref_f4, s5] assert list(g1.get_stages(step_into_ref=True)) == \ [g1, g2, s, s1, s2, s3, g3, s4, s5] g1_copy = g1.copy_expand_ref() # the copy shouldn't have any refs assert len(list(g1_copy.get_stages(step_into_ref=False))) == \ len(list(g1.get_stages(step_into_ref=True))) for original_f, new_f in zip( g1.get_stages(step_into_ref=True), g1_copy.get_stages(step_into_ref=False)): assert_stages_same(original_f, new_f, compare_name=False)
def test_can_other_stage_be_added_ref(stage_factory: StageFactoryBase): g1 = make_stage(stage_factory) g2 = make_stage(stage_factory) ref_g2 = stage_factory.get_stage_ref(stage=g2) g1.add_stage(ref_g2) s1 = make_stage(stage_factory, order='parallel') g2.add_stage(s1) s2 = make_stage(stage_factory, complete_on='any') g1.add_stage(s2) g3 = make_stage(stage_factory) g1.add_stage(g3) s4 = make_stage(stage_factory, order='parallel', complete_on='all') g3.add_stage(s4) assert g1.can_other_stage_be_added(g2) assert g1.can_other_stage_be_added(ref_g2) assert g1.can_other_stage_be_added(g3) assert not g1.can_other_stage_be_added(g1) assert not g2.can_other_stage_be_added(g1) assert g2.can_other_stage_be_added(g3) assert not g2.can_other_stage_be_added(g2) assert not g2.can_other_stage_be_added(ref_g2) assert not g3.can_other_stage_be_added(g1) assert g3.can_other_stage_be_added(g2) assert g3.can_other_stage_be_added(ref_g2) assert not g3.can_other_stage_be_added(g3)
def stage_factory(function_factory: FunctionFactoryBase, shape_factory: CeedPaintCanvasBehavior) -> StageFactoryBase: stage_factory = StageFactoryBase(function_factory=function_factory, shape_factory=shape_factory) add_prop_watch(stage_factory, 'on_changed', 'test_changes_count') yield stage_factory
def test_shape_add_remove(stage_factory: StageFactoryBase): assert not stage_factory.stages assert not stage_factory.stage_names stage = SerialAllStage(stage_factory=stage_factory, show_in_gui=False) stage.create_stage() stage = stage.stage # add stage stage_factory.test_changes_count = 0 stage_factory.add_stage(stage) assert stage in stage_factory.stages assert stage.name in stage_factory.stage_names assert stage is stage_factory.stage_names[stage.name] assert stage_factory.test_changes_count # remove shape stage_factory.test_changes_count = 0 assert stage_factory.remove_stage(stage) assert stage not in stage_factory.stages assert stage.name not in stage_factory.stage_names assert stage_factory.test_changes_count # remove same shape again with pytest.raises(ValueError): stage_factory.remove_stage(stage)
def __init__(self, **kwargs): self.view_controller = ViewSideViewControllerBase() self.ceed_data = CeedDataWriterBase() self.data_serializer = DataSerializerBase() self.function_factory = FunctionFactoryBase() register_all_functions(self.function_factory) self.shape_factory = CeedPaintCanvasBehavior() self.stage_factory = StageFactoryBase( function_factory=self.function_factory, shape_factory=self.shape_factory) super(CeedViewApp, self).__init__(**kwargs)
def load_experiment(self, experiment): self._block = block = self._nix_file.blocks[ CeedDataWriterBase.get_experiment_block_name(experiment)] section = self._nix_file.sections['experiment{}_metadata'.format( experiment)] self.loaded_experiment = experiment self.experiment_stage_name = section['stage'] self.experiment_notes = section['notes'] if 'notes' in section else '' self.experiment_start_time = float( section['save_time']) if 'save_time' in section else 0 config = section.sections['app_config'] config_data = {} for prop in config.props: config_data[prop.name] = yaml_loads(read_nix_prop(prop)) view = self.view_controller = ViewControllerBase() ser = self.data_serializer = DataSerializerBase() func = self.function_factory = FunctionFactoryBase() register_all_functions(func) shape = self.shape_factory = CeedPaintCanvasBehavior() stage = self.stage_factory = StageFactoryBase(function_factory=func, shape_factory=shape) for name, obj in { 'view': view, 'serializer': ser, 'function': func }.items(): if name in config_data['app_settings']: apply_config(obj, config_data['app_settings'][name]) self.populate_config(config_data, shape, func, stage) self.experiment_cam_image = self.read_image_from_block(self._block) data = self.shapes_intensity = {} for item in block.data_arrays: if not item.name.startswith('shape_'): continue data[item.name[6:]] = item self.led_state = block.data_arrays['led_state'] if ('ceed_mcs_alignment' in self._nix_file.blocks and 'experiment_{}'.format(experiment) in self._nix_file.blocks['ceed_mcs_alignment'].data_arrays): self.electrode_intensity_alignment = self._nix_file.blocks[ 'ceed_mcs_alignment'].data_arrays['experiment_{}'.format( experiment)] else: self.electrode_intensity_alignment = None
def test_get_stage_ref(stage_factory: StageFactoryBase): g1 = make_stage(stage_factory) g2 = make_stage(stage_factory) ref_g2 = stage_factory.get_stage_ref(stage=g2) g1.add_stage(ref_g2) s1 = make_stage(stage_factory, order='parallel') g2.add_stage(s1) s2 = make_stage(stage_factory, complete_on='any') g1.add_stage(s2) assert list(g1.get_stages()) == [g1, g2, s1, s2] assert list(g1.get_stages(step_into_ref=False)) == [g1, ref_g2, s2]
def get_stage_time_intensity( stage_factory: StageFactoryBase, stage_name: str, frame_rate): tick = stage_factory.tick_stage(stage_name) # the sampling rate at which we sample the functions frame_rate = int(frame_rate) obj_values = defaultdict(list) count = 0 while True: count += 1 try: next(tick) shape_values = tick.send(Fraction(count, frame_rate)) except StageDoneException: break values = stage_factory.fill_shape_gl_color_values( None, shape_values) for name, r, g, b, a in values: obj_values[name].append((r, g, b, a)) return obj_values, count - 1
def test_simple_stage_intensity(stage_factory: StageFactoryBase): from ceed.function.plugin import LinearFunc shape = EllipseShapeP1( app=None, painter=stage_factory.shape_factory, show_in_gui=False, create_add_shape=True) shape2 = EllipseShapeP2( app=None, painter=stage_factory.shape_factory, show_in_gui=False, create_add_shape=True) f: LinearFunc = LinearFunc( function_factory=stage_factory.function_factory, b=0, m=.1, duration=5) stage = make_stage( stage_factory, color_r=True, color_g=False, color_b=True) stage_factory.add_stage(stage) stage.add_func(f) stage.add_shape(shape.shape) stage.add_shape(shape2.shape) values, n = get_stage_time_intensity(stage_factory, stage.name, 10) assert n == 5 * 10 assert len(values) == 2 colors = values[shape.name] colors2 = values[shape2.name] assert len(colors) == 10 * 5 for i, (r, g, b, a) in enumerate(colors): assert math.isclose(r, i / 10 * .1) assert math.isclose(b, i / 10 * .1) assert g == 0 for i, (r, g, b, a) in enumerate(colors2): assert math.isclose(r, i / 10 * .1) assert math.isclose(b, i / 10 * .1) assert g == 0
def __init__(self, **kwargs): drag = self.drag_controller = CeedDragNDrop() drag.knsname = 'dragger' self.function_factory = FunctionFactoryBase() register_all_functions(self.function_factory) self.stage_factory = StageFactoryBase( function_factory=self.function_factory, shape_factory=None) self.player = CeedPlayer() self.view_controller = ControllerSideViewControllerBase() self.ceed_data = CeedDataWriterBase() self.data_serializer = DataSerializerBase() self.remote_viewer = RemoteViewerListenerBase() self.remote_player = CeedRemotePlayer() super(CeedApp, self).__init__(**kwargs) self.load_app_settings_from_file() self.apply_app_settings()
def test_clear_stages(stage_factory: StageFactoryBase): assert not stage_factory.stages assert not stage_factory.stage_names stage = SerialAllStage(stage_factory=stage_factory, show_in_gui=False) stage.create_stage() stage = stage.stage stage2 = SerialAllStage(stage_factory=stage_factory, show_in_gui=False) stage2.create_stage() stage2 = stage2.stage stage_factory.add_stage(stage) stage_factory.add_stage(stage2) assert stage_factory.stages == [stage, stage2] stage_factory.test_changes_count = 0 stage_factory.clear_stages() assert not stage_factory.stages assert not stage_factory.stage_names
def load_application_data(self): self.app_logs = self.app_notes = '' if 'app_logs' in self._nix_file.sections: self.app_logs = self._nix_file.sections['app_logs']['log_data'] self.app_notes = self._nix_file.sections['app_logs']['notes'] config = self._nix_file.sections['app_config'] config_data = {} for prop in config.props: config_data[prop.name] = yaml_loads(read_nix_prop(prop)) self.ceed_version = config_data.get('ceed_version', '') view = ViewControllerBase() ser = DataSerializerBase() func = FunctionFactoryBase() register_all_functions(func) shape = CeedPaintCanvasBehavior() stage = StageFactoryBase(function_factory=func, shape_factory=shape) for name, obj in { 'view': view, 'serializer': ser, 'function': func }.items(): if name in config_data['app_settings']: apply_config(obj, config_data['app_settings'][name]) self.populate_config(config_data, shape, func, stage) self.app_config = { 'view_controller': view, 'data_serializer': ser, 'function_factory': func, 'shape_factory': shape, 'stage_factory': stage, }
def test_copy_stages(stage_factory: StageFactoryBase): s1 = make_stage( stage_factory, name='stage1', order='parallel', complete_on='any', color_r=True) s2 = make_stage( stage_factory, name='stage2', order='serial', complete_on='all', color_g=True) s3 = make_stage( stage_factory, name='stage3', order='parallel', complete_on='all', color_b=True) s4 = make_stage( stage_factory, name='stage4', order='serial', complete_on='any', color_g=True) g = make_stage( stage_factory, name='grouped', order='parallel', complete_on='any', color_b=True) stage_factory.add_stage(s1) stage_factory.add_stage(s2) stage_factory.add_stage(s3) stage_factory.add_stage(s4) g.add_stage(stage_factory.get_stage_ref(name='stage1')) g.add_stage(stage_factory.get_stage_ref(name='stage2')) g.add_stage(stage_factory.get_stage_ref(name='stage3')) g.add_stage(stage_factory.get_stage_ref(name='stage4')) for stage in (s1, s2, s3, s4): stage_copy = deepcopy(stage) assert stage is not stage_copy assert isinstance(stage_copy, stage.__class__) assert_stages_same(stage, stage_copy, compare_name=False) stage_copy = deepcopy(g) assert len(stage_copy.stages) == 4 for new_s, original_s in zip(stage_copy.stages, g.stages): assert new_s is not original_s assert isinstance(new_s, CeedStageRef) assert isinstance(original_s, CeedStageRef) assert new_s.stage is original_s.stage
class CeedApp(BaseKivyApp): '''The app which runs the GUI. ''' __config_props__ = ('last_directory', ) last_directory = StringProperty('~') kv_loaded = False """For tests, we don't want to load kv multiple times. """ yesno_prompt = ObjectProperty(None, allownone=True) '''Stores a instance of :class:`YesNoPrompt` that is automatically created by this app class. That class is described in ``base_kivy_app/graphics.kv``. ''' function_factory = None # type: FunctionFactoryBase player = None # type: CeedPlayer view_controller = None # type: ControllerSideViewControllerBase ceed_data = None # type: CeedDataWriterBase data_serializer = None # type: DataSerializerBase stage_factory = None # type: StageFactoryBase shape_factory = ObjectProperty(None, rebind=True) # type: CeedPainter # remote_player = None # type: CeedRemotePlayer agreed_discard = False drag_controller = ObjectProperty(None, rebind=True) # type: CeedDragNDrop stages_container = ObjectProperty(None, rebind=True) # type: StageList funcs_container = ObjectProperty(None, rebind=True) # type: FuncList shapes_container = ObjectProperty(None, rebind=True) # type: ShapeList shape_groups_container = ObjectProperty( None, rebind=True) # type: ShapeGroupList pinned_graph = None """PinnedGraph into which the stage graph may be pinned. """ mea_align_widget = ObjectProperty(None, rebind=True) # type: MEAArrayAlign central_display = ObjectProperty(None, rebind=True) # type: BufferImage _processing_error = False @classmethod def get_config_classes(cls): d = super(CeedApp, cls).get_config_classes() d['function'] = FunctionFactoryBase d['view'] = ControllerSideViewControllerBase d['data'] = CeedDataWriterBase d['serializer'] = DataSerializerBase d['player'] = CeedPlayer return d def get_config_instances(self): d = super(CeedApp, self).get_config_instances() d['function'] = self.function_factory d['view'] = self.view_controller d['data'] = self.ceed_data d['serializer'] = self.data_serializer d['player'] = self.player return d def __init__(self, open_player_thread=True, **kwargs): self.drag_controller = CeedDragNDrop() self.function_factory = FunctionFactoryBase() register_all_functions(self.function_factory) self.stage_factory = StageFactoryBase( function_factory=self.function_factory, shape_factory=None) self.player = CeedPlayer(open_player_thread=open_player_thread) self.view_controller = ControllerSideViewControllerBase() self.ceed_data = CeedDataWriterBase() self.data_serializer = DataSerializerBase() super(CeedApp, self).__init__(**kwargs) self.load_app_settings_from_file() self.apply_app_settings() def load_app_kv(self): if CeedApp.kv_loaded: return CeedApp.kv_loaded = True base = dirname(__file__) # Builder.load_file(join(base, 'graphics', 'graphics.kv')) Builder.load_file(join(base, 'ceed_style.kv')) Builder.load_file(join(base, 'player', 'player_style.kv')) Builder.load_file(join(base, 'shape', 'shape_style.kv')) Builder.load_file(join(base, 'function', 'func_style.kv')) Builder.load_file(join(base, 'stage', 'stage_style.kv')) Builder.load_file(join(base, 'view', 'view_style.kv')) Builder.load_file(join(base, 'storage', 'storage_style.kv')) def build(self): self.load_app_kv() self.yesno_prompt = Factory.FlatYesNoPrompt() self.player.create_widgets() root = Factory.get('MainView')() return super(CeedApp, self).build(root) def _clear_all(self): self.funcs_container.clear_all() self.stages_container.clear_all() def on_start(self): self.stage_factory.shape_factory = self.shape_factory remove_shapes_upon_deletion( self.stage_factory, self.shape_factory, self.stages_container.remove_shape_from_stage) self.shape_factory.shape_widgets_list = self.shapes_container self.ceed_data.stage_display_callback = \ self.stages_container.show_stage self.ceed_data.func_display_callback = \ self.funcs_container.show_function self.ceed_data.clear_all_callback = self._clear_all HighightButtonBehavior.init_class() self.ceed_data.create_file('') self.stage_factory.fbind('on_changed', self.changed_callback) self.function_factory.fbind('on_changed', self.changed_callback) for func in self.function_factory.funcs_inst_default.values(): func.fbind('on_changed', self.changed_callback) self.shape_factory.fbind('on_changed', self.changed_callback) self.view_controller.fbind('on_changed', self.changed_callback) self.set_tittle() self.ceed_data.fbind('filename', self.set_tittle) self.ceed_data.fbind('config_changed', self.set_tittle) self.ceed_data.fbind('has_unsaved', self.set_tittle) self.ceed_data.fbind('read_only_file', self.set_tittle) self.view_controller.set_led_mode(self.view_controller.LED_mode_idle) def set_tittle(self, *largs): ''' Sets the title of the window using the currently running tab. This is called at 1Hz. ''' star = '' if self.ceed_data.has_unsaved or self.ceed_data.config_changed: star = '*' read_only = '' if self.ceed_data.read_only_file: read_only = ' - Read Only' if self.ceed_data.filename: filename = ' - {}'.format(self.ceed_data.filename) else: filename = ' - Unnamed File' Window.set_title('Ceed v{}, CPL lab{}{}{}'.format( ceed.__version__, star, filename, read_only)) def changed_callback(self, *largs, **kwargs): self.ceed_data.config_changed = True def check_close(self): if self.view_controller.stage_active or self.ceed_data.data_thread: self._close_message = 'Cannot close during an experiment.' return False self.player.stop() self.view_controller.stop_process() self.view_controller.finish_stop_process() if not self.ceed_data.ui_close(app_close=True): self._close_message = '' return False return True def handle_exception(self, *largs, **kwargs): processing = self._processing_error self._processing_error = True val = super(CeedApp, self).handle_exception(*largs, **kwargs) if not processing: self.view_controller.request_stage_end() self._processing_error = False return val def clean_up(self): super(CeedApp, self).clean_up() if self.ceed_data is not None: if self.ceed_data.backup_event is not None: self.ceed_data.backup_event.cancel() self.ceed_data.backup_event = None self.ceed_data.clear_all_callback = None if self.stage_factory is not None: self.stage_factory.funbind('on_changed', self.changed_callback) if self.function_factory is not None: self.function_factory.funbind('on_changed', self.changed_callback) if self.shape_factory is not None: self.shape_factory.funbind('on_changed', self.changed_callback) if self.view_controller is not None: self.view_controller.funbind('on_changed', self.changed_callback) for func in self.function_factory.funcs_inst_default.values(): func.funbind('on_changed', self.changed_callback) if self.view_controller is not None: self.view_controller.stop_process() self.view_controller.finish_stop_process() if self.ceed_data is not None: self.ceed_data.stop_experiment() self.ceed_data.funbind('filename', self.set_tittle) self.ceed_data.funbind('config_changed', self.set_tittle) self.ceed_data.funbind('has_unsaved', self.set_tittle) self.ceed_data.funbind('read_only_file', self.set_tittle) self.dump_app_settings_to_file() self.player.clean_up() HighightButtonBehavior.uninit_class()
class CeedApp(BaseKivyApp): """The app which runs the main Ceed GUI. """ _config_props_ = ('last_directory', 'external_function_plugin_package', 'external_stage_plugin_package') _config_children_ = { 'function': 'function_factory', 'view': 'view_controller', 'data': 'ceed_data', 'serializer': 'data_serializer', 'player': 'player', } last_directory = StringProperty('~') """The last directory opened in the GUI. """ external_function_plugin_package: str = '' """The name of an external function plugin package that contains additional functions to be displayed in the GUI to the user. See :mod:`~ceed.function.plugin` for details. """ external_stage_plugin_package: str = '' """The name of an external stage plugin package that contains additional stages to be displayed in the GUI to the user. See :mod:`~ceed.stage.plugin` for details. """ kv_loaded = False """For tests, we don't want to load kv multiple times so we only load kv if it wasn't loaded before. """ yesno_prompt = ObjectProperty(None, allownone=True) '''Stores a instance of :class:`YesNoPrompt` that is automatically created by this app class. That class is described in ``base_kivy_app/graphics.kv`` and shows a prompt with yes/no options and callback. ''' function_factory: FunctionFactoryBase = None """The :class:`~ceed.function.FunctionFactoryBase` that contains all the functions shown in the GUI. """ player: CeedPlayer = None """The :class:`ceed.player.CeedPlayer` used to play the video camera and record the images to disk. """ view_controller: ControllerSideViewControllerBase = None """The :class:`~ceed.view.controller.ControllerSideViewControllerBase` used to run the experiment and display the stages. """ ceed_data: CeedDataWriterBase = None """The :class:`~ceed.storage.controller.CeedDataWriterBase` used to load and save the data to disk. """ data_serializer: DataSerializerBase = None """The :class:`~ceed.storage.controller.DataSerializerBase` used to generate the corner pixel values for Ceed-MCS temporal synchronization. """ stage_factory: StageFactoryBase = None """The :class:`~ceed.stage.StageFactoryBase` that contains all the stages shown in the GUI. """ shape_factory: CeedPainter = ObjectProperty(None, rebind=True) """The :class:`~ceed.shape.shape_widgets.CeedPainter` used to draw shapes and contains all the shapes shown in the GUI. """ agreed_discard = False drag_controller: CeedDragNDrop = ObjectProperty(None, rebind=True) """The :class:`~ceed.graphics.CeedDragNDrop` used for dragging and dropping widgets on in the GUI. """ stages_container: StageList = ObjectProperty(None, rebind=True) """The :class:`~ceed.stage.stage_widgets.StageList` widget that contains all the root stages' widgets in the GUI. """ funcs_container: FuncList = ObjectProperty(None, rebind=True) """The :class:`~ceed.function.func_widgets.FuncList` widget that contains all the root functions' widgets in the GUI. """ shapes_container: ShapeList = ObjectProperty(None, rebind=True) """The :class:`~ceed.shape.shape_widgets.ShapeList` widget that contains all the shapes' widgets in the GUI. """ shape_groups_container = ObjectProperty( None, rebind=True) # type: ShapeGroupList """The :class:`~ceed.shape.shape_widgets.ShapeGroupList` widget that contains all the shape groups' widgets in the GUI. """ pinned_graph = None """PinnedGraph widget into which the experiment preview graph may be pinned. When pinned, it's displayed not as a popup, but as a flat widget. """ mea_align_widget: MEAArrayAlign = ObjectProperty(None, rebind=True) """The :class:`~ceed.view.view_widgets.MEAArrayAlign` widget used to align the MEA grid to the camera. """ central_display: BufferImage = ObjectProperty(None, rebind=True) """The :class:`~base_kivy_app.graphics.BufferImage` widget into which the camera widget is drawn. """ _processing_error = False def __init__(self, open_player_thread=True, **kwargs): self.drag_controller = CeedDragNDrop() self.function_factory = FunctionFactoryBase() register_all_functions(self.function_factory) self.stage_factory = StageFactoryBase( function_factory=self.function_factory, shape_factory=None) register_all_stages(self.stage_factory) self.player = CeedPlayer(open_player_thread=open_player_thread) self.view_controller = ControllerSideViewControllerBase() self.ceed_data = CeedDataWriterBase() self.data_serializer = DataSerializerBase() super(CeedApp, self).__init__(**kwargs) self.load_app_settings_from_file() self.apply_app_settings() def load_app_kv(self): """Loads the app's kv files, if not yet loaded. """ if CeedApp.kv_loaded: return CeedApp.kv_loaded = True base = dirname(__file__) # Builder.load_file(join(base, 'graphics', 'graphics.kv')) Builder.load_file(join(base, 'ceed_style.kv')) Builder.load_file(join(base, 'player', 'player_style.kv')) Builder.load_file(join(base, 'shape', 'shape_style.kv')) Builder.load_file(join(base, 'function', 'func_style.kv')) Builder.load_file(join(base, 'stage', 'stage_style.kv')) Builder.load_file(join(base, 'view', 'view_style.kv')) Builder.load_file(join(base, 'storage', 'storage_style.kv')) def build(self): self.load_app_kv() self.yesno_prompt = Factory.FlatYesNoPrompt() self.player.create_widgets() root = Factory.get('MainView')() return super(CeedApp, self).build(root) def _clear_all(self): self.funcs_container.clear_all() self.stages_container.clear_all() def on_start(self): if self.external_function_plugin_package: register_external_functions(self.function_factory, self.external_function_plugin_package) if self.external_stage_plugin_package: register_external_stages(self.stage_factory, self.external_stage_plugin_package) self.stage_factory.shape_factory = self.shape_factory remove_shapes_upon_deletion( self.stage_factory, self.shape_factory, self.stages_container.remove_shape_from_stage) self.shape_factory.shape_widgets_list = self.shapes_container self.ceed_data.stage_display_callback = \ self.stages_container.show_stage self.ceed_data.func_display_callback = \ self.funcs_container.show_function self.ceed_data.clear_all_callback = self._clear_all HighightButtonBehavior.init_class() self.ceed_data.create_file('') self.stage_factory.fbind('on_changed', self.changed_callback) for stage in self.stage_factory.stages_inst_default.values(): stage.fbind('on_changed', self.changed_callback) self.function_factory.fbind('on_changed', self.changed_callback) for func in self.function_factory.funcs_inst_default.values(): func.fbind('on_changed', self.changed_callback) self.shape_factory.fbind('on_changed', self.changed_callback) self.view_controller.fbind('on_changed', self.changed_callback) self.set_tittle() self.ceed_data.fbind('filename', self.set_tittle) self.ceed_data.fbind('config_changed', self.set_tittle) self.ceed_data.fbind('has_unsaved', self.set_tittle) self.ceed_data.fbind('read_only_file', self.set_tittle) self.view_controller.set_led_mode(self.view_controller.LED_mode_idle) def set_tittle(self, *largs): """Periodically called by the Kivy Clock to update the title. """ star = '' if self.ceed_data.has_unsaved or self.ceed_data.config_changed: star = '*' read_only = '' if self.ceed_data.read_only_file: read_only = ' - Read Only' if self.ceed_data.filename: filename = ' - {}'.format(self.ceed_data.filename) else: filename = ' - Unnamed File' Window.set_title('Ceed v{}, CPL lab{}{}{}'.format( ceed.__version__, star, filename, read_only)) def changed_callback(self, *largs, **kwargs): """Callback bound to anything that can change the Ceed data to indicate whether it needs to be re-saved. """ self.ceed_data.config_changed = True def check_close(self): if self.view_controller.stage_active or self.ceed_data.data_thread: self._close_message = 'Cannot close during an experiment.' return False self.player.stop() self.view_controller.stop_process() self.view_controller.finish_stop_process() if not self.ceed_data.ui_close(app_close=True): self._close_message = '' return False return True def handle_exception(self, *largs, **kwargs): processing = self._processing_error self._processing_error = True val = super(CeedApp, self).handle_exception(*largs, **kwargs) if not processing: self.view_controller.request_stage_end() self._processing_error = False return val def clean_up(self): super(CeedApp, self).clean_up() if self.ceed_data is not None: if self.ceed_data.backup_event is not None: self.ceed_data.backup_event.cancel() self.ceed_data.backup_event = None self.ceed_data.clear_all_callback = None if self.stage_factory is not None: self.stage_factory.funbind('on_changed', self.changed_callback) if self.function_factory is not None: self.function_factory.funbind('on_changed', self.changed_callback) if self.shape_factory is not None: self.shape_factory.funbind('on_changed', self.changed_callback) if self.view_controller is not None: self.view_controller.funbind('on_changed', self.changed_callback) for stage in self.stage_factory.stages_inst_default.values(): stage.funbind('on_changed', self.changed_callback) for func in self.function_factory.funcs_inst_default.values(): func.funbind('on_changed', self.changed_callback) if self.view_controller is not None: self.view_controller.stop_process() self.view_controller.finish_stop_process() if self.ceed_data is not None: self.ceed_data.stop_experiment() self.ceed_data.funbind('filename', self.set_tittle) self.ceed_data.funbind('config_changed', self.set_tittle) self.ceed_data.funbind('has_unsaved', self.set_tittle) self.ceed_data.funbind('read_only_file', self.set_tittle) self.dump_app_settings_to_file() self.player.clean_up() HighightButtonBehavior.uninit_class()
def test_factory_stage_unique_names(stage_factory: StageFactoryBase): assert not stage_factory.stages assert not stage_factory.stage_names stage = SerialAllStage(stage_factory=stage_factory, show_in_gui=False) stage.create_stage() stage = stage.stage # add first stage stage_factory.test_changes_count = 0 stage_factory.add_stage(stage) assert len(stage_factory.stages) == 1 assert len(stage_factory.stage_names) == 1 assert stage in stage_factory.stages assert stage.name in stage_factory.stage_names assert stage is stage_factory.stage_names[stage.name] assert stage_factory.test_changes_count stage2 = SerialAllStage(stage_factory=stage_factory, show_in_gui=False) stage2.create_stage() stage2 = stage2.stage stage2.name = stage.name assert stage2.name == stage.name # add second stage stage_factory.test_changes_count = 0 stage_factory.add_stage(stage2) assert stage2.name != stage.name assert len(stage_factory.stages) == 2 assert len(stage_factory.stage_names) == 2 assert stage2 in stage_factory.stages assert stage2.name in stage_factory.stage_names assert stage2 is stage_factory.stage_names[stage2.name] assert stage_factory.test_changes_count # try making stage2 the same name as stage 1 stage_factory.test_changes_count = 0 stage2.name = stage.name assert stage2.name != stage.name assert len(stage_factory.stages) == 2 assert len(stage_factory.stage_names) == 2 assert stage2 in stage_factory.stages assert stage2.name in stage_factory.stage_names assert stage2 is stage_factory.stage_names[stage2.name] assert stage_factory.test_changes_count # give stage2an explicit name stage_factory.test_changes_count = 0 stage.name = 'stagnation' assert stage2.name != stage.name assert stage.name == 'stagnation' assert len(stage_factory.stages) == 2 assert len(stage_factory.stage_names) == 2 assert stage in stage_factory.stages assert stage is stage_factory.stage_names['stagnation'] assert stage_factory.test_changes_count # try setting stage2 to the empty name stage_factory.test_changes_count = 0 stage2.name = '' assert stage2.name != stage.name assert stage2.name assert len(stage_factory.stages) == 2 assert len(stage_factory.stage_names) == 2 assert stage2 in stage_factory.stages assert stage2.name in stage_factory.stage_names assert stage2 is stage_factory.stage_names[stage2.name] assert stage_factory.test_changes_count
def test_clear_stages_with_ref(stage_factory: StageFactoryBase): s = make_stage(stage_factory) s2 = make_stage(stage_factory) s.name = 'stage' s2.name = 'stage2' stage_factory.add_stage(s) stage_factory.add_stage(s2) assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages assert stage_factory.stage_names['stage2'] is s2 assert s2 in stage_factory.stages ref = stage_factory.get_stage_ref(name='stage') stage_factory.clear_stages() # s should not have been removed, but s2 was removed assert ref.stage is s assert s.has_ref assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages assert 'stage2' not in stage_factory.stage_names assert s2 not in stage_factory.stages stage_factory.clear_stages(force=True) assert ref.stage is s assert s.has_ref assert 'stage' not in stage_factory.stage_names assert s not in stage_factory.stages stage_factory.return_stage_ref(ref) assert not s.has_ref
def test_replace_ref_stage_with_source_stages( stage_factory: StageFactoryBase): g1 = make_stage(stage_factory, order='parallel', name='g1') g2 = make_stage(stage_factory, order='parallel', name='g2') ref_g2 = stage_factory.get_stage_ref(stage=g2) g1.add_stage(ref_g2) s = make_stage( stage_factory, order='parallel', color_r=True, complete_on='any') g2.add_stage(s) s1 = make_stage( stage_factory, order='parallel', color_b=True, complete_on='any', name='s1') stage_factory.add_stage(s1) ref_s1 = stage_factory.get_stage_ref(stage=s1) g2.add_stage(ref_s1) s2 = make_stage( stage_factory, order='parallel', color_r=False, complete_on='all') g1.add_stage(s2) s3 = make_stage( stage_factory, order='serial', color_r=True, color_g=True, complete_on='any', name='s3') stage_factory.add_stage(s3) ref_s3 = stage_factory.get_stage_ref(stage=s3) g1.add_stage(ref_s3) with pytest.raises(ValueError): g1.replace_ref_stage_with_source(s2) with pytest.raises(ValueError): g1.replace_ref_stage_with_source(ref_s1) s3_new, i = g1.replace_ref_stage_with_source(ref_s3) assert i == 2 assert ref_s3 not in g1.stages assert s3 not in g1.stages assert not isinstance(s3_new, CeedStageRef) assert isinstance(s3_new, s3.__class__) assert g1.stages[i] is s3_new assert_stages_same(s3, s3_new, compare_name=False) g2_new: CeedStage g2_new, i = g1.replace_ref_stage_with_source(ref_g2) assert i == 0 assert ref_g2 not in g1.stages assert g2 not in g1.stages assert not isinstance(g2_new, CeedStageRef) assert isinstance(g2_new, CeedStage) assert g1.stages[i] is g2_new assert len(g2_new.stages) == 2 assert g2_new.stages[0] is not g2.stages[0] assert g2_new.stages[1] is not g2.stages[1] assert isinstance(g2_new.stages[0], s.__class__) assert isinstance(g2_new.stages[1], ref_s1.__class__) assert isinstance(g2_new.stages[1], CeedStageRef) assert_stages_same(s, g2_new.stages[0], compare_name=False) assert g2_new.stages[1].stage is s1
class CeedApp(CPLComApp): '''The app which runs the GUI. ''' function_factory = None player = None view_controller = None ceed_data = None data_serializer = None remote_viewer = None stage_factory = None shape_factory = None remote_player = None agreed_discard = False drag_controller = None ''' ''' @classmethod def get_config_classes(cls): d = super(CeedApp, cls).get_config_classes() d['function'] = FunctionFactoryBase d['view'] = ControllerSideViewControllerBase d['data'] = CeedDataWriterBase d['serializer'] = DataSerializerBase d['player'] = CeedPlayer d['point_gray_cam'] = CeedPTGrayPlayer d['video_file_playback'] = CeedFFmpegPlayer d['remote_viewer'] = RemoteViewerListenerBase app = cls.get_running_app() if app is not None: d['function'] = app.function_factory d['view'] = app.view_controller d['data'] = app.ceed_data d['serializer'] = app.data_serializer p = d['player'] = app.player d['point_gray_cam'] = p.pt_player d['video_file_playback'] = p.ff_player d['remote_viewer'] = app.remote_viewer return d def __init__(self, **kwargs): drag = self.drag_controller = CeedDragNDrop() drag.knsname = 'dragger' self.function_factory = FunctionFactoryBase() register_all_functions(self.function_factory) self.stage_factory = StageFactoryBase( function_factory=self.function_factory, shape_factory=None) self.player = CeedPlayer() self.view_controller = ControllerSideViewControllerBase() self.ceed_data = CeedDataWriterBase() self.data_serializer = DataSerializerBase() self.remote_viewer = RemoteViewerListenerBase() self.remote_player = CeedRemotePlayer() super(CeedApp, self).__init__(**kwargs) self.load_app_settings_from_file() self.apply_app_settings() def build(self): base = dirname(__file__) # Builder.load_file(join(base, 'graphics', 'graphics.kv')) Builder.load_file(join(base, 'ceed_style.kv')) Builder.load_file(join(base, 'player', 'player_style.kv')) Builder.load_file(join(base, 'shape', 'shape_style.kv')) Builder.load_file(join(base, 'function', 'func_style.kv')) Builder.load_file(join(base, 'stage', 'stage_style.kv')) Builder.load_file(join(base, 'view', 'view_style.kv')) Builder.load_file(join(base, 'storage', 'storage_style.kv')) self.yesno_prompt = Factory.CeedYesNoPrompt() root = Factory.get('MainView')() return super(CeedApp, self).build(root) def on_start(self): self.stage_factory.shape_factory = self.shape_factory = knspace.painter remove_shapes_upon_deletion( self.stage_factory, self.shape_factory, knspace.stages.remove_shape_from_stage) knspace.painter.shapes_canvas = knspace.painter.canvas knspace.painter.shape_widgets_list = knspace.shapes def clear_all(): knspace.funcs.clear_all() knspace.stages.clear_all() self.ceed_data.stage_display_callback = knspace.stages.show_stage self.ceed_data.func_display_callback = knspace.funcs.show_function self.ceed_data.clear_all_callback = clear_all HighightButtonBehavior.init_class() self.ceed_data.create_file('') self.stage_factory.fbind('on_changed', self.changed_callback) self.function_factory.fbind('on_changed', self.changed_callback) for func in self.function_factory.funcs_inst_default.values(): func.fbind('on_changed', self.changed_callback) knspace.painter.fbind('on_changed', self.changed_callback) self.view_controller.fbind('on_changed', self.changed_callback) self.set_tittle() self.ceed_data.fbind('filename', self.set_tittle) self.ceed_data.fbind('config_changed', self.set_tittle) self.ceed_data.fbind('has_unsaved', self.set_tittle) self.ceed_data.fbind('read_only_file', self.set_tittle) try: self.view_controller.set_led_mode(self.view_controller.LED_mode_idle) except ImportError: pass def set_tittle(self, *largs): ''' Sets the title of the window using the currently running tab. This is called at 1Hz. ''' star = '' if self.ceed_data.has_unsaved or self.ceed_data.config_changed: star = '*' read_only = '' if self.ceed_data.read_only_file: read_only = ' - Read Only' if self.ceed_data.filename: filename = ' - {}'.format(self.ceed_data.filename) else: filename = ' - Unnamed File' Window.set_title('Ceed v{}, CPL lab{}{}{}'.format( ceed.__version__, star, filename, read_only)) def changed_callback(self, *largs, **kwargs): self.ceed_data.config_changed = True def check_close(self): if CeedPlayer.is_player_active(): self._close_message = 'Cannot close while player is active.' return False if self.view_controller.stage_active or self.ceed_data.data_thread: self._close_message = 'Cannot close during an experiment.' return False self.view_controller.stop_process() self.view_controller.finish_stop_process() if not self.ceed_data.ui_close(app_close=True): self._close_message = '' return False return True def handle_exception(self, *largs, **kwargs): val = super(CeedApp, self).handle_exception(*largs, **kwargs) self.view_controller.request_stage_end() return val
def test_remove_stage_with_ref(stage_factory: StageFactoryBase): s = make_stage(stage_factory) s2 = make_stage(stage_factory) s3 = make_stage(stage_factory) s.name = 'stage' s2.name = 'stage2' s3.name = 'stage3' stage_factory.add_stage(s) stage_factory.add_stage(s2) stage_factory.add_stage(s3) assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages assert stage_factory.stage_names['stage2'] is s2 assert s2 in stage_factory.stages assert stage_factory.stage_names['stage3'] is s3 assert s3 in stage_factory.stages ref = stage_factory.get_stage_ref(name='stage') ref3 = stage_factory.get_stage_ref(name='stage3') assert not stage_factory.remove_stage(s) assert ref.stage is s assert s.has_ref assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages assert stage_factory.stage_names['stage2'] is s2 assert s2 in stage_factory.stages assert stage_factory.stage_names['stage3'] is s3 assert s3 in stage_factory.stages assert stage_factory.remove_stage(s2) assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages assert 'stage2' not in stage_factory.stage_names assert s2 not in stage_factory.stages assert stage_factory.stage_names['stage3'] is s3 assert s3 in stage_factory.stages assert not stage_factory.remove_stage(s3) assert ref3.stage is s3 assert s3.has_ref assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages assert stage_factory.stage_names['stage3'] is s3 assert s3 in stage_factory.stages assert stage_factory.remove_stage(s3, force=True) assert ref3.stage is s3 assert s3.has_ref assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages assert 'stage3' not in stage_factory.stage_names assert s3 not in stage_factory.stages assert not stage_factory.remove_stage(s) assert ref.stage is s assert s.has_ref assert stage_factory.stage_names['stage'] is s assert s in stage_factory.stages stage_factory.return_stage_ref(ref) assert not s.has_ref assert stage_factory.remove_stage(s) assert 'stage' not in stage_factory.stage_names assert s not in stage_factory.stages stage_factory.return_stage_ref(ref3) assert not s3.has_ref