Пример #1
0
    def _ylfit_incremental_update(self, ylfit: YoungLaplaceFit) -> None:
        if self._stop_flag:
            ylfit.cancel()
            return

        editor = self._data.edit(timeout=1)
        assert editor is not None

        try:
            apex_pos = Vector2(ylfit.apex_x, ylfit.apex_y)
            rotation = ylfit.rotation
            profile_fit = ylfit(np.linspace(0, 1,
                                            num=self.PROFILE_FIT_SAMPLES))

            if not self._is_sessile:
                apex_pos = Vector2(apex_pos.x, -apex_pos.y)
                rotation *= -1
                profile_fit[:, 1] *= -1

            editor.set_value('apex_pos', apex_pos)
            editor.set_value('apex_radius', ylfit.apex_radius)
            editor.set_value('bond_number', ylfit.bond_number)
            editor.set_value('rotation', rotation)
            editor.set_value('profile_fit', profile_fit)
            editor.set_value('residuals', ylfit.residuals)
            editor.set_value('volume', ylfit.volume)
            editor.set_value('surface_area', ylfit.surface_area)
        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()
Пример #2
0
    def _recalculate(self) -> None:
        features = self._features
        params = self.params

        drop_profile = features.bn_drop_profile_px.get()
        if drop_profile is None:
            return

        surface = params.bn_surface_line_px.get()
        if surface is None:
            return

        drop_profile = drop_profile.copy()
        surface_poly1d = np.poly1d((surface.gradient, surface.eval(x=0).y))

        # ContactAngle expects the coordinates of drop profile to be such that the surface has a lower y-coordinate than
        # the drop, so mirror the drop in y-direction. (Remember drop profile is in 'image coordinates', where
        # increasing y-coordinate is 'downwards')
        drop_profile[:, 1] *= -1
        surface_poly1d = -surface_poly1d

        conancalc = ContactAngle(drop_profile, surface_poly1d)

        # Mirror the tangents and contact points back to original coordinate system as well.
        left_tangent = -conancalc.left_tangent
        left_point = Vector2(x=conancalc.left_point.x, y=-conancalc.left_point.y)
        right_tangent = -conancalc.right_tangent
        right_point = Vector2(x=conancalc.right_point.x, y=-conancalc.right_point.y)

        self.bn_left_tangent.set(left_tangent)
        self.bn_left_angle.set(conancalc.left_angle)
        self.bn_left_point.set(left_point)
        self.bn_right_tangent.set(right_tangent)
        self.bn_right_angle.set(conancalc.right_angle)
        self.bn_right_point.set(right_point)
Пример #3
0
    def _calculate_right_contact_point(
            self, right_contact_tangent: np.poly1d) -> Vector2[float]:
        roots = right_contact_tangent.roots
        if not roots:
            return Vector2(math.nan, math.nan)

        return Vector2(roots[0], right_contact_tangent(roots[0]))
Пример #4
0
    def __init__(self, drop_profile: np.ndarray, surface: np.poly1d) -> None:
        self._drop_profile = drop_profile
        self._surface = surface

        self.left_tangent, self.left_angle, self.left_point \
            = (np.poly1d((math.nan, math.nan)), math.nan, Vector2(math.nan, math.nan))

        self.right_tangent, self.right_angle, self.right_point \
            = (np.poly1d((math.nan, math.nan)), math.nan, Vector2(math.nan, math.nan))

        self._calculate()
Пример #5
0
    def _widget_dist_from_canvas(
            self, dist_canvas: Vector2Like[float]) -> Vector2[float]:
        dist_canvas = Vector2(*dist_canvas)

        viewport_size = self.props.viewport_extents.size
        viewport_widget_size = self.props.viewport_widget_extents.size

        scale = Vector2(viewport_widget_size.x / viewport_size.x,
                        viewport_widget_size.y / viewport_size.y)
        dist_widget = Vector2(scale.x * dist_canvas.x, scale.y * dist_canvas.y)

        return dist_widget
