rocket-welder-sdk 1.1.43__py3-none-any.whl → 1.1.44__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.
- rocket_welder_sdk/__init__.py +18 -22
- rocket_welder_sdk/binary_frame_reader.py +222 -0
- rocket_welder_sdk/binary_frame_writer.py +213 -0
- rocket_welder_sdk/confidence.py +206 -0
- rocket_welder_sdk/delta_frame.py +150 -0
- rocket_welder_sdk/high_level/__init__.py +8 -1
- rocket_welder_sdk/high_level/client.py +114 -3
- rocket_welder_sdk/high_level/connection_strings.py +3 -15
- rocket_welder_sdk/high_level/frame_sink_factory.py +2 -15
- rocket_welder_sdk/high_level/transport_protocol.py +4 -130
- rocket_welder_sdk/keypoints_protocol.py +520 -55
- rocket_welder_sdk/rocket_welder_client.py +0 -77
- rocket_welder_sdk/segmentation_result.py +387 -2
- rocket_welder_sdk/session_id.py +6 -182
- rocket_welder_sdk/transport/__init__.py +10 -3
- rocket_welder_sdk/transport/frame_sink.py +3 -3
- rocket_welder_sdk/transport/frame_source.py +2 -2
- rocket_welder_sdk/transport/websocket_transport.py +316 -0
- rocket_welder_sdk/varint.py +213 -0
- {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.44.dist-info}/METADATA +1 -4
- {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.44.dist-info}/RECORD +23 -18
- rocket_welder_sdk/transport/nng_transport.py +0 -197
- {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.44.dist-info}/WHEEL +0 -0
- {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.44.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=[])
|
|
@@ -12,7 +12,12 @@ Example:
|
|
|
12
12
|
client.start(process_frame)
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
from .client import
|
|
15
|
+
from .client import (
|
|
16
|
+
IRocketWelderClient,
|
|
17
|
+
RocketWelderClient,
|
|
18
|
+
RocketWelderClientFactory,
|
|
19
|
+
RocketWelderClientOptions,
|
|
20
|
+
)
|
|
16
21
|
from .connection_strings import (
|
|
17
22
|
KeyPointsConnectionString,
|
|
18
23
|
SegmentationConnectionString,
|
|
@@ -39,11 +44,13 @@ __all__ = [
|
|
|
39
44
|
"FrameSinkFactory",
|
|
40
45
|
"IKeyPointsDataContext",
|
|
41
46
|
"IKeyPointsSchema",
|
|
47
|
+
"IRocketWelderClient",
|
|
42
48
|
"ISegmentationDataContext",
|
|
43
49
|
"ISegmentationSchema",
|
|
44
50
|
"KeyPointDefinition",
|
|
45
51
|
"KeyPointsConnectionString",
|
|
46
52
|
"RocketWelderClient",
|
|
53
|
+
"RocketWelderClientFactory",
|
|
47
54
|
"RocketWelderClientOptions",
|
|
48
55
|
"SegmentClass",
|
|
49
56
|
"SegmentationConnectionString",
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
RocketWelderClient - High-level API matching C# RocketWelder.SDK.
|
|
3
3
|
|
|
4
4
|
Usage:
|
|
5
|
-
with
|
|
5
|
+
with RocketWelderClientFactory.from_environment() as client:
|
|
6
6
|
# Define schema
|
|
7
7
|
nose = client.keypoints.define_point("nose")
|
|
8
8
|
person = client.segmentation.define_class(1, "person")
|
|
9
9
|
|
|
10
10
|
# Start processing
|
|
11
11
|
client.start(process_frame)
|
|
12
|
+
|
|
13
|
+
Alternatively:
|
|
14
|
+
# Using class methods directly
|
|
15
|
+
with RocketWelderClient.from_environment() as client:
|
|
16
|
+
...
|
|
12
17
|
"""
|
|
13
18
|
|
|
14
19
|
from __future__ import annotations
|
|
15
20
|
|
|
16
21
|
import logging
|
|
22
|
+
from abc import ABC, abstractmethod
|
|
17
23
|
from dataclasses import dataclass, field
|
|
18
24
|
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
19
25
|
|
|
@@ -50,6 +56,73 @@ Mat: TypeAlias = npt.NDArray[np.uint8]
|
|
|
50
56
|
logger = logging.getLogger(__name__)
|
|
51
57
|
|
|
52
58
|
|
|
59
|
+
class IRocketWelderClient(ABC):
|
|
60
|
+
"""
|
|
61
|
+
Main entry point for RocketWelder SDK high-level API.
|
|
62
|
+
|
|
63
|
+
Provides schema definitions and frame processing loop.
|
|
64
|
+
Matches C# IRocketWelderClient interface.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def keypoints(self) -> IKeyPointsSchema:
|
|
70
|
+
"""Schema for defining keypoints."""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def segmentation(self) -> ISegmentationSchema:
|
|
76
|
+
"""Schema for defining segmentation classes."""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def start(
|
|
81
|
+
self,
|
|
82
|
+
process_frame: Callable[[Mat, ISegmentationDataContext, IKeyPointsDataContext, Mat], None],
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Start the processing loop with full context (keypoints + segmentation).
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
process_frame: Callback for each frame with:
|
|
89
|
+
- input_frame: Source video frame (Mat)
|
|
90
|
+
- segmentation: Segmentation data context
|
|
91
|
+
- keypoints: KeyPoints data context
|
|
92
|
+
- output_frame: Output frame for visualization (Mat)
|
|
93
|
+
"""
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def start_keypoints(
|
|
98
|
+
self,
|
|
99
|
+
process_frame: Callable[[Mat, IKeyPointsDataContext, Mat], None],
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Start the processing loop (keypoints only)."""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def start_segmentation(
|
|
106
|
+
self,
|
|
107
|
+
process_frame: Callable[[Mat, ISegmentationDataContext, Mat], None],
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Start the processing loop (segmentation only)."""
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def close(self) -> None:
|
|
114
|
+
"""Release resources."""
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
def __enter__(self) -> IRocketWelderClient:
|
|
118
|
+
"""Context manager entry."""
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
def __exit__(self, *args: object) -> None:
|
|
122
|
+
"""Context manager exit."""
|
|
123
|
+
self.close()
|
|
124
|
+
|
|
125
|
+
|
|
53
126
|
@dataclass
|
|
54
127
|
class RocketWelderClientOptions:
|
|
55
128
|
"""Configuration options for RocketWelderClient."""
|
|
@@ -72,11 +145,12 @@ class RocketWelderClientOptions:
|
|
|
72
145
|
)
|
|
73
146
|
|
|
74
147
|
|
|
75
|
-
class RocketWelderClient:
|
|
148
|
+
class RocketWelderClient(IRocketWelderClient):
|
|
76
149
|
"""
|
|
77
150
|
High-level client for RocketWelder SDK.
|
|
78
151
|
|
|
79
|
-
|
|
152
|
+
Implements IRocketWelderClient interface.
|
|
153
|
+
Mirrors C# RocketWelder.SDK.RocketWelderClientImpl.
|
|
80
154
|
"""
|
|
81
155
|
|
|
82
156
|
def __init__(self, options: RocketWelderClientOptions) -> None:
|
|
@@ -233,3 +307,40 @@ class RocketWelderClient:
|
|
|
233
307
|
|
|
234
308
|
def __exit__(self, *args: object) -> None:
|
|
235
309
|
self.close()
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class RocketWelderClientFactory:
|
|
313
|
+
"""
|
|
314
|
+
Factory for creating RocketWelderClient instances.
|
|
315
|
+
|
|
316
|
+
Matches C# RocketWelderClientFactory static class.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def from_environment() -> IRocketWelderClient:
|
|
321
|
+
"""
|
|
322
|
+
Creates a client configured from environment variables.
|
|
323
|
+
|
|
324
|
+
Environment variables:
|
|
325
|
+
- VIDEO_SOURCE or CONNECTION_STRING: Video input
|
|
326
|
+
- KEYPOINTS_CONNECTION_STRING: KeyPoints output
|
|
327
|
+
- SEGMENTATION_CONNECTION_STRING: Segmentation output
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
IRocketWelderClient configured from environment.
|
|
331
|
+
"""
|
|
332
|
+
options = RocketWelderClientOptions.from_environment()
|
|
333
|
+
return RocketWelderClient(options)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def create(options: Optional[RocketWelderClientOptions] = None) -> IRocketWelderClient:
|
|
337
|
+
"""
|
|
338
|
+
Creates a client with explicit configuration.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
options: Configuration options. If None, uses defaults.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
IRocketWelderClient with the specified configuration.
|
|
345
|
+
"""
|
|
346
|
+
return RocketWelderClient(options or RocketWelderClientOptions())
|
|
@@ -4,10 +4,8 @@ Strongly-typed connection strings with parsing support.
|
|
|
4
4
|
Connection string format: protocol://path?param1=value1¶m2=value2
|
|
5
5
|
|
|
6
6
|
Examples:
|
|
7
|
-
nng+push+ipc://tmp/keypoints?masterFrameInterval=300
|
|
8
|
-
nng+pub+tcp://localhost:5555
|
|
9
7
|
file:///path/to/output.bin
|
|
10
|
-
socket:///tmp/my.sock
|
|
8
|
+
socket:///tmp/my.sock?masterFrameInterval=300
|
|
11
9
|
"""
|
|
12
10
|
|
|
13
11
|
from __future__ import annotations
|
|
@@ -148,8 +146,6 @@ class KeyPointsConnectionString:
|
|
|
148
146
|
Supported protocols:
|
|
149
147
|
- file:///path/to/file.bin - File output (absolute path)
|
|
150
148
|
- socket:///tmp/socket.sock - Unix domain socket
|
|
151
|
-
- nng+push+ipc://tmp/keypoints - NNG Push over IPC
|
|
152
|
-
- nng+push+tcp://host:port - NNG Push over TCP
|
|
153
149
|
|
|
154
150
|
Supported parameters:
|
|
155
151
|
- masterFrameInterval: Interval between master frames (default: 300)
|
|
@@ -164,7 +160,7 @@ class KeyPointsConnectionString:
|
|
|
164
160
|
@classmethod
|
|
165
161
|
def default(cls) -> KeyPointsConnectionString:
|
|
166
162
|
"""Default connection string for KeyPoints."""
|
|
167
|
-
return cls.parse("
|
|
163
|
+
return cls.parse("socket:///tmp/rocket-welder-keypoints.sock?masterFrameInterval=300")
|
|
168
164
|
|
|
169
165
|
@classmethod
|
|
170
166
|
def from_environment(
|
|
@@ -217,9 +213,6 @@ class KeyPointsConnectionString:
|
|
|
217
213
|
elif protocol.is_socket:
|
|
218
214
|
# socket:///tmp/sock -> /tmp/sock
|
|
219
215
|
address = path_part if path_part.startswith("/") else "/" + path_part
|
|
220
|
-
elif protocol.is_nng:
|
|
221
|
-
# NNG protocols need proper address format
|
|
222
|
-
address = protocol.create_nng_address(path_part)
|
|
223
216
|
else:
|
|
224
217
|
return None
|
|
225
218
|
|
|
@@ -249,8 +242,6 @@ class SegmentationConnectionString:
|
|
|
249
242
|
Supported protocols:
|
|
250
243
|
- file:///path/to/file.bin - File output (absolute path)
|
|
251
244
|
- socket:///tmp/socket.sock - Unix domain socket
|
|
252
|
-
- nng+push+ipc://tmp/segmentation - NNG Push over IPC
|
|
253
|
-
- nng+push+tcp://host:port - NNG Push over TCP
|
|
254
245
|
"""
|
|
255
246
|
|
|
256
247
|
value: str
|
|
@@ -261,7 +252,7 @@ class SegmentationConnectionString:
|
|
|
261
252
|
@classmethod
|
|
262
253
|
def default(cls) -> SegmentationConnectionString:
|
|
263
254
|
"""Default connection string for Segmentation."""
|
|
264
|
-
return cls.parse("
|
|
255
|
+
return cls.parse("socket:///tmp/rocket-welder-segmentation.sock")
|
|
265
256
|
|
|
266
257
|
@classmethod
|
|
267
258
|
def from_environment(
|
|
@@ -314,9 +305,6 @@ class SegmentationConnectionString:
|
|
|
314
305
|
elif protocol.is_socket:
|
|
315
306
|
# socket:///tmp/sock -> /tmp/sock
|
|
316
307
|
address = path_part if path_part.startswith("/") else "/" + path_part
|
|
317
|
-
elif protocol.is_nng:
|
|
318
|
-
# NNG protocols need proper address format
|
|
319
|
-
address = protocol.create_nng_address(path_part)
|
|
320
308
|
else:
|
|
321
309
|
return None
|
|
322
310
|
|
|
@@ -51,7 +51,7 @@ class FrameSinkFactory:
|
|
|
51
51
|
|
|
52
52
|
Args:
|
|
53
53
|
protocol: The transport protocol (from ConnectionString.protocol), or None
|
|
54
|
-
address: The address (file path
|
|
54
|
+
address: The address (file path or socket path)
|
|
55
55
|
logger_instance: Optional logger for diagnostics
|
|
56
56
|
|
|
57
57
|
Returns:
|
|
@@ -64,7 +64,7 @@ class FrameSinkFactory:
|
|
|
64
64
|
cs = SegmentationConnectionString.parse("socket:///tmp/seg.sock")
|
|
65
65
|
sink = FrameSinkFactory.create(cs.protocol, cs.address)
|
|
66
66
|
"""
|
|
67
|
-
from rocket_welder_sdk.transport import
|
|
67
|
+
from rocket_welder_sdk.transport import NullFrameSink
|
|
68
68
|
from rocket_welder_sdk.transport.stream_transport import StreamFrameSink
|
|
69
69
|
from rocket_welder_sdk.transport.unix_socket_transport import UnixSocketFrameSink
|
|
70
70
|
|
|
@@ -87,19 +87,6 @@ class FrameSinkFactory:
|
|
|
87
87
|
log.info("Creating Unix socket frame sink (server/bind) at: %s", address)
|
|
88
88
|
return UnixSocketFrameSink.bind(address)
|
|
89
89
|
|
|
90
|
-
if protocol.is_nng:
|
|
91
|
-
log.info("Creating NNG frame sink (%s) at: %s", protocol.schema, address)
|
|
92
|
-
|
|
93
|
-
if protocol.is_pub:
|
|
94
|
-
return NngFrameSink.create_publisher(address)
|
|
95
|
-
if protocol.is_push:
|
|
96
|
-
return NngFrameSink.create_pusher(address)
|
|
97
|
-
|
|
98
|
-
raise ValueError(
|
|
99
|
-
f"NNG protocol '{protocol.schema}' is not supported for sinks "
|
|
100
|
-
"(only pub and push are supported)"
|
|
101
|
-
)
|
|
102
|
-
|
|
103
90
|
raise ValueError(f"Transport protocol '{protocol.schema}' is not supported for frame sinks")
|
|
104
91
|
|
|
105
92
|
@staticmethod
|