Skip to content

state

sleap.gui.state

Module with object for storing and accessing gui state variables.

Each project open in the GUI will have its own instance of GuiState, as will any video player (QtVideoPlayer widget) which shows different images than in the main app GUI (e.g., QtImageDirectoryWidget used for visualizing results during training).

The state object makes it easier to separate code which updates state (e.g., sets current frame or current video) and code which updates the GUI in response to state-change.

The state object is effectively a dictionary which allows you to bind functions to keys so that the functions each get called when the value for that key changes (or is initially set).

Note that there's no type checking, e.g., to ensure that state["video"] is being set to a Video object. This is a potential source of bugs since callbacks connected to some key will often assume that value will always be of some specific type.

Classes:

Name Description
GuiState

Class for passing persistent gui state variables.

GuiState

Bases: object

Class for passing persistent gui state variables.

Arbitrary variables can be set, bools can be toggled, and callbacks can be automatically triggered on variable changes.

This allows us to separate controls (which set state variables) and views (which can update themselves when the relevant state variables change).

Methods:

Name Description
__contains__

Does state contain key?

__delitem__

Removes key from state. Doesn't trigger callbacks.

__getitem__

Gets value for key, or None if no value.

__setitem__

Sets value for key, triggering any callbacks bound to key.

connect

Connects one or more callbacks for state variable.

emit

Trigger callbacks for state variable.

get

Getter with support for default value.

increment

Increment numeric value for specified key.

increment_in_list

Advance to subsequent (or prior) value in list.

set

Functional version of setter (for use in lambdas).

toggle

Toggle boolean value for specified key.

Source code in sleap/gui/state.py
 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
class GuiState(object):
    """
    Class for passing persistent gui state variables.

    Arbitrary variables can be set, bools can be toggled, and callbacks can be
    automatically triggered on variable changes.

    This allows us to separate controls (which set state variables) and views
    (which can update themselves when the relevant state variables change).
    """

    def __init__(self):
        self._state_vars = dict()
        self._callbacks = dict()

    def __repr__(self) -> str:
        message = "GuiState("
        for key in self._state_vars:
            message += f"'{key}'={self.get(key)}, "
        return f"{message[:-2]})"

    def __getitem__(self, key: GSVarType) -> Any:
        """Gets value for key, or None if no value."""
        return self.get(key, default=None)

    def __setitem__(self, key: GSVarType, value):
        """Sets value for key, triggering any callbacks bound to key."""
        old_val = self.get(key, default=object())
        self._state_vars[key] = value
        if old_val != value:
            self.emit(key)

    def __contains__(self, key) -> bool:
        """Does state contain key?"""
        return key in self._state_vars

    def __delitem__(self, key: GSVarType):
        """Removes key from state. Doesn't trigger callbacks."""
        if key in self:
            del self._state_vars[key]

    def get(self, key: GSVarType, default=NO_ARG) -> Any:
        """Getter with support for default value."""
        if default is not NO_ARG:
            return self._state_vars.get(key, default)
        return self._state_vars.get(key)

    def set(self, key: GSVarType, value: Any):
        """Functional version of setter (for use in lambdas)."""
        self[key] = value

    def toggle(self, key: GSVarType, default: bool = False):
        """Toggle boolean value for specified key."""
        self[key] = not self.get(key, default=default)

    def increment(
        self, key: GSVarType, step: int = 1, mod: Optional[int] = None, default: int = 0
    ):
        """Increment numeric value for specified key.

        Args:
            key: The key.
            step: What to add to current value.
            mod: Wrap value (i.e., apply modulus) if not None.
            default: Set value to this if there's no current value for key.

        Returns:
            None.
        """
        if key not in self._state_vars:
            self[key] = default
            return

        new_value = self.get(key) + step

        # Wrap the value if it's out of bounds.
        if mod is not None:
            new_value %= mod

        self[key] = new_value

    def increment_in_list(
        self, key: GSVarType, value_list: list, reverse: bool = False
    ):
        """Advance to subsequent (or prior) value in list.

        When current value for key is not found in list, the value is set to
        the first (or last, if reverse) item in list.

        Args:
            key: The key.
            value_list: List of values of any type which supports equality check.
            reverse: Whether to use next or previous item in value list.

        Returns:
            None.
        """
        if self[key] not in value_list:
            if reverse:
                self[key] = value_list[-1]
            else:
                self[key] = value_list[0]
        else:
            idx = value_list.index(self[key])
            step = 1 if not reverse else -1
            self[key] = value_list[(idx + step) % len(value_list)]

    def connect(self, key: GSVarType, callbacks: Union[Callable, List[Callable]]):
        """
        Connects one or more callbacks for state variable.

        Callbacks are called (triggered) whenever the state is changed, i.e.,
        when the value for some key is set either (i) initially or (ii) to
        a different value than the current value.

        This is analogous to connecting a function to a Qt slot.

        Callback should take a single arg, which will be the current (new)
        value of whatever state var is triggering the callback.
        """
        if callable(callbacks):
            self._connect_callback(key, callbacks)
        else:
            for callback in callbacks:
                self._connect_callback(key, callback)

    def _connect_callback(self, key: GSVarType, callback: Callable):
        """Connect a callback for state variable."""
        if not callable(callback):
            raise ValueError("callback must be callable")
        if key not in self._callbacks:
            self._callbacks[key] = []
        self._callbacks[key].append(callback)

    def emit(self, key: GSVarType):
        """
        Trigger callbacks for state variable.

        This calls each callback for the specified key, without needing to
        change the value of the key.

        This is analogous to emitting a Qt signal.
        """
        if key in self._state_vars and key in self._callbacks:
            val = self.get(key)
            for i, callback in enumerate(self._callbacks[key]):
                try:
                    # if callback doesn't take positional args, just call it
                    if not inspect.signature(callback).parameters:
                        callback()
                    # otherwise, pass value as first positional arg
                    else:
                        callback(val)
                except Exception as e:
                    print(f"Error occurred during callback {i} for {key}!")
                    print(self._callbacks[key])
                    print(e)

