Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
b7ac2b8
added schema
sauravbanna Mar 28, 2026
e42bf21
refactored possession tracker
sauravbanna Mar 28, 2026
debcd45
fixed BUILD
sauravbanna Mar 28, 2026
cf1839d
Merge branch 'sauravbanna/verbose_log_schema' of github.com:UBC-Thund…
sauravbanna Mar 30, 2026
ee18c64
added from team
sauravbanna Mar 30, 2026
ecdee53
added from team
sauravbanna Mar 30, 2026
9eb22be
format
sauravbanna Mar 30, 2026
9e40188
Merge branch 'sauravbanna/verbose_log_schema' of github.com:UBC-Thund…
sauravbanna Mar 30, 2026
201c1b7
refactored all trackers to new api
sauravbanna Mar 30, 2026
67b49bc
updated schema
sauravbanna Mar 30, 2026
855ee5b
Merge branch 'sauravbanna/verbose_log_schema' of github.com:UBC-Thund…
sauravbanna Mar 30, 2026
44aca4e
made list of robots have constant len in csv
sauravbanna Mar 30, 2026
e0aa9d0
minor type hint fix
sauravbanna Mar 30, 2026
be01270
add robot states based on id instead of order
sauravbanna Mar 30, 2026
b0bde2e
use protobufs in tracked event
Thunderbots Mar 30, 2026
fb949b9
Merge branch 'sauravbanna/verbose_log_schema' of https://github.com/U…
Thunderbots Mar 30, 2026
681ccaa
docs:
Thunderbots Mar 30, 2026
c252877
docs
Thunderbots Mar 30, 2026
11ad662
Merge branch 'sauravbanna/verbose_log_schema' of https://github.com/U…
Thunderbots Mar 30, 2026
cdd7724
fixed bugs, stats csv works now
Thunderbots Mar 30, 2026
d682a99
fixed bugs
Thunderbots Mar 30, 2026
05e03ec
Merge branch 'sauravbanna/verbose_log_schema' of https://github.com/U…
Thunderbots Mar 30, 2026
5387f96
docs update
Thunderbots Mar 30, 2026
1fee2ef
fix build
Thunderbots Mar 31, 2026
1937952
Merge branch 'sauravbanna/verbose_log_schema' into sauravbanna/refact…
Thunderbots Mar 31, 2026
8621606
Merge branch 'master' of https://github.com/UBC-Thunderbots/Software …
Thunderbots Mar 31, 2026
bdc5e18
Merge branch 'master' of github.com:UBC-Thunderbots/Software into sau…
nycrat Mar 31, 2026
5f9dd00
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Mar 31, 2026
5069913
Merge branch 'master' of github.com:UBC-Thunderbots/Software into sau…
sauravbanna Mar 31, 2026
146f94e
Merge branch 'sauravbanna/verbose_log_schema' of github.com:UBC-Thund…
sauravbanna Mar 31, 2026
2b8ac00
Merge branch 'sauravbanna/verbose_log_schema' of github.com:UBC-Thund…
sauravbanna Mar 31, 2026
2a35535
format
sauravbanna Mar 31, 2026
061dc16
testing
sauravbanna Apr 2, 2026
6ce1c07
lot of refactoring
sauravbanna Apr 4, 2026
96c12f1
Merge branch 'master' of github.com:UBC-Thunderbots/Software into sau…
sauravbanna Apr 4, 2026
be09413
fix
sauravbanna Apr 4, 2026
d41c399
add files
sauravbanna Apr 4, 2026
06d6fed
fuixed bugs
Thunderbots Apr 5, 2026
daaefd3
BUILD files + format
sauravbanna Apr 5, 2026
751f534
format
sauravbanna Apr 5, 2026
f47e37f
test
Thunderbots Apr 5, 2026
4b1f396
fix
sauravbanna Apr 5, 2026
5dd256c
Merge branch 'sauravbanna/ml_testing' of https://github.com/UBC-Thund…
Thunderbots Apr 5, 2026
e580201
added BUILD + requirements to bazel
Thunderbots Apr 5, 2026
4a204b8
format
sauravbanna Apr 5, 2026
844fc95
update req lock
Thunderbots Apr 7, 2026
4d73bc9
fixed bugs w model, changed labelling to predict all interval probabi…
Thunderbots Apr 7, 2026
2779b31
fix pass log tracker
Thunderbots Apr 25, 2026
b3c342c
removed log statement
Thunderbots Apr 25, 2026
8c47855
fix bugs with timestamp reading + ordering
Thunderbots Apr 25, 2026
f0bbb3d
add sklearn + replace bit accuracy with f1 score + undersample passes
Thunderbots Apr 25, 2026
9efef7a
Update labelled_passes.py to use difference between intervals instead…
sauravbanna Apr 28, 2026
dc66924
Merge branch 'sauravbanna/ml_testing' of github.com:UBC-Thunderbots/S…
sauravbanna Apr 28, 2026
649c6b7
fixed syntax + added label weights function
sauravbanna Apr 28, 2026
a1830d7
format + requirements
Thunderbots Apr 28, 2026
207ce4e
fix python req in BUILD
Thunderbots Apr 29, 2026
89b1763
added scaled weights for priority events
Thunderbots Apr 29, 2026
551a527
format
Thunderbots Apr 29, 2026
f873bc4
seperate labelling and training
Thunderbots Apr 29, 2026
024b47f
refactored train passing to only train from pre-labelled passes
sauravbanna Apr 29, 2026
70ff89f
refactored train passing to only train from pre-labelled passes
sauravbanna Apr 29, 2026
995dd88
added options to specify pass results and game events file names
sauravbanna Apr 29, 2026
c9ee7a7
update pass logger to use custom file
sauravbanna Apr 29, 2026
03d7d0a
format
sauravbanna Apr 29, 2026
52db2d2
update yellow stats file name
sauravbanna Apr 29, 2026
dfcf853
fixed BUILD + bugs in generate labelled passes
Thunderbots May 1, 2026
3eae010
format
Thunderbots May 1, 2026
0f986a2
added reqs + updated training to use more hidden dims etc
Thunderbots May 2, 2026
06efc7a
add scaling of weights
sauravbanna May 2, 2026
994337b
replace w constants
sauravbanna May 2, 2026
ac74634
use scaling
sauravbanna May 2, 2026
f939b30
use interval scaled weights instead
sauravbanna May 2, 2026
959ca49
reduce interval scales
sauravbanna May 2, 2026
f6eb940
back to normal weights
sauravbanna May 2, 2026
71d662e
event scaled weights
sauravbanna May 9, 2026
2140263
Merge branch 'sauravbanna/ml_testing' into sauravbanna/stats_refactor
sauravbanna May 13, 2026
d80dacc
remove ml related changes
sauravbanna May 13, 2026
1d273b6
format
sauravbanna May 13, 2026
633e7ed
remove ml deps
sauravbanna May 13, 2026
b85cd71
added docs
sauravbanna May 13, 2026
a71c7a0
format
sauravbanna May 13, 2026
d8668b3
fix build
sauravbanna May 14, 2026
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
9 changes: 9 additions & 0 deletions src/software/evaluation/loggers/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package(default_visibility = ["//visibility:public"])

