Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions PUBLIC_SOURCE_MANIFEST.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
},
{
"path": "README.md",
"sha256": "a1871e26e9c458960a35aec358baf2d818e3a8a4a12c2ecba247b0966ed207ae",
"size_bytes": 10242
"sha256": "f9646d68ef1c9896ee66ae2752a9f8eedcb1319141d98bb7e9f1864022a4c9be",
"size_bytes": 10368
},
{
"path": "SECURITY.md",
Expand All @@ -100,6 +100,16 @@
"sha256": "4dd2b88529c6238c9a38076e8aa28ffccaa1cd0442ad3c5b06923ee8dd4371c0",
"size_bytes": 153512
},
{
"path": "assets/attentrix/attentrix-readme-lockup-dark.png",
"sha256": "d185f4e9e572cccb1b29fbbbadc54e0d084e4c31bc06df7dac3138525ab06896",
"size_bytes": 46851
},
{
"path": "assets/attentrix/attentrix-readme-lockup-light.png",
"sha256": "5af0ce26e3a02701e4a21d5068edc92f09e8dc4381e06984a562917deb25314b",
"size_bytes": 64898
},
{
"path": "configs/README.md",
"sha256": "573f5a668ad4e419ece3452f1bb7dfcda2bf55463871f71b5328e6daca2a556d",
Expand Down Expand Up @@ -1492,8 +1502,8 @@
},
{
"path": "public_repository.toml",
"sha256": "383ed5c30aa539434491a2f100f90ea096f8b011fabb202e2948e8786029094b",
"size_bytes": 3069
"sha256": "4f7e613b6e320faafcfe122eebb237086a147ae7720d631be092d0d8e1f45e0c",
"size_bytes": 3180
},
{
"path": "pyproject.toml",
Expand Down Expand Up @@ -1822,8 +1832,8 @@
},
{
"path": "scripts/generate_attentrix_brand_assets.py",
"sha256": "3ff01899564c8573943ccbb42cabe703650f0cd2e05a52d1afe95d74eabeb292",
"size_bytes": 1312
"sha256": "a80a0b43613d084c3144889a8cda1451fa0befdea70ca91eb83ae13ada0b52f1",
"size_bytes": 2207
},
{
"path": "scripts/generate_research_slideshow.py",
Expand Down Expand Up @@ -2632,8 +2642,8 @@
},
{
"path": "src/tavc/signalroom/branding.py",
"sha256": "3593d2b2a2b7c6f605e36a9b371aea8c52220326232f9e5b93f3a98bee90ca67",
"size_bytes": 2377
"sha256": "af2eea017bd7b612e135f2d59ed3f7cf25ada535954a69a5e05b7c4c074ee40b",
"size_bytes": 4021
},
{
"path": "src/tavc/signalroom/confusion_specialist.py",
Expand Down Expand Up @@ -2887,8 +2897,8 @@
},
{
"path": "tests/test_attentrix_brand_assets.py",
"sha256": "6cf7251e28c60ad0acd53b371ad0b3a6ae859117d206103565555852974d6fde",
"size_bytes": 4461
"sha256": "6575360070bc5422b5b28c38a03e6049b2ac39d2142c0469789828f5fe569f00",
"size_bytes": 6048
},
{
"path": "tests/test_attentrix_pre_paper_evidence.py",
Expand Down Expand Up @@ -3540,6 +3550,6 @@
"product": "ATTENTRIX",
"product_version": "0.2.0-beta.4",
"schema_version": "attentrix_public_source_v1",
"source_revision": "1ef9dbb7e4ad46d0268b96d315bc6ee9fcb8f060",
"source_revision": "131fdd24bda90882fc45028bcace4489b552b1e7",
"source_worktree_dirty": false
}
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<p align="center">
<img src="assets/attentrix/attentrix-logo.png" width="112" alt="ATTENTRIX logo">
<img src="assets/attentrix/attentrix-readme-lockup-dark.png#gh-dark-mode-only" width="550" alt="ATTENTRIX">
<img src="assets/attentrix/attentrix-readme-lockup-light.png#gh-light-mode-only" width="550" alt="ATTENTRIX">
</p>

