Skip to content

Sensor tracking

Module contains a guidance sensor for tracking the "tracking" task acceptability, see TrackingTaskAcceptabilitySensor documentation for details.

TrackingTaskAcceptabilitySensor

Bases: TaskAcceptabilitySensor

Guidance sensor for the tracking task.

Source code in matbii\guidance\sensor_tracking.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class TrackingTaskAcceptabilitySensor(TaskAcceptabilitySensor):
    """Guidance sensor for the tracking task."""

    _BOX_ID = tracking_box_id()
    _TARGET_ID = tracking_target_id()

    def __init__(self, *args: list[Any], **kwargs: dict[str, Any]):
        """Constructor.

        Args:
            args (list[Any], optional): additional optional arguments.
            kwargs (dict[Any], optional): additional optional keyword arguments.
        """
        super().__init__(TASK_ID_TRACKING, *args, **kwargs)
        # initialise beliefs about the tracking tasks
        self.beliefs[TrackingTaskAcceptabilitySensor._BOX_ID] = dict()
        self.beliefs[TrackingTaskAcceptabilitySensor._TARGET_ID] = dict()

    def is_acceptable(self, task: str = None, **kwargs: dict[str, Any]) -> bool:  # noqa
        if task is None or task == self.task_name:
            return self.is_tracking_acceptable()
        else:
            raise KeyError(
                f"Invalid subtask: {task}, doesn't exist for task {self.task_name}"
            )

    def is_tracking_acceptable(self) -> bool:
        """Determines whether the tracking task is in an acceptable state.

        Acceptable: "target" is within the central box of the task.
        Unacceptable: otherwise.

        Raises:
            ValueError: if there is missing observational data about the task - this may mean the task is not active.

        Returns:
            bool: whether the task is in an acceptable state.
        """
        try:
            target = self.beliefs.get(TrackingTaskAcceptabilitySensor._TARGET_ID, None)
        except KeyError:
            raise KeyError(
                f"Failed to determine acceptability of task: '{TASK_ID_TRACKING}'.\n-- Missing beliefs for elements: '{TrackingTaskAcceptabilitySensor._TARGET_ID}'"
            )
        try:
            box = self.beliefs.get(TrackingTaskAcceptabilitySensor._BOX_ID, None)
        except KeyError:
            raise KeyError(
                f"Failed to determine acceptability of task: '{TASK_ID_TRACKING}'.\n-- Missing beliefs for elements: '{TrackingTaskAcceptabilitySensor._BOX_ID}'"
            )
        return TrackingTaskAcceptabilitySensor.is_point_in_rectangle(
            target["xy"], box["tl"], box["br"]
        )

    def on_observation(self, observation: Observation):
        """Update beliefs about the tracking task based on the incoming observation.

        Args:
            observation (Observation): observation
        """
        for data in observation.values:
            try:
                if data["id"] == TrackingTaskAcceptabilitySensor._TARGET_ID:
                    tl = (data["x"], data["y"])
                    size = (data["width"], data["height"])
                    xy = (tl[0] + size[0] / 2, tl[1] + size[1] / 2)
                    self.beliefs[TrackingTaskAcceptabilitySensor._TARGET_ID]["xy"] = xy
                    self.beliefs[TrackingTaskAcceptabilitySensor._TARGET_ID]["size"] = (
                        size
                    )
                elif data["id"] == TrackingTaskAcceptabilitySensor._BOX_ID:
                    tl = (data["x"], data["y"])
                    self.beliefs[TrackingTaskAcceptabilitySensor._BOX_ID]["tl"] = tl
                    self.beliefs[TrackingTaskAcceptabilitySensor._BOX_ID]["br"] = (
                        tl[0] + data["width"],
                        tl[1] + data["height"],
                    )
            except KeyError:
                raise KeyError(
                    f"Observation: {observation} doesn't contain the required `id` attribute."
                )

    def sense(self) -> list[Select]:
        """Generates the sense actions that are required for checking whether the tracking task is in an acceptable state.

        The actions will request the following data:
        - the bounds of the target element.
        - the bounds of the central box of the tracking task.

        Returns:
            list[Select]: list of sense actions to take.
        """
        return [
            select(
                xpath=f"//*[@id='{tracking_target_id()}']",
                attrs=["id", "x", "y", "width", "height"],
            ),
            select(
                xpath=f"//*[@id='{tracking_box_id()}']",
                attrs=["id", "x", "y", "width", "height"],
            ),
        ]

    @staticmethod
    def is_point_in_rectangle(
        point: tuple[float, float],
        rect_min: tuple[float, float],
        rect_max: tuple[float, float],
    ) -> bool:
        """Checks wether the given `point` is within the rectangle as specified by the top left and bottom right coordinate.

        Args:
            point (tuple[float, float]): point to check.
            rect_min (tuple[float,float]): top left.
            rect_max (tuple[float,float]): bottom right.

        Returns:
            bool: whether the point is in the rectangle.
        """
        px, py = point
        min_x, min_y = rect_min
        max_x, max_y = rect_max
        return min_x <= px <= max_x and min_y <= py <= max_y

__init__(*args, **kwargs)

Constructor.

Parameters:

Name Type Description Default
args list[Any]

additional optional arguments.

()
kwargs dict[Any]

additional optional keyword arguments.

