Skip to content

Commit c3a9b16

Browse files
authored
Merge pull request #116 from rogermt/v0.10.1
chore(v0.10.1): Add project table validation and dependency updates
2 parents a403af1 + 9049c97 commit c3a9b16

13 files changed

Lines changed: 862 additions & 108 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,8 @@ logs/
5050

5151
# Scratch
5252
scratch/
53+
54+
#Yolo-Trcker Models
55+
train_ball_detector.pt
56+
train_pitch_keypoint_detector.pt
57+
train_player_detector.pt

plugins/forgesyte-yolo-tracker/src/forgesyte_yolo_tracker/configs/models.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
# Model file names (relative to the models/ directory)
55
models:
6-
player_detection: "football-player-detection-v3.pt"
7-
ball_detection: "football-ball-detection-v2.pt"
8-
pitch_detection: "football-pitch-detection-v1.pt"
6+
player_detection: "train_player_detector.pt"
7+
ball_detection: "train_ball_detector.pt"
8+
pitch_detection: "train_pitch_keypoint_detector.pt"
99

1010
# Confidence thresholds for detection
1111
confidence:
@@ -14,11 +14,11 @@ confidence:
1414
pitch: 0.25
1515

1616
# Default device for inference
17-
device: "cuda"
17+
device: "cpu"
1818

1919
# Default detections to run in analyze()
2020
# Enable/disable each detection type. Set to true/false to control which run.
2121
default_detections:
2222
players: true
2323
ball: true
24-
pitch: true
24+
pitch: true