__contains__(key)

Does state contain key?

Source code in sleap/gui/state.py
63
64
65
def __contains__(self, key) -> bool:
    """Does state contain key?"""
    return key in self._state_vars

__delitem__(key)

Removes key from state. Doesn't trigger callbacks.

Source code in sleap/gui/state.py
67
68
69
70
def __delitem__(self, key: GSVarType):
    """Removes key from state. Doesn't trigger callbacks."""
    if key in self:
        del self._state_vars[key]

__getitem__(key)

Gets value for key, or None if no value.

Source code in sleap/gui/state.py
52
53
54
def __getitem__(self, key: GSVarType) -> Any:
    """Gets value for key, or None if no value."""
    return self.get(key, default=None)

__setitem__(key, value)

Sets value for key, triggering any callbacks bound to key.

Source code in sleap/gui/state.py
56
57
58
59
60
61
def __setitem__(self, key: GSVarType, value):
    """Sets value for key, triggering any callbacks bound to key."""
    old_val = self.get(key, default=object())
    self._state_vars[key] = value
    if old_val != value:
        self.emit(key)

connect(key, callbacks)

Connects one or more callbacks for state variable.

Callbacks are called (triggered) whenever the state is changed, i.e., when the value for some key is set either (i) initially or (ii) to a different value than the current value.

This is analogous to connecting a function to a Qt slot.

Callback should take a single arg, which will be the current (new) value of whatever state var is triggering the callback.

Source code in sleap/gui/state.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def connect(self, key: GSVarType, callbacks: Union[Callable, List[Callable]]):
    """
    Connects one or more callbacks for state variable.

    Callbacks are called (triggered) whenever the state is changed, i.e.,
    when the value for some key is set either (i) initially or (ii) to
    a different value than the current value.

    This is analogous to connecting a function to a Qt slot.

    Callback should take a single arg, which will be the current (new)
    value of whatever state var is triggering the callback.
    """
    if callable(callbacks):
        self._connect_callback(key, callbacks)
    else:
        for callback in callbacks:
            self._connect_callback(key, callback)