{}
Source code in matbii\guidance\sensor_tracking.py
17
18
19
20
21
22
23
24
25
26
27
def __init__(self, *args: list[Any], **kwargs: dict[str, Any]):
    """Constructor.

    Args:
        args (list[Any], optional): additional optional arguments.
        kwargs (dict[Any], optional): additional optional keyword arguments.
    """
    super().__init__(TASK_ID_TRACKING, *args, **kwargs)
    # initialise beliefs about the tracking tasks
    self.beliefs[TrackingTaskAcceptabilitySensor._BOX_ID] = dict()
    self.beliefs[TrackingTaskAcceptabilitySensor._TARGET_ID] = dict()

is_point_in_rectangle(point, rect_min, rect_max) staticmethod

Checks wether the given point is within the rectangle as specified by the top left and bottom right coordinate.

Parameters:

Name Type Description Default
point tuple[float, float]

point to check.

required
rect_min tuple[float, float]

top left.

required
rect_max tuple[float, float]

bottom right.

required

Returns:

Name Type Description
bool bool

whether the point is in the rectangle.

Source code in matbii\guidance\sensor_tracking.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def is_point_in_rectangle(
    point: tuple[float, float],
    rect_min: tuple[float, float],
    rect_max: tuple[float, float],
) -> bool:
    """Checks wether the given `point` is within the rectangle as specified by the top left and bottom right coordinate.

    Args:
        point (tuple[float, float]): point to check.
        rect_min (tuple[float,float]): top left.
        rect_max (tuple[float,float]): bottom right.

    Returns:
        bool: whether the point is in the rectangle.
    """
    px, py = point
    min_x, min_y = rect_min
    max_x, max_y = rect_max
    return min_x <= px <= max_x and min_y <= py <= max_y

is_tracking_acceptable()

Determines whether the tracking task is in an acceptable state.

Acceptable: "target" is within the central box of the task. Unacceptable: otherwise.

Raises:

Type Description
ValueError

if there is missing observational data about the task - this may mean the task is not active.

Returns:

Name Type Description
bool bool

whether the task is in an acceptable state.

Source code in matbii\guidance\sensor_tracking.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def is_tracking_acceptable(self) -> bool:
    """Determines whether the tracking task is in an acceptable state.

    Acceptable: "target" is within the central box of the task.
    Unacceptable: otherwise.

    Raises:
        ValueError: if there is missing observational data about the task - this may mean the task is not active.

    Returns:
        bool: whether the task is in an acceptable state.
    """
    try:
        target = self.beliefs.get(TrackingTaskAcceptabilitySensor._TARGET_ID, None)
    except KeyError:
        raise KeyError(
            f"Failed to determine acceptability of task: '{TASK_ID_TRACKING}'.\n-- Missing beliefs for elements: '{TrackingTaskAcceptabilitySensor._TARGET_ID}'"
        )
    try:
        box = self.beliefs.get(TrackingTaskAcceptabilitySensor._BOX_ID, None)
    except KeyError:
        raise KeyError(
            f"Failed to determine acceptability of task: '{TASK_ID_TRACKING}'.\n-- Missing beliefs for elements: '{TrackingTaskAcceptabilitySensor._BOX_ID}'"
        )
    return TrackingTaskAcceptabilitySensor.is_point_in_rectangle(
        target["xy"], box["tl"], box["br"]
    )

on_observation(observation)

Update beliefs about the tracking task based on the incoming observation.

Parameters:

Name Type Description Default
observation Observation

observation

required
Source code in matbii\guidance\sensor_tracking.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def on_observation(self, observation: Observation):
    """Update beliefs about the tracking task based on the incoming observation.

    Args:
        observation (Observation): observation
    """
    for data in observation.values:
        try:
            if data["id"] == TrackingTaskAcceptabilitySensor._TARGET_ID:
                tl = (data["x"], data["y"])
                size = (data["width"], data["height"])
                xy = (tl[0] + size[0] / 2, tl[1] + size[1] / 2)
                self.beliefs[TrackingTaskAcceptabilitySensor._TARGET_ID]["xy"] = xy
                self.beliefs[TrackingTaskAcceptabilitySensor._TARGET_ID]["size"] = (
                    size
                )
            elif data["id"] == TrackingTaskAcceptabilitySensor._BOX_ID:
                tl = (data["x"], data["y"])
                self.beliefs[TrackingTaskAcceptabilitySensor._BOX_ID]["tl"] = tl
                self.beliefs[TrackingTaskAcceptabilitySensor._BOX_ID]["br"] = (
                    tl[0] + data["width"],
                    tl[1] + data["height"],
                )
        except KeyError:
            raise KeyError(
                f"Observation: {observation} doesn't contain the required `id` attribute."
            )

sense()

Generates the sense actions that are required for checking whether the tracking task is in an acceptable state.

The actions will request the following data: - the bounds of the target element. - the bounds of the central box of the tracking task.

Returns:

Type Description
list[Select]

list[Select]: list of sense actions to take.

Source code in matbii\guidance\sensor_tracking.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def sense(self) -> list[Select]:
    """Generates the sense actions that are required for checking whether the tracking task is in an acceptable state.

    The actions will request the following data:
    - the bounds of the target element.
    - the bounds of the central box of the tracking task.

    Returns:
        list[Select]: list of sense actions to take.
    """
    return [
        select(
            xpath=f"//*[@id='{tracking_target_id()}']",
            attrs=["id", "x", "y", "width", "height"],
        ),
        select(
            xpath=f"//*[@id='{tracking_box_id()}']",
            attrs=["id", "x", "y", "width", "height"],
        ),
    ]