def estimate(self, roi: Roi = None) -> None: """Estimate the transform matrix from a set of coordinates. Coordinates should correspond to the corners of the outline of the design, relative to the video frame size: x in [0,1] ~ width y in [0,1] ~ height """ # todo: sanity check roi if roi is not None: self.config(roi=roi) self.set(self._implementation.estimate(self.adjust(roi), self._video_shape, self._design_shape)) streams.update()
def clear_filters(self) -> bool: log.debug(f"clearing filters") for mask in self.masks: mask.clear_filter() self.set_state(AnalyzerState.CAN_FILTER) self.state_transition() self.event(PushEvent.CONFIG, self.get_config()) self.commit() streams.update() return True
def test_normal_operation(self): class TestRegistry(ImmutableRegistry): method1 = Endpoint(Callable[[], np.ndarray], stream_image) method2 = Endpoint(Callable[[], np.ndarray], stream_image) test_registry = TestRegistry() class StreamableClass(Instance, RootInstance): _endpoints = test_registry _instance_class = Instance _config_class = BaseConfig def __init__(self): super().__init__() self._gather_instances() @stream @test_registry.expose(test_registry.method1) def method1(self) -> np.ndarray: return np.random.randint(0, 255, (64, 64, 3), dtype=np.uint8) @stream @test_registry.expose(test_registry.method2) def method2(self) -> np.ndarray: return np.random.randint(0, 255, (64, 64, 3), dtype=np.uint8) streamable = StreamableClass() thread1 = StreamerThread( streams.register(streamable, streamable.get(test_registry.method1)).stream()) thread1.start() thread2 = StreamerThread( streams.register(streamable, streamable.get(test_registry.method2)).stream()) thread2.start() streams.update() time.sleep(0.1) # Unregiter specific method streams.unregister(streamable, streamable.get(test_registry.method1)) # Unregister all methods & stop streams.stop() # FrameStreamer -> double yield # each streamer pushes once when registered and once on streams.update() self.assertEqual(4, len(thread1.data)) self.assertEqual(4, len(thread2.data))
def seek(self, position: float = None) -> float: """Seek to the relative position ~ [0,1] """ # todo: had to remove lock to enable reading frames :/ # (otherwise streams.update() can get deadocked @ VideoFileHandler if not reading frames from cache) if position is not None: frame_number = int(position * self.frame_count) if settings.cache.resolve_frame_number: self.frame_number = self._resolve_frame(frame_number) else: self.frame_number = frame_number log.debug(f"seeking {self.path} {self.frame_number}/{self.frame_count}") streams.update() return self.frame_number / self.frame_count
def set_filter_click(self, relative_x: float, relative_y: float): log.debug(f'set_filter_click @ ({relative_x}, {relative_y})') click = ShapeCoo( x = relative_x, y = relative_y, shape = self.design.shape[::-1] ) hits = [mask for mask in self.masks if mask.contains(click)] if len(hits) == 1: hit = hits[0] frame = self.video.read_frame() click = self.transform.coordinate(click) color = HsvColor(*click.value(frame)) log.debug(f"color @ {click.idx}: {color}") hit.set_filter(color) for fs in self._featuresets.values(): for feature in fs.features: assert isinstance(feature, MaskFunction) try: if feature.mask == hit: # type: ignore feature._ready = True except AttributeError: pass self.get_colors() self.state_transition() self.event(PushEvent.CONFIG, self.get_config()) self.commit() streams.update() self.commit() elif len(hits) == 0: log.debug(f"no hit for {click.idx}") elif len(hits) > 1: self.notice( f"Multiple valid options: {[hit.name for hit in hits]}. " f"Select a point where masks don't overlap." )
def set_config(self, config: dict, silent: bool = False) -> dict: with self.lock(): if True: # todo: sanity check do_commit = False do_relaunch = False log.debug(f"Setting VideoAnalyzerConfig to {config}") previous_name = copy.copy(self.config.name) previous_desc = copy.copy(self.config.description) previous_Nf = copy.copy(self.config.Nf) previous_dt = copy.copy(self.config.dt) previous_fis = copy.copy(self.config.frame_interval_setting) previous_features = copy.deepcopy(self.config.features) # todo: clean up previous_video_path = copy.deepcopy(self.config.video_path) previous_design_path = copy.deepcopy(self.config.design_path) previous_design = copy.deepcopy(self.config.design) previous_flip = copy.deepcopy(self.config.transform.flip) previous_turn = copy.deepcopy(self.config.transform.turn) previous_roi = copy.deepcopy(self.config.transform.roi) previous_masks = copy.deepcopy(self.config.masks) # Set implementations if hasattr(self, 'transform') and 'transform' in config and 'type' in config['transform']: self.transform.set_implementation(config['transform']['type']) # todo: shouldn't do this every time if hasattr(self, 'design') and 'masks' in config: for i, mask in enumerate(self.design.masks): if 'filter' in config['masks'][i] and 'type' in config['masks'][i]['filter']: mask.filter.set_implementation(config['masks'][i]['filter']['type']) # todo: shouldn't do this every time self._set_config(config) if hasattr(self, 'transform'): self.transform._config(**self.config.transform.to_dict()) self.transform.set_implementation() # todo: shouldn't do this every time if hasattr(self, 'design'): self.design._config(**self.config.design.to_dict()) # Check for file changes if self.launched and previous_video_path != self.config.video_path: do_commit = True do_relaunch = True if self.launched and previous_design_path != self.config.design_path: do_commit = True do_relaunch = True # Check for design render changes if previous_design != self.config.design: self.design._render(mask_config=self.config.masks) self.transform._design_shape = self.design.shape self.estimate_transform() do_commit = True # Check for name/description changes if self.config.name != previous_name: do_commit = True if self.config.description != previous_desc: do_commit = True # Check for changes in frames if hasattr(self, 'video'): if any([ previous_Nf != self.config.Nf, previous_dt != self.config.dt, previous_fis != self.config.frame_interval_setting ]): self.video.set_requested_frames(list(self.frame_numbers())) do_commit = True # Check for changes in features if previous_features != self.config.features: if self.launched: self._get_featuresets() do_commit = True # Get featureset instances todo: overlap with previous block? if self.launched and not self._featuresets: self._get_featuresets() # Check for ROI adjustments if previous_flip != self.config.transform.flip \ or previous_turn != self.config.transform.turn \ or previous_roi != self.config.transform.roi: self.estimate_transform() # todo: self.config.bla.thing should be exactly self.bla.config.thing always do_commit = True # Check for mask adjustments if previous_masks != self.config.masks: for i, mask in enumerate(self.design.masks): mask.set_config(self.config.masks[i].to_dict()) do_commit = True if do_commit and not silent: # todo: better config handling in AnalysisMdoel.store() instead! self.commit() if do_relaunch: self._launch() self._gather_instances() # Check for state transitions self.state_transition(push=True) config = self.get_config() # Push config event self.event(PushEvent.CONFIG, config) # Push streams streams.update() return config
def clear(self) -> None: self.config(roi=None) self.set(None) streams.update()