emit(key)

Trigger callbacks for state variable.

This calls each callback for the specified key, without needing to change the value of the key.

This is analogous to emitting a Qt signal.

Source code in sleap/gui/state.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def emit(self, key: GSVarType):
    """
    Trigger callbacks for state variable.

    This calls each callback for the specified key, without needing to
    change the value of the key.

    This is analogous to emitting a Qt signal.
    """
    if key in self._state_vars and key in self._callbacks:
        val = self.get(key)
        for i, callback in enumerate(self._callbacks[key]):
            try:
                # if callback doesn't take positional args, just call it
                if not inspect.signature(callback).parameters:
                    callback()
                # otherwise, pass value as first positional arg
                else:
                    callback(val)
            except Exception as e:
                print(f"Error occurred during callback {i} for {key}!")
                print(self._callbacks[key])
                print(e)

get(key, default=NO_ARG)

Getter with support for default value.

Source code in sleap/gui/state.py
72
73
74
75
76
def get(self, key: GSVarType, default=NO_ARG) -> Any:
    """Getter with support for default value."""
    if default is not NO_ARG:
        return self._state_vars.get(key, default)
    return self._state_vars.get(key)

increment(key, step=1, mod=None, default=0)

Increment numeric value for specified key.

Parameters:

Name Type Description Default
key GSVarType

The key.

required
step int

What to add to current value.

1
mod Optional[int]

Wrap value (i.e., apply modulus) if not None.

None
default int

Set value to this if there's no current value for key.

0

Returns:

Type Description

None.

Source code in sleap/gui/state.py
 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
def increment(
    self, key: GSVarType, step: int = 1, mod: Optional[int] = None, default: int = 0
):
    """Increment numeric value for specified key.

    Args:
        key: The key.
        step: What to add to current value.
        mod: Wrap value (i.e., apply modulus) if not None.
        default: Set value to this if there's no current value for key.

    Returns:
        None.
    """
    if key not in self._state_vars:
        self[key] = default
        return

    new_value = self.get(key) + step

    # Wrap the value if it's out of bounds.
    if mod is not None:
        new_value %= mod

    self[key] = new_value

increment_in_list(key, value_list, reverse=False)

Advance to subsequent (or prior) value in list.

When current value for key is not found in list, the value is set to the first (or last, if reverse) item in list.

Parameters:

Name Type Description Default
key GSVarType

The key.

required
value_list list

List of values of any type which supports equality check.

required
reverse bool

Whether to use next or previous item in value list.

False

Returns:

Type Description

None.

Source code in sleap/gui/state.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def increment_in_list(
    self, key: GSVarType, value_list: list, reverse: bool = False
):
    """Advance to subsequent (or prior) value in list.

    When current value for key is not found in list, the value is set to
    the first (or last, if reverse) item in list.

    Args:
        key: The key.
        value_list: List of values of any type which supports equality check.
        reverse: Whether to use next or previous item in value list.

    Returns:
        None.
    """
    if self[key] not in value_list:
        if reverse:
            self[key] = value_list[-1]
        else:
            self[key] = value_list[0]
    else:
        idx = value_list.index(self[key])
        step = 1 if not reverse else -1
        self[key] = value_list[(idx + step) % len(value_list)]

set(key, value)

Functional version of setter (for use in lambdas).

Source code in sleap/gui/state.py
78
79
80
def set(self, key: GSVarType, value: Any):
    """Functional version of setter (for use in lambdas)."""
    self[key] = value

toggle(key, default=False)

Toggle boolean value for specified key.

Source code in sleap/gui/state.py
82
83
84
def toggle(self, key: GSVarType, default: bool = False):
    """Toggle boolean value for specified key."""
    self[key] = not self.get(key, default=default)