py_library(
name = "stats_logger",
srcs = ["stats_logger.py"],
deps = [
"//software/evaluation/trackers:tracker",
],
)
170 changes: 170 additions & 0 deletions src/software/evaluation/loggers/stats_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import os

from software.evaluation.trackers import (
PossessionTracker,
ShotTracker,
PassTracker,
TrackerBuilder,
RefereeTracker,
GoalieTracker,
)
from dataclasses import dataclass
from software.thunderscope.proto_unix_io import ProtoUnixIO
from software.thunderscope.constants import RuntimeManagerConstants
from software.evaluation.logs.event_log import Team as TeamEnum, EventLog
import logging
from proto.import_all_protos import *
import queue


@dataclass
class FSStats:
"""Stats for how well a FullSystem is performing"""

num_yellow_cards: int = 0
num_red_cards: int = 0
num_scores: int = 0

num_shots_on_net: int = 0
num_enemy_shots_blocked: int = 0


class StatsLogger:
# From GoalieTacticConfig
INCOMING_SHOT_MIN_VELOCITY = 0.2

EVENT_BUFFER_SIZE = 100

def __init__(
self,
proto_unix_io: ProtoUnixIO,
friendly_colour_yellow: bool,
out_file_name: str | None = None,
buffer_size: int = 5,
record_enemy_stats: bool = False,
):
"""Initializes the FullSystem Stats Tracker

:param friendly_colour_yellow: if the friendly colour is yellow
:param out_file_name: name of file to write stats to.
If None, uses the value from constants
:param buffer_size: the buffer size for protocol buffers
:param record_enemy_stats: if this should record both friendly and enemy stats or just friendly
"""
self.friendly_colour_yellow = friendly_colour_yellow