Пример #6
0
    def _canvas_dist_from_widget(
            self, dist_widget: Tuple[float, float]) -> Vector2[float]:
        dist_widget = Vector2(*dist_widget)

        viewport_size = self.props.viewport_extents.size
        viewport_widget_size = self.props.viewport_widget_extents.size

        scale = Vector2(viewport_size.x / viewport_widget_size.x,
                        viewport_size.y / viewport_widget_size.y)
        dist_canvas = Vector2(scale.x * dist_widget.x, scale.y * dist_widget.y)

        return dist_canvas
Пример #7
0
    def _calculate(self) -> None:
        drop_profile = np.copy(self._drop_profile)
        surface = self._surface

        surface_angle = math.atan(surface.c[0]) if len(surface.c) > 1 else 0

        rot_mtx = np.array(
            [[math.cos(surface_angle), -math.sin(surface_angle)],
             [math.sin(surface_angle),
              math.cos(surface_angle)]])

        # Transform drop profile to coordinates where surface line is y=0
        drop_profile = drop_profile.astype(float)
        drop_profile[:, 1] -= surface.c[-1]

        drop_profile = (rot_mtx.T @ drop_profile.T).T
        drop_profile = drop_profile.astype(int)

        self._right_segment, self.right_tangent, self.right_angle, self.right_point \
            = self._calculate_right_params(drop_profile)

        # Mirror the contour left-to-right
        drop_profile = np.flipud(drop_profile)
        drop_profile[:, 0] *= -1

        self._left_segment, self.left_tangent, self.left_angle, self.left_point \
            = self._calculate_right_params(drop_profile)

        # Mirror back tangent and contact point.
        self.left_tangent = np.poly1d(self.left_tangent.coefficients * [-1, 1])
        self.left_point = Vector2(-self.left_point.x, self.left_point.y)
        self._left_segment = self._left_segment * [-1, 1]

        # Transform back to given coordinates.
        self._left_segment = (rot_mtx @ self._left_segment.T).T
        self._left_segment[:, 1] += surface.c[-1]
        self._left_segment = self._left_segment.astype(int)

        self._right_segment = (rot_mtx @ self._right_segment.T).T
        self._right_segment[:, 1] += surface.c[-1]
        self._right_segment = self._right_segment.astype(int)

        self.left_tangent = _transform_line(self.left_tangent, rot_mtx)
        self.left_tangent += surface.c[-1]
        self.left_point = Vector2(*(rot_mtx @ self.left_point))
        self.left_point += (0, surface.c[-1])

        self.right_tangent = _transform_line(self.right_tangent, rot_mtx)
        self.right_tangent += surface.c[-1]
        self.right_point = Vector2(*(rot_mtx @ self.right_point))
        self.right_point += (0, surface.c[-1])
Пример #8
0
    def _do_init(self, in_analysis: Bindable[Optional[ConanAnalysis]]) -> None:
        self._bn_analysis = in_analysis
        self._analysis_unbind_tasks = []

        self.bn_image = VariableBindable(None)

        self.bn_left_angle = VariableBindable(math.nan)
        self.bn_left_point = VariableBindable(Vector2(math.nan, math.nan))
        self.bn_right_angle = VariableBindable(math.nan)
        self.bn_right_point = VariableBindable(Vector2(math.nan, math.nan))

        self.bn_surface_line = VariableBindable(None)

        self.__event_connections = []
Пример #9
0
 def _viewport_widget_size(self) -> Vector2[float]:
     widget_size = Vector2(self.get_allocated_width(),
                           self.get_allocated_height())
     return self._calculate_stretch_size(
         stretch=self._viewport_stretch,
         reference_size=widget_size,
         child_size=self._viewport_extents.size)
