def __init__( self, *, image_acquisition: ImageAcquisitionService, features_params_factory: PendantFeaturesParamsFactory, features_service: PendantFeaturesService, ) -> None: self._image_acquisition = image_acquisition self._features_params_factory = features_params_factory self._features_service = features_service self._watchers = 0 self._acquirer_controller = None # type: Optional[AcquirerController] self.bn_acquirer_controller = AccessorBindable( getter=lambda: self._acquirer_controller ) self.bn_source_image = VariableBindable(None) # type: Bindable[Optional[np.ndarray]] self.bn_labels = VariableBindable(None) # type: Bindable[Optional[np.ndarray]] self.bn_drop_points = VariableBindable(None) # type: Bindable[Optional[np.ndarray]] self.bn_needle_rect = VariableBindable(None) self._image_acquisition.bn_acquirer.on_changed.connect( self._update_acquirer_controller, )
def __init__( self, *, image_acquisition: ImageAcquisitionModel, feature_extractor_params: FeatureExtractorParams, do_extract_features: Callable[[Bindable[np.ndarray]], FeatureExtractor] ) -> None: self._image_acquisition = image_acquisition self._feature_extractor_params = feature_extractor_params self._do_extract_features = do_extract_features self._watchers = 0 self._acquirer_controller = None # type: Optional[AcquirerController] self.bn_acquirer_controller = AccessorBindable( getter=lambda: self._acquirer_controller) self.bn_source_image = VariableBindable( None) # type: Bindable[Optional[np.ndarray]] self.bn_foreground_detection = VariableBindable( None) # type: Bindable[Optional[np.ndarray]] self.bn_drop_profile = VariableBindable( None) # type: Bindable[Optional[np.ndarray]] self._image_acquisition.bn_acquirer.on_changed.connect( self._update_acquirer_controller, )
class TestAccessorBindable_WithGetterAndSetter: def setup(self): self.getter = Mock() self.setter = Mock() self.bindable = AccessorBindable(getter=self.getter, setter=self.setter) def test_get(self): self.getter.assert_not_called() value = self.bindable.get() assert value == self.getter.return_value self.getter.assert_called_once_with() def test_set(self): self.setter.assert_not_called() new_value = object() self.bindable.set(new_value) self.setter.assert_called_once_with(new_value) def test_poke(self): on_changed_callback = Mock() self.bindable.on_changed.connect(on_changed_callback) self.bindable.poke() on_changed_callback.assert_called_once_with()
class TestAccessorBindable_WithNoGetter: def setup(self): self.bindable = AccessorBindable(setter=Mock()) def test_get(self): with pytest.raises(NotImplementedError): self.bindable.get()
def __init__(self): super().__init__() self._camera_index = None # type: Optional[int] self.bn_camera_index = AccessorBindable(getter=self._get_camera_index) self._camera_alive_changed_ec = None # type: Optional[EventConnection]
def __init__( self, *, acquirer: ImageSequenceAcquirer, source_image_out: Bindable[Optional[np.ndarray]] ) -> None: self._acquirer = acquirer self._source_image_out = source_image_out self._image_registry = [] # type: MutableSequence[self._ImageRegistration] self.bn_num_images = AccessorBindable( getter=self._get_num_images, ) self._showing_image_index = None # type: Optional[int] self.bn_showing_image_index = AccessorBindable( getter=self._get_showing_image_index, setter=self._set_showing_image_index, ) self._showing_image_id = None # type: Optional[Hashable] self.__event_connections = [ acquirer.bn_images.on_changed.connect( self._hdl_acquirer_images_changed ), ] self._hdl_acquirer_images_changed()
def __init__(self, image: Bindable[np.ndarray], params: 'FeatureExtractorParams', *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: self._loop = loop or asyncio.get_event_loop() self._bn_image = image self.params = params self._data = self._Data( _loop=self._loop, bn_foreground_detection=None, bn_drop_profile_px=None, ) self.is_busy = AccessorBindable(getter=self.get_is_busy) self._updater_worker = UpdaterWorker( do_update=self._update, on_idle=self.is_busy.poke, loop=self._loop, ) self.bn_foreground_detection = self._data.bn_foreground_detection # type: Bindable[Optional[np.ndarray]] self.bn_drop_profile_px = self._data.bn_drop_profile_px # type: Bindable[Optional[np.ndarray]] # Update extracted features whenever image or params change. self._bn_image.on_changed.connect(self._queue_update) self.params.bn_drop_region_px.on_changed.connect(self._queue_update) self.params.bn_thresh.on_changed.connect(self._queue_update) # First update to initialise features. self._queue_update()
def __init__(self): super().__init__() self._harvester = harvesters.Harvester() for cti_path in os.environ.get('GENICAM_GENTL64_PATH', '').split(os.pathsep): for cti_file in map(str, Path(cti_path).glob('*.cti')): self._harvester.add_file(cti_file) self._camera_id = None self.bn_camera_id = AccessorBindable( self._get_camera_id) # type: ReadBindable[Optional[str]] self._camera_alive_changed_conn = None # type: Optional[EventConnection]
def __init__( self, *, image_acquisition: ImageAcquisitionModel, feature_extractor_params: FeatureExtractorParams, conancalc_params: ContactAngleCalculatorParams, do_extract_features: Callable[ [Bindable[np.ndarray], FeatureExtractorParams], FeatureExtractor] ) -> None: self._image_acquisition = image_acquisition self._feature_extractor_params = feature_extractor_params self._conancalc_params = conancalc_params self._do_extract_features = do_extract_features self.bn_active_tool = BoxBindable(ToolID.DROP_REGION) region_clip = AccessorBindable(getter=self._get_region_clip) self.drop_region_plugin = DefineRegionPluginModel( in_region=self._feature_extractor_params.bn_drop_region_px, in_clip=region_clip, ) self.surface_plugin = DefineLinePluginModel( in_line=self._conancalc_params.bn_surface_line_px, in_clip=region_clip, ) self.foreground_detection_plugin = ForegroundDetectionPluginModel( feature_extractor_params=feature_extractor_params, ) self.preview_plugin = ConanPreviewPluginModel( image_acquisition=image_acquisition, feature_extractor_params=feature_extractor_params, do_extract_features=do_extract_features, )
def __init__( self, *, image_acquisition: ImageAcquisitionService, features_params_factory: PendantFeaturesParamsFactory, features_service: PendantFeaturesService, ) -> None: self._image_acquisition = image_acquisition self.bn_active_tool = VariableBindable(ToolID.DROP_REGION) region_clip = AccessorBindable(getter=self._get_region_clip) self.drop_region_plugin = DefineRegionPluginModel( in_region=GObjectPropertyBindable(features_params_factory, 'drop-region'), in_clip=region_clip, ) self.needle_region_plugin = DefineRegionPluginModel( in_region=GObjectPropertyBindable(features_params_factory, 'needle-region'), in_clip=region_clip, ) self.preview_plugin = IFTPreviewPluginModel( image_acquisition=image_acquisition, features_params_factory=features_params_factory, features_service=features_service, )
def __init__( self, *, image_acquisition: ImageAcquisitionModel, feature_extractor_params: FeatureExtractorParams, do_extract_features: Callable[[Bindable[np.ndarray], FeatureExtractorParams], FeatureExtractor] ) -> None: self._image_acquisition = image_acquisition self._feature_extractor_params = feature_extractor_params self._do_extract_features = do_extract_features self.bn_active_tool = VariableBindable(ToolID.DROP_REGION) region_clip = AccessorBindable( getter=self._get_region_clip ) self.drop_region_plugin = DefineRegionPluginModel( in_region=self._feature_extractor_params.bn_drop_region_px, in_clip=region_clip, ) self.needle_region_plugin = DefineRegionPluginModel( in_region=self._feature_extractor_params.bn_needle_region_px, in_clip=region_clip, ) self.edge_detection_plugin = EdgeDetectionPluginModel( feature_extractor_params=feature_extractor_params, ) self.preview_plugin = IFTPreviewPluginModel( image_acquisition=image_acquisition, feature_extractor_params=feature_extractor_params, do_extract_features=do_extract_features, )
def __init__( self, *, image_acquisition: ImageAcquisitionService, params_factory: ConanParamsFactory, features_service: ConanFeaturesService, ) -> None: self._loop = asyncio.get_event_loop() self._image_acquisition = image_acquisition self._params_factory = params_factory self.bn_active_tool = VariableBindable(ToolID.BASELINE) region_clip = AccessorBindable(getter=self._get_region_clip) self.baseline_plugin = DefineLinePluginModel( in_line=GObjectPropertyBindable(params_factory, 'baseline'), in_clip=region_clip, ) self.roi_plugin = DefineRegionPluginModel( in_region=GObjectPropertyBindable(params_factory, 'roi'), in_clip=region_clip, ) self.thresh_plugin = ConanThreshPluginModel( params_factory=params_factory) self.preview_plugin = ConanPreviewPluginModel( image_acquisition=image_acquisition, params_factory=params_factory, features_service=features_service, )
def __init__(self, features: FeatureExtractor, *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: self._loop = loop or asyncio.get_event_loop() self._features = features self._is_sessile = False self._data = self._Data( _loop=self._loop, apex_pos=Vector2(math.nan, math.nan), apex_radius=math.nan, bond_number=math.nan, rotation=math.nan, profile_fit=None, residuals=None, volume=math.nan, surface_area=math.nan, ) self._stop_flag = False self.bn_is_busy = AccessorBindable(getter=self.get_is_busy) self._updater_worker = UpdaterWorker(do_update=self._update, on_idle=self.bn_is_busy.poke, loop=self._loop) self.bn_apex_pos = self._data.apex_pos # type: Bindable[Vector2[float]] self.bn_apex_radius = self._data.apex_radius # type: Bindable[float] self.bn_bond_number = self._data.bond_number # type: Bindable[float] self.bn_rotation = self._data.rotation # type: Bindable[float] self.bn_profile_fit = self._data.profile_fit # type: Bindable[np.ndarray] self.bn_residuals = self._data.residuals # type: Bindable[np.ndarray] self.bn_volume = self._data.volume # type: Bindable[float] self.bn_surface_area = self._data.surface_area # type: Bindable[float] self._log = '' self._log_lock = threading.Lock() self.bn_log = AccessorBindable(getter=self.get_log) # Reanalyse when extracted drop profile changes features.bn_drop_profile_px.on_changed.connect( self._hdl_features_changed) # First update to initialise attributes. self._queue_update()
def __init__(self, *, _loop: Optional[asyncio.AbstractEventLoop] = None, **initial_values) -> None: self._loop = _loop or asyncio.get_event_loop() self._accessors = { name: AccessorBindable(getter=lambda name=name: self._get_value(name)) for name in self._fields } self._values = initial_values assert set(self._values.keys()) == set(self._fields) self._editor = None self._editor_lock = threading.Lock() self._read_lock = threading.Lock()
def __init__( self, *, image_acquisition: ImageAcquisitionService, params_factory: ConanParamsFactory, features_service: ConanFeaturesService, ) -> None: self._image_acquisition = image_acquisition self._params_factory = params_factory self._features_service = features_service self._watchers = 0 self._acquirer_controller = None # type: Optional[AcquirerController] self.bn_acquirer_controller = AccessorBindable( getter=lambda: self._acquirer_controller) self.bn_source_image = VariableBindable(None) self.bn_features = VariableBindable(None) self._image_acquisition.bn_acquirer.on_changed.connect( self._update_acquirer_controller, )
def __init__( self, in_analyses: Bindable[Sequence[IFTDropAnalysis]], do_cancel_analyses: Callable[[], Any], do_save_analyses: Callable[[IFTAnalysisSaverOptions], Any], create_save_options: Callable[[], IFTAnalysisSaverOptions], check_if_safe_to_discard: Callable[[], bool], ): self.bn_analyses = in_analyses self._do_cancel_analyses = do_cancel_analyses self._do_save_analyses = do_save_analyses self._create_save_options = create_save_options self._check_if_safe_to_discard = check_if_safe_to_discard self.bn_selection = BoxBindable( None) # type: Bindable[Optional[IFTDropAnalysis]] self.individual = IndividualModel( in_analyses=self.bn_analyses, bind_selection=self.bn_selection, ) self.graphs = GraphsModel(in_analyses=self.bn_analyses, ) self._tracked_analyses = [] self._analysis_untrack_tasks = {} self.bn_fitting_status = AccessorBindable( getter=self._get_fitting_status) self.bn_analyses_time_start = AccessorBindable( getter=self._get_analyses_time_start) self.bn_analyses_time_est_complete = AccessorBindable( getter=self._get_analyses_time_est_complete) self.bn_analyses_completion_progress = AccessorBindable( getter=self._get_analyses_completion_progress) self.bn_analyses.on_changed.connect(self._hdl_analyses_changed)
class IFTPreviewPluginModel: def __init__( self, *, image_acquisition: ImageAcquisitionService, features_params_factory: PendantFeaturesParamsFactory, features_service: PendantFeaturesService, ) -> None: self._image_acquisition = image_acquisition self._features_params_factory = features_params_factory self._features_service = features_service self._watchers = 0 self._acquirer_controller = None # type: Optional[AcquirerController] self.bn_acquirer_controller = AccessorBindable( getter=lambda: self._acquirer_controller ) self.bn_source_image = VariableBindable(None) # type: Bindable[Optional[np.ndarray]] self.bn_labels = VariableBindable(None) # type: Bindable[Optional[np.ndarray]] self.bn_drop_points = VariableBindable(None) # type: Bindable[Optional[np.ndarray]] self.bn_needle_rect = VariableBindable(None) self._image_acquisition.bn_acquirer.on_changed.connect( self._update_acquirer_controller, ) def watch(self) -> None: self._watchers += 1 self._update_acquirer_controller() def unwatch(self) -> None: self._watchers -= 1 self._update_acquirer_controller() def _update_acquirer_controller(self) -> None: self._destroy_acquirer_controller() if self._watchers <= 0: return new_acquirer = self._image_acquisition.bn_acquirer.get() if isinstance(new_acquirer, ImageSequenceAcquirer): new_acquirer_controller = IFTImageSequenceAcquirerController( acquirer=new_acquirer, features_params_factory=self._features_params_factory, features_service=self._features_service, out_image=self.bn_source_image, show_features=self._show_features, ) elif isinstance(new_acquirer, CameraAcquirer): new_acquirer_controller = IFTCameraAcquirerController( acquirer=new_acquirer, features_params_factory=self._features_params_factory, features_service=self._features_service, out_image=self.bn_source_image, show_features=self._show_features, ) elif new_acquirer is None: new_acquirer_controller = None else: raise ValueError( "Unknown acquirer '{}'" .format(new_acquirer) ) self._acquirer_controller = new_acquirer_controller self.bn_acquirer_controller.poke() def _show_features(self, features: Optional[PendantFeatures]) -> None: if features is None: self.bn_labels.set(None) self.bn_drop_points.set(None) self.bn_needle_rect.set(None) return self.bn_labels.set(features.labels) self.bn_drop_points.set(features.drop_points) self.bn_needle_rect.set(features.needle_rect) def _destroy_acquirer_controller(self) -> None: acquirer_controller = self._acquirer_controller if acquirer_controller is None: return acquirer_controller.destroy() self._acquirer_controller = None
class ConanPreviewPluginModel: def __init__( self, *, image_acquisition: ImageAcquisitionService, params_factory: ConanParamsFactory, features_service: ConanFeaturesService, ) -> None: self._image_acquisition = image_acquisition self._params_factory = params_factory self._features_service = features_service self._watchers = 0 self._acquirer_controller = None # type: Optional[AcquirerController] self.bn_acquirer_controller = AccessorBindable( getter=lambda: self._acquirer_controller) self.bn_source_image = VariableBindable(None) self.bn_features = VariableBindable(None) self._image_acquisition.bn_acquirer.on_changed.connect( self._update_acquirer_controller, ) def watch(self) -> None: self._watchers += 1 self._update_acquirer_controller() def unwatch(self) -> None: self._watchers -= 1 self._update_acquirer_controller() def _update_acquirer_controller(self) -> None: self._destroy_acquirer_controller() if self._watchers <= 0: return new_acquirer = self._image_acquisition.bn_acquirer.get() if isinstance(new_acquirer, ImageSequenceAcquirer): new_acquirer_controller = ConanImageSequenceAcquirerController( acquirer=new_acquirer, params_factory=self._params_factory, features_service=self._features_service, source_image_out=self.bn_source_image, show_features=self._show_features, ) elif isinstance(new_acquirer, CameraAcquirer): new_acquirer_controller = ConanCameraAcquirerController( acquirer=new_acquirer, params_factory=self._params_factory, features_service=self._features_service, source_image_out=self.bn_source_image, show_features=self._show_features, ) elif new_acquirer is None: new_acquirer_controller = None else: raise ValueError("Unknown acquirer '{}'".format(new_acquirer)) self._acquirer_controller = new_acquirer_controller self.bn_acquirer_controller.poke() def _show_features(self, features: Optional[ConanFeatures]) -> None: if features is None: self.bn_features.set(None) return self.bn_features.set(features) def _destroy_acquirer_controller(self) -> None: acquirer_controller = self._acquirer_controller if acquirer_controller is None: return acquirer_controller.destroy() self._acquirer_controller = None
class ConanPreviewPluginModel: def __init__( self, *, image_acquisition: ImageAcquisitionModel, feature_extractor_params: FeatureExtractorParams, do_extract_features: Callable[[Bindable[np.ndarray]], FeatureExtractor] ) -> None: self._image_acquisition = image_acquisition self._feature_extractor_params = feature_extractor_params self._do_extract_features = do_extract_features self._watchers = 0 self._acquirer_controller = None # type: Optional[AcquirerController] self.bn_acquirer_controller = AccessorBindable( getter=lambda: self._acquirer_controller) self.bn_source_image = VariableBindable( None) # type: Bindable[Optional[np.ndarray]] self.bn_foreground_detection = VariableBindable( None) # type: Bindable[Optional[np.ndarray]] self.bn_drop_profile = VariableBindable( None) # type: Bindable[Optional[np.ndarray]] self._image_acquisition.bn_acquirer.on_changed.connect( self._update_acquirer_controller, ) def watch(self) -> None: self._watchers += 1 self._update_acquirer_controller() def unwatch(self) -> None: self._watchers -= 1 self._update_acquirer_controller() def _update_acquirer_controller(self) -> None: self._destroy_acquirer_controller() if self._watchers <= 0: return new_acquirer = self._image_acquisition.bn_acquirer.get() if isinstance(new_acquirer, ImageSequenceAcquirer): new_acquirer_controller = ConanImageSequenceAcquirerController( acquirer=new_acquirer, do_extract_features=self._do_extract_features, source_image_out=self.bn_source_image, foreground_detection_out=self.bn_foreground_detection, drop_profile_out=self.bn_drop_profile, ) elif isinstance(new_acquirer, CameraAcquirer): new_acquirer_controller = ConanCameraAcquirerController( acquirer=new_acquirer, do_extract_features=self._do_extract_features, source_image_out=self.bn_source_image, foreground_detection_out=self.bn_foreground_detection, drop_profile_out=self.bn_drop_profile, ) elif new_acquirer is None: new_acquirer_controller = None else: raise ValueError("Unknown acquirer '{}'".format(new_acquirer)) self._acquirer_controller = new_acquirer_controller self.bn_acquirer_controller.poke() def _destroy_acquirer_controller(self) -> None: acquirer_controller = self._acquirer_controller if acquirer_controller is None: return acquirer_controller.destroy() self._acquirer_controller = None
def __init__(self) -> None: self._acquirer = None # type: Optional[ImageAcquirer] self.bn_acquirer = AccessorBindable(getter=self._get_acquirer, )
def setup(self): self.bindable = AccessorBindable(setter=Mock())
def _do_init(self) -> Gtk.Widget: self._widget = Gtk.Grid(row_spacing=10, hexpand=False, width_request=220) parameters_lbl = Gtk.Label(xalign=0) parameters_lbl.set_markup('<b>Parameters</b>') self._widget.attach(parameters_lbl, 0, 0, 1, 1) sheet = Gtk.Grid(row_spacing=10, column_spacing=10) self._widget.attach(sheet, 0, 1, 1, 1) interfacial_tension_lbl = Gtk.Label('IFT (mN/m):', xalign=0) sheet.attach(interfacial_tension_lbl, 0, 0, 1, 1) volume_lbl = Gtk.Label('Volume (mm³):', xalign=0) sheet.attach(volume_lbl, 0, 1, 1, 1) surface_area_lbl = Gtk.Label('Surface area (mm²):', xalign=0) sheet.attach(surface_area_lbl, 0, 2, 1, 1) sheet.attach( Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL, hexpand=True), 0, 3, 2, 1) worthington_lbl = Gtk.Label('Worthington:', xalign=0) sheet.attach(worthington_lbl, 0, 4, 1, 1) bond_number_lbl = Gtk.Label('Bond number:', xalign=0) sheet.attach(bond_number_lbl, 0, 5, 1, 1) apex_coords_lbl = Gtk.Label('Apex coordinates (px):', xalign=0) sheet.attach(apex_coords_lbl, 0, 6, 1, 1) image_angle_lbl = Gtk.Label('Image angle:', xalign=0) sheet.attach(image_angle_lbl, 0, 7, 1, 1) interfacial_tension_val = Gtk.Label(xalign=0) sheet.attach_next_to(interfacial_tension_val, interfacial_tension_lbl, Gtk.PositionType.RIGHT, 1, 1) volume_val = Gtk.Label(xalign=0) sheet.attach_next_to(volume_val, volume_lbl, Gtk.PositionType.RIGHT, 1, 1) surface_area_val = Gtk.Label(xalign=0) sheet.attach_next_to(surface_area_val, surface_area_lbl, Gtk.PositionType.RIGHT, 1, 1) worthington_val = Gtk.Label(xalign=0) sheet.attach_next_to(worthington_val, worthington_lbl, Gtk.PositionType.RIGHT, 1, 1) bond_number_val = Gtk.Label(xalign=0) sheet.attach_next_to(bond_number_val, bond_number_lbl, Gtk.PositionType.RIGHT, 1, 1) apex_coords_val = Gtk.Label(xalign=0) sheet.attach_next_to(apex_coords_val, apex_coords_lbl, Gtk.PositionType.RIGHT, 1, 1) image_angle_val = Gtk.Label(xalign=0) sheet.attach_next_to(image_angle_val, image_angle_lbl, Gtk.PositionType.RIGHT, 1, 1) self._widget.foreach(Gtk.Widget.show_all) self.bn_interfacial_tension = AccessorBindable( setter=(lambda v: interfacial_tension_val.set_text('{:.4g}'.format( v * 1e3)))) self.bn_volume = AccessorBindable( setter=(lambda v: volume_val.set_text('{:.4g}'.format(v * 1e9)))) self.bn_surface_area = AccessorBindable(setter=( lambda v: surface_area_val.set_text('{:.4g}'.format(v * 1e6)))) self.bn_worthington = AccessorBindable( setter=(lambda v: worthington_val.set_text('{:.4g}'.format(v)))) self.bn_bond_number = AccessorBindable( setter=(lambda v: bond_number_val.set_text('{:.4g}'.format(v)))) self.bn_apex_coords = AccessorBindable( setter=(lambda v: apex_coords_val.set_text( '({0[0]:.4g}, {0[1]:.4g})'.format(v)))) self.bn_image_angle = AccessorBindable( setter=(lambda v: image_angle_val.set_text('{:.4g}°'.format( math.degrees(v))))) self.presenter.view_ready() return self._widget
def setup(self): self.getter = Mock() self.setter = Mock() self.bindable = AccessorBindable(getter=self.getter, setter=self.setter)
class ImageSequenceAcquirerController(AcquirerController): class _ImageRegistration: def __init__(self, image_id: Hashable, image: np.ndarray) -> None: self.image_id = image_id self.image = image def __init__(self, *, acquirer: ImageSequenceAcquirer, source_image_out: Bindable[Optional[np.ndarray]]) -> None: self._acquirer = acquirer self._source_image_out = source_image_out self._image_registry = [ ] # type: MutableSequence[self._ImageRegistration] self.bn_num_images = AccessorBindable(getter=self._get_num_images, ) self._showing_image_index = None # type: Optional[int] self.bn_showing_image_index = AccessorBindable( getter=self._get_showing_image_index, setter=self._set_showing_image_index, ) self._showing_image_id = None # type: Optional[Hashable] self.__event_connections = [ acquirer.bn_images.on_changed.connect( self._hdl_acquirer_images_changed), ] self._hdl_acquirer_images_changed() def _hdl_acquirer_images_changed(self) -> None: self._update_image_registry() self._update_showing_image() def _update_image_registry(self) -> None: acquirer_images = list(self._acquirer.bn_images.get()) for image_reg in tuple( self._get_unassociated_registered_images(acquirer_images)): self._image_registry.remove(image_reg) self._on_image_deregistered(image_reg.image_id) for image in self._filter_unregistered_images(acquirer_images): new_image_reg = self._ImageRegistration( image_id=id(image), image=image, ) self._image_registry.append(new_image_reg) self._on_image_registered( image_id=new_image_reg.image_id, image=image, ) self.bn_num_images.poke() def _filter_unregistered_images( self, images: Iterable[np.ndarray]) -> Iterable[np.ndarray]: return itertools.filterfalse(self._is_image_registered, images) def _is_image_registered(self, image: np.ndarray) -> bool: try: self._get_image_reg_by_image(image) except ValueError: return False else: return True def _get_unassociated_registered_images( self, images: Iterable[np.ndarray]) -> Iterable[_ImageRegistration]: return (image_reg for image_reg in self._image_registry if not self._is_registered_image_associated(images, image_reg)) @staticmethod def _is_registered_image_associated(images: Iterable[np.ndarray], image_reg: _ImageRegistration) -> bool: for image in images: if np.array_equal(image_reg.image, image): return True else: return False def _update_showing_image(self) -> None: acquirer_images = list(self._acquirer.bn_images.get()) if self._showing_image_index is None and len(acquirer_images) > 0: self._showing_image_index = 0 self.bn_showing_image_index.poke() if self._showing_image_index is None: return new_showing_image_reg = self._get_image_reg_by_image( image=acquirer_images[self._showing_image_index]) new_showing_image_id = new_showing_image_reg.image_id if new_showing_image_id == self._showing_image_id: return self._showing_image_id = new_showing_image_id self._on_image_changed(new_showing_image_id) self._update_source_image_out() def _update_source_image_out(self) -> None: image_reg = self._get_image_reg_by_image_id( image_id=self._showing_image_id, ) image = image_reg.image self._source_image_out.set(image) def _get_image_reg_by_image(self, image: np.ndarray) -> _ImageRegistration: for image_reg in self._image_registry: if np.array_equal(image_reg.image, image): return image_reg else: raise ValueError( "No _ImageRegistration found for image '{}'".format(image)) def _get_image_reg_by_image_id(self, image_id: Hashable) -> _ImageRegistration: for image_reg in self._image_registry: if image_reg.image_id == image_id: return image_reg else: raise ValueError( "No _ImageRegistration found for image_id '{}'".format( image_id)) def _get_showing_image_index(self) -> Optional[int]: return self._showing_image_index def _set_showing_image_index(self, idx: Optional[int]) -> None: if idx is None: return idx = clamp(idx, 0, self.bn_num_images.get() - 1) self._showing_image_index = idx self._update_showing_image() def _get_num_images(self) -> int: return len(self._acquirer.bn_images.get()) def _on_image_registered(self, image_id: Hashable, image: np.ndarray) -> None: pass def _on_image_deregistered(self, image_id: Hashable) -> None: pass def _on_image_changed(self, image_id: Hashable) -> None: pass def destroy(self) -> None: for ec in self.__event_connections: ec.disconnect()
class IFTDropAnalysis: class Status(Enum): WAITING_FOR_IMAGE = ('Waiting for image', False) FITTING = ('Fitting', False) FINISHED = ('Finished', True) CANCELLED = ('Cancelled', True) def __init__(self, display_name: str, is_terminal: bool) -> None: self.display_name = display_name self.is_terminal = is_terminal def __str__(self) -> str: return self.display_name def __init__( self, input_image: InputImage, do_extract_features: Callable[[Bindable[np.ndarray]], FeatureExtractor], do_young_laplace_fit: Callable[[FeatureExtractor], YoungLaplaceFitter], do_calculate_physprops: Callable[[FeatureExtractor, YoungLaplaceFitter], PhysicalPropertiesCalculator] ) -> None: self._loop = asyncio.get_event_loop() self._time_start = time.time() self._time_end = math.nan self._input_image = input_image self._do_extract_features = do_extract_features self._do_young_laplace_fit = do_young_laplace_fit self._do_calculate_physprops = do_calculate_physprops self._status = self.Status.WAITING_FOR_IMAGE self.bn_status = AccessorBindable( getter=self._get_status, setter=self._set_status, ) self._image = None # type: Optional[np.ndarray] # The time (in Unix time) that the image was captured. self._image_timestamp = math.nan # type: float self._extracted_features = None # type: Optional[FeatureExtractor] self._physical_properties = None # type: Optional[PhysicalPropertiesCalculator] self._young_laplace_fit = None # type: Optional[YoungLaplaceFitter] self.bn_image = AccessorBindable(self._get_image) self.bn_image_timestamp = AccessorBindable(self._get_image_timestamp) # Attributes from YoungLaplaceFitter self.bn_bond_number = BoxBindable(math.nan) self.bn_apex_coords_px = BoxBindable(Vector2(math.nan, math.nan)) self.bn_apex_radius_px = BoxBindable(math.nan) self.bn_rotation = BoxBindable(math.nan) self.bn_drop_profile_fit = BoxBindable(None) self.bn_residuals = BoxBindable(None) # Attributes from PhysicalPropertiesCalculator self.bn_interfacial_tension = BoxBindable(math.nan) self.bn_volume = BoxBindable(math.nan) self.bn_surface_area = BoxBindable(math.nan) self.bn_apex_radius = BoxBindable(math.nan) self.bn_worthington = BoxBindable(math.nan) # Attributes from FeatureExtractor self.bn_drop_region = BoxBindable(None) self.bn_needle_region = BoxBindable(None) self.bn_drop_profile_extract = BoxBindable(None) self.bn_needle_profile_extract = BoxBindable(None) self.bn_needle_width_px = BoxBindable(math.nan) # Log self.bn_log = BoxBindable('') self.bn_is_done = AccessorBindable(getter=self._get_is_done) self.bn_is_cancelled = AccessorBindable(getter=self._get_is_cancelled) self.bn_progress = AccessorBindable(self._get_progress) self.bn_time_start = AccessorBindable(self._get_time_start) self.bn_time_est_complete = AccessorBindable(self._get_time_est_complete) self.bn_status.on_changed.connect(self.bn_is_done.poke) self.bn_status.on_changed.connect(self.bn_progress.poke) self._loop.create_task(self._input_image.read()).add_done_callback(self._hdl_input_image_read) def _hdl_input_image_read(self, read_task: Future) -> None: if read_task.cancelled(): self.cancel() return if self.bn_is_done.get(): return image, image_timestamp = read_task.result() self._start_fit(image, image_timestamp) def _start_fit(self, image: np.ndarray, image_timestamp: float) -> None: assert self._image is None self._image = image self._image_timestamp = image_timestamp # Set given image to be readonly to prevent introducing some accidental bugs. self._image.flags.writeable = False extracted_features = self._do_extract_features(BoxBindable(self._image)) young_laplace_fit = self._do_young_laplace_fit(extracted_features) physical_properties = self._do_calculate_physprops(extracted_features, young_laplace_fit) self._extracted_features = extracted_features self._young_laplace_fit = young_laplace_fit self._physical_properties = physical_properties self._bind_fit() self.bn_image.poke() self.bn_image_timestamp.poke() young_laplace_fit.bn_is_busy.on_changed.connect( self._hdl_young_laplace_fit_is_busy_changed ) self.bn_status.set(self.Status.FITTING) def _bind_fit(self) -> None: # Bind extracted features attributes self._extracted_features.bn_drop_profile_px.bind( self.bn_drop_profile_extract ) self._extracted_features.bn_needle_profile_px.bind( self.bn_needle_profile_extract ) self._extracted_features.bn_needle_width_px.bind( self.bn_needle_width_px ) self._extracted_features.params.bn_drop_region_px.bind( self.bn_drop_region ) self._extracted_features.params.bn_needle_region_px.bind( self.bn_needle_region ) # Bind Young-Laplace fit attributes self._young_laplace_fit.bn_bond_number.bind( self.bn_bond_number ) self._young_laplace_fit.bn_apex_pos.bind( self.bn_apex_coords_px ) self._young_laplace_fit.bn_apex_radius.bind( self.bn_apex_radius_px ) self._young_laplace_fit.bn_rotation.bind( self.bn_rotation ) self._young_laplace_fit.bn_profile_fit.bind( self.bn_drop_profile_fit ) self._young_laplace_fit.bn_residuals.bind( self.bn_residuals ) self._young_laplace_fit.bn_log.bind( self.bn_log ) # Bind physical properties attributes self._physical_properties.bn_interfacial_tension.bind( self.bn_interfacial_tension ) self._physical_properties.bn_volume.bind( self.bn_volume ) self._physical_properties.bn_surface_area.bind( self.bn_surface_area ) self._physical_properties.bn_apex_radius.bind( self.bn_apex_radius ) self._physical_properties.bn_worthington.bind( self.bn_worthington ) def _hdl_young_laplace_fit_is_busy_changed(self) -> None: if self.bn_status.get() is self.Status.CANCELLED: return young_laplace_fit = self._young_laplace_fit # If bond number is a sensible value, then assume young_laplace_fit has completed. if not math.isnan(young_laplace_fit.bn_bond_number.get()): self.bn_status.set(self.Status.FINISHED) def cancel(self) -> None: if self.bn_status.get().is_terminal: # This is already at the end of its life. return if self.bn_status.get() is self.Status.WAITING_FOR_IMAGE: self._input_image.cancel() if self._young_laplace_fit is not None: self._young_laplace_fit.stop() self.bn_status.set(self.Status.CANCELLED) def _get_status(self) -> Status: return self._status def _set_status(self, new_status: Status) -> None: self._status = new_status self.bn_is_cancelled.poke() if new_status.is_terminal: self._time_end = time.time() def _get_image(self) -> Optional[np.ndarray]: return self._image def _get_image_timestamp(self) -> float: return self._image_timestamp def _get_is_done(self) -> bool: return self.bn_status.get().is_terminal def _get_is_cancelled(self) -> bool: return self.bn_status.get() is self.Status.CANCELLED def _get_progress(self) -> float: if self.bn_is_done.get(): return 1 else: return 0 def _get_time_start(self) -> float: return self._time_start def _get_time_est_complete(self) -> float: if self._input_image is None: return math.nan return self._input_image.est_ready def calculate_time_elapsed(self) -> float: time_start = self._time_start if math.isfinite(self._time_end): time_elapsed = self._time_end - time_start else: time_now = time.time() time_elapsed = time_now - time_start return time_elapsed def calculate_time_remaining(self) -> float: if self.bn_is_done.get(): return 0 time_est_complete = self.bn_time_est_complete.get() time_now = time.time() time_remaining = time_est_complete - time_now return time_remaining @property def is_image_replicated(self) -> bool: return self._input_image.is_replicated
class FeatureExtractor: _Data = thread_safe_bindable_collection(fields=[ 'bn_edge_detection', 'bn_drop_profile_px', 'bn_needle_profile_px', 'bn_needle_width_px', ]) def __init__(self, image: ReadBindable[np.ndarray], params: 'FeatureExtractorParams', *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: self._loop = loop or asyncio.get_event_loop() self._bn_image = image self.params = params self._data = self._Data( _loop=self._loop, bn_edge_detection=None, bn_drop_profile_px=None, bn_needle_profile_px=None, bn_needle_width_px=math.nan, ) self.is_busy = AccessorBindable(getter=self.get_is_busy) self._updater_worker = UpdaterWorker( do_update=self._update, on_idle=self.is_busy.poke, loop=self._loop, ) self.bn_edge_detection = self._data.bn_edge_detection # type: ReadBindable[Optional[np.ndarray]] self.bn_drop_profile_px = self._data.bn_drop_profile_px # type: ReadBindable[Optional[np.ndarray]] self.bn_needle_profile_px = self._data.bn_needle_profile_px # type: ReadBindable[Optional[Tuple[np.ndarray, np.ndarray]]] self.bn_needle_width_px = self._data.bn_needle_width_px # type: ReadBindable[float] # Update extracted features whenever image or params change. self._bn_image.on_changed.connect(self._queue_update) self.params.bn_drop_region_px.on_changed.connect(self._queue_update) self.params.bn_needle_region_px.on_changed.connect(self._queue_update) self.params.bn_canny_min.on_changed.connect(self._queue_update) self.params.bn_canny_max.on_changed.connect(self._queue_update) # First update to initialise features. self._queue_update() def _queue_update(self) -> None: was_busy = self._updater_worker.is_busy self._updater_worker.queue_update() if not was_busy: self.is_busy.poke() # This method will be run on different threads (could be called by UpdaterWorker), so make sure it stays # thread-safe. def _update(self) -> None: editor = self._data.edit(timeout=1) assert editor is not None try: new_edge_detection = self._apply_edge_detection() new_drop_profile_px = self._extract_drop_profile_px( new_edge_detection) new_needle_profile_px = self._extract_needle_profile_px( new_edge_detection) new_needle_width_px = self._extract_needle_width_px( new_needle_profile_px) editor.set_value('bn_edge_detection', new_edge_detection) editor.set_value('bn_drop_profile_px', new_drop_profile_px) editor.set_value('bn_needle_profile_px', new_needle_profile_px) editor.set_value('bn_needle_width_px', new_needle_width_px) except Exception as exc: # If any exceptions occur, discard changes and re-raise the exception. editor.discard() raise exc else: # Otherwise commit the changes. editor.commit() def _apply_edge_detection(self) -> Optional[np.ndarray]: image = self._bn_image.get() if image is None: return None return apply_edge_detection( image=image, canny_min=self.params.bn_canny_min.get(), canny_max=self.params.bn_canny_max.get(), ) def _extract_drop_profile_px( self, binary_image: Optional[np.ndarray]) -> Optional[np.ndarray]: if binary_image is None: return None drop_region = self.params.bn_drop_region_px.get() if drop_region is None: return None drop_region = drop_region.map(int) drop_image = binary_image[drop_region.y0:drop_region.y1, drop_region.x0:drop_region.x1] drop_profile_px = extract_drop_profile(drop_image) drop_profile_px += drop_region.position return drop_profile_px def _extract_needle_profile_px( self, binary_image: Optional[np.ndarray] ) -> Optional[Tuple[np.ndarray, np.ndarray]]: if binary_image is None: return None needle_region = self.params.bn_needle_region_px.get() if needle_region is None: return None needle_region = needle_region.map(int) needle_image = binary_image[needle_region.y0:needle_region.y1, needle_region.x0:needle_region.x1] needle_profile_px = extract_needle_profile(needle_image) needle_profile_px = tuple(x + needle_region.position for x in needle_profile_px) return needle_profile_px def _extract_needle_width_px( self, needle_profile: Optional[Tuple[np.ndarray, np.ndarray]]) -> float: if needle_profile is None: return math.nan return calculate_width_from_needle_profile(needle_profile) @property def is_sessile(self) -> bool: drop_region = self.params.bn_drop_region_px.get() needle_region = self.params.bn_needle_region_px.get() if needle_region is None or drop_region is None: # Can't determine if is sessile, just return False. return False if needle_region.pt0.y > drop_region.pt0.y: # Needle region is below drop region, probably sessile drop. return True def get_is_busy(self) -> bool: return self._updater_worker.is_busy async def wait_until_not_busy(self) -> None: while self.is_busy.get(): await self.is_busy.on_changed.wait()
def __init__( self, input_image: InputImage, do_extract_features: Callable[[Bindable[np.ndarray]], FeatureExtractor], do_young_laplace_fit: Callable[[FeatureExtractor], YoungLaplaceFitter], do_calculate_physprops: Callable[[FeatureExtractor, YoungLaplaceFitter], PhysicalPropertiesCalculator] ) -> None: self._loop = asyncio.get_event_loop() self._time_start = time.time() self._time_end = math.nan self._input_image = input_image self._do_extract_features = do_extract_features self._do_young_laplace_fit = do_young_laplace_fit self._do_calculate_physprops = do_calculate_physprops self._status = self.Status.WAITING_FOR_IMAGE self.bn_status = AccessorBindable( getter=self._get_status, setter=self._set_status, ) self._image = None # type: Optional[np.ndarray] # The time (in Unix time) that the image was captured. self._image_timestamp = math.nan # type: float self._extracted_features = None # type: Optional[FeatureExtractor] self._physical_properties = None # type: Optional[PhysicalPropertiesCalculator] self._young_laplace_fit = None # type: Optional[YoungLaplaceFitter] self.bn_image = AccessorBindable(self._get_image) self.bn_image_timestamp = AccessorBindable(self._get_image_timestamp) # Attributes from YoungLaplaceFitter self.bn_bond_number = BoxBindable(math.nan) self.bn_apex_coords_px = BoxBindable(Vector2(math.nan, math.nan)) self.bn_apex_radius_px = BoxBindable(math.nan) self.bn_rotation = BoxBindable(math.nan) self.bn_drop_profile_fit = BoxBindable(None) self.bn_residuals = BoxBindable(None) # Attributes from PhysicalPropertiesCalculator self.bn_interfacial_tension = BoxBindable(math.nan) self.bn_volume = BoxBindable(math.nan) self.bn_surface_area = BoxBindable(math.nan) self.bn_apex_radius = BoxBindable(math.nan) self.bn_worthington = BoxBindable(math.nan) # Attributes from FeatureExtractor self.bn_drop_region = BoxBindable(None) self.bn_needle_region = BoxBindable(None) self.bn_drop_profile_extract = BoxBindable(None) self.bn_needle_profile_extract = BoxBindable(None) self.bn_needle_width_px = BoxBindable(math.nan) # Log self.bn_log = BoxBindable('') self.bn_is_done = AccessorBindable(getter=self._get_is_done) self.bn_is_cancelled = AccessorBindable(getter=self._get_is_cancelled) self.bn_progress = AccessorBindable(self._get_progress) self.bn_time_start = AccessorBindable(self._get_time_start) self.bn_time_est_complete = AccessorBindable(self._get_time_est_complete) self.bn_status.on_changed.connect(self.bn_is_done.poke) self.bn_status.on_changed.connect(self.bn_progress.poke) self._loop.create_task(self._input_image.read()).add_done_callback(self._hdl_input_image_read)
class GenicamAcquirer(CameraAcquirer): def __init__(self): super().__init__() self._harvester = harvesters.Harvester() for cti_path in os.environ.get('GENICAM_GENTL64_PATH', '').split(os.pathsep): for cti_file in map(str, Path(cti_path).glob('*.cti')): self._harvester.add_file(cti_file) self._camera_id = None self.bn_camera_id = AccessorBindable( self._get_camera_id) # type: ReadBindable[Optional[str]] self._camera_alive_changed_conn = None # type: Optional[EventConnection] def _get_camera_id(self) -> Optional[str]: if not GENICAM_ENABLED: return return self._camera_id def update(self) -> None: if not GENICAM_ENABLED: return self._harvester.update() def enumerate_cameras(self) -> Sequence[GenicamCameraInfo]: if not GENICAM_ENABLED: return () raw = self._harvester.device_info_list out = [] for raw_info in raw: camera_id = raw_info.id_ vendor = raw_info.vendor model = raw_info.model tl_type = raw_info.tl_type try: name = raw_info.user_defined_name except genicam.gentl.NotImplementedException: name = '{} {} ({})'.format(vendor, model, id) try: version = raw_info.version except genicam.gentl.NotImplementedException: version = 'n/a' out.append( GenicamCameraInfo( camera_id=camera_id, vendor=vendor, model=model, name=name, tl_type=tl_type, version=version, )) return out def open_camera(self, id_: str) -> None: if not GENICAM_ENABLED: raise RuntimeError("GenICam libraries not found") try: ia = self._harvester.create_image_acquirer(id_=id_) except ValueError: raise ValueError("Failed to open '{}'".format(id_)) try: new_camera = GenicamCamera(ia) except ValueError: raise ValueError("Failed to open '{}'".format(id_)) self.remove_current_camera(_poke_current_camera_id=False) self._camera_alive_changed_conn = new_camera.bn_alive.on_changed.connect( self._camera_alive_changed) self.bn_camera.set(new_camera) self._camera_id = id_ self.bn_camera_id.poke() def _camera_alive_changed(self) -> None: camera = self.bn_camera.get() assert camera is not None if camera.bn_alive.get() is False: self.remove_current_camera() def remove_current_camera(self, _poke_current_camera_id: bool = True) -> None: camera = self.bn_camera.get() if camera is None: return assert self._camera_alive_changed_conn is not None assert self._camera_alive_changed_conn.status is EventConnection.Status.CONNECTED self._camera_alive_changed_conn.disconnect() self._camera_alive_changed_conn = None camera.destroy() self._camera_id = None self.bn_camera.set(None) if _poke_current_camera_id: self.bn_camera_id.poke() def destroy(self) -> None: self.remove_current_camera(_poke_current_camera_id=False) self._harvester.reset() super().destroy()
class ConanSaveDialogView(View['ConanSaveDialogPresenter', Gtk.Window]): STYLE = ''' .small-pad { min-height: 0px; min-width: 0px; padding: 6px 4px 6px 4px; } .small-combobox .combo { min-height: 0px; min-width: 0px; } .conan-analysis-saver-view-footer-button { min-height: 0px; min-width: 60px; padding: 10px 4px 10px 4px; } .error { color: red; border: 1px solid red; } .error-text { color: red; } ''' _STYLE_PROV = Gtk.CssProvider() _STYLE_PROV.load_from_data(bytes(STYLE, 'utf-8')) Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), _STYLE_PROV, Gtk.STYLE_PROVIDER_PRIORITY_USER) def _do_init(self, parent_window: Optional[Gtk.Window] = None) -> Gtk.Window: self._window = Gtk.Window( title='Save analysis', resizable=False, modal=True, transient_for=parent_window, window_position=Gtk.WindowPosition.CENTER, ) body = Gtk.Grid(margin=10, row_spacing=10) self._window.add(body) content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) body.attach(content, 0, 0, 1, 1) save_location_frame = Gtk.Frame(label='Save location') content.add(save_location_frame) save_location_content = Gtk.Grid(margin=10, column_spacing=10, row_spacing=5) save_location_frame.add(save_location_content) save_dir_lbl = Gtk.Label('Parent:', xalign=0) save_location_content.attach(save_dir_lbl, 0, 0, 1, 1) self._save_dir_parent_inp = Gtk.FileChooserButton( action=Gtk.FileChooserAction.SELECT_FOLDER, hexpand=True) self._save_dir_parent_inp.get_style_context().add_class( 'small-combobox') save_location_content.attach_next_to(self._save_dir_parent_inp, save_dir_lbl, Gtk.PositionType.RIGHT, 1, 1) save_dir_parent_err_lbl = Gtk.Label(xalign=0, width_request=190) save_dir_parent_err_lbl.get_style_context().add_class('error-text') save_location_content.attach_next_to(save_dir_parent_err_lbl, self._save_dir_parent_inp, Gtk.PositionType.RIGHT, 1, 1) save_name_lbl = Gtk.Label('Name:', xalign=0) save_location_content.attach(save_name_lbl, 0, 1, 1, 1) save_dir_name_inp = Gtk.Entry() save_dir_name_inp.get_style_context().add_class('small-pad') save_location_content.attach_next_to(save_dir_name_inp, save_name_lbl, Gtk.PositionType.RIGHT, 1, 1) save_dir_name_err_lbl = Gtk.Label(xalign=0, width_request=190) save_dir_name_err_lbl.get_style_context().add_class('error-text') save_location_content.attach_next_to(save_dir_name_err_lbl, save_dir_name_inp, Gtk.PositionType.RIGHT, 1, 1) figures_frame = Gtk.Frame(label='Figures') content.add(figures_frame) figures_content = Gtk.Grid(margin=10, column_spacing=10, row_spacing=5) figures_frame.add(figures_content) _, angles_figure_options_area = self.new_component( figure_options_cs.factory( model=self.presenter.angle_figure_options, figure_name='contact angles plot', )) angles_figure_options_area.show() figures_content.attach(angles_figure_options_area, 0, 0, 1, 1) footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) body.attach_next_to(footer, content, Gtk.PositionType.BOTTOM, 1, 1) ok_btn = Gtk.Button('OK') ok_btn.get_style_context().add_class( 'conan-analysis-saver-view-footer-button') footer.pack_end(ok_btn, expand=False, fill=False, padding=0) cancel_btn = Gtk.Button('Cancel') cancel_btn.get_style_context().add_class( 'conan-analysis-saver-view-footer-button') footer.pack_end(cancel_btn, expand=False, fill=False, padding=0) self._window.show_all() # Wiring things up ok_btn.connect('clicked', lambda *_: self.presenter.ok()) cancel_btn.connect('clicked', lambda *_: self.presenter.cancel()) self._window.connect('delete-event', self._hdl_window_delete_event) self.bn_save_dir_parent = AccessorBindable(self._get_save_dir_parent, self._set_save_dir_parent) self.bn_save_dir_name = GObjectPropertyBindable( save_dir_name_inp, 'text') self._confirm_overwrite_dialog = None self._file_exists_info_dialog = None self.presenter.view_ready() return self._window def _hdl_window_delete_event(self, widget: Gtk.Dialog, event: Gdk.Event) -> bool: self.presenter.cancel() # return True to prevent the dialog from closing. return True def show_confirm_overwrite_dialog(self, path: Path) -> None: if self._confirm_overwrite_dialog is not None: return self._confirm_overwrite_dialog = YesNoDialog(message_format=( "This save location '{!s}' already exists, do you want to clear its contents?" .format(path)), parent=self._window) self._confirm_overwrite_dialog.connect( 'response', self._hdl_confirm_overwrite_dialog_response) self._confirm_overwrite_dialog.connect('delete-event', lambda *_: True) self._confirm_overwrite_dialog.show() def _hdl_confirm_overwrite_dialog_response( self, widget: Gtk.Dialog, response: Gtk.ResponseType) -> None: accept = (response == Gtk.ResponseType.YES) self.presenter.hdl_confirm_overwrite_dialog_response(accept) def hide_confirm_overwrite_dialog(self) -> None: if self._confirm_overwrite_dialog is None: return self._confirm_overwrite_dialog.destroy() self._confirm_overwrite_dialog = None def tell_user_file_exists_and_is_not_a_directory(self, path: Path) -> None: if self._file_exists_info_dialog is not None: return self._file_exists_info_dialog = ErrorDialog(message_format=( "Cannot save to '{!s}', the path already exists and is a non-directory file." .format(path)), parent=self._window) self._file_exists_info_dialog.show() def hdl_delete_event(*_) -> None: self._file_exists_info_dialog = None def hdl_response(dialog: Gtk.Window, *_) -> None: self._file_exists_info_dialog = None dialog.destroy() self._file_exists_info_dialog.connect('delete-event', hdl_delete_event) self._file_exists_info_dialog.connect('response', hdl_response) def _get_save_dir_parent(self) -> Path: path_str = self._save_dir_parent_inp.get_filename() path = Path(path_str) if path_str is not None else None return path def _set_save_dir_parent(self, path: Optional[Path]) -> None: if path is None: self._save_dir_parent_inp.unselect_all() return path = str(path) self._save_dir_parent_inp.set_filename(path) def flush_save_dir_parent(self) -> None: self.bn_save_dir_parent.poke() def _do_destroy(self) -> None: self._window.destroy() self.hide_confirm_overwrite_dialog()
def _do_init(self, parent_window: Optional[Gtk.Window] = None) -> Gtk.Window: self._window = Gtk.Window( title='Save analysis', resizable=False, modal=True, transient_for=parent_window, window_position=Gtk.WindowPosition.CENTER, ) body = Gtk.Grid(margin=10, row_spacing=10) self._window.add(body) content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) body.attach(content, 0, 0, 1, 1) save_location_frame = Gtk.Frame(label='Save location') content.add(save_location_frame) save_location_content = Gtk.Grid(margin=10, column_spacing=10, row_spacing=5) save_location_frame.add(save_location_content) save_dir_lbl = Gtk.Label('Parent:', xalign=0) save_location_content.attach(save_dir_lbl, 0, 0, 1, 1) self._save_dir_parent_inp = Gtk.FileChooserButton( action=Gtk.FileChooserAction.SELECT_FOLDER, hexpand=True) self._save_dir_parent_inp.get_style_context().add_class( 'small-combobox') save_location_content.attach_next_to(self._save_dir_parent_inp, save_dir_lbl, Gtk.PositionType.RIGHT, 1, 1) save_dir_parent_err_lbl = Gtk.Label(xalign=0, width_request=190) save_dir_parent_err_lbl.get_style_context().add_class('error-text') save_location_content.attach_next_to(save_dir_parent_err_lbl, self._save_dir_parent_inp, Gtk.PositionType.RIGHT, 1, 1) save_name_lbl = Gtk.Label('Name:', xalign=0) save_location_content.attach(save_name_lbl, 0, 1, 1, 1) save_dir_name_inp = Gtk.Entry() save_dir_name_inp.get_style_context().add_class('small-pad') save_location_content.attach_next_to(save_dir_name_inp, save_name_lbl, Gtk.PositionType.RIGHT, 1, 1) save_dir_name_err_lbl = Gtk.Label(xalign=0, width_request=190) save_dir_name_err_lbl.get_style_context().add_class('error-text') save_location_content.attach_next_to(save_dir_name_err_lbl, save_dir_name_inp, Gtk.PositionType.RIGHT, 1, 1) figures_frame = Gtk.Frame(label='Figures') content.add(figures_frame) figures_content = Gtk.Grid(margin=10, column_spacing=10, row_spacing=5) figures_frame.add(figures_content) _, angles_figure_options_area = self.new_component( figure_options_cs.factory( model=self.presenter.angle_figure_options, figure_name='contact angles plot', )) angles_figure_options_area.show() figures_content.attach(angles_figure_options_area, 0, 0, 1, 1) footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) body.attach_next_to(footer, content, Gtk.PositionType.BOTTOM, 1, 1) ok_btn = Gtk.Button('OK') ok_btn.get_style_context().add_class( 'conan-analysis-saver-view-footer-button') footer.pack_end(ok_btn, expand=False, fill=False, padding=0) cancel_btn = Gtk.Button('Cancel') cancel_btn.get_style_context().add_class( 'conan-analysis-saver-view-footer-button') footer.pack_end(cancel_btn, expand=False, fill=False, padding=0) self._window.show_all() # Wiring things up ok_btn.connect('clicked', lambda *_: self.presenter.ok()) cancel_btn.connect('clicked', lambda *_: self.presenter.cancel()) self._window.connect('delete-event', self._hdl_window_delete_event) self.bn_save_dir_parent = AccessorBindable(self._get_save_dir_parent, self._set_save_dir_parent) self.bn_save_dir_name = GObjectPropertyBindable( save_dir_name_inp, 'text') self._confirm_overwrite_dialog = None self._file_exists_info_dialog = None self.presenter.view_ready() return self._window