self.events_file_path = os.path.join(
RuntimeManagerConstants.RUNTIME_EVENTS_DIRECTORY_PATH,
RuntimeManagerConstants.RUNTIME_EVENTS_FILE
if out_file_name is None
else out_file_name,
)
# initialized in setup()
self.events_file_handle = None

self.event_queue = queue.Queue(self.EVENT_BUFFER_SIZE)

# flag to turn off logging stats if needed
self.logging_enabled = True

self.tracker = (
TrackerBuilder(
proto_unix_io=proto_unix_io,
from_team=(
TeamEnum.YELLOW if self.friendly_colour_yellow else TeamEnum.BLUE
),
event_queue=self.event_queue,
buffer_size=buffer_size,
)
.add_tracker(PassTracker)
.add_tracker(ShotTracker)
.add_tracker(PossessionTracker)
.add_tracker(
RefereeTracker,
friendly_color_yellow=self.friendly_colour_yellow,
toggle_logging=self._toggle_logging,
)
.add_tracker(GoalieTracker, for_friendly=True)
)

self.record_enemy_stats = record_enemy_stats
if self.record_enemy_stats:
self.enemy_tracker = (
TrackerBuilder(
proto_unix_io=proto_unix_io,
from_team=(
TeamEnum.YELLOW
if self.friendly_colour_yellow
else TeamEnum.BLUE
),
for_team=(
TeamEnum.BLUE
if self.friendly_colour_yellow
else TeamEnum.YELLOW
),
event_queue=self.event_queue,
buffer_size=buffer_size,
)
.add_tracker(
RefereeTracker,
friendly_color_yellow=(not self.friendly_colour_yellow),
toggle_logging=self._toggle_logging,
)
.add_tracker(GoalieTracker, for_friendly=False)
)

def refresh(self) -> None:
"""Refreshes the events for the game so far"""
self.tracker.refresh()

if not self.events_file_handle:
return

while not self.event_queue.empty():
try:
# Get item without blocking
event = self.event_queue.get_nowait()

self._write_event_to_file(event)
except queue.Empty:
return

def __enter__(self):
"""Sets up the file resources for logging
Creates any missing directories and stores the file handle
"""
# create temp stats directory if it doesn't exist
os.makedirs(os.path.dirname(self.events_file_path), exist_ok=True)

self.events_file_handle = open(self.events_file_path, "a")

return self

def __exit__(self, exc_type, exc_value, traceback):
"""Writes all logs back to file, and cleans up any created file resources after logging"""
if self.events_file_handle:
self.events_file_handle.flush()
self.events_file_handle.close()

def _toggle_logging(self, should_log: bool) -> None:
"""Turns logging off or on based on the given boolean

;param should_log: True if logging should continue, False if not
"""
self.logging_enabled = should_log