Пример #10
0
    def __init__(self, features: FeatureExtractor, params: ContactAngleCalculatorParams) -> None:
        self._features = features
        self.params = params

        self.bn_left_tangent = VariableBindable(np.poly1d((math.nan, math.nan)))
        self.bn_left_angle = VariableBindable(math.nan)
        self.bn_left_point = VariableBindable(Vector2(math.nan, math.nan))
        self.bn_right_tangent = VariableBindable(np.poly1d((math.nan, math.nan)))
        self.bn_right_angle = VariableBindable(math.nan)
        self.bn_right_point = VariableBindable(Vector2(math.nan, math.nan))

        # Recalculate when inputs change
        features.bn_drop_profile_px.on_changed.connect(self._recalculate)
        params.bn_surface_line_px.on_changed.connect(self._recalculate)

        self._recalculate()
Пример #11
0
    def _canvas_coord_from_widget(
            self, coord_widget: Tuple[float, float]) -> Vector2[float]:
        coord_widget = Vector2(*coord_widget)

        viewport_extents = self.props.viewport_extents
        viewport_widget_extents = self.props.viewport_widget_extents

        coord_viewport_widget = coord_widget - viewport_widget_extents.position
        coord_viewport_pct = Vector2(
            coord_viewport_widget.x / viewport_widget_extents.size.x,
            coord_viewport_widget.y / viewport_widget_extents.size.y)
        coord_viewport = Vector2(
            coord_viewport_pct.x * viewport_extents.size.x,
            coord_viewport_pct.y * viewport_extents.size.y)
        coord_canvas = viewport_extents.position + coord_viewport

        return coord_canvas
Пример #12
0
    def _widget_coord_from_canvas(
            self, coord_canvas: Vector2Like[float]) -> Vector2[float]:
        coord_canvas = Vector2(*coord_canvas)

        viewport_extents = self.props.viewport_extents
        viewport_widget_extents = self.props.viewport_widget_extents

        coord_viewport = coord_canvas - viewport_extents.pos
        coord_viewport_pct = Vector2(
            coord_viewport.x / viewport_extents.size.x,
            coord_viewport.y / viewport_extents.size.y)
        coord_viewport_widget = Vector2(
            coord_viewport_pct.x * viewport_widget_extents.size.x,
            coord_viewport_pct.y * viewport_widget_extents.size.y)
        coord_widget = viewport_widget_extents.pos + coord_viewport_widget

        return coord_widget
Пример #13
0
    def _calculate_right_params(self, drop_profile: np.ndarray) -> Tuple[np.ndarray, np.poly1d, float, Vector2[float]]:
        try:
            right_segment, right_tangent = self._calculate_right_contact_tangent(drop_profile)
        except self.NotEnoughDropPoints:
            return (np.empty((0, 2)), np.poly1d((math.nan, math.nan)), math.nan, Vector2(math.nan, math.nan))

        return (
            right_segment,
            right_tangent,
            self._calculate_right_contact_angle(right_tangent),
            self._calculate_right_contact_point(right_tangent)
        )
Пример #14
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()
Пример #15
0
def draw_angle_marker(image: np.ndarray, vertex_pos: Vector2[float],
                      start_angle: float, delta_angle: float, radius: float,
                      color: Tuple[float, float, float]) -> None:
    if not Rect2(pos=(0, 0),
                 size=image.shape[1::-1]).contains_point(vertex_pos):
        # Vertex is outside of the image, ignore.
        return

    end_angle = start_angle + delta_angle

    start_pos = vertex_pos
    delta_pos = radius * Vector2(math.cos(-end_angle), math.sin(-end_angle))
    end_pos = start_pos + delta_pos

    cv2.line(image,
             pt1=tuple(start_pos.as_type(int)),
             pt2=tuple(end_pos.as_type(int)),
             color=color,
             thickness=1)
