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.
@@ -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 RocketWelderClient, RocketWelderClientOptions
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 RocketWelderClient.from_environment() as client:
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
- Mirrors C# RocketWelder.SDK.IRocketWelderClient interface.
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&param2=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("nng+push+ipc://tmp/rocket-welder-keypoints?masterFrameInterval=300")
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("nng+push+ipc://tmp/rocket-welder-segmentation")
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, socket path, or NNG address)
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 NngFrameSink, NullFrameSink
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