rocket-welder-sdk 1.1.43__py3-none-any.whl → 1.1.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. rocket_welder_sdk/__init__.py +44 -22
  2. rocket_welder_sdk/binary_frame_reader.py +222 -0
  3. rocket_welder_sdk/binary_frame_writer.py +213 -0
  4. rocket_welder_sdk/confidence.py +206 -0
  5. rocket_welder_sdk/delta_frame.py +150 -0
  6. rocket_welder_sdk/graphics/__init__.py +42 -0
  7. rocket_welder_sdk/graphics/layer_canvas.py +157 -0
  8. rocket_welder_sdk/graphics/protocol.py +72 -0
  9. rocket_welder_sdk/graphics/rgb_color.py +109 -0
  10. rocket_welder_sdk/graphics/stage.py +494 -0
  11. rocket_welder_sdk/graphics/vector_graphics_encoder.py +575 -0
  12. rocket_welder_sdk/high_level/__init__.py +8 -1
  13. rocket_welder_sdk/high_level/client.py +114 -3
  14. rocket_welder_sdk/high_level/connection_strings.py +88 -15
  15. rocket_welder_sdk/high_level/frame_sink_factory.py +2 -15
  16. rocket_welder_sdk/high_level/transport_protocol.py +4 -130
  17. rocket_welder_sdk/keypoints_protocol.py +520 -55
  18. rocket_welder_sdk/rocket_welder_client.py +210 -89
  19. rocket_welder_sdk/segmentation_result.py +387 -2
  20. rocket_welder_sdk/session_id.py +7 -182
  21. rocket_welder_sdk/transport/__init__.py +10 -3
  22. rocket_welder_sdk/transport/frame_sink.py +3 -3
  23. rocket_welder_sdk/transport/frame_source.py +2 -2
  24. rocket_welder_sdk/transport/websocket_transport.py +316 -0
  25. rocket_welder_sdk/varint.py +213 -0
  26. {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.45.dist-info}/METADATA +1 -4
  27. rocket_welder_sdk-1.1.45.dist-info/RECORD +51 -0
  28. {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.45.dist-info}/WHEEL +1 -1
  29. rocket_welder_sdk/transport/nng_transport.py +0 -197
  30. rocket_welder_sdk-1.1.43.dist-info/RECORD +0 -40
  31. {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.45.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,206 @@
1
+ """
2
+ Confidence value type for ML detection results.
3
+ Matches C# Confidence struct from RocketWelder.SDK.Protocols.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ from typing import ClassVar, Optional, Union
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class Confidence:
14
+ """
15
+ Represents a confidence score for ML detection results.
16
+
17
+ Internally stored as ushort (0-65535) for full precision.
18
+ Provides normalized float (0.0-1.0) for easy usage.
19
+
20
+ Examples:
21
+ >>> # Create from float (0.0-1.0)
22
+ >>> c1 = Confidence.from_float(0.95)
23
+ >>>
24
+ >>> # Create from raw ushort (0-65535)
25
+ >>> c2 = Confidence(raw=65535)
26
+ >>>
27
+ >>> # Get normalized float
28
+ >>> normalized = c1.normalized # 0.95
29
+ >>>
30
+ >>> # Compare with float
31
+ >>> if c1 > 0.9:
32
+ ... print("High confidence")
33
+ >>>
34
+ >>> # Get raw ushort value
35
+ >>> raw_value = c1.raw
36
+ """
37
+
38
+ MAX_RAW: ClassVar[int] = 65535 # ushort.MaxValue
39
+
40
+ raw: int
41
+ """The raw ushort value (0-65535)."""
42
+
43
+ def __post_init__(self) -> None:
44
+ """Validate raw value is in valid range."""
45
+ if not 0 <= self.raw <= Confidence.MAX_RAW:
46
+ raise ValueError(
47
+ f"Raw value must be between 0 and {Confidence.MAX_RAW}, got {self.raw}"
48
+ )
49
+
50
+ @property
51
+ def normalized(self) -> float:
52
+ """Get the normalized float value (0.0-1.0)."""
53
+ return self.raw / Confidence.MAX_RAW
54
+
55
+ @classmethod
56
+ def from_float(cls, value: float) -> Confidence:
57
+ """
58
+ Create Confidence from a float value (0.0-1.0).
59
+
60
+ The value is clamped to [0.0, 1.0] range before conversion.
61
+
62
+ Args:
63
+ value: Float value in range 0.0 to 1.0
64
+
65
+ Returns:
66
+ Confidence instance
67
+ """
68
+ clamped = max(0.0, min(1.0, value))
69
+ raw = int(clamped * cls.MAX_RAW)
70
+ return cls(raw=raw)
71
+
72
+ @classmethod
73
+ def full(cls) -> Confidence:
74
+ """Full confidence (1.0)."""
75
+ return cls(raw=cls.MAX_RAW)
76
+
77
+ @classmethod
78
+ def zero(cls) -> Confidence:
79
+ """Zero confidence (0.0)."""
80
+ return cls(raw=0)
81
+
82
+ @classmethod
83
+ def parse(cls, value: Union[str, int, float, Confidence]) -> Confidence:
84
+ """
85
+ Parse a string, number, or Confidence into a Confidence object.
86
+
87
+ Equivalent to C# IParsable<Confidence>.Parse().
88
+
89
+ Args:
90
+ value: Value to parse (e.g., "0.95", 0.95, 65535)
91
+
92
+ Returns:
93
+ Confidence instance
94
+
95
+ Raises:
96
+ ValueError: If the value cannot be parsed
97
+ """
98
+ if isinstance(value, Confidence):
99
+ return value
100
+
101
+ if isinstance(value, int):
102
+ if 0 <= value <= cls.MAX_RAW:
103
+ return cls(raw=value)
104
+ raise ValueError(f"Integer value must be between 0 and {cls.MAX_RAW}, got {value}")
105
+
106
+ if isinstance(value, float):
107
+ return cls.from_float(value)
108
+
109
+ if isinstance(value, str):
110
+ value = value.strip()
111
+ if not value:
112
+ raise ValueError("Cannot parse empty string as Confidence")
113
+
114
+ # Try parsing as percentage (e.g., "95.0%")
115
+ if value.endswith("%"):
116
+ try:
117
+ percent = float(value[:-1])
118
+ return cls.from_float(percent / 100.0)
119
+ except ValueError as e:
120
+ raise ValueError(f"Invalid percentage format: '{value}'") from e
121
+
122
+ # Try parsing as float (0.0-1.0)
123
+ try:
124
+ float_val = float(value)
125
+ if 0.0 <= float_val <= 1.0:
126
+ return cls.from_float(float_val)
127
+ # If > 1.0, treat as raw ushort value
128
+ if float_val == int(float_val) and 0 <= float_val <= cls.MAX_RAW:
129
+ return cls(raw=int(float_val))
130
+ raise ValueError(
131
+ f"Float value must be 0.0-1.0 or integer 0-{cls.MAX_RAW}, got {float_val}"
132
+ )
133
+ except ValueError:
134
+ raise ValueError(f"Invalid Confidence format: '{value}'") from None
135
+
136
+ raise ValueError(f"Cannot parse {type(value).__name__} as Confidence")
137
+
138
+ @classmethod
139
+ def try_parse(cls, value: Union[str, int, float, Confidence]) -> Optional[Confidence]:
140
+ """
141
+ Try to parse a value into Confidence, returning None on failure.
142
+
143
+ Args:
144
+ value: Value to parse
145
+
146
+ Returns:
147
+ Confidence instance or None if parsing failed
148
+ """
149
+ try:
150
+ return cls.parse(value)
151
+ except (ValueError, TypeError):
152
+ return None
153
+
154
+ def __float__(self) -> float:
155
+ """Convert to float (normalized value)."""
156
+ return self.normalized
157
+
158
+ def __int__(self) -> int:
159
+ """Convert to int (raw value)."""
160
+ return self.raw
161
+
162
+ def __str__(self) -> str:
163
+ """Return the normalized value as a percentage string (e.g., '95.0%')."""
164
+ return f"{self.normalized:.1%}"
165
+
166
+ def __repr__(self) -> str:
167
+ """Developer-friendly representation."""
168
+ return f"Confidence(raw={self.raw}, normalized={self.normalized:.4f})"
169
+
170
+ # Comparison operators with float
171
+ def __lt__(self, other: object) -> bool:
172
+ """Less than comparison."""
173
+ if isinstance(other, Confidence):
174
+ return self.raw < other.raw
175
+ if isinstance(other, (int, float)):
176
+ return self.normalized < other
177
+ return NotImplemented
178
+
179
+ def __le__(self, other: object) -> bool:
180
+ """Less than or equal comparison."""
181
+ if isinstance(other, Confidence):
182
+ return self.raw <= other.raw
183
+ if isinstance(other, (int, float)):
184
+ return self.normalized <= other
185
+ return NotImplemented
186
+
187
+ def __gt__(self, other: object) -> bool:
188
+ """Greater than comparison."""
189
+ if isinstance(other, Confidence):
190
+ return self.raw > other.raw
191
+ if isinstance(other, (int, float)):
192
+ return self.normalized > other
193
+ return NotImplemented
194
+
195
+ def __ge__(self, other: object) -> bool:
196
+ """Greater than or equal comparison."""
197
+ if isinstance(other, Confidence):
198
+ return self.raw >= other.raw
199
+ if isinstance(other, (int, float)):
200
+ return self.normalized >= other
201
+ return NotImplemented
202
+
203
+
204
+ # Convenience constants matching C# static properties
205
+ FULL = Confidence.full()
206
+ ZERO = Confidence.zero()
@@ -0,0 +1,150 @@
1
+ """
2
+ DeltaFrame generic container for delta-encoded streaming data.
3
+ Matches C# DeltaFrame<T> struct from RocketWelder.SDK.Protocols.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Generic, Iterator, Sequence, TypeVar
10
+
11
+ # TypeVar for the item type (equivalent to C# struct constraint)
12
+ T = TypeVar("T")
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class DeltaFrame(Generic[T]):
17
+ """
18
+ Generic container for delta-encoded streaming data.
19
+
20
+ Used by Reader/Source classes where IsDelta is needed for decoding.
21
+ When IsDelta is True, the items contain delta-encoded values relative
22
+ to the previous frame, otherwise they contain absolute values.
23
+
24
+ Type Parameters:
25
+ T: The item type (e.g., KeyPoint, SegmentationInstance)
26
+
27
+ Attributes:
28
+ frame_id: The frame identifier (ulong equivalent).
29
+ is_delta: True if this frame contains delta-encoded values relative to previous frame.
30
+ items: The items in this frame. Caller owns the backing memory.
31
+
32
+ Examples:
33
+ >>> from dataclasses import dataclass
34
+ >>> @dataclass(frozen=True)
35
+ ... class KeyPoint:
36
+ ... x: int
37
+ ... y: int
38
+ ...
39
+ >>> # Create a master frame (absolute values)
40
+ >>> master = DeltaFrame[KeyPoint](
41
+ ... frame_id=1,
42
+ ... is_delta=False,
43
+ ... items=[KeyPoint(100, 200), KeyPoint(150, 250)]
44
+ ... )
45
+ >>>
46
+ >>> # Create a delta frame (relative values)
47
+ >>> delta = DeltaFrame[KeyPoint](
48
+ ... frame_id=2,
49
+ ... is_delta=True,
50
+ ... items=[KeyPoint(5, -3), KeyPoint(-2, 1)]
51
+ ... )
52
+ >>>
53
+ >>> # Check if it's a master frame
54
+ >>> if not delta.is_delta:
55
+ ... print("Master frame")
56
+ """
57
+
58
+ frame_id: int
59
+ """The frame identifier (ulong equivalent, 0 to 2^64-1)."""
60
+
61
+ is_delta: bool
62
+ """True if this frame contains delta-encoded values relative to previous frame."""
63
+
64
+ items: Sequence[T]
65
+ """The items in this frame. Caller owns the backing memory."""
66
+
67
+ def __post_init__(self) -> None:
68
+ """Validate frame_id is non-negative."""
69
+ if self.frame_id < 0:
70
+ raise ValueError(f"frame_id must be non-negative, got {self.frame_id}")
71
+
72
+ @property
73
+ def is_master(self) -> bool:
74
+ """True if this is a master frame (not delta-encoded)."""
75
+ return not self.is_delta
76
+
77
+ @property
78
+ def count(self) -> int:
79
+ """Number of items in this frame."""
80
+ return len(self.items)
81
+
82
+ def __len__(self) -> int:
83
+ """Return the number of items in this frame."""
84
+ return len(self.items)
85
+
86
+ def __bool__(self) -> bool:
87
+ """Return True if the frame has items."""
88
+ return len(self.items) > 0
89
+
90
+ def __iter__(self) -> Iterator[T]:
91
+ """Iterate over items in this frame."""
92
+ return iter(self.items)
93
+
94
+ def __getitem__(self, index: int) -> T:
95
+ """Get item by index."""
96
+ return self.items[index]
97
+
98
+ @classmethod
99
+ def master(cls, frame_id: int, items: Sequence[T]) -> DeltaFrame[T]:
100
+ """
101
+ Create a master frame (absolute values, not delta-encoded).
102
+
103
+ Args:
104
+ frame_id: The frame identifier
105
+ items: The items with absolute values
106
+
107
+ Returns:
108
+ DeltaFrame with is_delta=False
109
+ """
110
+ return cls(frame_id=frame_id, is_delta=False, items=items)
111
+
112
+ @classmethod
113
+ def delta(cls, frame_id: int, items: Sequence[T]) -> DeltaFrame[T]:
114
+ """
115
+ Create a delta frame (values relative to previous frame).
116
+
117
+ Args:
118
+ frame_id: The frame identifier
119
+ items: The items with delta-encoded values
120
+
121
+ Returns:
122
+ DeltaFrame with is_delta=True
123
+ """
124
+ return cls(frame_id=frame_id, is_delta=True, items=items)
125
+
126
+ @classmethod
127
+ def empty_master(cls, frame_id: int) -> DeltaFrame[T]:
128
+ """
129
+ Create an empty master frame.
130
+
131
+ Args:
132
+ frame_id: The frame identifier
133
+
134
+ Returns:
135
+ DeltaFrame with no items and is_delta=False
136
+ """
137
+ return cls(frame_id=frame_id, is_delta=False, items=[])
138
+
139
+ @classmethod
140
+ def empty_delta(cls, frame_id: int) -> DeltaFrame[T]:
141
+ """
142
+ Create an empty delta frame.
143
+
144
+ Args:
145
+ frame_id: The frame identifier
146
+
147
+ Returns:
148
+ DeltaFrame with no items and is_delta=True
149
+ """
150
+ return cls(frame_id=frame_id, is_delta=True, items=[])
@@ -0,0 +1,42 @@
1
+ """
2
+ VectorGraphics module for streaming graphics overlays.
3
+
4
+ This module provides classes for encoding and streaming vector graphics
5
+ to the browser using Protocol V2.
6
+
7
+ Example:
8
+ from rocket_welder_sdk.graphics import StageSink, RgbColor
9
+ from rocket_welder_sdk.transport import UnixSocketFrameSink
10
+
11
+ # Create transport and sink
12
+ sink = UnixSocketFrameSink("/tmp/graphics.sock")
13
+ stage = StageSink(sink)
14
+
15
+ # Draw graphics for each frame
16
+ with stage.create_writer(frame_id) as writer:
17
+ writer[0].set_stroke(RgbColor.Red)
18
+ writer[0].draw_polygon([(0, 0), (100, 0), (100, 100)])
19
+ writer[1].draw_text("Hello", 10, 20)
20
+ """
21
+
22
+ from .layer_canvas import ILayerCanvas
23
+ from .protocol import END_MARKER_BYTE1, END_MARKER_BYTE2, FrameType, OpType, PropertyId
24
+ from .rgb_color import RgbColor
25
+ from .stage import IStageSink, IStageWriter, LayerEncoder, StageSink, StageWriter
26
+ from .vector_graphics_encoder import VectorGraphicsEncoder
27
+
28
+ __all__ = [
29
+ "END_MARKER_BYTE1",
30
+ "END_MARKER_BYTE2",
31
+ "FrameType",
32
+ "ILayerCanvas",
33
+ "IStageSink",
34
+ "IStageWriter",
35
+ "LayerEncoder",
36
+ "OpType",
37
+ "PropertyId",
38
+ "RgbColor",
39
+ "StageSink",
40
+ "StageWriter",
41
+ "VectorGraphicsEncoder",
42
+ ]
@@ -0,0 +1,157 @@
1
+ """
2
+ ILayerCanvas protocol - per-layer drawing interface.
3
+
4
+ Matches C# ILayerCanvas interface from BlazorBlaze.Server.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Protocol, Sequence, Tuple, runtime_checkable
10
+
11
+ if TYPE_CHECKING:
12
+ from .rgb_color import RgbColor
13
+
14
+
15
+ @runtime_checkable
16
+ class ILayerCanvas(Protocol):
17
+ """
18
+ Represents a single rendering layer with stateful context management.
19
+
20
+ Mirrors SkiaSharp's canvas API for familiar usage patterns.
21
+ This is a Protocol class (interface) matching C# ILayerCanvas.
22
+ """
23
+
24
+ @property
25
+ def layer_id(self) -> int:
26
+ """The layer ID (z-order index)."""
27
+ ...
28
+
29
+ # ============== Layer Frame Type ==============
30
+
31
+ def master(self) -> None:
32
+ """
33
+ Sets this layer to Master mode - clears and redraws with operations that follow.
34
+
35
+ This is the default mode when drawing operations are added.
36
+ """
37
+ ...
38
+
39
+ def remain(self) -> None:
40
+ """
41
+ Sets this layer to Remain mode - keeps previous content unchanged.
42
+
43
+ No operations are sent for this layer, saving bandwidth.
44
+ """
45
+ ...
46
+
47
+ def clear(self) -> None:
48
+ """
49
+ Sets this layer to Clear mode - clears to transparent with no redraw.
50
+
51
+ Use when you want to hide a layer without drawing new content.
52
+ """
53
+ ...
54
+
55
+ # ============== Context State - Styling ==============
56
+
57
+ def set_stroke(self, color: RgbColor) -> None:
58
+ """Sets the stroke color for subsequent draw operations."""
59
+ ...
60
+
61
+ def set_fill(self, color: RgbColor) -> None:
62
+ """Sets the fill color for subsequent draw operations."""
63
+ ...
64
+
65
+ def set_thickness(self, width: int) -> None:
66
+ """Sets the stroke thickness in pixels."""
67
+ ...
68
+
69
+ def set_font_size(self, size: int) -> None:
70
+ """Sets the font size in pixels."""
71
+ ...
72
+
73
+ def set_font_color(self, color: RgbColor) -> None:
74
+ """Sets the font color for text operations."""
75
+ ...
76
+
77
+ # ============== Context State - Transforms ==============
78
+
79
+ def translate(self, dx: float, dy: float) -> None:
80
+ """Sets the translation offset for subsequent draw operations."""
81
+ ...
82
+
83
+ def rotate(self, degrees: float) -> None:
84
+ """Sets the rotation in degrees for subsequent draw operations."""
85
+ ...
86
+
87
+ def scale(self, sx: float, sy: float) -> None:
88
+ """Sets the scale factors for subsequent draw operations."""
89
+ ...
90
+
91
+ def skew(self, kx: float, ky: float) -> None:
92
+ """Sets the skew factors for subsequent draw operations."""
93
+ ...
94
+
95
+ def set_matrix(
96
+ self,
97
+ scale_x: float,
98
+ skew_x: float,
99
+ trans_x: float,
100
+ skew_y: float,
101
+ scale_y: float,
102
+ trans_y: float,
103
+ ) -> None:
104
+ """
105
+ Sets a full transformation matrix for subsequent draw operations.
106
+
107
+ Takes precedence over individual transform properties.
108
+
109
+ Matrix layout matches SKMatrix:
110
+ | ScaleX SkewX TransX |
111
+ | SkewY ScaleY TransY |
112
+ """
113
+ ...
114
+
115
+ # ============== Context Stack ==============
116
+
117
+ def save(self) -> None:
118
+ """
119
+ Pushes the current context state onto a stack.
120
+
121
+ Use with restore() for hierarchical transforms.
122
+ """
123
+ ...
124
+
125
+ def restore(self) -> None:
126
+ """Pops and restores the most recently saved context state."""
127
+ ...
128
+
129
+ def reset_context(self) -> None:
130
+ """Resets the context to default values (black stroke, identity transform)."""
131
+ ...
132
+
133
+ # ============== Draw Operations ==============
134
+
135
+ def draw_polygon(self, points: Sequence[Tuple[float, float]]) -> None:
136
+ """Draws a polygon using the current context state."""
137
+ ...
138
+
139
+ def draw_text(self, text: str, x: int, y: int) -> None:
140
+ """Draws text at the specified position using the current context state."""
141
+ ...
142
+
143
+ def draw_circle(self, center_x: int, center_y: int, radius: int) -> None:
144
+ """Draws a circle using the current context state."""
145
+ ...
146
+
147
+ def draw_rectangle(self, x: int, y: int, width: int, height: int) -> None:
148
+ """Draws a rectangle using the current context state."""
149
+ ...
150
+
151
+ def draw_line(self, x1: int, y1: int, x2: int, y2: int) -> None:
152
+ """Draws a line using the current context state."""
153
+ ...
154
+
155
+ def draw_jpeg(self, jpeg_data: bytes, x: int, y: int, width: int, height: int) -> None:
156
+ """Draws a JPEG image at the specified position and size."""
157
+ ...
@@ -0,0 +1,72 @@
1
+ """
2
+ Protocol V2 enums and constants for VectorGraphics encoding.
3
+
4
+ Matches C# ProtocolV2 from BlazorBlaze.VectorGraphics.Protocol.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import IntEnum
10
+
11
+
12
+ class FrameType(IntEnum):
13
+ """
14
+ Frame type for layer updates.
15
+
16
+ Matches C# FrameType enum.
17
+ """
18
+
19
+ MASTER = 0x00 # Clear and redraw with operations
20
+ REMAIN = 0x01 # Keep previous content unchanged
21
+ CLEAR = 0x02 # Clear to transparent with no redraw
22
+
23
+
24
+ class OpType(IntEnum):
25
+ """
26
+ Operation type codes.
27
+
28
+ Matches C# OpType enum.
29
+ """
30
+
31
+ # Draw operations (0x01-0x0F)
32
+ DRAW_POLYGON = 0x01
33
+ DRAW_TEXT = 0x02
34
+ DRAW_CIRCLE = 0x03
35
+ DRAW_RECT = 0x04
36
+ DRAW_LINE = 0x05
37
+ DRAW_JPEG = 0x07
38
+
39
+ # Context operations (0x10-0x1F)
40
+ SET_CONTEXT = 0x10
41
+ SAVE_CONTEXT = 0x11
42
+ RESTORE_CONTEXT = 0x12
43
+ RESET_CONTEXT = 0x13
44
+
45
+
46
+ class PropertyId(IntEnum):
47
+ """
48
+ Property IDs for SetContext operation.
49
+
50
+ Matches C# PropertyId enum.
51
+ """
52
+
53
+ # Styling properties (0x01-0x0F)
54
+ STROKE = 0x01
55
+ FILL = 0x02
56
+ THICKNESS = 0x03
57
+ FONT_SIZE = 0x04
58
+ FONT_COLOR = 0x05
59
+
60
+ # Transform properties (0x10-0x1F)
61
+ OFFSET = 0x10
62
+ ROTATION = 0x11
63
+ SCALE = 0x12
64
+ SKEW = 0x13
65
+
66
+ # Matrix (0x20)
67
+ MATRIX = 0x20
68
+
69
+
70
+ # Protocol constants
71
+ END_MARKER_BYTE1: int = 0xFF
72
+ END_MARKER_BYTE2: int = 0xFF