Пример #16
0
class Render(Gtk.DrawingArea, protocol.Render):
    _canvas_size = Vector2(1, 1)
    _viewport_extents = Rect2(position=(0, 0),
                              size=_canvas_size)  # type: Rect2[float]
    _viewport_stretch = protocol.Render.ViewportStretch.FIT

    STYLE = """
        .render {
            border: 1px solid white;
        }
    """

    _STYLE_PROV = Gtk.CssProvider()
    _STYLE_PROV.load_from_data(bytes(STYLE, 'utf-8'))

    class RenderObjectContainer:
        def __init__(self, render_object: protocol.RenderObject,
                     handler_ids: Sequence[int]) -> None:
            self.render_object = render_object
            self.handler_ids = tuple(handler_ids)

    def __init__(self, *, can_focus=True, **options) -> None:
        super().__init__(focus_on_click=True, can_focus=can_focus, **options)
        self._ro_containers = [
        ]  # type: MutableSequence[Render.RenderObjectContainer]

        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK
                        | Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK
                        | Gdk.EventMask.FOCUS_CHANGE_MASK
                        | Gdk.EventMask.ENTER_NOTIFY_MASK
                        | Gdk.EventMask.LEAVE_NOTIFY_MASK
                        | Gdk.EventMask.KEY_PRESS_MASK)

        self.get_style_context().add_class('render')
        self.get_style_context().add_provider(self._STYLE_PROV,
                                              Gtk.STYLE_PROVIDER_PRIORITY_USER)

    def do_draw(self, cr: cairo.Context) -> None:
        viewport_widget_extents = self.props.viewport_widget_extents

        with cairo_saved(cr):
            cr.rectangle(*viewport_widget_extents.position,
                         *viewport_widget_extents.size)
            cr.clip()
            for ro in self._render_objects:
                ro.draw(cr)

        if self.has_focus():
            # Draw focus indicator
            stroke_width = 1
            rectangle_pos = viewport_widget_extents.position + (
                stroke_width / 2, stroke_width / 2)
            rectangle_size = viewport_widget_extents.size - (stroke_width,
                                                             stroke_width)
            cr.rectangle(*rectangle_pos, *rectangle_size)
            cr.set_source_rgb(70 / 255, 142 / 255, 220 / 255)
            cr.set_line_width(stroke_width)
            cr.stroke()

    @property
    def _render_objects(self) -> Sequence[protocol.RenderObject]:
        return sorted(
            (container.render_object for container in self._ro_containers),
            key=lambda ro: ro.props.z_index,
        )

    def add_render_object(self, ro: protocol.RenderObject) -> None:
        handler_ids = (ro.connect('request-draw',
                                  lambda _: self.queue_draw()), )
        self._ro_containers.append(
            self.RenderObjectContainer(render_object=ro,
                                       handler_ids=handler_ids))
        ro.set_parent(self)

        self.queue_draw()

    def remove_render_object(self, ro: protocol.RenderObject) -> None:
        container = self._ro_container_from_ro(ro)

        for handler_id in container.handler_ids:
            ro.disconnect(handler_id)
        self._ro_containers.remove(container)

        self.queue_draw()

    def _ro_container_from_ro(
            self, ro: protocol.RenderObject) -> 'Render.RenderObjectContainer':
        for container in self._ro_containers:
            if container.render_object is ro:
                return container
        else:
            raise ValueError('No container found for {}.'.format(ro))

    def do_button_press_event(self, event: Gdk.EventButton) -> None:
        self.emit('cursor-down-event',
                  self._canvas_coord_from_widget(Vector2(event.x, event.y)))

        if self.props.can_focus:
            self.grab_focus()

    def do_button_release_event(self, event: Gdk.EventButton) -> None:
        self.emit('cursor-up-event',
                  self._canvas_coord_from_widget(Vector2(event.x, event.y)))

    def do_motion_notify_event(self, event: Gdk.EventButton) -> None:
        self.emit('cursor-motion-event',
                  self._canvas_coord_from_widget(Vector2(event.x, event.y)))

    def do_key_press_event(self, event: Gdk.EventKey) -> bool:
        if event.keyval == Gdk.KEY_Tab:
            # Allow user to use the tab key to cycle focus to another widget
            return False

        # Stop event propagation
        return True

    # Cursor signals

    @GObject.Signal(arg_types=(object, ))
    def cursor_down_event(self, pos: Vector2[float]) -> None:
        pass

    @GObject.Signal(arg_types=(object, ))
    def cursor_up_event(self, pos: Vector2[float]) -> None:
        pass

    @GObject.Signal(arg_types=(object, ))
    def cursor_motion_event(self, pos: Vector2[float]) -> None:
        pass

    # Coordinate transform functions

    def _widget_coord_from_canvas(
            self, coord_canvas: Tuple[float, float]) -> Vector2[float]:
        coord_canvas = Vector2(*coord_canvas)

        viewport_extents = self.props.viewport_extents
        viewport_widget_extents = self.props.viewport_widget_extents

        coord_viewport = coord_canvas - viewport_extents.position
        coord_viewport_pct = Vector2(
            coord_viewport.x / viewport_extents.size.x,
            coord_viewport.y / viewport_extents.size.y)
        coord_viewport_widget = Vector2(
            coord_viewport_pct.x * viewport_widget_extents.size.x,
            coord_viewport_pct.y * viewport_widget_extents.size.y)
        coord_widget = viewport_widget_extents.position + coord_viewport_widget

        return coord_widget

    def _canvas_coord_from_widget(
            self, coord_widget: Tuple[float, float]) -> Vector2[float]:
        coord_widget = Vector2(*coord_widget)

        viewport_extents = self.props.viewport_extents
        viewport_widget_extents = self.props.viewport_widget_extents

        coord_viewport_widget = coord_widget - viewport_widget_extents.position
        coord_viewport_pct = Vector2(
            coord_viewport_widget.x / viewport_widget_extents.size.x,
            coord_viewport_widget.y / viewport_widget_extents.size.y)
        coord_viewport = Vector2(
            coord_viewport_pct.x * viewport_extents.size.x,
            coord_viewport_pct.y * viewport_extents.size.y)
        coord_canvas = viewport_extents.position + coord_viewport

        return coord_canvas

    def _widget_dist_from_canvas(
            self, dist_canvas: Tuple[float, float]) -> Vector2[float]:
        dist_canvas = Vector2(*dist_canvas)

        viewport_size = self.props.viewport_extents.size
        viewport_widget_size = self.props.viewport_widget_extents.size

        scale = Vector2(viewport_widget_size.x / viewport_size.x,
                        viewport_widget_size.y / viewport_size.y)
        dist_widget = Vector2(scale.x * dist_canvas.x, scale.y * dist_canvas.y)

        return dist_widget

    def _canvas_dist_from_widget(
            self, dist_widget: Tuple[float, float]) -> Vector2[float]:
        dist_widget = Vector2(*dist_widget)

        viewport_size = self.props.viewport_extents.size
        viewport_widget_size = self.props.viewport_widget_extents.size

        scale = Vector2(viewport_size.x / viewport_widget_size.x,
                        viewport_size.y / viewport_widget_size.y)
        dist_canvas = Vector2(scale.x * dist_widget.x, scale.y * dist_widget.y)

        return dist_canvas

    # Canvas geometry properties

    @GObject.Property
    def canvas_size(self) -> Vector2[float]:
        return self._canvas_size

    @canvas_size.setter
    def canvas_size(self, new_size: Vector2[float]) -> None:
        self._canvas_size = new_size
        self.queue_draw()

    @GObject.Property
    def viewport_extents(self) -> Rect2[float]:
        return self._viewport_extents

    @viewport_extents.setter
    def viewport_extents(self, new_extents: Rect2[float]) -> None:
        self._viewport_extents = new_extents
        self.queue_draw()

    @GObject.Property
    def viewport_widget_extents(self) -> Rect2[float]:
        return Rect2(position=self._viewport_widget_pos,
                     size=self._viewport_widget_size)

    @property
    def _viewport_widget_size(self) -> Vector2[float]:
        widget_size = Vector2(self.get_allocated_width(),
                              self.get_allocated_height())
        return self._calculate_stretch_size(
            stretch=self._viewport_stretch,
            reference_size=widget_size,
            child_size=self._viewport_extents.size)

    @property
    def _viewport_widget_pos(self) -> Vector2[float]:
        widget_size = Vector2(self.get_allocated_width(),
                              self.get_allocated_height())
        return self._calculate_offset_for_centred_rectangles(
            reference_size=widget_size, child_size=self._viewport_widget_size)

    @classmethod
    def _calculate_stretch_size(cls, stretch: protocol.Render.ViewportStretch,
                                reference_size: Vector2[float],
                                child_size: Vector2[float]) -> Vector2[float]:
        reference_aspect = reference_size[0] / reference_size[1]
        child_aspect = child_size[0] / child_size[1]

        if stretch is cls.ViewportStretch.FILL and (reference_aspect > child_aspect) \
                or stretch is cls.ViewportStretch.FIT and (reference_aspect <= child_aspect):
            scale_factor = reference_size[0] / child_size[0]
        else:
            scale_factor = reference_size[1] / child_size[1]

        return child_size * scale_factor

    @staticmethod
    def _calculate_offset_for_centred_rectangles(reference_size: Vector2[float], child_size: Vector2[float]) \
            -> Vector2[float]:
        offset = reference_size / 2 - child_size / 2
        return offset