def _write_event_to_file(self, event: EventLog) -> None:
"""Write the given stats to the given file

:param event: the event to write
"""
if not self.events_file_handle:
return

try:
csv_row = event.to_csv_row()
self.events_file_handle.write(csv_row + "\n")
self.events_file_handle.flush()

except (IOError, FileNotFoundError, PermissionError):
logging.warning("Failed to write event to file")
39 changes: 39 additions & 0 deletions src/software/evaluation/logs/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])

py_library(
name = "log_interface",
srcs = ["log_interface.py"],
deps = [
"//proto:import_all_protos",
"//software/thunderscope:time_provider",
],
)

py_library(
name = "world_state_log",
srcs = ["world_state_log.py"],
data = [
"//software:py_constants.so",
],
deps = [
":log_interface",
],
)

py_library(
name = "event_log",
srcs = ["event_log.py"],
deps = [
":log_interface",
":world_state_log",
],
)

py_library(
name = "logs",
deps = [
":event_log",
":log_interface",
":world_state_log",
],
)
107 changes: 107 additions & 0 deletions src/software/evaluation/logs/event_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum, auto
from proto.import_all_protos import *
from typing import Any, override
from software.evaluation.logs.log_interface import TimestampedEvalLog
from software.evaluation.logs.world_state_log import WorldStateLog


class EventType(StrEnum):
"""Enum for the different types of events we want to track"""

PASS = auto()
SHOT_ON_GOAL = auto()
ENEMY_SHOT_ON_GOAL = auto()
SHOT_BLOCKED = auto()
FRIENDLY_POSSESSION_START = auto()
FRIENDLY_POSSESSION_END = auto()
ENEMY_POSSESSION_START = auto()
ENEMY_POSSESSION_END = auto()
GAME_START = auto()
GAME_END = auto()
GOAL_SCORED = auto()
YELLOW_CARD = auto()
RED_CARD = auto()


class Team(StrEnum):
"""The teams present in the game"""

BLUE = auto()
YELLOW = auto()


@dataclass(kw_only=True)
class EventLog(TimestampedEvalLog):
"""Represents a single event being tracked, where and for whom the event is,
and the game state at the time of the event
"""

event_type: EventType
from_team: Team
for_team: Team
world_state_log: WorldStateLog

num_cols = TimestampedEvalLog.get_num_cols() + 3 + WorldStateLog.get_num_cols()

@staticmethod
def from_world(
world_msg: World, event_type: EventType, from_team: Team, for_team: Team
) -> EventLog:
"""Creates an EventLog from a world protobuf message

:param world_msg: the world object containing the state of the game
:param event_type: the type of event being recorded
:param from_team: the team that the event is coming from
:param for_team: the team that the event is for
:return: a fully populated EventLog including world state
"""
world_state_log = WorldStateLog.from_world(world_msg=world_msg)

return EventLog(
event_type=event_type,
from_team=from_team,
for_team=for_team,
world_state_log=world_state_log,
)

@override
@classmethod
def get_num_cols(cls) -> int:
return EventLog.num_cols

@override
def to_array(self) -> list[Any]:
return (
super().to_array()
+ [
self.event_type.value,
self.from_team.value,
self.for_team.value,
]
+ self.world_state_log.to_array()
)

@staticmethod
@override
def from_csv_row(row_iter: Iterator[str]) -> EventLog | None:
"""Parses a full CSV row into an EventLog."""
timestamp = float(next(row_iter))

event_type = EventType(next(row_iter))
from_team = Team(next(row_iter))
for_team = Team(next(row_iter))

world_state = WorldStateLog.from_csv_row(row_iter)

if not world_state:
return None

return EventLog(
timestamp=timestamp,
event_type=event_type,
from_team=from_team,
for_team=for_team,
world_state_log=world_state,
)
Loading
Loading