rocket-welder-sdk 1.1.34__py3-none-any.whl → 1.1.34.dev7__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.
@@ -338,7 +338,7 @@ class OneWayShmController(IController):
338
338
  Matches C# CreateMat behavior - creates Mat wrapping the data.
339
339
 
340
340
  Frame data layout from GStreamer zerosink:
341
- [FrameMetadata (16 bytes)][Pixel Data (WxHxC bytes)]
341
+ [FrameMetadata (16 bytes)][Pixel Data (W×H×C bytes)]
342
342
 
343
343
  Args:
344
344
  frame: ZeroBuffer frame
@@ -18,7 +18,7 @@ from __future__ import annotations
18
18
 
19
19
  import struct
20
20
  from dataclasses import dataclass
21
- from typing import ClassVar, Dict, Optional
21
+ from typing import Optional
22
22
 
23
23
  # Size of the FrameMetadata structure in bytes
24
24
  FRAME_METADATA_SIZE = 16
@@ -113,7 +113,7 @@ class GstVideoFormat:
113
113
  GRAY16_BE = 26
114
114
  GRAY16_LE = 27
115
115
 
116
- _FORMAT_NAMES: ClassVar[Dict[int, str]] = {
116
+ _FORMAT_NAMES: dict[int, str] = {
117
117
  0: "UNKNOWN",
118
118
  2: "I420",
119
119
  3: "YV12",
@@ -1,18 +1,26 @@
1
1
  """
2
2
  High-level API for RocketWelder SDK.
3
3
 
4
- Mirrors C# RocketWelder.SDK API for consistent developer experience.
4
+ Provides a simplified, user-friendly API for common video processing workflows
5
+ with automatic transport management and schema definitions.
5
6
 
6
7
  Example:
7
- from rocket_welder_sdk.high_level import RocketWelderClient
8
+ from rocket_welder_sdk.high_level import RocketWelderClient, Transport
8
9
 
9
- with RocketWelderClient.from_environment() as client:
10
+ async with RocketWelderClient.from_environment() as client:
11
+ # Define keypoints schema
10
12
  nose = client.keypoints.define_point("nose")
13
+ left_eye = client.keypoints.define_point("left_eye")
14
+
15
+ # Define segmentation classes
11
16
  person = client.segmentation.define_class(1, "person")
12
- client.start(process_frame)
17
+
18
+ async for input_frame, seg_ctx, kp_ctx, output_frame in client.start():
19
+ # Process frame...
20
+ kp_ctx.add(nose, x=100, y=200, confidence=0.95)
21
+ seg_ctx.add(person, instance_id=0, points=contour_points)
13
22
  """
14
23
 
15
- from .client import RocketWelderClient, RocketWelderClientOptions
16
24
  from .connection_strings import (
17
25
  KeyPointsConnectionString,
18
26
  SegmentationConnectionString,
@@ -26,11 +34,15 @@ from .data_context import (
26
34
  from .schema import (
27
35
  IKeyPointsSchema,
28
36
  ISegmentationSchema,
29
- KeyPointDefinition,
37
+ KeyPoint,
30
38
  SegmentClass,
31
39
  )
32
40
  from .transport_protocol import (
33
- TransportKind,
41
+ MessagingLibrary,
42
+ MessagingPattern,
43
+ Transport,
44
+ TransportBuilder,
45
+ TransportLayer,
34
46
  TransportProtocol,
35
47
  )
36
48
 
@@ -39,13 +51,15 @@ __all__ = [
39
51
  "IKeyPointsSchema",
40
52
  "ISegmentationDataContext",
41
53
  "ISegmentationSchema",
42
- "KeyPointDefinition",
54
+ "KeyPoint",
43
55
  "KeyPointsConnectionString",
44
- "RocketWelderClient",
45
- "RocketWelderClientOptions",
56
+ "MessagingLibrary",
57
+ "MessagingPattern",
46
58
  "SegmentClass",
47
59
  "SegmentationConnectionString",
48
- "TransportKind",
60
+ "Transport",
61
+ "TransportBuilder",
62
+ "TransportLayer",
49
63
  "TransportProtocol",
50
64
  "VideoSourceConnectionString",
51
65
  "VideoSourceType",
@@ -6,8 +6,7 @@ Connection string format: protocol://path?param1=value1&param2=value2
6
6
  Examples:
7
7
  nng+push+ipc://tmp/keypoints?masterFrameInterval=300
8
8
  nng+pub+tcp://localhost:5555
9
- file:///path/to/output.bin
10
- socket:///tmp/my.sock
9
+ file://path/to/output.bin
11
10
  """
12
11
 
13
12
  from __future__ import annotations
@@ -145,19 +144,19 @@ class KeyPointsConnectionString:
145
144
  """
146
145
  Strongly-typed connection string for KeyPoints output.
147
146
 
148
- Supported protocols:
149
- - file:///path/to/file.bin - File output (absolute path)
150
- - 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
147
+ Supported protocols (composable with + operator):
148
+ - Transport.Nng + Transport.Push + Transport.Ipc → nng+push+ipc://tmp/keypoints
149
+ - Transport.Nng + Transport.Push + Transport.Tcp → nng+push+tcp://host:port
150
+ - file://path/to/file.bin - File output
153
151
 
154
152
  Supported parameters:
155
153
  - masterFrameInterval: Interval between master frames (default: 300)
156
154
  """
157
155
 
158
156
  value: str
159
- protocol: TransportProtocol
160
- address: str
157
+ protocol: Optional[TransportProtocol] = None
158
+ is_file: bool = False
159
+ address: str = ""
161
160
  master_frame_interval: int = 300
162
161
  parameters: Dict[str, str] = field(default_factory=dict)
163
162
 
@@ -200,26 +199,25 @@ class KeyPointsConnectionString:
200
199
 
201
200
  # Parse protocol and address
202
201
  scheme_end = endpoint_part.find("://")
203
- if scheme_end <= 0:
204
- return None
205
-
206
- schema_str = endpoint_part[:scheme_end]
207
- path_part = endpoint_part[scheme_end + 3 :] # skip "://"
208
-
209
- protocol = TransportProtocol.try_parse(schema_str)
210
- if protocol is None:
211
- return None
212
-
213
- # Build address based on protocol type
214
- if protocol.is_file:
215
- # file:///absolute/path -> /absolute/path
216
- address = path_part if path_part.startswith("/") else "/" + path_part
217
- elif protocol.is_socket:
218
- # socket:///tmp/sock -> /tmp/sock
219
- 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)
202
+ if scheme_end > 0:
203
+ protocol_str = endpoint_part[:scheme_end]
204
+ path_part = endpoint_part[scheme_end + 3 :] # skip "://"
205
+
206
+ if protocol_str.lower() == "file":
207
+ address = "/" + path_part # Restore absolute path
208
+ is_file = True
209
+ protocol = None
210
+ else:
211
+ protocol = TransportProtocol.try_parse(protocol_str)
212
+ if protocol is None:
213
+ return None
214
+ address = protocol.create_nng_address(path_part)
215
+ is_file = False
216
+ elif s.startswith("/"):
217
+ # Assume absolute file path
218
+ address = s
219
+ is_file = True
220
+ protocol = None
223
221
  else:
224
222
  return None
225
223
 
@@ -232,6 +230,7 @@ class KeyPointsConnectionString:
232
230
  return cls(
233
231
  value=s,
234
232
  protocol=protocol,
233
+ is_file=is_file,
235
234
  address=address,
236
235
  master_frame_interval=master_frame_interval,
237
236
  parameters=parameters,
@@ -246,16 +245,16 @@ class SegmentationConnectionString:
246
245
  """
247
246
  Strongly-typed connection string for Segmentation output.
248
247
 
249
- Supported protocols:
250
- - file:///path/to/file.bin - File output (absolute path)
251
- - 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
248
+ Supported protocols (composable with + operator):
249
+ - Transport.Nng + Transport.Push + Transport.Ipc → nng+push+ipc://tmp/segmentation
250
+ - Transport.Nng + Transport.Push + Transport.Tcp → nng+push+tcp://host:port
251
+ - file://path/to/file.bin - File output
254
252
  """
255
253
 
256
254
  value: str
257
- protocol: TransportProtocol
258
- address: str
255
+ protocol: Optional[TransportProtocol] = None
256
+ is_file: bool = False
257
+ address: str = ""
259
258
  parameters: Dict[str, str] = field(default_factory=dict)
260
259
 
261
260
  @classmethod
@@ -297,32 +296,32 @@ class SegmentationConnectionString:
297
296
 
298
297
  # Parse protocol and address
299
298
  scheme_end = endpoint_part.find("://")
300
- if scheme_end <= 0:
301
- return None
302
-
303
- schema_str = endpoint_part[:scheme_end]
304
- path_part = endpoint_part[scheme_end + 3 :] # skip "://"
305
-
306
- protocol = TransportProtocol.try_parse(schema_str)
307
- if protocol is None:
308
- return None
309
-
310
- # Build address based on protocol type
311
- if protocol.is_file:
312
- # file:///absolute/path -> /absolute/path
313
- address = path_part if path_part.startswith("/") else "/" + path_part
314
- elif protocol.is_socket:
315
- # socket:///tmp/sock -> /tmp/sock
316
- 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)
299
+ if scheme_end > 0:
300
+ protocol_str = endpoint_part[:scheme_end]
301
+ path_part = endpoint_part[scheme_end + 3 :] # skip "://"
302
+
303
+ if protocol_str.lower() == "file":
304
+ address = "/" + path_part # Restore absolute path
305
+ is_file = True
306
+ protocol = None
307
+ else:
308
+ protocol = TransportProtocol.try_parse(protocol_str)
309
+ if protocol is None:
310
+ return None
311
+ address = protocol.create_nng_address(path_part)
312
+ is_file = False
313
+ elif s.startswith("/"):
314
+ # Assume absolute file path
315
+ address = s
316
+ is_file = True
317
+ protocol = None
320
318
  else:
321
319
  return None
322
320
 
323
321
  return cls(
324
322
  value=s,
325
323
  protocol=protocol,
324
+ is_file=is_file,
326
325
  address=address,
327
326
  parameters=parameters,
328
327
  )
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
17
17
  from rocket_welder_sdk.keypoints_protocol import IKeyPointsWriter
18
18
  from rocket_welder_sdk.segmentation_result import SegmentationResultWriter
19
19
 
20
- from .schema import KeyPointDefinition, SegmentClass
20
+ from .schema import KeyPoint, SegmentClass
21
21
 
22
22
  # Type aliases
23
23
  Point = Tuple[int, int]
@@ -37,12 +37,12 @@ class IKeyPointsDataContext(ABC):
37
37
  pass
38
38
 
39
39
  @abstractmethod
40
- def add(self, point: KeyPointDefinition, x: int, y: int, confidence: float) -> None:
40
+ def add(self, point: KeyPoint, x: int, y: int, confidence: float) -> None:
41
41
  """
42
42
  Add a keypoint detection for this frame.
43
43
 
44
44
  Args:
45
- point: KeyPointDefinition from schema definition
45
+ point: KeyPoint from schema definition
46
46
  x: X coordinate in pixels
47
47
  y: Y coordinate in pixels
48
48
  confidence: Detection confidence (0.0 to 1.0)
@@ -50,22 +50,17 @@ class IKeyPointsDataContext(ABC):
50
50
  pass
51
51
 
52
52
  @abstractmethod
53
- def add_point(self, point: KeyPointDefinition, position: Point, confidence: float) -> None:
53
+ def add_point(self, point: KeyPoint, position: Point, confidence: float) -> None:
54
54
  """
55
55
  Add a keypoint detection using a Point tuple.
56
56
 
57
57
  Args:
58
- point: KeyPointDefinition from schema definition
58
+ point: KeyPoint from schema definition
59
59
  position: (x, y) tuple
60
60
  confidence: Detection confidence (0.0 to 1.0)
61
61
  """
62
62
  pass
63
63
 
64
- @abstractmethod
65
- def commit(self) -> None:
66
- """Commit the context (called automatically when delegate returns)."""
67
- pass
68
-
69
64
 
70
65
  class ISegmentationDataContext(ABC):
71
66
  """
@@ -97,11 +92,6 @@ class ISegmentationDataContext(ABC):
97
92
  """
98
93
  pass
99
94
 
100
- @abstractmethod
101
- def commit(self) -> None:
102
- """Commit the context (called automatically when delegate returns)."""
103
- pass
104
-
105
95
 
106
96
  class KeyPointsDataContext(IKeyPointsDataContext):
107
97
  """Implementation of keypoints data context."""
@@ -111,6 +101,8 @@ class KeyPointsDataContext(IKeyPointsDataContext):
111
101
  frame_id: int,
112
102
  writer: IKeyPointsWriter,
113
103
  ) -> None:
104
+ from .schema import KeyPoint # noqa: F401
105
+
114
106
  self._frame_id = frame_id
115
107
  self._writer = writer
116
108
 
@@ -118,11 +110,11 @@ class KeyPointsDataContext(IKeyPointsDataContext):
118
110
  def frame_id(self) -> int:
119
111
  return self._frame_id
120
112
 
121
- def add(self, point: KeyPointDefinition, x: int, y: int, confidence: float) -> None:
113
+ def add(self, point: KeyPoint, x: int, y: int, confidence: float) -> None:
122
114
  """Add a keypoint detection for this frame."""
123
115
  self._writer.append(point.id, x, y, confidence)
124
116
 
125
- def add_point(self, point: KeyPointDefinition, position: Point, confidence: float) -> None:
117
+ def add_point(self, point: KeyPoint, position: Point, confidence: float) -> None:
126
118
  """Add a keypoint detection using a Point tuple."""
127
119
  self._writer.append_point(point.id, position, confidence)
128
120
 
@@ -139,6 +131,8 @@ class SegmentationDataContext(ISegmentationDataContext):
139
131
  frame_id: int,
140
132
  writer: SegmentationResultWriter,
141
133
  ) -> None:
134
+ from .schema import SegmentClass # noqa: F401
135
+
142
136
  self._frame_id = frame_id
143
137
  self._writer = writer
144
138
 
@@ -10,11 +10,11 @@ from __future__ import annotations
10
10
  import json
11
11
  from abc import ABC, abstractmethod
12
12
  from dataclasses import dataclass
13
- from typing import Dict, List, Any
13
+ from typing import Dict, List
14
14
 
15
15
 
16
16
  @dataclass(frozen=True)
17
- class KeyPointDefinition:
17
+ class KeyPoint:
18
18
  """
19
19
  A keypoint definition with ID and name.
20
20
 
@@ -26,7 +26,7 @@ class KeyPointDefinition:
26
26
  name: str
27
27
 
28
28
  def __str__(self) -> str:
29
- return f"KeyPointDefinition({self.id}, '{self.name}')"
29
+ return f"KeyPoint({self.id}, '{self.name}')"
30
30
 
31
31
 
32
32
  @dataclass(frozen=True)
@@ -54,7 +54,7 @@ class IKeyPointsSchema(ABC):
54
54
  """
55
55
 
56
56
  @abstractmethod
57
- def define_point(self, name: str) -> KeyPointDefinition:
57
+ def define_point(self, name: str) -> KeyPoint:
58
58
  """
59
59
  Define a new keypoint.
60
60
 
@@ -62,13 +62,13 @@ class IKeyPointsSchema(ABC):
62
62
  name: Human-readable name for the keypoint (e.g., "nose", "left_eye")
63
63
 
64
64
  Returns:
65
- KeyPointDefinition handle for use with IKeyPointsDataContext.add()
65
+ KeyPoint handle for use with IKeyPointsDataContext.add()
66
66
  """
67
67
  pass
68
68
 
69
69
  @property
70
70
  @abstractmethod
71
- def defined_points(self) -> List[KeyPointDefinition]:
71
+ def defined_points(self) -> List[KeyPoint]:
72
72
  """Get all defined keypoints."""
73
73
  pass
74
74
 
@@ -116,41 +116,34 @@ class KeyPointsSchema(IKeyPointsSchema):
116
116
  """Implementation of keypoints schema."""
117
117
 
118
118
  def __init__(self) -> None:
119
- self._points: Dict[str, KeyPointDefinition] = {}
119
+ self._points: Dict[str, KeyPoint] = {}
120
120
  self._next_id = 0
121
121
 
122
- def define_point(self, name: str) -> KeyPointDefinition:
122
+ def define_point(self, name: str) -> KeyPoint:
123
123
  """Define a new keypoint."""
124
124
  if name in self._points:
125
125
  raise ValueError(f"Keypoint '{name}' already defined")
126
126
 
127
- point = KeyPointDefinition(id=self._next_id, name=name)
127
+ point = KeyPoint(id=self._next_id, name=name)
128
128
  self._points[name] = point
129
129
  self._next_id += 1
130
130
  return point
131
131
 
132
132
  @property
133
- def defined_points(self) -> List[KeyPointDefinition]:
133
+ def defined_points(self) -> List[KeyPoint]:
134
134
  """Get all defined keypoints."""
135
135
  return list(self._points.values())
136
136
 
137
137
  def get_metadata_json(self) -> str:
138
- """
139
- Get JSON metadata for serialization.
140
-
141
- Format matches C# SDK:
142
- {
143
- "version": 1,
144
- "type": "keypoints",
145
- "points": [{"id": 0, "name": "nose"}, ...]
146
- }
147
- """
148
- metadata: Dict[str, Any] = {
149
- "version": 1,
150
- "type": "keypoints",
151
- "points": [{"id": p.id, "name": p.name} for p in self._points.values()],
152
- }
153
- return json.dumps(metadata, indent=2)
138
+ """Get JSON metadata for serialization."""
139
+ return json.dumps(
140
+ {
141
+ "version": "1.0",
142
+ "compute_module_name": "",
143
+ "points": {p.name: p.id for p in self._points.values()},
144
+ },
145
+ indent=2,
146
+ )
154
147
 
155
148
 
156
149
  class SegmentationSchema(ISegmentationSchema):
@@ -177,21 +170,11 @@ class SegmentationSchema(ISegmentationSchema):
177
170
  return list(self._classes.values())
178
171
 
179
172
  def get_metadata_json(self) -> str:
180
- """
181
- Get JSON metadata for serialization.
182
-
183
- Format matches C# SDK:
184
- {
185
- "version": 1,
186
- "type": "segmentation",
187
- "classes": [{"classId": 1, "name": "person"}, ...]
188
- }
189
- """
190
- metadata: Dict[str, Any] = {
191
- "version": 1,
192
- "type": "segmentation",
193
- "classes": [
194
- {"classId": c.class_id, "name": c.name} for c in self._classes.values()
195
- ],
196
- }
197
- return json.dumps(metadata, indent=2)
173
+ """Get JSON metadata for serialization."""
174
+ return json.dumps(
175
+ {
176
+ "version": "1.0",
177
+ "classes": {str(c.class_id): c.name for c in self._classes.values()},
178
+ },
179
+ indent=2,
180
+ )
@@ -1,204 +1,103 @@
1
1
  """
2
- Unified transport protocol as a value type.
2
+ Transport protocol types with composable + operator.
3
3
 
4
- Supports: file://, socket://, nng+push+ipc://, nng+push+tcp://, etc.
5
-
6
- Examples:
7
- file:///home/user/output.bin - absolute file path
8
- socket:///tmp/my.sock - Unix domain socket
9
- nng+push+ipc://tmp/keypoints - NNG Push over IPC
10
- nng+push+tcp://host:5555 - NNG Push over TCP
4
+ Allows building transport protocols like:
5
+ protocol = Transport.Nng + Transport.Push + Transport.Ipc
6
+ # Results in TransportProtocol("nng", "push", "ipc")
11
7
  """
12
8
 
13
9
  from __future__ import annotations
14
10
 
15
- from enum import Enum, auto
16
- from typing import ClassVar, Dict, Optional
17
-
18
-
19
- class TransportKind(Enum):
20
- """Transport kind enumeration."""
21
-
22
- FILE = auto()
23
- """File output."""
24
-
25
- SOCKET = auto()
26
- """Unix domain socket (direct, no messaging library)."""
11
+ from dataclasses import dataclass
12
+ from typing import Optional
27
13
 
28
- NNG_PUSH_IPC = auto()
29
- """NNG Push over IPC."""
30
14
 
31
- NNG_PUSH_TCP = auto()
32
- """NNG Push over TCP."""
15
+ @dataclass(frozen=True)
16
+ class MessagingLibrary:
17
+ """Messaging library (nng, zeromq, etc.)."""
33
18
 
34
- NNG_PULL_IPC = auto()
35
- """NNG Pull over IPC."""
19
+ name: str
36
20
 
37
- NNG_PULL_TCP = auto()
38
- """NNG Pull over TCP."""
21
+ def __add__(self, pattern: MessagingPattern) -> TransportBuilder:
22
+ """Compose with messaging pattern: Nng + Push."""
23
+ return TransportBuilder(library=self, pattern=pattern)
39
24
 
40
- NNG_PUB_IPC = auto()
41
- """NNG Pub over IPC."""
25
+ def __str__(self) -> str:
26
+ return self.name
42
27
 
43
- NNG_PUB_TCP = auto()
44
- """NNG Pub over TCP."""
45
28
 
46
- NNG_SUB_IPC = auto()
47
- """NNG Sub over IPC."""
29
+ @dataclass(frozen=True)
30
+ class MessagingPattern:
31
+ """Messaging pattern (push/pull, pub/sub, etc.)."""
48
32
 
49
- NNG_SUB_TCP = auto()
50
- """NNG Sub over TCP."""
33
+ name: str
51
34
 
35
+ def __str__(self) -> str:
36
+ return self.name
52
37
 
53
- class TransportProtocol:
54
- """
55
- Unified transport protocol specification as a value type.
56
-
57
- Supports: file://, socket://, nng+push+ipc://, nng+push+tcp://, etc.
58
- """
59
-
60
- # Predefined protocols
61
- File: TransportProtocol
62
- Socket: TransportProtocol
63
- NngPushIpc: TransportProtocol
64
- NngPushTcp: TransportProtocol
65
- NngPullIpc: TransportProtocol
66
- NngPullTcp: TransportProtocol
67
- NngPubIpc: TransportProtocol
68
- NngPubTcp: TransportProtocol
69
- NngSubIpc: TransportProtocol
70
- NngSubTcp: TransportProtocol
71
-
72
- _SCHEMA_MAP: ClassVar[Dict[str, TransportKind]] = {
73
- "file": TransportKind.FILE,
74
- "socket": TransportKind.SOCKET,
75
- "nng+push+ipc": TransportKind.NNG_PUSH_IPC,
76
- "nng+push+tcp": TransportKind.NNG_PUSH_TCP,
77
- "nng+pull+ipc": TransportKind.NNG_PULL_IPC,
78
- "nng+pull+tcp": TransportKind.NNG_PULL_TCP,
79
- "nng+pub+ipc": TransportKind.NNG_PUB_IPC,
80
- "nng+pub+tcp": TransportKind.NNG_PUB_TCP,
81
- "nng+sub+ipc": TransportKind.NNG_SUB_IPC,
82
- "nng+sub+tcp": TransportKind.NNG_SUB_TCP,
83
- }
84
-
85
- _KIND_TO_SCHEMA: ClassVar[Dict[TransportKind, str]] = {}
86
-
87
- def __init__(self, kind: TransportKind, schema: str) -> None:
88
- self._kind = kind
89
- self._schema = schema
90
38
 
91
- @property
92
- def kind(self) -> TransportKind:
93
- """The transport kind."""
94
- return self._kind
39
+ @dataclass(frozen=True)
40
+ class TransportLayer:
41
+ """Transport layer (ipc, tcp, etc.)."""
95
42
 
96
- @property
97
- def schema(self) -> str:
98
- """The schema string (e.g., 'file', 'socket', 'nng+push+ipc')."""
99
- return self._schema
43
+ name: str
44
+ uri_prefix: str
100
45
 
101
- # Classification properties
46
+ def __str__(self) -> str:
47
+ return self.name
102
48
 
103
- @property
104
- def is_file(self) -> bool:
105
- """True if this is a file transport."""
106
- return self._kind == TransportKind.FILE
107
49
 
108
- @property
109
- def is_socket(self) -> bool:
110
- """True if this is a Unix socket transport."""
111
- return self._kind == TransportKind.SOCKET
50
+ @dataclass(frozen=True)
51
+ class TransportBuilder:
52
+ """Builder for constructing transport protocols."""
112
53
 
113
- @property
114
- def is_nng(self) -> bool:
115
- """True if this is any NNG-based transport."""
116
- return self._kind in {
117
- TransportKind.NNG_PUSH_IPC,
118
- TransportKind.NNG_PUSH_TCP,
119
- TransportKind.NNG_PULL_IPC,
120
- TransportKind.NNG_PULL_TCP,
121
- TransportKind.NNG_PUB_IPC,
122
- TransportKind.NNG_PUB_TCP,
123
- TransportKind.NNG_SUB_IPC,
124
- TransportKind.NNG_SUB_TCP,
125
- }
54
+ library: MessagingLibrary
55
+ pattern: MessagingPattern
126
56
 
127
- @property
128
- def is_push(self) -> bool:
129
- """True if this is a Push pattern."""
130
- return self._kind in {TransportKind.NNG_PUSH_IPC, TransportKind.NNG_PUSH_TCP}
57
+ def __add__(self, layer: TransportLayer) -> TransportProtocol:
58
+ """Compose with transport layer: (Nng + Push) + Ipc."""
59
+ return TransportProtocol(library=self.library, pattern=self.pattern, layer=layer)
131
60
 
132
- @property
133
- def is_pull(self) -> bool:
134
- """True if this is a Pull pattern."""
135
- return self._kind in {TransportKind.NNG_PULL_IPC, TransportKind.NNG_PULL_TCP}
61
+ def __str__(self) -> str:
62
+ return f"{self.library}+{self.pattern}"
136
63
 
137
- @property
138
- def is_pub(self) -> bool:
139
- """True if this is a Pub pattern."""
140
- return self._kind in {TransportKind.NNG_PUB_IPC, TransportKind.NNG_PUB_TCP}
141
64
 
142
- @property
143
- def is_sub(self) -> bool:
144
- """True if this is a Sub pattern."""
145
- return self._kind in {TransportKind.NNG_SUB_IPC, TransportKind.NNG_SUB_TCP}
65
+ @dataclass(frozen=True)
66
+ class TransportProtocol:
67
+ """Complete transport protocol specification."""
146
68
 
147
- @property
148
- def is_ipc(self) -> bool:
149
- """True if this uses IPC layer."""
150
- return self._kind in {
151
- TransportKind.NNG_PUSH_IPC,
152
- TransportKind.NNG_PULL_IPC,
153
- TransportKind.NNG_PUB_IPC,
154
- TransportKind.NNG_SUB_IPC,
155
- }
69
+ library: MessagingLibrary
70
+ pattern: MessagingPattern
71
+ layer: TransportLayer
156
72
 
157
73
  @property
158
- def is_tcp(self) -> bool:
159
- """True if this uses TCP layer."""
160
- return self._kind in {
161
- TransportKind.NNG_PUSH_TCP,
162
- TransportKind.NNG_PULL_TCP,
163
- TransportKind.NNG_PUB_TCP,
164
- TransportKind.NNG_SUB_TCP,
165
- }
74
+ def protocol_string(self) -> str:
75
+ """Protocol string for parsing (e.g., 'nng+push+ipc')."""
76
+ return f"{self.library}+{self.pattern}+{self.layer}"
166
77
 
167
78
  def create_nng_address(self, path_or_host: str) -> str:
168
79
  """
169
80
  Create the NNG address from a path/host.
170
81
 
171
- For IPC: ipc:///path
172
- For TCP: tcp://host:port
173
-
174
- Raises:
175
- ValueError: If this is not an NNG protocol.
82
+ For IPC: adds leading "/" to make absolute path
83
+ For TCP: uses as-is
176
84
  """
177
- if not self.is_nng:
178
- raise ValueError(f"Cannot create NNG address for {self._kind} transport")
85
+ if self.layer == Transport.Ipc and not path_or_host.startswith("/"):
86
+ return f"{self.layer.uri_prefix}/{path_or_host}"
87
+ return f"{self.layer.uri_prefix}{path_or_host}"
179
88
 
180
- if self.is_ipc:
181
- # IPC paths need leading "/" for absolute paths
182
- if not path_or_host.startswith("/"):
183
- return f"ipc:///{path_or_host}"
184
- return f"ipc://{path_or_host}"
89
+ @property
90
+ def is_push(self) -> bool:
91
+ """Check if this is a push pattern."""
92
+ return self.pattern == Transport.Push
185
93
 
186
- # TCP
187
- return f"tcp://{path_or_host}"
94
+ @property
95
+ def is_pub(self) -> bool:
96
+ """Check if this is a pub pattern."""
97
+ return self.pattern == Transport.Pub
188
98
 
189
99
  def __str__(self) -> str:
190
- return self._schema
191
-
192
- def __repr__(self) -> str:
193
- return f"TransportProtocol({self._kind.name}, '{self._schema}')"
194
-
195
- def __eq__(self, other: object) -> bool:
196
- if isinstance(other, TransportProtocol):
197
- return self._kind == other._kind
198
- return False
199
-
200
- def __hash__(self) -> int:
201
- return hash(self._kind)
100
+ return self.protocol_string
202
101
 
203
102
  @classmethod
204
103
  def parse(cls, s: str) -> TransportProtocol:
@@ -209,30 +108,59 @@ class TransportProtocol:
209
108
  return result
210
109
 
211
110
  @classmethod
212
- def try_parse(cls, s: Optional[str]) -> Optional[TransportProtocol]:
111
+ def try_parse(cls, s: str) -> Optional[TransportProtocol]:
213
112
  """Try to parse a protocol string."""
214
113
  if not s:
215
114
  return None
216
115
 
217
- schema = s.lower().strip()
218
- kind = cls._SCHEMA_MAP.get(schema)
219
- if kind is None:
116
+ parts = s.lower().split("+")
117
+ if len(parts) != 3:
118
+ return None
119
+
120
+ # Parse library
121
+ if parts[0] == "nng":
122
+ library = Transport.Nng
123
+ else:
124
+ return None
125
+
126
+ # Parse pattern
127
+ if parts[1] == "push":
128
+ pattern = Transport.Push
129
+ elif parts[1] == "pull":
130
+ pattern = Transport.Pull
131
+ elif parts[1] == "pub":
132
+ pattern = Transport.Pub
133
+ elif parts[1] == "sub":
134
+ pattern = Transport.Sub
135
+ else:
220
136
  return None
221
137
 
222
- return cls(kind, schema)
138
+ # Parse layer
139
+ if parts[2] == "ipc":
140
+ layer = Transport.Ipc
141
+ elif parts[2] == "tcp":
142
+ layer = Transport.Tcp
143
+ else:
144
+ return None
145
+
146
+ return cls(library=library, pattern=pattern, layer=layer)
147
+
148
+
149
+ class Transport:
150
+ """Static helpers for building transport protocols using + operator."""
151
+
152
+ # Messaging libraries
153
+ Nng: MessagingLibrary = MessagingLibrary("nng")
223
154
 
155
+ # Messaging patterns
156
+ Push: MessagingPattern = MessagingPattern("push")
157
+ Pull: MessagingPattern = MessagingPattern("pull")
158
+ Pub: MessagingPattern = MessagingPattern("pub")
159
+ Sub: MessagingPattern = MessagingPattern("sub")
224
160
 
225
- # Initialize predefined protocols
226
- TransportProtocol.File = TransportProtocol(TransportKind.FILE, "file")
227
- TransportProtocol.Socket = TransportProtocol(TransportKind.SOCKET, "socket")
228
- TransportProtocol.NngPushIpc = TransportProtocol(TransportKind.NNG_PUSH_IPC, "nng+push+ipc")
229
- TransportProtocol.NngPushTcp = TransportProtocol(TransportKind.NNG_PUSH_TCP, "nng+push+tcp")
230
- TransportProtocol.NngPullIpc = TransportProtocol(TransportKind.NNG_PULL_IPC, "nng+pull+ipc")
231
- TransportProtocol.NngPullTcp = TransportProtocol(TransportKind.NNG_PULL_TCP, "nng+pull+tcp")
232
- TransportProtocol.NngPubIpc = TransportProtocol(TransportKind.NNG_PUB_IPC, "nng+pub+ipc")
233
- TransportProtocol.NngPubTcp = TransportProtocol(TransportKind.NNG_PUB_TCP, "nng+pub+tcp")
234
- TransportProtocol.NngSubIpc = TransportProtocol(TransportKind.NNG_SUB_IPC, "nng+sub+ipc")
235
- TransportProtocol.NngSubTcp = TransportProtocol(TransportKind.NNG_SUB_TCP, "nng+sub+tcp")
161
+ # Transport layers
162
+ Ipc: TransportLayer = TransportLayer("ipc", "ipc://")
163
+ Tcp: TransportLayer = TransportLayer("tcp", "tcp://")
236
164
 
237
- # Initialize reverse lookup map
238
- TransportProtocol._KIND_TO_SCHEMA = {v: k for k, v in TransportProtocol._SCHEMA_MAP.items()}
165
+ # File output (not a real transport)
166
+ File: str = "file"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rocket-welder-sdk
3
- Version: 1.1.34
3
+ Version: 1.1.34.dev7
4
4
  Summary: High-performance video streaming SDK for RocketWelder services using ZeroBuffer IPC
5
5
  Home-page: https://github.com/modelingevolution/rocket-welder-sdk
6
6
  Author: ModelingEvolution
@@ -1,8 +1,8 @@
1
1
  rocket_welder_sdk/__init__.py,sha256=pcYOCqqYuK8oJwkFRnWej4uNfEkuYWXP6jcUhPSLgrc,3041
2
2
  rocket_welder_sdk/bytes_size.py,sha256=Myl29-wyWCIYdbMmgaxXebT8Dz8_Fwcr3fnfaNW81P0,7463
3
3
  rocket_welder_sdk/connection_string.py,sha256=NIC6OiOXF-DeBFCWzgMFOWsenrSS45hY81j_HLMSpgo,9974
4
- rocket_welder_sdk/controllers.py,sha256=tPfQ2GZRfLbj-M21uAa9X3cFzrBBvqSbxTUxPiaZ5xc,32690
5
- rocket_welder_sdk/frame_metadata.py,sha256=TMLIY47cIdIlxqk9xj7I3M8FZFmZ3GcVoLZht7prjQM,3929
4
+ rocket_welder_sdk/controllers.py,sha256=WY3fgUUrlicA_ibDhvsfsF0ucD7RuoHuL-BWojAhjtQ,32692
5
+ rocket_welder_sdk/frame_metadata.py,sha256=8AbCgSQ17QeCHSzdQiSN0E1KxE5rrNpOlTYZ2em8kes,3903
6
6
  rocket_welder_sdk/gst_metadata.py,sha256=jEQvZX4BdR6OR3lqp12PV-HEXZhcxfiS010diA2CbMM,14213
7
7
  rocket_welder_sdk/keypoints_protocol.py,sha256=NKiSPrevWG4_RrD6jtFxPjwftlaPWe1CqoFVKRMwp4k,21858
8
8
  rocket_welder_sdk/opencv_controller.py,sha256=MDM6_yFBB9BaMa5jnZRqw7xZZB-WuLr7EPrrfHQ2DK4,9905
@@ -14,12 +14,11 @@ rocket_welder_sdk/session_id.py,sha256=sRhzQw90shqq_DJVtrsSggcGZ775kz7cRfbI-1LMe
14
14
  rocket_welder_sdk/external_controls/__init__.py,sha256=ldOLGhLLS5BQL8m4VKFYV0SvsNNlV2tghlc7rkqadU8,699
15
15
  rocket_welder_sdk/external_controls/contracts.py,sha256=3DU6pdpteN50gF2fsS7C2279dGjDa0tZLrLntkBa2LM,2607
16
16
  rocket_welder_sdk/external_controls/contracts_old.py,sha256=XWriuXJZu5caTSS0bcTIOZcKnj-IRCm96voA4gqLBfU,2980
17
- rocket_welder_sdk/high_level/__init__.py,sha256=C1Gan74Ce_JQZKJmXhpP3L3O11wvOClesBVaAxRZQaA,1305
18
- rocket_welder_sdk/high_level/client.py,sha256=kVtxpzP9qZgv-SYG_0glDckHapEod4MFl5NQ75TR_b4,9584
19
- rocket_welder_sdk/high_level/connection_strings.py,sha256=q1uZJQ7mt1RR-E8MJzIwG6vz3Ddruoc3pTCdTvg_pe4,10434
20
- rocket_welder_sdk/high_level/data_context.py,sha256=SXJvDpDBFi8Lm4XqSRSHK7YUUHuugXGo4ZRCb6_z5l0,4833
21
- rocket_welder_sdk/high_level/schema.py,sha256=7F5DfPACHUfOpbJE4YZJRpro9VL8tLa6Kt9ZJuY7R7w,5397
22
- rocket_welder_sdk/high_level/transport_protocol.py,sha256=EFF0bgNn9hxRMj67FwU6MVu-UiEFINSGhd2VC8agrgc,7393
17
+ rocket_welder_sdk/high_level/__init__.py,sha256=5oTCBL2qMGonCytHDckmX1b1U97-7Xb8Bg9Z70nR7fc,1759
18
+ rocket_welder_sdk/high_level/connection_strings.py,sha256=4undnkbWZ837vY-o6ybIj1827F2VlUbCJX4m3Filz-s,10469
19
+ rocket_welder_sdk/high_level/data_context.py,sha256=Pmwsl9MgBfKA9BqmBJmAVRvnaqPdjnq2rZEA_pzzYsw,4585
20
+ rocket_welder_sdk/high_level/schema.py,sha256=UlefNAV2UL9eO_Th2q19a7tf_eoaeLE92fYZ5LNq7-M,4849
21
+ rocket_welder_sdk/high_level/transport_protocol.py,sha256=lvLVHikW_MNZhXqaXlyXscT68OklyMbGs8DnDEMCtgE,4515
23
22
  rocket_welder_sdk/transport/__init__.py,sha256=DYmZpohGPU7RhS6EdVT_BwCy5MZzyTQ6Eymm8TpmxJ8,751
24
23
  rocket_welder_sdk/transport/frame_sink.py,sha256=16dUefZF1QJv62Ig0ezPR6nEho_7A3WJu4M9_PPMqJM,2164
25
24
  rocket_welder_sdk/transport/frame_source.py,sha256=G1rBAQS1AgOOdtASB0_CYon8g20hUGXpP2exCp5hlhk,2169
@@ -33,7 +32,7 @@ rocket_welder_sdk/ui/icons.py,sha256=DcDklZkPmiEzlOD4IR7VTJOtGPCuuh_OM_WN7ScghWE
33
32
  rocket_welder_sdk/ui/ui_events_projection.py,sha256=siiNhjLEBOPfTKw1ZhOPGkwIN5rLDH7V9VCZTNrhEtQ,7836
34
33
  rocket_welder_sdk/ui/ui_service.py,sha256=uRdpyJGoCQmtOli_HKSrxLwhZYG-XRuHIYdkmFz1zNk,12026
35
34
  rocket_welder_sdk/ui/value_types.py,sha256=f7OA_9zgXEDPoITc8v8SfAR23I4XeFhE3E2_GcAbR6k,1616
36
- rocket_welder_sdk-1.1.34.dist-info/METADATA,sha256=zI8JTjId1nm1yfU6y-10l4vKl2vOiJqj6uL4GZUfkVc,24847
37
- rocket_welder_sdk-1.1.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- rocket_welder_sdk-1.1.34.dist-info/top_level.txt,sha256=2iZvBjnwVCUW-uDE23-eJld5PZ9-mlPI69QiXM5IrTA,18
39
- rocket_welder_sdk-1.1.34.dist-info/RECORD,,
35
+ rocket_welder_sdk-1.1.34.dev7.dist-info/METADATA,sha256=qUlyc_-dz2TRBVvqqV6N38UvFF9Gb6r-Vw1PsqppeYw,24852
36
+ rocket_welder_sdk-1.1.34.dev7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ rocket_welder_sdk-1.1.34.dev7.dist-info/top_level.txt,sha256=2iZvBjnwVCUW-uDE23-eJld5PZ9-mlPI69QiXM5IrTA,18
38
+ rocket_welder_sdk-1.1.34.dev7.dist-info/RECORD,,
@@ -1,262 +0,0 @@
1
- """
2
- RocketWelderClient - High-level API matching C# RocketWelder.SDK.
3
-
4
- Usage:
5
- with RocketWelderClient.from_environment() as client:
6
- # Define schema
7
- nose = client.keypoints.define_point("nose")
8
- person = client.segmentation.define_class(1, "person")
9
-
10
- # Start processing
11
- client.start(process_frame)
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import logging
17
- from dataclasses import dataclass, field
18
- from typing import TYPE_CHECKING, Any, Callable, Optional
19
-
20
- import numpy as np
21
- import numpy.typing as npt
22
- from typing_extensions import TypeAlias
23
-
24
- from .connection_strings import (
25
- KeyPointsConnectionString,
26
- SegmentationConnectionString,
27
- VideoSourceConnectionString,
28
- )
29
- from .data_context import (
30
- IKeyPointsDataContext,
31
- ISegmentationDataContext,
32
- KeyPointsDataContext,
33
- SegmentationDataContext,
34
- )
35
- from .schema import (
36
- IKeyPointsSchema,
37
- ISegmentationSchema,
38
- KeyPointsSchema,
39
- SegmentationSchema,
40
- )
41
- from .transport_protocol import TransportKind
42
-
43
- if TYPE_CHECKING:
44
- from rocket_welder_sdk.keypoints_protocol import KeyPointsSink
45
- from rocket_welder_sdk.transport.frame_sink import IFrameSink
46
-
47
- # Type alias for OpenCV Mat (numpy array)
48
- Mat: TypeAlias = npt.NDArray[np.uint8]
49
-
50
- logger = logging.getLogger(__name__)
51
-
52
-
53
- @dataclass
54
- class RocketWelderClientOptions:
55
- """Configuration options for RocketWelderClient."""
56
-
57
- video_source: VideoSourceConnectionString = field(
58
- default_factory=VideoSourceConnectionString.default
59
- )
60
- keypoints: KeyPointsConnectionString = field(default_factory=KeyPointsConnectionString.default)
61
- segmentation: SegmentationConnectionString = field(
62
- default_factory=SegmentationConnectionString.default
63
- )
64
-
65
- @classmethod
66
- def from_environment(cls) -> RocketWelderClientOptions:
67
- """Create from environment variables."""
68
- return cls(
69
- video_source=VideoSourceConnectionString.from_environment(),
70
- keypoints=KeyPointsConnectionString.from_environment(),
71
- segmentation=SegmentationConnectionString.from_environment(),
72
- )
73
-
74
-
75
- class RocketWelderClient:
76
- """
77
- High-level client for RocketWelder SDK.
78
-
79
- Mirrors C# RocketWelder.SDK.IRocketWelderClient interface.
80
- """
81
-
82
- def __init__(self, options: RocketWelderClientOptions) -> None:
83
- self._options = options
84
- self._keypoints_schema = KeyPointsSchema()
85
- self._segmentation_schema = SegmentationSchema()
86
- self._keypoints_sink: Optional[KeyPointsSink] = None
87
- self._keypoints_frame_sink: Optional[IFrameSink] = None
88
- self._segmentation_frame_sink: Optional[IFrameSink] = None
89
- self._closed = False
90
- logger.debug("RocketWelderClient created with options: %s", options)
91
-
92
- @classmethod
93
- def from_environment(cls) -> RocketWelderClient:
94
- """Create client from environment variables."""
95
- logger.info("Creating RocketWelderClient from environment variables")
96
- return cls(RocketWelderClientOptions.from_environment())
97
-
98
- @classmethod
99
- def create(cls, options: Optional[RocketWelderClientOptions] = None) -> RocketWelderClient:
100
- """Create client with explicit options."""
101
- return cls(options or RocketWelderClientOptions())
102
-
103
- @property
104
- def keypoints(self) -> IKeyPointsSchema:
105
- """Schema for defining keypoints."""
106
- return self._keypoints_schema
107
-
108
- @property
109
- def segmentation(self) -> ISegmentationSchema:
110
- """Schema for defining segmentation classes."""
111
- return self._segmentation_schema
112
-
113
- def start(
114
- self,
115
- process_frame: Callable[[Mat, ISegmentationDataContext, IKeyPointsDataContext, Mat], None],
116
- ) -> None:
117
- """Start with both keypoints and segmentation."""
118
- self._run_loop(process_frame, use_keypoints=True, use_segmentation=True)
119
-
120
- def start_keypoints(
121
- self,
122
- process_frame: Callable[[Mat, IKeyPointsDataContext, Mat], None],
123
- ) -> None:
124
- """Start with keypoints only."""
125
- self._run_loop(process_frame, use_keypoints=True, use_segmentation=False)
126
-
127
- def start_segmentation(
128
- self,
129
- process_frame: Callable[[Mat, ISegmentationDataContext, Mat], None],
130
- ) -> None:
131
- """Start with segmentation only."""
132
- self._run_loop(process_frame, use_keypoints=False, use_segmentation=True)
133
-
134
- def _run_loop(
135
- self,
136
- process_frame: Callable[..., None],
137
- use_keypoints: bool,
138
- use_segmentation: bool,
139
- ) -> None:
140
- """Run processing loop."""
141
- from rocket_welder_sdk.keypoints_protocol import KeyPointsSink
142
-
143
- logger.info(
144
- "Starting processing loop (keypoints=%s, segmentation=%s)",
145
- use_keypoints,
146
- use_segmentation,
147
- )
148
-
149
- # Initialize sinks
150
- if use_keypoints:
151
- cs = self._options.keypoints
152
- logger.info("Initializing keypoints sink: %s -> %s", cs.protocol, cs.address)
153
- self._keypoints_frame_sink = self._create_frame_sink(cs.protocol, cs.address)
154
- self._keypoints_sink = KeyPointsSink(
155
- frame_sink=self._keypoints_frame_sink,
156
- master_frame_interval=cs.master_frame_interval,
157
- owns_sink=False, # We manage frame sink lifecycle in close()
158
- )
159
- logger.debug(
160
- "KeyPointsSink created with master_frame_interval=%d", cs.master_frame_interval
161
- )
162
-
163
- if use_segmentation:
164
- seg_cs = self._options.segmentation
165
- logger.info("Initializing segmentation sink: %s -> %s", seg_cs.protocol, seg_cs.address)
166
- self._segmentation_frame_sink = self._create_frame_sink(seg_cs.protocol, seg_cs.address)
167
- logger.debug("Segmentation frame sink created")
168
-
169
- # TODO: Video capture loop - for now raise NotImplementedError
170
- raise NotImplementedError(
171
- "Video capture not implemented. Use process_frame_sync() or low-level API."
172
- )
173
-
174
- def process_frame_sync(
175
- self,
176
- frame_id: int,
177
- input_frame: Mat,
178
- output_frame: Mat,
179
- width: int,
180
- height: int,
181
- ) -> tuple[Optional[IKeyPointsDataContext], Optional[ISegmentationDataContext]]:
182
- """
183
- Process a single frame synchronously.
184
-
185
- Returns (keypoints_context, segmentation_context) for the caller to use.
186
- Caller must call commit() on contexts when done.
187
- """
188
- from rocket_welder_sdk.segmentation_result import SegmentationResultWriter
189
-
190
- kp_ctx: Optional[IKeyPointsDataContext] = None
191
- seg_ctx: Optional[ISegmentationDataContext] = None
192
-
193
- if self._keypoints_sink is not None:
194
- kp_writer = self._keypoints_sink.create_writer(frame_id)
195
- kp_ctx = KeyPointsDataContext(frame_id, kp_writer)
196
-
197
- if self._segmentation_frame_sink is not None:
198
- seg_writer = SegmentationResultWriter(
199
- frame_id, width, height, frame_sink=self._segmentation_frame_sink
200
- )
201
- seg_ctx = SegmentationDataContext(frame_id, seg_writer)
202
-
203
- return kp_ctx, seg_ctx
204
-
205
- def _create_frame_sink(self, protocol: Any, address: str) -> IFrameSink:
206
- """Create frame sink from protocol."""
207
- from rocket_welder_sdk.transport import NngFrameSink
208
- from rocket_welder_sdk.transport.stream_transport import StreamFrameSink
209
- from rocket_welder_sdk.transport.unix_socket_transport import UnixSocketFrameSink
210
-
211
- from .transport_protocol import TransportProtocol
212
-
213
- if not isinstance(protocol, TransportProtocol):
214
- raise TypeError(f"Expected TransportProtocol, got {type(protocol)}")
215
-
216
- if protocol.kind == TransportKind.FILE:
217
- logger.debug("Creating file sink: %s", address)
218
- file_handle = open(address, "wb")
219
- try:
220
- return StreamFrameSink(file_handle)
221
- except Exception:
222
- file_handle.close()
223
- raise
224
- elif protocol.kind == TransportKind.SOCKET:
225
- logger.debug("Creating Unix socket sink: %s", address)
226
- return UnixSocketFrameSink.connect(address)
227
- elif protocol.kind in (TransportKind.NNG_PUSH_IPC, TransportKind.NNG_PUSH_TCP):
228
- logger.debug("Creating NNG pusher: %s", address)
229
- return NngFrameSink.create_pusher(address)
230
- elif protocol.kind in (TransportKind.NNG_PUB_IPC, TransportKind.NNG_PUB_TCP):
231
- logger.debug("Creating NNG publisher: %s", address)
232
- return NngFrameSink.create_publisher(address)
233
- else:
234
- raise ValueError(f"Unsupported protocol: {protocol}")
235
-
236
- def close(self) -> None:
237
- """Release resources."""
238
- if self._closed:
239
- return
240
-
241
- logger.info("Closing RocketWelderClient")
242
-
243
- # Close frame sinks (KeyPointsSink has owns_sink=False, so we manage lifecycle)
244
- self._keypoints_sink = None
245
- if self._keypoints_frame_sink is not None:
246
- logger.debug("Closing keypoints frame sink")
247
- self._keypoints_frame_sink.close()
248
- self._keypoints_frame_sink = None
249
-
250
- if self._segmentation_frame_sink is not None:
251
- logger.debug("Closing segmentation frame sink")
252
- self._segmentation_frame_sink.close()
253
- self._segmentation_frame_sink = None
254
-
255
- self._closed = True
256
- logger.info("RocketWelderClient closed")
257
-
258
- def __enter__(self) -> RocketWelderClient:
259
- return self
260
-
261
- def __exit__(self, *args: object) -> None:
262
- self.close()