Пример #17
0
 def _viewport_widget_pos(self) -> Vector2[float]:
     widget_size = Vector2(self.get_allocated_width(),
                           self.get_allocated_height())
     return self._calculate_offset_for_centred_rectangles(
         reference_size=widget_size, child_size=self._viewport_widget_size)
Пример #18
0
 def do_motion_notify_event(self, event: Gdk.EventButton) -> None:
     self.emit('cursor-motion-event',
               self._canvas_coord_from_widget(Vector2(event.x, event.y)))
Пример #19
0
 def do_button_release_event(self, event: Gdk.EventButton) -> None:
     self.emit('cursor-up-event',
               self._canvas_coord_from_widget(Vector2(event.x, event.y)))
Пример #20
0
    def do_button_press_event(self, event: Gdk.EventButton) -> None:
        self.emit('cursor-down-event',
                  self._canvas_coord_from_widget(Vector2(event.x, event.y)))

        if self.props.can_focus:
            self.grab_focus()
Пример #21
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)
Пример #22
0
    def __init__(
        self,
        input_image: InputImage,
        do_extract_features: Callable[[Bindable[np.ndarray]],
                                      FeatureExtractor],
        do_calculate_conan: Callable[[FeatureExtractor],
                                     ContactAngleCalculator],
    ) -> 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_calculate_conan = do_calculate_conan

        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._calculated_conan = None  # type: Optional[ContactAngleCalculator]

        self.bn_image = AccessorBindable(self._get_image)
        self.bn_image_timestamp = AccessorBindable(self._get_image_timestamp)

        # Attributes from ContactAngleCalculator
        self.bn_left_angle = BoxBindable(math.nan)
        self.bn_left_tangent = BoxBindable(np.poly1d((math.nan, math.nan)))
        self.bn_left_point = BoxBindable(Vector2(math.nan, math.nan))
        self.bn_right_angle = BoxBindable(math.nan)
        self.bn_right_tangent = BoxBindable(np.poly1d((math.nan, math.nan)))
        self.bn_right_point = BoxBindable(Vector2(math.nan, math.nan))

        self.bn_surface_line = BoxBindable(None)

        # Attributes from FeatureExtractor
        self.bn_drop_region = BoxBindable(None)
        self.bn_drop_profile_extract = BoxBindable(None)

        # Log
        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)