diff --git a/sleap/gui/learning/features.py b/sleap/gui/learning/features.py new file mode 100644 index 0000000000..053dc6a4d1 --- /dev/null +++ b/sleap/gui/learning/features.py @@ -0,0 +1,33 @@ +"""Temporary feature flags for in-progress functionality. + +NOTE: This module is intentionally short-lived. The centroid-only models flag +gates UI for a feature whose *inference* half is still blocked on sleap-nn +(epic talmolab/sleap-nn#508 / PR #562). Once centroid-only inference lands and +the feature is fully supported, delete this module and its call sites +(grep for is_centroid_models_enabled). + +In the GUI, this feature is enabled via the "Experimental Features" toggle in +the Help menu. The SLEAP_ENABLE_CENTROID_MODELS environment variable remains +available as a developer override. +""" + +import os + + +def is_centroid_models_enabled(experimental_features: bool = False) -> bool: + """Return True if the experimental centroid-only models UI is enabled. + + Enabled when EITHER: + - experimental_features is True (the "Experimental Features" toggle in + the Help menu, threaded in by the caller), or + - the SLEAP_ENABLE_CENTROID_MODELS environment variable is truthy + ("1"/"true"/"yes"/"on", case-insensitive). + """ + if experimental_features: + return True + return os.environ.get("SLEAP_ENABLE_CENTROID_MODELS", "").strip().lower() in ( + "1", + "true", + "yes", + "on", + ) diff --git a/sleap/skeletons/centroid.json b/sleap/skeletons/centroid.json new file mode 100644 index 0000000000..907bd9d82c --- /dev/null +++ b/sleap/skeletons/centroid.json @@ -0,0 +1,28 @@ +{ + "description": "Single-node centroid skeleton for centroid-only models.", + "nx_graph": { + "directed": true, + "graph": { + "name": "centroid", + "num_edges_inserted": 0 + }, + "links": [], + "multigraph": true, + "nodes": [ + { + "id": { + "py/object": "sleap.skeleton.Node", + "py/state": { + "py/tuple": [ + "centroid", + 1.0 + ] + } + } + } + ] + }, + "preview_image": { + "py/b64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAACWCAYAAACb3McZAAABQElEQVR4nO3cwQ3CMBBFwUAZ1EMplEUp1EMbUAGLlOSvg5m5IhQJ/Lw+WFkWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADiTvlH8M3lcX99+ux5vfmPBvLjHzCKT8TS7zzgmX9vTRxbvsd6JkijPRe4adLDBGmy9+5vmvQQSIPUYhZJnkCgIJCw9C5vimQJZILFK5IcgUBBIFAQyCTHHsesDIFAQSBQEAgUBAIFgUBBIFAQSEj3dXTX3zMEAgWBQEEgExx7HK9yBPLji1ccWQKBgkAapHZ50yNPIE32Xszi6OG1PwNsuZoujF4myABrF7k4+pkgB+DdvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwTO8NFK1ENqv3Iq8AAAAASUVORK5CYII=" + } +} \ No newline at end of file diff --git a/tests/gui/learning/test_features.py b/tests/gui/learning/test_features.py new file mode 100644 index 0000000000..cbf8d26a31 --- /dev/null +++ b/tests/gui/learning/test_features.py @@ -0,0 +1,43 @@ +"""Tests for the temporary centroid-only models feature flag.""" + +import pytest + +from sleap.gui.learning.features import is_centroid_models_enabled + + +def test_disabled_by_default(monkeypatch): + """The flag is off when the env var is unset.""" + monkeypatch.delenv("SLEAP_ENABLE_CENTROID_MODELS", raising=False) + assert is_centroid_models_enabled() is False + assert is_centroid_models_enabled(False) is False + + +@pytest.mark.parametrize("value", ["1", "true", "yes", "on"]) +@pytest.mark.parametrize("transform", [str.lower, str.upper, str.title]) +def test_enabled_for_truthy_values(monkeypatch, value, transform): + """Truthy values enable the flag regardless of case.""" + monkeypatch.setenv("SLEAP_ENABLE_CENTROID_MODELS", transform(value)) + assert is_centroid_models_enabled() is True + assert is_centroid_models_enabled(False) is True + + +@pytest.mark.parametrize("value", ["0", "false", ""]) +def test_disabled_for_falsy_values(monkeypatch, value): + """Falsy values keep the flag off.""" + monkeypatch.setenv("SLEAP_ENABLE_CENTROID_MODELS", value) + assert is_centroid_models_enabled() is False + assert is_centroid_models_enabled(False) is False + + +def test_enabled_by_experimental_features_without_env(monkeypatch): + """The Experimental Features toggle enables the flag without the env var.""" + monkeypatch.delenv("SLEAP_ENABLE_CENTROID_MODELS", raising=False) + assert is_centroid_models_enabled(experimental_features=True) is True + assert is_centroid_models_enabled(True) is True + + +@pytest.mark.parametrize("value", ["0", "false", ""]) +def test_experimental_features_overrides_falsy_env(monkeypatch, value): + """The Experimental Features toggle enables the flag even with a falsy env.""" + monkeypatch.setenv("SLEAP_ENABLE_CENTROID_MODELS", value) + assert is_centroid_models_enabled(experimental_features=True) is True diff --git a/tests/gui/test_skeleton_templates.py b/tests/gui/test_skeleton_templates.py new file mode 100644 index 0000000000..08514a0956 --- /dev/null +++ b/tests/gui/test_skeleton_templates.py @@ -0,0 +1,29 @@ +"""Tests for shipped skeleton template files.""" + +import base64 +import io +import json + +from PIL import Image + +import sleap.util +from sleap.gui.commands import OpenSkeleton + + +def test_centroid_template_loads_single_node(): + """The shipped centroid.json loads to a single-node, edge-less skeleton.""" + path = sleap.util.get_package_file("skeletons/centroid.json") + skeleton = OpenSkeleton.load_skeleton(path) + assert skeleton.node_names == ["centroid"] + assert len(skeleton.edges) == 0 + + +def test_centroid_template_preview_is_rgba(): + """The centroid.json preview image decodes to an RGBA PIL image.""" + path = sleap.util.get_package_file("skeletons/centroid.json") + with open(path, "r") as f: + skeleton_data = json.load(f) + + b64 = skeleton_data["preview_image"]["py/b64"] + image = Image.open(io.BytesIO(base64.b64decode(b64))) + assert image.mode == "RGBA"