# ATTENTRIX

ATTENTRIX is a local research application that turns visible audience cues in a Zoom gallery into glanceable presenter feedback. It tracks each measurable participant separately, estimates four visual states, and summarizes the room as a small set of actions: continue, clarify, or re-engage.

The current public beta is `0.2.0-beta.4`. Its frozen inference checkpoint is Live v20 (`classroom_v6_signalroom_live_v20`) with the participant-relative presenter policy `gallery_person_relative_v1` and compute profile `primary_hand_1440_q82`.
Expand Down
Binary file added assets/attentrix/attentrix-readme-lockup-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions public_repository.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ exclude_patterns = [

allowed_binary_patterns = [
"assets/attentrix/attentrix-logo.png",
"assets/attentrix/attentrix-readme-lockup-dark.png",
"assets/attentrix/attentrix-readme-lockup-light.png",
"docs/assets/attentrix_compression_evidence_20260716.png",
"docs/assets/attentrix_paper_teaser.jpg",
"docs/audio_compression_seed_mean.png",
Expand Down
29 changes: 28 additions & 1 deletion scripts/generate_attentrix_brand_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

from pathlib import Path

from tavc.signalroom.branding import save_attentrix_png
from tavc.signalroom.branding import (
render_attentrix_readme_lockup,
save_attentrix_png,
)


ROOT = Path(__file__).resolve().parents[1]
CANONICAL_LOGO_PATH = ROOT / "assets" / "attentrix" / "attentrix-logo.png"
CANONICAL_WORDMARK_PATH = (
ROOT / "website" / "public" / "images" / "attentrix-wordmark.png"
)
PNG_TARGETS = (
(ROOT / "website" / "public" / "images" / "attentrix-logo.png", 512),
(ROOT / "website" / "public" / "favicon.png", 64),
Expand All @@ -31,16 +37,37 @@
),
(ROOT / "src" / "tavc" / "app" / "static" / "favicon.png", 64),
)
README_LOCKUP_TARGETS = (
(
ROOT / "assets" / "attentrix" / "attentrix-readme-lockup-dark.png",
(255, 255, 255),
),
(
ROOT / "assets" / "attentrix" / "attentrix-readme-lockup-light.png",
(14, 29, 49),
),
)


def main() -> int:
if not CANONICAL_LOGO_PATH.is_file():
raise FileNotFoundError(
f"Canonical ATTENTRIX logo is missing: {CANONICAL_LOGO_PATH}"
)
if not CANONICAL_WORDMARK_PATH.is_file():
raise FileNotFoundError(
f"Canonical ATTENTRIX wordmark is missing: {CANONICAL_WORDMARK_PATH}"
)
for destination, size in PNG_TARGETS:
save_attentrix_png(CANONICAL_LOGO_PATH, destination, size=size)
print(f"Generated {destination.relative_to(ROOT)} ({size}x{size})")
for destination, color in README_LOCKUP_TARGETS:
render_attentrix_readme_lockup(
CANONICAL_LOGO_PATH,
CANONICAL_WORDMARK_PATH,
wordmark_color=color,
).save(destination, format="PNG", optimize=True)
print(f"Generated {destination.relative_to(ROOT)} (1100x220)")
return 0


Expand Down
51 changes: 51 additions & 0 deletions src/tavc/signalroom/branding.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

DEFAULT_PADDING_FRACTION = 1 / 16
RUNTIME_LOGO_PATH = Path(__file__).resolve().parent / "static" / "attentrix-logo.png"
README_LOCKUP_SIZE = (1100, 220)


def render_attentrix_logo(
Expand Down Expand Up @@ -39,6 +40,56 @@ def render_attentrix_logo(
return canvas


def render_attentrix_readme_lockup(
logo_source: str | Path,
wordmark_source: str | Path,
*,
wordmark_color: tuple[int, int, int],
) -> Image.Image:
"""Render the horizontal emblem and custom wordmark used by GitHub."""

if len(wordmark_color) != 3 or any(
not 0 <= channel <= 255 for channel in wordmark_color
):
raise ValueError("Wordmark color must contain three 8-bit channels.")

with Image.open(wordmark_source) as opened:
wordmark = opened.convert("RGBA")
visible_alpha = wordmark.getchannel("A").point(
lambda alpha: 255 if alpha >= 16 else 0
)
bounds = visible_alpha.getbbox()
if bounds is None:
raise ValueError("The ATTENTRIX wordmark contains no visible pixels.")

margin = 4
left, top, right, bottom = bounds
crop_bounds = (
max(0, left - margin),
max(0, top - margin),
min(wordmark.width, right + margin),
min(wordmark.height, bottom + margin),
)
alpha = wordmark.getchannel("A").crop(crop_bounds)
recolored = Image.new("RGBA", alpha.size, (*wordmark_color, 0))
recolored.putalpha(alpha)
recolored.thumbnail((840, 98), Image.Resampling.LANCZOS)

canvas = Image.new("RGBA", README_LOCKUP_SIZE, (0, 0, 0, 0))
emblem = render_attentrix_logo(
logo_source,
size=180,
padding_fraction=1 / 24,
)
canvas.alpha_composite(emblem, (20, 20))
wordmark_offset = (
230,
(README_LOCKUP_SIZE[1] - recolored.height) // 2,
)
canvas.alpha_composite(recolored, wordmark_offset)
return canvas


def save_attentrix_png(
source: str | Path,
destination: str | Path,
Expand Down
43 changes: 43 additions & 0 deletions tests/test_attentrix_brand_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
from PIL import Image

from tavc.signalroom.branding import (
render_attentrix_readme_lockup,
render_attentrix_logo,
save_attentrix_ico,
)


ROOT = Path(__file__).resolve().parents[1]
CANONICAL_LOGO = ROOT / "assets" / "attentrix" / "attentrix-logo.png"
CANONICAL_WORDMARK = (
ROOT / "website" / "public" / "images" / "attentrix-wordmark.png"
)


class AttentrixBrandAssetTests(unittest.TestCase):
Expand Down Expand Up @@ -70,6 +74,45 @@ def test_windows_icon_contains_standard_sizes(self):
sizes = icon.info.get("sizes", set())
self.assertTrue({(16, 16), (32, 32), (48, 48), (256, 256)}.issubset(sizes))

def test_readme_lockup_keeps_the_logo_and_recolors_the_exact_wordmark(self):
white_lockup = render_attentrix_readme_lockup(
CANONICAL_LOGO,
CANONICAL_WORDMARK,
wordmark_color=(255, 255, 255),
)
navy_lockup = render_attentrix_readme_lockup(
CANONICAL_LOGO,
CANONICAL_WORDMARK,
wordmark_color=(14, 29, 49),
)

self.assertEqual(white_lockup.mode, "RGBA")
self.assertEqual(white_lockup.size, (1100, 220))
self.assertIsNotNone(white_lockup.getbbox())
self.assertGreater(
sum(
1
for red, green, blue, alpha in white_lockup.get_flattened_data()
if alpha > 200 and (red, green, blue) == (255, 255, 255)
),
1_000,
)
self.assertGreater(
sum(
1
for red, green, blue, alpha in navy_lockup.get_flattened_data()
if alpha > 200 and (red, green, blue) == (14, 29, 49)
),
1_000,
)

def test_readme_uses_theme_aware_horizontal_brand_lockups(self):
readme = (ROOT / "README.md").read_text(encoding="utf-8")

self.assertIn("attentrix-readme-lockup-dark.png#gh-dark-mode-only", readme)
self.assertIn("attentrix-readme-lockup-light.png#gh-light-mode-only", readme)
self.assertNotIn("\n# ATTENTRIX\n", readme)

def test_product_surfaces_reference_the_canonical_generated_assets(self):
website_layout = (ROOT / "website" / "app" / "layout.tsx").read_text(
encoding="utf-8"
Expand Down
Loading