rocket-welder-sdk 1.1.34rc1__py3-none-any.whl → 1.1.35__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 +20 -1
- rocket_welder_sdk/controllers.py +7 -3
- rocket_welder_sdk/frame_metadata.py +2 -2
- rocket_welder_sdk/high_level/__init__.py +11 -25
- rocket_welder_sdk/high_level/client.py +262 -0
- rocket_welder_sdk/high_level/connection_strings.py +56 -55
- rocket_welder_sdk/high_level/data_context.py +17 -11
- rocket_welder_sdk/high_level/schema.py +42 -27
- rocket_welder_sdk/high_level/transport_protocol.py +180 -108
- rocket_welder_sdk/rocket_welder_client.py +34 -14
- rocket_welder_sdk/session_id.py +123 -0
- rocket_welder_sdk/transport/__init__.py +0 -8
- {rocket_welder_sdk-1.1.34rc1.dist-info → rocket_welder_sdk-1.1.35.dist-info}/METADATA +1 -1
- {rocket_welder_sdk-1.1.34rc1.dist-info → rocket_welder_sdk-1.1.35.dist-info}/RECORD +16 -15
- {rocket_welder_sdk-1.1.34rc1.dist-info → rocket_welder_sdk-1.1.35.dist-info}/WHEEL +0 -0
- {rocket_welder_sdk-1.1.34rc1.dist-info → rocket_welder_sdk-1.1.35.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
20
|
+
from .schema import KeyPointDefinition, 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:
|
|
40
|
+
def add(self, point: KeyPointDefinition, 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:
|
|
45
|
+
point: KeyPointDefinition 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,17 +50,22 @@ class IKeyPointsDataContext(ABC):
|
|
|
50
50
|
pass
|
|
51
51
|
|
|
52
52
|
@abstractmethod
|
|
53
|
-
def add_point(self, point:
|
|
53
|
+
def add_point(self, point: KeyPointDefinition, position: Point, confidence: float) -> None:
|
|
54
54
|
"""
|
|
55
55
|
Add a keypoint detection using a Point tuple.
|
|
56
56
|
|
|
57
57
|
Args:
|
|
58
|
-
point:
|
|
58
|
+
point: KeyPointDefinition 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
|
+
|
|
64
69
|
|
|
65
70
|
class ISegmentationDataContext(ABC):
|
|
66
71
|
"""
|
|
@@ -92,6 +97,11 @@ class ISegmentationDataContext(ABC):
|
|
|
92
97
|
"""
|
|
93
98
|
pass
|
|
94
99
|
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def commit(self) -> None:
|
|
102
|
+
"""Commit the context (called automatically when delegate returns)."""
|
|
103
|
+
pass
|
|
104
|
+
|
|
95
105
|
|
|
96
106
|
class KeyPointsDataContext(IKeyPointsDataContext):
|
|
97
107
|
"""Implementation of keypoints data context."""
|
|
@@ -101,8 +111,6 @@ class KeyPointsDataContext(IKeyPointsDataContext):
|
|
|
101
111
|
frame_id: int,
|
|
102
112
|
writer: IKeyPointsWriter,
|
|
103
113
|
) -> None:
|
|
104
|
-
from .schema import KeyPoint # noqa: F401
|
|
105
|
-
|
|
106
114
|
self._frame_id = frame_id
|
|
107
115
|
self._writer = writer
|
|
108
116
|
|
|
@@ -110,11 +118,11 @@ class KeyPointsDataContext(IKeyPointsDataContext):
|
|
|
110
118
|
def frame_id(self) -> int:
|
|
111
119
|
return self._frame_id
|
|
112
120
|
|
|
113
|
-
def add(self, point:
|
|
121
|
+
def add(self, point: KeyPointDefinition, x: int, y: int, confidence: float) -> None:
|
|
114
122
|
"""Add a keypoint detection for this frame."""
|
|
115
123
|
self._writer.append(point.id, x, y, confidence)
|
|
116
124
|
|
|
117
|
-
def add_point(self, point:
|
|
125
|
+
def add_point(self, point: KeyPointDefinition, position: Point, confidence: float) -> None:
|
|
118
126
|
"""Add a keypoint detection using a Point tuple."""
|
|
119
127
|
self._writer.append_point(point.id, position, confidence)
|
|
120
128
|
|
|
@@ -131,8 +139,6 @@ class SegmentationDataContext(ISegmentationDataContext):
|
|
|
131
139
|
frame_id: int,
|
|
132
140
|
writer: SegmentationResultWriter,
|
|
133
141
|
) -> None:
|
|
134
|
-
from .schema import SegmentClass # noqa: F401
|
|
135
|
-
|
|
136
142
|
self._frame_id = frame_id
|
|
137
143
|
self._writer = writer
|
|
138
144
|
|
|
@@ -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
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass(frozen=True)
|
|
17
|
-
class
|
|
17
|
+
class KeyPointDefinition:
|
|
18
18
|
"""
|
|
19
19
|
A keypoint definition with ID and name.
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ class KeyPoint:
|
|
|
26
26
|
name: str
|
|
27
27
|
|
|
28
28
|
def __str__(self) -> str:
|
|
29
|
-
return f"
|
|
29
|
+
return f"KeyPointDefinition({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) ->
|
|
57
|
+
def define_point(self, name: str) -> KeyPointDefinition:
|
|
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
|
-
|
|
65
|
+
KeyPointDefinition 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[
|
|
71
|
+
def defined_points(self) -> List[KeyPointDefinition]:
|
|
72
72
|
"""Get all defined keypoints."""
|
|
73
73
|
pass
|
|
74
74
|
|
|
@@ -116,34 +116,41 @@ class KeyPointsSchema(IKeyPointsSchema):
|
|
|
116
116
|
"""Implementation of keypoints schema."""
|
|
117
117
|
|
|
118
118
|
def __init__(self) -> None:
|
|
119
|
-
self._points: Dict[str,
|
|
119
|
+
self._points: Dict[str, KeyPointDefinition] = {}
|
|
120
120
|
self._next_id = 0
|
|
121
121
|
|
|
122
|
-
def define_point(self, name: str) ->
|
|
122
|
+
def define_point(self, name: str) -> KeyPointDefinition:
|
|
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 =
|
|
127
|
+
point = KeyPointDefinition(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[
|
|
133
|
+
def defined_points(self) -> List[KeyPointDefinition]:
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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)
|
|
147
154
|
|
|
148
155
|
|
|
149
156
|
class SegmentationSchema(ISegmentationSchema):
|
|
@@ -170,11 +177,19 @@ class SegmentationSchema(ISegmentationSchema):
|
|
|
170
177
|
return list(self._classes.values())
|
|
171
178
|
|
|
172
179
|
def get_metadata_json(self) -> str:
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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": [{"classId": c.class_id, "name": c.name} for c in self._classes.values()],
|
|
194
|
+
}
|
|
195
|
+
return json.dumps(metadata, indent=2)
|
|
@@ -1,103 +1,204 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Unified transport protocol as a value type.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
7
11
|
"""
|
|
8
12
|
|
|
9
13
|
from __future__ import annotations
|
|
10
14
|
|
|
11
|
-
from
|
|
12
|
-
from typing import Optional
|
|
15
|
+
from enum import Enum, auto
|
|
16
|
+
from typing import ClassVar, Dict, Optional
|
|
13
17
|
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"""Messaging library (nng, zeromq, etc.)."""
|
|
19
|
+
class TransportKind(Enum):
|
|
20
|
+
"""Transport kind enumeration."""
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
FILE = auto()
|
|
23
|
+
"""File output."""
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return TransportBuilder(library=self, pattern=pattern)
|
|
25
|
+
SOCKET = auto()
|
|
26
|
+
"""Unix domain socket (direct, no messaging library)."""
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
NNG_PUSH_IPC = auto()
|
|
29
|
+
"""NNG Push over IPC."""
|
|
27
30
|
|
|
31
|
+
NNG_PUSH_TCP = auto()
|
|
32
|
+
"""NNG Push over TCP."""
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"""Messaging pattern (push/pull, pub/sub, etc.)."""
|
|
34
|
+
NNG_PULL_IPC = auto()
|
|
35
|
+
"""NNG Pull over IPC."""
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
NNG_PULL_TCP = auto()
|
|
38
|
+
"""NNG Pull over TCP."""
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
NNG_PUB_IPC = auto()
|
|
41
|
+
"""NNG Pub over IPC."""
|
|
37
42
|
|
|
43
|
+
NNG_PUB_TCP = auto()
|
|
44
|
+
"""NNG Pub over TCP."""
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"""Transport layer (ipc, tcp, etc.)."""
|
|
46
|
+
NNG_SUB_IPC = auto()
|
|
47
|
+
"""NNG Sub over IPC."""
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
NNG_SUB_TCP = auto()
|
|
50
|
+
"""NNG Sub over TCP."""
|
|
45
51
|
|
|
46
|
-
def __str__(self) -> str:
|
|
47
|
-
return self.name
|
|
48
52
|
|
|
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
|
+
|
|
91
|
+
@property
|
|
92
|
+
def kind(self) -> TransportKind:
|
|
93
|
+
"""The transport kind."""
|
|
94
|
+
return self._kind
|
|
49
95
|
|
|
50
|
-
@
|
|
51
|
-
|
|
52
|
-
|
|
96
|
+
@property
|
|
97
|
+
def schema(self) -> str:
|
|
98
|
+
"""The schema string (e.g., 'file', 'socket', 'nng+push+ipc')."""
|
|
99
|
+
return self._schema
|
|
53
100
|
|
|
54
|
-
|
|
55
|
-
pattern: MessagingPattern
|
|
101
|
+
# Classification properties
|
|
56
102
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
103
|
+
@property
|
|
104
|
+
def is_file(self) -> bool:
|
|
105
|
+
"""True if this is a file transport."""
|
|
106
|
+
return self._kind == TransportKind.FILE
|
|
60
107
|
|
|
61
|
-
|
|
62
|
-
|
|
108
|
+
@property
|
|
109
|
+
def is_socket(self) -> bool:
|
|
110
|
+
"""True if this is a Unix socket transport."""
|
|
111
|
+
return self._kind == TransportKind.SOCKET
|
|
63
112
|
|
|
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
|
+
}
|
|
64
126
|
|
|
65
|
-
@
|
|
66
|
-
|
|
67
|
-
|
|
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}
|
|
131
|
+
|
|
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}
|
|
136
|
+
|
|
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}
|
|
68
141
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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}
|
|
146
|
+
|
|
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
|
+
}
|
|
72
156
|
|
|
73
157
|
@property
|
|
74
|
-
def
|
|
75
|
-
"""
|
|
76
|
-
return
|
|
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
|
+
}
|
|
77
166
|
|
|
78
167
|
def create_nng_address(self, path_or_host: str) -> str:
|
|
79
168
|
"""
|
|
80
169
|
Create the NNG address from a path/host.
|
|
81
170
|
|
|
82
|
-
For IPC:
|
|
83
|
-
For TCP:
|
|
171
|
+
For IPC: ipc:///path
|
|
172
|
+
For TCP: tcp://host:port
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ValueError: If this is not an NNG protocol.
|
|
84
176
|
"""
|
|
85
|
-
if
|
|
86
|
-
|
|
87
|
-
return f"{self.layer.uri_prefix}{path_or_host}"
|
|
177
|
+
if not self.is_nng:
|
|
178
|
+
raise ValueError(f"Cannot create NNG address for {self._kind} transport")
|
|
88
179
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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}"
|
|
93
185
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"""Check if this is a pub pattern."""
|
|
97
|
-
return self.pattern == Transport.Pub
|
|
186
|
+
# TCP
|
|
187
|
+
return f"tcp://{path_or_host}"
|
|
98
188
|
|
|
99
189
|
def __str__(self) -> str:
|
|
100
|
-
return self.
|
|
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)
|
|
101
202
|
|
|
102
203
|
@classmethod
|
|
103
204
|
def parse(cls, s: str) -> TransportProtocol:
|
|
@@ -108,59 +209,30 @@ class TransportProtocol:
|
|
|
108
209
|
return result
|
|
109
210
|
|
|
110
211
|
@classmethod
|
|
111
|
-
def try_parse(cls, s: str) -> Optional[TransportProtocol]:
|
|
212
|
+
def try_parse(cls, s: Optional[str]) -> Optional[TransportProtocol]:
|
|
112
213
|
"""Try to parse a protocol string."""
|
|
113
214
|
if not s:
|
|
114
215
|
return None
|
|
115
216
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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:
|
|
217
|
+
schema = s.lower().strip()
|
|
218
|
+
kind = cls._SCHEMA_MAP.get(schema)
|
|
219
|
+
if kind is None:
|
|
136
220
|
return None
|
|
137
221
|
|
|
138
|
-
|
|
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")
|
|
222
|
+
return cls(kind, schema)
|
|
154
223
|
|
|
155
|
-
# Messaging patterns
|
|
156
|
-
Push: MessagingPattern = MessagingPattern("push")
|
|
157
|
-
Pull: MessagingPattern = MessagingPattern("pull")
|
|
158
|
-
Pub: MessagingPattern = MessagingPattern("pub")
|
|
159
|
-
Sub: MessagingPattern = MessagingPattern("sub")
|
|
160
224
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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")
|
|
164
236
|
|
|
165
|
-
|
|
166
|
-
|
|
237
|
+
# Initialize reverse lookup map
|
|
238
|
+
TransportProtocol._KIND_TO_SCHEMA = {v: k for k, v in TransportProtocol._SCHEMA_MAP.items()}
|
|
@@ -16,7 +16,11 @@ from .connection_string import ConnectionMode, ConnectionString, Protocol
|
|
|
16
16
|
from .controllers import DuplexShmController, IController, OneWayShmController
|
|
17
17
|
from .frame_metadata import FrameMetadata # noqa: TC001 - used at runtime in callbacks
|
|
18
18
|
from .opencv_controller import OpenCvController
|
|
19
|
-
from .session_id import
|
|
19
|
+
from .session_id import (
|
|
20
|
+
get_configured_nng_urls,
|
|
21
|
+
get_nng_urls_from_env,
|
|
22
|
+
has_explicit_nng_urls,
|
|
23
|
+
)
|
|
20
24
|
from .transport.nng_transport import NngFrameSink
|
|
21
25
|
|
|
22
26
|
if TYPE_CHECKING:
|
|
@@ -90,27 +94,33 @@ class RocketWelderClient:
|
|
|
90
94
|
"""
|
|
91
95
|
return self._nng_publishers
|
|
92
96
|
|
|
93
|
-
def _create_nng_publishers(self
|
|
97
|
+
def _create_nng_publishers(self) -> None:
|
|
94
98
|
"""Create NNG publishers for result streaming.
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
URLs are read from environment variables (preferred) or derived from SessionId (fallback).
|
|
101
|
+
|
|
102
|
+
Priority:
|
|
103
|
+
1. Explicit URLs: SEGMENTATION_SINK_URL, KEYPOINTS_SINK_URL, ACTIONS_SINK_URL
|
|
104
|
+
2. Derived from SessionId environment variable (backwards compatibility)
|
|
98
105
|
"""
|
|
99
106
|
try:
|
|
100
|
-
urls =
|
|
107
|
+
urls = get_configured_nng_urls()
|
|
101
108
|
|
|
102
109
|
for name, url in urls.items():
|
|
103
110
|
sink = NngFrameSink.create_publisher(url)
|
|
104
111
|
self._nng_publishers[name] = sink
|
|
105
112
|
logger.info("NNG publisher ready: %s at %s", name, url)
|
|
106
113
|
|
|
114
|
+
# Log configuration summary
|
|
107
115
|
logger.info(
|
|
108
|
-
"NNG publishers
|
|
109
|
-
|
|
110
|
-
urls
|
|
111
|
-
urls
|
|
112
|
-
urls["actions"],
|
|
116
|
+
"NNG publishers configured: seg=%s, kp=%s, actions=%s",
|
|
117
|
+
urls.get("segmentation", "(not configured)"),
|
|
118
|
+
urls.get("keypoints", "(not configured)"),
|
|
119
|
+
urls.get("actions", "(not configured)"),
|
|
113
120
|
)
|
|
121
|
+
except ValueError as ex:
|
|
122
|
+
# No URLs configured - this is expected for containers that don't publish results
|
|
123
|
+
logger.debug("NNG publishers not configured: %s", ex)
|
|
114
124
|
except Exception as ex:
|
|
115
125
|
logger.warning("Failed to create NNG publishers: %s", ex)
|
|
116
126
|
# Don't fail start() - NNG is optional for backwards compatibility
|
|
@@ -162,10 +172,20 @@ class RocketWelderClient:
|
|
|
162
172
|
else:
|
|
163
173
|
raise ValueError(f"Unsupported protocol: {self._connection.protocol}")
|
|
164
174
|
|
|
165
|
-
# Auto-create NNG publishers if
|
|
166
|
-
|
|
167
|
-
if
|
|
168
|
-
self._create_nng_publishers(
|
|
175
|
+
# Auto-create NNG publishers if URLs are configured
|
|
176
|
+
# (explicit URLs via SEGMENTATION_SINK_URL etc., or derived from SessionId)
|
|
177
|
+
if has_explicit_nng_urls():
|
|
178
|
+
self._create_nng_publishers()
|
|
179
|
+
else:
|
|
180
|
+
# Log that NNG is not configured (informational)
|
|
181
|
+
urls = get_nng_urls_from_env()
|
|
182
|
+
logger.info(
|
|
183
|
+
"NNG sink URLs not configured (this is normal if not publishing AI results). "
|
|
184
|
+
"seg=%s, kp=%s, actions=%s",
|
|
185
|
+
urls.get("segmentation") or "(not set)",
|
|
186
|
+
urls.get("keypoints") or "(not set)",
|
|
187
|
+
urls.get("actions") or "(not set)",
|
|
188
|
+
)
|
|
169
189
|
|
|
170
190
|
# If preview is enabled, wrap the callback to capture frames
|
|
171
191
|
if self._preview_enabled:
|