plugins/forgesyte-yolo-tracker/src/forgesyte_yolo_tracker/manifest.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"id": "yolo-tracker",
33
"name": "YOLO Tracker",
4-
"version": "1.0.0",
5-
"description": "ForgeSyte plugin for football player, ball, pitch, and radar analysis using YOLO + Supervision.",
4+
"version": "0.10.0",
5+
"description": "YOLO + Supervision plugin with streaming video analysis.",
66
"author": "Roger",
77
"license": "MIT",
88
"entrypoint": "forgesyte_yolo_tracker.plugin",
@@ -11,7 +11,8 @@
1111
"player_detection",
1212
"ball_detection",
1313
"pitch_detection",
14-
"radar"
14+
"radar",
15+
"streaming_video_analysis"
1516
],
1617
"tools": [
1718
{
@@ -88,7 +89,7 @@
8889
"description": "Run ball detection on video frames.",
8990
"input_types": ["video"],
9091
"output_types": ["frames"],
91-
"capabilities": ["ball_detection"],
92+
"capabilities": ["ball_detection", "streaming_video_analysis"],
9293
"inputs": {
9394
"video_path": "string",
9495
"device": "string"
@@ -104,7 +105,7 @@
104105
"description": "Run pitch detection on video frames.",
105106
"input_types": ["video"],
106107
"output_types": ["frames"],
107-
"capabilities": ["pitch_detection"],
108+
"capabilities": ["pitch_detection", "streaming_video_analysis"],
108109
"inputs": {
109110
"video_path": "string",
110111
"device": "string"
@@ -120,7 +121,7 @@
120121
"description": "Generate radar view from video frames.",
121122
"input_types": ["video"],
122123
"output_types": ["frames"],
123-
"capabilities": ["radar"],
124+
"capabilities": ["radar", "streaming_video_analysis"],
124125
"inputs": {
125126
"video_path": "string",
126127
"device": "string"
@@ -136,7 +137,7 @@
136137
"description": "Track players across video frames.",
137138
"input_types": ["video"],
138139
"output_types": ["frames"],
139-
"capabilities": ["player_detection"],
140+
"capabilities": ["player_detection", "streaming_video_analysis"],
140141
"inputs": {
141142
"video_path": "string",
142143
"device": "string"
@@ -147,4 +148,4 @@
147148
}
148149
}
149150
]
150-
}
151+
}

plugins/forgesyte-yolo-tracker/src/forgesyte_yolo_tracker/plugin.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def on_unload(self) -> None: # pragma: no cover # noqa: B027
5858
from forgesyte_yolo_tracker.tracking import ByteTrackFactory, get_tracker_ids
5959
from forgesyte_yolo_tracker.inference.radar import radar_json_with_annotated_frame
6060
from forgesyte_yolo_tracker.configs import load_model_config
61+
from forgesyte_yolo_tracker.utils.json_sanitize import sanitize_json
6162

6263
logger = logging.getLogger(__name__)
6364

@@ -121,10 +122,10 @@ def _tool_player_detection(
121122
if error:
122123
return error
123124
if annotated and frame is not None:
124-
return detect_players_json_with_annotated_frame(frame, device=device)
125+
return {"success": True, "result": detect_players_json_with_annotated_frame(frame, device=device)}
125126
if frame is not None:
126-
return detect_players_json(frame, device=device)
127-
return {"error": "image_decode_failed"}
127+
return {"success": True, "result": detect_players_json(frame, device=device)}
128+
return {"success": False, "error": "image_decode_failed"}
128129

129130

130131

@@ -136,10 +137,10 @@ def _tool_player_tracking(
136137
if error:
137138
return error
138139
if annotated and frame is not None:
139-
return track_players_json_with_annotated_frame(frame, device=device)
140+
return {"success": True, "result": track_players_json_with_annotated_frame(frame, device=device)}
140141
if frame is not None:
141-
return track_players_json(frame, device=device)
142-
return {"error": "image_decode_failed"}
142+
return {"success": True, "result": track_players_json(frame, device=device)}
143+
return {"success": False, "error": "image_decode_failed"}
143144

144145

145146
def _tool_video_player_tracking(
@@ -220,10 +221,10 @@ def _tool_ball_detection(
220221
if error:
221222
return error
222223
if annotated and frame is not None:
223-
return detect_ball_json_with_annotated_frame(frame, device=device)
224+
return {"success": True, "result": detect_ball_json_with_annotated_frame(frame, device=device)}
224225
if frame is not None:
225-
return detect_ball_json(frame, device=device)
226-
return {"error": "image_decode_failed"}
226+
return {"success": True, "result": detect_ball_json(frame, device=device)}
227+
return {"success": False, "error": "image_decode_failed"}
227228

228229

229230
def _tool_pitch_detection(
@@ -233,10 +234,10 @@ def _tool_pitch_detection(
233234
if error:
234235
return error
235236
if annotated and frame is not None:
236-
return detect_pitch_json_with_annotated_frame(frame, device=device)
237+
return {"success": True, "result": detect_pitch_json_with_annotated_frame(frame, device=device)}
237238
if frame is not None:
238-
return detect_pitch_json(frame, device=device)
239-
return {"error": "image_decode_failed"}
239+
return {"success": True, "result": detect_pitch_json(frame, device=device)}
240+
return {"success": False, "error": "image_decode_failed"}
240241

241242

242243
def _tool_radar(
@@ -246,10 +247,10 @@ def _tool_radar(
246247
if error:
247248
return error
248249
if annotated and frame is not None:
249-
return radar_json_with_annotated_frame(frame, device=device)
250+
return {"success": True, "result": radar_json_with_annotated_frame(frame, device=device)}
250251
if frame is not None:
251-
return radar_json(frame, device=device)
252-
return {"error": "image_decode_failed"}
252+
return {"success": True, "result": radar_json(frame, device=device)}
253+
return {"success": False, "error": "image_decode_failed"}
253254

254255

255256
# ---------------------------------------------------------
@@ -304,9 +305,13 @@ def _run_video_tool(
304305

305306
logger.info(f"Completed: {frame_index} frames")
306307

308+
# v0.10.0: Sanitize output for JSON serialization
307309
return {
308-
"total_frames": frame_index,
309-
"frames": frame_results,
310+
"success": True,
311+
"result": sanitize_json({
312+
"total_frames": frame_index,
313+
"frames": frame_results,
314+
})
310315
}
311316

312317

@@ -475,10 +480,11 @@ def _tool_video_player_tracking(
475480

476481
frame_index += 1
477482

478-
return {
483+
# v0.10.0: Sanitize output for JSON serialization
484+
return sanitize_json({
479485
"total_frames": frame_index,
480486
"frames": frame_results,
481-
}
487+
})
482488

483489

484490
class Plugin(BasePlugin): # type: ignore[misc]

plugins/forgesyte-yolo-tracker/src/forgesyte_yolo_tracker/utils/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,20 @@
1313
"""
1414

1515
from . import ball, soccer_pitch
16-
from .team import TeamClassifier, create_batches
17-
from .view import ViewTransformer
16+
17+
# Lazy import to avoid torch/transformers at module load time
18+
def __getattr__(name: str):
19+
"""Lazy load heavy dependencies to avoid import errors in tests."""
20+
if name == "TeamClassifier":
21+
from .team import TeamClassifier
22+
return TeamClassifier
23+
elif name == "create_batches":
24+
from .team import create_batches
25+
return create_batches
26+
elif name == "ViewTransformer":
27+
from .view import ViewTransformer
28+
return ViewTransformer
29+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
1830

1931
__all__ = [
2032
# From local implementations
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""JSON Sanitizer for NumPy types.
2+
3+
v0.10.0: Ensures all plugin outputs are JSON-serializable.
4+
5+
Converts NumPy types to Python primitives:
6+
- np.float16, np.float32, np.float64 → float
7+
- np.int8, np.int16, np.int32, np.int64 → int
8+
- np.uint8, np.uint16, np.uint32, np.uint64 → int
9+
- np.ndarray → list
10+
- np.bool_ → bool
11+
"""
12+
13+
import numpy as np
14+
from typing import Any
15+
16+
17+
def sanitize_json(obj: Any) -> Any:
18+
"""
19+
Recursively convert NumPy types into JSON-safe Python types.
20+
21+
Ensures no np.float32, np.int64, np.ndarray, or other non-serializable
22+
objects appear in the final plugin output.
23+
24+
Args:
25+
obj: Any Python object, potentially containing NumPy types
26+
27+
Returns:
28+
Object with all NumPy types converted to Python primitives
29+
30+
Example:
31+
>>> sanitize_json({
32+
... "track_id": np.int64(1),
33+
... "center": np.array([np.float32(100.5), np.float32(200.5)])
34+
... })
35+
{'track_id': 1, 'center': [100.5, 200.5]}
36+
"""
37+
# Dict → sanitize values
38+
if isinstance(obj, dict):
39+
return {k: sanitize_json(v) for k, v in obj.items()}
40+
41+
# List / tuple → sanitize each element
42+
if isinstance(obj, (list, tuple)):
43+
return [sanitize_json(v) for v in obj]
44+
45+
# NumPy array → convert to list, then sanitize elements
46+
if isinstance(obj, np.ndarray):
47+
return sanitize_json(obj.tolist())
48+
49+
# NumPy floats → Python float
50+
if isinstance(obj, (np.float16, np.float32, np.float64)):
51+
return float(obj)
52+
53+
# NumPy ints → Python int
54+
if isinstance(obj, (np.int8, np.int16, np.int32, np.int64)):
55+
return int(obj)
56+
57+
# NumPy unsigned ints → Python int
58+
if isinstance(obj, (np.uint8, np.uint16, np.uint32, np.uint64)):
59+
return int(obj)
60+
61+
# NumPy bool → Python bool
62+
if isinstance(obj, np.bool_):
63+
return bool(obj)
64+
65+
# Pass through Python primitives and None
66+
return obj

0 commit comments

Comments
 (0)