Esempio n. 1
0
    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,
        )
Esempio n. 2
0
    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, )
Esempio n. 3
0
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()
Esempio n. 4
0
class TestAccessorBindable_WithNoGetter:
    def setup(self):
        self.bindable = AccessorBindable(setter=Mock())

    def test_get(self):
        with pytest.raises(NotImplementedError):
            self.bindable.get()
Esempio n. 5
0
    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]
Esempio n. 6
0
    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()
Esempio n. 7
0
    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()
Esempio n. 8
0
    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]
Esempio n. 9
0
    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,
        )
Esempio n. 10
0
    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,
        )
Esempio n. 11
0
    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,
        )
Esempio n. 12
0
    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,
        )
Esempio n. 13
0
    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()
Esempio n. 15
0
    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, )
Esempio n. 16
0
    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)
Esempio n. 17
0
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
Esempio n. 18
0
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
Esempio n. 19
0
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
Esempio n. 20
0
    def __init__(self) -> None:
        self._acquirer = None  # type: Optional[ImageAcquirer]

        self.bn_acquirer = AccessorBindable(getter=self._get_acquirer, )
Esempio n. 21
0
 def setup(self):
     self.bindable = AccessorBindable(setter=Mock())
Esempio n. 22
0
    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
Esempio n. 23
0
    def setup(self):
        self.getter = Mock()
        self.setter = Mock()

        self.bindable = AccessorBindable(getter=self.getter, setter=self.setter)
Esempio n. 24
0
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()
Esempio n. 25
0
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
Esempio n. 26
0
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()
Esempio n. 27
0
    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)
Esempio n. 28
0
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()
Esempio n. 29
0
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()
Esempio n. 30
0
    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